当集群中的某个服务需要升级时,我们需要停止目前与该服务相关的所有Pod,然后下载新版本镜像并创建新的Pod。如果集群规模比较大,则这个工作变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用。
Kubernetes提供了滚动升级功能来解决上述问题。
如果Pod是通过Deployment创建的,则用户可以在运行时修改Deployment的Pod定义(spec.template)或镜像名称,并应用到Deployment对象上,系统即可完成Deployment的自动更新操作。如果在更新过程中发生了错误,则还可以通过回滚操作恢复Pod的版本。
# nginx-deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
[root@k8s-master01 pod]# kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
[root@k8s-master01 pod]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-5bf87f5f59-h2llz 1/1 Running 0 3s 10.244.2.44 k8s-node01 <none> <none>
nginx-deployment-5bf87f5f59-mlz84 1/1 Running 0 3s 10.244.2.45 k8s-node01 <none> <none>
nginx-deployment-5bf87f5f59-qdjvm 1/1 Running 0 3s 10.244.1.25 k8s-node02 <none> <none>
现在Pod镜像需要被更新为Nginx:1.9.1,我们可以通过kubectl set image命令为Deployment设置新的镜像名称:
kubectl set image deployment/nginx-development nginx=nginx:1.9.1
另一种更新的方法是使用kubectl edit命令修改Deployment的配置,将spec.template.spec.containers[0].image从Nginx:1.7.9更改为Nginx:1.9.1:
kubectl edit deployment/nginx-deployment
一旦镜像名(或Pod定义)发生了修改,则将触发系统完成Deployment所有运行Pod的滚动升级操作
可以使用kubectl rollout status命令查看Deployment的更新过程:
查看Pod使用的镜像,已经更新为Nginx:1.9.1了:
[root@k8s-master01 pod]# kubectl describe pod nginx-deployment-678645bf77-4ltnc
Image: nginx:1.9.1
使用kubectl describe deployments/nginx-deployment命令仔细观察Deployment的更新过程。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 6m4s deployment-controller Scaled down replica set nginx-deployment-5bf87f5f59 to 2
Normal ScalingReplicaSet 6m4s deployment-controller Scaled up replica set nginx-deployment-678645bf77 to 2
Normal ScalingReplicaSet 6m3s deployment-controller Scaled up replica set nginx-deployment-678645bf77 to 3
Normal ScalingReplicaSet 6m1s deployment-controller Scaled down replica set nginx-deployment-5bf87f5f59 to 0
Normal ScalingReplicaSet 4m55s deployment-controller Scaled up replica set nginx-deployment-5bf87f5f59 to 1
Normal ScalingReplicaSet 4m54s deployment-controller Scaled down replica set nginx-deployment-678645bf77 to 2
Normal ScalingReplicaSet 4m53s (x2 over 8m4s) deployment-controller Scaled up replica set nginx-deployment-5bf87f5f59 to 3
Normal ScalingReplicaSet 103s (x2 over 6m5s) deployment-controller Scaled up replica set nginx-deployment-678645bf77 to 1
Normal ScalingReplicaSet 101s (x2 over 6m3s) deployment-controller Scaled down replica set nginx-deployment-5bf87f5f59 to 1
Normal ScalingReplicaSet 100s (x7 over 4m54s) deployment-controller (combined from similar events): Scaled down replica set nginx-deployment-5bf87f5f59 to 0
初始创建Deployment时,系统创建了一个ReplicaSet(nginx-deployment-5bf87f5f59),并按用户的需求创建了3个Pod副本。当更新Deployment时,系统创建了一个新的ReplicaSet(nginx-deployment-678645bf77),并将其副本数量扩展到1,然后将旧的ReplicaSet缩减为2。之后,系统继续按照相同的更新策略对新旧两个ReplicaSet进行逐个调整。最后,新的ReplicaSet运行了3个新版本Pod副本,旧的ReplicaSet副本数量则缩减为0。如图所示。
[root@k8s-master01 pod]# kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-5bf87f5f59 0 0 0 17m
nginx-deployment-678645bf77 3 3 3 15m
在整个升级的过程中,系统会保证至少有两个Pod可用,并且最多同时运行4个Pod,这是Deployment通过复杂的算法完成的。Deployment需要确保在整个更新过程中只有一定数量的Pod可能处于不可用状态。在默认情况下,Deployment确保可用的Pod总数至少为所需的副本数量(DESIRED)减1,也就是最多1个不可用(maxUnavailable=1)。Deployment还需要确保在整个更新过程中Pod的总数量不会超过所需的副本数量太多。在默认情况下,Deployment确保Pod的总数最多比所需的Pod数多1个,也就是最多1个浪涌值(maxSurge=1)。Kubernetes从1.6版本开始,maxUnavailable和maxSurge的默认值将从1、1更新为所需副本数量的25%、25%。
这样,在升级过程中,Deployment就能够保证服务不中断,并且副本数量始终维持为用户指定的数量(DESIRED)。
对更新策略的说明如下。
在Deployment的定义中,可以通过spec.strategy指定Pod更新的策略,目前支持两种策略:Recreate(重建)和RollingUpdate(滚动更新),默认值为RollingUpdate。在前面的例子中使用的就是RollingUpdate策略。
下面对滚动更新时两个主要参数的说明如下。
这里需要注意多重更新(Rollover)的情况。如果Deployment的上一次更新正在进行,此时用户再次发起Deployment的更新操作,那么Deployment会为每一次更新都创建一个ReplicaSet,而每次在新的ReplicaSet创建成功后,会逐个增加Pod副本数,同时将之前正在扩容的ReplicaSet停止扩容(更新),并将其加入旧版本ReplicaSet列表中,然后开始缩容至0的操作。
例如,假设我们创建一个Deployment,这个Deployment开始创建5个Nginx:1.7.9的Pod副本,在这个创建Pod动作尚未完成时,我们又将Deployment进行更新,在副本数不变的情况下将Pod模板中的镜像修改为Nginx:1.9.1,又假设此时Deployment已经创建了3个Nginx:1.7.9的Pod副本,则Deployment会立即杀掉已创建的3个Nginx:1.7.9 Pod,并开始创建Nginx:1.9.1 Pod。Deployment不会在等待Nginx:1.7.9的Pod创建到5个之后再进行更新操作。
还需要注意更新Deployment的标签选择器(Label Selector)的情况。通常来说,不鼓励更新Deployment的标签选择器,因为这样会导致Deployment选择的Pod列表发生变化,也可能与其他控制器产生冲突。如果一定要更新标签选择器,那么请务必谨慎,确保不会出现其他问题。关于Deployment标签选择器的更新的注意事项如下。
1)添加选择器标签时,必须同步修改Deployment配置的Pod的标签,为Pod添加新的标签,否则Deployment的更新会报验证错误而失败:
添加标签选择器是无法向后兼容的,这意味着新的标签选择器不会匹配和使用旧选择器创建的ReplicaSets和Pod,因此添加选择器将会导致所有旧版本的ReplicaSets和由旧ReplicaSets创建的Pod处于孤立状态(不会被系统自动删除,也不受新的ReplicaSet控制)。
为标签选择器和Pod模板添加新的标签(使用kubectl edit deployment命令)后,效果如下:
# kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-4087004473 0 0 0 52m
nginx-deployment-3599678771 3 3 3 1m
nginx-deployment-3661742516 3 3 3 2s
可以看到新ReplicaSet(nginx-deployment-3661742516)创建的3个新Pod:
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-3599678771-01h26 1/1 Running 0 2m
nginx-deployment-3599678771-57thr 1/1 Running 0 2m
nginx-deployment-3599678771-s8p21 1/1 Running 0 2m
nginx-deployment-3661742516-46djm 1/1 Running 0 52s
nginx-deployment-3661742516-kws84 1/1 Running 0 52s
nginx-deployment-3661742516-wq30s 1/1 Running 0 52s
(2)更新标签选择器,即更改选择器中标签的键或者值,也会产生与添加选择器标签类似的效果。
(3)删除标签选择器,即从Deployment的标签选择器中删除一个或者多个标签,该Deployment的ReplicaSet和Pod不会受到任何影响。但需要注意的是,被删除的标签仍会存在于现有的Pod和ReplicaSets上。
有时(例如新的Deployment不稳定时)我们可能需要将Deployment回滚到旧版本。在默认情况下,所有Deployment的发布历史记录都被保留在系统中,以便于我们随时进行回滚(可以配置历史记录数量)。
假设在更新Deployment镜像时,将容器镜像名误设置成Nginx:1.91(一个不存在的镜像):
kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
则这时Deployment的部署过程会卡住:
kubectl rollout status deployments nginx-deployment
Waiting for rollout to finish: 1 out of 3 new replicas have been updated...
检查Deployment部署的历史记录
[root@k8s-master01 pod]# kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
5 <none>
6 <none>
注意,在创建Deployment时使用–record参数,就可以在CHANGE-CAUSE列看到每个版本使用的命令了。另外,Deployment的更新操作是在Deployment进行部署(Rollout)时被触发的,这意味着当且仅当Deployment的Pod模板(即spec.template)被更改时才会创建新的修订版本,例如更新模板标签或容器镜像。其他更新操作(如扩展副本数)将不会触发Deployment的更新操作,这也意味着我们将Deployment回滚到之前的版本时,只有Deployment的Pod模板部分会被修改。
如果需要查看特定版本的详细信息,则可以加上–revision=<N>
参数:
查看特定版本的信息,则可以加上–revision=<N>
参数:
[root@k8s-master01 pod]# kubectl rollout history deployment/nginx-deployment --revision=5
deployment.apps/nginx-deployment with revision #5
Pod Template:
Labels: app=nginx
pod-template-hash=5bf87f5f59
Containers:
nginx:
Image: nginx:1.7.9
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
现在我们决定撤销本次发布并回滚到上一个部署版本:
[root@k8s-master01 pod]# kubectl rollout undo deployment/nginx-deployment
deployment.apps/nginx-deployment rolled back
也可以使用 --to–revision 参数指定回滚到的部署版本号:
[root@k8s-master01 pod]# kubectl rollout undo deployment/nginx-deployment --to-revision=6
deployment.apps/nginx-deployment rolled back
对于一次复杂的Deployment配置修改,为了避免频繁触发Deployment的更新操作,可以先暂停Deployment的更新操作,然后进行配置修改,再恢复Deployment,一次性触发完整的更新操作,就可以避免不必要的Deployment更新操作了
以之前创建的Nginx为例:
[root@k8s-master01 pod]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 42m
[root@k8s-master01 pod]# kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-5bf87f5f59 0 0 0 43m
nginx-deployment-678645bf77 3 3 3 41m
通过kubectl rollout pause命令暂停deployment的更新操作
[root@k8s-master01 pod]# kubectl rollout pause deployment/nginx-deployment
deployment.apps/nginx-deployment paused
之后修改Deployment的镜像信息:
kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
查看Deployment的历史记录,并发现没有触发新的Deployment部署操作
[root@k8s-master01 pod]# kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
11 kubectl set image deployment/nginx-deployment nginx=nginx:1.7.9 --record=true
12 kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1 --record=true
在暂停Deployment部署之后,可以根据需要进行任意次数的配置更新。例如,再次更新容器的资源限制:
[root@k8s-master01 pod]# kubectl set resources deployment nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
deployment.apps/nginx-deployment resource requirements updated
最后,恢复这个Deployment的部署操作:
kubectl rollout resume deploy nginx-deployment
可以看到一个新的ReplicaSet被创建出来
[root@k8s-master01 pod]# kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-56bbb744cc 3 3 3 12s
nginx-deployment-5bf87f5f59 0 0 0 50m
nginx-deployment-678645bf77 0 0 0 48m
查看Deployment的事件信息,可以看到Deployment完成了更新:
[root@k8s-master01 pod]# kubectl describe deployment nginx-deployment
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.9.1
Port: 80/TCP
Host Port: 0/TCP
Limits:
cpu: 200m
memory: 512Mi
注意,在恢复暂停的Deployment之前,无法回滚该Deployment。
对于RC的滚动升级,kubernetes还提供了一个 kubectl rolling-update命令进行实现。该命令创建了一个新的RC,然后自动控制旧的RC中的Pod副本数量减少到0,同时新的的RC中的Pod副本数量从0逐个增加到目标值,来完成Pod的升级。需要注意的是,系统要求新的RC与旧的RC都在相同的命名空间内。
# tomcat-controller-v1.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: tomcat
labels:
name: tomcat
version: v1
spec:
replicas: 2
selector:
name: tomcat
template:
metadata:
labels: #可以自己定义key-value
name: tomcat
spec:
containers:
- name: tomcat
image: tomcat:6.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env: #环境变量
- name: GET_HOSTS_FROM
value: dns
[root@k8s-master01 pod]# kubectl apply -f tomcat-controller-v1.yaml
[root@k8s-master01 pod]# kubectl get rc -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
tomcat 2 2 2 16s tomcat tomcat:6.0 name=tomcat
# tomcat-controller-v2.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: tomcat2
labels:
name: tomcat
version: v2
spec:
replicas: 2
selector:
name: tomcat2
template:
metadata:
labels: #可以自己定义key-value
name: tomcat2
spec:
containers:
- name: tomcat
image: tomcat:7.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
env: #环境变量
- name: GET_HOSTS_FROM
value: dns
[root@k8s-master01 pod]# kubectl rolling-update tomcat -f tomcat-controller-v2.yaml
在配置文件中需要注意以下两点:
等所有新的Pod都启动完成后,旧的Pod也被全部销毁,这样就完成了容器集群的更新工作。
另一种方法是不使用配置文件,直接用kubectl rolling-update命令,加上–image参数指定新版镜像名称来完成Pod的滚动升级
kubectl rolling-update tomcat --image=tomcat:6.0
与使用配置文件的方式不同,执行的结果是旧RC被删除,新RC仍将使用旧RC的名称。
可以看到,kubectl通过新建一个新版本Pod,停掉一个旧版本Pod,如此逐步迭代来完成整个RC的更新。
可以看到,kubectl给RC增加了一个key为“deployment”的Label(这个key的名字可通过–deployment-label-key参数进行修改),Label的值是RC的内容进行Hash计算后的值,相当于签名,这样就能很方便地比较RC里的Image名字及其他信息是否发生了变化。
如果在更新过程中发现配置有误,则用户可以中断更新操作,并通过执行kubectl rolling- update --rollback完成Pod版本的回滚:
kubectl rolling-update tomcat --image=tomcat:6.0 --rollback
可以看出,RC的滚动升级不具有Deployment在应用版本升级过程中的历史记录、新旧版本数量的精细控制等功能,在Kubernetes的演进过程中,RC将逐渐被RS和Deployment所取代,建议用户优先考虑使用Deployment完成Pod的部署和升级操作。
Kubernetes从1.6版本开始,对DaemonSet和StatefulSet的更新策略也引入类似于Deployment的滚动升级,通过不同的策略自动完成应用的版本升级。
目前DaemonSet的升级策略包括两种:OnDelete和RollingUpdate。
(1)OnDelete:DaemonSet的默认升级策略,与1.5及以前版本的Kubernetes保持一致。当使用OnDelete作为升级策略时,在创建好新的DaemonSet配置之后,新的Pod并不会被自动创建,直到用户户手动删除旧版本的Pod,才触发新建操作。
(2)RollingUpdate:从Kubernetes 1.6版本开始引入。当使用RollingUpdate作为升级策略对DaemonSet进行更新时,旧版本的Pod将被自动杀掉,然后自动创建新版本的DaemonSet Pod。整个过程与普通Deployment的滚动升级一样是可控的。不过有两点不同于普通Pod的滚动升级:一是目前Kubernetes还不支持查看和管理DaemonSet的更新历史记录;二是DaemonSet的回滚(Rollback)并不能如同Deployment一样直接通过kubectl rollback命令来实现,必须通过再次提交旧版本配置的方式实现。
Kubernetes从1.6版本开始,针对StatefulSet的更新策略逐渐向Deployment和DaemonSet的更新策略看齐,也将实现RollingUpdate、Paritioned和OnDelete这几种策略,以保证StatefulSet中各Pod有序地、逐个地更新,并且能够保留更新历史,也能回滚到某个历史版本。