因为Pod生命周期是短暂的,一旦运行完成则立即回收,且涉及Pod的创建、自愈、删除等操作比较复杂,所以很少在Kubernetes中直接使用Pod。而是使用更高级的称为Controller(控制器)的抽象层,来完成对Pod的创建、调度及全生命周期的自动控制任务。所以从这个角度上来说,Controller可以看成是基于场景的对Pod运维的最佳实践。
这里首先介绍下Deployment,Deployment自动化的完成了Pod副本的部署、扩容和缩容、升级和回滚等运维能力。
Deployment概述
Deployment自动化的完成了Pod副本的部署、升级和回滚、扩容和缩容等运维能力。开发者只需在 Deployment声明文件中描述期望状态,然后 Deployment Controller 就会自动将 Pod 从实际状态改变到期望状态。更多 Controller的介绍可以参考官网。Deployment提供了运行Pod的能力,并且为Pod提供副本管理、升级和回滚、水平扩容和缩容等功能,一般用于运行无状态的应用。
副本管理
最初Pod副本管理(副本数量始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的Pod来替代;如果副本数量不足,则会自动创建Pod)是由Replication Controller负责,这个控制器独立于所控制的Pod,并通过标签选择器控制目标Pod实例的创建和销毁。随着Kubernetes的发展,Replication Controller也出现了新的继任者——ReplicaSet,ReplicaSet进一步增强了标签选择器的灵活性。相比Replication Controller的标签选择器只能选择一个标签,ReplicaSet拥有集合式的标签选择器,可以选择多个Pod标签。而Deployment则是通过控制ReplicaSet实现对Pod的管理。相比ReplicaSet只能负责副本控制,Deployment还提供了升级和回滚、扩容和缩容等能力。
ReplicaSet使用示例如下:
apiVersion: v1
kind: ReplicaSet # 指定资源类型
metadata:name: demo-replica-set
spec:replicas: 3 # 指定pod副本数量selector: # 可以不指定标签选择器内容,因为Kubernetes会自动更新该字段,显式声明的目的是为了方便理解matchLabels:tier: backendmatchExpressions:- {key: tier, operator: In, values: [backend]}template:metadata:labels:app: demo-podtier: backendspec:containers:- name: custom-iamge-001image: custom-image-name
在上述示例中,ReplicaSet通过spec.template来声明PodTemplate,而PodTemplate遵循Pod的Schema规范。也就是说,ReplicaSet会通过spec.template来创建Pod,并根据spec.replicas来控制Pod的数量,同时通过spec.selector来关联对应的Pod。
尽管ReplicaSet可以独立使用,但一般还是建议使用 Deployment 来自动管理 ReplicaSet。这主要有以下几方面的考虑:
(1) Deployment是ReplicaSet的增强。相比ReplicaSet,Deployment除了通过管理RelicaSet获得了副本管理的能力外,还支持升级和回滚、水平扩容和缩容等功能。
(2) 无需担心跟其他机制的不兼容问题。在创建 Deployment 资源对象之后,Deployment Controller 也默默创建了对应的ReplicaSet。对ReplicaSet或Replication Controller的兼容性问题,可以通过Deployment来实现透明管理。
Deployment、ReplicaSet、Pod之间是一种"层层控制"的关系,其拓扑图如下:
从上图可知,Deployment控制器实际管理的是ReplicaSet对象,而ReplicaSet管理的是Pod对象。这样,Deployment通过管理ReplicaSet,间接实现了对Pod对象的管理。使用示例如下:
apiVersion: v1
kind: Deployment # 指定资源类型
metadata:name: demo-deployment
spec:selector:matchLabels:app: demo-appreplicas: 2 # 告知Deployment Controller运行2个与该模板匹配的 Podtemplate:metadata:labels:app: demo-app # 定义Pod的labelspec:containers:- name: demo-app # 容器名称image: demo-app:1.0.0 # 容器镜像名称及版本ports:- containerPort: 80 # 声明的端口
在上述示例中,Deployment对象通过指定spec.replicas来指定需要运行的Pod对象的个数。然后ReplicaSet负责保证系统中Pod的个数等于指定个数。
水平扩容和缩容
在生产环境中,经常会遇到某个服务需要扩容的场景,也可能会遇到由于资源紧张或者工作负载降低而需要减少服务实例数量的场景。此时可以利用Deployment的水平扩容和缩容的能力来完成这些工作。
Kubernetes对Pod的扩缩容操作提供了手动和自动两种模式,手动模式通过运行kubectl scale命令或通过修改Deployment中Pod副本数量的方式实现。自动模式则需要用户根据某个性能指标或者自定义业务指标,并指定Pod副本数量的范围,系统将自动在这个范围内根据性能指标的变化进行调整。自动扩缩模式可以通过Horizontal Pod Autoscaler(HPA) 控制器实现,有兴趣的同学可以自行学习。这里重点介绍下手动模式。
使用kubectl scale deployment命令指定Pod副本的数量,可以实现Pod的水平扩容或缩容。示例如下:
kubectl scale deployment demo-deployment --replicas 10
上述命令将demo-deployment管理的Pod扩容到10个(之前定义的是3个)。
也可以通过
$ kubectl edit deployment/demo-deployment
deployment "demo-deployment" edited
执行kubectl edit后,会自动打开一个文本编辑器(Linux下默认是vim),通过修改spec.replicas来实现Pod的水平扩容或缩容。
水平扩展/收缩的实现原理非常简单,Deployment Controller只需要修改它所控制的ReplicaSet的Pod副本个数就可以了。比如,把这个值从3改成 10,那么Deployment所对应的RepllcaSet Controller就会根据修改后的值自动创建—个新Pod。反之,就是水平缩容。
升级和回滚
在生产环境中,需要对服务进行不中断升级(如增加页面特性或修复已知缺陷等)。此外,如果在升级的过程中出现意外,就需要快速的回退到之前的版本。以上两个场景的运维能力可以通过Deployment的升级和回滚能力实现。
升级
当集群中的某个服务需要升级时,需要停止目前与该服务相关的所有Pod,然后下载新版本镜像并创建新的Pod。如果集群规模比较大,则这个工作变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用。Kubernetes提供了滚动升级功能来解决上述问题。
如果Pod是通过Deployment创建的,则用户可以在运行时修改Deployment的Pod定义(spec.template)或镜像名称,并应用到 Deployment对象上,系统即可完成Deployment的rollout动作,rollout可被视为Deployment的自动更新或者自动部署动作。
这里将正在运行同一个应用的多个Pod的版本交替地逐一升级的过程称之为滚动更新。可以通过更新Deployment的Pod模板或容器镜像的名称来触发滚动更新。滚动更新示意图如下:
可以看到,滚动升级的过程就是旧ReplicaSet中管理Pod减少,以及新ReplicaSet中管理Pod增加的过程。
可以使用kubectl rollout status命令查看Deployment的更新过程:
$ kubectl rollout status deployment/demo-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "demo-deployment" successfully rolled out
Deployment的控制器实际上控制的是ReplicaSet的数目,以及每个ReplicaSet的属性。一个应用的版本对应的正是一个ReplicaSet。这个版本应用的Pod数量则由ReplicaSet通过ReplicaSet Controller来保证。这样就实现了应用版本和ReplicaSet一一对应的设计。
回滚
如果在Deployment升级过程中出现意外,比如写错新镜像的名称、新镜像还没被放入镜像仓库里、新镜像的配置文件发生不兼容性改变、新镜像的启动参数不对,以及因可能更复杂的依赖关系而导致升级失败等,就需要回退到之前的旧版本,这时就可以用到Deployment的回滚功能了。
只需要执行kubectl rollout undo命令,就能把整个Deployment回滚到上一个版本。
$ kubectl rollout undo deployment/demo-deployment
deployment "demo-deployment" rolled back
如果想回滚到更早之前的版本,可以先使用kubectl rollout history命令查看每次Deployment变更对应的版本。在创建Deployment时指定 --record参数,就可以创建这些版本时执行的kubectl命令被记录下来。这个操作的输出示例如下所示:
$ kubectl rollout history deployment/demo-deployment
deployments "demo-deployment":
REVISION CHANGE-CAUSE
1 kubectl create -f https://kubernetes.io/docs/demo-deployment.yaml --record
2 kubectl set image deployment/demo-deployment demo-app=demo-app:1.0.0
3 kubectl set image deployment/demo-deployment demo-app=demo-app:0.0.1
然后,就可以在kubectl rollout undo命令行最后加上目标版本号,来回滚到指定版本了。这个指令的用法如下:
$ kubectl rollout undo deployment/demo-deployment --to-revision=2
deployment "demo-deployment" rolled back
这样,Deployment Controller还会按照滚动更新的方式完成对Deployment的降级操作。
暂停与恢复
如果对Deployment进行的每—次更新操作都会生成一个新的ReplicaSet对象,这可能会带来一定程度的资源浪费。因为可能存在需要对Deployment多次更新的操作,但是期望只生成一个ReplicaSet的情况。针对这种情况,可以使用Deployment的暂停与恢复功能。
首先,在更新Deployment前先执行一次kubectl rollout pause命令。用法 如下:
$ kubectl rollout pause deployment/demo-deployment
deployment "demo-deployment" paused
kubectl rollout pause命令的作用是让指定的Deployment进入暂停状态。接下来就可以使用kubectl edit或者kubectl set image等命令来修改这个Deployment了。注意,此时修改Deployment不会触发新ReplicaSet的生成。
等到对Deployment的修改操作都完成后,只需再执行kubectl rollout resume命令,就可以把这个Deployment恢复。
$ kubectl rollout resume deploy demo-deployment
deployment "demo-deployment" resumed
这样,对Deployment进行的所有修改最后都只会触发一次滚动更新。
需要说明的是,尽管使用Deployment的暂停和恢复功能可以一定程度减少ReplicaSet的生成,但是随着修改次数的增加,ReplicaSet的数量还是会线性的增长。为了控制ReplicaSet的数量,可以通过Deployment对象的spec.revisionHistoryLimit字段限制Deployment保留的"历史版本"个数。不建议将其值设置为0,因为这会导致无法再进行回滚操作。
参考
《Kubernetes权威指南 从Docker到Kubernetes实践全接触》 龚正 吴治辉 闫健勇 编著
《深入剖析Kubernetes》 张磊 著
https://lib.jimmysong.io/kubernetes-handbook/controllers/deployment/ Deployment
https://kubernetes.io/docs/concepts/architecture/controller/ Controllers
https://lib.jimmysong.io/kubernetes-handbook/controllers/replicaset/ ReplicationController 和 ReplicaSet
https://kubernetes.io/zh-cn/docs/tasks/run-application/run-stateless-application-deployment/ 使用 Deployment 运行一个无状态应用