理解最基础的控制器模式-Deployment
Pod 是K8S的最小调度单元,但是单单使用pod与现实中的应用的部署需求相差甚远,譬如定义水平扩展、应用的滚动更新等,这都不是单Pod能实现的,而Kubernetes 的核心是编排,因此这就需要控制器模式,其中Deployment就是最基本的一个控制器。Pod 这个看似复杂的 API 对象,实际上就是对容器的进一步抽象和封装而已,而Deployment是对Pod的一种API对象的调用和封装。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
这是一个标准的deployment的yaml文件,创建一个名字为nginx-deployment的控制器模式,该控制器调用pod摸板,创建名字为nginx的nginx-1.7.9的容器,创建几个呢?控制器定义中表明了replicas=2,所以默认要创建2个。
通过deployment可以操作pod的扩展,比如,增加 Pod,删除已有的 Pod,或者更新 Pod 的某个字段。这也是 Kubernetes 项目“面向 API 对象编程”的一个直观体现。其实,像 Deployment 这种控制器的设计原理,就是我们前面提到过的,“用一种对象管理另一种对象”的“艺术”。其中,这个控制器对象本身,负责定义被管理对象的期望状态。比如,Deployment 里的 replicas=2 这个字段。而被控制对象的定义,则来自于一个“模板”。比如,Deployment 里的 template 字段。可以看到,Deployment 这个 template 字段里的内容,跟一个标准的 Pod 对象的 API 定义,丝毫不差。而所有被这个 Deployment 管理的 Pod 实例,其实都是根据这个 template 字段的内容创建出来的。像 Deployment 定义的 template 字段,在 Kubernetes 项目中有一个专有的名字,叫作 PodTemplate(Pod 模板)。这个概念非常重要,因为后面我要讲解到的大多数控制器,都会使用 PodTemplate 来统一定义它所要管理的 Pod。更有意思的是,我们还会看到其他类型的对象模板,比如 Volume 的模板。
最后,对于我们这个 nginx-deployment 来说,它创建出来的 Pod 的 ownerReference 就是 nginx-deployment 吗?或者说,nginx-deployment 所直接控制的,就是 Pod 对象么?这个问题的答案就是:ReplicaSet。一个 ReplicaSet 对象,其实就是由副本数目的定义和一个 Pod 模板组成的。不难发现,它的定义其实是 Deployment 的一个子集。更重要的是,Deployment 控制器实际操纵的,正是这样的 ReplicaSet 对象,而不是 Pod 对象。因此可以理解,deployment通过了中间一层ReplicaSet控制了pod的状态(增删扩展等),这个巧妙的设计,就为容器的版本控制提供了便利,譬如使用一个deployment创建出应用v1.0的定义,此时通过ReplicaSet-v1.0生成对应的pod;如果修改了deployment-v2.0,那么对应ReplicaSet-v2.0,这样就保证了一份deployment的yaml(deployment的名字也为改变,保持一致)文件,你随时修改。通过中间隐藏这一层 ReplicaSet 对象,Kubernetes 项目就实现了对多个“应用版本”的描述。

如上所示,Deployment 的控制器,实际上控制的是 ReplicaSet 的数目,以及每个 ReplicaSet 的属性。而一个应用的版本,对应的正是一个 ReplicaSet;这个版本应用的 Pod 数量,则由 ReplicaSet 通过它自己的控制器(ReplicaSet Controller)来保证。通过这样的多个 ReplicaSet 对象,Kubernetes 项目就实现了对多个“应用版本”的描述。
控制器模式下的水平扩展
接下来举例,把一个replicas=2,扩展到3个pod,实现控制器模式下的pod水平扩展(不改变应用的版本)。首先运行如下命令,可以看到nginx-deployment的控制器产生了2个nginx-deployment-xxx为名字的pod。
root@k8s-master:~# kubectl create -f nginx-deployment.yaml --record
很重要,此处要加上record参数之后,能记录每次变更的信息。
我们此时查看pod,可以看到两个以nginx-deployment-xxx的pod成功创建,同时查看nginx-deployment这个控制器的状态,表示2/2的容器创建已经成功了。
root@k8s-master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 2/2 Running 8 (22h ago) 10d 10.10.1.12 k8s-worker01 <none> <none>
nginx-deployment-5d59d67564-j94lx 1/1 Running 0 43s 10.10.2.30 k8s-worker02 <none> <none>
nginx-deployment-5d59d67564-szmss 1/1 Running 0 43s 10.10.2.29 k8s-worker02 <none> <none>
root@k8s-master:~# kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 2/2 2 2 16s nginx nginx:1.7.9 app=nginx
至此,可以很清楚理解到deployment按照它所定义的pod template和replicas字段,成功调度在worker节点上产生对应的pod,并且状态也为available。此时自然联想到,如果需要水平扩展pod怎么办?譬如要扩展replicas=3,常用的有两种方法,一种方法是使用kubectl edit这个命令集编辑nginx-deployment的yaml文件。
root@k8s-master:~# kubectl edit deployment/nginx-deployment
这样就打开了yaml文件,把replicas该为3,马上pod就变为4了。
另外一种方法是运行kubectl scale命令(CKA考试题啊):
root@k8s-master:~# kubectl scale deployment nginx-deployment --replicas=3
deployment.apps/nginx-deployment scaled
# 手快的话,可以看到pod在创建,但一瞬间就显示pod创建完毕了。
root@k8s-master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 2/2 Running 8 (22h ago) 10d 10.10.1.12 k8s-worker01 <none> <none>
nginx-deployment-5d59d67564-b5kh5 0/1 ContainerCreating 0 2s <none> k8s-worker01 <none> <none>
nginx-deployment-5d59d67564-j94lx 1/1 Running 0 6m5s 10.10.2.30 k8s-worker02 <none> <none>
nginx-deployment-5d59d67564-szmss 1/1 Running 0 6m5s 10.10.2.29 k8s-worker02 <none> <none>
# pod成功创建,可以清楚看到产生了3个nginx pod。
root@k8s-master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 2/2 Running 8 (22h ago) 10d 10.10.1.12 k8s-worker01 <none> <none>
nginx-deployment-5d59d67564-b5kh5 1/1 Running 0 11s 10.10.1.21 k8s-worker01 <none> <none>
nginx-deployment-5d59d67564-j94lx 1/1 Running 0 6m14s 10.10.2.30 k8s-worker02 <none> <none>
nginx-deployment-5d59d67564-szmss 1/1 Running 0 6m14s 10.10.2.29 k8s-worker02 <none> <none>
此时,可以清楚看到这个deployment是部署了一个版本的nginx,版本没有变化,只是为了处理能力提高,扩展了nginx的instance的个数,从2变为3。因此这时候前面提到的ReplicaSet还是等于1个。
root@k8s-master:~# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
nginx-deployment-5d59d67564 3 3 3 7m55s nginx nginx:1.7.9 app=nginx,pod-template-hash=5d59d67564
# 此处pod-template的hash,表示pod表示包括hash number,与其他pod做区分。
root@k8s-master:~# kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 3/3 3 3 8m5s nginx nginx:1.7.9 app=nginx
此时这个deployment已经成功运行,它的子集有一个replicaset,也就是对应一个应用的版本。
控制器模式下的滚动更新
理解了上述根据deployment配置文件实现同版本的应用的水平扩展后,自然联想到,如果我的应用需要更新版本,那该怎么做呢?此时我们假设要升级nginx到1.9.1,那么就需要编辑yaml文件了或者用kubectl set命令行来完成。
root@k8s-master:~# kubectl edit deployment nginx-deployment
deployment.apps/nginx-deployment edited
......
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:latest
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
resources: {}
......
# 如果过程有错误,删除deployment
root@k8s-master:~# kubectl delete deploy nginx-deployment
此时开始查看状态:
root@k8s-master:~# kubectl edit deployment nginx-deployment
deployment.apps/nginx-deployment edited
# 手快查看deployment的状态,显示还没有完成3个pod的全部更新。
root@k8s-master:~# kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 3/3 2 3 12m nginx nginx:latest app=nginx
# 稍等几秒钟,pod就创建完毕。
root@k8s-master:~# kubectl get deployments -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 3/3 3 3 12m nginx nginx:latest app=nginx
# 再来查看该deployment对应的replicaset,可以看到nginx:latest版本对应的replicaset,hash number为75b69bd684,这些pod都已经创建完毕。
root@k8s-master:~# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
nginx-deployment-5d59d67564 0 0 0 12m nginx nginx:1.7.9 app=nginx,pod-template-hash=5d59d67564
nginx-deployment-75b69bd684 3 3 3 24s nginx nginx:latest app=nginx,pod-template-hash=75b69bd684
# 3个以hash number为75b69bd684的pod已经运行起来了,不再是升级之前的那三个pod。
root@k8s-master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 2/2 Running 8 (22h ago) 10d 10.10.1.12 k8s-worker01 <none> <none>
nginx-deployment-75b69bd684-f65nv 1/1 Running 0 4m4s 10.10.2.33 k8s-worker02 <none> <none>
nginx-deployment-75b69bd684-fm8m5 1/1 Running 0 4m8s 10.10.2.31 k8s-worker02 <none> <none>
nginx-deployment-75b69bd684-xzq9r 1/1 Running 0 4m6s 10.10.2.32 k8s-worker02 <none> <none>
查看进度。如果手快的话,马上查看rollout的状态,会发现旧的pod在下线,新的pod在逐一上线,这个过程就是滚动更新。在升级刚开始的时候,集群里只有 1 个新版本的 Pod。如果这时,新版本 Pod 有问题启动不起来,那么“滚动更新”就会停止,从而允许开发和运维人员介入。而在这个过程中,由于应用本身还有两个旧版本的 Pod 在线,所以服务并不会受到太大的影响。当然,这也就要求你一定要使用 Pod 的 Health Check 机制检查应用的运行状态,而不是简单地依赖于容器的 Running 状态。要不然的话,虽然容器已经变成 Running 了,但服务很有可能尚未启动,“滚动更新”的效果也就达不到了。而为了进一步保证服务的连续性,Deployment Controller 还会确保,在任何时间窗口内,只有指定比例的 Pod 处于离线状态。同时,它也会确保,在任何时间窗口内,只有指定比例的新 Pod 被创建出来。这两个比例的值都是可以配置的,默认都是 DESIRED 值的 25%。
root@k8s-master:~# kubectl rollout status deployment/nginx-deployment
deployment "nginx-deployment" successfully rolled out
# 我这里手慢没有看到。。。。。
上述滚动更新已经运行完毕,接下来查看有几个版本更新,此时要注意上述创建deployment时要用到–record参数,否则change-cause中记录都为空。此时可以看到这个nginx-deployment变更过两次,有过两个revision。
root@k8s-master:~# kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
1 kubectl create --filename=nginx-deployment.yaml --record=true
2 kubectl create --filename=nginx-deployment.yaml --record=true
查看每个revision的详细改变
root@k8s-master:~# kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
1 kubectl create --filename=nginx-deployment.yaml --record=true
2 kubectl create --filename=nginx-deployment.yaml --record=true
查看具体某个revision的信息,本例中为revision=2,是吧nginx升级到latest。
root@k8s-master:~# kubectl rollout history deployment/nginx-deployment --revision=2
deployment.apps/nginx-deployment with revision #2
Pod Template:
Labels: app=nginx
pod-template-hash=75b69bd684
Annotations: kubernetes.io/change-cause: kubectl create --filename=nginx-deployment.yaml --record=true
Containers:
nginx:
Image: nginx:latest
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
经过上述操作已经实现了nginx版本从1.7.9到最新版本latest的滚动升级。但如果升级的应用遇到问题或者升级失败,需要回滚到旧状态时,该如何操作呢?这也是应用发布中经常遇到的问题。
root@k8s-master:~# kubectl rollout undo deployment/nginx-deployment
# 如果有多个revision,也可以加上--to-revision=num(num表示第几个revision)这个参数。
查看是否回滚成功
root@k8s-master:~# kubectl rollout status deployment/nginx-deployment
deployment "nginx-deployment" successfully rolled out
root@k8s-master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 2/2 Running 8 (23h ago) 10d 10.10.1.12 k8s-worker01 <none> <none>
nginx-deployment-5d59d67564-j96tj 1/1 Running 0 7s 10.10.1.23 k8s-worker01 <none> <none>
nginx-deployment-5d59d67564-k4l7r 1/1 Running 0 10s 10.10.2.34 k8s-worker02 <none> <none>
nginx-deployment-5d59d67564-vgxw9 1/1 Running 0 12s 10.10.1.22 k8s-worker01 <none> <none>
# 又要回到了以pod hash num=5d59d67564这个旧的nginx-1.7.9版本了。
root@k8s-master:~# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
nginx-deployment-5d59d67564 3 3 3 33m nginx nginx:1.7.9 app=nginx,pod-template-hash=5d59d67564
nginx-deployment-75b69bd684 0 0 0 21m nginx nginx:latest app=nginx,pod-template-hash=75b69bd684
root@k8s-master:~# kubectl get deployment -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
nginx-deployment 3/3 3 3 33m nginx nginx:1.7.9 app=nginx
至此,nginx这个app,通过了设置replicas=3实现水平扩展,也可以通过kubectl edit yaml文件的方式实现版本的滚动更新。上述操作做完后,可以按需删除该deployment,命令如下。
root@k8s-master:~# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 2/2 2 2 7h38m
root@k8s-master:~# kubectl delete deployment nginx-deployment
deployment.apps "nginx-deployment" deleted
root@k8s-master:~# kubectl get deployment
No resources found in default namespace.
总结,deployment作为k8s中最重要的一个控制器模式,完美地实现了应用的扩展、滚动更新,这也是经典PaaS项目中对平台的基本要求,
相信你也能够感受到,Kubernetes 项目对 Deployment 的设计,实际上是代替我们完成了对“应用”的抽象,使得我们可以使用这个 Deployment 对象来描述应用,使用 kubectl rollout 命令控制应用的版本。可是,在实际使用场景中,应用发布的流程往往千差万别,也可能有很多的定制化需求。比如,我的应用可能有会话黏连(session sticky),这就意味着“滚动更新”的时候,哪个 Pod 能下线,是不能随便选择的。这种场景,光靠 Deployment 自己就很难应对了。这就需要掌握其他高级知识,譬如Statefulset等组件,拭目以待吧。
暂无评论