基于 Kubernetes 的云原生 DevOps 第 14 章 Kubernetes 的持续部署
🏷️ Kubernetes 《基于 Kubernetes 的云原生 DevOps》
道常无为而无不为。
-- 老子
14.1 什么是持续部署?
持续部署( Continuous Deployment, CD )是指将成功的构建自动部署到生产环境。与测试套件一样,部署也应该集中管理和自动化。开发人员只需一个按钮、合并一个请求或推送一个 Git 发布标签就可以部署新版本。
CD 常常与持续集成( Continuous Integration, CI )密不可分,后者的意思是针对开发人员对主线分支所作的更改进行自动集成和测试。基本思想是,如果在分支上完成的更改,在合并到主线时会破坏构建,则持续集成会立即通知你,因此你不必等到完成分支以及最终合并。
持续集成和持续部署的组合通常被称为 CI/CD 。
构建部署的机制通常称为流水线,即一系列的自动化操作,从开发人员的机器上拿到代码,通过一系列测试和验收阶段,最终推送到生产环境。
容器化应用程序最常见的流水线如下所示:
- 开发人员将代码变更推送到 Git。
- 构建系统自动构建当前版本的代码并运行测试。
- 如果所有测试均通过,则将容器镜像发布到中心容器仓库。
- 新建的容器自动部署到预发布环境。
- 预发布环境实施自动验收测试。
- 将经过验证后的容器镜像部署到生产。
CD 的最大优势在于,生产环境不会发生意外。只有在预发布环境中成功通过测试的二进制镜像才会被部署到生产环境。
14.2 CD 工具
Jenkins
Jenkins 是一种使用非常广泛的 CD 工具,已有很多年的历史。它拥有 CD 工作流程所需的插件,包括 Docker、kubectl 和 Helm。
此外,还有一个专门用于在 Kubernetes 集群中运行 Jenkins 的辅助项目,名叫 JenkinsX 。
Drone
Drone 是面向容器的新型 CD 工具,而且其本身也是由容器构建的。这款工具简单轻巧,定义流水线只需要一个 YAML 文件。每个构建步骤也是运行容器,而且 YAML 的配置就存储在你的代码库中。
Google 云构建
如果你在 Google 云平台上运行基础设施,那么 Google 云构建应该是 CD 的首选。
与 Drone 一样,Google 云构建的各个构建步骤也是运行容器,而且 YAML 的配置就存储在你的代码库中。
你可以通过配置 Google 云构建来监视 Git 代码库(它也集成了 GitHub)。当预设条件被触发时(比如推送到某个分支或标签),Google 云构建就会运行指定的流水线、构建新容器、运行测试套件、发布镜像并将新版本部署到 Kubernetes。
Concourse
Concourse 是一款用 Go 编写的开源 CD 工具。它也采用了声明式的流水线方法,就像 Drone 和 Google 云工具一样,使用 YAML 文件来定义和执行构建步骤。Concourse 已有官方发布的稳定 Helm Chart,可部署到 Kubernetes 上,你可以利用该工具轻松快速地启动并运行容器化流水线。
Spinnaker
Spinnaker 非常强大且灵活,但最初的印象可能会觉得有点复杂。它由 Netflix 开发,擅长大型复杂的部署,例如蓝绿部署。更多信息可以阅读官网提供的一本免费的电子书。
GitLab CI
GitLab 内置了功能强大的 CD 工具:GitLabCI,可用户测试和部署代码。
Codefresh
Codefresh 是一项托管的 CD 服务,用于测试应用程序并将其部署到 Kubernetes。这款工具的亮点是能够针对每个功能分支部署临时的预发布环境。
Codefresh 使用容器构建、测试和部署所需的环境,然后由你来配置如何将容器部署到集群的各种环境中。
Azure 流水线
微软的 Azure 开发运维服务(原名 Visual Studio 团队服务, VSTS)包括一个名叫 Azure 流水线的持续交付流水线设施,类似于 Google 的云构建。
14.3 CD 组件
Docker Hub
如果你想在代码方式变化时自动构建新容器,那么最简单的方法之一就是使用 Docker Hub。可以创建一个针对 GitHub 或 BitBucket 代码库的触发器,自动构建新容器并将其发布到 Docker Hub。
Gitkube
Gitkube 是一个自托管工具,可在集群中运行,监视 Git 代码库,并在触发器被触发时自动构建并推送新容器。这款工具简单、可移植,且易于设置。
Flux
通过 Git 分支或标签触发 CD 流水线(或其它自动化过程)的模式也称为 GitOps。FLux 扩展了这个概念,它监视的是容器仓库(而不是 Git 代码库)。每当新容器被推送到容器仓库时,Flux 就会自动将其部署到 Kubernetes 集群。
Keel
与 Flux 一样,Keel 的用途也是部署容器仓库的新容器镜像。通过配置之后,Keel 可响应 Webhook,实现发送和接收 Slack 消息,或者在部署前等待批准,还可以执行其它工作流程。
14.4 Google 云构建的 CD 流水线
设置 Google 云和 GKE
第一次注册 Google 云时,可以获得大量的免费积分,足够你在很长一段时间内免费运行 Kubernetes 集群和其它资源。
注册完毕并登录到自己的 Google 云项目后,创建 GKE 集群。
接下来在集群中初始化 Helm。
之后设置流水线:
- 将 demo 代码库分叉到你个人的 GitHub 账号中。
- 创建一个 Google 云构建触发器,在某个 Git 分支收到推送时执行构建和测试。
- 创建基于 Git 标签的触发器,执行部署到 GKE 的操作。
分叉 demo 代码库
可以使用 GitHub 界面创建 demo 代码库的分叉,或者直接复制本书作者的代码库,并将其推送到自己的代码库。
Google 云构建简介
在 Google 云构建中,构建流程的每个步骤都需要运行一个容器。而构建步骤则由 Git 代码库中的 YAML 文件定义。
当流水线被某次提交触发时,Google 云构建会利用该提交对应的代码版本创建一个代码库的副本,并按顺序执行流水线中的每个步骤。
在 demo 代码库中,hello-cloudbuild/cloudbuild.yaml 文件就是 Google 云构建流水线的定义。
steps:
# build only the first stage, so we can run tests with it
- id: build-test-image
dir: hello-cloudbuild
name: gcr.io/cloud-builders/docker
entrypoint: bash
args:
- -c
- |
docker image build --target build --tag demo:test .
- id: run-tests
dir: hello-cloudbuild
name: gcr.io/cloud-builders/docker
entrypoint: bash
args:
- -c
- |
docker container run demo:test go test
- id: build-app
dir: hello-cloudbuild
name: gcr.io/cloud-builders/docker
entrypoint: bash
args:
- -c
- |
docker image build --tag gcr.io/${PROJECT_ID}/demo:${COMMIT_SHA} .
- id: kubeval
dir: hello-cloudbuild
name: cloudnatived/helm-cloudbuilder
entrypoint: sh
args:
- -c
- |
helm template ./k8s/demo/ | kubeval
images:
- gcr.io/${PROJECT_ID}/demo:${COMMIT_SHA}
对应的 Dockerfile :
FROM golang:1.17-alpine AS build
RUN apk add --update --no-cache gcc git build-base
WORKDIR /src/
COPY main*.go go.* /src/
RUN CGO_ENABLED=0 go build -o /bin/demo
FROM scratch
COPY --from=build /bin/demo /bin/demo
ENTRYPOINT ["/bin/demo"]
构建测试容器
第一步是构建测试容器:
# build only the first stage, so we can run tests with it
- id: build-test-image
dir: hello-cloudbuild
name: gcr.io/cloud-builders/docker
entrypoint: bash
args:
- -c
- |
docker image build --target build --tag demo:test .
与其它 Google 云构建的步骤一样,它由一组 YAML 键值对组成:
- id:为构建步骤指定可供人阅读的标签。
- dir:指定要使用的 Git 代码库的子目录。
- name:指定该步骤需要运行的容器。
- entrypoint:指定要在容器中运行的命令(如果不采用默认值的话)。
- args:为 entrypoint 命令提供必要的参数。
这个步骤的目的是构建一个容器,并运行应用程序测试。
docker image build --target build --tag demo:test .
--target build
参数告诉 Docker 只构建 Dockerfile 中 FROM golang:1.17-alpine AS build
以下(以上?)的部分,并在运行下一步前停止。
这意味着生成的容器仍将安装 Go,还会安装标记了 ...... AS build
步骤中用到的所有软件包或文件。实际上,这是一个一次性容器,仅用于运行应用程序的测试套件,然后就可以丢弃了。
运行测试
上一步为一次性容器打上了 demo:test 标签,因此在 Google 云构建中,这个临时的镜像也可用于构建的其余部分。
这个步骤针对该容器执行 go test
。如果任何测试失败,则构建将退出,并报告失败。否则,它将继续进行下一步。
- id: run-tests
dir: hello-cloudbuild
name: gcr.io/cloud-builders/docker
entrypoint: bash
args:
- -c
- |
docker container run demo:test go test
构建应用程序容器
这一步再一次运行 docker build
,但不使用 --target
标志,即完整的运行多阶段构建,并得到最终的应用程序容器。
- id: build-app
dir: hello-cloudbuild
name: gcr.io/cloud-builders/docker
entrypoint: bash
args:
- -c
- |
docker image build --tag gcr.io/${PROJECT_ID}/demo:${COMMIT_SHA} .
验证 Kubernetes 清单
生成最终的容器镜像后,运行 helm template
来生成 Kubernetes 清单,然后利用流水线传递给 kubeval 工具进行检查。
- id: kubeval
dir: hello-cloudbuild
name: cloudnatived/helm-cloudbuilder
entrypoint: sh
args:
- -c
- |
helm template ./k8s/demo/ | kubeval
请注意,在这个示例中,我们使用自己的 Helm 容器镜像(cloudnatived/helm-cloudbuilder)。奇怪的是,作为一款专业的容器部署工具,Helm 并没有官方的容器镜像。在这个例子中,你可以使用我们的就行,但在生产中,你可能需要自己构建。
发布镜像
流水线成功完成后,Google 云构建会自动将生成的容器镜像发布到容器仓库。如果想指定发布哪个镜像,则可以在 Google 云构建文件的 images 中列出:
images:
- gcr.io/${PROJECT_ID}/demo:${COMMIT_SHA}
Git SHA 标签
什么是 COMMIT_SHA 标签?在 Git 中,每个提交都有一个唯一的标志服,名叫 SHA。SHA 是一长串十六进制的数字。
使用原始 Git SHA 标记构建产物的好处在于,你可以同时构建和测试多个功能分支,而不会发生任何冲突。
创建第一个构建触发器
Google 云构建触发器需要指定监视的 Git 代码库、触发的添加(比如推送到特定分支或标签),以及一个需要执行的流水线文件。
JiaJia:
由于没法实际运行,国内也基本上用不到,这里就不写了。
测试触发器
就是修改下代码,提交并推送,之后看看 Google 云构建中有没有看到触发的构建。
CD 流水线部署
我们可以将 Google 云部署配置为:如果看到包含 Git 标签 staging 就部署到 staging ;如果看到 Git 标签 production 则部署到 production 。这需要通过一个单独的文件 cloudbuild-deploy.yaml 建立一个新的流水线。
cloudbuild-deploy.yaml :
steps:
- id: get-kube-config
dir: hello-cloudbuild
name: gcr.io/cloud-builders/kubectl
env:
- CLOUDSDK_CORE_PROJECT=${_CLOUDSDK_CORE_PROJECT}
- CLOUDSDK_COMPUTE_ZONE=${_CLOUDSDK_COMPUTE_ZONE}
- CLOUDSDK_CONTAINER_CLUSTER=${_CLOUDSDK_CONTAINER_CLUSTER}
- KUBECONFIG=/workspace/.kube/config
args:
- cluster-info
- id: update-deploy-tag
dir: hello-cloudbuild
name: gcr.io/cloud-builders/gcloud
args:
- container
- images
- add-tag
- gcr.io/${PROJECT_ID}/demo:${COMMIT_SHA}
- gcr.io/${PROJECT_ID}/demo:${TAG_NAME}
- id: deploy
dir: hello-cloudbuild
name: cloudnatived/helm-cloudbuilder
env:
- KUBECONFIG=/workspace/.kube/config
args:
- helm
- upgrade
- --install
- ${TAG_NAME}-demo
- --namespace=${TAG_NAME}-demo
- --values
- k8s/demo/${TAG_NAME}-values.yaml
- --set
- container.image=gcr.io/${PROJECT_ID}/demo
- --set
- container.tag=${COMMIT_SHA}
- ./k8s/demo
获取 Kubernetes 集群的凭证
- id: get-kube-config
dir: hello-cloudbuild
name: gcr.io/cloud-builders/kubectl
env:
- CLOUDSDK_CORE_PROJECT=${_CLOUDSDK_CORE_PROJECT}
- CLOUDSDK_COMPUTE_ZONE=${_CLOUDSDK_COMPUTE_ZONE}
- CLOUDSDK_CONTAINER_CLUSTER=${_CLOUDSDK_CONTAINER_CLUSTER}
- KUBECONFIG=/workspace/.kube/config
args:
- cluster-info
JiaJia:
这里应该是将集群凭证保存在了 /workspace/.kube/config 文件中。
我们可以在构建触发器中定义一些变量(如本例中的 _CLOUDSDK_CORE_PROJECT
,也可以在流水线文件的 substitutions 下定义:
substitutions:
_CLOUDSDK_CORE_PROJECT=demo_project
用户定义的 substitutions 必须以下划线字符(_
)开头,并且只能使用大写字母和数字。
Google 云构建还预定义了一些 substitutions ,例如 PROJECT_ID
和 COMMIT_SHA
。
添加环境标签
使用触发部署时使用的 Git 标签来标记容器:
- id: update-deploy-tag
dir: hello-cloudbuild
name: gcr.io/cloud-builders/gcloud
args:
- container
- images
- add-tag
- gcr.io/${PROJECT_ID}/demo:${COMMIT_SHA}
- gcr.io/${PROJECT_ID}/demo:${TAG_NAME}
JiaJia:
这一步应该是给容器添加
gcr.io/${PROJECT_ID}/demo:${COMMIT_SHA}
和gcr.io/${PROJECT_ID}/demo:${TAG_NAME}
标签。至于命令为何这样写就不知道了,估计是 gcloud 镜像的功能。
部署到集群
使用前面获取的 Kubernetes 凭证来运行 Helm,以升级集群中的应用程序:
- id: deploy
dir: hello-cloudbuild
name: cloudnatived/helm-cloudbuilder
env:
- KUBECONFIG=/workspace/.kube/config
args:
- helm
- upgrade
- --install
- ${TAG_NAME}-demo
- --namespace=${TAG_NAME}-demo
- --values
- k8s/demo/${TAG_NAME}-values.yaml
- --set
- container.image=gcr.io/${PROJECT_ID}/demo
- --set
- container.tag=${COMMIT_SHA}
- ./k8s/demo
JiaJia:
这里使用了和第一步同样的环境变量
KUBECONFIG=/workspace/.kube/config
。
使用helm upgrad
命令升级部署。
helm upgrad
命令使用了如下标志:
- namespace:应用程序部署的目标命名空间
- values:该环境使用的 Helm 值文件
- set container.image:设置部署的容器名称
- set container.tag:部署带有指定标签(源自 Git SHA 的标签)的镜像。
创建部署触发器
使用 cloudbuild-deploy 创建触发器,Substitution variables 变量设置如下:
_CLOUDSDK_CORE_PROJECT
:需要设置成运行 GKE 集群的 Google 云项目 ID。_CLOUDSDK_COMPUTE_ZONE
:应该与集群可用区域一致。_CLOUDSDK_CONTAINER_CLUSTER
:GKE 集群的实际名称。
这些变量意味着我们可以使用同一个 YAML 文件来部署预发布和生成,即使我们想在不同的集群甚至不同的 GCP 项目中运行这些环境。
在创建 staging 标签的触发器后,试试将 staging 标签推到代码库。
git tag -f staging
git push -f origin refs/tags/staging
如果一起按计划进行,则 Google 云构建应该能够成功地通过 GKE 集群的身份验证,并将应用程序的预发布版本部署到 staging-demo 命名空间中。
优化构建流水线
如果你正在使用基于容器的 CD 流水线工具,则每个步骤的容器都应该尽可能保持最低限度。每天你都需要运行成百上千个容器,臃肿的容器日益增加的拉去时间会成为隐患。
调整示例流水线
如果你使用的是 Google 云构建,则可以从这个示例代码着手设置自己的流水线。如果你使用的是其它工具,则可以根据你自己的工作环境,调整本章介绍的步骤。
14.5 小结
为应用程序设置持续部署流水线可以帮助你通过一致、可靠且快速的方式部署软件。在理想情况下,开发人员只需将代码推送到源代码控制库中,而所有构建、测试和部署阶段都应该自动在集中式流水线中完成。
- 在建立新流水线时,确定要使用的 CD 工具是一个重要过程。我们在本书中提到的所有工具都可以融合到现有的 CD 工具中。
- Jenkins、GitLab、Drone、Google 云构建和 Spinnaker 只是众多可与 Kubernetes 配合使用的流行 CD 工具中的一部分。此外最新的 Gitkube、Flux 和 Keel 等工具时专门为自动部署 Kubernetes 集群而构建的。
- 使用代码定义构建流水线步骤,这样就可以与应用程序代码一起跟踪和修改。
- 在容器的帮助下,开发人员可以将构建的成果部署到所以环境(比如测试、预发布以及最终的生成),而且理想情况下无需重建新容器。
- 我们的示例流水线使用了 Google 云构建,但经过调整后应该很容易应用于其它工具和类型的应用程序。无论使用哪种工具或哪种软件,总的来说任何 CD 流水线的构建、测试以及部署步骤基本都相同。