以下主要介绍 k8s 中的 deployment ,deployment 是 k8s 中最常用的资源类型之一,用于管理 pod 的生命周期。
改造之前的 pod
之前我们使用 pod 来管理应用,但是 pod 有如下缺点:
- pod 无法实现滚动升级
- pod 无法实现回滚
- pod 无法实现自动伸缩
- pod 无法实现自动恢复
- pod 无法实现自动发布
- pod 无法实现自动扩容
- pod 无法实现自动缩容
- pod 无法实现自动重启
为了解决这些问题,我们需要使用 deployment 来管理应用。
创建 deployment 文件
首先创建数据的文件,将其改造为 deployment 方式 deploy-mysql.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
hostNetwork: true
volumes:
- name: mysql-data
hostPath:
path: /opt/mysql/data
nodeSelector:
component: mysql
containers:
- name: mysql
image: mariadb:11.1
args:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- containerPort: 3306
env:
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: myblog-secret
key: MYSQL_USER
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: myblog-secret
key: MYSQL_PASSWD
- name: MYSQL_DATABASE
valueFrom:
configMapKeyRef:
name: myblog
key: MYSQL_DATABASE
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
之后创建业务应用的 deployment 文件 deploy-myblog.yaml,为了更好的实验,我把 blog 的应用换成 wordpress 了.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myblog
namespace: test
spec:
replicas: 1
selector:
matchLabels:
app: myblog
template:
metadata:
labels:
app: myblog
spec:
containers:
- name: myblog
image: wordpress:php8.2-alpine
imagePullPolicy: IfNotPresent
env:
- name: WORDPRESS_DB_HOST
valueFrom:
configMapKeyRef:
name: myblog
key: MYSQL_HOST
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
name: myblog-secret
key: MYSQL_USER
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: myblog-secret
key: MYSQL_PASSWD
- name: WORDPRESS_DB_NAME
valueFrom:
configMapKeyRef:
name: myblog
key: MYSQL_DATABASE
ports:
- containerPort: 80
创建 deployment
kubectl apply -f deploy-mysql.yaml kubectl apply -f deploy-myblog.yaml
查看 deployment
此时可以通过 kubectl get deploy -n test 查看 deployment 的状态:
$ kubectl get deploy -n test
NAME READY UP-TO-DATE AVAILABLE AGE
mysql 1/1 1 1 2m
myblog 1/1 1 1 2m
NAME列出了集群中 Deployments 的名称。READY显示当前正在运行的副本数/期望的副本数。UP-TO-DATE显示已更新以实现期望状态的副本数。AVAILABLE显示应用程序可供用户使用的副本数。AGE显示应用程序运行的时间量。
查看 pod
$ kubectl get rs -n test
NAME DESIRED CURRENT READY AGE
mysql-7b4f8f8d5f 1 1 1 2m
myblog-7c96c9f76b 1 1 1 2m
查看 deployment 详细信息
$ kubectl describe deploy mysql -n test
Name: mysql
Namespace: test
CreationTimestamp: Fri, 03 Sep 2021 23:59:15 +0800
Labels: <none>
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=mysql
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=mysql
Containers:
mysql:
Image: mariadb:11.1
Port: 3306/TCP
Host Port: 0/TCP
Args:
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
Environment:
MYSQL_USER: <set to the key 'MYSQL_USER' in secret 'myblog-secret'> Optional: false
MYSQL_ROOT_PASSWORD: <set to the key 'MYSQL_PASSWD' in secret 'myblog-secret'> Optional: false
MYSQL_DATABASE: <set to the key 'MYSQL_DATABASE' of config map 'myblog'> Optional: false
Mounts:
/var/lib/mysql from mysql-data (rw)
Volumes:
mysql-data:
Type: HostPath (bare host directory volume)
Path: /opt/mysql/data
HostPathType:
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: mysql-7f97cb6cc9 (1/1 replicas created)
Events: <none>
副本保障机制
controller实时检测pod状态,并保障副本数一直处于期望的值。
# 删除pod,观察pod状态变化
$ kubectl -n test delete pod myblog-7c96c9f76b-qbbg7
# 观察pod
$ kubectl get pods -o wide -n test
# 设置两个副本, 或者通过 kubectl -n test edit deploy myblog 的方式,最好通过修改文件,然后 apply 的方式,这样 yaml 文件可以保持同步
$ kubectl -n test scale deploy myblog --replicas=2
deployment.extensions/myblog scaled
# 观察pod
$ kubectl get pods -o wide -n test
NAME READY STATUS RESTARTS AGE
mysql-7b4f8f8d5f-4j4q4 1/1 Running 0 3m18s
myblog-7c96c9f76b-qbbg7 1/1 Running 0 11m
myblog-7c96c9f76b-s6brm 1/1 Running 0 3m18s
Pod驱逐策略
K8S 有个特色功能叫 pod eviction,它在某些场景下如节点 NotReady,或者资源不足时,把 pod 驱逐至其它节点,这也是出于业务保护的角度去考虑的。
- Kube-controller-manager: 周期性检查所有节点状态,当节点处于 NotReady 状态超过一段时间后,驱逐该节点上所有 pod。
pod-eviction-timeout:NotReady状态节点超过该时间后,执行驱逐,默认 5 min,适用于k8s 1.13版本之前- 1.13版本后,集群开启
TaintBasedEvictions与TaintNodesByCondition功能,即taint-based-evictions,即节点若失联或者出现各种异常情况,k8s会自动为node打上污点,同时为pod默认添加如下容忍设置:
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
即各pod可以独立设置驱逐容忍时间。
2. Kubelet: 周期性检查本节点资源,当资源不足时,按照优先级驱逐部分 pod
memory.available:节点可用内存nodefs.available:节点根盘可用存储空间nodefs.inodesFree:节点inodes可用数量imagefs.available:镜像存储盘的可用空间imagefs.inodesFree:镜像存储盘的inodes可用数量
服务更新方法
修改服务,重新打tag模拟服务更新至下一个版本。
更新方式:
- 修改yaml文件,使用
kubectl -n test apply -f deploy-myblog.yaml来应用更新 kubectl -n test edit deploy myblog在线更新kubectl -n test set image deploy myblog myblog=wordpress:php8.2 --record
更新策略
...
spec:
replicas: 2
selector:
matchLabels:
app: myblog
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
#指定更新方式为滚动更新,默认策略,通过get deploy yaml查看
type: RollingUpdate
...

策略控制:
- maxSurge:最大激增数, 指更新过程中, 最多可以比replicas预先设定值多出的pod数量, 可以为固定值或百分比,默认为desired Pods数的25%。计算时向上取整(比如3.4,取4),更新过程中最多会有replicas + maxSurge个pod
- maxUnavailable: 指更新过程中, 最多有几个pod处于无法服务状态 , 可以为固定值或百分比,默认为desired Pods数的25%。计算时向下取整(比如3.6,取3)
在Deployment rollout时,需要保证Available(Ready) Pods数不低于 desired pods number - maxUnavailable; 保证所有的非异常状态Pods数不多于 desired pods number + maxSurge。
以myblog为例,使用默认的策略,更新过程:
- maxSurge 25%,2个实例,向上取整,则maxSurge为1,意味着最多可以有2+1=3个Pod,那么此时会新创建1个ReplicaSet,RS-new,把副本数置为1,此时呢,副本控制器就去创建这个新的Pod
- 同时,maxUnavailable是25%,副本数2*25%,向下取整,则为0,意味着,滚动更新的过程中,不能有少于2个可用的Pod,因此,旧的Replica(RS-old)会先保持不动,等RS-new管理的Pod状态Ready后,此时已经有3个Ready状态的Pod了,那么由于只要保证有2个可用的Pod即可,因此,RS-old的副本数会有2个变成1个,此时,会删掉一个旧的Pod
- 删掉旧的Pod的时候,由于总的Pod数量又变成2个了,因此,距离最大的3个还有1个Pod可以创建,所以,RS-new把管理的副本数由1改成2,此时又会创建1个新的Pod,等RS-new管理了2个Pod都ready后,那么就可以把RS-old的副本数由1置为0了,这样就完成了滚动更新
#查看滚动更新事件
$ kubectl -n test describe deploy myblog
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 11s deployment-controller Scaled up replica set myblog-6cf56fc848 to 1
Normal ScalingReplicaSet 11s deployment-controller Scaled down replica set myblog-6fdcf98f9 to 1
Normal ScalingReplicaSet 11s deployment-controller Scaled up replica set myblog-6cf56fc848 to 2
Normal ScalingReplicaSet 6s deployment-controller Scaled down replica set myblog-6fdcf98f9 to 0
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
myblog-6cf56fc848 2 2 2 16h
myblog-6fdcf98f9 0 0 0 16h
除了滚动更新以外,还有一种策略是Recreate,直接在当前的pod基础上先删后建:
...
strategy:
type: Recreate
...
我们的mysql服务应该使用Recreate来管理:
$ kubectl -n test edit deploy mysql
...
selector:
matchLabels:
app: mysql
strategy:
type: Recreate
template:
metadata:
creationTimestamp: null
labels:
app: mysql
...
服务回滚
通过滚动升级的策略可以平滑的升级Deployment,若升级出现问题,需要最快且最好的方式回退到上一次能够提供正常工作的版本。为此K8S提供了回滚机制。
revision:更新应用时,K8S都会记录当前的版本号,即为revision,当升级出现问题时,可通过回滚到某个特定的revision,默认配置下,K8S只会保留最近的几个revision,可以通过Deployment配置文件中的spec.revisionHistoryLimit属性增加revision数量,默认是10。
查看当前:
$ kubectl -n test rollout history deploy myblog ##CHANGE-CAUSE为空
$ kubectl delete -f deploy-myblog.yaml ## 方便演示到具体效果,删掉已有deployment
记录回滚:
$ kubectl apply -f deploy-myblog.yaml --record
$ kubectl -n test set image deploy myblog myblog=wordpress:php8.2 --record=true
查看deployment更新历史:
$ kubectl -n test rollout history deploy myblog
deployment.extensions/myblog
REVISION CHANGE-CAUSE
1 kubectl create --filename=deploy-myblog.yaml --record=true
2 kubectl set image deploy myblog myblog=wordpress:php8.2-apache --record=true
回滚到具体的REVISION:
$ kubectl -n test rollout undo deploy myblog --to-revision=1
deployment.extensions/myblog rolled back
# 访问应用测试
curl 10.244.1.150:8002/