声明式部署模式的核心是 Kubernetes 的 Deployment 资源。这个抽象概念封装了容器组的升级和回滚过程,使其执行成为一种可重复且自动化的活动。
问题
我们可以通过自助服务的方式将隔离环境配置为命名空间,并通过调度器将应用程序放置在这些环境中,几乎无需人为干预。但是,随着微服务数量的不断增加,持续更新和替换它们为新版本也变得越来越繁重。
将服务升级到新版本涉及一系列活动,如启动新版本的 Pod、优雅地停止旧版本的 Pod、等待并验证新版本是否成功启动,有时在失败的情况下需要将其回滚到以前的版本。这些活动要么是在允许一些停机时间的情况下执行,从而不运行并发的服务版本,要么是在没有停机时间的情况下执行,但会由于更新过程中同时运行两个版本的服务而增加资源使用。手动执行这些步骤可能会导致人为错误,而正确编写脚本则可能需要大量的精力,这两者都可能很快将发布过程变成一个瓶颈。
解决方案
幸运的是,Kubernetes 也自动化了应用程序的升级过程。通过使用 Deployment 的概念,我们可以描述应用程序应该如何更新,使用不同的策略并调整更新过程的各个方面。如果考虑到每个发布周期内,您为每个微服务实例执行多个 Deployment(这取决于团队和项目,可能从几分钟到几个月不等),这无疑是 Kubernetes 提供的又一节省精力的自动化功能。
在第 2 章“可预测的需求”中,我们看到,为了有效地完成其任务,调度器需要主机系统上有足够的资源、适当的放置策略,以及适当定义资源配置文件的容器。同样,为了正确执行 Deployment,它期望容器能够成为良好的云原生公民。Deployment 的核心功能是能够可预测地启动和停止一组 Pod。为了让这一切按预期工作,容器通常需要监听并遵守生命周期事件(如 SIGTERM;参见第 5 章“受管生命周期”),并提供健康检查端点(如第 4 章“健康检查”所述),以指示它们是否成功启动。
如果容器在这两个方面都做得准确,平台就可以干净地关闭旧容器并通过启动更新的实例来替换它们。然后,更新过程的所有其余方面都可以以声明式方式定义,并作为一个具有预定义步骤和预期结果的原子操作执行。让我们来看看容器更新行为的选项。
使用 kubectl rollout 进行 Deployment 更新
在 Kubernetes 以前的版本中,滚动更新是在客户端侧通过 kubectl rolling-update 命令实现的。在 Kubernetes 1.18 中,rolling-update 被移除,取而代之的是 kubectl 的 rollout 命令。两者的区别在于,kubectl rollout 通过更新 Deployment 声明来在服务器端管理应用程序更新,并由 Kubernetes 执行更新。相反,kubectl rolling-update 是命令式的:客户端 kubectl 会告诉服务器每个更新步骤该做什么。
一个 Deployment 可以通过更新 Kubernetes 资源文件完全管理。然而,kubectl rollout 在日常的更新任务中非常方便:
kubectl rollout status:显示Deployment更新的当前状态。kubectl rollout pause:暂停滚动更新,以便在不触发另一次更新的情况下对Deployment进行多次更改。kubectl rollout resume:恢复先前暂停的更新。kubectl rollout undo:将Deployment回滚到先前的修订版本。回滚在更新过程中出错时非常有用。kubectl rollout history:显示Deployment的可用修订版本。kubectl rollout restart:不执行更新,但使用配置的滚动策略重新启动属于Deployment的当前 Pod 集合。
您可以在示例中找到 kubectl rollout 命令的使用示例。
滚动部署
在 Kubernetes 中,更新应用程序的声明式方法是通过 Deployment 的概念。在幕后,Deployment 创建一个支持基于集合标签选择器的 ReplicaSet。此外,Deployment 抽象允许您使用诸如 RollingUpdate(默认)和 Recreate 等策略来塑造更新过程的行为。示例 3-1 显示了为滚动更新策略配置 Deployment 的重要部分。
示例 3-1. 滚动更新的 Deployment 配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: random-generator
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
minReadySeconds: 60
selector:
matchLabels:
app: random-generator
template:
metadata:
labels:
app: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
readinessProbe:
exec:
command: [ "stat", "/random-generator-ready" ]
- 声明了三个副本。滚动更新需要多个副本才有意义。
- 在更新期间,临时运行的 Pod 数量可以超过指定的副本数。在此示例中,最多可以有四个副本。
- 在更新期间可能不可用的 Pod 数量。在此示例中,更新期间可能只有两个 Pod 可用。
- 滚动更新过程中,所有健康检查探测在经过配置的秒数后必须成功,才能继续进行滚动更新。
- 健康检查探测对于滚动部署至关重要,以确保零停机时间——不要忘记它们(详见第 4 章“健康检查”)。
RollingUpdate 策略行为确保在更新过程中没有停机时间。在幕后,Deployment 实现通过创建新的 ReplicaSet 并用新的容器替换旧的容器来执行类似操作。此处的一个改进是,通过 Deployment,可以控制新容器推出的速度。Deployment 对象允许您通过 maxSurge 和 maxUnavailable 字段控制可用和多余 Pod 的范围。
这两个字段可以是 Pod 的绝对数量,也可以是相对于 Deployment 配置的副本数量的百分比,并四舍五入为下一个整数值。默认情况下,maxSurge 和 maxUnavailable 都设置为 25%。
另一个影响滚动更新行为的重要参数是 minReadySeconds。该字段指定在滚动更新中,一个 Pod 的健康检查探测成功后需要经过的秒数,直到该 Pod 被认为是可用的。增加此值可以确保您的应用程序 Pod 在继续滚动更新之前成功运行了一段时间。此外,较长的 minReadySeconds 间隔有助于调试和探索新版本。当更新步骤之间的间隔较大时,kubectl rollout pause 可能更容易利用。
图 3-1 显示了滚动更新过程。
要触发声明式更新,您有三种选择:
- 使用
kubectl replace将整个 Deployment 替换为新版本的 Deployment。 - 使用
kubectl patch进行补丁操作或使用kubectl edit进行交互式编辑,设置新版本的容器镜像。 - 使用
kubectl set image在 Deployment 中设置新的镜像。
您还可以参阅我们仓库中的完整示例,该示例演示了这些命令的用法,并展示了如何使用 kubectl rollout 监控或回滚升级。
除了解决命令式服务部署方式的缺点之外,Deployment 还有以下优点:
- Deployment 是一个 Kubernetes 资源对象,其状态完全由 Kubernetes 内部管理。整个更新过程都在服务器端进行,无需客户端干预。
- Deployment 的声明式特性指定了已部署状态应如何呈现,而不是获得该状态的必要步骤。
- Deployment 定义是一个可执行对象,而不仅仅是文档。它可以在多个环境中进行尝试和测试,直到最终进入生产环境。
- 更新过程也会被完全记录和版本化,并提供暂停、继续和回滚到以前版本的选项。
固定部署
滚动更新策略有助于在更新过程中确保零停机时间。然而,这种方法的副作用是,在更新过程中,会同时运行两个版本的容器。当更新过程引入了与之前版本不兼容的服务 API 更改,而客户端无法处理这些更改时,这可能会给服务消费者带来问题。对于这种情况,您可以使用 Recreate 策略,如图 3-2 所示。
Recreate 策略的效果是将 maxUnavailable 设置为声明的副本数。这意味着它首先杀死当前版本的所有容器,然后在旧容器被驱逐后同时启动所有新容器。这一过程的结果是,在所有旧版本容器停止且没有新容器准备好处理传入请求的期间会出现停机。好的一面是,不会同时运行两个不同版本的容器,因此服务消费者只能连接到一个版本的服务。
蓝绿发布
蓝绿部署是一种在生产环境中部署软件的发布策略,旨在通过最小化停机时间和降低风险来实现无缝过渡。Kubernetes 的 Deployment 抽象是一个基本概念,它允许您定义 Kubernetes 如何从一个版本无缝过渡到另一个版本的不可变容器。我们可以将 Deployment 原语作为构建块,结合其他 Kubernetes 原语,来实现这种更高级的发布策略。
然而,如果没有使用如服务网格或 Knative 之类的扩展,蓝绿部署需要手动完成。从技术上讲,它的工作原理是创建一个包含最新版本容器的第二个 Deployment(我们称之为 green),但此时它还不处理任何请求。在这个阶段,原始 Deployment 的旧 Pod 副本(称为 blue)仍然在运行并处理实时请求。
一旦我们确认新版本的 Pod 健康且准备好处理实时请求,我们就可以将流量从旧的 Pod 副本切换到新的副本。在 Kubernetes 中,您可以通过更新 Service 的选择器,使其与新容器(标记为 green)匹配来实现这一点。如图 3-3 所示,一旦 green(v1.1)容器处理了所有流量,blue(v1.0)容器就可以被删除,释放资源以供未来的蓝绿部署使用。
蓝绿发布的一个优点是,在任何时候只有一个版本的应用程序在处理请求,这减少了服务消费者处理多个并发版本的复杂性。缺点是,在蓝色(blue)和绿色(green)容器同时运行时,需要两倍的应用程序容量。此外,在过渡期间,长时间运行的进程和数据库状态漂移可能会导致显著的复杂性。
金丝雀发布
金丝雀发布是一种将新版本的应用程序逐步部署到生产环境中的方法,通过仅替换一小部分旧实例为新实例来实现。这种技术通过让只有一部分消费者接触到更新版本,从而降低了将新版本引入生产环境的风险。当我们对服务的新版本以及它在少量用户中的表现感到满意时,可以在金丝雀发布之后的额外步骤中,将所有旧实例替换为新版本。图 3-4 展示了金丝雀发布的实际操作。
在 Kubernetes 中,可以通过创建一个具有较小副本数的新 Deployment 作为金丝雀实例来实现这一技术。在此阶段,Service 应将一部分消费者引导到更新后的 Pod 实例。在金丝雀发布之后,一旦我们确认新 ReplicaSet 的一切工作如预期般顺利,我们可以将新 ReplicaSet 扩展,并将旧 ReplicaSet 缩减到零。在某种程度上,我们是在执行一个受控且经过用户测试的增量部署。
讨论
Deployment 原语是 Kubernetes 将手动更新应用程序的繁琐过程转变为一种可重复和自动化的声明式活动的一个例子。开箱即用的部署策略(滚动更新和重新创建)控制着新容器替换旧容器的过程,而高级发布策略(蓝绿发布和金丝雀发布)则控制新版本如何向服务消费者开放。后两种发布策略基于人工决策来触发过渡,因此并不是完全由 Kubernetes 自动化的,而是需要人工干预。图 3-5 总结了这些部署和发布策略,并展示了在过渡期间的实例数量。
所有软件都是不同的,部署复杂系统通常需要额外的步骤和检查。本章讨论的技术涵盖了 Pod 的更新过程,但不包括更新和回滚其他 Pod 依赖项,如 ConfigMaps、Secrets 或其他依赖服务。
部署前后钩子(PRE AND POST DEPLOYMENT HOOKS)
过去,有一个提议允许 Kubernetes 在部署过程中使用钩子。前后钩子(Pre 和 Post hooks)可以在 Kubernetes 执行部署策略之前和之后执行自定义命令。这些命令可以在部署过程中执行额外的操作,并能够中止、重试或继续部署。这些钩子是朝着新自动化部署和发布策略迈出的重要一步。不幸的是,这项工作自 2023 年以来已经停滞了,因此尚不清楚这一功能是否会最终加入 Kubernetes。
目前可行的一种方法是创建一个脚本,使用本书中讨论的 Deployment 和其他原语来管理服务及其依赖项的更新过程。然而,这种描述每个更新步骤的命令式方法与 Kubernetes 的声明式特性不匹配。
作为替代方案,一些更高层次的声明式方法已经在 Kubernetes 之上出现。以下边栏中描述的最重要的平台就是例子。这些技术使用操作器(参见第 28 章“操作器”),它们接受滚动更新过程的声明式描述,并在服务器端执行必要的操作,其中一些还包括在更新出错时的自动回滚功能。对于高级的、可用于生产环境的滚动更新场景,建议您了解这些扩展。
高层次的部署(HIGHER-LEVEL DEPLOYMENTS)
Deployment 资源是 ReplicaSets 和 Pods 之上的一个良好抽象,允许简单的声明式滚动更新,可以通过少量参数进行调整。然而,正如我们所见,Deployment 并不直接支持更复杂的策略,如金丝雀发布或蓝绿部署。有更高层次的抽象通过引入新的资源类型来增强 Kubernetes,允许声明更灵活的部署策略。这些扩展都利用了第 28 章描述的操作器模式,并引入了自定义资源来描述所需的滚动更新行为。
截至 2023 年,支持高层次部署的最著名的平台包括以下几个:
- Flagger
Flagger 实现了几种部署策略,是 Flux CD GitOps 工具的一部分。它支持金丝雀发布和蓝绿部署,并与许多入口控制器和服务网格集成,以在应用程序的旧版和新版之间提供必要的流量拆分。它还可以根据自定义指标监控滚动更新过程的状态,并检测更新失败以触发自动回滚。 - Argo Rollouts
Argo 工具家族中的这一部分专注于为 Kubernetes 提供全面且独特的持续交付(CD)解决方案。Argo Rollouts 支持高级部署策略,如 Flagger,并与许多入口控制器和服务网格集成。它的功能与 Flagger 非常相似,因此选择使用哪个工具应基于您更喜欢的 CD 解决方案,即 Argo 还是 Flux。 - Knative
Knative 是 Kubernetes 之上的无服务器平台。Knative 的核心功能之一是流量驱动的自动扩展支持,详见第 29 章“弹性扩展”。Knative 还提供了简化的部署模型和流量拆分,这对于支持高级部署滚动更新非常有帮助。虽然其滚动更新或回滚的支持不如 Flagger 或 Argo Rollouts 高级,但仍比 KubernetesDeployments的滚动更新功能有显著改进。如果您已经在使用 Knative,分割两个应用版本之间的流量是一个很好的替代Deployments的方式。
与 Kubernetes 一样,这些项目都是云原生计算基金会(CNCF)项目的一部分,并且拥有出色的社区支持。
无论您使用何种部署策略,对于 Kubernetes 来说,了解您的应用程序 Pod 何时启动并运行以执行达到定义目标部署状态所需的步骤顺序至关重要。下一章“健康检查”(Health Probe)将描述您的应用程序如何向 Kubernetes 传达其健康状态。
更多信息
- 声明式部署示例
- 执行滚动更新
- Deployments
- 使用 Deployment 运行无状态应用程序
- 蓝绿部署
- 金丝雀发布
- Flagger:部署策略
- Argo Rollouts
- Knative:流量管理