基于 Kubernetes 的云原生 DevOps 第 13 章 开发流程
🏷️ Kubernetes 《基于 Kubernetes 的云原生 DevOps》
Surfing is such an amazing concept. You're taking on Nautre with a little stick and saying, I'm gonna ride you! And a lot of times Nature says, No you're not! and crashes you to the bottom.
-- Jolene Blalock
13.1 开发工具
Skaffold
Google 的一款开源工具,旨在提供快速的本地开发工作流程。在本地开发时,它会自动重建容器,并将这些更改部署到本地或远程集群。
你可以在自己的代码库中通过 skaffold.yaml 定义所需的工作流程,然后运行 skaffold 命令行工具启动流水线。在修改本地目录中的文件时,Skaffold 就会触发,根据改动构建一个新容器,然后自动部署,因此你不需要自己处理仓库等事宜。
Draft
Draft 是微软 Azure 团队维护的一款开源工具。与 Skaffold 类似,在修改代码时,它可以利用 Helm 自动将更新部署到集群。
Draft 还引入了 Draft Pack 的概念。Draft Pack 是提前编写好的 Dockerfile 和 Helm Chart,支持应用程序常用的多种编程语言。目前支持的编程语言包括 .NET、Go、Node、Erlang、Clojure、C#、PHP、Java、Python、Rust、Swift 和 Ruby。
运行 draft init && draft create
,Draft 就会检查本地应用程序目录中的文件,并尝试确定代码使用的语言。然后根据语言创建 Dockerfile 和 Helm Chart。
运行 draft up
命令即可应用 Dockerfile 和 Helm Chart。Draft 会使用它创建的 Dockerfile 构建本地 Docker 容器,并将其部署到你的 Kubernetes 集群。
Telepresence
Telepresence Pod 会在实际的集群中作为应用程序的占位符运行。然后,它会拦截发往应用程序 Pod 流量,并将其路由到在本地计算机上运行的容器。
这样,开发人员的本地计算机就可以加入远程集群。应用程序代码的改动会直接反应到实际的集群中,而不需要部署新的容器。
Knative
Knative 的目标是提供一种将本地工作负载部署到 Kubernetes 的标准机制,不仅包括容器化的应用程序,还包括无服务器风格的函数。
Knative 同时集成了 Kubernetes 和 Istio,提供一个完整的应用程序 / 功能部署平台,包括设置构建过程、自动部署以及事件处理机制,以标准化应用程序使用消息传递和排队系统的方式(比如 Pub/Sub、Kafka 或 RabbitMQ 等)。
Knative 项目尚处于初期阶段,但我们非常期待。
13.2 部署策略
在 Kubernetes 中,你可以选择最合适的部署策略。 RollingUpdate 是一种零停机时间、逐个 Pod 处理的方案,而 Recreate 则是一次性快速升级所有 Pod 的方案。此外,你还可以调整一些字段来满足应用程序的需求。
在 Kubernetes 中,应用程序的部署策略定义在部署清单中。默认策略是 RollingUpdate ,因此如果你不指定策略,那么 Kubernetes 就会使用这个默认值。如果想将策略更改为 Recreate ,则需要按照如下进行设置:
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 1
strategy:
type: Recreate
2
3
4
5
6
滚动更新
如果采用滚动更新,则一次只能升级一个或几个 Pod,直到所有副本都被替换成新版本。这就是零停机时间部署。
**在滚动更新期间,应用程序的新旧版本在同时服务用户。**虽然一般情况下不会出现问题,但你可能还是需要采取措施来确保安全。例如,如果更新设计数据库的迁移,则不能进行常规的滚动更新。
如果 Pod 在进入准备就绪状态后的短时间内偶尔崩溃或失败,则请使用 minReadySeconds 字段,确保推出过程会一直等到每个 Pod 稳定后再继续。
Recreate 模式
在 Recreate 模式下,所有正在运行的副本会被立即终止,然后创建新副本。
只有不需要直接处理请求的应用程序才可以采用这种模式。Recreate 的优势在于,它避免了两个不同版本的应用程序同时运行的情况。
maxSurge 和 maxUnavailable
- maxSurge :设置过量 Pod 的最大值。
- maxUnavailable :设置不可用 Pod 的最大数量。
这两个值可以设置为整数或百分比:
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 20%
maxUnavailable: 3
2
3
4
5
6
7
8
9
一般情况下,使用二者的默认值就很好( 25% 或 1 ,具体取决于 Kubernetes 的版本),并不需要调整。
maxSurge 的值越大,推出的速度就越快,但是对集群资源造成的额外负载就越多。
maxUnavailable 的值越大,部署的速度就越快,但会牺牲应用程序的容量。
反之,这两个值越小,对集群和用户造成的影响就越小,但部署所需的时间越长。
蓝绿部署
蓝绿部署不需要一次性干掉所有的 Pod 然后再替换,我们可以创建一个全新的部署,并单独启动一系列 v2 版本的 Pod,与 v1 部署并存。
优点:不必面对新旧版本应用程序同时处理请求的局面;
缺点:你的集群必须足够大,才能运行双倍的应用程序所需副本,代价非常昂贵,并且意味着大部分时间都有大量未使用的容量。
示例:将流量发送到标记了 deployment: blue 的 Pod 上:
apiVersion: v1
kind: Service
metadata:
name: demo
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app: demo
deployment: blue
type: ClusterIP
2
3
4
5
6
7
8
9
10
11
12
13
部署新版本时,你可以将其标记为 deployment: green ,这样即使新版本完全启动并处于运行状态,也不会收到任何流量,因为服务只会发送到 blue Pod 上。你可以对其进行测试并确保它已经准备就绪,然后再进行切换。
如果想切换到新部署,则请编辑服务,将选择器改为 deployment: blue ,那么新的 green Pod 就会开始接收流量,等到所有旧的 blue Pod 都处于空闲状态后再将其关闭。
彩虹部署
在极少数情况下,尤其是当 Pod 会建立长时间的连接(如 websocket )时,仅靠蓝绿部署可能还不够。你可能需要同时维护三个或更多版本的应用程序。这种部署有时被称为彩虹部署。每次部署更新时,你都需要建立一套新颜色的 Pod。等到最终排空旧 Pod 集中的连接后,才可以关闭。
Brandon Dimcheff 在这篇文章中详细描述了彩虹部署的示例。
金丝雀部署
蓝绿(或彩虹)部署的优点是,如果你不喜欢新版本,或者新版本的行为不正确,则只需切换回仍在运行的旧版本即可。但是,这种方法非常昂贵,因此你需要足够的容量同时运行两个版本。
金丝雀部署可以避免这个问题,将几个 Pod 暴露在危险的生产世界中,观察它们的状况。如果这几个 Pod 存活下来,则部署可以继续完成。如果有问题,则可以严格控制影响范围。
与蓝绿部署一样,你可以使用标签来完成此操作。Kubernetes 的文档提供了运行金丝雀部署的详细示例。
还有一种更复杂的方法是使用 Istio,它允许随机将一部分流量路由到服务的一个或多个版本上。这种方法也可以用于执行 A/B 测试。
13.3 使用 Helm 处理迁移
无状态程序易于部署和升级,但是当设计数据库时,情况就会更加复杂。数据库架构的变更通常需要在部署的特定时间点上运行迁移任务。
在 Kubernetes 上,你可以使用作业资源来执行此操作。你可以使用 kubectl 命令编写脚本并将其作为升级过程的一部分,或者,如果你使用的是 Helm,则可以使用一种名叫钩子的内置功能。
Helm 钩子
Helm 的钩子允许你控制部署期间各种操作发生的顺序。如果出现问题,还可以放弃升级。
使用 Helm 部署 Rails 应用程序的数据库迁移作业的示例:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Values.appName }}-db-migrate
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-delete-policy": hook-succeeded
spec:
activeDeadlineSeconds: 60
template:
spec:
restartPolicy: Never
containers:
- name: {{ .Values.appName}}-migration-job
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
command:
- bundle
- exec
- rails
- db:migrate
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
helm.sh/hook 属性的定义位于 annotations 部分:
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-delete-policy": hook-succeeded
2
3
pre-upgrade 设置告诉 Helm 在升级之前应用这个作业清单。而这个作业将运行标准的 Rails 迁移命令。
"helm.sh/hook-delete-policy": hook-succeeded
告诉 Helm 如果作业成功完成(以状态 0 退出),则删除该作业。
处理失败的钩子
如果作业返回一个非 0 的退出代码,则表明出错且迁移未成功。Helm 会让作业保持失败状态,方便你调试问题。
如果发生这种情况,发布过程将停止,而且应用程序不会被升级。运行 kubectl get pods
命令会显示失败的 Pod,你可以检查日志,看看出了什么问题。
在问题得到解决后,你可以删除失败的作业(命令:kubectl delete job <作业名称>
,然后重新尝试升级。
其它钩子
钩子的作用不仅限于 pre-upgrade (升级前)阶段。发布的下列阶段都可以使用钩子:
- pre-install(安装前):在模板渲染之后,创建任何资源之前执行。
- post-install(安装后):在所有资源加载完成后执行。
- pre-delete(删除前):在接到删除请求,但在实际删除任何资源之前执行。
- post-delete(删除后):在接到删除请求,并删除了所有发布的资源之后执行。
- pre-upgrade(升级前):在模板渲染之后,加载任何资源之前执行(比如在 kubectl apply 操作之前)。
- post-upgrade(升级会):所有资源都已升级后执行。
- pre-rollback(回滚前):在模板渲染之后接到回滚请求,但在回滚任何资源之前执行。
- post-rollback(回滚后):在接到回滚请求,并对所有资源完成修改之后执行。
- crd-install :Adds CRD resources before any other checks are run. This is used only on CRD definitions that are used by other manifests in the chart.
- test-success :Executes when running helm test and expects the pod to return successfully (return code == 0).
- test-failure :Executes when running helm test and expects the pod to fail (return code != 0).
JiaJia:
后面 3 种是从官方文档中复制过来的。
钩子连接
Helm 还能够使用 helm.sh/hook-weight 属性将钩子按照一定的顺序连接在一起。这些钩子将按照从低到高依次运行, hook-weight 为 0 的作业将在 hook-weight 为 1 的作业前面运行:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Values.appName }}-stage-0
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-delete-policy": hook-succeeded
"helm.sh/hook-weight": "0"
2
3
4
5
6
7
8
9
有关钩子的详细信息,请参照 Helm 文档 。
13.4 小结
- Kubernetes 默认的 RollingUpdate 部署策略一次只能升级几个 Pod,需要等到每个新 Pod 准备就绪后,才会关闭旧 Pod。
- 滚动更新可以避免停机时间,但代价是延长了部署时间。这也意味着应用程序的新旧版本将在更新推出期间同时运行。
- 你可以通过调整 maxSurge 和 maxUnavailable 字段来微调滚动更新。根据你所使用的 Kubernetes API 的版本,默认值可能适合你的情况,但也可有可能不合适。
- Recreate 策略会一次性用新的 Pod 替换掉所有旧 Pod。这种方式虽然很快,但会引发停机,因此不适合面向用户的应用程序。
- 在蓝绿部署中,新 Pod 不会收到任何用户流量,必须等到所有新 Pod 均启动完毕且准备就绪之后,所有流量一口气切换到新 Pod,然后再让所有旧 Pod 退出。
- 彩虹部署类似蓝绿部署,只不过同时提供服务的版本有两个以上。
- 为了在 Kubernetes 中实现蓝绿部署和彩虹部署,你可以通过调整 Pod 上的标签以及修改前端服务上的选择器,将流量定向到适当的 Pod 上。
- Helm 钩子提供了一种在部署的特定阶段应用某些 Kubernetes 资源(通常是作业)的方法,例如运行数据库迁移等。钩子可以定义部署期间应用资源的顺序,并在某些操作未能成功时暂停部署。