Deployment是一种更高层的资源,用于部署应用程序并以声明的方式升级应用,而不是通过ReplicationController或ReplicaSet进行部署(它们都被认为是更底层的概念)。

当创建一个Deployment时,ReplicaSet资源也会随之创建。在使用Deployment时,实际的pod是由它的Replicaset创建和管理的,而不是由Deployment直接创建和管理的(如图9.8所示)。
image.png

为什么要在ReplicationController或ReplicaSet之上引入另一个对象来使整个过程变得更复杂,它们已经足够保持一组pod的实例运行了?
如9.2节中的滚动升级示例所示,在升级应用程序时,需要引入一个额外的ReplicationController,并协调两个Controller。所以需要另一个资源用来协调。Deployment资源就是用来负责处理这个问题的(不是Deployment资源本身,而是在Kubernetes control plane上运行的控制器进程。在第11章中将介绍)。使用Deployment可以更容易地更新应用程序,因为你可以通过单个的Deployment资源来定义期望的状态,并让Kubernetes处理中间的状态。

9.3.1 创建—个Deployment

image.png
一个Deployment资源位于版本之上。Deployment可以同时管理多个版本的 pod, 所以在命名时不需要指定应用的版本号。

创建Deployment资源

  1. #清理环境
  2. kubectl delete all --all
  3. cd /root/k8s/
  4. cat >kubia-deployment-v1.yaml <<'EOF'
  5. apiVersion: apps/v1
  6. kind: Deployment
  7. metadata:
  8. name: kubia
  9. spec:
  10. replicas: 3
  11. template:
  12. metadata:
  13. name: kubia
  14. labels:
  15. app: kubia
  16. spec:
  17. containers:
  18. - image: 10.0.0.10:5000/luksa/kubia:v1
  19. name: nodejs
  20. #imagePullPolicy: Always
  21. selector:
  22. matchLabels:
  23. app: kubia
  24. EOF
  25. kubectl create -f kubia-deployment-v1.yaml --record
  26. cat >kubia-svc-kubia.yaml <<'EOF'
  27. apiVersion: v1
  28. kind: Service
  29. metadata:
  30. name: kubia
  31. spec:
  32. type: NodePort
  33. selector:
  34. app: kubia
  35. ports:
  36. - port: 80
  37. targetPort: 8080
  38. nodePort: 30123
  39. EOF
  40. kubectl create -f kubia-svc-kubia.yaml

注意:确保在创建时使用了 —record 选项。 这将在修订历史(revison history)中记录该命令,这将在以后有用。
image.png

展示 Deployment rollout的状态

#检查Deployment状态
kubectl rollout status deployment kubia

image.png

了解Deployment如何创建ReplicaSet,ReplicaSet创建pod

查看pod,标红部分对应了ReplicaSet中pod模板的哈希值(pod-template-hash)。
image.png
image.png
ReplicaSet的名称(Deployment名称加上pod模板哈希值)中也包含了其pod模板的哈希值。Deployment会创建多个ReplicaSet,每个ReplicaSet对应一个版本的pod模板。像这样使用pod模板的哈希值,可以让Deployment始终对给定版本的pod模板使用相同的(或已有的)ReplicaSet。

补充:
ReplicationController创建的pod名称对应的是RC名称加上一个随机字符串(例如 kubia-v1-m33mv)。
ReplicaSet创建的pod名称对应Deployment名称加上pod模板哈希值加上一个随机字符串。

通过 Service 访问 pod

ReplicaSet创建了三个副本并成功运行以后,因为新的pod的标签(app: kubia)和Service的标签选择器(app: kubia)相匹配,因此可通过Service kubia来访问它们。

9.3.2 升级Deployment

只需修改Deployment资源中定义的pod模板,Kubemetes会自动使实际的系统状态达到资源中定义的状态。

不同的 Deployment 升级策略

如何达到新的系统状态的过程是上Deployment的升级策略决定的:

  • RollingUpdate:(滚动升级;默认策略)逐个地删除旧的pod,同时创建新的pod。使应用程序在整个升级过程中都处于可用状态,并确保其处理请求的能力没有因为升级而有所影响。升级过程中pod数量可以在期望副本数的一定区间内浮动,并且其上、下限是可配置的。适用于应用程序能够支持多个版本同时对外提供服务的场景。
  • Recreate:一次性删除所有旧版本的pod,然后创建新pod。适用于应用程序不支持多个版本同时对外提供服务的情景。会导致应用程序出现短时间的不可用。

演示如何减慢滚动升级速度

通过在Deployment上设置minReadySeconds属性来减慢RollingUpdate升级的速度,以便观察滚动升级过程。

kubectl patch deployment kubia -p '{"spec": {"minReadySeconds": 10}}'

注意:后面实验rollout过程慢,是因为这个配置。不然更新速度很快。
image.png
这里使用patch命令更改了Deployment的spec,并不会导致pod的任何更新,因为pod模板并没有被修改。更改Deployment的其他属性,比如所需的副本数或部署策略,也不会触发滚动升级,现有运行中的pod也不会受其影响。

触发滚动升级

#在另一个终端中运行curl循环
while true; do curl 10.0.0.10:30123; done

#要触发滚动升级,需要将pod镜像(luksa/kubia:v1)修改为luksa/kubia:v2
kubectl set image deployment kubia nodejs=10.0.0.10:5000/luksa/kubia:v2 --record

如果循环执行curl命令,将看到一开始请求只是命中到v1 pod;然后越来越多的请求命中到v2 pod,最后所有的v1 pod都被删除,请求全部命中到v2 pod。

Deployment的优点

整个升级过程是由作为Kubernetes control plane一部分运行的控制器执行的,而不是kubectl客户端。让 Kubemetes 的控制器接管使得整个升级过程变得更加简单可靠。

注意:如果Deployment中的pod模板引用了一个ConfigMap(或Secret),那么更改ConfigMap资原本身将不会触发升级操作。如果真的需要修改应用程序的配置并想触发更新的话,可以通过创建一个新的ConfigMap并修改pod模板引用新的ConfigMap。

Deployment的升级过程:
一个新的ReplicaSet会自动被创建然后慢慢扩容(scale up,先有扩容,再有缩容),同时之前版本的Replicaset会慢慢缩容(scale down)至0(初始状态和最终状态如图9.10所示)。
image.png
新、旧两个ReplicaSet会同时存在:(这与RC的滚动升级不同,RC升级完会删除旧RC)
image.png

9.3.3 回滚(roll back)Deployment

创建 v3 版本的应用

在v3版本中,将引入一个bug,使你的应用程序只能正确地处理前四个请求。第五个请求之后的所有请求将返回一个内部服务器错误(HTTP状态代码500)。
image.png

部署v3版本

#准备v3版本镜像
docker pull luksa/kubia:v3
docker tag luksa/kubia:v3 10.0.0.10:5000/luksa/kubia:v3
docker push 10.0.0.10:5000/luksa/kubia:v3

#修改Deployment kubia中的镜像来部署新版本
kubectl set image deployment kubia nodejs=10.0.0.10:5000/luksa/kubia:v3 --record

#通过kubectl rollout status 来观察整个升级过程:
kubectl rollout status deployment kubia

image.png

发送几个请求之后,客户端开始收到服务端返回的错误:
image.png

回滚滚动升级(undo a rollout)

这里先介绍如何手动停止出错版本的滚动升级

#让Deployment回滚到上一个版本:
kubectl rollout undo deployment kubia

提示:undo命令也可以在滚动升级过程中运行,并直接停止滚动升级。在升级过程中已创建的pod会被删除并被老版本的pod替代。

显示Deployment的滚动升级(rollout)历史

回滚滚动升级之所以成为可能,是因为Deployment始终保持着升级的版本历史(revison history)记录。之后也会看到,历史版本号会被保存在ReplicaSet的annotations中。滚动升级完成后,老版本的ReplicaSet不会被删掉,这使得回滚操作可以回滚到任何一个历史版本,而不仅仅是上个版本。
image.png

#显示rollout历史
kubectl rollout history deployment kubia

回滚滚动升级前
image.png
#回滚滚动升级后(原版本为revision 3,回滚到revision 2;原来的revision 2已经变为revison 4)
image.png
注意:kubectl set image也需要添加—record选项,不然将只显示上一次添加的记录,如下图
image.png

如果从创建开始所有操作都不给定—record选项,版本历史中的CHANGE-CAUSE一例会为空,这会使用户很难辨别每个版本做了哪些修改。如下图
image.png

回滚到一个特定的Deployment版本

#回滚到revison 1版本
kubectl rollout undo deployment kubia --to-revision=1

每个ReplicaSet都保存着特定版本Deployment的完整信息,所以不应该手动删除ReplicaSet。如果这么做便会丢失Deployment历史版本记录而导致无法回滚。
image.png
旧版本的ReplicaSet过多会导致ReplicaSet列表过于混乱,可以通过指定Deployment的revisionHistoryLimit(默认为10,多余的ReplicaSet会被删除)属性来限制历史版本数量。

9.3.4 控制滚动升级速率

当执行kubectl rollout status命令来观察升级到v3的过程时,会看到首先一个新pod被创建,等到它运行起来后,一个旧的pod会被删除,然后又一个新的pod被创建。如此反复,直到没有旧pod剩余。这种创建新pod、删除旧pod的方式可以通过两个滚动升级策略的属性配置。

介绍滚动升级策略的maxSurge和maxUnavailable属性

kubectl explain deployment.spec.strategy.rollingUpdate
image.png
image.png
image.png
由于在之前场景中,设置的期望副本数为3,上述的两个属性都默认设置为25%,maxSurge (325% 四舍五入到整数后为1)允许最大pod数量达到4 ,同时maxUnavailable (325% 向下取整后为0) 不允许出现任何不可用的pod(也就是说3个pod必须一直处于available状态),如图9.12所示。
image.png
注意:如果没有设置minReadySeconds,会发现滚动升级速度很快。观察结果,可以断定Terminating的pod不计入总pod数中,ContainerCreating有计算进去。见下图。
image.png

理解maxUnavailable 属性

我们应该根据maxUnavailable的值,来保证available状态的pod数量 (多余的pod可以删除),基于此,再结合maxSurge的值,来决定每次创建多少个新pod。
image.png
在这种情况下,只需要2个副本处于available状态,最多允许存在4个pod (3+1)。所以滚动升级一开始就会立即删除1个pod并创建2个新pod。一旦2个新的pod处于available状态 (现在总共有4个available状态的pod),剩下的2个旧的pod就会被删除。

重要的是要知道maxUnavailable是相对于期望副本数而言的,我们至少要保持 (期望副本数 - maxUnavailable)个pod始终处于available状态,而不可用的pod数量可以超过一个。

9.3.5 暂停滚动升级

Deployment也可以在滚动升级过程中暂停。这允许你验证新版本应用是否一切正常,然后再继续进行剩余的滚动升级操作。

暂停滚动升级

前面实验中的v3版本的应用BUG已经修复,并发布为v4版本,其镜像是Docker Hub上的luksa/kubia:v4。我们将前面部署的Deployment中的镜像修改为新版本镜像,然后立即(在几秒之内)暂停滚动升级:

#准备v4版本镜像
docker pull luksa/kubia:v4
docker tag luksa/kubia:v4 10.0.0.10:5000/luksa/kubia:v4
docker push 10.0.0.10:5000/luksa/kubia:v4

#修改Deployment中的镜像,并立即暂停
kubectl set image deployment kubia nodejs=10.0.0.10:5000/luksa/kubia:v4 --record
kubectl rollout pause deployment kubia

#执行kubectl rollout status,发现滚动升级已经暂停
kubectl rollout status deployment kubia

image.png
一个新的pod会被创建,与此同时所有旧的pod还在运行。一旦新的pod成功运行,服务的一部分请求将被重定向到新的pod。这样相当于运行了一个金丝雀发布(canary release)。金丝雀发布技术可以最大限度地降低发布出错版本的应用程序并影响到所有用户的风险。它不是直接向每个用户发布新版本,而是用新版本pod替换一个或一小部分pod。通过这种方式,在升级的初期只有少数用户会访问新版本。在验证新版本是否正常工作之后,可以将剩余的pod继续滚动升级或者回滚到上一个的版本。

恢复滚动升级

在上述例子中,通过暂停滚动升级过程,只有一小部分客户端请求会命中v4 pod,而大多数请求依然仍只会命中v3 pod。一旦确认新版本能够正常工作,就可以恢复滚动升级,用新版本pod替换所有旧版本的pod:

#恢复滚动升级
kubectl rollout resume deployment kubia

#执行kubectl rollout status,发现滚动升级会继续进行
kubectl rollout status deployment kubia

image.png

目前想要进行金丝雀发布的正确方式是,使用两个不同的Deployment并同时调整它们对应的pod数量。(scale them)

使用pause功能来阻止滚动升级

暂停Deployment还可以用于阻止因更改Deployment而自动触发的滚动升级过程。用户可以对Deployment进行多次更改,并在完成所有更改后才恢复(resume)滚动升级过程。

注意:如果Deployment被暂停,将无法执行kubectl rollout undo deployment,除非先恢复Deployment。
image.png

9.3.6 阻止出错版本的滚动升级

minReadySeconds的主要功能是避免部署出错版本的应用,而不只是单纯地减慢deployment更新的速度。

了解 minReadySeconds 的用处

minReadySeconds属性指定新创建的pod至少要就绪多久之后,才能将其视为可用。在pod可用之前,滚动升级的过程不会继续(maxUnavailable决定)。当所有容器的就绪探针返回成功时,pod就被认为就绪。如果一个就绪的新pod在minReadySeconds时间内它的就绪探针开始出现失败,那么新版本的滚动升级将被阻止。

(9.3.2例子中)使用这个属性可以通过让Kubemetes在pod就绪之后继续等待10秒,然后继续执行滚动升级,来减缓滚动升级的过程。通常情况下需要将minReadySeconds设置为更高的值,以确保pod在它们真正开始接收实际流量之后可以持续保持就绪状态。

配置就绪探针来防止v3版本被完全滚动部署

再一次部署v3版本,但这一次会为pod配置正确的就绪探针。先回滚到v2版本,来模拟假设是第一次升级到v3。之前因为就绪探针一直未被定义,所以容器和pod都处于就绪状态,即使应用程序本身并没有真正就绪甚至是正在返回错误。
image.png

#先回滚到v2版本,来模拟假设是第一次升级到v3
kubectl rollout undo deployment kubia --to-revision=2

cd /root/k8s/
cat >kubia-deployment-v3-with-readinesscheck.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3
  minReadySeconds: 10
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: 10.0.0.10:5000/luksa/kubia:v3
        name: nodejs
        readinessProbe:
          periodSeconds: 1
          httpGet:
            path: /
            port: 8080
  selector:
    matchLabels:
      app: kubia
EOF

使用kubectl apply升级Deployment

#应用新的deployment定义文件,会自动开始升级
kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml --record

#查看升级过程
kubectl rollout status deployment kubia

kubectl apply命令用YAML文件中定义的所有内容更新Deployment。运行该命令之后会自动开始升级过程。
提示:使用kubectlapply更新Deployment时如果不想让期望副本数被更改,则不用在YAML文件中添加replicas字段。
image.png
#kubectl rollout status显示升级时一直停在这里!
image.png
#有一个pod一直处于未就绪状态!为什么?

就绪探针如何阻止出错版本的滚动升级

当新的pod启动时,就绪探针会每隔1秒(periodSeconds: 1)发起请求。在就绪探针发起第五个请求的时候出现失败,因为应用从第五个请求开始一直返回HTTP状态码500.
image.png
因引,pod会从Service的endpoints中移除。如果你使用curl发起请求会发现它不会命中到新pod上。
image.png

为滚动升级配置 deadline

判定Deployment滚动升级失败的超时时间,可以通过设定deployment.spec.progressDeadlineSeconds来指定,默认值为10分钟。
image.png

取消出错版本的滚动升级

#撤销上一次的 rollout
kubectl rollout undo deployment kubia

注意:达到了progressDeadlineSeconds指定的时间,滚动升级过程并不会自动取消。需使用kubectl rollout undo命令手动取消。