再战 k8s(12):Deployment 指导下 Pod 的升级和回滚

在这里插入图片描述

Pod的升级和回滚

当集群中的某个服务需要升级时,我们需要停止目前与该服务相关所有Pod,然后下载新版本镜像创建新的Pod。如果集群规模比较大,则这个工作变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用

Kubernetes提供了滚动升级功能来解决上述问题。

如果Pod是通过Deployment创建的,则用户可以在运行时修改Deployment的Pod定义spec.template)或镜像名称,并应用到Deployment对象上,系统即可完成Deployment的自动更新操作。如果在更新过程中发生了错误,则还可以通过回滚操作恢复Pod的版本。

Deployment的升级

# 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的更新过程:

img

查看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。如图所示。

img

[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策略。

  • Recreate:设置spec.strategy.type=Recreate,表示Deployment在更新Pod时,会先杀掉所有正在运行的Pod,然后创建新的Pod。
  • RollingUpdate:设置spec.strategy.type=RollingUpdate,表示Deployment会以滚动更新的方式来逐个更新Pod。同时,可以通过设置spec.strategy.rollingUpdate下的两个参数(maxUnavailable和maxSurge)来控制滚动更新的过程。

下面对滚动更新时两个主要参数的说明如下。

  • spec.strategy.rollingUpdate.maxUnavailable:用于指定Deployment在更新过程中不可用状态的Pod数量的上限。该maxUnavailable的数值可以是绝对值(例如5)或Pod期望的副本数的百分比(例如10%),如果被设置为百分比,那么系统会先以向下取整的方式计算出绝对值(整数)。而当另一个参数maxSurge被设置为0时,maxUnavailable则必须被设置为绝对数值大于0(从Kubernetes 1.6开始,maxUnavailable的默认值从1改为25%)。举例来说,当maxUnavailable被设置为30%时,旧的ReplicaSet可以在滚动更新开始时立即将副本数缩小到所需副本总数的70%。一旦新的Pod创建并准备好,旧的ReplicaSet会进一步缩容,新的ReplicaSet又继续扩容,整个过程中系统在任意时刻都可以确保可用状态的Pod总数至少占Pod期望副本总数的70%。
  • spec.strategy.rollingUpdate.maxSurge:用于指定在Deployment更新Pod的过程中Pod总数超过Pod期望副本数部分的最大值。该maxSurge的数值可以是绝对值(例如5)或Pod期望副本数的百分比(例如10%)。如果设置为百分比,那么系统会先按照向上取整的方式计算出绝对数值(整数)。从Kubernetes 1.6开始,maxSurge的默认值从1改为25%。举例来说,当maxSurge的值被设置为30%时,新的ReplicaSet可以在滚动更新开始时立即进行副本数扩容,只需要保证新旧ReplicaSet的Pod副本数之和不超过期望副本数的130%即可。一旦旧的Pod被杀掉,新的ReplicaSet就会进一步扩容。在整个过程中系统在任意时刻都能确保新旧ReplicaSet的Pod副本总数之和不超过所需副本数的130%。

这里需要注意多重更新(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的更新会报验证错误而失败:

img

添加标签选择器是无法向后兼容的,这意味着新的标签选择器不会匹配和使用旧选择器创建的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的发布历史记录都被保留在系统中,以便于我们随时进行回滚(可以配置历史记录数量)。

假设在更新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,一次性触发完整的更新操作,就可以避免不必要的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。

使用kubectl rolling-update命令完成RC的滚动升级

对于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
  • 进行RC滚动升级
# 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

在配置文件中需要注意以下两点:

  • RC的名字(name)不能与旧RC的名字相同。
  • 在selector中应至少有一个Label与旧RC的Label不同,以标识其为新RC。在本例中新增了一个名为version的Label,以与旧RC进行区分。即确保同一个Key至少有一个Value不一个。

等所有新的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的滚动升级,通过不同的策略自动完成应用的版本升级。

1.DaemonSet的更新策略

目前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命令来实现,必须通过再次提交旧版本配置的方式实现。

2.StatefulSet的更新策略

Kubernetes从1.6版本开始,针对StatefulSet的更新策略逐渐向Deployment和DaemonSet的更新策略看齐,也将实现RollingUpdate、Paritioned和OnDelete这几种策略,以保证StatefulSet中各Pod有序地、逐个地更新,并且能够保留更新历史,也能回滚到某个历史版本。

猜你喜欢

转载自blog.csdn.net/qq_43762191/article/details/123295468