美文网首页k8s in action实践笔记
9.3 使用Deployment声明式地升级应用

9.3 使用Deployment声明式地升级应用

作者: 众神开挂 | 来源:发表于2021-07-28 09:19 被阅读0次

    9.3 使用Deployment声明式地升级应用

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

    当创建一个Deployment时,ReplicaSet资源也会随之创建(最终会有更多的资源被创建)。在第4章中,ReplicaSet是新一代的ReplicationController,并推荐使用它替代ReplicationController来复制和管理pod。在使用Deployment时,实际的pod是由Deployment的Replicaset创建和管理的,而不是由Deployment直接创建和管理的。

    你可能想知道为什么要在ReplicationController或ReplicaSet上引入另一个对象来使整个过程变得更复杂,因为它们已经足够保证一组pod的实例正常运行了。如9.2节中的滚动升级示例所示,在升级应用程序时,需要引入一个额外的ReplicationController,并协调两个Controller,使它们再根据彼此不断修改,而不会造成干扰。所以需要另一个资源用来协调。Deployment资源就是用来负责处理这个问题的(不是Deployment资源本身,而是在Kubernetes控制层上运行的控制器进程。我们会在第11章中再做介绍)。

    使用Deployment可以更容易地更新应用程序,因为可以直接定义单个Deployment资源所需达到的状态,并让Kubernetes处理中间的状态,接下来将会介绍整个过程。

    9.3.1 创建一个Deployment

    创建Deployment与创建ReplicationController并没有任何区别。Deployment也是由标签选择器、期望副数和pod模板组成的。此外,它还包含另一个字段,指定一个部署策略,该策略定义在修改Deployment资源时应该如何执行更新。

    创建一个Deployment Manifest

    让我们看一下如何使用本章前面的 kubia-v1 ReplicationController示例,并对其稍作修改,使其描述一个Deployment,而不是一个ReplicationController。这只需要三个简单的更改,下面的代码清单显示了修改后的YAML。

    代码清单9.1 v1版本的应用:v1/app.js

    const http = require('http');
    const os = require('os');
    
    console.log("Kubia server starting...");
    
    var handler = function(request, response) {
      console.log("Received request from " + request.connection.remoteAddress);
      response.writeHead(200);
      response.end("This is v1 running in pod " + os.hostname() + "\n");
    };
    
    var www = http.createServer(handler);
    www.listen(8080);
    

    代码清单9.7 Deployment定义:kubia-deployment-v1.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: kubia
    spec:
      replicas: 3
      template:
        metadata:
          name: kubia
          labels:
            app: kubia
        spec:
          containers:
          - image: luksa/kubia:v1
            name: nodejs
      selector:
        matchLabels:
          app: kubia
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: kubia
    spec:
      type: LoadBalancer
      selector:
        app: kubia
      ports:
      - port: 80
        targetPort: 8080
    

    因为之前的ReplicationController只维护和管理了一个特定版本的pod,并需要命名为kubia-v1。另一方面,一个Deployment资源高于版本本身。Deployment可以同时管理多个版本的pod,所以在命名时不需要指定应用的版本号。

    创建Deployment资源

    在创建这个Deployment之前,请确保删除仍在运行的任何ReplicationController和pod,但是暂时保留kubia Service。可以使用 --all 选项来删除所有的ReplicationController:

    $ kubectl delete rc --all
    

    此时已经可以创建一个Deployment了:

    $ kubectl create -f kubia-deployment-v1.yaml --record
    deployment.apps/kubia2 created
    

    注意 确保在创建时使用了--record选项。这个选项会记录历史版本号,在之后的操作中非常有用。

    展示Deployment滚动过程中的状态

    可以直接使用 kubectl get deploymentkubectl describe deployment 命令来查看Deployment的详细信息,但是还有另外一个命令,专门用于查看部署状态:

    $ kubectl rollout status deployment kubia
    deployment "kubia" successfully rolled out
    

    通过上述命令查看到,Deployment已经完成了滚动升级,可以看到三个pod副本已经正常创建和运行了:

    $ kubectl get po
    NAME                                  READY   STATUS    RESTARTS   AGE
    kubia2-7b778df8b9-8dz8p               1/1     Running   0          112s
    kubia2-7b778df8b9-kcxk8               1/1     Running   0          112s
    kubia2-7b778df8b9-lhlfp               1/1     Running   0          112s
    

    了解Deployment如何创建Replicaset以及pod

    注意这些pod的命名,之前当使用ReplicationController创建pod时,它们的名称是由Controller的名称加上一个运行时生成的随机字符串(例如kubia-v1-m33mv)组成的。现在由Deployment创建的三个pod名称中均包含一个额外的数字。那是什么呢?

    这个数字实际上对应Deployment和ReplicaSet中的pod模板的哈希值。如前所述,Deployment不能直接管理pod。相反,它创建了ReplicaSet来管理pod。所以让我们看看Deployment创建的ReplicaSet是什么样子的。

    $ kubectl get replicasets
    NAME                          DESIRED   CURRENT   READY   AGE
    kubia2-7b778df8b9             3         3         3       2m32s
    

    ReplicaSet的名称中也包含了其pod模板的哈希值。之后的篇幅也会介绍,Deployment会创建多个ReplicaSet,用来对应和管理一个版本的pod模板。像这样使用pod模板的哈希值,可以让Deployment始终对给定版本的pod模板创建相同的(或使用已有的)ReplicaSet。

    通过Service访问pod

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

    至此可能还没有从根本上解释,为什么更推荐使用Deployment而不是直接使用ReplicationController。另一方面看,创建一个Deployment的难度和成本也并没有比ReplicationController更高。后面将针对这个Deployment做一些操作,并从根本上了解Deployment的优点和强大之处。接下来会介绍如何通过Deployment资源升级应用,并对比通过ReplicationController升级应用的区别,你就会明白这一点。

    这个YAML定义了一个名为kubia-v1 的Deployment和一个名为kubia 的Service。将此YAML发布到Kubernetes之后,三个 v1 pod和负载均衡器都会开始工作。如下面的代码清单所示,可以通过查找服务的外部IP并使用 curl命令来访问服务。

    代码清单9.3 查找到Service IP并使用curl循环调用服务接口

    $ kubectl get svc kubia
    NAME            TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
    kubia      LoadBalancer       10.100.209.238    <pending>     80:31851/TCP   66s
    $ minikube service kubia -n custom --url
    http://172.17.0.2:31851
    $ while true; do curl http://172.17.0.2:31851; sleep 3; done
    

    注意 如果使用Minikube或者其他不支持LoadBalancer的Kubernetes集群,需要使用Service的节点端口方式来访问你的应用。在第5章中有提到这一点。

    9.3.2 升级Deployment

    前面提到,当使用ReplicationController部署应用时,必须通过运行 kubectl rolling-update 显式地告诉Kubernetes来执行更新,甚至必须为新的ReplicationController指定名称来替换旧的资源。Kubernetes会将所有原来的pod替换为新的pod,并在结束后删除原有的ReplicationController。在整个过程中必须保持终端处于打开状态,让 kubectl 完成滚动升级。

    接下来更新Deployment的方式和上述的流程相比,只需修改Deployment资源中定义的pod模板,Kubernetes会自动将实际的系统状态收敛为资源中定义的状态。类似于将ReplicationController或ReplicaSet扩容或者缩容,升级需要做的就是在部署的pod模板中修改镜像的tag,Kubernetes会收敛系统,匹配期望的状态。

    不同的Deployment升级策略

    实际上,如何达到新的系统状态的过程是由Deployment的升级策略决定的,默认策略是执行滚动更新(策略名为RollingUpdate)。另一种策略为Recreate,它会一次性删除所有旧版本的pod,然后创建新的pod,整个行为类似于修改ReplicationController的pod模板,然后删除所有的pod(在9.1.1节中已讨论过)。

    Recreate 策略在删除旧的pod之后才开始创建新的pod。如果你的应用程序不支持多个版本同时对外提供服务,需要在启动新版本之前完全停用旧版本,那么需要使用这种策略。但是使用这种策略的话,会导致应用程序出现短暂的不可用。

    RollingUpdate 策略会渐进地删除旧的pod,与此同时创建新的pod,使应用程序在整个升级过程中都处于可用状态,并确保其处理请求的能力没有因为升级而有所影响。这就是Deployment默认使用的升级策略。升级过程中pod数量可以在期望副本数的一定区间内浮动,并且其上限和下限是可配置的。如果应用能够支持多个版本同时对外提供服务,则推荐使用这个策略来升级应用。

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

    在接下来的练习中,将使用 RollingUpdate 策略,但是需要略微减慢滚动升级的速度,以便观察升级过程确实是以滚动的方式执行的。可以通过在Deployment上设置 minReadySeconds 属性来实现。我们将在本章末尾解释这个属性的作用。现在,使用 kubectl patch 命令将其设置为10秒。

    使用 kubectl patch 更新 API 对象

    # 这个命令不可用,需要使用yaml文件patch,参考上方的引用$ kubectl patch deployment kubia -p '("spec": {"minReadySeconds": 10})'
    

    提示 kubectl patch 对于修改单个或者少量资源属性非常有用,不需要再通过编辑器编辑。

    使用patch命令更改Deployment的自有属性,并不会导致pod的任何更新,因为pod模板并没有被修改。更改其他Deployment的属性,比如所需的副本数或部署策略,也不会触发滚动升级,现有运行中的pod也不会受其影响。

    触发滚动升级

    如果想要跟踪更新过程中应用的运行状况,需要先在另一个终端中再次运行curl 循环,以查看请求的返回情况(需要将IP替换为Service实际暴露IP):

    $ while true; do curl http://172.17.0.2:31851; sleep 3; done
    

    要触发滚动升级,需要将pod镜像修改为luksa/kubia:v2。和直接编辑Deployment资源的YAML文件或使用patch命令更改镜像有所不同,将使用 kubectl set image 命令来更改任何包含容器资源的镜像(ReplicationController、ReplicaSet、Deployment等)。将使用这个命令来修改Deployment:

    $ kubectl set image deployment kubia nodejs=luksa/kubia:v2 --record #下面的命令也可以用$ kubectl --record deployment.apps/kubia2 set image deployment.v1.apps/kubia nodejs=luksa/kubia:v2deployment.apps/kubia2 image updated
    

    当执行完这个命令,kubia Deployment的pod模板内的镜像会被更改为 luksa/kubia2:v2(从:v1更改而来)。

    修改Deployment或其他资源的不同方式

    在本书中已经了解了几种不同的修改对象方式,现在把它们列在一起来重新回顾一下。

    表9.1 在Kubernetes中修改资源

    img

    这些方式在操作Deployment资源时效果都是一样的。它们无非就是修改Deployment的规格定义,修改后会触发滚动升级过程。

    如果循环执行了 curl 命令,将看到一开始请求只是切换到 v1 pod;然后越来越多的请求切换到 v2 pod中,最后所有的 v1 pod都被删除,请求全部切换到 v2.pod。这和 kubectl的滚动更新过程非常相似。

    Deployment的优点

    回顾一下刚才的过程,通过更改Deployment资源中的pod模板,应用程序已经被升级为一个更新的版本——仅仅通过更改一个字段而已!

    这个升级过程是由运行在Kubernetes上的一个控制器处理和完成的,而不再是运行 kubectl rolling-update 命令,它的升级是由 kubectl 客户端执行的。让Kubernetes的控制器接管使得整个升级过程变得更加简单可靠。

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

    Deployment背后完成的整个升级过程和执行 kubectl rolling-update命令非常相似。一个新的ReplicaSet会被创建然后慢慢扩容,同时之前版本的Replicaset会慢慢缩容至0(初始状态和最终状态如图9.10所示)。

    [图片上传失败...(image-39e5fb-1627435144461)]

    图9.10 滚动升级开始和结束时Deployment状态

    可以通过下面的命令列出所有新旧ReplicaSet:

    $ kubectl get rsNAME                          DESIRED   CURRENT   READY   AGEkubia2-64cbd448b4             3         3         3       6mkubia2-7b778df8b9             0         0         0       13m
    

    与ReplicationController类似,所有新的pod现在都由新的ReplicaSet管理。与以前不同的是,旧的ReplicaSet仍然会被保留,而旧的ReplicationController会在滚动升级过程结束后被删除。之后马上会介绍这个被保留的旧ReplicaSet的用处。

    因为并没有直接创建ReplicaSet,所以这里的ReplicaSet本身并不需要用户去关心和维护。所有操作都是在Deployment资源上完成的,底层的ReplicaSet只是实现的细节。和处理与维护多个ReplicationController相比,管理单个Deployment对象要容易得多。

    尽管这种差异在滚动升级中可能不太明显,但是如果在滚动升级过程中出错或遇到问题,就可以明显看出两种方案的差异。下面来模拟一个错误。

    9.3.3 回滚Deployment

    现阶段,应用使用v2 版本的镜像运行,接下来会先准备v3 版本的镜像。

    创建v3 版本的应用程序

    在v3 版本中,将引入一个bug,使你的应用程序只能正确地处理前四个请求。第五个请求之后的所有请求将返回一个内部服务器错误(HTTP状态代码500)。你将通过在处理程序函数的开头添加 if 语句来模拟这个bug。下面的代码清单显示了修改后的代码,所有需要更改的地方都用粗体显示。

    代码清单9.8 v3 版本的应用(运行出错版本): v3/app.js

    const http = require('http');const os = require('os');var requestCount = 0;console.log("Kubia server starting...");var handler = function(request, response) {  console.log("Received request from " + request.connection.remoteAddress);  if (++requestCount >= 5) {    response.writeHead(500);    response.end("Some internal error has occurred! This is pod " + os.hostname() + "\n");    return;  }  response.writeHead(200);  response.end("This is v3 running in pod " + os.hostname() + "\n");};var www = http.createServer(handler);www.listen(8080);
    

    如你所见,在第5个请求和所有后续请求中,返回一个状态码为500的错误,错误消息 “Some internal error has occurred…”

    部署v3版本

    笔者已经将v3 版本的镜像推送至 jliudong/kubia2:v3。可以直接修改Deployment中镜像的字段来部署新的版本:

    $ kubectl set image deployment kubia nodejs=luksa/kubia:v3 --record deployment.apps/kubia2 image updated
    

    可以通过运行 kubectl rollout status 来观察整个升级过程:

    $ kubectl rollout status deployment kubia deployment "kubia" successfully rolled out
    

    新的版本已经开始运行。接下来的代码清单会显示,在发送几个请求之后,客户端开始收到服务端返回的错误。

    代码清单9.9 访问出错的v3版本

    $ while true; do curl http://172.17.0.2:31851; sleep 3; done
    

    回滚升级

    不能让你的用户感知到升级导致的内部服务器错误,因此需要快速处理。在9.3.6节中,你将看到如何自动停止出错版本的滚动升级,但是现在先看一下如何手动停止。比较好的是,Deployment可以非常容易地回滚到先前部署的版本,它可以让Kubernetes取消最后一次部署的Deployment:

    $ kubectl rollout undo deployment kubia --record 
    

    Deployment会被回滚到上一个版本。

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

    显示Deployment的滚动升级历史

    回滚升级之所以可以这么快地完成,是因为Deployment始终保持着升级的版本历史记录。之后也会看到,历史版本号会被保存在ReplicaSet中。滚动升级成功后,老版本的ReplicaSet也不会被删掉,这也使得回滚操作可以回滚到任何一个历史版本,而不仅仅是上一个版本。可以使用 kubectl rollout history 来显示升级的版本:

    $ kubectl rollout history deployment kubia
    

    还记得创建Deployment时的 --record 参数吗?如果不给定这个参数,版本历史中的 CHANGE-CAUSE 这一栏会为空。这也会使用户很难辨别每次的版本做了哪些修改。

    回滚到一个特定的Deployment版本

    通过在 undo 命令中指定一个特定的版本号,便可以回滚到那个特定的版本。例如,如果想回滚到第一个版本,可以执行下述命令:

    $ kubectl rollout undo deployment kubia --to-revision=1
    

    还记得第一次修改Deployment时留下的ReplicaSet吗?这个ReplicaSet便表示Deployment的第一次修改版本。由Deployment创建的所有ReplicaSet表示完整的修改版本历史,如图9.11所示。每个ReplicaSet都用特定的版本号来保存Deployment的完整信息,所以不应该手动删除ReplicaSet。如果这么做便会丢失Deployment的历史版本记录而导致无法回滚。

    [图片上传失败...(image-938f15-1627435144461)]image

    图9.11 Deployment的ReplicaSet也保存版本历史

    旧版本的ReplicaSet过多会导致ReplicaSet列表过于混乱,可以通过指定Deployment的 revisionHistoryLimit 属性来限制历史版本数量。默认值是 2,所以正常情况下在版本列表里只有当前版本和上一个版本(以及只保留了当前和上一个ReplicaSet),所有再早之前的ReplicaSet都会被删除。

    注意 extensions/v1beta1 版本的Deployment的revisionHistoryLimit没有值,在 apps/v1beta2 版本中,这个默认值是 10。

    9.3.4 控制滚动升级速率

    当执行 kubectl rollout status 命令来观察升级到 v3 的过程时,会看到第一个pod被新创建,等到它运行时,一个旧的pod会被删除,然后又一个新的pod被创建,直到再没有新的pod可以更新。创建新pod和删除旧pod的方式可以通过配置滚动更新策略内的两个属性。

    介绍滚动升级策略的 maxSurgemaxUnavailable 属性

    在Deployment的滚动升级期间,有两个属性会决定一次替换多少个 pod:maxSurgemaxUnavailable 。可以通过Deployment的 strategy 字段下 rollingUpdate 的子属性来配置,如下面的代码清单所示。

    代码清单9.10 为rollingUpdate 策略指定参数

    spec:  strategy:    rollingUpdate:    maxSurge: 1     maxUnavailable: 0 type: RollingUpdate
    

    这些参数的含义会在表9.2中详细解释。

    表9.2 控制滚动升级速率的属性

    img

    由于在之前场景中,设置的期望副本数为3,上述的两个属性都设置为25%,maxSurge 允许最多pod数量达到 4,同时 maxUnavailable 不允许出现任何不可用的pod(也就是说三个pod必须一直处于可运行状态)。

    了解 maxUnavailable属性

    extensions/v1beta1 版本的Deployment使用不一样的默认值,maxSurgemaxUnavailable 会被设置为1,而不是25%。对于三个副本的情况,maxSurge 还是和之前一样,但是 maxUnavailable 是不一样的(是1而不是0),这使得滚动升级的过程稍有不同。

    在这种情况下,一个副本处于不可用状态,如果期望副本数为3,则只需要两个副本处于可用状态。这就是为什么上述滚动升级过程中会立即删除一个pod并创建两个新的pod。这确保了两个pod是可用的并且不超过pod允许的最大数量(在这种情况下,最大数量是4,三个加上maxSurges的一个。一旦两个新的pod处于可用状态,剩下的两个旧的pod就会被删除了。

    这里相对有点难以理解,主要是 maxUnavailable 这个属性,它表示最大不可用pod数量。但是如果仔细查看前一个图的第二列,即使 maxUnavailable 被设置为1,可以看到两个pod处于不可用的状态。

    重要的是要知道 maxUnavailable 是相对于期望副本数而言的。如果replica的数量设置为3,maxUnavailable 设置为1,则更新过程中必须保持至少两个(3-1)pod始终处于可用状态,而不可用pod数量可以超过一个。

    9.3.5 暂停滚动升级

    在经历了v3 版本应用的糟糕体验之后,假设你现在已经修复了这个错误,并推送了第四个版本的镜像。但是你还是有点担心像之前那样将其滚动升级到所有的pod上。你需要的是在现有的v2 pod之外运行一个v4 pod,并查看一小部分用户请求的处理情况。如果一旦确定符合预期,就可以用新的pod替换所有旧的pod。

    可以通过直接运行一个额外的pod或通过一个额外的Deployment、ReplicationController或ReplicaSet来实现上述的需求。但是通过Deployment自身的一个选项,便可以让部署过程暂停,方便用户在继续完成滚动升级之前来验证新的版本的行为是否都符合预期。

    暂停滚动升级

    笔者已经事先准备好了 v4 的镜像,所以可以直接将镜像修改为 luksa/kubia:v4 并触发滚动更新,然后立马(在几秒之内)暂停滚动更新:

    $ kubectl set image deployment kubia nodejs=luksa/kubia:v4 --recorddeployment.apps/kubia2 image updated$ kubectl rollout pause deployment kubiadeployment.apps/kubia2 paused
    

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

    恢复滚动升级

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

    $ kubectl rollout resume deployment kubia2 --recorddeployment.apps/kubia2 resumed
    

    在滚动升级过程中,想要在一个确切的位置暂停滚动升级目前还无法做到,以后可能会有一种新的升级策略来自动完成上面的需求。但目前想要进行金丝雀发布的正确方式是,使用两个不同的Deployment并同时调整它们对应的pod数量。

    使用暂停功能来停止滚动升级

    暂停部署还可以用于阻止更新Deployment而自动触发的滚动升级过程,用户可以对Deployment进行多次更改,并在完成所有更改后才恢复滚动升级。一旦更改完毕,则恢复并启动滚动更新过程。

    注意 如果部署被暂停,那么在恢复部署之前,撤销命令不会撤销它。

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

    在结束本章之前,我们还会讨论Deployment资源的另一个属性。还记得在9.3.2节开始时在Deployment中设置的 minReadySeconds 属性吗?使用它来减慢滚动升级速率,使用这个参数之后确实执行了滚动更新,并且没有一次性替换所有的pod。minReadySeconds 的主要功能是避免部署出错版本的应用,而不只是单纯地减慢部署的速度。

    了解minReadySeconds的用处

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

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

    当然在将pod部署到生产环境之前,需要在测试和预发布环境中对pod进行测试。但使用 minReadySeconds 就像一个安全气囊,保证了即使不小心将bug发布到生产环境的情况下,也不会导致更大规模的问题。

    使用正确配置的就绪探针和适当的 minReadySeconds 值,Kubernetes将预先阻止我们发布部署带有bug的v3 版本。下面会展示如何实现。

    配置就绪探针来阻止全部v3 版本的滚动部署

    再一次部署 v3 版本,但这一次会为pod配置正确的就绪探针。由于当前部署的是 v4 版本,所以在开始之前再次回滚到 v2 版本,来模拟假设是第一次升级到v3。当然也可以直接从 v4 升级到 v3,但是后续我们假设都是先回滚到了 v2 版本。

    与之前只更新pod模板中的镜像不同的是,还将同时为容器添加就绪探针。之前因为就绪探针一直未被定义,所以容器和pod都处于就绪状态,即使应用程序本身并没有真正就绪甚至是正在返回错误。Kubernetes是无法知道应用本身是否出现了故障,也不会将未就绪信息暴露给客户端。

    同时更改镜像并添加就绪探针,则可以使用 kubectl apply 命令。使用下面的YAML来更新Deployment(将它另存为 kubian-deployment-v3-withreadinesscheck.yaml),如下面的代码清单所示。

    代码清单9.11 Deployment包含一个就绪探针:kubia-deployment-v3with-readinesscheck.yaml

    apiVersion: apps/v1kind: Deploymentmetadata:  name: kubiaspec:  replicas: 3  minReadySeconds: 10 #设置为10  strategy:    rollingUpdate:      maxSurge: 1      maxUnavailable: 0  #确保升级过程中pod被挨个替换    type: RollingUpdate  selector:    matchLabels:      app: kubia  template:    metadata:      name: kubia      labels:        app: kubia    spec:      containers:      - image: luksa/kubia:v3        name: nodejs        readinessProbe:          periodSeconds: 1  #就绪探针,每秒执行一次http请求          httpGet:            path: /            port: 8080---apiVersion: v1kind: Servicemetadata:  name: kubiaspec:  type: LoadBalancer  selector:    app: kubia  ports:  - port: 80    targetPort: 8080
    

    使用kubectl apply升级Deployment 可以使用如下方式直接使用 kubectl apply 来升级Deployment:

    $ kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml
    

    apply 命令可以用YAML文件中声明的字段来更新Deployment。不仅更新镜像,而且还添加了就绪探针,以及在YAML中添加或修改的其他声明。如果新的YAML也包含 replicas 字段,当它与现有Deployment中的数量不一致时,那么 apply 操作也会对Deployment进行扩容。

    提示 使用 kubectl apply 更新Deployment时如果不期望副本数被更改,则不用在YAML文件中添加 replicas 这个字段。

    运行 apply 命令会自动开始滚动升级过程,可以再一次运行 rollout status 命令来查看升级过程:

    $ kubectl rollout status deployment kubia
    

    因为升级状态显示一个新的pod已经创建,一小部分流量应该也会切换到这个pod。可以通过下面的命令看到:

    $ while true; do curl http://130.211.109.222; done
    

    结果显示并没有请求被切换到 v3 pod,为什么呢?让我们先列出所有pod:

    $ kubectl get po
    

    可以看到,有一个pod并没有处于就绪状态,这是为什么呢?

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

    当新的pod启动时,就绪探针会每隔一秒发起请求(在pod spec中,就绪探针的间隔被设置为1秒)。在就绪探针发起第五个请求的时候会出现失败,因为应用从第五个请求开始一直返回HTTP状态码500。

    因此,pod会从Service的endpoint中移除。当执行 curl 循环请求服务时,pod已经被标记为未就绪。这就解释了为什么 curl 发出的请求不会切换到新的pod。这正是符合预期的,因为你不希望客户端流量会切换到一个无法正常工作的pod。

    rollout status 命令显示只有一个新副本启动,之后滚动升级过程没有再继续下去,因为新的pod一直处于不可用状态。即使变为就绪状态之后,也至少需要保持10秒,才是真正可用的。在这之前滚动升级过程将不再创建任何新的pod,因为当前 maxUnavailable 属性设置为0,所以也不会删除任何原始的pod。

    实际上部署过程自动被阻止是一件好事。如果继续用新的pod替换旧的pod,那么最终服务将处于完全不能工作的状态,就和当时没有使用就绪探针的情况下滚动升级v3 版本时出现的结果一样。但是添加了就绪探针之后,升级出错程序而对用户造成的影响面不会过大,对比之前替换所有pod的方式,只有一小部分用户受到了影响。

    提示 如果只定义就绪探针没有正确设置 minReadySeconds,一旦有一次就绪探针调用成功,便会认为新的pod已经处于可用状态。因此最好适当地设置 minReadySeconds 的值。

    为滚动升级配置deadline

    默认情况下,在10分钟内不能完成滚动升级的话,将被视为失败。如果运行 kubectl describe deployment 命令,将会显示一条 ProgressDeadlineExceeded 的记录,如下面的代码清单所示。

    代码清单9.12 使用 kubectl describe 查看Deployment的详细情况

    $ kubectl describe deploy kubia
    

    判定Deployment滚动升级失败的超时时间,可以通过设定Deployment spec中的 progressDeadlineSeconds 来指定。

    注意 extensions/v1beta1 版本不会设置默认的deadline。

    取消出错版本的滚动升级

    因为滚动升级过程不再继续,所以只能通过 rollout undo 命令来取消滚动升级:

    $ kubectl rollout undo deployment kubia
    

    注意 在后续的版本中,如果达到了 progressDeadlineSeconds 指定的时间,则滚动升级过程会自动取消。

    相关文章

      网友评论

        本文标题:9.3 使用Deployment声明式地升级应用

        本文链接:https://www.haomeiwen.com/subject/sbeumltx.html