Skip to content
欢迎扫码关注公众号

基于 Kubernetes 的云原生 DevOps 第 13 章 开发流程


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 ,则需要按照如下进行设置:

yaml
apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 1
  strategy:
    type: Recreate

滚动更新

如果采用滚动更新,则一次只能升级一个或几个 Pod,直到所有副本都被替换成新版本。这就是零停机时间部署

**在滚动更新期间,应用程序的新旧版本在同时服务用户。**虽然一般情况下不会出现问题,但你可能还是需要采取措施来确保安全。例如,如果更新设计数据库的迁移,则不能进行常规的滚动更新。

如果 Pod 在进入准备就绪状态后的短时间内偶尔崩溃或失败,则请使用 minReadySeconds 字段,确保推出过程会一直等到每个 Pod 稳定后再继续。

Recreate 模式

在 Recreate 模式下,所有正在运行的副本会被立即终止,然后创建新副本。

只有不需要直接处理请求的应用程序才可以采用这种模式。Recreate 的优势在于,它避免了两个不同版本的应用程序同时运行的情况。

maxSurge 和 maxUnavailable

  • maxSurge :设置过量 Pod 的最大值。
  • maxUnavailable :设置不可用 Pod 的最大数量。

这两个值可以设置为整数或百分比:

yaml
apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 20%
      maxUnavailable: 3

一般情况下,使用二者的默认值就很好( 25%1 ,具体取决于 Kubernetes 的版本),并不需要调整。

maxSurge 的值越大,推出的速度就越快,但是对集群资源造成的额外负载就越多。
maxUnavailable 的值越大,部署的速度就越快,但会牺牲应用程序的容量。

反之,这两个值越小,对集群和用户造成的影响就越小,但部署所需的时间越长。

蓝绿部署

蓝绿部署不需要一次性干掉所有的 Pod 然后再替换,我们可以创建一个全新的部署,并单独启动一系列 v2 版本的 Pod,与 v1 部署并存。

优点:不必面对新旧版本应用程序同时处理请求的局面;
缺点:你的集群必须足够大,才能运行双倍的应用程序所需副本,代价非常昂贵,并且意味着大部分时间都有大量未使用的容量。

示例:将流量发送到标记了 deployment: blue 的 Pod 上:

yaml
apiVersion: v1
kind: Service
metadata:
  name: demo
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: demo
    deployment: blue
  type: ClusterIP

部署新版本时,你可以将其标记为 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 应用程序的数据库迁移作业的示例:

yaml
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

helm.sh/hook 属性的定义位于 annotations 部分:

yaml
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-delete-policy": hook-succeeded

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 的作业前面运行:

yaml

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"

有关钩子的详细信息,请参照 Helm 文档

13.4 小结

  • Kubernetes 默认的 RollingUpdate 部署策略一次只能升级几个 Pod,需要等到每个新 Pod 准备就绪后,才会关闭旧 Pod。
  • 滚动更新可以避免停机时间,但代价是延长了部署时间。这也意味着应用程序的新旧版本将在更新推出期间同时运行。
  • 你可以通过调整 maxSurgemaxUnavailable 字段来微调滚动更新。根据你所使用的 Kubernetes API 的版本,默认值可能适合你的情况,但也可有可能不合适。
  • Recreate 策略会一次性用新的 Pod 替换掉所有旧 Pod。这种方式虽然很快,但会引发停机,因此不适合面向用户的应用程序。
  • 在蓝绿部署中,新 Pod 不会收到任何用户流量,必须等到所有新 Pod 均启动完毕且准备就绪之后,所有流量一口气切换到新 Pod,然后再让所有旧 Pod 退出。
  • 彩虹部署类似蓝绿部署,只不过同时提供服务的版本有两个以上。
  • 为了在 Kubernetes 中实现蓝绿部署和彩虹部署,你可以通过调整 Pod 上的标签以及修改前端服务上的选择器,将流量定向到适当的 Pod 上。
  • Helm 钩子提供了一种在部署的特定阶段应用某些 Kubernetes 资源(通常是作业)的方法,例如运行数据库迁移等。钩子可以定义部署期间应用资源的顺序,并在某些操作未能成功时暂停部署。