声明式部署(Declarative Deployment)模式的核心是 Kubernetes 的 Deployment 资源。这个抽象封装了一组容器的升级和回滚过程,并使其执行成为一个可重复和自动化的活动。

问题描述

我们可以以自助服务的方式将隔离的环境作为命名空间进行调配,并通过调度器将服务放置在这些环境中,只需要最少的人工干预。但是随着微服务数量的不断增加,不断地更新和替换成新的版本也成为了一个越来越大的负担。

将一个服务升级到下一个版本涉及到启动 Pod 的新版本,优雅地停止旧版本的 Pod,等待并验证它已经成功启动,有时在失败的情况下还会全部回滚到以前的版本。这些活动的执行方式有两种,一种是允许一些停机时间,但不允许同时运行服务版本,另一种是不允许停机时间,但在更新过程中由于两个版本的服务都在运行而增加资源使用。手动执行这些步骤可能会导致人为错误,而正确地编写脚本也需要花费大量的精力,这两点都会使发布过程很快变成瓶颈。

解决方案

幸运的是,Kubernetes 也实现了这项活动的自动化。使用 Deployment 的概念,我们可以描述我们的应用程序应该如何更新,使用不同的策略,并调整更新过程的各个方面。如果你考虑到每个发布周期为每个微服务实例做多次 Deployment(这根据团队和项目的不同,可以从几分钟到几个月不等),这是 Kubernetes 的另一个省力的自动化能力。

在第 2 章中,我们已经看到,为了有效地完成它的工作,调度器需要主机系统上有足够的资源,适当的放置策略,以及具有充分定义的资源配置文件的容器。同样,为了使部署正确地完成其工作,它希望容器成为良好的云本地公民。部署的核心是以可预测的方式启动和停止一组 Pod 的能力。为了使这一工作符合预期,容器本身通常会监听和尊重生命周期事件(如 SIGTERM;请参见第 5 章 “托管生命周期”),并且还提供第 4 章 “健康探针” 中所述的健康检查端点,以指示它们是否成功启动。

如果一个容器准确地覆盖了这两个方面,平台就可以干净利落地关闭旧容器,并通过启动更新的实例来替换它们。然后,更新过程中所有剩余的方面都可以以声明的方式定义,并作为一个具有预定义步骤和预期结果的原子动作来执行。让我们看看容器更新行为的选项。

滚动部署(Rolling Deployment)

Kubernetes 中更新应用的声明方式是通过 Deployment 的概念。在幕后,Deployment 创建了一个 ReplicaSet,支持基于集合的标签选择器。同时,Deployment 抽象允许通过 RollingUpdate(默认)和 Recreate 等策略来塑造更新过程行为。例 3-1 显示了为滚动更新策略配置 Deployment 的重要部分。

  1. # 例 3-1 一个采用滚动更新策略的部署
  2. ---
  3. apiVersion: apps/v1
  4. kind: Deployment
  5. metadata:
  6. name: random-generator
  7. spec:
  8. # 声明三个副本,你需要一个以上的副本,滚动更新才有意义
  9. replicas: 3
  10. strategy:
  11. type: RollingUpdate
  12. rollingUpdate:
  13. # 除了在更新期间指定的副本之外,可以临时运行的 Pod 数量
  14. # 在这个例子中,它可能是在最大限度内总共有 3 + 1 = 4 个副本
  15. maxSurge: 1
  16. # 更新期间无法使用的 Pod 数量,这里可能是在更新期间一次只有 3 - 1 = 2 个 Pod 可用
  17. maxUnavailable: 1
  18. selector:
  19. matchLabels:
  20. app: random-generator
  21. template:
  22. metadata:
  23. labels:
  24. app: random-generator
  25. spec:
  26. containers:
  27. - image: k8spatterns/random-generator:1.0
  28. name: random-generator
  29. # 就绪探针对于滚动部署非常重要,可以提供零停机时间 - 不要忘记它们(参见第 4 章 “健康探针”)
  30. readinessProbe:
  31. exec:
  32. command: [ "stat", "/random-generator-ready" ]

RollingUpdate 策略行为确保了更新过程中没有停机时间。在幕后,Deployment 实现通过创建新的 ReplicaSet 和用新容器替换旧容器来执行类似的动作。这里的一个增强是,通过 Deployment,可以控制新容器推出的速度。Deployment 对象允许你通过 maxSurgemaxUnavailable 字段来控制可用和多余 Pod 的范围。图 3-1 显示了滚动更新过程。
image.png
图 3-1 滚动更新部署

要触发声明式更新,您有三个选项:

  • 通过 kubectl replace 用新版本的 Deployment 替换整个 Deployment。
  • 通过 kubectl patch 修改或 kubectl edit 交互式编辑 Deployment 来设置新版本的新容器镜像。
  • 使用 kubectl set image 来设置部署中的新镜像。

:::info 另请参见我们示例仓库中的完整示例,其中演示了这些命令的用法,并展示了如何使用 kubectl rollout 监控或回滚升级。 :::

除了解决前面提到的命令式部署服务的方式的缺点,Deployment 还带来了以下好处。

  • Deployment 是一个 Kubernetes 资源对象,其状态完全由 Kubernetes 内部管理。整个更新过程在服务器端进行,无需客户端交互。
  • Deployment 的声明性使你看到部署的状态应该是怎样的,而不是到达那里的必要步骤。
  • 更新过程也完全被记录下来,并通过暂停、继续和回滚到以前版本的选项进行版本调整。


原地部署(Fixed Deployment)

滚动更新(RollingUpdate)策略对于确保更新过程中的零停机时间非常有用。然而,这种方法的副作用是,在更新过程中,容器的两个版本同时运行。这可能会给服务消费者带来问题,特别是当更新过程在服务 API 中引入了向后不兼容的变化,而客户端又无法处理这些变化时。对于这种情况,Deployment 也提供了 Recreate 策略,如图 3-2 所示。
image.png
图 3-2 使用 Recreate 策略进行原地部署

Recreate 策略的效果是将 maxUnavailable 设置为声明的副本数量。这意味着它首先杀死当前版本的所有容器,然后在旧容器被驱逐时同时启动所有新容器。这一系列操作的结果是,当所有使用旧版本的容器被停止时,会有一些停机时间,而且没有新的容器准备好处理传入的请求。从积极的一面来看,不会有两个版本的容器同时运行,简化了服务消费者的生活,一次只需处理一个版本。

蓝绿发布(Blue-Green Release)

蓝绿部署是一种用于在生产环境中部署软件的发布策略,可以最大限度地减少停机时间,降低风险。Kubernetes 的 Deployment 抽象是一个基本概念,它可以让你定义 Kubernetes 如何将不可变容器从一个版本过渡到另一个版本。我们可以将 Deployment 作为一个构件,与其他 Kubernetes 基本要素租户在一起,实现蓝绿部署这种更高级的发布策略。

不过如果没有使用服务网格(Service Mesh)或 Knative 等扩展,则需要手动完成蓝绿部署。从技术上讲,它的工作原理是创建第二个 Deployment,容器的最新版本(我们称之为绿色)还没有服务任何请求。在这个阶段,原始 Deployment 中的旧 Pod 复制体(称为蓝色)仍在运行并服务于实时请求。

一旦我们确信新版本的 Pod 是健康的,并且准备好处理实时请求,我们就会将流量从旧的 Pod 复制体切换到新的复制体。Kubernetes 中的这个活动可以通过更新服务选择器来匹配新的容器(标记为绿色)来完成。如图 3-3 所示,一旦绿色容器处理了所有的流量,就可以删除蓝色容器,腾出资源用于未来的蓝绿部署。
image.png
图 3-3 蓝绿发布

蓝绿方法的一个好处是,只有一个版本的应用服务于请求,这降低了 Service 消费者处理多个并发版本的复杂性。缺点是它需要两倍的应用容量,而蓝色和绿色容器都在运行。另外,在过渡期间,可能会出现长期运行的进程和数据库状态漂移的重大并发症。

金丝雀发布(Canary Release)

金丝雀发布是一种通过用新的实例替换一小部分旧的实例来软性地将一个应用程序的新版本部署到生产中的方法。这种技术通过只让部分消费者达到更新的版本来降低将新版本引入生产的风险。当我们对新版本的服务以及它在小样本用户中的表现感到满意时,我们就会用新版本替换所有的旧实例。图 3-4 显示了一个金丝雀版本的运行情况。
image.png
图 3-4 金丝雀发布

在 Kubernetes 中,可以通过为新的容器版本(最好使用 Deployment)创建一个新的 ReplicaSet 来实现这一技术,该 ReplicaSet 的副本数量较少,可以作为 Canary 实例使用。在这个阶段,服务应该将一些消费者引导到更新的 Pod 实例上。一旦我们确信使用新 ReplicaSet 的每一件事情都能按预期工作,我们就会将新的 ReplicaSet 扩展,旧的 ReplicaSet 则降为零。从某种程度上来说,我们是在执行一个可控的、经过用户测试的增量部署。

一些讨论

Deployment 是 Kubernetes 将手动更新应用程序的繁琐过程转化为可重复和自动进行的声明性活动的一个例子。开箱即用的部署策略(Rolling 和 Recreate)控制新容器对旧容器的替换,而发布策略(Blue-Green 和 Canary)控制新版本如何提供给服务消费者。后两种发布策略基于过渡触发器的人工决策,因此不是完全自动化的,而是需要人工交互。图 3-5 显示了部署和发布策略的概要,显示了过渡期间的实例数。
image.png
图 3-5 不同部署和发布策略的概览

每个软件都是不同的,部署复杂的系统通常需要额外的步骤和检查。本章所讨论的技术涵盖了 Pod 更新过程,但不包括更新和回滚其他 Pod 依赖关系,如 ConfigMap、Secret 或其他依赖服务。

截至本文撰写之时,Kubernetes 有一项建议,允许在部署过程中使用钩子。前和后钩子将允许在 Kubernetes 执行部署策略之前和之后执行自定义命令。这些命令可以在部署进行时执行额外的操作,另外还能中止、重试或继续部署。这些命令是向新的自动化部署和发布策略迈出的良好一步。目前,一种行之有效的方法是在更高层次上编写更新过程的脚本,以使用本书中讨论的 Deployment 和其他基元来管理服务及其依赖关系的更新过程。

无论你使用何种部署策略,Kubernetes 都必须知道你的应用 Pod 何时启动并运行,以执行所需的步骤序列达到定义的目标部署状态。第 4 章中的下一个模式 “健康探针 “描述了你的应用程序如何将其健康状态传达给 Kubernetes。

参考资料