Kubernetes:玩转 Pod 滚动更新

标签: dev | 发表时间:2021-01-08 00:00 | 作者:
出处:http://itindex.net/relian

前言

今天推荐一篇关于Kubernetes上服务滚动更新相关的配置选项的文章,文章列出了最常用的几个配置项,解释了他们是怎么影响调度器对服务进行滚动更新的,同时还带出了 Kubernetes项目中 Pod这个逻辑单元的 Ready状态是怎么确定的,并不是容器运行起来后 Pod就进入 Ready状态的。总之个人觉得是篇非常好的普及 Kubernetes基础的文章,文章由本人完全手工翻译,尽量做到通顺易懂,英文好的同学可以直接看原文。

原文标题:Kubernetes Deployments 滚动更新配置/ Kubernetes Deployments Rolling Update Configration.

发布时间:February 26, 2020

原文链接:https://www.bluematador.com/blog/kubernetes-deployments-rolling-update-configuration

文章作者:Keilan Jackson

文章译者:Keivn Yan

DeploymentKubernetes中一种常用的 Pod控制器,它对 Pod提供了细粒度的全面控制:如何进行 Pod配置、如何执行 Pod更新,应运行多少 Pod以及何时终止 Pod。有许多这方面的资源会教你如何配置 Deployment,但是你可能很难理解每个选项是如何影响滚动更新的执行方式的。在此博客文章中,我们将涵盖以下主题,以帮助您成为 Kubernetes Deployment的专家:

  • Kubernetes Deployment概貌;
  • Kubernetes服务的滚动更新;
  • 怎么定义 Pod的Ready状态;
  • Pod Affinity和Anti-Affinity;

Deployment 概貌

Deployment实质上是 ReplicaSet的上层包装。 ReplicaSet管理正在运行的 Pod数量, Deployment在其之上实现功能从而拥有了 Pod滚动更新,对Pod的运行状况进行健康检查以及轻松回滚更新的能力。在常规运行期间, Deployment仅管理一个 ReplicaSet,以确保期望数量的 Pod正在运行:

Deployment-Replicate-Pod之间的关系

Kubernetes里我们不应直接操作由 Deployment创建的 ReplicaSet,对 ReplicaSet执行的所有操作应改为在 Deployment上执行,然后由 Deployment管理更新 ReplicaSet的过程。以下是一些通常在 Deployment上执行的操作的示例 kubectl命令:

  # 列出默认命名空间下的所有Deployment   
kubectl get deploy

# 通过定义文件更新Deployment
kubectl apply -f test.yaml

# 监控"test"这个Deployment的状态更新
kubectl rollout status deploy/test

# 暂停"test"这个Deployment的更新流程:
kubectl rollout pause deploy/test

# 恢复"test"这个Deployment的更新流程:
kubectl rollout resume deploy/test

# 查看"test"这个Deployment的更新历史:
kubectl rollout history deploy/test

# 回退test最近的更新
kubectl rollout undo deploy/test

# 把test这个Deployment回滚到指定版本
kubectl rollout undo deploy/test --to-revision=1

Pod的滚动更新

使用 Deployment来控制 Pod的主要好处之一是能够执行滚动更新。滚动更新允许你逐步更新 Pod的配置,并且 Deployment提供了许多选项来控制滚动更新的过程。

控制滚动更新最重要的选项是更新策略。在 DeploymentYAML定义文件中,由 spec.strategy.type字段指定 Pod的滚动更新策略,它有两个可选值:

  • RollingUpdate (默认值):逐步创建新的Pod,同时逐步终止旧Pod,用新Pod替换旧Pod。
  • Recreate:在创建新Pod前,所有旧Pod必须全部终止。

大多数情况下, RollingUpdateDeployment的首选更新策略。如果你的 Pod需要作为单例运行,并且不允许在任何时间存在多副本,这种时候 Recreate更新策略会很有用。

使用 RollingUpdate策略时,还有两个选项可以让你微调更新过程:

  • maxSurge:在更新期间,允许创建超过期望状态定义的 Pod数的最大值。
  • maxUnavailable:在更新期间,容忍不可访问的 Pod数的最大值

maxSurgemaxUnavailable选项都可以使用整数(比如:2)或者百分比(比如:50%)进行配置,而且这两项不能同时为零。当指定为整数时,表示允许超期创建或者不可访问的 Pod数。当指定为百分比时,将使用期望状态里定义的 Pod数作为基数。比如:如果对 maxSurgemaxUnavailable都使用默认值25%,并且将更新应用于具有8个容器的 Deployment,那么意味着 maxSurge为2个容器, maxUnavailable也将为2个容器。这意味着在更新过程中,将满足以下条件:

  • 最多有10个 Pod(8个期望状态里指定的 Pod和2个 maxSurge允许超期创建的 Pod)在更新过程中处于Ready状态。
  • 最少有6个 Pod(8个期望状态里指定的 Pod和2个 maxUnavailable允许不可访问的 Pod)将始终处于Ready状态。

值得注意的一点是,在考虑 Deployment应在更新期间运行的 Pod数量时,使用的是在 Deployment的更新版本中指定的副本数,而不是现有 Deployment版本的期望状态中指定的副本数。

可以用另外一种方式理解这两个选项: maxSurge是一次将创建的新 Pod的最大数量, maxUnavailable是一次将被删除的旧 Pod的最大数量。让我们具体看一下使用以下更新策略将具有3个副本的 Deployment从"v1"更新为" v2"的过程:

  replicas: 3     
strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

这个更新策略是,我们想一次新建一个 Pod,并且应该始终保持 Deployment中的 Pod有三个是Ready状态。下面的动图说明了滚动更新的每一步都发生了什么。如果 Deployment看到 Pod已经完全部署好了将会把 Pod标记为Ready,创建中的 Pod标记为 NotReady,正在被删除的 Pod标记为Terminating。


Deployment滚动更新Pod的过程

怎么判读Pod是否Ready

Kubernetes自身实现了一个叫做Ready Pod的概念来辅助滚动更新。具体来说就是, ReadinessProbe(就绪探针)可以使 Deployment逐步更新 Pod,同时也可以使用它控制何时才能进行滚动更新, Service也使用它来确定应该将哪些 Pod包含在服务的Endpoints中。就绪探针与活动性探针相似但不相同,活性探针使 kubelet可以根据其“重新启动策略”来确定哪些 Pod需要重新启动,并且它们与就绪性探针分开配置,不会影响 Deployment的滚动更新的过程。

译者注:关于就绪探针和活性探针详细的解释可以看我以前的文章: 浅析Kubernetes Pod重启策略和健康检查

一个Ready状态的 Pod是指: Pod通过了就绪探针的测试,并且自创建以来经过了 spec.minReadySeconds指定的秒数则被视为已经 Ready。这些选项的默认值将导致 Pod内部的容器启动后 Pod立即进入Ready状态。

事实上,有几个原因通常让你并不想让容器启动后 Pod立即进入Ready状态:

  • 希望在接收流量前, Pod能够先通过健康检查。
  • 服务需要先预热,然后再提供流量。
  • 你要放慢部署速度,以减少对运行中的系统的影响。

对于 Web应用程序,要求通过健康检查非常常见,这对于以最小的中断执行更新至关重要。下面是为了对一个Web应用进行健康检查而配置的就绪探针:

  readinessProbe:   
          periodSeconds: 15
          timeoutSeconds: 2
          successThreshold: 2
          failureThreshold: 2
          httpGet:
            path: /health
            port: 80

这个探针要求对端口80上的 /health的调用在2秒内成功完成,每15秒执行一次,并且在 Pod准备就绪之前必须进行2次成功的调用。这意味着在最佳情况下, Pod将在约30秒内准备就绪。许多应用程序在启动后2秒钟之内无法立即提供服务,即使是简单的请求,因此应该为前1项或2次检查的失败做好准备,这种情况下实际需要约60秒的准备时间 Pod才能进入Ready状态。

您还可以配置在容器上执行命令的就绪探针。这让你可以编写可执行的自定义脚本,并确定 Pod是否已准备好, Deployment是否可以继续执行滚动更新:

  readinessProbe:   
          exec:
            command:
              - /startup.sh
          initialDelaySeconds: 5
          periodSeconds: 15
          successThreshold: 1

在这个配置中, DeploymentPod创建成功后将等待5秒钟,然后每15秒钟执行一次命令。脚本的exit code为0被认为是执行成功。使用命令脚本的灵活性让你可以执行以下类似操作,例如将数据加载到缓存中或预热JVM,或在不修改应用程序代码的情况下对下游服务进行运行状况检查。

我们将在这里讨论的最后一种情况是故意减慢更新过程,以最大程度地减少对系统的影响。乍一看似乎不需要,但是在某些情况下它可能非常有用,包括事件处理系统,监视工具和预热时间较长的 Pod。通过在 Deployment定义中指定 spec.minReadySeconds字段可以轻松实现此目标。当指定 minReadySeconds时, Pod必须运行这么多秒,而且其容器中的任何一个都不能崩溃,才能被 Deployment视为进入Ready状态。

比如说,假设一个 Deployment管控着5个 Pod副本,它们从事件流读取、处理事件,然后把数据保存在数据库中。每个 Pod需要预热60秒后才能全速处理事件,如果使用默认的选项值, Pod创建后立即进入Ready状态,但是它们在第一分钟内处理事件的速度会很慢。更新完成后,由于所有 Pod同时预热,因此事件将会堆积在事件流里面。相反,你可以将 maxSurge设置为1,将 maxUnavailable设置为0,将 minReadySeconds设置为60。这将确保一次创建一个新的 Pod,在经过一分钟预热后新建的 Pod才能进入Ready状态,并且旧 Pod在新 Pod就绪之前不会被删除。这样,就可以在大约5分钟的时间内更新所有 Pod,并且能保持更新期间服务的稳定。

Pod Affinity和Anti-Affinity

PodAffinityPodAntiAffinity这两个配置让你可以控制将 DeploymentPod调度到哪些节点上。尽管这个功能并非 Deployment控制器特有的,但对于许多应用程序来说可能非常有用。在配置 PodAffinityPodAntiAffinity时,你必须选择在不同环境条件下要添加给新建的 Pod的调度偏好。这里有两个选项:

  • requiredDuringSchedulingIgnoredDuringExecution:除非节点与 Affinity配置相匹配,否则即使没有匹配的节点,直接调度失败,也不会把 Pod调度到不匹配的节点上去。
  • preferredDuringSchedulingIgnoredDuringExecution:调度程序将尝试在与配置匹配的节点上调度Pod,但如果无法这样做,则仍将 Pod调度在另一个节点上。

podAffinity用于将 Pod调度到某些节点上。如果知道某个 Pod具有特定的资源要求(例如,只有一组特定的,带有GPU的节点或者某个区域中的节点),则通常需要进行 podAffinity配置。或者你希望 Pod与其他 Pod并置在一个节点上:

  affinity:   
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - cache
        topologyKey: "kubernetes.io/hostname"
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - web
          topologyKey: kubernetes.io/hostname

相反地, podAntiAffinity在确保同属一个 DeploymentPod不被调度到同一个节点上时非常有用,上面例子偏向于将标签为 app:webPod部署到不同的节点上,降低服务的所有 Pod因为节点出问题同时出故障的可能性。

使用 affinity配置时,要注意非常重要的一点是, affinity规则是在对 Pod进行调度时进行评估的。而调度器无法预见 Pod的调度位置,这意味着 affinity配置可能无法达到预期的效果。考虑有一个拥有3个节点的集群,一个拥有3个 Pod副本的使用了上面示例 affinity规则的 DeploymentDeploymentmaxSurge配置成1。你期望的调度目标可能是,在每个节点上运行 Deployment的一个 Pod,但是由于 maxSurge设置为1,在滚动更新期间调度器每次只能创建一个新 Pod。这意味着随着时间的流逝,你可能最终会得到一个更新后没有任何这些 Pod的节点,然后所有或大多数 Pod将在下一次更新时移至该节点。调度程序不知道你将要终止旧的Pod,在做 affinity规则判断时仍然会算上旧 Pod,这就是导致上面说的可能情况的原因。如果确实需要在每个节点上只能运行一个 Pod副本,则应使用 DaemonSets控制器。如果您的应用程序可以接受,另一种选择是将更新策略更改为“重新创建”。这样,当调度程序评估 affinity规则时,将不会算上旧的 Pod

podAffinityPodAntiAffinity有很多选项可以影响 Pod的调度方式,但通常无法保证滚动更新也能满足规则。在某些情况下,这是一个非常有用的功能,但是除非真的需要控制 Pod的运行位置,否则应让 kubernetes调度程序来做出这些决定。可以在此处 [1]找到有关 podAffinitypodAntiAffinity的详细文档。

总结

我们已经介绍了 Deployment的基本用法,滚动更新的工作方式以及用于微调更新和 Pod调度的许多配置选项。此时,你应该能够使用更新策略,就绪探针和 Pod关联性( affinity)来自信地创建和修改 Deployment的定义文件,以达到应用程序期望的状态。有关 Deployment支持的所有选项的详细参考,请查看 Kubernetes文档。

引用链接

[1]

此处:https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity

相关 [kubernetes pod 滚动更新] 推荐:

Kubernetes:玩转 Pod 滚动更新

- - IT瘾-dev
今天推荐一篇关于Kubernetes上服务滚动更新相关的配置选项的文章,文章列出了最常用的几个配置项,解释了他们是怎么影响调度器对服务进行滚动更新的,同时还带出了 Kubernetes项目中 Pod这个逻辑单元的 Ready状态是怎么确定的,并不是容器运行起来后 Pod就进入 Ready状态的.

解决Kubernetes Pod故障的5个简单的技巧

- - DockOne.io
——轻松地解决Kubernetes应用程序的故障并理解其机制. 在很多情况下,你可能会发现Kubernetes中的应用程序没有正确地部署,或者没有正常地工作. 今天这篇文章就提供了如何去快速解决这类故障以及一些技巧. 在阅读了这篇文章之后,你还将深入了解Kubernetes的内部机制,另外,我还将与大家分享一些关于自己操作K8S的一些非常有用的技巧.

Kubernetes & Microservice

- - 午夜咖啡
这是前一段时间在一个微服务的 meetup 上的分享,整理成文章发布出来. 谈微服务之前,先澄清一下概念. 微服务这个词的准确定义很难,不同的人有不同的人的看法. 比如一个朋友是『微服务原教旨主义者』,坚持微服务一定是无状态的 http API 服务,其他的都是『邪魔歪道』,它和 SOA,RPC,分布式系统之间有明显的分界.

kubernetes移除Docker?

- -
两周前,Kubernetes在其最新的Changelog中宣布1.20之后将要弃用dockershime,也就说Kubernetes将不再使用Docker做为其容器运行时. 这一消息持续发酵,掀起了不小的波澜,毕竟Kubernetes+Docker的经典组合是被市场所认可的,大量企业都在使用. 看上去这个“弃用”的决定有点无厘头,那么为什么Kubernetes会做出这样的决定.

深入掌握K8S Pod - Yabea - 博客园

- -
K8S configmap介绍. Pod是k8s中最小的调度单元,包含了一个“根容器”和其它用户业务容器. 如果你使用过k8s的话,当然会了解pod的基本使用,但是为了更好的应用,你需要深入了解pod的配置、调度、升级和扩缩容等. pod包含一个或多个相对紧密耦合的容器,处于同一个pod中的容器共享同样的存储空间、IP地址和Port端口.

Kubernetes 完全教程

- - 午夜咖啡
经过一个阶段的准备,视频版本的 《Kubernetes 完全教程》出炉了. 课程一共分为七节,另外有一节 Docker 预备课,每节课大约一个多小时. 目标是让从没接触过 Kubernetes 的同学也能通过这个课程掌握 Kubernetes. 为什么要学习 Kubernetes. 在介绍课程之前,先说说为什么要学习 Kubernetes 以及什么人需要学习 Kubernetes.

Kubernetes 监控详解

- - DockOne.io
【编者的话】监控 Kubernetes 并不是件容易的事. 本文介绍了监控 Kubernetes 的难点、用例以及有关工具,希望可以帮助大家进一步了解监控 Kubernetes. 如果想要监控 Kubernetes,包括基础架构平台和正在运行的工作负载,传统的监控工具和流程可能还不够用. 就目前而言,监控 Kubernetes 并不是件容易的事.

k8s外网如何访问业务应用之Service 池化pod

- - IT瘾-geek
一、废话:先讲述一个k8s重要概念,我觉得这个概念是整个k8s集群实现微服务的最核心的概念. Service定义了Pod的逻辑集合和访问该集合的策略,是真实服务的抽象. Service提供了一个统一的服务访问入口以及服务代理和发现机制,用户不需要了解后台Pod是如何运行. 只需要将一组跑同一服务的pod池化成一个service,k8s集群会自动给这个service分配整个集群唯一ip和端口号(这个端口号自己在yaml文件中定义),一个service定义了访问pod的方式,就像单个固定的IP地址和与其相对应的DNS名之间的关系.

监控 Pod 时,我们在监控什么

- - DockOne.io
应用 Kubernetes 化已经开始推进了一段时间,监控系统也提供了 Kubernetes Pod 相关的监控指标和警报规则. 由于 Kubernetes 和传统的物理机/虚拟机是完全不同的运行环境,因此监控系统提供的监控指标也存在一定的区别. 虽然我们已经尽量统一不同平台的差异,但是日常工作中仍会收到用户对 Kubernetes 监控指标的反馈.

在 k8s 中对指定 Pod 进行抓包

- -
Distributed Tracing时,遇到了一些问题,需要深入到底层去进行网络抓包分析报文. 但是应用时运行在 k8s 集群中的,与传统的在一台机器上跑一个进程直接通过 tcpdump 抓包方式略有不同. 最初对容器的理解不深刻认为一定要进入到这个容器抓包,而进入容器内并没有 tcpdump 等基础工具,相当于自己还是把容器当作虚拟机在看待.