第 5 章 持续集成、测试和部署

在本章中,我们将介绍如何集成持续集成/连续部署 (CI/CD) 流水线以将应用程序交付给 Kubernetes 的关键概念。构建一个集成良好的流水线将使您能够信心十足地将应用程序交付到生产环境中,因此,在这里我们将介绍在您的环境中启用 CI/CD 的方法、工具和流程。CI/CD 的目标是实现完全自动化的流程,从开发人员签入代码到将新代码发布到生产。您希望避免手动发布部署到 Kubernetes 的应用的更新,因为它可能非常容易出错。在 Kubernetes 中手动管理应用程序更新会导致配置偏移和脆弱的部署更新,而失去了交付应用程序的整体敏捷性。

我们将在本章中介绍以下主题:

  • 版本控制
  • 持续结成
  • 测试
  • 镜像标签
  • 持续部署
  • 部署策略
  • 测试部署
  • 混沌测试

我们还通过一个示例 CI/CD 流水线,其中包括以下任务:

  • 将代码更改推送到 Git 仓库
  • 运行应用程序代码的构建
  • 对代码运行测试
  • 在成功测试的基础上构建容器镜像
  • 将容器镜像推送到镜像仓库
  • 将应用程序部署到 Kubernetes
  • 对已部署的应用程序运行测试
  • 对部署执行滚动升级

版本控制

每个 CI/CD 流水线都是从版本控制开始,版本控制维护应用程序和配置代码更改的运行历史记录。Git已经成为作为源代码管理平台的行业标准,每个Git仓库都将包含一个master分支。master分支包含生产代码。您将有用于功能和开发工作的其他分支,这些分支最终也将合并到您的主分支中。设置分支策略的方法有很多种,并且设置将非常依赖于组织结构和职责的分离。我们发现,包括应用程序代码和配置代码(如 Kubernetes 清单或 Helm 图表)有助于促进良好的 DevOps 通信和协作原则。让应用程序开发人员和操作工程师在单个仓库中协作,可以增强团队对将应用程序交付到生产环境的信心。

持续集成

CI 是将代码更改连续集成到版本控制仓库的过程。不是更少的提交大的更改,而是更频繁地提交较小的更改。每次将代码更改提交到仓库时,都会启动构建。这样,如果确实出现问题,就可以更快的反馈到可能损坏应用程序的地方。此时,您可能会问,“为什么我需要知道应用程序是如何构建的,这难道不是应用程序开发人员的职责吗?”传统上,情况可能是这样的,但是随着公司逐渐接受 DevOps 文化,运营团队更接近于应用程序代码和软件开发工作流。

有许多提供CI的解决方案 , Jenkins 是其中比较流行的工具之一。

测试

在流水线中运行测试的目的是快速为中断构建的代码更改提供反馈循环。您使用的语言将决定您使用的测试框架。例如,Go 应用程序可以使用go test针对代码库运行一组单元测试。拥有一个广泛的测试套件有助于避免将错误代码传递到生产环境。您需要确保如果测试在流水线中失败,那么构建在测试套件运行后失败。如果对代码库的测试失败,则不希望构建容器镜像并将其推送到镜像仓库。

同样,您可能会问,“创建测试不是开发人员的工作吗?”在开始自动化将基础架构和应用程序交付到生产环境时,您需要考虑针对代码库的所有部分运行自动测试。例如,在第2章中,我们讨论了使用 Helm 来打包 Kubernetes 应用程序。 Helm 包括一个名为 helm lint 的工具,该工具针对图表运行一系列测试,以检查所提供的图表的任何潜在问题。需要在端到端流水线中运行许多不同的测试。有些是开发人员的责任,比如应用程序的单元测试,但是其他的,如冒烟测试,将是一个共同工作。测试代码库并将其交付到生产环境是团队工作,需要从头到尾实现。

容器构建

在构建镜像时,应优化镜像的大小。拥有较小的镜像可减少拉取和部署镜像所需的时间,同时提高镜像的安全性。有多种方法可以优化镜像大小,但有些方法确实具有权衡性。以下策略将帮助您为应用程序构建尽可能小的镜像:

  • 多阶段构建

    这些允许您删除应用程序运行不需要的依赖项。例如,对于 Golang,我们不需要用于生成静态二进制文件的所有构建工具,因此多阶段生成允许您在单个 Dockerfile 中运行构建步骤,最终镜像仅包含运行应用程序所需的静态二进制文件。

  • 无组件基础镜像

    这将从镜像中删除所有不需要的二进制文件和shell。这确实减小了镜像的大小,并提高了安全性。使用无组件基础镜像的权衡是,您没有 shell,因此无法将调试器附加到镜像。您可能认为这很好,但调试应用程序可能会很痛苦。无组件镜像不包含包管理器、shell 或其他典型的操作系统包,因此您可能无法访问典型操作系统所习惯的调试工具。

  • 优化的基础镜像

    这些镜像侧重于去掉操作系统层中用不到的软件并提供精简镜像。例如,Alpine 提供了一个初始大小仅为 10 MB的基本镜像,它还允许您为本地开发附件一个本地调试器。其他发行版通常还提供优化的基本镜像,例如 Debian 的 Slim 镜像。对于您来说,这可能是一个不错的选择,因为它优化的镜像为您提供开发所需的功能,同时优化镜像大小和降低安全暴露。

优化您的镜像非常重要,往往被用户忽视。您可能有理由,因为公司的操作系统标准已被批准在企业中使用,但是您可以推迟这些标准,这样您就可以最大化容器的价值。

我们发现,刚开始使用 Kubernetes 的公司往往会成功的使用它们当前操作系统,然后选择一个更优化的镜像,如 Debian Slim。在您熟悉了容器环境进行操作和开发,您将熟悉无组件镜像。

容器镜像标签

CI 流水线中的另一个步骤是构建 Docker 镜像,以便将镜像部署到环境中。拥有一个镜像标签策略非常重要,这样您就可以轻松地识别已部署到环境中的版本化镜像。我们不能说得够多的最重要的事情之一就是不要使用"latest"作为镜像标签。将它用作镜像标记不是一个版本,并将导致无法识别哪些代码更改属于推出的镜像。在 CI 流水线中构建的每个镜像都应该为构建的镜像提供一个唯一的标签。

我们发现,在 CI 流水线中标记镜像时,有多种有效的策略。以下策略可让您轻松识别代码更改及其相关联的构建:

  • BuildID

    当 CI 构建开始时,它有一个与其关联的BuildID。使用标记的这一部分,您可以参考哪个构建组装的镜像。

  • Build System-BuildID

    这一个与 BuildID 相同,但为拥有多个构建系统的用户添加了构建系统。

  • Git Hash

    在提交新代码时,会生成一个 Git 哈希值,使用标签的哈希可以方便的识别是哪个提交构建的镜像。

  • githash-buildID

    这允许您同时引用代码提交和构建镜像的buildID。这里唯一的注意事项是,标记可能有点长。

持续部署

CD是在没有人工干预的情况下将通过 CI 流水线成功提交的更改部署到生产环境中的过程。容器为将更改部署到生产环境中提供了巨大的优势。容器镜像成为不可变的对象,可以通过开发、暂存和投入生产来升级。例如,我们一直的主要问题之一是保持一致的环境。几乎每个人都经历过一个部署,在暂存中工作正常,但当它被提升到生产时,它就会中断。这是因为配置漂移,每个环境中的组件库和版本控制各不相同。Kubernetes 为我们提供了一种声明性的方法来描述我们的部署对象,这些对象可以以一致的方式进行版本化控制和部署。

需要记住的一点是,在专注于 CD 之前,您需要设置可靠的 CI 流水线。如果您没有一组可靠的测试来在早期捕获问题,那么您最终将把有问题的代码滚动更新到所有环境中。

部署策略

现在,我们学习了 CD 的原理,让我们来看看您可以使用的不同更新策略。Kubernetes 提供了多种策略来更新应用程序的新版本。即使它具有提供滚动更新的内置机制,您也可以利用一些更高级的策略。在这里,我们研究以下策略,以为您的应用程序提供更新:

  • 滚动更新
  • 蓝/绿部署
  • 金丝雀部署

滚动更新内置于 Kubernetes 中,允许您在不停机的情况下触发对当前正在运行的应用程序的更新。例如,如果您使用当前运行frontend:v1 的前端应用并将部署更新到frontend:v2,则 Kubernetes 会以滚动方式将副本更新到frontend:v2。图 5-1 描述了滚动更新。

Figure 6.1

图 5-1 Kubernetes滚动更新

部署对象还允许您配置要更新的最大副本数量以及更新期间pod的最大不可用数量。以下清单是指定滚动更新策略的示例:

 1kind: Deployment
 2apiVersion: v1
 3metadata:
 4  name: frontend
 5spec:
 6  replicas: 3
 7  template:
 8    spec:
 9      containers:
10      - name: frontend
11        image: brendanburns/frontend:v1
12  strategy:
13    type: RollingUpdate
14    rollingUpdate:
15      maxSurge: 1 # Maximum amount of replicas to update at one time
16      maxUnavailable: 1 # Maximum amount of replicas unavailable during rollout

您需要谨慎对待滚动更新,因为使用此策略可能会导致连接断开。要解决此问题,您可以使用readinessProbepreStop生命周期挂钩。就绪性探测可确保部署的新版本已准备好接受流量,而预停止挂钩可确保在当前部署的应用程序中耗尽连接。在容器退出之前调用生命周期挂钩,并且它是同步的,因此必须在给出最终终止信号之前完成。以下示例实现就绪性探测和生命周期挂钩:

 1kind: Deployment
 2apiVersion: v1
 3metadata:
 4  name: frontend
 5spec:
 6  replicas: 3
 7  template:
 8    spec:
 9      containers:
10      - name: frontend
11        image: brendanburns/frontend:v1
12        livenessProbe:
13          # ...
14        readinessProbe:
15          httpGet:
16            path: /readiness # probe endpoint
17            port: 8888
18        lifecycle:
19          preStop:
20            exec:
21              command: ["/usr/sbin/nginx","-s","quit"]
22  strategy:
23    # ...

本示例中的preStop生命周期钩子将优雅的退出 NGINX,而 SIGTERM 则执行非正常、快速退出。

滚动更新的另一个问题是,您现在有两个版本的应用程序在滚动更新期间同时运行。您的数据库模式需要同时支持两个版本的应用程序。您还可以使用特性标志策略,其中模式指示新应用版本创建的新列。滚动更新完成后,可以删除旧列。

我们还在部署清单中定义了就绪性和存活性探测。就绪性探测将确保应用程序准备好为流量提供服务,然后再将其作为端点放在服务后面。存活性探测可确保应用程序正常运行并运行,并在其存活性探测失败时重新启动pod。只有当pod出错时,Kubernetes 才能自动重新启动发生故障的pod。例如,存活性探测可以检查其端点,并在有死锁时重新启动它,而pod未从死锁中退出。

蓝色/绿色部署允许您以可预测的方式发布应用程序。使用蓝/绿部署,您可以控制流量何时转移到新环境,因此,您可以对新版本应用程序的更新进行大量控制。使用蓝/绿部署时,您需要具有同时部署现有和新环境的能力。这些类型的部署具有许多优点,例如可以轻松地切换回以前的应用程序版本。但是,使用此部署策略需要考虑一些事项:

  • 使用此部署选项,数据库迁移可能会变得困难,因为您需要考虑动态事务和模式更新兼容性。
  • 存在意外删除这两个环境的风险。
  • 两种环境都需要额外的容量。
  • 混合部署存在协调问题,遗留应用程序无法处理这种部署。

图 5-2 描述了蓝色/绿色部署。

Figure 6.2

图 5-2 蓝色/绿色部署

金丝雀部署与蓝/绿部署非常相似,但它们使您能够对将流量转移到新版本进行更多控制。大多数现代的 Ingress 实现将使您能够将一定比例的流量释放到新版本,但您也可以实现服务网格技术,如 Istio、Linkerd 或 HashiCorp Consul,这些技术为您提供了实施此部署策略许多特性。

金丝雀部署允许您仅针对一部分用户测试新功能。例如,您可能更新应用程序的新版本,并且只想测试部署的 10% 的用户群。这允许您将错误部署或功能损坏的风险降低到小得多的用户子集。如果部署或新功能没有错误,则可以开始将更大百分比的流量转移到新版本的应用程序。还有一些更高级的技术,可用于金丝雀部署,其中仅向特定用户区域发布,或仅针对具有特定配置文件的用户。这些类型的版本通常称为 A/B 或暗版本,因为用户不知道他们正在测试新功能部署。

对于金丝雀部署,您有一些与蓝/绿部署相同的注意事项,但也有一些额外的注意事项。您必须具有:

  • 能够转移用户流量百分比的能力
  • 一个稳固的知识稳定的状态来比较新版本
  • 了解新版本处于"好"或"坏"状态的衡量指标

图 5-3 提供了金丝雀部署的示例。

Figure 6.3

图 5-3 金丝雀部署


注意

金丝雀版本也会因同时运行应用程序的多个版本而受到影响。数据库模式需要支持应用程序的两个版本。使用这些策略时,您需要真正关注如何处理从属服务并运行多个版本。这包括具有强大的 API 协定,并确保数据服务支持同时部署的多个版本。


生产测试

在生产环境中测试可帮助您建立对应用程序的弹性、可扩展性和用户体验方面的信心。这伴随着一个警告,即在生产环境中进行测试并不是没有挑战和风险,但为了确保系统的可靠性,这是值得的。在开始实施时,您需要预先处理一些重要方面。您需要确保您有一个深入的可观察性策略,从而能够识别生产中测试的影响。如果不能观察到影响最终用户应用程序体验的指标,那么在尝试提高系统弹性时,您就无法清楚的了解应该关注哪些方面。您还需要适当的高度自动化,以便能够从注入到系统的故障中自动恢复。

您需要实施许多工具,来降低风险,并在系统处于生产中时对其进行有效测试。我们在本章中已经讨论过的一些工具,但也有一些新的工具,如分布式跟踪、检测、混沌工程和流量隐藏。概括地说,下面是我们已经提到的工具:

  • 金丝雀部署
  • A/B 测试
  • 流量转移
  • 功能标志

混沌工程由 Netflix 开发。将实验部署到实际生产系统中以发现这些系统中的弱点的一种实践。混沌工程允许您通过在受控实验期间观察系统的行为来了解系统的行为。以下是在进行"游戏日"实验之前要执行的步骤:

  1. 建立一个假设,并了解你的稳定状态。
  2. 具有可能影响系统的不同程度的实际事件。
  3. 建立对照组并进行实验与稳定状态比较。
  4. 进行实验以形成假设。

在进行实验时,将"爆炸半径"最小化以确保可能出现的问题是最小的,这一点非常重要。您还需要确保在构建实验时,您专注于实现实验自动化,因为运行实验可能耗费大量人力。

此时,您可能会问,“为什么我不只是在暂存中测试?我们发现在暂存测试中存在一些固有的问题,例如:

  • 资源部署不同。
  • 配置偏离生产。
  • 流量和用户行为往往是综合生成的。
  • 生成的请求数量无法模拟真实的工作负载。
  • 在准备阶段缺乏实现监测。
  • 部署的数据服务包含与生产环境不同的数据和负载。

我们再怎么强调这一点也不为过:确保您对生产环境的监控有可靠的信心,因为这种做法往往会让对生产系统缺乏足够可观察性的用户失败。此外,从较小的实验开始,首先了解您的实验和它们的效果将有助于建立信心。

设置流水线并执行混沌实验

该过程的第一步是将 GitHub 仓库分叉,以便您可以在本章中使用自己的存储库。您需要使用 GitHub 接口来分叉存储库

设置 CI

现在您已经了解了 CI,您将设置以前克隆的代码的生成。

在此示例中,我们使用托管*drone.io。*你需要注册一个免费帐户。使用 GitHub 凭据登录(这将在 Drone 中注册存储库,并允许您同步存储库)。登录到Drone之后,选择"Activate on your forked repository”。您需要做的第一件事是向设置添加一些secret,以便您可以将应用推送到 Docker Hub 仓库,并将应用部署到 Kubernetes 群集。

在Drone中的存储库下,单击"设置"并添加以下secret(参见图 5-4):

  • docker_username
  • docker_password
  • kubernetes_server
  • kubernetes_cert
  • kubernetes_token

Docker 用户名和密码将是您在 Docker 中心注册的任何内容。以下步骤演示如何创建 Kubernetes 服务帐户和证书并检索令牌。

对于 Kubernetes 服务器,您将需要一个公开可用的 Kubernetes API端点。

Figure 6.4

图 5-4 Drone Secert配置


注意

您需要在 Kubernetes 群集上获得群集管理员权限才能执行本节中的步骤。


可以使用以下命令检索 API 端点:

1kubectl cluster-info

你应该看到类似的东西:Kubernetes master is running https://kbp.centralus.azmk8s.io:443您将其存储在kubernetes_server secret中。

现在,让我们创建一个服务帐户,Drone 将使用该帐户连接到群集。使用以下命令创建 :serviceaccount

1kubectl create serviceaccount drone

现在,使用以下命令为 serviceaccount创建 clusterrolebinding

1kubectl create clusterrolebinding drone-admin \
2  --clusterrole=cluster-admin \
3  --serviceaccount=default:drone

接下来,检索令牌:serviceaccount

1TOKENNAME=`kubectl -n default get serviceaccount/drone -o jsonpath='{.secrets[0].name}'`
2TOKEN=`kubectl -n default get secret $TOKENNAME -o jsonpath='{.data.token}' | base64 -d`
3echo $TOKEN

您需要将令牌的输出存储在kubernetes_token secret中。

您还需要用户证书来对群集进行身份验证,因此请使用以下命令并粘贴 ca.crtkubernetes_cert secret:

1kubectl get secret $TOKENNAME -o yaml | grep 'ca.crt:'

现在,在Drone流水线中构建你的应用,然后将其推送到 Docker Hub。

第一步是构建步骤,它将生成 Node.js 前端。Drone 利用容器镜像来运行其步骤,这给您提供了很大的灵活性,您可以使用它来做什么。对于构建步骤,请使用 Docker Hub中的 Node.js 镜像:

1pipeline:
2  build:
3    image: node
4    commands:
5      - cd frontend
6      - npm i redis --save

构建完成后,您需要对其进行测试,因此我们包括一个测试步骤,该步骤将针对npm新生成的应用运行:

1test:
2    image: node
3    commands:
4      - cd frontend
5      - npm i redis --save
6      - npm test

现在,您已成功构建和测试应用,接下来将转到发布步骤,以创建应用的 Docker 镜像并将其推送到 Docker Hub。

在*.drone.yml*文件中,进行以下代码更改:

1repo: <your-registry>/frontend
2publish:
3    image: plugins/docker
4    dockerfile: ./frontend/Dockerfile
5    context: ./frontend
6    repo: dstrebel/frontend
7    tags: [latest, v2]
8    secrets: [ docker_username, docker_password ]

Docker 构建步骤完成后,它将将镜像推送到 Docker Hub。

设置 CD

对于流水线中的部署步骤,您将应用程序发布到 Kubernetes 群集。您将使用存储库中前端应用文件夹下的部署清单:

1kubectl:
2    image: dstrebel/drone-kubectl-helm
3    secrets: [ kubernetes_server, kubernetes_cert, kubernetes_token ]
4    kubectl: "apply -f ./frontend/deployment.yaml"

流水线完成部署后,您将看到群集中正在运行的 Pod。运行以下命令以确认pods正在运行:

1kubectl get pods

您还可以添加测试步骤,通过在 Drone 流水线中添加以下步骤来检索部署状态:

1  test-deployment:
2    image: dstrebel/drone-kubectl-helm
3    secrets: [ kubernetes_server, kubernetes_cert, kubernetes_token ]
4    kubectl: "get deployment frontend"

执行滚动升级

让我们通过更改前端代码中的行来演示滚动升级。在server.js文件中,更改以下行,然后提交更改:

1console.log('api server is running.');

您将看到部署在现有 Pod 上推出和滚动更新。滚动更新完成后,您将部署应用程序的新版本。

一个简单的混沌实验

Kubernetes 生态系统中有多种工具可以帮助在您的环境中执行混沌实验。它们包括复杂的托管混沌服务解决方案,以及可杀死环境中的pod的基本混沌实验工具。以下是一些我们看到用户成功的工具:

  • Gremlin

    托管的混沌服务,为运行混沌实验提供高级特性

  • PowerfulSeal

    提供高级混沌场景的开源项目

  • Chaos Toolkit

    开源项目的使命是为所有各种形式的混沌工程工具提供免费、开放和社区驱动的工具包和 API

  • KubeMonkey

    为群集中的 Pod 提供基本弹性测试的开源工具

让我们设置一个快速混沌实验,通过自动终止 Pod 来测试应用程序的恢复能力。对于这个实验,我们将使用Chaos Toolkit

1pip install -U chaostoolkit
2pip install chaostoolkit-kubernetes
3export FRONTEND_URL="http://$(kubectl get svc frontend -o jsonpath="{.status.loadBalancer.ingress[*].ip}"):8080/api/"
4chaos run experiment.json

CI/CD 的最佳做法

您的 CI/CD 流水线不会在第一天就很完美,但是可以考虑以下一些最佳实践来迭代改进流水线:

  • 使用 CI,专注于自动化并提供快速构建。优化构建速度将为开发人员提供快速反馈,如果他们的更改破坏了构建。
  • 专注于在流水线中提供可靠的测试。这将为开发人员提供快速反馈有关其代码问题的反馈。向开发人员提供反馈循环的速度越快,他们在工作流中的工作效率就越高。
  • 在决定使用 CI/CD 工具时,请确保这些工具允许您将流水线定义为代码。这将允许您使用应用程序代码对流水线进行版本控制。
  • 确保您优化了您的镜像,以便在生产中运行镜像时减小镜像大小并减少攻击面。多阶段 Docker 构建允许您删除应用程序运行不需要的包。例如,您可能需要 Maven 来生成应用程序,但实际运行的镜像不需要它。
  • 避免使用"latest"作为镜像标签,并使用可引用回buildID 或 Git 提交的标记
  • 如果您是 CD 新手,请使用 Kubernetes 滚动升级开始。它们易于使用,并将使您适应部署。随着你对 CD 越来越熟悉和自信,可以考虑使用蓝色/绿色和金丝雀部署策略。
  • 使用 CD,请确保测试应用程序中如何处理客户端连接和数据库模式升级。
  • 在生产中测试将帮助您在应用程序中建立可靠性,并确保您有良好的监控。在生产中进行测试后,也从小规模开始,并限制实验的爆炸半径。

总结

在本章中,我们讨论了为应用程序构建 CI/CD 流水线的阶段,这使您能够自信地可靠地交付软件。CI/CD 流水线有助于降低风险并提高向 Kubernetes 交付应用程序的吞吐量。我们还讨论了可用于交付应用程序的不同部署策略。