GitOps-秘籍-全-

GitOps 秘籍(全)

原文:zh.annas-archive.org/md5/76852e1f8699ee05381233cbc5f94f1c

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

几年前,在为红帽公司的一次米兰活动旅行期间,我在红帽办公室遇到了一位热情的同事。我们就意大利客户如何在 OpenShift 上采用容器加快应用程序开发的话题进行了深入交流。虽然当时我忘记了他的名字,但他对这个主题的热情印象深刻,特别是因为他还热情地带我去办公室附近的一家浓缩咖啡吧,让我体验真正的意式咖啡味道。不久后,在一次会议上,我认识了一位开发者倡导者,他将在会议上讲述如何使用我们团队当时提供的 OpenShift Pipelines 和 OpenShift GitOps 等产品进行 CI/CD。那一刻,我立刻认出了 Natale。出席那次讲座的许多人认为这是一次富有洞察力的演讲,因为他对客户在交付应用程序时遇到的挑战有第一手的理解,以及他对技术的实际应用。

应用程序交付是一个复杂的过程,涉及许多系统和团队之间的多次交接,往往伴随着每个环节的延迟和反复沟通。自动化长期以来一直是改善这一过程的关键推动因素,并且在 DevOps 运动中变得尤为流行。持续集成、基础设施即代码和许多其他实践在许多组织中变得普遍,因为它们在采纳 DevOps 过程中航行。

近年来,随着 Kubernetes 的广泛采用增加,GitOps 作为实施 DevOps 实践子集的蓝图已成为经常被问及的领域。虽然 GitOps 倡导的术语和实践并不新鲜,但它结合了现有的知识,以一种简单易懂的工作流呈现,并可以在许多团队中以标准方式实施。

尽管采用 GitOps 工作流的路径简单而具体,但需要在符合每个组织的安全、合规性、运营和其他需求的技术选择中作出许多决策。因此,我特别高兴看到这本书的存在以及它提供的实用指南,帮助这些团队为他们的应用程序、团队和组织做出合适的选择。

Siamak Sadeghianfar

产品管理,红帽公司

序言

我们为开发者、DevOps 工程师、可靠性工程师(SRE)或处理 Kubernetes 的平台工程师撰写了本书。无论您是哪一类构建者,我们希望分享我们从现场和社区中学到的关于流水线和 CI/CD 工作负载的最新 Kubernetes 自动化见解。本书包含了 Kubernetes 和云原生生态系统中最受欢迎的软件和工具的全面清单,旨在提供一系列可能有助于您日常工作或值得进一步探索的实用配方。在实施 Kubernetes 自动化方面,我们没有特定的技术或项目的依恋,但对于一些选择我们有着自己的看法,以提供简洁的 GitOps 路径。

本书按照 GitOps 原则,从基础到 Kubernetes 生态系统中的高级主题进行了顺序排列的章节。我们希望这些配方对您的项目有价值并激发灵感!

  • 第一章介绍了 GitOps 原则以及它们为何在任何新的 IT 项目中持续变得更加普遍和重要。

  • 第二章涵盖了在 Kubernetes 集群中运行这些配方所需的安装要求。Git、容器注册表、容器运行时和 Kubernetes 等概念和工具对于这一旅程至关重要。

  • 第三章全面介绍了容器及其在今天的应用开发和部署中的重要性。Kubernetes 是一个容器编排平台;然而,它不能直接构建容器。因此,我们将提供一系列使用云原生社区中最流行工具制作容器应用的实用配方清单。

  • 第四章概述了 Kustomize,这是一款管理 Kubernetes 资源的流行工具。Kustomize 具有互操作性,在 CI/CD 流水线中经常被使用。

  • 第五章探索了 Helm,一款将应用程序打包在 Kubernetes 中的时尚工具。Helm 还是一个模板系统,您可以用来在 CI/CD 工作负载中部署应用程序。

  • 第六章详细介绍了针对 Kubernetes 的云原生 CI/CD 系统。它提供了使用 Tekton 进行持续集成的全面配方列表,Tekton 是 Kubernetes 原生的 CI/CD 系统。此外,还涵盖了其他工具,如 Drone 和 GitHub Actions。

  • 第七章开启了本书中 GitOps 纯粹部分,专注于使用 Argo CD 进行持续部署,Argo CD 是一款流行的 Kubernetes GitOps 工具。

  • 第八章 深入介绍了使用 Argo CD 进行 GitOps 的高级主题,如秘密管理、渐进式应用交付和多集群部署。这总结了您今天和明天可能会使用的最常见的用例和架构,遵循 GitOps 方法。

本书中使用的约定

本书使用以下印刷约定:

斜体

指示新术语、URL、电子邮件地址、文件名和文件扩展名。

等宽字体

用于程序清单,以及在段落内部引用程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。

等宽粗体

显示用户应按字面意思输入的命令或其他文本。

等宽斜体

显示应替换为用户提供的值或上下文确定值的文本。

提示

此元素表示提示或建议。

注意

此元素表示一般注释。

警告

此元素指示警告或注意事项。

使用代码示例

本书附带的补充材料(代码示例、练习等)可在https://github.com/gitops-cookbook下载。

如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com

本书旨在帮助您完成工作。一般情况下,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分,否则您无需联系我们以获得许可。例如,编写使用本书多个代码片段的程序不需要许可。销售或分发 O’Reilly 图书的示例则需要许可。引用本书并引用示例代码来回答问题不需要许可。将本书中大量示例代码整合到产品文档中需要许可。

我们感谢,但通常不要求归属。归属通常包括标题、作者、出版商和 ISBN。例如:“GitOps Cookbook 由 Natale Vinto 和 Alex Soto Bueno(O’Reilly)著。版权所有 2023 年 Natale Vinto 和 Alex Soto Bueno,978-1-492-09747-1。”

如果您觉得您对代码示例的使用超出了公平使用范围或以上授予的权限,请随时通过permissions@oreilly.com联系我们。

O’Reilly 在线学习

注意

40 多年来,O’Reilly Media 提供技术和商业培训、知识和见解,帮助企业成功。

我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专业知识。O’Reilly 的在线学习平台为您提供按需访问的实时培训课程、深入学习路径、交互式编码环境以及来自 O’Reilly 和其他 200 多个出版商的大量文本和视频。欲了解更多信息,请访问http://oreilly.com

如何联系我们

关于本书的评论和问题,请联系出版商:

  • O’Reilly Media, Inc.

  • 1005 Gravenstein Highway North

  • Sebastopol, CA 95472

  • 800-998-9938 (美国或加拿大)

  • 707-829-0515 (国际或本地)

  • 707-829-0104 (传真)

我们为这本书制作了一个网页,上面列出了勘误、示例和任何额外信息。你可以访问这个页面:https://oreil.ly/gitops-cookbook

发送邮件至 bookquestions@oreilly.com 评论或提问关于这本书的技术问题。

欲了解有关我们的书籍和课程的新闻和信息,请访问https://oreilly.com

在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media

在 Twitter 上关注我们:https://twitter.com/oreillymedia

在 YouTube 上观看我们:https://youtube.com/oreillymedia

致谢

我们特别感谢我们的技术审阅员彼得·米隆和安迪·布洛克,他们的准确审查帮助我们改进了这本书的阅读体验。同时也感谢 O’Reilly 全程对我们的帮助。非常感谢我们的同事奥布里·穆赫拉克和科琳·洛布纳,他们在出版这本书时给予了极大的支持。还要感谢卡梅什·桑帕斯和所有在早期版本中给予意见和建议的人们——非常感谢你们的贡献!

Alex Soto

在这个充满挑战的时期,我要感谢圣塔(今年是的),乌里(别停止音乐),圭里(一个骑行者),加维纳,加比(感谢支持),以及埃德加和埃斯特(生活在星期五尤其美好);我的朋友埃德森、塞比(最好的旅伴),伯尔(我从你身上学到了很多),卡梅什和所有红帽开发团队,我们是最棒的。

Jonathan Vila, Abel Salgado 和 Jordi Sola,感谢你们关于 Java 和 Kubernetes 的精彩交流。

最后但同样重要的是,我要感谢安娜一直在这里;感谢我的父母米利和拉蒙为我购买了第一台电脑;感谢我的女儿阿达和亚历山德拉,“你们是我眼中的宝贝。”

纳塔莱·温托

特别感谢阿莱西亚,在我写这本书期间给予我的耐心和激励。还要感谢我的父母为我所做的一切,谢谢你们,你们是最棒的!

第一章:介绍

随着基础设施即代码(IaC)等实践的出现,软件开发已经推动了可以运行应用程序的平台的边界。随着可编程的、API 驱动的平台如公共云和开源基础设施解决方案的普及,这种情况变得更为频繁。几年前开发人员只关注应用程序源代码,而今天他们也有机会编写应用程序运行的基础设施。这给予了控制权并实现了自动化,大大缩短了交付时间。

Kubernetes 是一个流行的开源容器工作负载编排平台,也是在公有或私有云上运行生产应用程序的事实标准。该平台的开放性和可扩展性使得自动化成为可能,从而降低了交付风险并提高了服务质量。此外,这种强大的范式还通过一个越来越受欢迎的方法扩展,称为 GitOps。

1.1 什么是 GitOps?

GitOps 是一种利用 Git 仓库作为单一真相源来交付基础设施即代码的方法和实践。它借鉴了 DevOps 文化的支柱和方法,并提供了一个开始实现结果的框架。GitOps 与 DevOps 的关系密切,因为 GitOps 已成为实施和增强 DevOps、平台工程和 SRE 的流行选择。

GitOps 是一种不可知的方法,可以使用诸如 Git、Kubernetes 和 CI/CD 解决方案等工具构建 GitOps 框架。GitOps 的三大支柱是:

  • Git 是唯一的真相之源

  • 将一切视为代码

  • 操作通过 Git 工作流进行

GitOps 有一个活跃的社区,GitOps 工作组OpenGitOps 提供了一套 GitOps 原则(当前版本为 1.0.0):

声明式

由 GitOps 管理的系统必须通过声明方式表达其期望状态。

版本化和不可变

期望状态存储在一种强制不可变和版本化的方式中,并保留完整的版本历史记录。

自动拉取

软件代理自动从源中拉取期望状态声明。

持续协调

软件代理持续监控实际系统状态,并尝试应用期望状态。

1.2 为什么选择 GitOps?

使用开发人员熟悉的常见基于 Git 的工作流程,GitOps 扩展了从应用程序开发到部署、应用生命周期管理和基础设施配置的现有流程。

应用程序整个生命周期中的每一次变更都在 Git 仓库中进行跟踪,并且可审计。这种方法对开发人员和运维团队都有益,因为它增强了快速跟踪和重现问题的能力,从而提高了整体安全性。其中一个关键点是减少不需要的变更(漂移),并在它们进入生产环境之前进行更正。

以下是 GitOps 在四个关键方面采用的好处摘要:

标准工作流程

使用应用开发团队熟悉的工具和 Git 工作流程

增强安全性

在部署前审查变更,检测配置漂移,并采取行动

可见性与审计

通过 Git 历史捕获和跟踪对集群的任何更改

多集群一致性

可靠且一致地配置多个环境和多个 Kubernetes 集群及其部署

1.3 Kubernetes CI/CD

持续集成(CI)和持续交付(CD)是通过将自动化引入应用开发阶段来频繁交付应用的方法。CI/CD 流水线是 GitOps 最常见的用例之一。

在典型的 CI/CD 流水线中,提交的代码检查 CI 过程,而 CD 过程则检查并应用诸如安全性、基础设施即代码或应用框架设定的其他要求。所有代码更改都被跟踪,使得更新变得简单,同时在需要回滚时提供版本控制。CD 是 GitOps 的领域,它与 CI 部分一起工作,以在多个环境中部署应用,如 图 1-1 所示。

持续集成和持续交付

图 1-1. 持续集成与持续交付

使用 Kubernetes,可以轻松实现集群内的 CI/CD 流水线。您可以使用 CI 软件创建代表您的应用程序的容器映像,并将其存储在容器映像注册表中。然后,像拉取请求这样的 Git 工作流程可以更改 Kubernetes 清单,说明应用程序的部署,并启动 CD 同步循环,如 图 1-2 所示。

应用部署模型

图 1-2. 应用部署模型

本教程将展示在 Kubernetes 上作为 CI/CD 和 GitOps 平台实施此模型的实用配方。

1.4 在 Kubernetes 上使用 GitOps 进行应用部署

由于 GitOps 是一种平台无关的方法,Kubernetes 上的应用部署模型可以是集群内或多集群的。外部的 GitOps 工具可以将 Kubernetes 视为部署应用的目标平台。同时,集群内方法在 Kubernetes 内部运行 GitOps 引擎,用于部署应用并同步一个或多个 Kubernetes 集群中的清单文件。

GitOps 引擎负责 CI/CD 流水线的 CD 部分,并完成 GitOps 循环,该循环由四个主要操作组成,如 图 1-3 所示:

部署

从 Git 部署清单。

监视

监视 Git 仓库或集群状态。

检测漂移

从 Git 描述的内容和集群中实际存在的内容中检测任何变更。

采取行动

执行反映 Git 上内容的操作(回滚或三向差分)。Git 是事实真相,任何更改都通过 Git 工作流程执行。

GitOps 循环

图 1-3. GitOps 循环

在 Kubernetes 中,使用 GitOps 方法部署应用程序至少使用两个 Git 存储库:一个用于应用程序源代码,另一个用于描述应用程序部署的 Kubernetes 清单(如 Deployment、Service 等)。

Figure 1-4 展示了 Kubernetes 上 GitOps 项目的结构。

Kubernetes GitOps 循环

图 1-4. Kubernetes GitOps 循环

以下列表概述了工作流程中的项目:

  1. 应用程序源代码存储库

  2. CI 管道创建容器镜像

  3. 容器镜像注册表

  4. Kubernetes 清单存储库

  5. GitOps 引擎将清单同步到一个或多个集群并检测漂移

1.5 DevOps 与敏捷性

GitOps 是一种以开发者为中心的持续交付和基础设施运营方法,通过 Git 进行开发工作流的自动化。与敏捷软件开发互补,GitOps 对基础设施自动化和应用程序生命周期管理对 DevOps 也是互补的。如你在 Figure 1-5 中所见,这是一个用于自动化运营的开发者工作流程。

敏捷方法论中最关键的一个方面是减少上线时间,这更抽象地描述为从识别需求到其实现所经过的时间。

GitOps 开发循环

图 1-5. GitOps 开发循环

缩短这段时间是基础性的,并需要 IT 组织文化上的变革。看到应用程序实时运行为开发者提供了一个反馈循环,重新设计和改进他们的代码,使项目蓬勃发展。类似于 DevOps,GitOps 在业务流程中也需要文化的采纳。所有操作,如应用程序部署或基础设施更改,只能通过 Git 工作流程来完成。有时,这意味着一种文化的转变。

“Teaching Elephants to Dance (and Fly!)” 演讲由 Burr Sutter 提供了关于背景的清晰理解。大象代表了你的组织当前所处的位置。在传统和现代环境之间的变化阶段,由 GitOps 工具驱动。一些组织有幸可以从头开始,但对于许多企业来说,挑战在于教会他们沉重的大象像优雅的芭蕾舞者一样跳舞。

第二章:要求

本书涉及 GitOps 和 Kubernetes,因此您需要一个容器注册表来发布本书中构建的容器(参见 Recipe 2.1)。

此外,实施 GitOps 方法论需要一个 Git 服务;您将学习如何注册公共 Git 服务,如 GitHub 或 GitLab(参见 Recipe 2.2)。

最后,最好拥有一个 Kubernetes 集群来运行本书中的示例。尽管我们将展示如何安装 Minikube 作为 Kubernetes 集群(参见 Recipe 2.3),并且本书已经在 Minikube 上进行了测试,任何 Kubernetes 安装也应该适用。

让我们准备您的笔记本电脑以执行本书提供的配方。

2.1 注册容器注册表

问题

您希望为容器注册表服务创建一个帐户,以便可以存储生成的容器。

解决方案

在您阅读本书时,可能需要将一些容器发布到公共容器注册表。使用 Docker Hub(docker.io)发布容器。

如果您已经拥有 docker.io 的帐户,可以跳过以下步骤。否则,请继续阅读以了解如何注册帐户。

讨论

访问DockerHub注册一个帐户。页面应类似于图 2-1。

DockerHub 注册页面

图 2-1. DockerHub 注册页面

当页面加载时,填写表单设置 Docker ID、电子邮件和密码,并点击“注册”按钮。

当您注册并确认帐户后,您将可以在前面步骤中的 Docker ID 下发布容器。

参见

另一个流行的容器注册表服务是 quay.io。它可以在云上使用(类似于 docker.io)或者在本地安装。

访问该网站以获取有关 Quay 的更多信息。页面应类似于图 2-2。

Quay 注册页面

图 2-2. Quay 注册页面

2.2 注册 Git 仓库

问题

您希望为 Git 服务创建一个帐户,以便可以将源代码存储在仓库中。

解决方案

在本书中,您可能需要将一些源代码发布到公共 Git 服务。使用 GitHub 作为 Git 服务来创建和派生 Git 仓库。

如果您已经有 GitHub 帐户,可以跳过以下步骤;否则,请继续阅读以了解如何注册帐户。

讨论

访问GitHub 网页注册一个帐户。页面应类似于图 2-3。

GitHub 欢迎页面进行注册

图 2-3. GitHub 欢迎页面进行注册

当页面加载时,点击“注册 GitHub”按钮(见图 2-3)并按照说明操作。登录页面应类似于图 2-4。

GitHub 登录页面

图 2-4. GitHub 登录页面

当您注册并确认帐户后,您将准备好开始在 GitHub 帐户中创建或分叉 Git 仓库。

同时,您需要将书籍源代码仓库分叉到您的帐户中。点击图 2-5 中显示的分叉按钮。

分叉按钮

图 2-5. 分叉按钮

然后在 Owner 部分选择您的帐户(如果尚未选择),并点击“创建分叉”按钮,如图 2-6 所示。

创建分叉按钮

图 2-6. 创建分叉按钮

要跟随下一章节中的示例,请在本地克隆这本书的仓库。如果未明确提到,我们将使用章节仓库中提供的示例:

git clone https://github.com/gitops-cookbook/chapters

参见

另一个流行的 Git 服务是 GitLab。它可以在云上或本地安装使用。

访问GitLab获取更多信息。

2.3 创建本地 Kubernetes 集群

问题

您想在本地启动 Kubernetes 集群。

解决方案

在这本书中,您可能需要一个 Kubernetes 集群来运行大多数的示例。使用 Minikube 在本地机器上启动 Kubernetes 集群。

讨论

Minikube 使用 Docker、Podman、Hyperkit、Hyper-V、KVM 或 VirtualBox 等容器/虚拟化技术,在 Linux 机器上启动一个带有 Kubernetes 集群的 Linux 系统。

为了简便起见并且能在大多数平台上使用,我们将使用 VirtualBox 作为虚拟化系统。

要安装 VirtualBox(如果尚未安装),请访问主页,并点击下载链接,如图 2-7 所示。

注意

对于使用 macOS 的用户,在 macOS Monterey 和 VirtualBox 6.1 上测试了以下说明。在撰写本书时,使用 ARM 版本或 macOS Ventura 时存在一些不兼容性。

gocb 0208

图 2-7. VirtualBox 主页

根据操作系统选择软件包,下载并安装到计算机上。安装 VirtualBox 后(我们使用了 6.1.x 版本),下一步是下载 Minikube 并启动集群。

访问GitHub 仓库,展开“资产”部分,并下载符合您平台规格的 Minikube 文件。例如,在 AMD Mac 上,您应选择 minikube-darwin-amd64,如图 2-8 所示。

解压文件(如果需要),并将其复制为 minikube 的名称放在 PATH 环境变量可访问的目录中,例如 Linux 或 macOS 中的 /usr/local/bin

安装了 VirtualBox 和 Minikube 后,我们可以在本地机器上启动 Kubernetes 集群。让我们安装 Kubernetes 版本 1.23.0,因为这是撰写本书时的最新版本(当然,也可以使用任何之前的版本)。

gocb 0209

图 2-8. Minikube 发行页面

在终端窗口中运行以下命令,分配 8 GB 内存来启动 Kubernetes 集群:

minikube start --kubernetes-version='v1.23.0' /
--driver='virtualbox' --memory=8196 -p gitops ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png) ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png) ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

创建一个版本为 1.23.0 的 Kubernetes 集群

2

使用 VirtualBox 作为虚拟化工具

3

为集群创建一个配置文件名 (gitops) 以便以后引用它

输出行应类似于:

[gitops] Minikube v1.24.0 on Darwin 12.0.1
 Using the virtualbox driver based on user configuration
Starting control plane node gitops in cluster gitops ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
Creating virtualbox VM (CPUs=2, Memory=8196MB, Disk=20000MB) ...
    > kubeadm.sha256: 64 B / 64 B [--------------------------] 100.00% ? p/s 0s
    > kubelet.sha256: 64 B / 64 B [--------------------------] 100.00% ? p/s 0s
    > kubectl.sha256: 64 B / 64 B [--------------------------] 100.00% ? p/s 0s
    > kubeadm: 43.11 MiB / 43.11 MiB [---------------] 100.00% 3.46 MiB p/s 13s
    > kubectl: 44.42 MiB / 44.42 MiB [---------------] 100.00% 3.60 MiB p/s 13s
    > kubelet: 118.73 MiB / 118.73 MiB [-------------] 100.00% 6.32 MiB p/s 19s

    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ... ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    ▪ Configuring RBAC rules ...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
...

  Verifying Kubernetes components...
Enabled addons: storage-provisioner, default-storageclass

 /usr/local/bin/kubectl is version 1.21.0, which
may have incompatibilites with Kubernetes 1.23.0. ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    ▪ Want kubectl v1.23.0? Try 'minikube kubectl -- get pods -A'
Done! kubectl is now configured to use "gitops" cluster and
 "default" namespace by default ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)

1

启动 gitops 集群

2

启动 Kubernetes 集群控制平面

3

检测到我们有一个旧版本的 kubectl 工具

4

集群已启动并运行

为了与 Kubernetes 集群和 Kubernetes CLI 工具版本保持一致,您可以下载 kubectl 1.23.0 版本,运行自 https://dl.k8s.io/release/v1.23.0/bin/darwin/amd64/kubectl

注意

您需要将 darwin/amd64 更改为您的特定架构。例如,在 Windows 上可能是 windows/amd64/kubectl.exe

kubectl CLI 工具复制到一个由 PATH 环境变量访问的目录中,例如在 Linux 或 macOS 中 (/usr/local/bin)。

参见

在本地机器上运行 Kubernetes 的其他方法还有很多。

非常受欢迎的一个是 kind

虽然本书中的示例应适用于任何 Kubernetes 实现,因为只使用了标准资源,但我们只在 Minikube 上进行了测试。

第三章:容器

容器是打包应用程序的流行和标准格式。这种格式是由开放容器倡议(OCI)推广的一种开放标准,是为了在容器格式和运行时周围创建开放行业标准的开放治理结构。这种格式的开放性确保了在不同操作系统、供应商、平台或云上的可移植性和互操作性。Kubernetes 运行容器化应用程序,因此在进入 GitOps 方法管理 Kubernetes 上的应用程序之前,我们提供了一些有用的配方,帮助您了解如何将应用程序打包为容器镜像。

创建镜像的第一步是使用容器引擎打包应用程序,构建包含基础操作系统和额外层(如运行时、库和应用程序)的分层结构。Docker 是广泛使用的开源容器引擎和运行时实现,可以通过指定一个称为 Dockerfile 的清单生成容器镜像(见配方 3.1)。

由于格式开放,可以使用其他工具创建容器镜像。Docker,一个流行的容器引擎,需要安装并执行一个守护程序来处理容器引擎的所有操作。开发者可以使用软件开发工具包(SDK)与 Docker 守护程序进行交互,或者使用像 JiB 这样的无 Docker解决方案来创建容器镜像(见配方 3.2)。

如果不想依赖特定的编程语言或 SDK 来构建容器镜像,可以使用另一个无守护程序解决方案,如 Buildah(见配方 3.3)或 Buildpacks(见配方 3.4)。这些是构建 OCI 容器镜像的其他流行开源工具。通过避免依赖操作系统,这些工具使得自动化更易管理和可移植(见第六章)。

Kubernetes 没有提供用于构建容器镜像的原生机制。但其高度可扩展的架构允许与外部工具进行互操作,并通过平台的可扩展性创建容器镜像。Shipwright 是一个在 Kubernetes 上构建容器镜像的开源框架,提供一个抽象层,可以使用诸如 kaniko、Buildpacks 或 Buildah 等工具来创建容器镜像(见配方 3.5)。

在本章末尾,您将学习如何使用 Dockerfile 从主机上安装了 Docker 或使用 Buildah 和 Buildpacks 等工具创建符合 OCI 标准的容器镜像。

3.1 使用 Docker 构建容器

问题

您希望使用 Docker 为您的应用创建一个容器镜像。

解决方案

首先需要安装Docker

注意

Docker 可用于 Mac、Windows 和 Linux。下载适合您操作系统的安装程序,并参考文档启动 Docker 服务。

开发人员可以通过定义 Dockerfile 来创建容器镜像。关于 Dockerfile 的最佳定义来自于Docker 文档本身:“Dockerfile 是一个文本文档,其中包含用户可以在命令行上调用的所有命令,用于组装镜像。”

容器镜像呈现层次结构,如您在图 3-1 中所见。每个容器镜像都为容器提供基础层,任何更新只是额外的可以在基础上提交的层。

容器镜像层

图 3-1. 容器镜像层

您可以创建像这里显示的 Dockerfile,它将为 Python 应用程序生成一个容器镜像。您也可以在本书的代码库中找到此示例。

FROM registry.access.redhat.com/ubi8/python-39 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png) ENV PORT 8080 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png) EXPOSE 8080 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png) WORKDIR /usr/src/app ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png) COPY requirements.txt ./ ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png) RUN pip install --no-cache-dir -r requirements.txt ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png) COPY . .

ENTRYPOINT ["python"] ![7](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/7.png) CMD ["app.py"] ![8](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/8.png)

1

FROM: 始终从一个基础镜像作为基础层开始。在本例中,我们从一个基于 RHEL 8 的 Universal Base Image (UBI) 公开可用的 Python 3.9 运行时开始。

2

ENV: 为应用设置一个环境变量。

3

EXPOSE: 将端口暴露到容器网络中,在本例中是 TCP 8080 端口。

4

WORKDIR: 在容器内设置工作目录。

5

COPY: 将工作站上源代码文件中的资产复制到容器镜像层,本例中复制到 WORKDIR

6

RUN: 在容器内运行命令,使用基础镜像中已有的工具。在本例中,运行 pip 工具来安装依赖项。

7

ENTRYPOINT: 定义容器内应用的入口点。可以是二进制文件或脚本。在本例中,运行 Python 解释器。

8

CMD: 启动容器时使用的命令。在本例中使用 Python 应用程序 app.py 的名称。

现在可以使用以下命令创建您的容器镜像:

docker build -f Dockerfile -t quay.io/gitops-cookbook/pythonapp:latest
注意

使用您的注册表、用户和存储库更改容器镜像名称。例如:quay.io/youruser/yourrepo:latest。请参阅第 2 章了解如何在 Quay.io 等注册表上创建新帐户。

您的容器镜像正在构建中。Docker 将从公共容器注册表(如 DockerHub、Quay、Red Hat Registry 等)获取现有的层,并根据 Dockerfile 中指定的内容添加新的层。如果已经下载,这些层也可以在特殊存储(称为容器缓存Docker 缓存)中本地可用。

STEP 1: FROM registry.access.redhat.com/ubi8/python-39
Getting image source signatures
Copying blob adffa6963146 done
Copying blob 4125bdfaec5e done
Copying blob 362566a15abb done
Copying blob 0661f10c38cc done
Copying blob 26f1167feaf7 done
Copying config a531ae7675 done
Writing manifest to image destination
Storing signatures
STEP 2: ENV PORT 8080
--> 6dbf4ac027e
STEP 3: EXPOSE 8080
--> f78357fe402
STEP 4: WORKDIR /usr/src/app
--> 547bf8ca5c5
STEP 5: COPY requirements.txt ./
--> 456cab38c97
STEP 6: RUN pip install --no-cache-dir -r requirements.txt
Collecting Flask
  Downloading Flask-2.0.2-py3-none-any.whl (95 kB)
     |████████████████████████████████| 95 kB 10.6 MB/s
Collecting itsdangerous>=2.0
  Downloading itsdangerous-2.0.1-py3-none-any.whl (18 kB)
Collecting Werkzeug>=2.0
  Downloading Werkzeug-2.0.2-py3-none-any.whl (288 kB)
     |████████████████████████████████| 288 kB 1.7 MB/s
Collecting click>=7.1.2
  Downloading click-8.0.3-py3-none-any.whl (97 kB)
     |████████████████████████████████| 97 kB 31.9 MB/s
Collecting Jinja2>=3.0
  Downloading Jinja2-3.0.3-py3-none-any.whl (133 kB)
     |████████████████████████████████| 133 kB 38.8 MB/s
STEP 7: COPY . .
--> 3e6b73464eb
STEP 8: ENTRYPOINT ["python"]
--> acabca89260
STEP 9: CMD ["app.py"]
STEP 10: COMMIT quay.io/gitops-cookbook/pythonapp:latest
--> 52e134d39af
52e134d39af013a25f3e44d25133478dc20b46626782762f4e46b1ff6f0243bb

你的容器镜像现在已经在你的 Docker 缓存中可用,并且可以使用以下命令验证其存在:

docker images

你应该在输出中获取缓存中可用的容器镜像列表。这些可能是你构建或使用 docker pull 命令下载的镜像:

REPOSITORY                           TAG         IMAGE ID      CREATED↳
        SIZE
quay.io/gitops-cookbook/pythonapp    latest      52e134d39af0  6 minutes ago↳
        907 MB

一旦创建了你的镜像,你可以在本地使用它,或将其推送到公共容器注册表中,以便从 CI/CD 流水线等其他地方使用。

首先,你需要登录你的公共注册表。在这个示例中,我们使用 Quay:

docker login quay.io

你应该得到类似这样的输出:

Login Succeeded!

然后,你可以将你的容器镜像推送到注册表中:

docker push quay.io/gitops-cookbook/pythonapp:latest

如确认的,你应该得到类似这样的输出:

Getting image source signatures
Copying blob e6e8a2c58ac5 done
Copying blob 3ba8c926eef9 done
Copying blob 558b534f4e1b done
Copying blob 25f82e0f4ef5 done
Copying blob 7b17276847a2 done
Copying blob 352ba846236b done
Copying blob 2de82c390049 done
Copying blob 26525e00a8d8 done
Copying config 52e134d39a done
Writing manifest to image destination
Copying config 52e134d39a [--------------------------------------] 0.0b / 5.4KiB
Writing manifest to image destination
Storing signatures

讨论

你可以从工作站或任何运行 Docker 服务/守护程序的主机上使用 Docker 这种方式创建容器镜像。

提示

另外,你可以使用公共注册表(如 Quay.io)提供的功能,直接从 Dockerfile 创建容器镜像并将其存储到注册表中。

构建需要访问所有层,因此需要与存储基础层的注册表连接的互联网连接,或者至少在容器缓存中有这些层。Docker 具有分层结构,你的应用程序的任何更改都将提交到现有层的顶部,因此每次只需要添加新变更的增量,无需每次下载所有层。

注意

容器镜像通常从基础操作系统层开始,如 Fedora、CentOS、Ubuntu、Alpine 等。但是,它们也可以从 scratch 开始,这是一个空白层,用于包含仅包含应用程序二进制的超小型镜像。有关更多信息,请参阅 scratch 文档

如果你想运行之前创建的容器镜像,可以使用以下命令:

docker run -p 8080:8080 -ti quay.io/gitops-cookbook/pythonapp:latest

docker run 有很多选项可以启动你的容器。最常见的是:

-p

将容器的端口绑定到运行该容器的主机的端口。

-t

将一个 TTY 附加到容器。

-i

进入交互模式。

-d

进入后台,打印一个哈希值,你可以用它与正在运行的容器异步交互。

上述命令将在 Docker 网络中启动你的应用程序,并将其绑定到工作站的端口 8080:

 * Serving Flask app 'app' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on all addresses.
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://10.0.2.100:8080/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 103-809-567

从一个新的终端尝试访问你正在运行的容器:

curl http://localhost:8080

你应该得到类似这样的输出:

Hello, World!

参见

3.2 使用 Dockerless Jib 构建容器

问题

你是一名软件开发人员,想要在工作站上创建一个容器镜像,而无需安装 Docker 或任何其他额外的软件。

解决方案

如 Recipe 3.1 中讨论的,你需要安装 Docker 引擎来创建容器镜像。Docker 需要权限安装一个作为守护程序运行的服务,因此是操作系统中的一个特权进程。今天,对于开发者来说也有 dockerless 解决方案可用,其中一个流行的是 Jib。

Jib 是 Google 开发的一个开源框架,用于构建符合 OCI 标准的 Java 容器镜像,无需 Docker 或任何容器运行时。Jib 作为一个库,Java 开发者可以将其导入到他们的 Maven 或 Gradle 项目中。这意味着您可以为您的应用程序创建一个容器镜像,而无需编写或维护任何 Dockerfile,将这个复杂性交给 Jib 处理。

我们从以下方法中看到其优势:^(1)

纯 Java

无需 Docker 或 Dockerfile 知识。只需将 Jib 添加为插件,它将为您生成容器镜像。

速度

应用程序分为多个层,将依赖项与类分开。与 Dockerfile 不同,无需重新构建容器镜像;Jib 负责修改发生变化的层。

可复现性

不会触发不必要的更新,因为相同的内容生成相同的镜像。

在现有的 Maven 项目中通过命令行添加插件是启动使用 Jib 构建容器镜像的最简单方法:

mvn compile com.google.cloud.tools:jib-maven-plugin:3.2.0:build -Dimage=<MY IMAGE>

或者,您可以通过在 pom.xml 中添加 Jib 作为插件来实现:

<project>
  ...
  <build>
    <plugins>
      ...
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <to>
            <image>myimage</image>
          </to>
        </configuration>
      </plugin>
      ...
    </plugins>
  </build>
  ...
</project>

这样,您还可以管理其他设置,例如构建的认证或参数。

现在让我们将 Jib 添加到现有的 Java 应用程序中,这是一个在 Spring Boot 中的 Hello World 应用程序,您可以在书籍的存储库中找到。

运行以下命令来创建一个容器镜像,而不使用 Docker,并直接将其推送到容器注册表。在本例中,我们使用 Quay.io,并将容器镜像存储在 quay.io/gitops-cookbook/jib-example:latest,因此您需要提供注册表的凭据:

mvn compile com.google.cloud.tools:jib-maven-plugin:3.2.0:build \
-Dimage=quay.io/gitops-cookbook/jib-example:latest \
-Djib.to.auth.username=<USERNAME> \
-Djib.to.auth.password=<PASSWORD>

这里的认证是通过命令行选项处理的,但 Jib 可以通过 Docker CLI 管理现有的认证,或者从您的 settings.xml 文件中读取凭据。

构建需要一些时间,结果是一个特定于 Java 的容器镜像,基于 adoptOpenJDK 基础镜像,在本地构建并直接推送到注册表。在这种情况下,到 Quay.io:

[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< com.redhat:hello >--------------------------
[INFO] Building hello 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
...
[INFO] Containerizing application to quay.io/gitops-cookbook/jib-example...
[INFO] Using credentials from <to><auth> for quay.io/gitops-cookbook/jib-example
[INFO] The base image requires auth. Trying again for eclipse-temurin:11-jre...
[INFO] Using base image with digest:↳
 sha256:83d92ee225e443580cc3685ef9574582761cf975abc53850c2bc44ec47d7d943O]
[INFO]
[INFO] Container entrypoint set to [java, -cp, @/app/jib-classpath-file,↳
 com.redhat.hello.HelloApplication]FO]
[INFO]
[INFO] Built and pushed image as quay.io/gitops-cookbook/jib-example
[INFO] Executing tasks:
[INFO] [==============================] 100,0% complete
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  41.366 s
[INFO] Finished at: 2022-01-25T19:04:09+01:00
[INFO] ------------------------------------------------------------------------
注意

如果您安装了 Docker 并运行 docker images 命令,您在本地缓存中看不到这个镜像!

讨论

由于不需要任何容器运行时来使用 Jib 构建镜像,因此您的容器镜像不会出现在本地缓存中。您无法通过 docker images 命令看到它,但稍后可以从公共容器注册表拉取它,并将其存储在您的缓存中。

这种方法非常适合开发速度和自动化,CI 系统不需要在运行它的节点上安装 Docker。Jib 可以创建容器镜像而无需任何 Dockerfile。此外,它可以将镜像推送到容器注册表。

如果您也想从一开始将其存储在本地,Jib 可以连接到 Docker 主机并为您执行此操作。

您可以从注册表中拉取您的容器镜像来尝试它:

docker run -p 8080:8080 -ti quay.io/gitops-cookbook/jib-example
Trying to pull quay.io/gitops-cookbook/jib-example:latest...
Getting image source signatures
Copying blob ea362f368469 done
Copying blob d5cc550bb6a0 done
Copying blob bcc17963ea24 done
Copying blob 9b46d5d971fa done
Copying blob 51f4f7c353f0 done
Copying blob 43b2cdfa19bb done
Copying blob fd142634d578 done
Copying blob 78c393914c97 done
Copying config 346462b8d3 done
Writing manifest to image destination
Storing signatures

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.3)

2022-01-25 18:36:24.762  INFO 1 --- [ main] com.redhat.hello.HelloApplication↳
        : Starting HelloApplication using Java 11.0.13 on a719cf76f440 with PID 1↳
         (/app/classes started by root in /)
2022-01-25 18:36:24.765  INFO 1 --- [ main] com.redhat.hello.HelloApplication↳
        : No active profile set, falling back to default profiles: default
2022-01-25 18:36:25.700  INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer↳
  : Tomcat initialized with port(s): 8080 (http)
2022-01-25 18:36:25.713  INFO 1 --- [ main] o.apache.catalina.core.StandardService↳
   : Starting service [Tomcat]
2022-01-25 18:36:25.713  INFO 1 --- [ main] org.apache.catalina.core.StandardEngine↳
  : Starting Servlet engine: [Apache Tomcat/9.0.56]
2022-01-25 18:36:25.781  INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/]↳
       : Initializing Spring embedded WebApplicationContext
2022-01-25 18:36:25.781  INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext↳
 : Root WebApplicationContext: initialization completed in 947 ms
2022-01-25 18:36:26.087  INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer↳
  : Tomcat started on port(s): 8080 (http) with context path ''
2022-01-25 18:36:26.096  INFO 1 --- [ main] com.redhat.hello.HelloApplication↳
        : Started HelloApplication in 1.778 seconds (JVM running for 2.177)

获取 hello 终端:

curl localhost:8080/hello
{"id":1,"content":"Hello, World!"}

参见

3.3 使用 Buildah 构建容器

问题

有时安装或管理 Docker 是不可能的。对于创建容器镜像的无 Docker 解决方案在本地开发或 CI/CD 系统等使用案例中非常有用。

解决方案

OCI 规范是一个开放标准,这有利于容器引擎和容器镜像构建机制的多个开源实现。今天两个日益流行的例子是PodmanBuildah

注意

虽然 Docker 使用单一的分散应用程序来创建、运行和发货容器镜像,但是这里容器管理功能的代码库已经在不同的项目(如 Podman、Buildah 和 Skopeo)之间拆分。Podman 支持已经在 Mac 和 Windows 上可用,然而 Buildah 目前只在 Linux 或 Linux 子系统(如 WSL2 用于 Windows)上可用。请查看文档在您的工作站上安装它。

这些是两个互补的开源项目和命令行工具,它们在 OCI 容器和镜像上工作;然而,它们在专业化上有所不同。Podman 专注于帮助您维护和修改容器镜像的命令和功能,如拉取、标记和推送,而 Buildah 专注于构建容器镜像。通过设计将不同功能解耦在不同的进程中进行,因为作者希望从单一特权进程的 Docker 模型转向轻量级、无根、无守护进程和解耦的工具集,以提高敏捷性和安全性。

提示

按照相同的方法,您会发现Skopeo,这是一个用于移动容器镜像的工具;以及CRI-O,这是一个符合 Kubernetes 容器运行时接口的容器引擎,用于运行应用程序。

Buildah 支持 Dockerfile 格式,但其目标是提供一个更低级别的接口来构建容器镜像,而无需 Dockerfile。Buildah 是一个无守护进程的解决方案,可以在容器内部创建镜像而无需挂载 Docker 套接字。这种功能提升了安全性和可移植性,因为可以轻松地将 Buildah 构建添加到 CI/CD 管道中,而 Linux 或 Kubernetes 节点不需要安装 Docker。

正如我们讨论的,您可以使用或不使用 Dockerfile 创建容器镜像。现在让我们创建一个简单的 HTTPD 容器镜像,而无需 Dockerfile。

您可以从任何基础镜像开始,比如 CentOS:

buildah from centos

您应该会得到类似于这样的输出:

Resolved short name "centos" to a recorded short-name alias↳
 (origin: /etc/containers/registries.conf.d/shortnames.conf)
Getting image source signatures
Copying blob 926a85fb4806 done
Copying config 2f3766df23 done
Writing manifest to image destination
Storing signatures
centos-working-container
提示

与 Docker 和docker images类似,您可以运行命令buildah containers来获取来自容器缓存的可用镜像列表。如果您还安装了 Podman,这与podman images类似。

在这种情况下,容器镜像 ID 是centos-working-container,您可以参考它来创建其他层。

现在让我们在一个新层内安装httpd包:

buildah run centos-working-container yum install httpd -y

你应该得到类似于这样的输出:

CentOS Linux 8 - AppStream                          9.0 MB/s | 8.4 MB     00:00
CentOS Linux 8 - BaseOS                             436 kB/s | 4.6 MB     00:10
CentOS Linux 8 - Extras                              23 kB/s |  10 kB     00:00
Dependencies resolved.
===============================================================================
 Package                      Arch    Version         Repository     Size
===============================================================================
Installing:
 httpd                        x86_64  2.4.37-43.module_el8.5.0+1022+b541f3b1
Installing dependencies:
 apr                          x86_64  1.6.3-12.el8
 apr-util                     x86_64  1.6.1-6.el8
 brotli                       x86_64  1.0.6-3.el8
 centos-logos-httpd           noarch  85.8-2.el8
 httpd-filesystem             noarch  2.4.37-43.module_el8.5.0+1022+b541f3b1
 httpd-tools                  x86_64  2.4.37-43.module_el8.5.0+1022+b541f3b1
 mailcap                      noarch  2.1.48-3.el8
 mod_http2                    x86_64  1.15.7-3.module_el8.4.0+778+c970deab
Installing weak dependencies:
 apr-util-bdb                 x86_64  1.6.1-6.el8
 apr-util-openssl             x86_64  1.6.1-6.el8
Enabling module streams:
...
Complete!

现在让我们在运行 HTTPD 的容器内复制一个欢迎 HTML 页面。你可以在 本书的存储库 中找到源代码。

<html>
    <head>
        <title>GitOps CookBook example</title>
    </head>
    <body>
        <h1>Hello, World!</h1>
    </body>
</html>
buildah copy centos-working-container index.xhtml /var/www/html/index.xhtml

每添加一个新层,你应该得到类似于以下内容的带有新容器镜像哈希的输出:

78c6e1dcd6f819581b54094fd38a3fd8f170a2cb768101e533c964e04aacab2e
buildah config --entrypoint "/usr/sbin/httpd -DFOREGROUND" centos-working-container
buildah commit centos-working-container quay.io/gitops-cookbook/gitops-website

你应该得到类似于这样的输出:

Getting image source signatures
Copying blob 618ce6bf40a6 skipped: already exists
Copying blob eb8c13ba832f done
Copying config b825e91208 done
Writing manifest to image destination
Storing signatures
b825e91208c33371e209cc327abe4f53ee501d5679c127cd71c4d10cd03e5370

你的容器镜像现在位于容器缓存中,准备运行或推送到另一个注册表。

如前所述,Buildah 也可以根据 Dockerfile 创建容器镜像。让我们从这里列出的 Dockerfile 中创建相同的容器镜像:

FROM centos:latest
RUN yum -y install httpd
COPY index.xhtml /var/www/html/index.xhtml
EXPOSE 80
CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
buildah bud -f Dockerfile -t quay.io/gitops-cookbook/gitops-website
STEP 1: FROM centos:latest
Resolved short name "centos" to a recorded short-name alias↳
 (origin: /etc/containers/registries.conf.d/shortnames.conf)
Getting image source signatures
Copying blob 926a85fb4806 done
Copying config 2f3766df23 done
Writing manifest to image destination
Storing signatures
STEP 2: RUN yum -y install httpd
CentOS Linux 8 - AppStream                      9.6 MB/s | 8.4 MB     00:00
CentOS Linux 8 - BaseOS                         7.5 MB/s | 4.6 MB     00:00
CentOS Linux 8 - Extras                          63 kB/s |  10 kB     00:00
Dependencies resolved.
...
Complete!
STEP 3: COPY index.xhtml /var/www/html/index.xhtml
STEP 4: EXPOSE 80
STEP 5: CMD ["/usr/sbin/httpd", "-DFOREGROUND"]
STEP 6: COMMIT quay.io/gitops-cookbook/gitops-website
Getting image source signatures
Copying blob 618ce6bf40a6 skipped: already exists
Copying blob 1be523a47735 done
Copying config 3128caf147 done
Writing manifest to image destination
Storing signatures
--> 3128caf1475
3128caf147547e43b84c13c241585d23a32601f2c2db80b966185b03cb6a8025

如果你也安装了 Podman,你可以这样运行它:

podman run -p 8080:80 -ti quay.io/gitops-cookbook/gitops-website

然后你可以通过在 http://localhost:8080 上打开浏览器来测试它。

讨论

使用 Buildah,你有机会从头开始或从 Dockerfile 开始创建容器镜像。你不需要安装 Docker,一切都围绕安全性设计:非特权机制、无守护程序的实用程序和更精细的控制创建镜像层。

Buildah 还可以从零开始构建镜像,因此它创建一个类似于 FROM scratch Dockerfile 语句的空层。这个方面对于创建只包含运行应用程序所需包的超轻量级镜像非常有用,正如你可以在 图 3-2 中看到的。

Buildah image shrink

图 3-2. Buildah 图像缩小

一个很好的示例用例是考虑开发镜像与分阶段或生产镜像。在开发过程中,容器镜像可能需要编译器和其他工具。然而,在生产环境中,你可能只需要运行时或你的包。

参见

3.4 使用 Buildpacks 构建容器

问题

在大规模情况下,通过 Dockerfiles 创建容器显得有些挑战。你需要一个工具来补充 Docker,可以检查你的应用程序源代码,从而无需编写 Dockerfile 就能创建容器镜像。

解决方案

Cloud Native Buildpacks 是一个开源项目,提供一组可执行文件来检查你的应用程序源代码,并创建一个构建和运行你的应用程序计划。

Buildpacks 可以根据应用程序源代码创建符合 OCI 标准的容器镜像,而无需 Dockerfile,正如你可以在 图 3-3 中看到的。

Buildpacks 构建

图 3-3. Buildpacks 构建

这个机制由两个阶段组成:

检测

Buildpacks 工具将浏览你的源代码,以发现使用的编程语言或框架(例如 POM、NPM 文件、Python requirements 等),并为构建分配适当的 buildpack。

构建

一旦找到一个 buildpack,源代码就会被编译,并且 Buildpacks 将创建一个带有适当入口点和启动脚本的容器镜像。

要使用 Buildpacks,你需要下载适合你操作系统(Mac、Windows、Linux)的 pack CLI,并且安装 Docker。

提示

在 macOS 上,通过Homebrew安装pack,如下所示:

brew install buildpacks/tap/pack

现在让我们开始使用 Buildpacks 从一个示例 Node.js 应用程序创建我们的容器镜像。您可以在这个书的存储库中找到应用程序源代码。

cd chapters/ch03/nodejs-app

应用程序的目录结构包含一个package.json文件,列出了此构建所需的 Node.js 包,这有助于 Buildpacks 理解使用哪个构建包。

您可以使用以下命令来验证:

pack builder suggest

您应该会得到类似于以下输出:

Suggested builders:
        Google:                gcr.io/buildpacks/builder:v1↳
              Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js,↳
              and Python
        Heroku:                heroku/buildpacks:18↳
               Base builder for Heroku-18 stack, based on ubuntu:18.04 base↳
               image
        Heroku:                heroku/buildpacks:20↳
               Base builder for Heroku-20 stack, based on ubuntu:20.04 base↳
               image
        Paketo Buildpacks:     paketobuildpacks/builder:base↳
               Ubuntu bionic base image with buildpacks for Java, .NET Core,↳
               Node.js, Go, Python, Ruby, NGINX and Procfile
        Paketo Buildpacks:     paketobuildpacks/builder:full↳
               Ubuntu bionic base image with buildpacks for Java, .NET Core,↳
               Node.js, Go, Python, PHP, Ruby, Apache HTTPD, NGINX and Procfile
        Paketo Buildpacks:     paketobuildpacks/builder:tiny↳
               Tiny base image (bionic build image, distroless-like run image)↳
               with buildpacks for Java, Java Native Image and Go

现在您可以决定选择其中一个建议的构建包。让我们尝试paketobuildpacks/builder:base,它也包含 Node.js 运行时:

pack build nodejs-app --builder paketobuildpacks/builder:base
提示

运行pack builder inspect paketobuildpacks/builder:base命令以了解此构建包中可用的库和框架的确切内容。

构建过程应该会相应地开始,并在一段时间后完成,您应该会得到类似于以下输出:

base: Pulling from paketobuildpacks/builder
bf99a8b93828: Pulling fs layer
...
Digest: sha256:7034e52388c11c5f7ee7ae8f2d7d794ba427cc2802f687dd9650d96a70ac0772
Status: Downloaded newer image for paketobuildpacks/builder:base
base-cnb: Pulling from paketobuildpacks/run
bf99a8b93828: Already exists
9d58a4841c3f: Pull complete
77a4f59032ac: Pull complete
24e58505e5e0: Pull complete
Digest: sha256:59aa1da9db6d979e21721e306b9ce99a7c4e3d1663c4c20f74f9b3876cce5192
Status: Downloaded newer image for paketobuildpacks/run:base-cnb
===> ANALYZING
Previous image with name "nodejs-app" not found
===> DETECTING
5 of 10 buildpacks participating
paketo-buildpacks/ca-certificates 3.0.1
paketo-buildpacks/node-engine     0.11.2
paketo-buildpacks/npm-install     0.6.2
paketo-buildpacks/node-module-bom 0.2.0
paketo-buildpacks/npm-start       0.6.1
===> RESTORING
===> BUILDING
...
Paketo NPM Start Buildpack 0.6.1
  Assigning launch processes
    web: node server.js

===> EXPORTING
Adding layer 'paketo-buildpacks/ca-certificates:helper'
Adding layer 'paketo-buildpacks/node-engine:node'
Adding layer 'paketo-buildpacks/npm-install:modules'
Adding layer 'launch.sbom'
Adding 1/1 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Setting default process type 'web'
Saving nodejs-app...
*** Images (82b805699d6b):
      nodejs-app
Adding cache layer 'paketo-buildpacks/node-engine:node'
Adding cache layer 'paketo-buildpacks/npm-install:modules'
Adding cache layer 'paketo-buildpacks/node-module-bom:cyclonedx-node-module'
Successfully built image nodejs-app

现在让我们用 Docker 来运行它:

docker run --rm -p 3000:3000 nodejs-app

您应该会得到类似于以下输出:

Server running at http://0.0.0.0:3000/

查看正在运行的应用程序:

curl http://localhost:3000/

您应该会得到类似于以下输出:

Hello Buildpacks!

讨论

Cloud Native Buildpacks 是 Cloud Native Computing Foundation(CNCF)中的一个孵化项目,支持 Docker 和 Kubernetes。在 Kubernetes 上,它可以与Tekton一起使用,Tekton 是一个 Kubernetes 原生的 CI/CD 系统,可以作为 Tekton Task运行 Buildpacks 来创建容器镜像。它最近采用了Boson 项目,通过构建包使得在 Kubernetes 上使用 Knative 提供函数即服务(FaaS)体验成为可能。

参见

3.5 在 Kubernetes 中使用 Shipwright 和 kaniko 构建容器

问题

您需要创建一个容器镜像,并且您希望使用 Kubernetes 来完成。

解决方案

Kubernetes 作为一个容器编排平台以部署和管理应用程序而闻名。然而,它不包含开箱即用的构建容器镜像的支持。根据Kubernetes 文档的说法:“(Kubernetes)不部署源代码,也不构建您的应用程序。持续集成、交付和部署(CI/CD)工作流取决于组织文化、偏好以及技术要求。”

如前所述,一个标准的选择是依赖于 CI/CD 系统,例如 Tekton(参见第六章)。另一个选择是使用一个管理构建的框架,该框架支持许多底层工具,就像我们在之前的示例中讨论的那样。一个例子是 Shipwright。

Shipwright是一个在 Kubernetes 上构建容器镜像的可扩展框架。它支持流行工具如 Buildah、Cloud Native Buildpacks 和 kaniko。它使用 Kubernetes 风格的 API,并通过 Tekton 运行工作负载。

对开发人员的好处是通过定义一个最小的 YAML 文件来简化构建容器镜像的方法,不需要任何关于容器或容器引擎的先前知识。这种方法使得此解决方案与 Kubernetes API 生态系统高度集成且无关。

首先要做的是将 Shipwright 安装到您的 Kubernetes 集群中,比如 kind 或 Minikube(参见第二章),根据 文档OperatorHub.io 进行操作。

提示

使用运算符和运算符生命周期管理器(OLM)在 Kubernetes 上安装/卸载软件时提供了一致性,以及依赖管理和生命周期控制。例如,如果通过运算符安装 Shipwright,Tekton Operator 依赖项会自动解析并安装。查看 OLM 文档 了解此方法的详细信息。

让我们按照文档中的标准流程进行。首先,您需要安装 Tekton 依赖项。在撰写本书时,版本为 0.30.0

kubectl apply -f \
  https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.30.0/release.yaml

然后安装 Shipwright。在撰写本书时,版本为 0.7.0

kubectl apply -f \
  https://github.com/shipwright-io/build/releases/download/v0.7.0/release.yaml

最后,安装 Shipwright 构建策略:

kubectl apply -f \
  https://github.com/shipwright-io/build/releases/download/v0.7.0/sample-strategies.yaml

一旦安装了 Shipwright,您可以开始使用其中一个工具创建您的容器镜像构建:

  • kaniko

  • 云原生 Buildpacks

  • BuildKit

  • Buildah

让我们探索 kaniko。

kaniko 是另一种无需 Docker 的解决方案,用于在容器或 Kubernetes 集群中从 Dockerfile 构建容器镜像。Shipwright 通过额外的 API 将工具(如 kaniko)引入 Kubernetes,以创建容器镜像,作为可扩展的 Kubernetes 构建系统的抽象层。

让我们探索从集群资源定义(CRD)中定义的 API:

ClusterBuildStrategy

表示要执行的构建类型。

Build

表示构建。它包括一个 ClusterBuildStrategy 对象的规范。

BuildRun

表示正在运行的构建。创建此对象时启动构建。

运行以下命令来检查所有可用的 ClusterBuildStrategy(CBS)对象:

kubectl get cbs

您应该获得一份可消费的 CBS 列表:

NAME                     AGE
buildah                  26s
buildkit                 26s
buildpacks-v3            26s
buildpacks-v3-heroku     26s
kaniko                   26s
kaniko-trivy             26s
ko                       26s
source-to-image          26s
source-to-image-redhat   26s
注意

此 CRD 是集群范围的,适用于所有命名空间。如果您没有看到任何项目,请按照之前讨论的方式安装 Shipwright 构建策略。

Shipwright 将在 Kubernetes 节点容器缓存上生成一个容器镜像,然后可以将其推送到容器注册表。

您需要提供凭据以将图像推送到注册表,以 Kubernetes Secret 的形式。例如,如果使用 Quay,可以创建如下所示的凭据:

REGISTRY_SERVER=quay.io
REGISTRY_USER=<your_registry_user>
REGISTRY_PASSWORD=<your_registry_password>
EMAIL=<your_email>
kubectl create secret docker-registry push-secret \
    --docker-server=$REGISTRY_SERVER \
    --docker-username=$REGISTRY_USER \
    --docker-password=$REGISTRY_PASSWORD  \
    --docker-email=$EMAIL
提示

使用 Quay,您可以使用加密密码而不是使用您的账户密码。有关更多详细信息,请参阅文档。

现在让我们创建一个build-kaniko.yaml文件,其中包含将使用 kaniko 将 Node.js 示例应用程序容器化的Build对象。您可以在这个书籍的存储库中找到源代码:

apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
  name: buildpack-nodejs-build
spec:
  source:
    url: https://github.com/shipwright-io/sample-nodejs ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    contextDir: docker-build ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  strategy:
    name: kaniko ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    kind: ClusterBuildStrategy
  output:
    image: quay.io/gitops-cookbook/sample-nodejs:latest ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    credentials:
      name: push-secret ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)

1

用于获取源代码的存储库。

2

源代码所在的目录。

3

要使用的ClusterBuildStrategy

4

结果容器映像的目的地。请更改为您的容器注册表仓库。

5

用于身份验证并推送映像到容器注册表的密钥。

现在,让我们创建Build对象:

kubectl create -f build-kaniko.yaml

您应该会得到类似于这样的输出:

build.shipwright.io/kaniko-nodejs-build created

让我们列出可用的构建:

kubectl get builds

您应该会得到类似于以下的输出:

NAME                  REGISTERED   REASON      BUILDSTRATEGYKIND↳
      BUILDSTRATEGYNAME   CREATIONTIME
kaniko-nodejs-build   True         Succeeded   ClusterBuildStrategy↳
      kaniko              13s

到目前为止,您的BuildREGISTERED,但尚未开始。让我们创建以下对象以便启动它:

apiVersion: shipwright.io/v1alpha1
kind: BuildRun
metadata:
  generateName: kaniko-nodejs-buildrun-
spec:
  buildRef:
    name: kaniko-nodejs-build
kubectl create -f buildrun.yaml

如果您检查正在运行的 pod 列表,应该会看到一个正在创建中:

kubectl get pods
NAME                                           READY   STATUS            RESTARTS↳
      AGE
kaniko-nodejs-buildrun-b9mmb-qbrgl-pod-dk7xt   0/3     PodInitializing   0↳
      19s

STATUS变化时,构建将开始,并且您可以通过检查此 pod 中用于在多个步骤中运行构建的容器的日志来跟踪进度:

步骤-源默认

用于获取源代码的第一步

步骤-构建和推送

运行构建的步骤,可以是从源代码或像这种情况下使用 kaniko 的 Dockerfile

步骤-结果

构建的结果

让我们检查构建阶段的日志:

kubectl logs -f kaniko-nodejs-buildrun-b9mmb-qbrgl-pod-dk7xt -c step-build-and-push
INFO[0001] Retrieving image manifest ghcr.io/shipwright-io/shipwright-samples/node:12
INFO[0001] Retrieving image ghcr.io/shipwright-io/shipwright-samples/node:12↳
 from registry ghcr.io
INFO[0002] Built cross stage deps: map[]
INFO[0002] Retrieving image manifest ghcr.io/shipwright-io/shipwright-samples/node:12
INFO[0002] Returning cached image manifest
INFO[0002] Executing 0 build triggers
INFO[0002] Unpacking rootfs as cmd COPY . /app requires it.
INFO[0042] COPY . /app
INFO[0042] Taking snapshot of files...
INFO[0042] WORKDIR /app
INFO[0042] cmd: workdir
INFO[0042] Changed working directory to /app
INFO[0042] No files changed in this command, skipping snapshotting.
INFO[0042] RUN     pwd &&     ls -l &&     npm install &&↳
     npm run print-http-server-version
INFO[0042] Taking snapshot of full filesystem...
INFO[0052] cmd: /bin/sh
INFO[0052] args: [-c pwd &&     ls -l &&     npm install &&↳
     npm run print-http-server-version]
INFO[0052] Running: [/bin/sh -c pwd &&     ls -l &&     npm install &&↳
     npm run print-http-server-version]
/app
total 44
-rw-r--r-- 1 node node   261 Jan 27 14:29 Dockerfile
-rw-r--r-- 1 node node 30000 Jan 27 14:29 package-lock.json
-rw-r--r-- 1 node node   267 Jan 27 14:29 package.json
drwxr-xr-x 2 node node  4096 Jan 27 14:29 public
npm WARN npm-simple-renamed@0.0.1 No repository field.
npm WARN npm-simple-renamed@0.0.1 No license field.

added 90 packages from 40 contributors and audited 90 packages in 6.405s

10 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

> npm-simple-renamed@0.0.1 print-http-server-version /app
> serve -v

13.0.2
INFO[0060] Taking snapshot of full filesystem...
INFO[0062] EXPOSE 8080
INFO[0062] cmd: EXPOSE
INFO[0062] Adding exposed port: 8080/tcp
INFO[0062] CMD ["npm", "start"]
INFO[0070] Pushing image to quay.io/gitops-cookbook/sample-nodejs:latest
INFO[0393] Pushed image to 1 destinations

映像已构建并推送到注册表,您也可以通过此命令检查结果:

kubectl get buildruns

并在您的注册表上,如图 3-4 所示。

Image pushed to Quay.io

图 3-4. 图像推送到 Quay

讨论

Shipwright 提供了在 Kubernetes 上创建容器映像的便捷方法,其不可知的方法使其强大且可互操作。该项目旨在成为 Kubernetes 的 Build API,为开发人员在 Kubernetes 上自动化提供更简便的路径。当 Tekton 在后台运行创建构建时,Shipwright 也使得从微流水线过渡到扩展流水线工作流更加容易。

作为参考,如果您想要使用 Buildah 而不是 kaniko 创建一个构建,只需在您的Build对象中更改ClusterBuildStrategy

apiVersion: shipwright.io/v1alpha1
kind: Build
metadata:
  name: buildpack-nodejs-build
spec:
  source:
    url: https://github.com/shipwright-io/sample-nodejs
    contextDir: source-build ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  strategy:
    name: buildah ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    kind: ClusterBuildStrategy
  output:
    image: quay.io/gitops-cookbook/sample-nodejs:latest
    credentials:
      name: push-secret

1

正如我们之前在 Recipe 3.3 中讨论的那样,Buildah 可以根据源代码创建容器映像。它不需要 Dockerfile。

2

选择 Buildah 作为ClusterBuildStrategy

3.6 最后的想法

容器格式是打包应用程序的事实标准,今天许多工具帮助创建容器映像。开发人员可以使用 Docker 或其他工具和框架创建映像,然后在任何 CI/CD 系统中使用它们部署其应用程序到 Kubernetes。

虽然 Kubernetes 本身不构建容器映像,但一些工具与 Kubernetes API 生态系统交互以添加此功能。这一方面提高了开发速度和环境一致性,将这种复杂性委托给平台。

在接下来的章节中,您将看到如何使用 Kustomize 或 Helm 等工具控制在 Kubernetes 上运行的容器的部署,然后如何添加自动化支持高度可扩展的工作负载与 CI/CD 和 GitOps。

^(1) 欲了解有关 Jib 的演示,请参阅 Appu Goundan 和 Qingyang Chen 在 Velocity San Jose 2018 的演示

第四章:Kustomize

总结来说,将一些 YAML 文件应用到 Kubernetes 集群中并检查结果就是部署到 Kubernetes 集群。

难点在于开发初始的 YAML 文件版本;之后,通常只会进行小的更改,例如更新容器镜像标签版本、副本数量或新的配置值。一种选项是直接在 YAML 文件中进行这些更改——虽然有效,但是版本中的任何错误(修改错误的行、误删内容、放入错误的空白符)可能是灾难性的。

因此,一些工具允许您定义基本的 Kubernetes 清单(这些清单不经常更改)和特定的文件(可能是每个环境一个文件),用于设置更频繁更改的参数。其中一个工具就是 Kustomize。

在这一章中,您将学习如何使用 Kustomize 以无模板的方式管理 Kubernetes 资源文件,而不使用任何 DSL。

第一步是创建一个 Kustomize 项目并将其部署到 Kubernetes 集群中(参见 4.1 节)。

第一次部署后,应用程序将自动更新新的容器镜像、新的配置值或任何其他字段,例如副本数(参见 4.2 和 4.3 节)。

如果您有多个运行环境(例如,预备、生产等),则需要类似的管理它们。尽管有其特殊性,Kustomize 允许您为每个环境定义一组自定义值(参见 4.4 节)。

应用程序配置值通常被映射为 Kubernetes 的 ConfigMap 属性。在 ConfigMap 上进行任何更改(及其在集群上的后续更新)都不会触发应用程序的滚动更新,这意味着应用程序将继续以先前的版本运行,直到您手动重新启动它。

Kustomize 提供了一些函数,用于在应用程序的 ConfigMap 更改时自动执行滚动更新(参见 4.5 节)。

4.1 使用 Kustomize 部署 Kubernetes 资源

问题

您希望一次部署多个 Kubernetes 资源。

解决方案

使用 Kustomize 配置要部署的资源。

将应用程序部署到 Kubernetes 集群并不像只需应用一个包含 Kubernetes Deployment 对象的 YAML/JSON 文件那样简单。通常,必须定义其他 Kubernetes 对象,如 ServiceIngressConfigMaps 等,这在管理和更新这些资源(维护的资源越多,更新错误的可能性就越大)以及将其应用于集群方面稍微复杂一些(我们应该运行多个 kubectl 命令吗?)。

Kustomize 是一个 CLI 工具,集成在 kubectl 工具中,用于以无模板的方式管理、自定义和应用 Kubernetes 资源。

使用 Kustomize,你需要设置一个基本目录,其中包含标准的 Kubernetes 资源文件(无需占位符),并创建一个名为 kustomization.yaml 的文件,声明资源和自定义内容,正如你可以在 图 4-1 中看到的。

Kustomize 布局

图 4-1. Kustomize 布局

让我们部署一个简单的网页,包含 HTML、JavaScript 和 CSS 文件。

首先,在终端窗口中创建一个名为 pacman 的目录,然后创建三个 Kubernetes 资源文件,创建一个 Namespace,一个 Deployment 和一个 Service,内容如下。

pacman/namespace.yaml 中的命名空间:

apiVersion: v1
kind: Namespace
metadata:
  name: pacman

pacman/deployment.yaml 中的部署文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman-kikd
  namespace: pacman
  labels:
    app.kubernetes.io/name: pacman-kikd
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
          - image: lordofthejars/pacman-kikd:1.0.0
            imagePullPolicy: Always
            name: pacman-kikd
            ports:
              - containerPort: 8080
                name: http
                protocol: TCP

pacman/service.yaml 中的服务文件:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: pacman
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app.kubernetes.io/name: pacman-kikd

注意,这些文件是 Kubernetes 文件,您可以将它们应用到 Kubernetes 集群中,因为它们没有使用任何特殊字符或占位符。

第二步是在 pacman 目录中创建 kustomization.yaml 文件,其中包含属于应用程序的资源列表,在运行 Kustomize 时应用这些资源:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
resources: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
- ./namespace.yaml
- ./deployment.yaml
- ./service.yaml

1

Kustomization 文件

2

深度优先顺序处理的属于应用程序的资源

现在,我们可以通过运行以下命令将 kustomization 文件应用到运行中的集群:

kubectl apply --dry-run=client -o yaml \ ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
              -k ./ ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png) ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

打印 kustomization 运行的结果,而不将结果发送到集群

2

使用 -k 选项设置 kubectl 使用 kustomization 文件

3

具有父级 kustomization.yaml 文件的目录

注意

我们假设您已经启动了一个 Minikube 集群,如 Recipe 2.3 所示。

如果未使用 dry-run 选项,输出的 YAML 文件将发送到服务器:

apiVersion: v1
items: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
- apiVersion: v1
  kind: Namespace ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  metadata:
    name: pacman
- apiVersion: v1
  kind: Service ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  metadata:
    labels:
      app.kubernetes.io/name: pacman-kikd
    name: pacman-kikd
    namespace: pacman
  spec:
    ports:
    - name: http
      port: 8080
      targetPort: 8080
    selector:
      app.kubernetes.io/name: pacman-kikd
- apiVersion: apps/v1
  kind: Deployment ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
  metadata:
    labels:
      app.kubernetes.io/name: pacman-kikd
    name: pacman-kikd
    namespace: pacman
  spec:
    replicas: 1
    selector:
      matchLabels:
        app.kubernetes.io/name: pacman-kikd
    template:
      metadata:
        labels:
          app.kubernetes.io/name: pacman-kikd
      spec:
        containers:
        - image: lordofthejars/pacman-kikd:1.0.0
          imagePullPolicy: Always
          name: pacman-kikd
          ports:
          - containerPort: 8080
            name: http
            protocol: TCP
kind: List
metadata: {}

1

列出 kustomization.yaml 中定义的所有 Kubernetes 对象以应用

2

命名空间文档

3

服务文档

4

部署文档

讨论

resources 部分支持不同的输入,除了直接设置 YAML 文件之外。

例如,您可以设置一个具有自己的 kustomization.yaml 和 Kubernetes 资源文件的基本目录,并从另一个目录中的另一个 kustomization.yaml 文件引用它。

给定以下目录布局:

.
├── base
│   ├── kustomization.yaml
│   └── deployment.yaml
├── kustomization.yaml
├── configmap.yaml

base 目录中的 Kustomization 定义:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml

您会看到根目录链接到 base 目录,并包含一个 ConfigMap 定义:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./base
- ./configmap.yaml

因此,应用根 kustomization 文件将自动应用在基础 kustomization 文件中定义的资源。

此外,resources 可以按照 HashiCorp URL 格式引用来自 URL 的外部资产。例如,我们通过设置 URL 引用 GitHub 存储库:

resources:
- github.com/lordofthejars/mysql ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
- github.com/lordofthejars/mysql?ref=test ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

具有根级 kustomization.yaml 文件的存储库

2

分支测试上具有根级 kustomization.yaml 文件的存储库

您已经看到如何使用 kubectl 应用 Kustomize 文件,但 Kustomize 也配备了自己的 CLI 工具,提供一组命令与 Kustomize 资源交互。

kubectl 构建 Kustomize 资源的等效命令是:

kustomize build

输出为:

apiVersion: v1
kind: Namespace
metadata:
  name: pacman
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: pacman
spec:
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  selector:
    app.kubernetes.io/name: pacman-kikd
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: pacman
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
      - image: lordofthejars/pacman-kikd:1.0.0
        imagePullPolicy: Always
        name: pacman-kikd
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP

如果要将 kustomize 生成的输出应用于集群,请在与 kustomization.yaml 文件相同的目录中运行以下命令:

kustomize build . | kubectl apply -f -

另请参阅

4.2 在 Kustomize 中更新容器镜像

问题

要使用 Kustomize 从部署文件更新容器镜像。

解决方案

使用 images 部分更新容器镜像。

软件开发中最重要且最常用的操作之一是更新应用程序至新版本,无论是修复错误还是添加新功能。在 Kubernetes 中,这意味着您需要创建一个新的容器镜像,并使用 tag 部分进行命名(<registry>/<username>/<project>:<tag>)。

给定以下部分部署文件:

spec:
    containers:
        - image: lordofthejars/pacman-kikd:1.0.0 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
        imagePullPolicy: Always
        name: pacman-kikd

1

部署了 Service 1.0.0

通过在 kustomization.yaml 文件中的 images 部分将版本标签更新为 1.0.1:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./namespace.yaml
- ./deployment.yaml
- ./service.yaml
images: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
- name: lordofthejars/pacman-kikd ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  newTag: 1.0.1 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

images 部分

2

将图像名称设置为 update

3

设置图像的新标签值

最后,使用 kubectl 中的 dry-runkustomize 来验证部署文件输出包含新的标签版本。在终端窗口中,运行以下命令:

kustomize build

上述命令的输出为:

...
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: pacman
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
      - image: lordofthejars/pacman-kikd:1.0.1 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
        imagePullPolicy: Always
        name: pacman-kikd
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP

1

kustomize.yaml 文件中设置版本号

注意

Kustomize 不会对原始的 deployment.yaml 文件造成影响,这意味着它仍然包含原始的标签(1.0.0)。

讨论

更新 newTag 字段的一种方法是编辑 kustomization.yaml 文件,但您也可以使用 kustomize 工具来实现这一目的。

在与 kustomization.yaml 文件相同的目录中运行以下命令:

kustomize edit set image lordofthejars/pacman-kikd:1.0.2

检查 kustomization.yaml 文件的内容,确认 newTag 字段已更新:

...
images:
- name: lordofthejars/pacman-kikd
  newTag: 1.0.2

4.3 在 Kustomize 中更新任何 Kubernetes 字段

问题

您希望使用 Kustomize 更新字段(例如副本数)。

解决方案

使用 patches 部分指定使用 JSON Patch 规范进行更改。

在上一个示例中,您看到如何更新容器镜像标签,但有时您可能会更改其他参数,例如副本数量或添加注解、标签、限制等。

为了涵盖这些情况,Kustomize 支持使用 JSON 补丁来修改任何作为 Kustomize 资源定义的 Kubernetes 资源。要使用它,您需要指定要应用的 JSON 补丁表达式以及应用补丁的资源。

例如,我们可以在以下部分部署文件中,将副本数从一个修改为三:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman-kikd
  namespace: pacman
  labels:
    app.kubernetes.io/name: pacman-kikd
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
...

首先,让我们更新 kustomization.yaml 文件,以修改部署文件中定义的副本数:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
patches: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  - target: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      version: v1
      group: apps
      kind: Deployment
      name: pacman-kikd
      namespace: pacman
    patch: |- ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
      - op: replace ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
        path: /spec/replicas ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
        value: 3 ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)

1

补丁资源。

2

target 部分设置需要更改的 Kubernetes 对象。这些值与之前创建的部署文件匹配。

3

补丁表达式。

4

值的修改。

5

要修改的字段路径。

6

新值。

最后,使用 kubectl 中的 dry-runkustomize 来验证部署文件的输出是否包含新的标签版本。在终端窗口中运行以下命令:

kustomize build

上述命令的输出是:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: pacman
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
...
提示

replicas 值也可以通过 kustomization.yaml 文件中的 replicas 字段进行更新。

使用 replicas 字段的等效 Kustomize 文件显示在以下片段中:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
replicas:
- name: pacman-kikd ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  count: 3 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
resources:
- deployment.yaml

1

更新副本的部署

2

新的 replicas

Kustomize 允许您添加(或删除)值,除了修改值外。让我们看看如何添加一个新的标签:

...
patches:
  - target:
      version: v1
      group: apps
      kind: Deployment
      name: pacman-kikd
      namespace: pacman
    patch: |-
      - op: replace
        path: /spec/replicas
        value: 3
      - op: add ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
        path: /metadata/labels/testkey ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
        value: testvalue ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

添加一个带有值的新字段

2

要添加的字段路径

3

要设置的值

应用文件的结果是:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
    testkey: testvalue ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  name: pacman-kikd
  namespace: pacman
spec:
  replicas: 3
  selector:
...

1

添加的标签

讨论

而不是嵌入 JSON 补丁表达式,您可以创建一个包含 JSON 补丁表达式的 YAML 文件,并使用 path 字段而不是 patch 来引用它。

创建一个名为 external_patch 的外部补丁文件,其中包含 JSON 补丁表达式:

- op: replace
  path: /spec/replicas
  value: 3
- op: add
  path: /metadata/labels/testkey
  value: testvalue

并将 patch 字段更改为指向补丁文件的 path

...
patches:
  - target:
      version: v1
      group: apps
      kind: Deployment
      name: pacman-kikd
      namespace: pacman
    path: external_patch.yaml ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

外部补丁文件路径

除了 JSON 补丁表达式外,Kustomize 还支持 Strategic Merge Patch 来修改 Kubernetes 资源。简而言之,Strategic Merge Patch(或 SMP)是一个不完整的 YAML 文件,与一个完整的 YAML 文件合并。

仅需要一个包含容器名称信息的最小部署文件来更新容器映像:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
patches:
  - target:
      labelSelector: "app.kubernetes.io/name=pacman-kikd" ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    patch: |- ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      apiVersion: apps/v1 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
      kind: Deployment
      metadata:
        name: pacman-kikd
      spec:
        template:
          spec:
            containers:
              - name: pacman-kikd
                image: lordofthejars/pacman-kikd:1.2.0 ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)

1

使用标签选择目标

2

补丁足够智能,可以检测到它是 SMP 还是 JSON 补丁

3

这是一个最小的部署文件

4

只设置要更改的字段,其余保持不变

生成的输出是原始的deployment.yaml 文件,但包含新的容器镜像:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: pacman
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
      - image: lordofthejars/pacman-kikd:1.2.0
        imagePullPolicy: Always
...
提示

path 也受到支持。

参见

4.4 部署到多个环境

问题

您希望使用 Kustomize 在不同的命名空间中部署相同的应用程序。

解决方案

使用 namespace 字段来设置目标命名空间。

在某些情况下,将应用程序部署到不同的命名空间是有好处的;例如,一个命名空间可以用作演示环境,另一个用作生产命名空间。在这两种情况下,基础的 Kubernetes 文件是相同的,只有一些最小的更改,如部署的命名空间、一些配置参数或容器版本等。图 4-2 展示了一个例子。

Kustomize 布局

图 4-2. Kustomize 布局

kustomize 允许您使用 namespace 字段将多个更改定义为常见基础上的叠加。例如,所有基础 Kubernetes 资源放在 base 目录中,并为每个环境的定制创建一个新目录:

.
├── base ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
│  ├── deployment.yaml
│  └── kustomization.yaml
├── production
│   └── kustomization.yaml ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
└── staging
    └── kustomization.yaml ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

基础文件

2

特定于生产环境的更改

3

特定于演示环境的更改

基础 kustomization 文件包含其资源的引用:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml

每个环境目录都有一个 kustomization 文件,其中设置了一些参数。这些参数引用了base目录、要注入到 Kubernetes 资源中的命名空间以及最后要部署的镜像,在生产环境中是1.1.0,但在演示环境中是1.2.0-beta

对于演示环境,kustomization.yaml 的内容如下所示:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
namespace: staging ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
images:
- name: lordofthejars/pacman-kikd
  newTag: 1.2.0-beta ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

base目录的引用

2

将命名空间设置为演示

3

设置演示环境的容器标签

生产的 kustomization 文件与 staging 的类似,但更改了命名空间和标签:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
namespace: prod ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
images:
- name: lordofthejars/pacman-kikd
  newTag: 1.1.0 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

设置生产的命名空间

2

设置生产环境的容器标签

运行 kustomize 根据运行的目录不同生成不同的输出;例如,在演示目录中运行 kustomize build 会生成:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: staging ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
spec:
  replicas: 1
...
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
      - image: lordofthejars/pacman-kikd:1.2.0-beta ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
...

1

命名空间值被注入

2

演示环境注入容器标签

但如果您在生产目录中运行它,则输出会根据生产配置进行调整:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
  namespace: prod ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
spec:
  replicas: 1
...
    spec:
      containers:
      - image: lordofthejars/pacman-kikd:1.1.0 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
...

1

注入生产命名空间

2

production 环境的容器标签

讨论

Kustomize 可以在所有资源和引用的名称前添加/附加一个值。当需要根据环境要求不同名称的资源时,或者为了设置名称中部署的版本时,这非常有用:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
namespace: staging
namePrefix: staging- ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
nameSuffix: -v1-2-0 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
images:
- name: lordofthejars/pacman-kikd
  newTag: 1.2.0-beta

1

要前置的前缀

2

要附加的后缀

输出结果如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: staging-pacman-kikd-v1-2-0 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  namespace: staging
spec:
...

1

部署文件的新名称

4.5 在 Kustomize 中生成 ConfigMaps

问题

您希望使用 Kustomize 生成 Kubernetes ConfigMap

解决方案

使用 ConfigMapGenerator 功能字段即可即时生成 Kubernetes ConfigMap 资源。

Kustomize 提供两种方式将 ConfigMap 添加为 Kustomize 资源:一种是将 ConfigMap 声明为任何其他资源,另一种是从 ConfigMapGenerator 声明 ConfigMap

虽然将 ConfigMap 作为资源使用与将 Kubernetes 资源填充为任何其他资源无异,但 ConfigMapGenerator 自动向 ConfigMap 元数据名称附加哈希,并且还修改了部署文件与新哈希。这种微小的变化对应用程序的生命周期有深远影响,我们将在例子中很快看到。

让我们考虑一个在 Kubernetes 中运行并使用 ConfigMap 配置的应用程序,例如数据库超时连接参数。我们决定在某个时候增加此数字,因此 ConfigMap 文件更改为这个新值,并且重新部署应用程序。由于 ConfigMap 是唯一更改的文件,因此不会对应用程序进行滚动更新。需要手动触发应用程序的滚动更新以将更改传播到应用程序。Figure 4-3 显示更新 ConfigMap 对象时发生的变化。

更改 ConfigMap 对象

第 4-3 图。更改一个 ConfigMap

但是,如果 ConfigMapGenerator 管理 ConfigMap,则配置文件的任何更改也会更改部署的 Kubernetes 资源。由于部署文件也已更改,因此在应用资源时将触发自动滚动更新,如 Figure 4-4 所示。

此外,使用 ConfigMapGenerator 时,可以将多个配置数据文件合并为单个 ConfigMap,在每个环境具有不同配置文件的完美用例。

使用 ConfigMapGenerator 更改 ConfigMap

第 4-4 图。使用 ConfigMapGenerator 更改一个 ConfigMap

让我们从一个简单的示例开始,在 kustomization.yaml 文件中添加 ConfigMapGenerator 部分。

部署文件类似于本章前几节中使用的文件,但包括 volumes 部分:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman-kikd
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
      - image: lordofthejars/pacman-kikd:1.0.0
        imagePullPolicy: Always
        name: pacman-kikd
        volumeMounts:
        - name: config
          mountPath: /config
      volumes:
      - name: config
        configMap:
          name: pacman-configmap ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

ConfigMap 名称在 kustomization.yaml 文件中使用

配置属性嵌入在 kustomization.yaml 文件中。注意,当构建 kustomization 文件时,ConfigMap 对象会即时创建:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
configMapGenerator:
- name: pacman-configmap ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  literals:	![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  - db-timeout=2000 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  - db-username=Ada

1

在部署文件中设置的 ConfigMap 名称

2

将配置值嵌入文件中

3

为属性设置键值对

最后,使用 kubectl 中的 dry-runkustomize 来验证部署文件的输出是否包含新的标签版本。在终端窗口中运行以下命令:

kustomize build

上述命令的输出是一个新的 ConfigMap,其中包含在 kustomization.yaml 中设置的配置值。此外,ConfigMap 的名称通过在生成的 ConfigMap 和部署文件中追加哈希来更新:

apiVersion: v1
data: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  db-timeout: "2000"
  db-username: Ada
kind: ConfigMap
metadata:
  name: pacman-configmap-96kb69b6t4 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman-kikd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman-kikd
    spec:
      containers:
      - image: lordofthejars/pacman-kikd:1.0.0
        imagePullPolicy: Always
        name: pacman-kikd
        volumeMounts:
        - mountPath: /config
          name: config
      volumes:
      - configMap:
          name: pacman-configmap-96kb69b6t4 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
        name: config

1

包含属性的 ConfigMap

2

带有哈希的名称

3

名称字段已更新为带有哈希的字段,触发滚动更新

由于哈希是根据配置属性的任何更改计算的,对它们的更改会触发输出的更改,从而触发应用程序的滚动更新。打开 kustomization.yaml 文件并将 db-timeout 字面值从 2000 更新为 1000,然后再次运行 kustomize build。注意使用新的哈希值更改 ConfigMap 名称:

apiVersion: v1
data:
  db-timeout: "1000"
  db-username: Ada
kind: ConfigMap
metadata:
  name: pacman-configmap-6952t58tb4 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
---
apiVersion: apps/v1
kind: Deployment
...
    volumes:
      - configMap:
          name: pacman-configmap-6952t58tb4
        name: config

1

新的哈希值

讨论

ConfigMapGenerator 还支持从不同源合并配置属性。

dev_literals 目录中创建一个新的 kustomization.yaml 文件,将其设置为上一个目录,并覆盖 db-username 值:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../literals ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
configMapGenerator:
- name: pacman-configmap
  behavior: merge ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  literals:
  - db-username=Alexandra ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

基础目录

2

合并属性(也可以是 createreplace

3

覆盖的值

运行 kustomize build 命令会生成一个包含两者配置属性合并的 ConfigMap

apiVersion: v1
data:
  db-timeout: "1000" ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  db-username: Alexandra ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
kind: ConfigMap
metadata:
  name: pacman-configmap-ttfdfdk5t8
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
...

1

继承自基础

2

覆盖值

除了将配置属性设置为字面值外,Kustomize 还支持将它们定义为 .properties 文件。

创建一个包含两个属性的 connection.properties 文件:

db-url=prod:4321/db
db-username=ada

kustomization.yaml 文件使用 files 字段而不是 literals

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
configMapGenerator:
- name: pacman-configmap
  files: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  - ./connection.properties ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

设置要读取的文件列表

2

属性文件的路径

运行 kustomize build 命令会生成一个包含文件名作为键和文件内容作为值的 ConfigMap

apiVersion: v1
data:
  connection.properties: |-
    db-url=prod:4321/db
    db-username=ada
kind: ConfigMap
metadata:
  name: pacman-configmap-g9dm2gtt77
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: pacman-kikd
  name: pacman-kikd
...

参见

Kustomize 提供了一种类似的方法来处理 Kubernetes Secrets。但正如我们将在 第八章 中看到的那样,处理 Kubernetes Secrets 的最佳方法是使用 Sealed Secrets。

4.6 最后的思考

Kustomize 是一个简单的工具,使用无模板技术,允许您定义普通的 YAML 文件,并使用合并策略或 JSON Patch 表达式覆盖值。项目的结构是自由的,您可以定义您感觉最舒适的目录布局;唯一的要求是存在一个kustomization.yaml文件。

但是还有另一个知名的工具来管理 Kubernetes 资源文件,据我们认为,它稍微复杂一些但更强大,特别是当要部署的应用/服务有多个依赖项,比如数据库、邮件服务器、缓存等。这个工具就是 Helm,我们将在第五章中详细介绍它。

第五章:Helm

在第四章中,您已经了解了 Kustomize,这是一个简单而强大的工具,用于管理 Kubernetes 资源。但是另一个流行的工具也旨在简化 Kubernetes 资源的管理:Helm。

Helm 的工作方式类似于 Kustomize,但它是一个模板解决方案,更像是一个包管理器,生成的构件可以进行版本控制、共享或部署。

在本章中,我们将介绍 Helm,这是一个 Kubernetes 的包管理工具,它使用 YAML 文件中的 Go 模板语言来帮助安装和管理 Kubernetes 应用程序。

第一步是创建一个 Helm 项目并将其部署到 Kubernetes 集群(参见配方 5.1 和 5.2)。在第一次部署之后,可以通过一个新的容器镜像、一个新的配置值或任何其他字段(例如副本数)来更新应用程序(参见配方 5.3)。

Kustomize 和 Helm 之间的一个区别是 Chart 的概念。Chart 是一个打包的构件,可以共享,并包含多个元素,如对其他 Chart 的依赖关系(参见配方 5.4、5.5 和 5.6)。

应用程序配置值通常映射为 Kubernetes 的 ConfigMap 属性。对 ConfigMap 的任何更改(及其在集群上的后续更新)不会触发应用程序的滚动更新,这意味着应用程序会继续以前一个版本运行,直到您手动重新启动它。

Helm 提供了一些功能,可以在应用程序的 ConfigMap 更改时自动执行滚动更新(参见配方 5.7)。

5.1 创建一个 Helm 项目

问题

您想要创建一个简单的 Helm 项目。

解决方案

使用Helm CLI 工具创建一个新项目。

与 Kustomize 相反,Kustomize 可以在 kubectl 命令中使用,也可以作为一个独立的 CLI 工具使用,但 Helm 需要在本地机器上下载并安装。

Helm 是一个 Kubernetes 的打包工具,它将相关的清单文件打包成一个单一的逻辑部署单元:一个 Chart。因此,对于许多工程师来说,Helm 简化了使用真实应用程序的 Kubernetes 的过程。

Helm Charts 对于解决应用程序的安装复杂性和简单的升级非常有用。

本书中,我们使用的是 Helm 3.7.2 版本,您可以从GitHub下载并安装到您的 PATH 目录中。

打开终端并运行以下命令,创建一个 Helm Chart 目录布局:

mkdir pacman
mkdir pacman/templates

cd pacman

然后创建三个文件:一个定义 Chart 的文件,另一个代表使用 Sprig 库的 Go 模板语言的部署模板,最后一个文件包含 Chart 的默认值。

Chart.yaml 文件声明了 Chart 的信息,如版本或名称。在根目录下创建该文件:

apiVersion: v2
name: pacman
description: A Helm chart for Pacman

type: application

version: 0.1.0 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

appVersion: "1.0.0" ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

图表的版本。当 Chart 定义中的内容发生更改时更新。

2

应用程序的版本。

让我们创建一个deployment.yaml和一个service.yaml模板文件来部署应用程序。

deployment.yaml文件模板化了部署的名称、应用程序版本、副本计数、容器镜像和标签、拉取策略、安全上下文以及端口:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name}} ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  labels:
    app.kubernetes.io/name: {{ .Chart.Name}}
    {{- if .Chart.AppVersion }} ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }} ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
    spec:
      containers:
          - image: "{{ .Values.image.repository }}:
          {{ .Values.image.tag | default .Chart.AppVersion}}" ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png) ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)
            imagePullPolicy: {{ .Values.image.pullPolicy }} ![7](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/7.png)
            securityContext:
              {{- toYaml .Values.securityContext | nindent 14 }}
            name: {{ .Chart.Name}}
            ports:
              - containerPort: {{ .Values.image.containerPort }}
                name: http
                protocol: TCP

1

Chart.yaml文件设置名称

2

根据Chart.yaml文件中是否存在appVersion来有条件地设置版本

3

设置appVersion值,但引用该属性

4

replicaCount属性的占位符

5

容器镜像的占位符

6

如果存在,则为镜像标签的占位符,否则默认为Chart.yaml属性

7

securityContext值设置为 YAML 对象,而不是字符串,并将其缩进 14 个空格

service.yaml文件模板化了服务名称和容器端口:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
  name: {{ .Chart.Name }}
spec:
  ports:
    - name: http
      port: {{ .Values.image.containerPort }}
      targetPort: {{ .Values.image.containerPort }}
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}

values.yaml文件包含 Chart 的默认值。这些值可以在运行时被覆盖,但它们提供了良好的初始值。

在根目录中创建文件,并使用一些默认值:

image: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  repository: quay.io/gitops-cookbook/pacman-kikd ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1
securityContext: {} ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

定义image部分

2

设置repository属性

3

空的securityContext

内置属性采用大写;因此,在Chart.yaml文件中定义的属性以大写字母开头。

由于toYaml函数用于securityContext值,因此values.yaml文件中securityContext属性的预期值应为 YAML 对象。例如:

securityContext:
  capabilities:
    drop:
    - ALL
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 1000

所有元素之间的关系显示在图 5-1 中。

Helm 元素之间的关系

图 5-1. Helm 元素之间的关系

到此为止,Helm 目录布局已创建,并应类似于以下内容:

pacman
    ├── Chart.yaml ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    ├── templates ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    │   ├── deployment.yaml ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    │   ├── service.yaml
    └── values.yaml ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)

1

Chart.yaml文件是 Chart 描述符,包含与 Chart 相关的元数据。

2

templates目录包含用于安装 Chart 的所有模板文件。

3

这些文件是用于部署应用程序的 Helm 模板文件。

4

values.yaml文件包含 Chart 的默认值。

要将 Helm Chart 本地渲染为 YAML,请在终端窗口中运行以下命令:

helm template .

输出为:

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
spec:
  ports:
    - name: http
      port: 8080 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      targetPort: 8080
  selector:
    app.kubernetes.io/name: pacman
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman
  labels:
    app.kubernetes.io/name: pacman
    app.kubernetes.io/version: "1.0.0" ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman
    spec:
      containers:
          - image: "quay.io/gitops-cookbook/pacman-kikd:1.0.0" ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
            imagePullPolicy: Always
            securityContext: ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
              {}
            name: pacman
            ports:
              - containerPort: 8080
                name: http
                protocol: TCP

1

名称从Chart.yaml中注入

2

端口在values.yaml中设置

3

版本取自 Chart 版本

4

将两个属性的内容连接起来

5

空安全上下文

您可以通过在 Helm 中使用--set参数来覆盖任何默认值。让我们将replicaCount值从values.yaml中的 1 覆盖为 3:

helm template  --set replicaCount=3 .

并更新replicas值:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman
  labels:
    app.kubernetes.io/name: pacman
    app.kubernetes.io/version: "1.0.0"
spec:
  replicas: 3
...

讨论

Helm 是 Kubernetes 的包管理器,帮助您进行版本控制、共享和升级 Kubernetes 应用程序等任务。

让我们看看如何将 Helm Chart 安装到 Kubernetes 集群并升级应用程序。

当 Minikube 处于运行状态时,在终端窗口中执行以下命令,并运行install命令将应用程序部署到集群中:

helm install pacman .

Chart 已安装在运行的 Kubernetes 实例中:

LAST DEPLOYED: Sat Jan 22 15:13:50 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

获取当前部署的 Pods、Deployments 和 Services 的列表,以验证 Helm Chart 是否已部署在 Kubernetes 集群中:

kubectl get pods

NAME                    READY   STATUS    RESTARTS   AGE
pacman-7947b988-kzjbc   1/1     Running   0          60s
kubectl get deployment

NAME     READY   UP-TO-DATE   AVAILABLE   AGE
pacman   1/1     1            1           4m50s
kubectl get services

NAME     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
pacman   ClusterIP   172.30.129.123   <none>        8080/TCP   9m55s

此外,还可以使用history命令获取有关部署的 Helm Chart 的历史信息:

helm history pacman

REVISION	UPDATED                 	STATUS  	CHART       ↳
   APP VERSION   DESCRIPTION
1       	Sat Jan 22 15:23:22 2022	deployed	pacman-0.1.0↳
   1.0.0         Install complete

要从集群中卸载 Chart,请运行uninstall命令:

helm uninstall pacman

release "pacman" uninstalled

Helm 是一个包管理器,允许您将 Chart(包)作为依赖项共享给其他 Chart。例如,您可以有一个定义应用程序部署的 Chart,另一个 Chart 作为设置数据库部署的依赖项。这样,安装过程会自动安装应用程序和数据库 Chart。

我们将在后面的章节中学习打包过程和添加依赖项。

提示

您可以使用helm create <name>命令让 Helm 工具为项目进行自动化构建。

另请参阅

5.2 在模板之间重用语句

问题

您希望在多个文件中重用模板语句。

解决方案

使用_helpers.tpl来定义可重用的语句。

在上一篇文章中,我们使用 Helm 将一个简单的应用程序部署到 Kubernetes。这个简单的应用程序由一个 Kubernetes Deployment 文件和一个 Kubernetes Service 文件组成,其中selector字段的值相同。

作为提醒:

...
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
...
service.yaml
----
...
selector:
    app.kubernetes.io/name: {{ .Chart.Name }}
----

如果需要更新此字段,例如添加新的标签作为选择器,您需要更新三个地方,如前面的片段所示。

Helm 允许您在templates目录中创建一个_helpers.tpl文件,定义可以在模板中调用的语句,以避免这个问题。

让我们重构之前的示例,使用_helpers.tpl文件定义selectorLabels

templates目录中创建_helpers.tpl文件,并添加以下内容:

{{- define "pacman.selectorLabels" -}} ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
app.kubernetes.io/name: {{ .Chart.Name}} ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
{{- end }}

1

定义语句名称

2

定义语句的逻辑

然后用podman.selectorLabels辅助语句中的include关键字替换前面片段中显示的模板占位符:

spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "pacman.selectorLabels" . | nindent 6 }} ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  template:
    metadata:
      labels:
        {{- include "pacman.selectorLabels" . | nindent 8 }} ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    spec:
      containers:

1

使用缩进调用了pacman.selectorLabels

2

使用缩进调用了pacman.selectorLabels

要在本地将 Helm Chart 渲染为 YAML,请在终端窗口中运行以下命令:

helm template .

输出为:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: pacman
  name: pacman
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app.kubernetes.io/name: pacman ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman
  labels:
    app.kubernetes.io/name: pacman
    app.kubernetes.io/version: "1.0.0"
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    spec:
      containers:
          - image: "quay.io/gitops-cookbook/pacman-kikd:1.0.0"
            imagePullPolicy: Always
            securityContext:
              {}
            name: pacman
            ports:
              - containerPort: 8080
                name: http
                protocol: TCP

1

使用_helpers.tpl中设置的值更新选择器

2

使用缩进调用了pacman.selectorLabels

3

使用_helpers.tpl中设置的值更新选择器

讨论

如果要更新选择器标签,您唯一需要做的更改就是更新_helpers.tpl文件:

{{- define "pacman.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name}}
app.kubernetes.io/version: {{ .Chart.AppVersion}} ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
{{- end }}

1

添加了新属性

要在本地将 Helm Chart 渲染为 YAML,请在终端窗口中运行以下命令:

helm template .

输出为:

---
# Source: pacman/templates/service.yaml apiVersion: v1
kind: Service
metadata:
...
  selector:
    app.kubernetes.io/name: pacman
    app.kubernetes.io/version: 1.0.0 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman
  labels:
    app.kubernetes.io/name: pacman
    app.kubernetes.io/version: "1.0.0"
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman
      app.kubernetes.io/version: 1.0.0 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman
        app.kubernetes.io/version: 1.0.0 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    spec:
...

1

添加了标签

2

添加了标签

3

添加了标签

提示

尽管通常使用__helpers.tpl作为文件名来定义函数,您也可以使用以__开头的任何文件名,Helm 也会读取其中的函数。

5.3 在 Helm 中更新容器镜像

问题

您想通过 Helm 从部署文件中更新容器镜像并升级运行实例。

解决方案

使用upgrade命令。

在 Minikube 正常运行时,部署pacman应用程序的 1.0.0 版本:

helm install pacman .

部署了第一个修订版本后,让我们将容器镜像更新为新版本并部署。

您可以通过运行以下命令检查修订号:

helm history pacman

REVISION   UPDATED                   STATUS    CHART          APP VERSION↳
  DESCRIPTION
1          Sun Jan 23 16:00:09 2022  deployed  pacman-0.1.0   1.0.0↳
  Install complete

要更新版本,请打开values.yaml并将image.tag字段更新为新的容器镜像标签:

image:
  repository: quay.io/gitops-cookbook/pacman-kikd
  tag: "1.1.0" ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1
securityContext: {}

1

更新到容器标记为 1.1.0

然后更新Chart.yaml文件的appVersion字段:

apiVersion: v2
name: pacman
description: A Helm chart for Pacman

type: application
version: 0.1.0
appVersion: "1.1.0" ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

版本将相应更新

提示

您可以将appVersion用作标签,而不是使用两个独立字段。使用两个字段或一个字段可能取决于您的用例、版本策略和软件生命周期。

进行这些更改后,通过运行以下命令升级部署:

helm upgrade pacman .

输出反映出已部署新修订版本:

Release "pacman" has been upgraded. Happy Helming!
NAME: pacman
LAST DEPLOYED: Mon Jan 24 11:39:28 2022
NAMESPACE: asotobue-dev
STATUS: deployed
REVISION: 2 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
TEST SUITE: None

1

新修订版本

history命令显示所有版本之间的所有更改:

helm history pacman

REVISION UPDATED                  STATUS    	CHART       	APP VERSION↳
DESCRIPTION
1        Mon Jan 24 10:22:06 2022 superseded	pacman-0.1.0	1.0.0↳
Install complete
2        Mon Jan 24 11:39:28 2022 deployed  	pacman-0.1.0	1.1.0↳
Upgrade complete
注意

appVersion是应用程序版本,因此每次更改应用程序版本时,您也应更新该字段。另一方面,version是 Chart 版本,在 Chart 定义(即模板)更改时应更新,因此这两个字段是独立的。

讨论

您不仅可以使用 Helm 安装或升级版本,还可以回滚到以前的修订版本。

在终端窗口中运行以下命令:

helm rollback pacman 1

Rollback was a success! Happy Helming!

运行history命令也反映了这一变化:

helm history pacman

REVISION  UPDATED                   STATUS    	CHART       	APP VERSION↳
DESCRIPTION
1         Mon Jan 24 10:22:06 2022  superseded	pacman-0.1.0	1.0.0↳
Install complete
2         Mon Jan 24 11:39:28 2022  superseded	pacman-0.1.0	1.1.0↳
Upgrade complete
3         Mon Jan 24 12:31:58 2022  deployed  	pacman-0.1.0	1.0.0↳
Rollback to

最后,Helm 提供了一种覆盖值的方式,不仅可以使用--set参数(如 Recipe 5.1 所示),还可以提供一个 YAML 文件。

在根目录创建一个名为newvalues.yaml的新 YAML 文件,内容如下:

image:
  tag: "1.2.0"

然后运行template命令,将新文件设置为values.yaml的覆盖:

helm template pacman -f newvalues.yaml .

生成的 YAML 文档使用values.yaml中设置的值,但覆盖了newvalues.yaml中设置的images.tag

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pacman
...
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pacman
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pacman
    spec:
      containers:
          - image: "quay.io/gitops-cookbook/pacman-kikd:1.2.0"
            imagePullPolicy: Always
...

5.4 打包和分发 Helm 图表

问题

您希望将 Helm 图表打包并分发,以便其他人可以重用它。

解决方案

使用package命令。

Helm 是一个用于 Kubernetes 的包管理器。正如我们在本章中看到的,Helm 中的基本单元是一个图表,其中包含部署应用程序所需的 Kubernetes 文件,模板的默认值等。

但我们还没有看到如何打包 Helm 图表并将其分发以供其他图表作为依赖项使用或由其他用户部署。

让我们将pacman图表打包成.tgz文件。在pacman目录中,运行以下命令:

helm package .

然后您将收到一条消息,告知存档的位置:

Successfully packaged chart and saved it to:↳
gitops-cookbook/code/05_helm/04_package/pacman/pacman-0.1.0.tgz

然后需要将图表发布到图表仓库中。图表仓库是一个 HTTP 服务器,包含有关图表和.tgz图表的元数据信息的index.yaml文件。

要发布它们,请更新index.yaml文件以及上传该工件的新元数据信息。

仓库的目录布局可能如下所示:

repo
├── index.yaml
├── pacman-0.1.0.tgz

仓库中每个图表的信息在index.yaml文件中看起来像这样:

apiVersion: v1
entries:
  pacman:
  - apiVersion: v2
    appVersion: 1.0.0
    created: "2022-01-24T16:42:54.080959+01:00"
    description: A Helm chart for Pacman
    digest: aa3cce809ffcca86172fc793d7804d1c61f157b9b247680a67d5b16b18a0798d
    name: pacman
    type: application
    urls:
    - pacman-0.1.0.tgz
    version: 0.1.0
generated: "2022-01-24T16:42:54.080485+01:00"
提示

您可以在存储打包图表的根目录中运行helm repo index以自动生成索引文件。

讨论

除了打包 Helm 图表外,Helm 还可以为打包的图表生成签名文件,以便以后验证其正确性。

通过这种方式,您可以确保它未被修改,并且是正确的图表。

要签名/验证包,请在计算机上准备一对 GPG 密钥;我们假设您已经创建了一对。

现在您需要调用package命令,但要使用必需的参数设置-sign参数以生成签名文件:

helm package --sign --key 'me@example.com' \
  --keyring /home/me/.gnupg/secring.gpg  .

现在,会创建两个文件—打包的 Helm 图表(.tgz)和签名文件(.tgz.prov):

.
├── Chart.yaml
├── pacman-0.1.0.tgz ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
├── pacman-0.1.0.tgz.prov ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
├── templates
│  ├── deployment.yaml
│  └── service.yaml
└── values.yaml

1

图表包

2

签名文件

注意

记得上传这两个文件到图表仓库中。

若要验证图表是否有效且未被篡改,请使用verify命令:

helm verify pacman-0.1.0.tgz

Signed by: alexs (book) <asotobu@example.com>
Using Key With Fingerprint: 57C4511D738BC0B288FAF9D69B40EB787040F3CF
Chart Hash Verified:↳
sha256:d8b2e0c5e12a8425df2ea3a903807b93aabe4a6ff8277511a7865c847de3c0bf ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

它是有效的

另请参阅

5.5 从仓库部署图表

问题

您希望部署存储在图表仓库中的 Helm 图表。

解决方案

使用repo add命令添加远程仓库,并使用install命令部署它。

类似Bitnami这样的公共 Helm 图表仓库可用于此目的。

要从仓库(公共或私有)安装图表,需要使用其 URL 进行注册:

helm repo add bitnami https://charts.bitnami.com/bitnami ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

Helm Chart 存放 index.yaml 的仓库 URL

列出已注册的仓库:

helm repo list

NAME     	URL
stable   	https://charts.helm.sh/stable
bitnami  	https://charts.bitnami.com/bitnami ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

Bitnami 仓库已注册

提示

运行 helm repo update 来获取每个仓库的最新图表列表。

注册仓库后,您可能想找到可用的图表。

如果要在集群中部署 PostgreSQL 实例,请使用 search 命令搜索所有仓库以匹配名称的图表:

helm search repo postgresql

输出是与名称匹配的图表列表,Chart 和 PostgreSQL 的版本,以及描述。注意,图表的名称由仓库名称和图表名称组成,即 bitnami/postgresql

NAME                               	CHART VERSION	APP VERSION↳
	DESCRIPTION
bitnami/postgresql                 	10.16.2      	11.14.0↳
    	Chart for PostgreSQL, an object-relational data...
bitnami/postgresql-ha              	8.2.6        	11.14.0↳
    	Chart for PostgreSQL with HA architecture (usin...
stable/postgresql                  	8.6.4        	11.7.0↳
     	DEPRECATED Chart for PostgreSQL, an object-rela...
stable/pgadmin                     	1.2.2        	4.18.0↳
     	pgAdmin is a web based administration tool for ...
stable/stolon                      	1.6.5        	0.16.0↳
     	DEPRECATED - Stolon - PostgreSQL cloud native H...
stable/gcloud-sqlproxy             	0.6.1        	1.11↳
       	DEPRECATED Google Cloud SQL Proxy
stable/prometheus-postgres-exporter	1.3.1        	0.8.0↳
      	DEPRECATED A Helm chart for prometheus postgres...

要部署 PostgreSQL 图表,请运行 install 命令,但将 Helm 图表的位置从本地目录更改为图表的完整名称(<repo>/<chart>):

helm install my-db \ ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
--set postgresql.postgresqlUsername=my-default,postgresql.↳
postgresqlPassword=postgres,postgresql.postgresqlDatabase=mydb,↳
postgresql.persistence.enabled=false \ ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
bitnami/postgresql ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

设置部署的名称

2

覆盖命令行中设置的默认值

3

设置存储在 Bitnami 仓库中的 PostgreSQL 图表

并在控制台显示详细输出:

NAME: my-db
LAST DEPLOYED: Mon Jan 24 22:33:56 2022
NAMESPACE: asotobue-dev
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: postgresql
CHART VERSION: 10.16.2
APP VERSION: 11.14.0

** Please be patient while the chart is being deployed **

PostgreSQL can be accessed via port 5432 on the following DNS names↳
from within your cluster:

    my-db-postgresql.asotobue-dev.svc.cluster.local - Read/Write connection

To get the((("passwords", "Helm Charts")))((("Helm", "Charts", "passwords")))((("Charts", "passwords"))) password for "postgres" run:

    export POSTGRES_ADMIN_PASSWORD=$(kubectl get secret↳
     --namespace asotobue-dev my-db-postgresql -o↳
     jsonpath="{.data.postgresql-postgres-password}" | base64 --decode)

To get the password for "my-default" run:

    export POSTGRES_PASSWORD=$(kubectl get secret↳
     --namespace asotobue-dev my-db-postgresql -o↳
     jsonpath="{.data.postgresql-password}" | base64 --decode)

To connect to your database run the following command:

    kubectl run my-db-postgresql-client --rm --tty -i --restart='Never'↳
     --namespace asotobue-dev↳
     --image docker.io/bitnami/postgresql:11.14.0-debian-10-r28↳
     --env="PGPASSWORD=$POSTGRES_PASSWORD"↳
     --command -- psql --host my-db-postgresql -U my-default -d mydb↳
     -p 5432

To connect to your ((("Helm", "Charts", "connecting to databases")))((("Charts", "databases", "connecting to")))((("databases", "connecting to", "Helm Charts")))database from outside the cluster execute the following commands:

    kubectl port-forward --namespace asotobue-dev svc/my-db-postgresql 5432:5432 &
    PGPASSWORD="$POSTGRES_PASSWORD" psql --host 127.0.0.1 -U my-default -d mydb -p 5432

通过列出 Pod、Service、StatefulSet 或 Secret 来检查安装情况:

kubectl get pods

NAME                 READY   STATUS    RESTARTS   AGE
my-db-postgresql-0   1/1     Running   0          23s
kubectl get services

NAME                        TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
my-db-postgresql            ClusterIP   172.30.35.1   <none>        5432/TCP   3m33s
my-db-postgresql-headless   ClusterIP   None          <none>        5432/TCP   3m33s
kubectl get statefulset

NAME               READY   AGE
my-db-postgresql   1/1     4m24s
kubectl get secrets

NAME                          TYPE                                  DATA   AGE
my-db-postgresql              Opaque                                2      5m23s
sh.helm.release.v1.my-db.v1   helm.sh/release.v1                    1      5m24s

讨论

当第三方创建图表时,无法直接访问默认值或要覆盖的参数列表。Helm 提供 show 命令来检查这些值:

helm show values bitnami/postgresql

并显示所有可能的值:

## @section Global parameters
## Global Docker image parameters
## Please, note that this will override the image parameters, including dependencies
## configured to use the global value
## Current available global Docker image parameters: imageRegistry, imagePullSecrets
## and storageClass
##

## @param global.imageRegistry Global Docker image registry
## @param global.imagePullSecrets Global Docker registry secret names as an array
## @param global.storageClass Global StorageClass for Persistent Volume(s)
##
global:
  imageRegistry: ""
  ## E.g.
  ## imagePullSecrets:
  ##   - myRegistryKeySecretName
  ##
  imagePullSecrets: []
...

5.6 使用依赖项部署图表

问题

您希望部署作为另一个图表依赖项的 Helm Chart。

解决方案

Chart.yaml 文件中使用 dependencies 部分注册其他图表。到目前为止,我们已经看到如何将简单服务部署到集群中,但通常一个服务可能有其他依赖项,如数据库、邮件服务器、分布式缓存等。

在前一节中,我们看到如何在 Kubernetes 集群中部署 PostgreSQL 服务器。在本节中,我们将看到如何部署由返回存储在 PostgreSQL 数据库中的歌曲列表的 Java 服务组成的服务。该应用程序概述在 Figure 5-2 中。

音乐应用程序概述

图 5-2。音乐应用程序概述

让我们开始创建 Recipe 5.1 中显示的图表布局:

mkdir music
mkdir music/templates

cd music

然后创建两个模板文件以部署音乐服务。

templates/deployment.yaml 文件包含 Kubernetes Deployment 定义:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name}}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name}}
    {{- if .Chart.AppVersion }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
    spec:
      containers:
          - image: "{{ .Values.image.repository }}:↳
          {{ .Values.image.tag | default .Chart.AppVersion}}"
            imagePullPolicy: {{ .Values.image.pullPolicy }}
            name: {{ .Chart.Name}}
            ports:
              - containerPort: {{ .Values.image.containerPort }}
                name: http
                protocol: TCP
            env:
              - name: QUARKUS_DATASOURCE_JDBC_URL
                value: {{ .Values.postgresql.server | ↳
                default (printf "%s-postgresql" ( .Release.Name )) | quote }}
              - name: QUARKUS_DATASOURCE_USERNAME
                value: {{ .Values.postgresql.postgresqlUsername | ↳
                default (printf "postgres" ) | quote }}
              - name: QUARKUS_DATASOURCE_PASSWORD
                valueFrom:
                  secretKeyRef:
                    name: {{ .Values.postgresql.secretName | ↳
                    default (printf "%s-postgresql" ( .Release.Name )) | quote }}
                    key: {{ .Values.postgresql.secretKey }}

templates/service.yaml 文件包含 Kubernetes Service 定义:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
  name: {{ .Chart.Name }}
spec:
  ports:
    - name: http
      port: {{ .Values.image.containerPort }}
      targetPort: {{ .Values.image.containerPort }}
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}

在模板创建后,现在是时间编写图表元数据 Chart.yaml 文件。在这种情况下,我们也需要定义该图表的依赖项。由于音乐服务使用 PostgreSQL 数据库,我们可以将 Recipe 5.5 中使用的图表作为依赖项添加:

apiVersion: v2
name: music
description: A Helm chart for Music service

type: application
version: 0.1.0
appVersion: "1.0.0"

dependencies: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  - name: postgresql ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    version: 10.16.2 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    repository: "https://charts.bitnami.com/bitnami" ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)

1

依赖部分

2

要添加为依赖的图表名称

3

图表版本

4

仓库

最终的文件是 Values.yaml,包含默认配置值。在这种情况下,添加了一个新的部分来配置使用 PostgreSQL 实例参数的音乐部署:

image:
  repository: quay.io/gitops-cookbook/music
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1

postgresql: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  server: jdbc:postgresql://music-db-postgresql:5432/mydb
  postgresqlUsername: my-default
  secretName: music-db-postgresql
  secretKey: postgresql-password

1

PostgreSQL 部分

有了图表之后,接下来要做的是下载依赖图表并将其存储在 charts 目录中。通过运行 dependency update 命令可以自动完成这个过程:

helm dependency update

命令输出显示已下载并保存了一个图表:

Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "stable" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈
Saving 1 charts
Downloading postgresql from repo https://charts.bitnami.com/bitnami
Deleting outdated charts

目录布局如下所示:

music
├── Chart.lock
├── Chart.yaml
├── charts
│    └── postgresql-10.16.2.tgz ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
├── templates
│    ├── deployment.yaml
│    └── service.yaml
└── values.yaml

1

PostgreSQL 图表已放置在正确的目录中

最后,我们部署图表,并通过命令行设置 PostgreSQL 部署的配置值:

helm install music-db --set postgresql.postgresqlPassword=postgres postgresql.postgresqlDatabase=mydb,postgresql.persistence.enabled=false .

安装过程显示了有关部署的信息:

NAME: music-db
LAST DEPLOYED: Tue Jan 25 17:53:17 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

通过列出 pods、Services、StatefulSets 或 Secrets 来检查安装情况:

kubectl get pods

NAME                     READY   STATUS    RESTARTS      AGE
music-67dbf986b7-5xkqm   1/1     Running   1 (32s ago)   39s
music-db-postgresql-0    1/1     Running   0             39s
kubectl get statefulset

NAME                  READY   AGE
music-db-postgresql   1/1     53s
kubectl get services

NAME                         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
kubernetes                   ClusterIP   10.96.0.1     <none>        443/TCP    40d
music                        ClusterIP   10.104.110.34 <none>        8080/TCP   82s
music-db-postgresql          ClusterIP   10.110.71.13  <none>        5432/TCP   82s
music-db-postgresql-headless ClusterIP   None          <none>        5432/TCP   82s

通过使用端口转发到 Kubernetes 服务来验证对音乐服务的访问权限。

打开一个新的终端窗口并运行以下命令:

kubectl port-forward service/music 8080:8080

Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

终端被阻塞,直到停止 kubectl port-forward 进程。通过端口转发,我们可以使用 localhost 地址和端口 8080 访问音乐服务。

在另一个终端中,curl 该服务:

curl localhost:8080/song

发送请求到部署在 Kubernetes 中的音乐服务,并返回一系列歌曲:

[
  {
    "id": 1,
    "artist": "DT",
    "name": "Quiero Munchies"
  },
  {
    "id": 2,
    "artist": "Lin-Manuel Miranda",
    "name": "We Don't Talk About Bruno"
  },
  {
    "id": 3,
    "artist": "Imagination",
    "name": "Just An Illusion"
  },
  {
    "id": 4,
    "artist": "Txarango",
    "name": "Tanca Els Ulls"
  },
  {
    "id": 5,
    "artist": "Halsey",
    "name": "Could Have Been Me"
  }
]

5.7 自动触发滚动更新

问题

ConfigMap 对象发生变化时,希望触发部署的滚动更新。

解决方案

使用 sha256sum 模板函数生成部署文件的变更。

在 Recipe 4.5 中,我们看到 Kustomize 有一个 ConfigMapGenerator,在使用时会自动向 ConfigMap 元数据名称附加一个哈希,并在使用时修改部署文件。对 ConfigMap 的任何更改都会触发部署的滚动更新。

Helm 不像 Kustomize 那样直接提供更新部署文件的方法,但是有一个模板函数可以计算任何文件的 SHA-256 哈希值,并将结果嵌入模板中。

假设我们有一个 Node.js 应用程序,返回一个问候消息。一个环境变量配置了这个问候消息,在 Kubernetes 部署中,这个变量是从 Kubernetes 的 ConfigMap 注入的。

Figure 5-3 显示了应用程序的概述。

Greetings application overview

图 5-3. 问候应用程序概述

让我们为问候应用程序创建 Helm 图表;请注意,我们没有涵盖创建图表的整个过程,只涉及其中的关键部分。您可以参考 Recipe 5.1 开始。

创建一个部署模板,将 ConfigMap 注入为环境变量。以下清单显示了文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name}}
  labels:
    app.kubernetes.io/name: {{ .Chart.Name}}
    {{- if .Chart.AppVersion }}
    app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
    {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
    spec:
      containers:
          - image: "{{ .Values.image.repository }}:↳
          {{ .Values.image.tag | default .Chart.AppVersion}}"
            imagePullPolicy: {{ .Values.image.pullPolicy }}
            name: {{ .Chart.Name}}
            ports:
              - containerPort: {{ .Values.image.containerPort }}
                name: http
                protocol: TCP
            env:
              - name: GREETING
                valueFrom:
                  configMapKeyRef:
                    name: {{ .Values.configmap.name}} ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
                    key: greeting ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

ConfigMap 名称

2

ConfigMap 的属性键

初始的 ConfigMap 文件显示在以下清单中:

apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
data:
  greeting: Aloha ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

设置 ConfigMap 名称

2

键/值

创建一个 Kubernetes 服务模板以访问服务:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: {{ .Chart.Name }}
  name: {{ .Chart.Name }}
spec:
  ports:
    - name: http
      port: {{ .Values.image.containerPort }}
      targetPort: {{ .Values.image.containerPort }}
  selector:
    app.kubernetes.io/name: {{ .Chart.Name }}

使用模板 configmap 参数更新 values.yaml 文件:

image:
  repository: quay.io/gitops-cookbook/greetings
  tag: "1.0.0"
  pullPolicy: Always
  containerPort: 8080

replicaCount: 1

configmap:
  name: greeting-config ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

提到 ConfigMap 名称

最后,使用 install 命令安装 Chart:

helm install greetings .

部署 Chart 后,在一个终端中使用 kubectl port-forward 命令来访问服务:

kubectl port-forward service/greetings 8080:8080

并在另一个终端窗口中 curl 服务:

curl localhost:8080
Aloha Ada ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

使用的配置问候语

现在,让我们将 ConfigMap 文件更新为新的问候消息:

apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Hola ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

新的问候消息

更新 Chart.yaml 文件中的 appVersion 字段为 1.0.1,并通过以下命令升级 Chart:

helm upgrade greetings .

重新启动 kubectl port-forward 进程并再次 curl 服务:

curl localhost:8080
Aloha Alexandra ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

问候消息未更新

在升级期间更新 ConfigMap 对象,但由于 Deployment 对象没有更改,因此不会重启 Pod;因此环境变量未设置为新值。列出的 Pod 显示没有执行滚动更新:

kubectl get pods

NAME                         READY   STATUS    RESTARTS   AGE
greetings-64ddfcb649-m5pml   1/1     Running   0          2m21s ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

年龄值显示没有滚动更新

图 5-4 总结了变化。

使用新配置值的问候应用

图 5-4. 使用新配置值的问候应用

让我们使用 sha256sum 函数计算 configmap.yaml 文件内容的 SHA-256 值,并将其设置为 Pod 注释,从而有效触发滚动更新,因为 Pod 定义已更改:

spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ .Chart.Name}}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ .Chart.Name}}
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml")↳
         . | sha256sum }} ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

包括 configmap.yaml 文件,计算 SHA-256 值,并将其设置为注释

再次使用新值更新 ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: greeting-config
data:
  greeting: Namaste ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

新的问候消息

更新 Chart.yaml 中的 appVersion 字段为 1.0.1,并通过以下命令升级 Chart:

helm upgrade greetings .

重新启动 kubectl port-forward 进程并再次 curl 服务:

curl localhost:8080
Namaste Alexandra ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

问候消息是新的

再次列出集群中部署的 Pod,您将注意到正在进行滚动更新:

kubectl get pods

NAME                         READY   STATUS              RESTARTS   AGE
greetings-5c6b86dbbd-2p9bd   0/1     ContainerCreating   0          3s ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
greetings-64ddfcb649-m5pml   1/1     Running             0          2m21s

1

正在进行滚动更新

描述 Pod 以验证是否存在具有 SHA-256 值的注释:

kubectl describe pod greetings-5c6b86dbbd-s4n7b

输出显示所有 Pod 参数。重要的是输出顶部显示的 annotations,其中包含计算的 SHA-256 值的 checksum/config 注释:

Name:                 greetings-5c6b86dbbd-s4n7b
Namespace:            asotobue-dev
Priority:             -3
Priority Class Name:  sandbox-users-pods
Node:                 ip-10-0-186-34.ec2.internal/10.0.186.34
Start Time:           Thu, 27 Jan 2022 11:55:02 +0100
Labels:               app.kubernetes.io/name=greetings
                      pod-template-hash=5c6b86dbbd
Annotations:          checksum/config:↳
59e9100616a11d65b691a914cd429dc6011a34e02465173f5f53584b4aa7cba8 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

计算出的数值

图 5-5 总结了更新应用程序时发生变化的元素。

Greetings 应用程序的最终概述

图 5-5. Greetings 应用程序的最终概述

5.8 总结

在前一章中,我们看到了 Kustomize;在本章中,我们看到了另一个帮助部署 Kubernetes 应用程序的工具。

当你需要在 Kustomize 和 Helm 之间进行选择时,你可能会对该使用哪一个有疑问。

根据我们的经验,对于简单项目,其中可能只需要在新部署之间进行简单更改,最佳方法是使用 Kustomize。

如果项目复杂且具有外部依赖关系以及多个部署参数,则 Helm 是更好的选择。

第六章:云原生 CI/CD

在前一章中,您了解了 Helm,这是 Kubernetes 的流行模板化系统。前几章的所有示例都代表了用于创建和管理 Kubernetes 容器的常见工具集,现在是时候考虑使用这些工具在 Kubernetes 上进行自动化了。让我们把焦点转向云原生持续集成/持续部署(CI/CD)。

持续集成是一个自动化流程,它获取开发人员创建的新代码并构建、测试和运行该代码。云原生 CI 指的是云计算和云服务参与此过程的模型。这种模型的好处很多,例如在高度可伸缩和按需使用案例中跨云平台上可移植和可重现的工作负载。它还代表了 GitOps 工作流的构建块,因为它通过 Git 执行的操作实现了自动化。

Tekton 是一个流行的开源实现,在 Kubernetes 之上构建了一个云原生 CI/CD 系统。实际上,Tekton 作为 Kubernetes 集群的扩展安装和运行,并包括一组 Kubernetes 自定义资源,定义了可以为您的流水线创建和重复使用的构建块。^(1) (参见 Recipe 6.1.)

Tekton 引擎位于 Kubernetes 集群内部,并通过其 API 对象表示一种声明性方式来定义执行操作的方式。核心组件如 TasksPipelines 可用于创建从 Git 存储库生成工件和/或容器的流水线(参见 Recipes 6.2,6.3,和 6.4)。

Tekton 还支持一种用于通过 Triggers 自动启动流水线的机制。这些允许您从多种来源(如 webhook)检测和提取事件信息,并相应地启动 Tasks 或 Pipelines(参见 Recipe 6.8)。

与私有 Git 存储库一起工作是 Tekton 支持的常见用例(参见 Recipe 6.4),可以通过多种方式构建工件和创建容器,例如使用 Buildah(参见 Recipe 6.5)或我们在 第三章 中讨论的 Shipwright。还可以集成 Kustomize(参见 Recipe 6.9)和 Helm(参见 Recipe 6.10),以使 CI 部分动态化并充分利用 Kubernetes 工具的丰富生态系统。

Tekton 是 Kubernetes 本地解决方案,因此它是通用的;然而,它并非市场上唯一的云原生 CI/CD 解决方案。其他适用于 GitOps 工作负载的良好示例包括 Drone (Recipe 6.11) 和 GitHub Actions (Recipe 6.12)。

6.1 安装 Tekton

问题

您希望安装 Tekton 以便在 Kubernetes 集群上实现云原生 CI/CD。

解决方案

Tekton是一种原生于 Kubernetes 的 CI/CD 解决方案,可安装在任何 Kubernetes 集群上。安装会为你带来一组Kubernetes 自定义资源(CRDs),你可以使用它们来组合你的流水线,如图 6-1 所示:

任务

一个可重用的、松耦合的步骤集,执行特定功能(例如构建容器镜像)。任务作为 Kubernetes pod 执行,而任务中的步骤映射到容器中。

流水线

一个构建和/或部署应用程序所需的任务列表。

任务运行

运行任务实例的执行和结果。

流水线运行

运行流水线实例的执行和结果,其中包括若干个任务运行。

触发器

检测事件并连接到其他 CRD 以指定当事件发生时发生的情况。

Tekton 流水线

图 6-1. Tekton 流水线

要安装 Tekton,你只需kubectl CLI 和一个像 Minikube 这样的 Kubernetes 集群(参见第二章)。

Tekton 具有模块化结构。你可以单独安装所有组件或一次性安装所有组件(例如使用操作员):

Tekton 流水线

包含任务和流水线

Tekton 触发器

包含触发器和事件监听器

Tekton 仪表板

一个便捷的仪表板,用于可视化流水线和日志

Tekton 命令行界面

一个 CLI 工具,用于管理 Tekton 对象(启动/停止流水线和任务,检查日志)

提示

你还可以使用 Kubernetes Operator 在集群上安装和管理 Tekton 组件。详细信息请参阅OperatorHub

首先,你需要安装Tekton 流水线组件。在撰写本书时,我们使用的是版本 0.37.0:

kubectl apply \
-f https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.37.0/release.yaml

安装将创建一个名为tekton-pipelines的新的 Kubernetes 命名空间,你应该看到类似以下的输出:

namespace/tekton-pipelines created
podsecuritypolicy.policy/tekton-pipelines created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrole.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
role.rbac.authorization.k8s.io/tekton-pipelines-controller created
role.rbac.authorization.k8s.io/tekton-pipelines-webhook created
role.rbac.authorization.k8s.io/tekton-pipelines-leader-election created
role.rbac.authorization.k8s.io/tekton-pipelines-info created
serviceaccount/tekton-pipelines-controller created
serviceaccount/tekton-pipelines-webhook created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-cluster-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-tenant-access created
clusterrolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-cluster-access created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-controller-leaderelection created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-webhook-leaderelection created
rolebinding.rbac.authorization.k8s.io/tekton-pipelines-info created
customresourcedefinition.apiextensions.k8s.io/clustertasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelines.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineruns.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/resolutionrequests.resolution.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/pipelineresources.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/runs.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/tasks.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/taskruns.tekton.dev created
secret/webhook-certs created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.pipeline.tekton.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.pipeline.tekton.dev created
validatingwebhookconfiguration.admissionregistration.k8s.io/config.webhook.pipeline.tekton.dev created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-edit created
clusterrole.rbac.authorization.k8s.io/tekton-aggregate-view created
configmap/config-artifact-bucket created
configmap/config-artifact-pvc created
configmap/config-defaults created
configmap/feature-flags created
configmap/pipelines-info created
configmap/config-leader-election created
configmap/config-logging created
configmap/config-observability created
configmap/config-registry-cert created
deployment.apps/tekton-pipelines-controller created
service/tekton-pipelines-controller created
horizontalpodautoscaler.autoscaling/tekton-pipelines-webhook created
deployment.apps/tekton-pipelines-webhook created
service/tekton-pipelines-webhook created

你可以使用以下命令监视和验证安装:

kubectl get pods -w -n tekton-pipelines

你应该看到如下输出:

NAME                                           READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-5fd68749f5-tz8dv   1/1     Running   0          3m4s
tekton-pipelines-webhook-58dcdbfd9b-hswpk      1/1     Running   0          3m4s
注意

前述命令进入监视模式,因此它会一直附加。当你看到控制器和 webhook pod 处于运行状态时,请按 Ctrl+C 停止它。

然后,你可以安装Tekton 触发器。在撰写本书时,我们使用的是版本 0.20.1:

kubectl apply \
-f https://storage.googleapis.com/tekton-releases/triggers/previous/v0.20.1/release.yaml

你应该看到以下输出:

podsecuritypolicy.policy/tekton-triggers created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-admin created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-core-interceptors created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-core-interceptors-secrets created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-eventlistener-roles created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-eventlistener-clusterroles created
role.rbac.authorization.k8s.io/tekton-triggers-admin created
role.rbac.authorization.k8s.io/tekton-triggers-admin-webhook created
role.rbac.authorization.k8s.io/tekton-triggers-core-interceptors created
role.rbac.authorization.k8s.io/tekton-triggers-info created
serviceaccount/tekton-triggers-controller created
serviceaccount/tekton-triggers-webhook created
serviceaccount/tekton-triggers-core-interceptors created
clusterrolebinding.rbac.authorization.k8s.io/tekton-triggers-controller-admin created
clusterrolebinding.rbac.authorization.k8s.io/tekton-triggers-webhook-admin created
clusterrolebinding.rbac.authorization.k8s.io/tekton-triggers-core-interceptors created
clusterrolebinding.rbac.authorization.k8s.io/tekton-triggers-core-interceptors-secrets created
rolebinding.rbac.authorization.k8s.io/tekton-triggers-controller-admin created
rolebinding.rbac.authorization.k8s.io/tekton-triggers-webhook-admin created
rolebinding.rbac.authorization.k8s.io/tekton-triggers-core-interceptors created
rolebinding.rbac.authorization.k8s.io/tekton-triggers-info created
customresourcedefinition.apiextensions.k8s.io/clusterinterceptors.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/clustertriggerbindings.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/eventlisteners.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/triggers.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/triggerbindings.triggers.tekton.dev created
customresourcedefinition.apiextensions.k8s.io/triggertemplates.triggers.tekton.dev created
secret/triggers-webhook-certs created
validatingwebhookconfiguration.admissionregistration.k8s.io/validation.webhook.triggers.tekton.dev created
mutatingwebhookconfiguration.admissionregistration.k8s.io/webhook.triggers.tekton.dev created
validatingwebhookconfiguration.admissionregistration.k8s.io/config.webhook.triggers.tekton.dev created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-aggregate-edit created
clusterrole.rbac.authorization.k8s.io/tekton-triggers-aggregate-view created
configmap/config-defaults-triggers created
configmap/feature-flags-triggers created
configmap/triggers-info created
configmap/config-logging-triggers created
configmap/config-observability-triggers created
service/tekton-triggers-controller created
deployment.apps/tekton-triggers-controller created
service/tekton-triggers-webhook created
deployment.apps/tekton-triggers-webhook created
deployment.apps/tekton-triggers-core-interceptors created
service/tekton-triggers-core-interceptors created
clusterinterceptor.triggers.tekton.dev/cel created
clusterinterceptor.triggers.tekton.dev/bitbucket created
clusterinterceptor.triggers.tekton.dev/github created
clusterinterceptor.triggers.tekton.dev/gitlab created
secret/tekton-triggers-core-interceptors-certs created

你可以使用以下命令监视和验证安装:

kubectl get pods -w -n tekton-pipelines

你应该看到三个新创建并运行的 pod——tekton-triggers-controllertekton-triggers-core-interceptorstekton-triggers-webhook

NAME                                                 READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-5fd68749f5-tz8dv         1/1     Running   0          27m
tekton-pipelines-webhook-58dcdbfd9b-hswpk            1/1     Running   0          27m
tekton-triggers-controller-854d44fd5d-8jf9q          1/1     Running   0          105s
tekton-triggers-core-interceptors-5454f8785f-dhsrb   1/1     Running   0          104s
tekton-triggers-webhook-86d75f875-zmjf4              1/1     Running   0          105s

安装完成后,你在 Kubernetes 集群上拥有一个完全可工作的 Tekton 安装,支持通过事件触发器进行流水线和自动化。此外,你还可以安装Tekton 仪表板,以便通过漂亮的 UI 可视化任务、流水线和日志。在撰写本书时,我们使用的是版本 0.28.0:

kubectl apply \
-f https://storage.googleapis.com/tekton-releases/dashboard/previous/v0.28.0/tekton-dashboard-release.yaml

你应该看到类似以下的输出:

customresourcedefinition.apiextensions.k8s.io/extensions.dashboard.tekton.dev created
serviceaccount/tekton-dashboard created
role.rbac.authorization.k8s.io/tekton-dashboard-info created
clusterrole.rbac.authorization.k8s.io/tekton-dashboard-backend created
clusterrole.rbac.authorization.k8s.io/tekton-dashboard-tenant created
rolebinding.rbac.authorization.k8s.io/tekton-dashboard-info created
clusterrolebinding.rbac.authorization.k8s.io/tekton-dashboard-backend created
configmap/dashboard-info created
service/tekton-dashboard created
deployment.apps/tekton-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/tekton-dashboard-tenant created

您可以使用以下命令监视和验证安装:

kubectl get pods -w -n tekton-pipelines

您应该看到一个新的 pod 被创建并运行—tekton-dashboard

NAME                                                READY STATUS   RESTARTS        AGE
tekton-dashboard-786b6b5579-sscgz                   1/1   Running  0               2m25s
tekton-pipelines-controller-5fd68749f5-tz8dv        1/1   Running  1 (7m16s ago)   5d7h
tekton-pipelines-webhook-58dcdbfd9b-hswpk           1/1   Running  1 (7m6s ago)    5d7h
tekton-triggers-controller-854d44fd5d-8jf9q         1/1   Running  2 (7m9s ago)    5d7h
tekton-triggers-core-interceptors-5454f8785f-dhsrb  1/1   Running  1 (7m7s ago)    5d7h
tekton-triggers-webhook-86d75f875-zmjf4             1/1   Running  2 (7m9s ago)    5d7h

默认情况下,仪表板不会在 Kubernetes 集群外部暴露。您可以使用以下命令访问它:

kubectl port-forward svc/tekton-dashboard 9097:9097 -n tekton-pipelines
提示

在 Kubernetes 中暴露内部服务有几种方法;您还可以创建一个Ingress,如 Tekton 仪表板文档中所示。

您现在可以浏览http://localhost:9097以访问您的仪表板,如 Figure 6-2 所示。

您可以下载并安装Tekton CLI适用于您的操作系统,以便从命令行开始创建任务和流水线。在撰写本书时,我们使用的是版本 0.25.0。

Tekton 仪表板

图 6-2. Tekton 仪表板

最后,验证tkn和 Tekton 是否正确配置:

tkn version

您应该获得以下输出:

Client version: 0.25.0
Pipeline version: v0.37.0
Triggers version: v0.20.1
Dashboard version: v0.28.0

参见

6.2 创建一个 Hello World 任务

问题

您希望通过探索任务并创建一个示例任务来开始使用 Tekton。

解决方案

在 Tekton 中,一个任务定义了一系列按顺序运行的步骤,以执行任务所需的逻辑。每个任务在您的 Kubernetes 集群上作为一个 pod 运行,每个步骤在自己的容器中运行。虽然任务内部的步骤是顺序执行的,但任务可以在流水线中并行执行。因此,任务是使用 Tekton 运行流水线的构建块。

让我们创建一个 Hello World 任务:

apiVersion: tekton.dev/v1beta1
kind: Task ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
metadata:
  name: hello ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
spec:
  steps: ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    - name: say-hello ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
      image: registry.access.redhat.com/ubi8/ubi ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
      command:
        - /bin/bash
      args: ['-c', 'echo Hello GitOps Cookbook reader!']

1

作为Task类型对象的 API

2

任务的名称

3

包含在此任务中的步骤列表,本例中只有一个

4

步骤的名称

5

步骤开始的容器镜像

首先,您需要在 Kubernetes 中创建此资源:

kubectl create -f helloworld-task.yaml

您应该获得以下输出:

task.tekton.dev/hello created

您可以验证对象是否已在当前的 Kubernetes 命名空间中创建:

kubectl get tasks

您应该看到类似以下的输出:

NAME    AGE
hello   90s

现在,您可以使用tkn CLI 启动您的 Tekton 任务:

tkn task start --showlog hello

您应该看到类似以下的输出:

TaskRun started: hello-run-8bmzz
Waiting for logs to be available...
[say-hello] Hello World
注意

一个 TaskRun 是一个正在运行的任务的 API 表示。查看 Recipe 6.3 获取更多细节。

参见

6.3 创建一个从 Git 编译和打包应用的任务

问题

您希望使用 Tekton 在 Kubernetes 上自动编译和打包来自 Git 的应用程序。

解决方案

正如在配方 6.2 中所见,Tekton 任务具有一种灵活的机制,可以添加一个顺序步骤列表以自动执行操作。其思想是创建一个任务列表,并通过输入/输出链条来组合管道。因此,任务可以包含一系列可选字段,以更好地控制资源:

inputs

任务摄取的资源。

outputs

任务产生的资源。

params

将用于任务步骤的参数。每个参数都有:

name

参数的名称。

description

参数的描述。

default

参数的默认值。

results

任务写入执行结果的名称。

workspaces

任务需要的卷的路径。

volumes

任务还可以使用volumes属性挂载外部卷。

如图 6-3 所示的示例显示了一个名为build-app的任务,该任务使用git命令克隆源代码并将源代码列出到输出中。

构建应用任务

图 6-3。build-app任务
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: build-app
spec:
  workspaces: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    - name: source
      description: The git repo will be cloned onto the volume backing this workspace
  params: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    - name: contextDir
      description: the context dir within source
      default: quarkus
    - name: tlsVerify
      description: tls verify
      type: string
      default: "false"
    - name: url
      default: https://github.com/gitops-cookbook/tekton-tutorial-greeter.git
    - name: revision
      default: master
    - name: subdirectory
      default: ""
    - name: sslVerify
      description: defines if http.sslVerify should be set to true or false in the global git config
      type: string
      default: "false"
  steps:
    - image: 'gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.21.0'
      name: clone
      resources: {}
      script: |
        CHECKOUT_DIR="$(workspaces.source.path)/$(params.subdirectory)"
        cleandir() {
          # Delete any existing contents of the repo directory if it exists.
          #
          # We don't just "rm -rf $CHECKOUT_DIR" because $CHECKOUT_DIR might be "/"
          # or the root of a mounted volume.
          if [[ -d "$CHECKOUT_DIR" ]] ; then
            # Delete non-hidden files and directories
            rm -rf "$CHECKOUT_DIR"/*
            # Delete files and directories starting with . but excluding ..
            rm -rf "$CHECKOUT_DIR"/.[!.]*
            # Delete files and directories starting with .. plus any other character
            rm -rf "$CHECKOUT_DIR"/..?*
          fi
        }
        /ko-app/git-init \
          -url "$(params.url)" \
          -revision "$(params.revision)" \
          -path "$CHECKOUT_DIR" \
          -sslVerify="$(params.sslVerify)"
        cd "$CHECKOUT_DIR"
        RESULT_SHA="$(git rev-parse HEAD)"
    - name: build-sources
      image: gcr.io/cloud-builders/mvn
      command:
        - mvn
      args:
        - -DskipTests
        - clean
        - install
      env:
        - name: user.home
          value: /home/tekton
      workingDir: "/workspace/source/$(params.contextDir)"

1

一个任务步骤和管道任务可以通过 Tekton 工作空间共享共享文件系统。工作空间可以由类似 PersistentVolumeClaim(PVC)和ConfigMap之类的东西支持,或者只是临时的(emptyDir)。

2

任务可以有参数;此功能使执行动态化。

让我们使用以下命令创建任务:

kubectl create -f build-app-task.yaml

您应该获得类似以下的输出:

task.tekton.dev/build-app created

您可以验证对象是否已在当前的 Kubernetes 命名空间中创建:

kubectl get tasks

您应该获得类似以下的输出:

NAME        AGE
build-app   3s

您还可以使用tkn CLI 列出任务:

tkn task ls

您应该获得类似以下的输出:

NAME        DESCRIPTION   AGE
build-app                 10 seconds ago

当您启动任务时,将创建一个新的TaskRun对象。TaskRuns 是正在运行任务的 API 表示;因此,您可以使用以下命令使用tkn CLI 创建它:

tkn task start build-app \
  --param contextDir='quarkus' \
  --workspace name=source,emptyDir="" \
  --showlog
提示

当在任务或管道中使用参数时,如果需要,您将被提示添加新值或确认默认值。为了在不提示值的情况下使用任务定义中的默认值,您可以使用--use-param-defaults选项。

您应该获得类似以下的输出:

? Value for param `tlsVerify` of type `string`? (Default is `false`) false
? Value for param `url` of type `string`? (Default is `https://github.com/gitops-cookbook/tekton-tutorial-greeter.git`) https://github.com/gitops-cookbook/tekton-tutorial-greeter.git
? Value for param `revision` of type `string`? (Default is `master`) master
? Value for param `subdirectory` of type `string`? (Default is ``)
? Value for param `sslVerify` of type `string`? (Default is `false`) false
TaskRun started: build-app-run-rzcd8
Waiting for logs to be available...
[clone] {"level":"info","ts":1659278019.0018234,"caller":"git/git.go:169","msg":"Successfully cloned https://github.com/gitops-cookbook/tekton-tutorial-greeter.git @ d9291c456db1ce29177b77ffeaa9b71ad80a50e6 (grafted, HEAD, origin/master) in path /workspace/source/"}
[clone] {"level":"info","ts":1659278019.0227938,"caller":"git/git.go:207","msg":"Successfully initialized and updated submodules in path /workspace/source/"}

[build-sources] [INFO] Scanning for projects...
[build-sources] Downloading from central: https://repo.maven.apache.org/maven2/io/quarkus/quarkus-universe-bom/1.6.1.Final/quarkus-universe-bom-1.6.1.Final.pom
Downloaded from central: https://repo.maven.apache.org/maven2/io/quarkus/quarkus-universe-bom/1.6.1.Final/quarkus-universe-bom-1.6.1.Final.pom (412 kB at 118 kB/s)
[build-sources] [INFO]
...
[build-sources] [INFO] Installing /workspace/source/quarkus/target/tekton-quarkus-greeter.jar to /root/.m2/repository/com/redhat/developers/tekton-quarkus-greeter/1.0.0-SNAPSHOT/tekton-quarkus-greeter-1.0.0-SNAPSHOT.jar
[build-sources] [INFO] Installing /workspace/source/quarkus/pom.xml to /root/.m2/repository/com/redhat/developers/tekton-quarkus-greeter/1.0.0-SNAPSHOT/tekton-quarkus-greeter-1.0.0-SNAPSHOT.pom
[build-sources] [INFO] ------------------------------------------------------------------------
[build-sources] [INFO] BUILD SUCCESS
[build-sources] [INFO] ------------------------------------------------------------------------
[build-sources] [INFO] Total time:  04:41 min
[build-sources] [INFO] Finished at: 2022-07-31T14:38:22Z
[build-sources] [INFO] ------------------------------------------------------------------------

或者,您可以像这样手动创建一个TaskRun对象:

apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  generateName: build-app-run- ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  labels:
    app.kubernetes.io/managed-by: tekton-pipelines
    tekton.dev/task: build-app
spec:
  params:
  - name: contextDir
    value: quarkus
  - name: revision
    value: master
  - name: sslVerify
    value: "false"
  - name: subdirectory
    value: ""
  - name: tlsVerify
    value: "false"
  - name: url
    value: https://github.com/gitops-cookbook/tekton-tutorial-greeter.git
  taskRef: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    kind: Task
    name: build-app
  workspaces:
  - emptyDir: {}
    name: source

1

如果您不想为每个TaskRun指定名称,可以使用generateName属性,让 Tekton 从您定义的字符串中随机选择一个。

2

在这里列出TaskRun引用的任务。

并以这种方式启动它:

kubectl create -f build-app-taskrun.yaml

您应该获得类似以下的输出:

taskrun.tekton.dev/build-app-run-65vmh created

您还可以使用tkn CLI 验证它:

tkn taskrun ls

您应该获得类似以下的输出:

NAME                  STARTED          DURATION   STATUS
build-app-run-65vmh   1 minutes ago   2m37s      Succeeded
build-app-run-rzcd8   2 minutes ago   3m58s      Succeeded

通过指定TaskRun的名称,您可以从中获取日志:

tkn taskrun logs build-app-run-65vmh -f

参见

调试 TaskRun

6.4 创建一个任务来从私有 Git 编译和打包应用程序

问题

你想要使用私有 Git 仓库来自动化在 Kubernetes 上使用 Tekton 编译和打包应用程序。

解决方案

在 Recipe 6.3 中,你看到如何使用公共 Git 存储库编译和打包示例 Java 应用程序,但大多数时候人们在工作中处理的是私有仓库,那么如何集成它们呢?Tekton 支持以下用于 Git 的身份验证方案:

  • 基本身份验证

  • SSH

你可以使用 Kubernetes 的秘钥来存储你的凭据,并将其附加到运行 Tekton 任务或流水线的ServiceAccount

提示

Tekton 使用默认的服务账户,但你可以按照这里的文档覆盖它。

让我们从一个常见的基本身份验证示例开始,以及像 GitHub 这样的热门 Git 服务。

注意

GitHub 使用个人访问令牌(PAT)作为与密码身份验证的替代方案。你可以使用 PAT 代替明文密码以增强安全性。

首先,你需要创建一个秘钥。你可以通过创建以下 YAML 文件来做到这一点:

apiVersion: v1
kind: Secret
metadata:
  name: github-secret
  annotations:
    tekton.dev/git-0: https://github.com ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
type: kubernetes.io/basic-auth ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
stringData:
  username: YOUR_USERNAME ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  password: YOUR_PASSWORD ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)

1

在这里,你指定 Tekton 将使用此秘钥的 URL,本例中为 GitHub

2

这是这种秘钥的类型,此处是基本身份验证

3

你的 Git 用户,本例中是你的 GitHub 用户

4

你的 Git 密码,本例中是你的 GitHub 个人访问令牌

现在你可以使用以下命令创建秘钥:

kubectl create -f git-secret.yaml

你应该得到以下输出:

secret/git-secret created

你还可以避免编写 YAML 并执行以下所有操作:

kubectl create secret generic git-secret \
    --type=kubernetes.io/basic-auth \
    --from-literal=username=YOUR_USERNAME \
    --from-literal=password=YOUR_PASSWORD

然后你只需如下注释秘钥:

kubectl annotate secret git-secret "tekton.dev/git-0=https://github.com"

一旦创建并注释了你的秘钥(Secret),你需要将其附加到运行 Tekton 任务或流水线的ServiceAccount

让我们为此目的创建一个新的ServiceAccount

apiVersion: v1
kind: ServiceAccount
metadata:
  name: tekton-bot-sa
secrets:
  - name: git-secret ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

附加到此ServiceAccount的秘钥列表

kubectl create -f tekton-bot-sa.yaml

你应该得到以下输出:

serviceaccount/tekton-bot-sa created
提示

你可以直接使用kubectl创建ServiceAccount,如下所示:

kubectl create serviceaccount tekton-bot-sa

然后进行补丁添加秘钥引用:

kubectl patch serviceaccount tekton-bot-sa -p '{"secrets": [{"name": "git-secret"}]}'

一旦设置了凭据并将其链接到运行任务或流水线的ServiceAccount,你可以在你的tkn命令中添加--serviceaccount=<NAME>选项,使用 Recipe 6.3 的示例:

tkn task start build-app \
  --serviceaccount='tekton-bot-sa' \ ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  --param url='https://github.com/gitops-cookbook/tekton-greeter-private.git' \ ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  --param contextDir='quarkus' \
  --workspace name=source,emptyDir="" \
  --showlog

1

在这里,你指定要使用的ServiceAccount;这将在运行时覆盖默认设置。

2

在这里,你可以使用你选择的替换默认仓库。在这个例子中,有一个你无法访问的私有仓库,但你可以自己创建一个私有仓库,并像这样进行测试。

您应该得到类似以下的输出:

...
[clone] {"level":"info","ts":1659354692.1365478,"caller":"git/git.go:169","msg":"Successfully cloned https://github.com/gitops-cookbook/tekton-greeter-private.git @ 5250e1fa185805373e620d1c04a0c48129efd2ee (grafted, HEAD, origin/master) in path /workspace/source/"}
[clone] {"level":"info","ts":1659354692.1546066,"caller":"git/git.go:207","msg":"Successfully initialized and updated submodules in path /workspace/source/"}
...
[build-sources] [INFO] ------------------------------------------------------------------------
[build-sources] [INFO] BUILD SUCCESS
[build-sources] [INFO] ------------------------------------------------------------------------
[build-sources] [INFO] Total time:  04:30 min
[build-sources] [INFO] Finished at: 2022-07-31T15:30:01Z
[build-sources] [INFO] ------------------------------------------------------------------------

另请参阅

6.5 使用 Tekton 任务和 Buildah 容器化应用程序

问题

您希望在 Kubernetes 上使用 Tekton 任务编译、打包和容器化您的应用程序。

解决方案

在采用云原生方法时自动化至关重要,如果您决定在 Kubernetes 上使用 CI/CD 工作负载,您需要提供一种打包和部署应用程序的方式。

实际上,Kubernetes 本身没有内置的构建容器的机制;它依赖于 Tekton 或外部服务来实现这一目的。这就是为什么在第三章中,我们概述了如何使用各种开源工具为打包应用程序创建容器。在第 3.3 节中,我们使用 Buildah 从 Dockerfile 创建了一个容器。

多亏了 Tekton 的可扩展模型,您可以重用在第 6.3 节的配方中定义的同一任务,以添加一个步骤来使用前面步骤的结果创建一个容器,如图 6-4 所示。

构建推送应用

图 6-4. 构建推送应用

容器可以推送到公共容器注册表,如 DockerHub 或 Quay.io,也可以推送到私有容器注册表。与我们在第 6.4 节中看到的私有 Git 存储库类似,将容器映像推送到容器注册表需要进行身份验证。需要将 Secret 附加到运行任务的ServiceAccount,如下所示。请参阅第二章,了解如何注册和使用公共注册表。

kubectl create secret docker-registry container-registry-secret \
  --docker-server='YOUR_REGISTRY_SERVER' \
  --docker-username='YOUR_REGISTRY_USER' \
  --docker-password='YOUR_REGISTRY_PASS'
secret/container-registry-secret created

确保它存在,并检查 Secret 的类型是kubernetes.io/dockerconfigjson

kubectl get secrets

您应该得到以下输出:

NAME                        TYPE                             DATA   AGE
container-registry-secret   kubernetes.io/dockerconfigjson   1      1s

让我们为此任务创建一个ServiceAccount

kubectl create serviceaccount tekton-registry-sa

然后将先前生成的Secret添加到此ServiceAccount

kubectl patch serviceaccount tekton-registry-sa \
  -p '{"secrets": [{"name": "container-registry-secret"}]}'

您应该得到以下输出:

serviceaccount/tekton-registry-sa patched

让我们添加一个新步骤来创建一个容器映像并将其推送到容器注册表。在以下示例中,我们使用的是 Quay.io 的书籍组织存储库—quay.io/gitops-cookbook/tekton-greeter:latest

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: build-push-app
spec:
  workspaces:
    - name: source
      description: The git repo will be cloned onto the volume backing this workspace
  params:
    - name: contextDir
      description: the context dir within source
      default: quarkus
    - name: tlsVerify
      description: tls verify
      type: string
      default: "false"
    - name: url
      default: https://github.com/gitops-cookbook/tekton-tutorial-greeter.git
    - name: revision
      default: master
    - name: subdirectory
      default: ""
    - name: sslVerify
      description: defines if http.sslVerify should be set to true or false in the global git config
      type: string
      default: "false"
    - name: storageDriver
      type: string
      description: Storage driver
      default: vfs
    - name: destinationImage
      description: the fully qualified image name
      default: ""
  steps:
    - image: 'gcr.io/tekton-releases/github.com/tektoncd/pipeline/cmd/git-init:v0.21.0'
      name: clone
      resources: {}
      script: |
        CHECKOUT_DIR="$(workspaces.source.path)/$(params.subdirectory)"
        cleandir() {
          # Delete any existing contents of the repo directory if it exists.
          #
          # We don't just "rm -rf $CHECKOUT_DIR" because $CHECKOUT_DIR might be "/"
          # or the root of a mounted volume.
          if [[ -d "$CHECKOUT_DIR" ]] ; then
            # Delete non-hidden files and directories
            rm -rf "$CHECKOUT_DIR"/*
            # Delete files and directories starting with . but excluding ..
            rm -rf "$CHECKOUT_DIR"/.[!.]*
            # Delete files and directories starting with .. plus any other character
            rm -rf "$CHECKOUT_DIR"/..?*
          fi
        }
        /ko-app/git-init \
          -url "$(params.url)" \
          -revision "$(params.revision)" \
          -path "$CHECKOUT_DIR" \
          -sslVerify="$(params.sslVerify)"
        cd "$CHECKOUT_DIR"
        RESULT_SHA="$(git rev-parse HEAD)"
    - name: build-sources
      image: gcr.io/cloud-builders/mvn
      command:
        - mvn
      args:
        - -DskipTests
        - clean
        - install
      env:
        - name: user.home
          value: /home/tekton
      workingDir: "/workspace/source/$(params.contextDir)"
    - name: build-and-push-image
      image: quay.io/buildah/stable
      script: |
        #!/usr/bin/env bash
        buildah --storage-driver=$STORAGE_DRIVER --tls-verify=$(params.tlsVerify) bud --layers -t $DESTINATION_IMAGE $CONTEXT_DIR
        buildah --storage-driver=$STORAGE_DRIVER --tls-verify=$(params.tlsVerify) push $DESTINATION_IMAGE docker://$DESTINATION_IMAGE
      env:
        - name: DESTINATION_IMAGE
          value: "$(params.destinationImage)"
        - name: CONTEXT_DIR
          value: "/workspace/source/$(params.contextDir)"
        - name: STORAGE_DRIVER
          value: "$(params.storageDriver)"
      workingDir: "/workspace/source/$(params.contextDir)"
      volumeMounts:
        - name: varlibc
          mountPath: /var/lib/containers
  volumes:
    - name: varlibc
      emptyDir: {}

让我们创建此任务:

kubectl create -f build-push-app.yaml

您应该得到以下输出:

task.tekton.dev/build-push-app created

现在让我们使用 Buildah 步骤启动任务,创建一个容器映像,并使用新参数destinationImage来指定将结果容器映像推送到哪里:

tkn task start build-push-app \
  --serviceaccount='tekton-registry-sa' \
  --param url='https://github.com/gitops-cookbook/tekton-tutorial-greeter.git' \
  --param destinationImage='quay.io/gitops-cookbook/tekton-greeter:latest' \ ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  --param contextDir='quarkus' \
  --workspace name=source,emptyDir="" \
  --use-param-defaults \
  --showlog

1

在这里,您可以放置您的注册表;在这个例子中,我们使用的是 Quay.io 的书籍组织存储库。

您应该得到类似以下的输出:

...
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-utils/3.0.5/plexus-utils-3.0.5.jar (230 kB at 301 kB/s)
[build-sources] [INFO] Installing /workspace/source/quarkus/target/tekton-quarkus-greeter.jar to /root/.m2/repository/com/redhat/developers/tekton-quarkus-greeter/1.0.0-SNAPSHOT/tekton-quarkus-greeter-1.0.0-SNAPSHOT.jar
[build-sources] [INFO] Installing /workspace/source/quarkus/pom.xml to /root/.m2/repository/com/redhat/developers/tekton-quarkus-greeter/1.0.0-SNAPSHOT/tekton-quarkus-greeter-1.0.0-SNAPSHOT.pom
[build-sources] [INFO] ------------------------------------------------------------------------
[build-sources] [INFO] BUILD SUCCESS
[build-sources] [INFO] ------------------------------------------------------------------------
[build-sources] [INFO] Total time:  02:59 min
[build-sources] [INFO] Finished at: 2022-08-02T06:18:37Z
[build-sources] [INFO] ------------------------------------------------------------------------
[build-and-push-image] STEP 1/2: FROM registry.access.redhat.com/ubi8/openjdk-11
[build-and-push-image] Trying to pull registry.access.redhat.com/ubi8/openjdk-11:latest...
[build-and-push-image] Getting image source signatures
[build-and-push-image] Checking if image destination supports signatures
[build-and-push-image] Copying blob sha256:1e09a5ee0038fbe06a18e7f355188bbabc387467144abcd435f7544fef395aa1
[build-and-push-image] Copying blob sha256:0d725b91398ed3db11249808d89e688e62e511bbd4a2e875ed8493ce1febdb2c
[build-and-push-image] Copying blob sha256:1e09a5ee0038fbe06a18e7f355188bbabc387467144abcd435f7544fef395aa1
[build-and-push-image] Copying blob sha256:0d725b91398ed3db11249808d89e688e62e511bbd4a2e875ed8493ce1febdb2c
[build-and-push-image] Copying blob sha256:e441d34134fac91baa79be3e2bb8fb3dba71ba5c1ea012cb5daeb7180a054687
[build-and-push-image] Copying blob sha256:e441d34134fac91baa79be3e2bb8fb3dba71ba5c1ea012cb5daeb7180a054687
[build-and-push-image] Copying config sha256:0c308464b19eaa9a01c3fdd6b63a043c160d4eea85e461bbbb7d01d168f6d993
[build-and-push-image] Writing manifest to image destination
[build-and-push-image] Storing signatures
[build-and-push-image] STEP 2/2: COPY target/quarkus-app /deployments/
[build-and-push-image] COMMIT quay.io/gitops-cookbook/tekton-greeter:latest
[build-and-push-image] --> 42fe38b4346
[build-and-push-image] Successfully tagged quay.io/gitops-cookbook/tekton-greeter:latest
[build-and-push-image] 42fe38b43468c3ca32262dbea6fd78919aba2bd35981cd4f71391e07786c9e21
[build-and-push-image] Getting image source signatures
[build-and-push-image] Copying blob sha256:647a854c512bad44709221b6b0973e884f29bcb5a380ee32e95bfb0189b620e6
[build-and-push-image] Copying blob sha256:f2ee6b2834726167d0de06f3bbe65962aef79855c5ede0d2ba93b4408558d9c9
[build-and-push-image] Copying blob sha256:8e0e04b5c700a86f4a112f41e7e767a9d7c539fe3391611313bf76edb07eeab1
[build-and-push-image] Copying blob sha256:69c55192bed92cbb669c88eb3c36449b64ac93ae466abfff2a575273ce05a39e
[build-and-push-image] Copying config sha256:42fe38b43468c3ca32262dbea6fd78919aba2bd35981cd4f71391e07786c9e21
[build-and-push-image] Writing manifest to image destination
[build-and-push-image] Storing signatures

另请参阅

6.6 使用 Tekton 任务部署应用程序到 Kubernetes

问题

您想要使用 Tekton 任务将应用程序从容器映像部署到 Kubernetes。

解决方案

在 示例 6.3、6.4 和 6.5 中,我们列出了一个对持续集成(CI)有用的 Tekton 任务,而在这个示例中,我们将从持续部署(CD)部分开始,通过将现有的容器镜像部署到 Kubernetes。

我们可以重用我们在 示例 6.5 中创建并推送的容器镜像,其位置为 quay.io/gitops-cookbook/tekton-greeter:latest

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: kubectl
spec:
  params:
    - name: SCRIPT
      description: The kubectl CLI arguments to run
      type: string
      default: "kubectl help"
  steps:
    - name: oc
      image: quay.io/openshift/origin-cli:latest ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
      script: |
        #!/usr/bin/env bash

        $(params.SCRIPT)

1

在此示例中,我们使用的是来自此容器镜像的 kubectl,它还包含 OpenShift CLI,并且其大小比 gcr.io/cloud-builders/kubectl 要小。

让我们创建这个任务:

kubectl create -f kubectl-task.yaml

你应该得到以下输出:

task.tekton.dev/kubectl created

正如在 示例 6.5 中所讨论的,Tekton 在运行任务和流水线时使用默认的 ServiceAccount,除非在运行时或全局范围内进行了特定定义的覆盖。最佳实践是始终为特定操作创建一个特定的 ServiceAccount,因此让我们为此示例创建一个名为 tekton-deployer-sa 的特定 ServiceAccount 如下:

kubectl create serviceaccount tekton-deployer-sa

你应该得到以下输出:

serviceaccount/tekton-deployer-sa created

一个 ServiceAccount 需要权限将应用程序部署到 Kubernetes。角色(Roles)和角色绑定(RoleBindings) 是用于将特定权限映射给用户或 ServiceAccount 的 API 对象。

首先,为运行 Tekton 任务的 ServiceAccount 定义一个名为 pipeline-role 的角色,并赋予部署应用程序的权限:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: task-role
rules:
  - apiGroups:
      - ""
    resources:
      - pods
      - services
      - endpoints
      - configmaps
      - secrets
    verbs:
      - "*"
  - apiGroups:
      - apps
    resources:
      - deployments
      - replicasets
    verbs:
      - "*"
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
  - apiGroups:
      - apps
    resources:
      - replicasets
    verbs:
      - get

现在,你需要将角色绑定到 ServiceAccount

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: task-role-binding
roleRef:
  kind: Role
  name: task-role
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: ServiceAccount
    name: tekton-deployer-sa

现在,你可以按以下方式创建这两个资源:

kubectl create -f task-role.yaml
kubectl create -f task-role-binding.yaml

你应该得到以下输出:

role.rbac.authorization.k8s.io/task-role created
rolebinding.rbac.authorization.k8s.io/task-role-binding created

最后,你可以按以下方式定义一个 TaskRun:

apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  name: kubectl-taskrun
spec:
  serviceAccountName: tekton-deployer-sa
  taskRef:
    name: kubectl
  params:
    - name: SCRIPT
      value: |
        kubectl create deploy tekton-greeter --image=quay.io/gitops-cookbook/tekton-greeter:latest

并以以下方式运行它:

kubectl create -f kubectl-taskrun.yaml

你应该得到以下输出:

taskrun.tekton.dev/kubectl-run created

你可以检查日志查看结果:

tkn taskrun logs kubectl-run -f

你应该得到类似以下的输出:

? Select taskrun: kubectl-run started 9 seconds ago
[oc] deployment.apps/tekton-greeter created

几秒钟后,你应该看到部署处于就绪状态:

kubectl get deploy

NAME             READY   UP-TO-DATE   AVAILABLE   AGE
tekton-greeter   1/1     1            0           30s
注意

第一次可能会花费一些时间,因为需要拉取容器镜像。

检查应用是否可用,将部署暴露,并将 Kubernetes 流量转发到你的工作站进行测试:

kubectl expose deploy/tekton-greeter --port 8080
kubectl port-forward svc/tekton-greeter 8080:8080

在另一个终端中,运行以下命令:

curl localhost:8080

你应该看到以下输出:

Meeow!! from Tekton ----

参见

6.7 创建 Tekton 流水线以构建并部署应用到 Kubernetes

问题

你想要创建一个用于在 Kubernetes 上编译、打包和部署应用程序的流水线。

解决方案

在之前的示例中,我们看到了如何创建任务以依次执行一个或多个步骤来构建应用程序。在本示例中,我们将讨论 Tekton 流水线,这是一组可以按照特定顺序(无论是顺序执行还是并行执行)定义和组合的任务,正如你可以在 图 6-5 中看到的那样。

Tekton 流水线流程

图 6-5. Tekton 流水线流程

Tekton Pipelines 支持参数和在不同任务之间交换结果的机制。例如,使用 Recipes 6.5 和 6.6 中所示的例子:

kubectl patch serviceaccount tekton-deployer-sa \
  -p '{"secrets": [{"name": "container-registry-secret"}]}'
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: tekton-greeter-pipeline
spec:
  params: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    - name: GIT_REPO
      type: string
    - name: GIT_REF
      type: string
    - name : DESTINATION_IMAGE
      type: string
    - name : SCRIPT
      type: string
  tasks: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    - name: build-push-app
      taskRef: ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
        name: build-push-app
      params:
        - name: url
          value: "$(params.GIT_REPO)"
        - name: revision
          value: "$(params.GIT_REF)"
        - name: destinationImage
          value: "$(params.DESTINATION_IMAGE)"
      workspaces:
        - name: source
    - name: deploy-app
      taskRef:
        name: kubectl
      params:
        - name: SCRIPT
          value: "$(params.SCRIPT)"
      workspaces:
        - name: source
      runAfter: ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
        - build-push-app
  workspaces: ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
    - name: source

1

流水线参数

2

流水线任务列表

3

要使用的任务的确切名称

4

您可以使用 runAfter 字段决定顺序,指示一个任务必须在一个或多个其他任务之后执行

5

用于在任务之间共享数据的一个或多个常见工作空间的确切名称

让我们按如下方式创建这个流水线:

kubectl create -f tekton-greeter-pipeline.yaml

您应该得到以下输出:

pipeline.tekton.dev/tekton-greeter-pipeline created

类似于 TaskRuns,您可以通过创建 PipelineRun 资源来运行此 Pipeline,方法如下:

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  generateName: tekton-greeter-pipeline-run-
spec:
  params:
  - name: GIT_REPO
    value: https://github.com/gitops-cookbook/tekton-tutorial-greeter.git
  - name: GIT_REF
    value: "master"
  - name: DESTINATION_IMAGE
    value: "quay.io/gitops-cookbook/tekton-greeter:latest"
  - name: SCRIPT
    value: |
        kubectl create deploy tekton-greeter --image=quay.io/gitops-cookbook/tekton-greeter:latest
  pipelineRef:
    name: tekton-greeter-pipeline
  workspaces:
    - name: source
      emptyDir: {}

您可以通过创建以下 PipelineRun 对象来运行该流水线:

kubectl create -f tekton-greeter-pipelinerun.yaml

您可以检查状态:

tkn pipelinerun ls
NAME                                STARTED         DURATION   STATUS
tekton-greeter-pipeline-run-ntl5r   7 seconds ago   ---        Running

现在您已经看到如何在 Pipeline 中重复使用现有任务,现在是时候介绍 Tekton Hub 了,这是一个供开发人员发现、分享和贡献 Tekton 任务和流水线的基于 Web 的平台(参见 图 6-6)。

Tekton Hub

图 6-6. Tekton Hub

你可以使用 Hub 中已有的任务实现相同的 Pipeline。在我们的情况下,我们有:

git-clone

克隆指定 URL 的 repo 到输出工作空间的任务。

buildah

构建源代码到容器映像并将其推送到容器注册表的任务。

kubernetes-actions

通用的 kubectl CLI 任务,可用于运行各种 k8s 命令。

首先让我们按如下方式将它们添加到我们的命名空间中:

tkn hub install task git-clone
tkn hub install task maven
tkn hub install task buildah
tkn hub install task kubernetes-actions

您应该得到类似以下的输出来确认它们在您的命名空间中已经正确安装:

Task git-clone(0.7) installed in default namespace
Task maven(0.2) installed in default namespace
Task buildah(0.4) installed in default namespace
Task kubernetes-actions(0.2) installed in default namespace

您可以使用以下命令进行交叉检查:

kubectl get tasks

您应该得到类似以下的输出:

NAME                 AGE
...
buildah              50s
git-clone            52s
kubernetes-actions   49s
maven                51s
...
提示

一些 Tekton 安装,例如使用 OpenShift Pipelines 运算符创建的安装,提供了一些常用任务的公共列表,如刚刚列出的那些,作为 ClusterTasks 提供。ClusterTasks 是在 Kubernetes 集群中所有命名空间中都可用的任务。使用以下命令检查您的安装是否已经提供了一些:kubectl get clustertasks

现在 Pipeline 有四个任务,如您在 图 6-7 中所见。

流水线

图 6-7. 流水线

在这个例子中,您将看到一个 PersistentVolumeClaim 作为工作空间,因为这里的数据在不同任务之间共享,所以我们需要持久化它:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-source-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

像往常一样,您可以使用 kubectl 创建资源:

kubectl create -f app-source-pvc.yaml

您应该看到以下输出:

persistentvolumeclaim/app-source-pvc created
kubectl get pvc

NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
app-source-pvc   Bound    pvc-e85ade46-aaca-4f3f-b644-d8ff99fd9d5e   1Gi        RWO            standard       61s
注意

在 Minikube 中,您有一个默认的 StorageClass,为集群提供动态存储。如果您正在使用其他 Kubernetes 集群,请确保支持动态存储。

管道定义如下:

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: tekton-greeter-pipeline-hub
spec:
  params:
  - default: https://github.com/gitops-cookbook/tekton-tutorial-greeter.git
    name: GIT_REPO
    type: string
  - default: master
    name: GIT_REF
    type: string
  - default: quay.io/gitops-cookbook/tekton-greeter:latest
    name: DESTINATION_IMAGE
    type: string
  - default: kubectl create deploy tekton-greeter --image=quay.io/gitops-cookbook/tekton-greeter:latest
    name: SCRIPT
    type: string
  - default: ./Dockerfile
    name: CONTEXT_DIR
    type: string
  - default: .
    name: IMAGE_DOCKERFILE
    type: string
  - default: .
    name: IMAGE_CONTEXT_DIR
    type: string
  tasks:
  - name: fetch-repo
    params:
    - name: url
      value: $(params.GIT_REPO)
    - name: revision
      value: $(params.GIT_REF)
    - name: deleteExisting
      value: "true"
    - name: verbose
      value: "true"
    taskRef:
      kind: Task
      name: git-clone
    workspaces:
    - name: output
      workspace: app-source
  - name: build-app
    params:
    - name: GOALS
      value:
      - -DskipTests
      - clean
      - package
    - name: CONTEXT_DIR
      value: $(params.CONTEXT_DIR)
    runAfter:
    - fetch-repo
    taskRef:
      kind: Task
      name: maven
    workspaces:
    - name: maven-settings
      workspace: maven-settings
    - name: source
      workspace: app-source
  - name: build-push-image
    params:
    - name: IMAGE
      value: $(params.DESTINATION_IMAGE)
    - name: DOCKERFILE
      value: $(params.IMAGE_DOCKERFILE)
    - name: CONTEXT
      value: $(params.IMAGE_CONTEXT_DIR)
    runAfter:
    - build-app
    taskRef:
      kind: Task
      name: buildah
    workspaces:
    - name: source
      workspace: app-source
  - name: deploy
    params:
    - name: script
      value: $(params.SCRIPT)
    runAfter:
    - build-push-image
    taskRef:
      kind: Task
      name: kubernetes-actions
  workspaces:
  - name: app-source
  - name: maven-settings

让我们创建资源:

kubectl create -f tekton-greeter-pipeline-hub.yaml
注意

我们使用与 Recipe 6.5 中定义的相同的秘密和 ServiceAccount 来登录 Quay.io,以推送容器映像。

您现在可以按照以下方式启动流水线:

tkn pipeline start tekton-greeter-pipeline-hub \
  --serviceaccount='tekton-deployer-sa' \
  --param GIT_REPO='https://github.com/gitops-cookbook/tekton-tutorial-greeter.git' \
  --param GIT_REF='master' \
  --param CONTEXT_DIR='quarkus' \
  --param DESTINATION_IMAGE='quay.io/gitops-cookbook/tekton-greeter:latest' \
  --param IMAGE_DOCKERFILE='quarkus/Dockerfile' \
  --param IMAGE_CONTEXT_DIR='quarkus' \
  --param SCRIPT='kubectl create deploy tekton-greeter --image=quay.io/gitops-cookbook/tekton-greeter:latest' \
  --workspace name=app-source,claimName=app-source-pvc \
  --workspace name=maven-settings,emptyDir="" \
  --use-param-defaults \
  --showlog
[fetch-repo : clone] + CHECKOUT_DIR=/workspace/output/
[fetch-repo : clone] + /ko-app/git-init '-url=https://github.com/gitops-cookbook/tekton-tutorial-greeter.git' '-revision=master' '-refspec=' '-path=/workspace/output/' '-sslVerify=true' '-submodules=true' '-depth=1' '-sparseCheckoutDirectories='
[fetch-repo : clone] {"level":"info","ts":1660819038.5526028,"caller":"git/git.go:170","msg":"Successfully cloned https://github.com/gitops-cookbook/tekton-tutorial-greeter.git @ d9291c456db1ce29177b77ffeaa9b71ad80a50e6 (grafted, HEAD, origin/master) in path /workspace/output/"}
[fetch-repo : clone] {"level":"info","ts":1660819038.5722632,"caller":"git/git.go:208","msg":"Successfully initialized and updated submodules in path /workspace/output/"}
[fetch-repo : clone] + cd /workspace/output/
[fetch-repo : clone] + git rev-parse HEAD
[fetch-repo : clone] + RESULT_SHA=d9291c456db1ce29177b77ffeaa9b71ad80a50e6
[fetch-repo : clone] + EXIT_CODE=0
[fetch-repo : clone] + '[' 0 '!=' 0 ]
[fetch-repo : clone] + printf '%s' d9291c456db1ce29177b77ffeaa9b71ad80a50e6
[fetch-repo : clone] + printf '%s' https://github.com/gitops-cookbook/tekton-tutorial-greeter.git
...
[build-app : mvn-goals] [INFO] [org.jboss.threads] JBoss Threads version 3.1.1.Final
[build-app : mvn-goals] [INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 1296ms
[build-app : mvn-goals] [INFO] ------------------------------------------------------------------------
[build-app : mvn-goals] [INFO] BUILD SUCCESS
[build-app : mvn-goals] [INFO] ------------------------------------------------------------------------
[build-app : mvn-goals] [INFO] Total time:  03:18 min
[build-app : mvn-goals] [INFO] Finished at: 2022-08-18T10:31:00Z
[build-app : mvn-goals] [INFO] ------------------------------------------------------------------------
[build-push-image : build] STEP 1/2: FROM registry.access.redhat.com/ubi8/openjdk-11
[build-push-image : build] Trying to pull registry.access.redhat.com/ubi8/openjdk-11:latest...
[build-push-image : build] Getting image source signatures
[build-push-image : build] Checking if image destination supports signatures
[build-push-image : build] Copying blob sha256:e441d34134fac91baa79be3e2bb8fb3dba71ba5c1ea012cb5daeb7180a054687
[build-push-image : build] Copying blob sha256:1e09a5ee0038fbe06a18e7f355188bbabc387467144abcd435f7544fef395aa1
[build-push-image : build] Copying blob sha256:0d725b91398ed3db11249808d89e688e62e511bbd4a2e875ed8493ce1febdb2c
[build-push-image : build] Copying blob sha256:e441d34134fac91baa79be3e2bb8fb3dba71ba5c1ea012cb5daeb7180a054687
[build-push-image : build] Copying blob sha256:1e09a5ee0038fbe06a18e7f355188bbabc387467144abcd435f7544fef395aa1
[build-push-image : build] Copying blob sha256:0d725b91398ed3db11249808d89e688e62e511bbd4a2e875ed8493ce1febdb2c
[build-push-image : build] Copying config sha256:0c308464b19eaa9a01c3fdd6b63a043c160d4eea85e461bbbb7d01d168f6d993
[build-push-image : build] Writing manifest to image destination
[build-push-image : build] Storing signatures
[build-push-image : build] STEP 2/2: COPY target/quarkus-app /deployments/
[build-push-image : build] COMMIT quay.io/gitops-cookbook/tekton-greeter:latest
[build-push-image : build] --> c07e36a8e61
[build-push-image : build] Successfully tagged quay.io/gitops-cookbook/tekton-greeter:latest
[build-push-image : build] c07e36a8e6104d2e5c7d79a6cd34cd7b44eb093c39ef6c1487a37d7bd2305b8a
[build-push-image : build] Getting image source signatures
[build-push-image : build] Copying blob sha256:7853a7797845542e3825d4f305e4784ea7bf492cd4364fc93b9afba3ac0c9553
[build-push-image : build] Copying blob sha256:8e0e04b5c700a86f4a112f41e7e767a9d7c539fe3391611313bf76edb07eeab1
[build-push-image : build] Copying blob sha256:647a854c512bad44709221b6b0973e884f29bcb5a380ee32e95bfb0189b620e6
[build-push-image : build] Copying blob sha256:69c55192bed92cbb669c88eb3c36449b64ac93ae466abfff2a575273ce05a39e
[build-push-image : build] Copying config sha256:c07e36a8e6104d2e5c7d79a6cd34cd7b44eb093c39ef6c1487a37d7bd2305b8a
[build-push-image : build] Writing manifest to image destination
[build-push-image : build] Storing signatures
[build-push-image : build] sha256:12dd3deb6305b9e125309b68418d0bb81f805e0fe7ac93942dc94764aee9f492quay.io/gitops-cookbook/tekton-greeter:latest
[deploy : kubectl] deployment.apps/tekton-greeter created
提示

您可以使用 Tekton 仪表板创建和可视化您正在运行的任务和流水线,如图 6-8 所示。

Tekton 仪表板 Pipeline 运行

图 6-8. Tekton 仪表板任务运行

参见

6.8 使用 Tekton 触发器在 Git 发生变化时自动编译和打包应用程序

问题

当 Git 发生变化时,您希望自动化您的 CI/CD 流水线。

解决方案

Tekton 触发器 是 Tekton 组件,为 Tekton 的任务和流水线带来自动化。它对于云原生的 CI/CD 的 GitOps 策略是一个有趣的功能,因为它支持来自各种源(如 Git 事件(Git push 或 pull 请求))的外部事件。

大多数 Git 仓库服务器支持 Webhook 的概念,在代码仓库发生变化时通过 HTTP(S) 调用外部源。Tekton 提供了一个 API 端点,支持从远程系统接收挂钩以触发构建。通过将代码库的挂钩指向 Tekton 资源,可以实现自动化的代码/构建/部署流水线。

我们在 Recipe 6.1 中讨论的 Tekton 触发器的安装引入了一组 CRD,用于管理任务和流水线的事件处理。在这个示例中,我们将使用如下内容,也在 图 6-9 中有所说明:

Tekton 触发器

图 6-9. Tekton 触发器

TriggerTemplate

一个用于新创建资源的模板。它支持参数以创建特定的 PipelineRuns

TriggerBinding

验证事件并提取负载字段。

EventListener

TriggerBindingsTriggerTemplates 连接到可寻址的端点(事件接收器)。它使用从每个 TriggerBinding 中提取的事件参数(以及任何提供的静态参数)来创建指定在相应 TriggerTemplate 中指定的资源。它还可选地允许外部服务通过拦截器字段预处理事件负载。

在创建这些资源之前,您需要设置权限,以便 Tekton 触发器创建流水线和任务。您可以使用从 书籍的存储库 中提供的设置和以下命令:

kubectl apply \
-f https://raw.githubusercontent.com/gitops-cookbook/chapters/main/chapters/ch06/rbac.yaml

这将创建一个名为 tekton-triggers-sa 的新 ServiceAccount,该账号具有与 Tekton Pipelines 组件交互所需的权限。确认之前的命令后,您应该会得到以下输出:

serviceaccount/tekton-triggers-sa created
rolebinding.rbac.authorization.k8s.io/triggers-example-eventlistener-binding configured
clusterrolebinding.rbac.authorization.k8s.io/triggers-example-eventlistener-clusterbinding configured

现在,你可以像我们在 Recipe 6.7 中定义的那样为你的流水线添加自动化,创建这三个资源:

apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
  name: tekton-greeter-triggertemplate
spec:
  params:
    - name: git-revision
    - name: git-commit-message
    - name: git-repo-url
    - name: git-repo-name
    - name: content-type
    - name: pusher-name
  resourcetemplates:
    - apiVersion: tekton.dev/v1beta1
      kind: PipelineRun
      metadata:
        labels:
          tekton.dev/pipeline: tekton-greeter-pipeline-hub
        name: tekton-greeter-pipeline-webhook-$(uid)
      spec:
        params:
          - name: GIT_REPO
            value: $(tt.params.git-repo-url)
          - name: GIT_REF
            value: $(tt.params.git-revision)
        serviceAccountName: tekton-triggers-example-sa
        pipelineRef:
          name: tekton-greeter-pipeline-hub
        workspaces:
        - name: app-source
          persistentVolumeClaim:
            claimName: app-source-pvc
        - name: maven-settings
          emptyDir: {}
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
  name: tekton-greeter-triggerbinding
spec:
  params:
  - name: git-repo-url
    value: $(body.repository.clone_url)
  - name: git-revision
    value: $(body.after)
apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
  name: tekton-greeter-eventlistener
spec:
  serviceAccountName: tekton-triggers-example-sa
  triggers:
  - bindings:
    - ref: tekton-greeter-triggerbinding
    template:
      ref: tekton-greeter-triggertemplate

你可以按照以下步骤创建刚才列出的资源:

kubectl create -f tekton-greeter-triggertemplate.yaml
kubectl create -f tekton-greeter-triggerbinding.yaml
kubectl create -f tekton-greeter-eventlistener.yaml

你应该会得到以下的输出:

triggertemplate.triggers.tekton.dev/tekton-greeter-triggertemplate created
triggerbinding.triggers.tekton.dev/tekton-greeter-triggerbinding created
eventlistener.triggers.tekton.dev/tekton-greeter-eventlistener created

上下文中,创建了一个代表 EventListener 的新 pod:

kubectl get pods

你应该会得到类似以下的输出:

NAME                                            READY  STATUS   RESTARTS AGE
el-tekton-greeter-eventlistener-5db7b9fcf9-6nrgx  1/1  Running  0        10s

EventListener 容器在指定端口监听事件,并绑定到一个 Kubernetes 服务:

kubectl get svc

你应该会得到类似以下的输出:

NAME                              TYPE        CLUSTER-IP      EXTERNAL-IP↳
   PORT(S)             AGE
el-tekton-greeter-eventlistener   ClusterIP   10.100.36.199   <none>     ↳
   8080/TCP,9000/TCP   10s
...

如果你的 Git 服务器运行在集群外(例如 GitHub 或 GitLab),你需要暴露服务,例如通过 Ingress。然后,你可以使用关联到你的 Ingress 的 EventListener URL 在你的 Git 服务器上配置 Webhook。

提示

使用 Minikube,你可以通过以下命令为 Ingress 添加支持:minikube addons enable ingress。然后,你需要为 Ingress 映射一个主机名。

本书示例中,我们可以模拟 Webhook,就像它从 Git 服务器传入一样。

首先,你可以通过以下命令将 EventListener 服务映射到本地网络:

kubectl port-forward svc/el-tekton-greeter-eventlistener 8080

然后,你可以通过使用 curlhttp://localhost:8080 发送 HTTP 请求来调用触发器。HTTP 请求必须是一个包含 JSON 负载的 POST 请求,并且它应该包含通过 TriggerBinding 引用的字段,例如 body.repository.clone_urlbody.after

注意

检查你的 Git 服务器文档,获取 Webhook 可以生成的参数列表。在这个示例中,我们使用了 GitHub Webhooks 参考文档

要测试触发器,请运行以下命令:

curl -X POST \
  http://localhost:8080 \
  -H 'Content-Type: application/json' \
  -d '{ "after": "d9291c456db1ce29177b77ffeaa9b71ad80a50e6", "repository": { "clone_url" : "https://github.com/gitops-cookbook/tekton-tutorial-greeter.git" } }'

你应该会得到类似以下的输出:

{"eventListener":"tekton-greeter-eventlistener","namespace":"default","eventListenerUID":"c00567eb-d798-4c4a-946d-f1732fdfc313","eventID":"17dd25bb-a1fe-4f84-8422-c3abc5f10066"}

现在启动了一个新的流水线,你可以使用以下命令检查它:

tkn pipelinerun ls

你应该会看到它处于以下的Running状态:

tekton-greeter-pipeline-3244b67f-31d3-4597-af1c-3c1aa6693719   4 seconds ago   ---        Running

参见

6.9 使用 Kustomize 更新 Kubernetes 资源并将更改推送到 Git

问题

你想在 Tekton 流水线中使用 Kustomize 自动化更新 Kubernetes 清单。

解决方案

正如我们在 第四章 中讨论的,Kustomize 是一个管理 Kubernetes 清单的强大工具。Kustomize 能够在不分叉的情况下添加、删除或修补配置选项。在 Recipe 4.2 中,你看到了如何使用 kustomize CLI 更新 Kubernetes 部署的新容器镜像哈希。

在这个示例中,你将看到如何使用 Kustomize 让 Tekton 更新它。这对于 GitOps 非常有用,因为它允许在 Git 上自动更新描述在 Kubernetes 上运行的应用程序的清单,有利于与 GitOps 工具(如 Argo CD)进行资源同步(参见 第七章)。

在采用 GitOps 方法时,通常会为 Kubernetes 清单拥有一个或多个仓库,以及一个或多个应用程序仓库。

因此,让我们介绍一个接受 Kubernetes 清单仓库作为参数,并且能够像在食谱 4.2 中看到的那样更新容器镜像引用的任务:

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  annotations:
    tekton.dev/pipelines.minVersion: 0.12.1
    tekton.dev/tags: git
  name: git-update-deployment
  labels:
    app.kubernetes.io/version: '0.2'
    operator.tekton.dev/provider-type: community
spec:
  description: >-
    This Task can be used to update image digest in a Git repo using kustomize.
    It requires a secret with credentials for accessing the git repo.
  params:
    - name: GIT_REPOSITORY
      type: string
    - name: GIT_REF
      type: string
    - name: NEW_IMAGE
      type: string
    - name: NEW_DIGEST
      type: string
    - name: KUSTOMIZATION_PATH
      type: string
  results:
    - description: The commit SHA
      name: commit
  steps:
    - image: 'docker.io/alpine/git:v2.26.2'
      name: git-clone
      resources: {}
      script: >
        rm -rf git-update-digest-workdir

        git clone $(params.GIT_REPOSITORY) -b $(params.GIT_REF)
        git-update-digest-workdir
      workingDir: $(workspaces.workspace.path)
    - image: 'quay.io/wpernath/kustomize-ubi:latest'
      name: update-digest
      resources: {}
      script: >
        cd git-update-digest-workdir/$(params.KUSTOMIZATION_PATH)

        kustomize edit set image $(params.NEW_IMAGE)@$(params.NEW_DIGEST)

        echo "##########################"

        echo "### kustomization.yaml ###"

        echo "##########################"

        cat kustomization.yaml
      workingDir: $(workspaces.workspace.path)
    - image: 'docker.io/alpine/git:v2.26.2'
      name: git-commit
      resources: {}
      script: |
        cd git-update-digest-workdir

        git config user.email "tektonbot@redhat.com"
        git config user.name "My Tekton Bot"

        git status
        git add $(params.KUSTOMIZATION_PATH)/kustomization.yaml
        git commit -m "[ci] Image digest updated"

        git push

        RESULT_SHA="$(git rev-parse HEAD | tr -d '\n')"
        EXIT_CODE="$?"
        if [ "$EXIT_CODE" != 0 ]
        then
          exit $EXIT_CODE
        fi
        # Make sure we don't add a trailing newline to the result!
        echo -n "$RESULT_SHA" > $(results.commit.path)
      workingDir: $(workspaces.workspace.path)
  workspaces:
    - description: The workspace consisting of maven project.
      name: workspace

此任务由三个步骤组成:

git-clone

克隆 Kubernetes 清单仓库

update-digest

运行 kustomize 更新 Kubernetes 部署,使用作为参数给出的容器镜像哈希

git-commit

更新 Kubernetes 清单仓库以新的容器镜像哈希值

您可以使用以下命令创建该任务:

kubectl create -f git-update-deployment-task.yaml

您应该得到以下输出:

task.tekton.dev/git-update-deployment created

您现在可以将此任务添加到类似于食谱 6.7 中看到的 Pipeline 中,以便使用 Kustomize 自动更新您的清单:

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: pacman-pipeline
spec:
  params:
  - default: https://github.com/gitops-cookbook/pacman-kikd.git
    name: GIT_REPO
    type: string
  - default: master
    name: GIT_REVISION
    type: string
  - default: quay.io/gitops-cookbook/pacman-kikd
    name: DESTINATION_IMAGE
    type: string
  - default: .
    name: CONTEXT_DIR
    type: string
  - default: 'https://github.com/gitops-cookbook/pacman-kikd-manifests.git'
    name: CONFIG_GIT_REPO
    type: string
  - default: main
    name: CONFIG_GIT_REVISION
    type: string
  tasks:
  - name: fetch-repo
    params:
    - name: url
      value: $(params.GIT_REPO)
    - name: revision
      value: $(params.GIT_REVISION)
    - name: deleteExisting
      value: "true"
    taskRef:
      name: git-clone
    workspaces:
    - name: output
      workspace: app-source
  - name: build-app
    taskRef:
      name: maven
    params:
      - name: GOALS
        value:
          - -DskipTests
          - clean
          - package
      - name: CONTEXT_DIR
        value: "$(params.CONTEXT_DIR)"
    workspaces:
      - name: maven-settings
        workspace: maven-settings
      - name: source
        workspace: app-source
    runAfter:
      - fetch-repo
  - name: build-push-image
    taskRef:
      name: buildah
    params:
    - name: IMAGE
      value: "$(params.DESTINATION_IMAGE)"
    workspaces:
      - name: source
        workspace: app-source
    runAfter:
      - build-app
  - name: git-update-deployment
    params:
    - name: GIT_REPOSITORY
      value: $(params.CONFIG_GIT_REPO)
    - name: NEW_IMAGE
      value: $(params.DESTINATION_IMAGE)
    - name: NEW_DIGEST
      value: $(tasks.build-push-image.results.IMAGE_DIGEST) ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    - name: KUSTOMIZATION_PATH
      value: env/dev
    - name: GIT_REF
      value: $(params.CONFIG_GIT_REVISION)
    runAfter:
      - build-push-image
    taskRef:
      kind: Task
      name: git-update-deployment
    workspaces:
    - name: workspace
      workspace: app-source
  workspaces:
    - name: app-source
    - name: maven-settings

1

正如您从这个示例中所看到的,您可以将先前任务的结果作为下一个任务的输入。在这种情况下,由 build-push-image 任务生成的容器镜像的哈希用于使用 Kustomize 更新清单。

您可以使用以下命令创建 Pipeline:

kubectl create -f pacman-pipeline.yaml

您应该得到以下输出:

pipeline.tekton.dev/pacman-pipeline created

git-commit 步骤需要对您的 Git 服务器进行身份验证,以便将更新推送到仓库。由于此示例在 GitHub 上,我们使用了 GitHub 个人访问令牌(参见食谱 6.4),附加到 ServiceAccount tekton-bot-sa

确保按照食谱 6.4 和 6.5 的说明添加仓库和注册表的 Kubernetes 密钥:

kubectl patch serviceaccount tekton-bot-sa -p '{"secrets": [{"name": "git-secret"}]}'
kubectl patch serviceaccount tekton-bot-sa \
 -p '{"secrets": [{"name": "containerregistry-
secret"}]}'
注意

确保您已按照食谱 6.7 中定义的方式为 Pipeline 创建了一个 PVC。

现在您可以按以下方式启动 Pipeline:

tkn pipeline start pacman-pipeline \
  --serviceaccount='tekton-bot-sa' \
  --param GIT_REPO='https://github.com/gitops-cookbook/pacman-kikd.git' \
  --param GIT_REVISION='main' \
  --param DESTINATION_IMAGE='quay.io/gitops-cookbook/pacman-kikd:latest' \
  --param CONFIG_GIT_REPO='https://github.com/gitops-cookbook/pacman-kikd-manifests.git' \
  --param CONFIG_GIT_REVISION='main' \
  --workspace name=app-source,claimName=app-source-pvc \
  --workspace name=maven-settings,emptyDir="" \
  --use-param-defaults \
  --showlog

6.10 使用 Helm 更新 Kubernetes 资源并创建拉取请求

问题

您希望通过 Tekton Pipeline 自动化 Helm 打包的应用程序的部署。

解决方案

在第五章中,我们讨论了 Helm 及其如何便捷地管理运行在 Kubernetes 上的应用程序。在此食谱中,您将看到如何通过 Pipeline 自动化 Helm 驱动的部署,以安装或更新运行在 Kubernetes 上的应用程序。

如食谱 6.7 所示,您可以使用 Tekton Hub 查找并安装 Tekton 任务。实际上,您可以使用helm-upgrade-from-repo任务来为您的 Pipeline 提供 Helm 支持。

要安装它,请运行以下命令:

tkn hub install task helm-upgrade-from-repo

此任务可以从 Helm 仓库安装 Helm 图表。在本示例中,我们在本书的仓库提供了一个 Helm 仓库,您可以使用以下命令添加:

helm repo add gitops-cookbook https://gitops-cookbook.github.io/helm-charts/

您应该得到以下输出:

"gitops-cookbook" has been added to your repositories

您可以使用以下命令安装 Helm 图表:

helm install pacman gitops-cookbook/pacman

您应该得到类似以下的输出:

NAME: pacman
LAST DEPLOYED: Mon Aug 15 17:02:21 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
USER-SUPPLIED VALUES:
{}

应用现在应该已经部署并在 Kubernetes 上运行:

kubectl get pods -l=app.kubernetes.io/name=pacman

您应该得到以下输出:

NAME                      READY   STATUS    RESTARTS   AGE
pacman-6798d65d84-9mt8p   1/1     Running   0          30s

现在让我们使用 Tekton 任务更新部署,运行一个带有以下 TaskRunhelm upgrade

apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
  generateName: helm-pacman-run-
spec:
  serviceAccountName: tekton-deployer-sa ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  taskRef:
    name: helm-upgrade-from-repo
  params:
  - name: helm_repo
    value: https://gitops-cookbook.github.io/helm-charts/
  - name: chart_name
    value: gitops-cookbook/pacman
  - name: release_version
    value: 0.1.0
  - name: release_name
    value: pacman
  - name: overwrite_values
    value: replicaCount=2 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

helm-upgrade-from-repo 任务需要在工作命名空间中列出对象的权限,因此您需要一个带有特殊权限的 ServiceAccount,如 Recipe 6.6 中所示。

2

您可以通过在此参数中设置来覆盖 Chart 的 values.yaml 文件中的值。在这里,我们为 Pac-Man 游戏设置了两个副本。

使用以下命令运行任务:

kubectl create -f helm-pacman-taskrun.yaml

您应该得到类似以下的输出:

taskrun.tekton.dev/helm-pacman-run-qghx8 created

使用 tkn CLI 检查日志并选择正在运行的任务:

tkn taskrun logs -f

您应该得到类似以下的输出,其中您可以看到 Helm 升级已成功执行:

[upgrade-from-repo] current installed helm releases
[upgrade-from-repo] NAME        NAMESPACE       REVISION        UPDATED                                         STATUS          CHART           APP VERSION
[upgrade-from-repo] pacman      default         1               2022-08-15 17:02:21.633934129 +0200 +0200       deployed        pacman-0.1.0    1.0.0
[upgrade-from-repo] parsing helms repo name...
[upgrade-from-repo] adding helm repo...
[upgrade-from-repo] "gitops-cookbook" has been added to your repositories
[upgrade-from-repo] adding updating repo...
[upgrade-from-repo] Hang tight while we grab the latest from your chart repositories...
[upgrade-from-repo] ...Successfully got an update from the "gitops-cookbook" chart repository
[upgrade-from-repo] Update Complete. ⎈Happy Helming!⎈
[upgrade-from-repo] installing helm chart...
[upgrade-from-repo] history.go:56: [debug] getting history for release pacman
[upgrade-from-repo] upgrade.go:123: [debug] preparing upgrade for pacman
[upgrade-from-repo] upgrade.go:131: [debug] performing update for pacman
[upgrade-from-repo] upgrade.go:303: [debug] creating upgraded release for pacman
[upgrade-from-repo] client.go:203: [debug] checking 2 resources for changes
[upgrade-from-repo] client.go:466: [debug] Looks like there are no changes for Service "pacman"
[upgrade-from-repo] wait.go:47: [debug] beginning wait for 2 resources with timeout of 5m0s
[upgrade-from-repo] ready.go:277: [debug] Deployment is not ready: default/pacman. 1 out of 2 expected pods are ready
[upgrade-from-repo] ready.go:277: [debug] Deployment is not ready: default/pacman. 1 out of 2 expected pods are ready
[upgrade-from-repo] ready.go:277: [debug] Deployment is not ready: default/pacman. 1 out of 2 expected pods are ready
[upgrade-from-repo] upgrade.go:138: [debug] updating status for upgraded release for pacman
[upgrade-from-repo] Release "pacman" has been upgraded. Happy Helming!
[upgrade-from-repo] NAME: pacman
[upgrade-from-repo] LAST DEPLOYED: Mon Aug 15 15:23:31 2022
[upgrade-from-repo] NAMESPACE: default
[upgrade-from-repo] STATUS: deployed
[upgrade-from-repo] REVISION: 2
[upgrade-from-repo] TEST SUITE: None
[upgrade-from-repo] USER-SUPPLIED VALUES:
[upgrade-from-repo] replicaCount: 2
[upgrade-from-repo]
[upgrade-from-repo] COMPUTED VALUES:
[upgrade-from-repo] image:
[upgrade-from-repo]   containerPort: 8080
[upgrade-from-repo]   pullPolicy: Always
[upgrade-from-repo]   repository: quay.io/gitops-cookbook/pacman-kikd
[upgrade-from-repo]   tag: 1.0.0
[upgrade-from-repo] replicaCount: 2
[upgrade-from-repo] securityContext: {}
[upgrade-from-repo]
[upgrade-from-repo] HOOKS:
[upgrade-from-repo] MANIFEST:
[upgrade-from-repo] ---
[upgrade-from-repo] # Source: pacman/templates/service.yaml
[upgrade-from-repo] apiVersion: v1
[upgrade-from-repo] kind: Service
[upgrade-from-repo] metadata:
[upgrade-from-repo]   labels:
[upgrade-from-repo]     app.kubernetes.io/name: pacman
[upgrade-from-repo]   name: pacman
[upgrade-from-repo] spec:
[upgrade-from-repo]   ports:
[upgrade-from-repo]     - name: http
[upgrade-from-repo]       port: 8080
[upgrade-from-repo]       targetPort: 8080
[upgrade-from-repo]   selector:
[upgrade-from-repo]     app.kubernetes.io/name: pacman
[upgrade-from-repo] ---
[upgrade-from-repo] # Source: pacman/templates/deployment.yaml
[upgrade-from-repo] apiVersion: apps/v1
[upgrade-from-repo] kind: Deployment
[upgrade-from-repo] metadata:
[upgrade-from-repo]   name: pacman
[upgrade-from-repo]   labels:
[upgrade-from-repo]     app.kubernetes.io/name: pacman
[upgrade-from-repo]     app.kubernetes.io/version: "1.0.0"
[upgrade-from-repo] spec:
[upgrade-from-repo]   replicas: 2
[upgrade-from-repo]   selector:
[upgrade-from-repo]     matchLabels:
[upgrade-from-repo]       app.kubernetes.io/name: pacman
[upgrade-from-repo]   template:
[upgrade-from-repo]     metadata:
[upgrade-from-repo]       labels:
[upgrade-from-repo]         app.kubernetes.io/name: pacman
[upgrade-from-repo]     spec:
[upgrade-from-repo]       containers:
[upgrade-from-repo]           - image: "quay.io/gitops-cookbook/pacman-kikd:1.0.0"
[upgrade-from-repo]             imagePullPolicy: Always
[upgrade-from-repo]             securityContext:
[upgrade-from-repo]               {}
[upgrade-from-repo]             name: pacman
[upgrade-from-repo]             ports:
[upgrade-from-repo]               - containerPort: 8080
[upgrade-from-repo]                 name: http
[upgrade-from-repo]                 protocol: TCP
[upgrade-from-repo]
kubectl get deploy -l=app.kubernetes.io/name=pacman
pacman              2/2     2            2           9s

6.11 使用 Drone 创建 Kubernetes 流水线

问题

您想要为 Kubernetes 创建一个 CI/CD 流水线,使用 Drone。

解决方案

Drone 是一个开源项目,用于云原生持续集成(CI)。它使用 YAML 构建文件来定义和执行容器内的构建流水线。

它有两个主要组件:

服务器

集成流行的 SCM 工具,如 GitHub、GitLab 或 Gitea

运行者

作为运行在特定平台上的代理

您可以按照 文档 安装您选择的服务器,并安装 Kubernetes Runner

在此示例中,您将使用 Pac-Man 应用程序创建一个基于 Java Maven 的流水线。首先,为您的操作系统安装 Drone CLI;您可以从官方网站 这里 获取。

提示

在 macOS 上,通过 Homebrew 安装 drone 如下所示:

brew tap drone/drone && brew install drone

然后配置 Drone,从 Drone 账户设置页面复制 DRONE_TOKEN,然后创建/更新名为 .envrc.local 的文件,并添加变量进行覆盖:

export DRONE_TOKEN="<YOUR-TOKEN>"

确保已加载令牌:

drone info

现在在 Drone 中激活仓库:

drone repo enable https://github.com/gitops-cookbook/pacman-kikd.git

类似于 Tekton,Drone 的流水线将编译、测试并构建应用程序。然后它将创建并推送容器镜像到注册表。

将凭据添加到您的容器注册表中,如下所示(这里我们使用的是 Quay.io):

drone secret add --name image_registry \
--data quay.io https://github.com/gitops-cookbook/pacman-kikd.git

drone secret add --name image_registry_user \
--data YOUR_REGISTRY_USER https://github.com/gitops-cookbook/pacman-kikd.git

drone secret add --name image_registry_password \
--data YOUR_REGISTRY_PASS https://github.com/gitops-cookbook/pacman-kikd.git

drone secret add --name destination_image \
--data quay.io/YOUR_REGISTRY_USER>/pacman-kikd.git https://github.com/gitops-cookbook/pacman-kikd.git

创建名为 .drone.yaml 的文件如下所示:

kind: pipeline
type: docker
name: java-pipeline
platform:
  os: linux
  arch: arm64
trigger:
  branch:
    - main
clone:
  disable: true
steps:
  - name: clone sources
    image: alpine/git
    pull: if-not-exists
    commands:
      - git clone https://github.com/gitops-cookbook/pacman-kikd.git .
      - git checkout $DRONE_COMMIT
  - name: maven-build
    image: maven:3-jdk-11
    commands:
      - mvn install -DskipTests=true -B
      - mvn test -B
  - name: publish
    image: plugins/docker:20.13
    pull: if-not-exists
    settings:
      tags: "latest"
      dockerfile: Dockerfile
      insecure: true
      mtu: 1400
      username:
        from_secret: image_registry_user
      password:
        from_secret: image_registry_password
      registry:
        from_secret: image_registry
      repo:
        from_secret: destination_image

启动流水线:

drone exec --pipeline=java-pipeline
提示

您还可以通过将代码推送到您的 Git 仓库来触发流水线的启动。

参见

6.12 使用 GitHub Actions 进行 CI

问题

您想要使用 GitHub Actions 进行 CI,以编译和打包准备部署到 CD 的容器镜像。

解决方案

GitHub Actions 是针对任何 GitHub 仓库提供的事件驱动自动化任务。事件会自动触发工作流程,其中包含一个作业。该作业然后使用步骤来控制操作运行的顺序。这些操作是自动化软件构建、测试和部署的命令。

在此示例中,您将添加一个 GitHub Action 来构建 Pac-Man 游戏的容器映像,并将其推送到GitHub 容器注册表

提示

由于 GitHub Actions 与存储库连接,您可以从本书的代码存储库中分叉 Pac-Man 存储库以添加您的 GitHub Actions。有关此主题的更多信息,请参阅有关分叉存储库的文档。

GitHub Actions 工作流程进入环境,它们可以引用一个环境以使用该环境的保护规则和秘密。

使用包含所有必要步骤的 YAML 文件来定义工作流程和作业。在您的存储库内,您可以创建路径为.github/workflows/pacman-ci-action.yml的文件:

# This is a basic workflow to help you get started with Actions

name: pacman-ci-action ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

env: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }}
  REGISTRY_USER: ${{ github.actor }}
  REGISTRY_PASSWORD: ${{ github.token }}
  APP_NAME: pacman
  IMAGE_TAGS: 1.0.0 ${{ github.sha }}

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the
  # "main" branch
  push: ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in
# parallel
jobs:
  # This workflow contains a single job called "build-and-push"
  build-and-push: ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the
    # job
    steps: ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can
      # access it
      - uses: actions/checkout@v3

      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'adopt'
          cache: maven

      - name: Build with Maven
        run: mvn --batch-mode package

      - name: Buildah Action ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)
        id: build-image
        uses: redhat-actions/buildah-build@v2
        with:
          image: ${{ env.IMAGE_REGISTRY }}/${{ env.APP_NAME }}
          tags: ${{ env.IMAGE_TAGS }}
          containerfiles: |
            ./Dockerfile
      - name: Push to Registry ![7](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/7.png)
        id: push-to-registry
        uses: redhat-actions/push-to-registry@v2
        with:
          image: ${{ steps.build-image.outputs.image }}
          tags: ${{ steps.build-image.outputs.tags }}
          registry: ${{ env.IMAGE_REGISTRY }}
          username: ${{ env.REGISTRY_USER }}
          password: ${{ env.REGISTRY_PASSWORD }}

1

操作名称。

2

将在工作流程中使用的环境变量。这包括默认环境变量和您添加到环境中的秘密。

3

这里是您定义工作流触发器类型的地方。在这种情况下,对存储库的任何更改(Push)到master分支将触发动作开始。查看触发器的完整列表以获取更多信息。

4

该作业的名称。

5

步骤列表;每个步骤包含管道中的一个动作。

6

Buildah 构建。此动作使用 Buildah 构建容器映像。

7

推送到注册表的动作。此操作用于使用 GitHub 仓库所有者的内置凭据推送到 GitHub 注册表。

每次 Git 推送或拉取请求后,动作将执行新的运行,如图 6-10 所示。

注意

GitHub 提供其自己的容器注册表,位于 ghcr.io,容器映像作为GitHub Packages的一部分引用。默认情况下,这些映像是公开的。请参阅本书的存储库作为参考。

GitHub Actions 环境

图 6-10. GitHub Actions 作业

参见

^(1) 参阅Tekton 文档

第七章:Argo CD

在前一章中,您学习了有关 Tekton 和其他引擎(如 GitHub Actions)如何实现项目的持续集成(CI)部分。

虽然 CI 很重要,因为这是构建应用程序并检查是否存在故障(运行单元测试、组件测试等)的地方,但仍有一个缺失的部分:如何使用 GitOps 方法论将此应用程序部署到环境(一个 Kubernetes 集群),而不是创建一个运行 kubectl/helm 命令的脚本。

正如 Daniel Bryant 所说:“如果您以前没有使用 SSH 在生产环境中部署应用程序,请不要使用 kubectl 在 Kubernetes 中执行此操作。”

在本章中,我们将介绍 Argo CD,这是一个用于 Kubernetes 的声明式 GitOps 连续交付(CD)工具。在本节的第一部分中,我们将看到如何使用 Argo CD 部署应用程序(Recipes 7.1 和 7.2)。

Argo CD 不仅支持普通 Kubernetes 清单的部署,还支持 Kustomize 项目(Recipe 7.3)和 Helm 项目(Recipe 7.4)的部署。

Kubernetes 中典型的操作是对容器的新版本进行滚动更新,Argo CD 与另一个工具集成,使此过程更加顺畅(Recipe 7.5)。

交付复杂的应用程序可能需要在何时以及如何部署和发布应用程序时进行一些编排(Recipes 7.7 和 7.8)。

我们将看到如何:

  • 安装并部署第一个应用程序。

  • 使用自动部署和自愈应用程序。

  • 当发布新容器时执行滚动更新。

  • 给出执行顺序。

在本章中,我们使用 https://github.com/gitops-cookbook/gitops-cookbook-sc.git GitHub 仓库作为源目录。要在本章中成功运行它,您应该 fork 它并在提供的示例 YAML 文件中使用它。

7.1 使用 Argo CD 部署应用程序

问题

您希望 Argo CD 部署在 Git 仓库中定义的应用程序。

解决方案

创建一个 Application 资源来设置 Argo CD 以部署该应用程序。

要安装 Argo CD,请创建 argocd 命名空间并应用 Argo CD 安装清单:

kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/v2.3.4/manifests/install.yaml

让我们让 Argo CD 部署一个简单的 Web 应用程序,显示一个配置了颜色的方框。该应用由三个 Kubernetes 清单文件组成,包括一个 Namespace、一个 Deployment 和一个 Service 定义。

这些文件位于书的存储库中的 ch07/bgd 文件夹中。

所有这些文件在 Argo CD 中被称为一个 Application。因此,您必须将其定义为这样的文件,以将这些清单应用到您的集群中。

让我们检查用于部署应用程序的 Argo CD Application 资源文件:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bgd-app
  namespace: argocd ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
spec:
  destination: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    namespace: bgd
    server: https://kubernetes.default.svc 
  project: default ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  source:
    repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    path: ch07/bgd ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
    targetRevision: main ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)

1

安装 Argo CD 的命名空间

2

目标集群和命名空间

3

在 Argo CD 的默认项目中安装应用程序

4

YAML 所在的清单仓库

5

查找清单的路径

6

要检出的分支

在终端窗口中,运行以下命令注册 Argo CD 应用程序:

kubectl apply -f manual-bgd-app.yaml

此时,应用程序已注册为 Argo CD 应用程序。

您可以使用argocd或 UI 来检查状态;也可以使用 CLI 运行以下命令来列出应用程序:

argocd app list

输出如下:

NAME     CLUSTER                         NAMESPACE  PROJECT  STATUS
bgd-app  https://kubernetes.default.svc  bgd        default  OutOfSync

这里重要的字段是STATUS。它是OutOfSync,这意味着应用程序已注册,当前状态(在本例中为未部署任何应用程序)与 Git 存储库中的内容(应用程序部署文件)之间存在偏差。

如果从bgd命名空间获取所有 Pod,您会注意到没有正在运行的 Pod:

kubectl get pods -n bgd

No resources found in bgd namespace.

Argo CD 默认不会自动同步应用程序。它只显示差异,用户可以通过触发同步操作来修复。

使用 CLI,在终端中运行以下命令来同步应用程序:

argocd app sync bgd-app

命令的输出显示了关于部署的所有重要信息:

Name:               bgd-app
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          bgd
URL:                https://openshift-gitops-server-openshift-gitops.apps.openshift.sotogcp.com/applications/bgd-app
Repo:               https://github.com/lordofthejars/gitops-cookbook-sc.git
Target:             main
Path:               ch07/bgd
SyncWindow:         Sync Allowed
Sync Policy:        <none>
Sync Status:        Synced to main (384cd3d)
Health Status:      Progressing

Operation:          Sync
Sync Revision:      384cd3d21c534e75cb6b1a6921a6768925b81244
Phase:              Succeeded
Start:              2022-06-16 14:45:12 +0200 CEST
Finished:           2022-06-16 14:45:13 +0200 CEST
Duration:           1s
Message:            successfully synced (all tasks run)

GROUP  KIND        NAMESPACE  NAME  STATUS   HEALTH       HOOK  MESSAGE
       Namespace   bgd        bgd   Running  Synced             namespace/bgd created
       Service     bgd        bgd   Synced   Healthy            service/bgd created
apps   Deployment  bgd        bgd   Synced   Progressing        deployment.apps/bgd created
       Namespace              bgd   Synced

您也可以通过单击 UI 中显示的SYNC按钮来同步应用程序,如图 7-1 所示。

同步按钮用于同步应用程序

图 7-1. Argo CD Web 控制台

如果从bgd命名空间获取所有 Pod,您会注意到有一个 Pod 正在运行:

kubectl get pods -n bgd

NAME                   READY   STATUS    RESTARTS   AGE
bgd-788cb756f7-jll9n   1/1     Running   0          60s

同样适用于服务:

kubectl get services -n bgd

NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
bgd    ClusterIP   172.30.35.199   <none>        8080:32761/TCP ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

暴露的端口为 32761

在接下来的章节中,您需要访问部署的服务以验证其部署情况。有几种方法可以访问部署到 Minikube 的服务;在接下来的章节中,我们使用 Minikube IP 和服务的暴露端口。

在终端窗口中运行以下命令以获取 Minikube IP:

minikube ip -p gitops
192.168.59.100

打开浏览器窗口,设置前面的 IP,后面跟上暴露的端口(例如192.168.59.100:32761),并访问服务以验证盒子中圆圈的颜色是否为蓝色,如图 7-2 所示。

蓝色框应用

图 7-2. 已部署的应用程序

讨论

现在是更新应用程序部署文件的时候了。这次我们将更改bgd-deployment.yaml文件中定义的环境变量的值。

在文件编辑器中打开ch07/bgd/bgd-deployment.yaml,将COLOR环境变量的值从blue改为green

spec:
  containers:
  - image: quay.io/redhatworkshops/bgd:latest
    name: bgd
    env:
    - name: COLOR
      value: "green"

在终端中运行以下命令来提交并推送文件,以便 Argo CD 可以使用更改:

git add .
git commit -m "Updates color"

git push origin main

推送更改后,再次检查应用程序的状态:

argocd app list

NAME     CLUSTER                         NAMESPACE  PROJECT  STATUS
bgd-app  https://kubernetes.default.svc  bgd        default  Sync

我们看到应用程序状态为Sync。这是因为 Argo CD 使用轮询方法检测部署和 Git 定义之间的差异。一段时间后(默认为 3 分钟),应用程序状态将为OutOfSync

argocd app list
NAME     CLUSTER                         NAMESPACE  PROJECT  STATUS     HEALTH
bgd-app  https://kubernetes.default.svc  bgd        default  OutOfSync  Healthy

要同步更改,请运行sync子命令:

argocd app sync bgd-app

几秒钟后,访问服务,并验证圆圈是否为绿色,如图 7-3 所示。

绿色方框应用

图 7-3. 已部署的应用程序

要删除应用程序,请使用 CLI 工具或 UI:

argocd app delete bgd-app

同时,撤消 Git 仓库中所做的更改,以获取应用程序的初始版本并推送它们:

git revert HEAD

git push origin main

7.2 自动同步

问题

您希望 Argo CD 在有更改时自动更新资源。

解决方案

使用具有automated策略的syncPolicy部分。

当 Argo CD 检测到 Git 中的清单与 Kubernetes 集群中定义的差异时,它可以自动同步应用程序。

自动同步的好处是无需登录到 Argo CD API,避免了涉及的安全问题(管理秘钥、网络等)和使用argocd工具。相反,当清单发生变化并推送到 Git 仓库时,跟踪 Git 仓库的清单会自动应用。

让我们修改先前的 Argo CD 清单文件(Application),添加sync​Po⁠licy部分,以便自动部署更改:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bgd-app
  namespace: argocd
spec:
  destination:
    namespace: bgd
    server: https://kubernetes.default.svc 
  project: default 
  source: 
    path: ch07/bgd
    repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
    targetRevision: main
  syncPolicy: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    automated: {} ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

启动同步策略配置部分

2

Argo CD 会自动同步仓库

在这一点上,我们可以通过运行以下命令将Application文件应用到运行中的集群中:

kubectl apply -f bgd/bgd-app.yaml

现在,Argo CD 部署应用程序,而无需执行任何其他命令。

运行kubectl命令或在 Argo CD UI 中检查,验证部署正在进行:

kubectl get pods -n bgd

NAME                   READY   STATUS    RESTARTS   AGE
bgd-788cb756f7-jll9n   1/1     Running   0          60s

访问服务,并验证圆圈是否为蓝色,如图 7-4 所示。

蓝色方框应用

图 7-4. 已部署的应用程序

要删除应用程序,请使用 CLI 工具或 UI:

argocd app delete bgd-app

讨论

尽管 Argo CD 会自动部署应用程序,但出于安全考虑,它使用一些默认的保守策略。

其中两个是删除已删除资源的修剪和应用程序的自愈,以防在 Kubernetes 集群中直接进行更改而不是通过 Git。

默认情况下,当 Argo CD 检测到 Git 中不再可用时,它不会删除(修剪)任何资源,并且资源状态将是OutOfSync。如果希望 Argo CD 删除这些资源,可以通过两种方式实现。

第一种方法是手动使用-prune选项调用同步:

argocd app sync --prune

第二种方法是通过在automated部分中将prune属性设置为true,让 Argo CD 自动删除修剪资源:

syncPolicy:
  automated:
    prune: true ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

启用自动修剪

另一个影响应用程序自动更新方式的重要概念是自我修复。

Argo CD 配置为不纠正手动在集群中进行的任何漂移。例如,Argo CD 允许在集群中直接执行 kubectl patch 改变应用程序的任何配置参数。

让我们看看它的效果。

圆圈的颜色设定为环境变量(COLOR)。

现在,让我们使用 kubectl patch 命令将 COLOR 环境变量改为 green

在终端中运行以下命令:

kubectl -n bgd patch deploy/bgd \
--type='json' -p='[{"op": "replace", "path": "/
spec/template/spec/containers/0/env/0/value", "value":"green"}]'

等待部署完成:

kubectl rollout status deploy/bgd -n bgd

如果刷新浏览器,您应该看到现在是绿色圆圈,如 图 7-5 所示。

绿色方框应用程序

图 7-5. 已部署的应用程序

查看 Argo CD 的同步状态,您会看到它处于 OutOfSync 状态,因为应用程序与 Git 仓库中定义的定义(COLOR: blue)不一致。

由于 selfHeal 属性的默认设置为 false,Argo CD 不会回滚以纠正此漂移。

让我们移除该应用程序并部署一个新的,但在这种情况下将 selfHeal 设置为 true

argocd app delete bgd-app

让我们启用 selfHealing 属性,并重复实验:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bgd-app
  namespace: argocd
spec:
  destination:
    namespace: bgd
    server: https://kubernetes.default.svc 
  project: default 
  source: 
    path: ch07/bgd
    repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
    targetRevision: main
  syncPolicy:
    automated:
      prune: true
      selfHeal: true ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

selfHeal 设置为 true 以纠正任何漂移

然后在终端应用该资源:

kubectl apply -f bgd/heal-bgd-app.yaml

重复之前的步骤:

  1. 打开浏览器检查圆圈是否为蓝色。

  2. 重新执行 kubectl -n bgd patch deploy/bgd ... 命令。

  3. 刷新浏览器并检查圆圈是否仍然是蓝色。

Argo CD 通过 patch 命令来纠正引入的漂移,将应用程序同步到 Git 仓库中定义的正确状态。

要移除该应用程序,请使用 CLI 工具或 UI:

argocd app delete bgd-app

参见

7.3 Kustomize 集成

问题

您希望使用 Argo CD 部署 Kustomize 清单。

解决方案

Argo CD 支持多种定义 Kubernetes 清单的方式:

  • Kustomize

  • Helm

  • Ksonnet

  • Jsonnet

您也可以将支持的方式扩展到自定义的方式,但这超出了本书的范围。

如果存在以下任一文件,则 Argo CD 将检测到 Kustomize 项目:kustomization.yamlkustomization.ymlKustomization

让我们部署相同的 BGD 应用程序,但这次使用 Kustomize 清单进行部署。

此外,我们将设置 kustomize 以覆盖 COLOR 环境变量为黄色。

仓库中定义的 Kustomize 文件如下所示:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: bgdk
resources:
- ../base ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
- bgdk-ns.yaml ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
patchesJson6902: ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  - target: ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
      version: v1
      group: apps
      kind: Deployment
      name: bgd
      namespace: bgdk
    patch: |- ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
      - op: replace
        path: /spec/template/spec/containers/0/env/0/value
        value: yellow

1

包含标准部署文件的目录(蓝色圆圈)

2

用于创建命名空间的特定文件

3

补丁标准部署文件

4

对部署文件进行补丁

5

将环境变量值覆盖为 yellow

注意

您无需创建此文件,因为它已存储在 Git 存储库中。

创建以下Application文件以部署应用程序:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bgdk-app
  namespace: argocd
spec:
  destination:
    namespace: bgdk
    server: https://kubernetes.default.svc 
  project: default 
  source: 
    path: ch07/bgdk/bgdk
    repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
    targetRevision: main
  syncPolicy:
    automated: {}

此时,我们可以通过运行以下命令将Application文件应用于正在运行的集群:

kubectl apply -f bgdk/bgdk-app.yaml

访问服务,您会注意到圆圈是黄色而不是蓝色。

要删除应用程序,请使用 CLI 工具或 UI:

argocd app delete bgdk-app

讨论

我们可以明确指定使用哪个工具,覆盖 Argo CD 在Application文件中使用的默认算法。例如,我们可以根据kustomization.yaml文件的存在使用简单的目录策略:

source:
  directory: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    recurse: true

1

覆盖始终使用简单的目录策略

可能的策略包括:directorycharthelmkustomizepathplugin

提示

当使用 Argo CD 时,Kustomize 的所有已见内容都是有效的。

参见

7.4 Helm 集成

问题

您想使用 Argo CD 来部署 Helm 清单。

解决方案

当检测到部署目录中存在 Helm 项目(即Chart.yaml文件存在)时,Argo CD 支持将 Helm Charts 安装到集群中。

让我们部署相同的 BGD 应用程序,但在这种情况下,部署为 Helm 清单。

项目的布局是在您先前克隆的 GitHub 存储库中已经创建的一个简单的 Helm 布局:

├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── ns.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

创建bgdh/bgdh-app.yaml文件以定义 Argo CD 应用程序:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bgdh-app
  namespace: argocd
spec:
  destination:
    namespace: bgdh
    server: https://kubernetes.default.svc 
  project: default 
  source: 
    path: ch07/bgdh
    repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
    targetRevision: main
  syncPolicy:
    automated: {}

此时,我们可以通过运行以下命令将Application文件应用到正在运行的集群中:

kubectl apply -f bgdh/bgdh-app.yaml

验证 Pod 是否在bgdh命名空间中运行:

kubectl get pods -n bgdh

NAME                        READY   STATUS    RESTARTS   AGE
bgdh-app-556c46fcd6-ctfkf   1/1     Running   0          5m43s

要删除应用程序,请使用 CLI 工具或 UI:

argocd app delete bgdh-app

讨论

Argo CD 将构建环境变量填充到 Helm 清单中(实际上还支持 Kustomize、Jsonnet 和自定义工具)。

设置以下变量:

  • ARGOCD_APP_NAME

  • ARGOCD_APP_NAMESPACE

  • ARGOCD_APP_REVISION

  • ARGOCD_APP_SOURCE_PATH

  • ARGOCD_APP_SOURCE_REPO_URL

  • ARGOCD_APP_SOURCE_TARGET_REVISION

  • KUBE_VERSION

  • KUBE_API_VERSIONS

在以下代码片段中,您可以查看应用程序名称的使用:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bgdh-app
  namespace: openshift-gitops
spec:
  destination:
  ...
  source:
    path: ch07/bgd
    ...
    helm: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
      parameters: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      - name: app ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
        value: $ARGOCD_APP_NAME ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)

1

特定的 Helm 部分

2

设置额外的参数,与在values.yaml中设置它们一样,但更高优先级

3

参数的名称

4

参数的值,在本例中来自 Build Env var

Argo CD 可以使用不同的values.yaml文件或设置参数值来覆盖values.yaml中定义的值:

argocd app set bgdh-app --values new-values.yaml

argocd app set bgdh-app -p service.type=LoadBalancer

请注意,值文件必须与 Helm Chart 存储在同一个 Git 存储库中。

注意

Argo CD 还支持 Helm 钩子。

参见

7.5 镜像更新器

问题

您希望 Argo CD 在发布时自动部署容器镜像。

解决方案

使用 Argo CD Image Updater 检测容器注册表上的更改,并更新部署文件。

在开发过程中最重复的任务之一是部署容器镜像的新版本。

使用纯 Argo CD 解决方案后,将容器镜像发布到容器注册表后,我们需要更新指向新容器镜像的 Kubernetes/Kustomize/Helm 清单文件,并将结果推送到 Git 仓库。

此过程意味着:

  1. 克隆仓库

  2. 解析 YAML 文件并相应更新它们

  3. 提交并推送更改

在持续集成阶段为每个仓库定义这些样板任务。虽然这种方法有效,但可以自动化,以便集群能够检测到推送到容器注册表的新镜像,并更新指向更新版本的当前部署文件。

这正是 Argo CD Image Updater(ArgoCD IU)所做的。它是一个 Kubernetes 控制器,监控新的容器版本,并更新在 Argo CD Application 文件中定义的清单。

Argo CD IU 的生命周期及其与 Argo CD 的关系显示在 图 7-6 中。

Argo CD 和 Argo CD Image Updater 的生命周期

图 7-6. Argo CD Image Updater 生命周期

目前,Argo CD IU 仅更新 Kustomize 或 Helm 的清单。在 Helm 的情况下,需要支持使用参数(image.tag)指定镜像的标签。

让我们在与 Argo CD 相同的命名空间中安装控制器:

kubectl apply -f \
https://raw.githubusercontent.com/argoproj-labs/argocd-imageupdater/v0.12.0/manifests/install.yaml -n argocd

验证安装过程,检查控制器的 Pod 状态是否为 Running

kubectl get pods -n argocd

NAME                                                         READY   STATUS    RESTARTS   AGE
argocd-image-updater-59c45cbc5c-kjjtp                        1/1     Running   0          40h

在使用 Argo CD IU 之前,我们创建一个代表 Git 凭据的 Kubernetes Secret,以便更新的清单可以推送到仓库。该密钥必须在 Argo CD 命名空间中,并且在本例中,我们将其命名为 git-creds

kubectl -n argocd create secret generic git-creds \ --from-literal=username=<git_user> \
--from-literal=password=<git_password_or_token>

最后,让我们使用一些特殊的注解来为 Application 清单添加注解,以便控制器可以开始监视注册表:

image-list

指定一个或多个(逗号分隔的)被考虑更新的镜像。

write-back-method

传播新版本的方法。实现了 gitargocd 方法以更新到更高版本的镜像。Git 方法将更改提交到 Git 仓库。Argo CD 使用 Kubernetes/ArgoCD API 更新资源。

还有更多配置选项,但前面的是开始的最重要部分。

让我们创建一个带有 Argo CD Application 注解的清单:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bgdk-app
  namespace: argocd
  annotations: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    argocd-image-updater.argoproj.io/image-list: myalias=quay.io/rhdevelopers/bgd ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    argocd-image-updater.argoproj.io/write-back-method: git:secret:openshift-gitops/git-creds ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    argocd-image-updater.argoproj.io/git-branch: main ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
spec:
  destination:
    namespace: bgdk
    server: https://kubernetes.default.svc 
  project: default 
  source: 
    path: ch07/bgdui/bgdk
    repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
    targetRevision: main
  syncPolicy:
    automated: {}

1

添加注解部分

2

设置监视的镜像名称

3

配置使用 Git 作为 write-back-method,设置凭据的位置(<namespace>/<secretname>

4

设置推送更改的分支

现在应用清单以部署应用程序的第一个版本,并启用 Argo CD IU 在推送新镜像到容器注册表时更新仓库:

kubectl apply -f bgdui/bgdui-app.yaml

此时,版本 1.0.0bgdk 命名空间中正在运行,您可以像以前一样访问它。让我们生成一个新的容器版本,以验证新图像是否在仓库中。

为简化流程,我们将标记容器版本 1.1.0 为新版本。

转到本章开头创建的 Quay 仓库,转到标签部分,点击齿轮图标,选择 Add New Tag 来创建一个新的容器,如图 图 7-7 所示。

在 Quay 中创建新标签

图 7-7. 标记容器

将标签设置为1.1.0,如图 图 7-8 所示。

设置新标签

图 7-8. 标记容器

在此步骤之后,您应该已经创建了一个新容器,如图 图 7-9 所示。

等待大约两分钟,直到检测到更改并触发仓库更新。

在 Quay 仓库中放置一个新容器

图 7-9. 最终结果

要验证触发过程,请检查控制器的日志:

kubectl logs argocd-image-updater-59c45cbc5c-kjjtp -f -n argocd

...
time="2022-06-20T21:19:05Z" level=info msg="Setting new image to quay.io/rhdevelopers/bgd:1.1.0" alias=myalias application=bgdk-app image_name=rhdevelopers/bgd image_tag=1.0.0 registry=quay.io
time="2022-06-20T21:19:05Z" level=info msg="Successfully updated image 'quay.io/rhdevelopers/bgd:1.0.0' to 'quay.io/rhdevelopers/bgd:1.1.0', but pending spec update (dry run=false)" alias=myalias application=bgdk-app image_name=rhdevelopers/bgd image_tag=1.0.0 registry=quay.io ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
time="2022-06-20T21:19:05Z" level=info msg="Committing 1 parameter update(s) for application bgdk-app" application=bgdk-app
...

1

检测到更改并更新图像

之后,如果检查仓库,您将看到一个名为 .argocd-source-bgdk-app.yaml 的新 Kustomize 文件,将图像值更新为新容器,如图 图 7-10 所示。

更新至新容器的新 Kustomize 文件

图 7-10. 新 Kustomize 文件更新至新容器

现在 Argo CD 可以检测到更改并正确地更新集群以适应新的镜像。

要删除应用程序,请使用 CLI 工具或 UI:

argocd app delete bgdk-app

讨论

更新策略定义了 Argo CD IU 如何查找新版本。如果没有更改,Argo CD IU 使用语义版本来检测最新版本。

可以添加一个可选的版本约束字段来限制允许自动更新的版本。要仅更新补丁版本,可以按以下片段更改 image-list 注释:

argocd-image-updater.argoproj.io/image-list: myalias=quay.io/rhdevelopers/bgd:1.2.x

Argo CD Image Updater 可以更新到具有最新构建日期的镜像:

argocd-image-updater.argoproj.io/myalias.update-strategy: latest
argocd-image-updater.argoproj.io/myimage.allow-tags: regexp:^[0-9a-f]{7}$ ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

限制用于更新的标签

摘要更新策略将使用镜像摘要更新应用程序的镜像标签:

argocd-image-updater.argoproj.io/myalias.update-strategy: digest

到目前为止,容器存储在公共注册表中。如果仓库是私有的,Argo CD Image Updater 需要读取仓库以检测任何更改。

首先,创建一个新的代表容器注册凭据的秘密:

kubectl create -n argocd secret docker-registry quayio --docker-server=quay.io --docker-username=$QUAY_USERNAME --docker-password=$QUAY_PASSWORD

Argo CD Image Updater 使用 ConfigMap 作为配置源,这是注册私有容器注册表的位置。创建一个新的 ConfigMap 清单设置支持的注册表:

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
data:
  registries.conf: |
    registries: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    - name: RedHat Quay ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
      api_url: https://quay.io ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
      prefix: quay.io ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
      insecure: yes
      credentials: pullsecret:argocd/quayio ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)

1

Argo CD IU 的 ConfigMap 名称

2

注册所有注册表的位置

3

用于标识的名称

4

服务的网址

5

容器镜像中使用的前缀

6

从存储在 argocd 命名空间中的 quayio 密钥获取凭据

Argo CD Image Updater 提交更新,附带默认消息:

commit 3caf0af8b7a26de70a641c696446bbe1cd04cea8 (HEAD -> main, origin/main)
Author: argocd-image-updater <noreply@argoproj.io>
Date:   Thu Jun 23 09:41:00 2022 +0000

    build: automatic update of bgdk-app

    updates image rhdevelopers/bgd tag '1.0.0' to '1.1.0'

我们可以将默认提交消息更新为符合您要求的消息。在 ArgoCD IU argocd-image-updater-config ConfigMap 中配置 git.commit-message-template 键与消息:

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
data:
  git.user: alex ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  git.email: alex@example.com ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  git.commit-message-template: | ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    build: automatic update of {{ .AppName }} ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)

    {{ range .AppChanges -}} ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)
    updates image {{ .Image }} tag '{{ .OldTag }}' to '{{ .NewTag }}' ![7](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/7.png) ![8](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/8.png) ![9](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/9.png)
    {{ end -}}

1

Argo CD IU ConfigMap

2

提交用户

3

提交的电子邮件

4

Golang text/template 内容

5

应用的名称

6

更新执行的更改列表

7

镜像名称

8

先前的容器标签

9

新的容器标签

注意

ConfigMap 更改时,请记得重新启动 Argo CD UI 控制器:

kubectl rollout restart deployment argocd-image-updater -n argocd

参见

7.6 从私有 Git 仓库部署

问题

您希望 Argo CD 部署清单。

解决方案

使用 Argo CD CLI/UI 或 YAML 文件注册仓库的凭据信息(用户名/密码/令牌/密钥)。

在 Argo CD 中,您有两种方法可以注册带有其凭据的 Git 仓库。一种方法是使用 Argo CD CLI/Argo CD UI 工具。要在 Argo CD 中注册私有仓库,请通过以下命令设置用户名和密码:

argocd repo add https://github.com/argoproj/argocd-example-apps \
--username <username> --password <password>

或者,我们也可以使用 Argo CD UI 进行注册。在浏览器中打开 Argo CD UI,点击“设置/仓库”按钮(带有齿轮图标),如图 7-11 所示。

选择仓库部分

图 7-11. 设置菜单

然后点击“使用 HTTPS 连接 Repo”按钮,并填写所需数据,如图 7-12 所示。

使用 HTTPS 凭据配置仓库

图 7-12. 仓库的配置

最后,单击“连接”按钮以测试是否可以建立连接并将仓库添加到 Argo CD 中。

另一种方法是创建一个带有仓库和凭据信息的 Kubernetes Secret 清单文件:

apiVersion: v1
kind: Secret
metadata:
  name: private-repo
  namespace: argocd ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  labels:
    argocd.argoproj.io/secret-type: repository ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
stringData:
  type: git
  url: https://github.com/argoproj/private-repo ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  password: my-password ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
  username: my-username ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)

1

在 Argo CD 命名空间中创建一个密钥

2

将密钥类型设置为 repository

3

要注册的仓库的网址

4

访问密码

5

访问用户名

如果您应用此文件,则效果与手动操作相同。

在这一点上,每当我们在 Application 资源中定义一个 repoURL 值,并且使用了为身份验证注册的存储库 URL,Argo CD 将使用注册的凭据登录。

讨论

除了为访问私有 Git 存储库设置用户名和密码之外,Argo CD 还支持其他方法,如令牌、TLS 客户端证书、SSH 私钥或 GitHub App 凭据。

让我们看一些使用 Argo CD CLI 或 Kubernetes Secrets 的示例。

要配置 TLS 客户端证书:

argocd repo add https://repo.example.com/repo.git \
--tls-client-cert-path ~/mycert.crt \
--tls-client-cert-key-path ~/mycert.key

对于 SSH,您只需设置 SSH 私钥的位置:

argocd repo add git@github.com:argoproj/argocd-example-apps.git \
--ssh-privatekey-path ~/.ssh/id_rsa

或者使用 Kubernetes Secret:

apiVersion: v1
kind: Secret
metadata:
  name: private-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: git
  url: git@github.com:argoproj/my-private-repository
  sshPrivateKey: | ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    -----BEGIN OPENSSH PRIVATE KEY-----
    ...
    -----END OPENSSH PRIVATE KEY-----

1

设置 SSH 私钥的内容

如果使用 GitHub App 方法,您需要设置 App ID、App Installation ID 和私钥:

argocd repo add https://github.com/argoproj/argocd-example-apps.git --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem

或者使用声明性方法:

apiVersion: v1
kind: Secret
metadata:
  name: github-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: git
  repo: https://ghe.example.com/argoproj/my-private-repository
  githubAppID: 1 ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  githubAppInstallationID: 2
  githubAppEnterpriseBaseUrl: https://ghe.example.com/api/v3 ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  githubAppPrivateKeySecret: |
    -----BEGIN OPENSSH PRIVATE KEY-----
    ...
    -----END OPENSSH PRIVATE KEY-----

1

设置 GitHub App 参数

2

仅当使用 GitHub App Enterprise 时有效

对于访问令牌,请使用账户名作为用户名,并在密码字段中使用令牌。

选择使用哪种策略将取决于您管理 Kubernetes Secrets 的经验。请记住,Kubernetes 中的 Secret 并不是加密的,而是以 Base64 编码,因此默认情况下不安全。

我们建议只在有了保护机密的良好策略时才使用声明性方法。

注意

我们还没有讨论过 Sealed Secrets 项目(我们将在下一章中讨论),但是在使用 Sealed Secrets 时,标签将被移除,以避免 SealedSecret 对象具有一个 template 部分,该部分编码了您希望控制器放入未密封 Secret 中的所有字段:

spec:
  ...
  template:
    metadata:
      labels:
        "argocd.argoproj.io/secret-type": repository

7.7 订单 Kubernetes 清单

问题

您想要使用 Argo CD 进行部署。

解决方案

使用 同步波资源钩子 修改应用清单的默认顺序。

Argo CD 使用以下逻辑按特定顺序应用 Kubernetes 清单(普通、Helm、Kustomize):

  1. By kind

    1. Namespaces

    2. NetworkPolicy

    3. Limit Range

    4. ServiceAccount

    5. Secret

    6. ConfigMap

    7. StorageClass

    8. PersistentVolumes

    9. ClusterRole

    10. Role

    11. Service

    12. DaemonSet

    13. Pod

    14. ReplicaSet

    15. Deployment

    16. StatefulSet

    17. Job

    18. Ingress

  2. 以相同类型,然后按名称(按字母顺序)

当应用资源时,Argo CD 有三个阶段:第一阶段在应用清单(PreSync)之前执行,第二阶段是应用清单(Sync),第三阶段在所有清单应用并同步后执行(PostSync)。

图 7-13 总结了这些阶段。

阶段和同步波之间的关系

图 7-13. 钩子和同步波

资源钩子是在给定阶段执行的脚本,或者如果同步阶段失败,则可以运行一些回滚操作。

表 7-1 列出了可用的资源钩子。

表 7-1. 资源钩子

钩子 描述 使用案例

|

PreSync

在清单应用之前执行 数据库迁移

|

Sync

与清单同时执行 复杂的滚动更新策略,如金丝雀发布或暗部署

|

PostSync

在所有 Sync 钩子完成并成功(健康)后执行 运行测试以验证部署是否正确完成

|

SyncFail

在同步操作失败时执行 在失败情况下执行回滚操作

|

Skip

跳过清单的应用 当需要手动步骤来部署应用(例如,释放公共流量到新版本)时

钩子被定义为一个名为 argocd.argoproj.io/hook 的注释,应用于 Kubernetes 资源。在以下片段中,定义了一个 PostSync 清单:

apiVersion: batch/v1
kind: Job
metadata:
  name: todo-insert ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  annotations:
    argocd.argoproj.io/hook: PostSync ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)

1

作业名称

2

设置清单应用的时间点

同步波是 Argo CD 应用存储在 Git 中的清单的一种排序方式。

默认情况下,所有清单的波次都为零,较低的值先执行。使用 argocd.argoproj.io/sync-wave 注释将波次号设置为资源。

例如,您可能希望先部署数据库,然后再创建数据库架构;对于这种情况,您应该在数据库部署文件中将 sync-wave 设置为比创建数据库架构作业中更低,如下片段所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgresql ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  namespace: todo
  annotations:
    argocd.argoproj.io/sync-wave: "0" ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
...
apiVersion: batch/v1
kind: Job
metadata:
  name: todo-table ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
  namespace: todo
  annotations:
    argocd.argoproj.io/sync-wave: "1" ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)

1

PostgreSQL 部署

2

PostgreSQL 部署的同步波是 0

3

作业名称

4

当 PostgreSQL 健康时执行的作业

讨论

当 Argo CD 开始应用清单时,它按以下方式排序资源:

  1. 阶段

  2. 波次(优先级低的先)

  3. 种类

  4. 名称

让我们部署一个更大的应用,其中包括部署文件、同步波和钩子。

部署的示例应用是一个 TODO 应用程序,连接到一个数据库(PostgreSQL)来存储 TODO。为了部署该应用程序,需要按特定顺序进行操作;例如,必须先运行数据库服务器,然后再创建数据库架构。此外,当整个应用程序部署完成后,我们会向数据库中插入一些默认的 TODO 来运行后同步清单。

整个过程显示在 图 7-14 中。

带有执行顺序清单的 TODO 应用

图 7-14. Todo 应用

创建一个指向应用程序的 Application 资源:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: todo-app
  namespace: argocd
spec:
  destination:
    namespace: todo
    server: https://kubernetes.default.svc
  project: default
  source:
    path: ch07/todo
    repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
    targetRevision: main
  syncPolicy:
    automated:
      prune: true
      selfHeal: false
    syncOptions:
    - CreateNamespace=true

在终端中应用资源,Argo CD 将按指定顺序部署所有应用程序。

参见

7.8 定义同步窗口

问题

您希望 Argo CD 根据时间阻塞或允许应用程序同步。

解决方案

Argo CD 提供了 同步窗口 的概念,用于配置应用程序同步的时间窗口(将推送到存储库的新资源应用)是否被阻止或允许。

要定义同步窗口,创建一个 AppProject 清单,设置 kind(允许或拒绝),以 cron 格式设置初始时间 schedule,窗口的 duration,以及应用同步窗口的资源(Application,命名空间或集群)。

AppProject 资源负责定义这些窗口,其中允许/阻止同步。

创建一个新文件,仅允许在 22:00 到 23:00 进行同步(仅一个小时),并且用于以 -prod 结尾的 Argo CD 应用程序

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: default
spec:
  syncWindows: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  - kind: allow  ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    schedule: '0 22 * * *' ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    duration: 1h ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    applications: ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
    - '*-prod' ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)

1

窗口列表

2

允许同步

3

仅在 22:00

4

1 小时(23:00)

5

设置影响此窗口的应用程序

6

正则表达式匹配以 -prod 结尾的任何应用程序

讨论

当未在 AppProject 清单中定义的时间窗口内配置的时间时,我们无法执行应用程序的同步(无论是自动还是手动)。但是,我们可以配置一个窗口来允许手动同步。

使用 CLI 工具:

argocd proj windows enable-manual-sync <PROJECT ID>

同时,在 YAML 文件中可以设置手动同步。在下面的示例中,我们设置了 default 命名空间的手动同步,拒绝在 22:00 进行一个小时的同步,并允许在 prod-cluster 中在 23:00 进行一个小时的同步:

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: default
  namespace: argocd
spec:
  syncWindows:
  - kind: deny ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    schedule: '0 22 * * *'
    duration: 1h
    manualSync: true ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
    namespaces: ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    - bgd
  - kind: allow
    schedule: '0 23 * * *'
    duration: 1h
    clusters: ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    - prod-cluster

1

阻止同步

2

启用到 default 命名空间的手动同步

3

配置要阻止的命名空间

4

配置集群,允许在 23:00 进行同步

我们可以通过转到 设置 → 项目 → 默认 → 窗口选项卡 或者使用 argocd CLI 工具来检查当前窗口的 UI:

argocd proj windows list default

ID  STATUS    KIND   SCHEDULE    DURATION  APPLICATIONS  NAMESPACES  CLUSTERS      MANUALSYNC
0   Inactive  deny   0 22 * * *  1h        -             bgd         -             Enabled
1   Inactive  allow  0 23 * * *  1h        -             -           prod-cluster  Disabled

第八章:高级主题

在前一章中,您已经概述了使用 Argo CD 配方实现 GitOps 工作流程。Argo CD 是一个著名且具有影响力的开源项目,可帮助处理简单和更高级的用例。在本章中,我们将讨论在 GitOps 旅程中前进时所需的主题,包括管理安全性、自动化和多集群场景中的高级部署模型。

安全性是自动化和 DevOps 的关键方面。DevSecOps 是一种新的定义,其中安全性在整个 IT 生命周期中是一种共享责任。此外,DevSecOps 宣言 指定安全性作为代码来操作并减少阻力,为价值贡献。这与 GitOps 原则一致,一切都是声明式的。

另一方面,这也引出了一个问题,即避免在 Git 中存储未加密的明文凭据。正如 Christian Hernandez 的书 Path to GitOps 中所述,Argo CD 目前幸运地提供了两种模式来管理 GitOps 工作流中的安全性:

  • 在 Git 中存储加密的秘密,例如 Sealed Secret(参见 Recipe 8.1)

  • 将密码存储在外部服务或保险库中,然后只在 Git 中存储对这些密码的引用(参见 Recipe 8.2)

本章然后介绍了高级部署技术,展示了如何使用 Argo CD 管理 Webhook(参见 Recipe 8.3)和应用集(参见 Recipe 8.4)。ApplicationSets 是 Argo CD 的一个组件,允许从单个 Kubernetes 资源管理多个应用程序、存储库或集群的部署。实质上,这是一个 GitOps 应用程序的模板系统,可以在多个 Kubernetes 集群中部署和同步(参见 Recipe 8.5)。

最后,本书以 Argo Rollouts 用于 Kubernetes 的渐进式交付配方(Recipe 8.6)结束,对于使用蓝绿或金丝雀等高级部署技术部署应用程序非常有用。

8.1 加密敏感数据(Sealed Secrets)

问题

您希望在 Git 中管理 Kubernetes Secrets 和加密对象。

解决方案

Sealed Secrets 是 Bitnami 开源项目,用于将 Kubernetes Secrets 加密为 SealedSecret Kubernetes 自定义资源,表示可以安全存储在 Git 中的加密对象。

Sealed Secrets 使用公钥加密技术,由两个主要组件组成:

  • 一个 Kubernetes 控制器,了解用于解密和加密加密密码的私钥和公钥,并负责协调。该控制器还支持私钥的自动轮换和密钥到期管理,以强制重新加密密码。

  • kubeseal 是开发人员用于在提交到 Git 存储库之前加密其密码的 CLI 工具。

SealedSecret对象仅由运行在目标 Kubernetes 集群中的SealedSecret控制器加密和解密。这个操作仅由此组件独占,因此没有其他人可以解密对象。kubeseal CLI 允许开发人员将普通的 Kubernetes Secret 资源转换为SealedSecret资源定义,如图 8-1 所示。

在您的 Kubernetes 集群中,使用GitHub 项目的发布可以为您的操作系统安装kubeseal CLI。在撰写本书时,我们使用的是版本 0.18.2。

提示

在 macOS 上,kubeseal可以通过Homebrew进行安装:

brew install kubeseal

使用 GitOps 密封密钥

图 8-1. 使用 GitOps 密封密钥

安装完 CLI 后,您可以按如下方式安装控制器:

kubectl create \
-f https://github.com/bitnami-labs/sealed-secrets/releases/download/0.18.2/controller.yaml

您应该得到类似以下的输出:

serviceaccount/sealed-secrets-controller created
deployment.apps/sealed-secrets-controller created
customresourcedefinition.apiextensions.k8s.io/sealedsecrets.bitnami.com created
service/sealed-secrets-controller created
rolebinding.rbac.authorization.k8s.io/sealed-secrets-controller created
rolebinding.rbac.authorization.k8s.io/sealed-secrets-service-proxier created
role.rbac.authorization.k8s.io/sealed-secrets-service-proxier created
role.rbac.authorization.k8s.io/sealed-secrets-key-admin created
clusterrolebinding.rbac.authorization.k8s.io/sealed-secrets-controller created
clusterrole.rbac.authorization.k8s.io/secrets-unsealer created

例如,让我们为部署在第五章中的 Pac-Man 游戏创建一个秘密:

kubectl create secret generic pacman-secret \
--from-literal=user=pacman \
--from-literal=pass=pacman

您应该得到以下输出:

secret/pacman-secret created

在这里您可以看到 YAML 表示:

kubectl get secret pacman-secret  -o yaml
apiVersion: v1
data:
  pass: cGFjbWFu
  user: cGFjbWFu
kind: Secret
metadata:
  name: pacman-secret
  namespace: default
type: Opaque

现在,您可以通过以下方式将秘密转换为SealedSecret

kubectl get secret pacman-secret -o yaml \
| kubeseal -o yaml > pacman-sealedsecret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: pacman-secret
  namespace: default
spec:
  encryptedData: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
    pass: AgBJR1AgZ5Gu5NOVsG1E8SKBcdB3QSDdzZka3RRYuWV7z8g7ccQ0dGc1suVOP8wX/ZpPmIMp8+urPYG62k4EZRUjuu/Vg2E1nSbsGBh9eKu3NaO6tGSF3eGk6PzN6XtRhDeER4u7MG5pj/+FXRAKcy8Z6RfzbVEGq/QJQ4z0ecSNdJmG07ERMm1Q+lPNGvph2Svx8aCgFLqRsdLhFyvwbTyB3XnmFHrPr+2DynxeN8XVMoMkRYXgVc6GAoxUK7CnC3Elpuy7lIdPwc5QBx9kUVfra83LX8/KxeaJwyCqvscIGjtcxUtpTpF5jm1t1DSRRNbc4m+7pTwTmnRiUuaMVeujaBco4521yTkh5iEPjnjvUt+VzK01NVoeNunqIazp15rFwTvmiQ5PAtbiUXpT733zCr60QBgSxPg31vw98+u+RcIHvaMIoDCqaXxUdcn2JkUF+bZXtxNmIRTAiQVQ1vEPmrZxpvZcUh/PPC4L/RFWrQWnOzKRyqLq9wRoSLPbKyvMXnaxH0v3USGIktmtJlGjlXoW/i+HIoSeMFS0mUAzOF5M5gweOhtxKGh3Y74ZDn5PbVA/9kbkuWgvPNGDZL924Dm6AyM5goHECr/RRTm1e22K9BfPASARZuGA6paqb9h1XEqyqesZgM0R8PLiyLuu+tpqydR0SiYLc5VltdjzpIyyy9Xmw6Aa3/4SB+4tSwXSUUrB5yc=
    user: AgBhYDZQzOwinetPceZL897aibTYp4QPGFvP6ZhDyuUAxOWXBQ7jBA3KPUqLvP8vBcxLAcS7HpKcDSgCdi47D2WhShdBR4jWJufwKmR3j+ayTdw72t3ALpQhTYI0iMYTiNdR0/o3vf0jeNMt/oWCRsifqBxZaIShE53rAFEjEA6D7CuCDXu8BHk1DpSr79d5Au4puzpHVODh+v1T+Yef3k7DUoSnbYEh3CvuRweiuq5lY8G0oob28j38wdyxm3GIrexa+M/ZIdO1hxZ6jz4edv6ejdZfmQNdru3c6lmljWwcO+0Ue0MqFi4ZF/YNUsiojI+781n1m3K/giKcyPLn0skD7DyeKPoukoN6W5P71OuFSkF+VgIeejDaxuA7bK3PEaUgv79KFC9aEEnBr/7op7HY7X6aMDahmLUc/+zDhfzQvwnC2wcj4B8M2OBFa2ic2PmGzrIWhlBbs1OgnpehtGSETq+YRDH0alWOdFBq1U8qn6QA8Iw6ewu8GTele3zlPLaADi5O6LrJbIZNlY0+PutWfjs9ScVVEJy+I9BGdyT6tiA/4v4cxH6ygG6NzWkqxSaYyNrWWXtLhOlqyCpTZtUwHnF+OLB3gCpDZPx+NwTe2Kn0jY0c83LuLh5PJ090AsWWqZaRQyELeL6y6mVekQFWHGfK6t57Vb7Z3+5XJCgQn+xFLkj3SIz0ME5D4+DSsUDS1fyL8uI=
  template:
    data: null
    metadata:
      creationTimestamp: null
      name: pacman-secret
      namespace: default
    type: Opaque

1

在这里,您可以找到由 Sealed Secrets 控制器加密的数据。

现在,您可以安全地将您的SealedSecret推送到您的 Kubernetes 清单存储库并创建 Argo CD 应用程序。这里有一个来自本书仓库的示例

argocd app create pacman \
--repo https://github.com/gitops-cookbook/pacman-kikd-manifests.git \
--path 'k8s/sealedsecrets' \
--dest-server https://kubernetes.default.svc \
--dest-namespace default \
--sync-policy auto

检查应用程序是否正在运行并且健康:

argocd app list

您应该得到类似以下的输出:

NAME    CLUSTER                         NAMESPACE  PROJECT  STATUS  HEALTH ↳
 SYNCPOLICY  CONDITIONS  REPO                                                           PATH TARGET
pacman  https://kubernetes.default.svc  default    default  Synced  Healthy↳
 <none>      <none>      https://github.com/gitops-cookbook/pacman-kikd-manifests.git  k8s/sealedsecrets

8.2 使用 ArgoCD 加密秘密(ArgoCD + HashiCorp Vault + 外部秘密)

问题

您希望避免在 Git 中存储凭据,并希望在外部服务或保险库中管理它们。

解决方案

在配方 8.1 中,您看到如何按照 GitOps 的声明性方式管理加密数据,但是如何避免即使使用 GitOps 也存储加密凭据?

一个解决方案是外部秘密,这是一个由 GoDaddy 最初创建的开源项目,旨在将秘密存储在来自不同供应商的外部服务或保险库中,然后仅在 Git 中存储对这些秘密的引用。

如今,External Secrets 支持诸如 AWS Secrets Manager、HashiCorp Vault、Google Secrets Manager、Azure Key Vault 等系统。其想法是为外部 API 提供一个用户友好的抽象,用于存储和管理秘密的生命周期。

具体而言,ExternalSecrets 是一个 Kubernetes 控制器,它通过自定义资源将 Secrets 调解到集群中,该资源包括对外部密钥管理系统中秘密的引用。自定义资源SecretStore指定了包含机密数据的后端,并定义了模板来将其转换为 Secret,正如您可以在图 8-2 中看到的那样。SecretStore 具有连接到外部秘密管理器的配置。

因此,ExternalSecrets 对象可以安全地存储在 Git 中,因为它们不包含任何机密信息,仅包含管理凭据的外部服务的引用。

Argo CD 中的 External Secrets

图 8-2. Argo CD 中的 External Secrets

您可以按如下方式使用 Helm Chart 安装 External Secrets。撰写本书时,我们使用的是版本 0.5.9:

helm repo add external-secrets https://charts.external-secrets.io

helm install external-secrets \
  external-secrets/external-secrets \
  -n external-secrets \
  --create-namespace

您应该会获得类似以下的输出:

NAME: external-secrets
LAST DEPLOYED: Fri Sep  2 13:09:53 2022
NAMESPACE: external-secrets
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
external-secrets has been deployed successfully!

要开始使用 ExternalSecrets,您需要设置 SecretStore 或 ClusterSecretStore 资源(例如通过创建 vault SecretStore)。

更多关于不同类型的 SecretStores 及其配置方法的信息可以在我们的 GitHub 页面 中找到。

提示

您还可以从 OperatorHub.io 安装 External Secrets Operator 与 OLM。

例如,您可以使用受支持的提供程序之一,比如 HashiCorp Vault,进行如下操作。

首先下载并安装适合您操作系统的 HashiCorp Vault,获取您的 Vault Token。然后按如下方式创建 Kubernetes Secret:

export VAULT_TOKEN=<YOUR_TOKEN>
kubectl create secret generic vault-token \
  --from-literal=token=$VAULT_TOKEN \
  -n external-secrets

然后,创建一个 SecretStore 作为对这个外部系统的引用:

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-secretstore
  namespace: default
spec:
  provider:
    vault:
      server: "http://vault.local:8200" ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
      path: "secret"
      version: "v2"
      auth:
        tokenSecretRef:
          name: "vault-token" ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
          key: "token" ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
          namespace: external-secrets

1

Vault 运行的主机名

2

包含 Vault 令牌的 Kubernetes Secret 的名称

3

Kubernetes Secret 中包含 Vault 令牌内容的地址键:

kubectl create -f vault-secretstore.yaml

现在,您可以按如下方式在 Vault 中创建一个 Secret:

vault kv put secret/pacman-secrets pass=pacman

然后,从 ExternalSecret 中引用它,操作如下:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: pacman-externalsecrets
  namespace: default
spec:
  refreshInterval: "15s"
  secretStoreRef:
    name: vault-secretstore
    kind: SecretStore
  target:
    name: pacman-externalsecrets
  data:
  - secretKey: token
    remoteRef:
      key: secret/pacman-secrets
      property: pass
kubectl create -f pacman-externalsecrets.yaml

现在,您可以使用 External Secrets 在 Argo CD 中部署 Pac-Man 游戏如下:

argocd app create pacman \
--repo https://github.com/gitops-cookbook/pacman-kikd-manifests.git \
--path 'k8s/externalsecrets' \
--dest-server https://kubernetes.default.svc \
--dest-namespace default \
--sync-policy auto

8.3 自动触发应用程序部署(Argo CD Webhooks)

问题

你不想等待 Argo CD 同步,而是希望在 Git 发生变化时立即部署应用程序。

解决方案

虽然 Argo CD 每三分钟轮询 Git 仓库以检测受监控的 Kubernetes 清单的更改,但也支持来自流行 Git 服务器(如 GitHub、GitLab 或 Bitbucket)的 Webhooks 通知的事件驱动方法。

Argo CD Webhooks 已在您的 Argo CD 安装中启用,并且可通过端点 /api/webhooks 访问。

要使用 Minikube 测试 Argo CD 的 Webhooks,您可以使用 Helm 安装本地 Git 服务器,如 Gitea,一个使用 Go 编写的开源轻量级服务器,操作如下:

helm repo add gitea-charts https://dl.gitea.io/charts/
helm install gitea gitea-charts/gitea

您应该会得到类似以下的输出:

helm install gitea gitea-charts/gitea
"gitea-charts" has been added to your repositories
NAME: gitea
LAST DEPLOYED: Fri Sep  2 15:04:04 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  echo "Visit http://127.0.0.1:3000 to use your application"
  kubectl --namespace default port-forward svc/gitea-http 3000:3000
提示

使用 Helm 在 Gitea 服务器上使用默认凭据登录,您可以从 Helm Chart 的 values.yaml 文件 这里 找到或通过覆盖定义新的凭据。

导入 Pac-Man 清单库到 Gitea 中。

配置 Argo 应用:

argocd app create pacman-webhook \
--repo http://gitea-http.default.svc:3000/gitea_admin/pacman-kikd-manifests.git \
--dest-server https://kubernetes.default.svc \
--dest-namespace default \
--path k8s \
--sync-policy auto

要向 Gitea 添加 Webhook,请导航到右上角并单击“设置”。 选择“Webhooks”选项卡,并按照 图 8-3 中所示进行配置:

  • 负载 URL:http://localhost:9090/api/webhooks

  • 内容类型:application/json

Gitea Webhooks

图 8-3. Gitea Webhooks
提示

您可以在此示例中省略 Secret;但是,最佳实践是为您的 Webhook 配置 Secrets。 从 文档 阅读更多。

保存并将更改推送到 Gitea 上的存储库。 在您推送后立即看到来自 Argo CD 的新同步。

8.4 部署到多个集群

问题

您想要将应用程序部署到不同的集群。

解决方案

Argo CD 支持ApplicationSet资源以“模板化”Argo CDApplication资源。 它涵盖了不同的用例,但最重要的是:

  • 使用 Kubernetes 清单来定位多个 Kubernetes 集群。

  • 从一个或多个 Git 存储库部署多个应用程序。

由于ApplicationSet是一个带有占位符的模板文件,需要在运行时用一些值填充这些占位符。 为此,ApplicationSet具有生成器的概念。

生成器负责生成参数,这些参数最终将替换模板占位符以生成有效的 Argo CDApplication

创建以下ApplicationSet

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: bgd-app
  namespace: argocd
spec:
  generators: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  - list:
      elements: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      - cluster: staging
        url: https://kubernetes.default.svc
        location: default
      - cluster: prod
        url: https://kubernetes.default.svc
        location: app
  template: ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    metadata:
      name: '{{cluster}}-app' ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    spec:
      project: default
      source:
        repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
        targetRevision: main
        path: ch08/bgd-gen/{{cluster}}
      destination:
        server: '{{url}}' ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
        namespace: '{{location}}'
      syncPolicy:
        syncOptions:
        - CreateNamespace=true

1

定义生成器

2

设置参数的值

3

Application资源定义为模板

4

cluster 占位符

5

url 占位符

通过运行以下命令应用先前的文件:

kubectl apply -f bgd-application-set.yaml

当将此ApplicationSet应用于集群时,Argo CD 会生成并自动注册两个Application资源。 第一个是:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: staging-app
spec:
  project: default
  source:
    path: ch08/bgd-gen/staging
    repoURL: https://github.com/example/app.git
    targetRevision: HEAD
  destination:
    namespace: default
    server: https://kubernetes.default.svc
    ...

和第二个:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prod-app
spec:
  project: default
  source:
    path: ch08/bgd-gen/prod
    repoURL: https://github.com/example/app.git
    targetRevision: HEAD
  destination:
    namespace: app
    server: https://kubernetes.default.svc
    ...

通过运行以下命令检查两个Application资源的创建:

# Remember to login first
argocd login --insecure --grpc-web $argoURL  --username admin --password $argoPass

argocd app list

输出应该类似于(截断):

NAME         CLUSTER                         NAMESPACE
prod-app     https://kubernetes.default.svc  app
staging-app  https://kubernetes.default.svc  default

通过删除ApplicationSet文件来删除两个应用程序:

kubectl delete -f bgd-application-set.yaml

讨论

我们已经看到了最简单的生成器,但在撰写本书时总共有八个生成器:

列表

通过固定的集群列表生成Application定义。(这是我们之前看到的)。

集群

类似于List,但基于在 Argo CD 中定义的集群列表。

Git

根据 Git 存储库中的 JSON/YAML 属性文件或基于存储库的目录布局生成Application定义。

SCM 提供者

从组织内的存储库生成Application定义。

拉取请求

从开放的拉取请求生成Application定义。

集群决策资源

使用鸭子类型生成Application定义。

矩阵

结合两个独立生成器的值。

合并

合并两个或更多生成器的值。

在前面的示例中,我们从固定元素列表创建了Application对象。当可配置环境的数量较少时,这很好;例如,在示例中,两个群集引用了两个 Git 文件夹(ch08/bgd-gen/stagingch08/bgd-gen/prod)。对于多个环境(即各种文件夹),我们可以动态使用 Git 生成器为每个目录生成一个Application

让我们将前面的示例迁移到使用 Git 生成器。作为提醒,使用的 Git 目录布局是:

bgd-gen
├── staging
│   ├── ...yaml
└── prod
    ├── ...yaml

创建一个新的ApplicationSet文件,为配置的 Git 仓库中的每个目录生成一个Application

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: openshift-gitops
spec:
  generators:
  - git: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
      repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
      revision: main
      directories:
      - path: ch08/bgd-gen/* ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
  template: ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
    metadata:
      name: '{{path[0]}}{{path[2]}}' ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
    spec:
      project: default
      source:
        repoURL: https://github.com/gitops-cookbook/gitops-cookbook-sc.git
        targetRevision: main
        path: '{{path}}' ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}' ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)

1

配置 Git 仓库以读取布局

2

开始扫描目录的初始路径

3

Application定义

4

Git 仓库内的目录路径匹配路径通配符(stagingprod

5

目录路径(完整路径)

6

最右边的路径名

应用资源:

kubectl apply -f bgd-git-application-set.yaml

Argo CD 根据有两个目录创建两个应用程序:

argocd app list

NAME         CLUSTER                         NAMESPACE
ch08prod     https://kubernetes.default.svc  prod
ch08staging  https://kubernetes.default.svc  staging

此生成器还可在应用程序由不同组件(服务、数据库、分布式缓存、电子邮件服务器等)组成时使用,并且每个元素的部署文件放置在其他目录中时非常方便。例如,在群集中安装所需的所有操作器的存储库:

app
├── tekton-operator
│   ├── ...yaml
├── prometheus-operator
│   ├── ...yaml
└── istio-operator
    ├── ...yaml

Git 生成器不再响应目录,而是根据 JSON/YAML 文件中指定的参数创建Application对象。

下面的片段显示了一个 JSON 文件示例:

{
  "cluster": {
    "name": "staging",
    "address": "https://1.2.3.4"
  }
}

这是一个ApplicationSet的摘录,用于响应这些文件:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: guestbook
spec:
  generators:
  - git:
      repoURL: https://github.com/example/app.git
      revision: HEAD
      files:
      - path: "app/**/config.json" ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
  template:
    metadata:
      name: '{{cluster.name}}-app' ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
....

1

查找放置在app所有子目录中的所有config.json文件

2

注入config.json中设置的值

ApplicationSet将为匹配path表达式的文件夹中的每个config.json文件生成一个Application

参见

8.5 将拉取请求部署到群集

问题

当创建拉取请求时,您希望部署应用程序的预览。

解决方案

使用pull request生成器自动发现存储库中的打开拉取请求,并创建Application对象。

让我们创建一个ApplicationSet,以响应配置仓库上创建的带有preview标签的任何 GitHub 拉取请求。

创建一个名为bgd-pr-application-set.yaml的新文件,并包含以下内容:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: myapps
  namespace: openshift-gitops
spec:
  generators:
  - pullRequest:
      github: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
        owner: gitops-cookbook ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
        repo: gitops-cookbook-sc ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
        labels: ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
        - preview
      requeueAfterSeconds: 60 ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
  template:
    metadata:
      name: 'myapp-{{branch}}-{{number}}' ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)
    spec:
      source:
        repoURL: 'https://github.com/gitops-cookbook/gitops-cookbook-sc.git'
        targetRevision: '{{head_sha}}' ![7](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/7.png)
        path: ch08/bgd-pr
      project: default
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{branch}}-{{number}}'

1

GitHub 拉取请求生成器

2

组织/用户

3

仓库

4

选择目标 PR

5

检查是否有新的 PR(60 秒的轮询时间)

6

设置带有分支名称和编号的名称

7

设置 Git SHA 号码

运行以下命令来应用上述文件:

kubectl apply -f bgd-pr-application-set.yaml

现在,如果您列出 Argo CD 应用程序,您会发现没有一个已注册。原因是存储库中还没有带有 preview 标签的拉取请求:

argocd app list
NAME  CLUSTER  NAMESPACE  PROJECT  STATUS

对存储库创建一个拉取请求,并使用 preview 标签标记它。

在 GitHub 中,拉取请求窗口应类似于 Figure 8-4。

GitHub 中的拉取请求

图 8-4. GitHub 中的拉取请求

等待一分钟,直到 ApplicationSet 检测到更改并创建 Application 对象。

运行以下命令来检查更改是否已被检测并注册:

kubectl describe applicationset myapps -n argocd

...
Events:
  Type    Reason     Age                From                       Message
  ----    ------     ----               ----                       -------
  Normal  created    23s                applicationset-controller  created Application "myapp-lordofthejars-patch-1-1"
  Normal  unchanged  23s (x2 over 23s)  applicationset-controller  unchanged Application "myapp-lordofthejars-patch-1-1"

检查 Application 是否已注册到拉取请求:

argocd app list
NAME                           CLUSTER                         NAMESPACE
myapp-lordofthejars-patch-1-1  https://kubernetes.default.svc  lordofthejars-patch-1-1

当关闭拉取请求时,Application 对象会自动移除。

讨论

在编写本书时,支持以下拉取请求提供程序:

  • GitHub

  • Bitbucket

  • Gitea

  • GitLab

ApplicationSet 控制器每隔 requeueAfterSeconds 秒轮询以检测更改,同时支持使用 Webhook 事件。

要进行配置,请参考 Recipe 8.3,同时在 Git 提供程序中启用发送拉取请求事件。

8.6 使用先进的部署技术

问题

您希望使用先进的部署技术,如蓝绿部署或金丝雀部署,来部署应用程序。

解决方案

使用 Argo Rollouts 项目来对应用程序进行更新的部署。

Argo Rollouts 是一个 Kubernetes 控制器,提供先进的部署技术,如蓝绿部署、金丝雀部署、镜像部署、暗金丝雀、流量分析等,用于 Kubernetes。它与许多 Kubernetes 项目集成,如 Ambassador、Istio、AWS 负载均衡控制器、NGNI、SMI 或 Traefik 用于流量管理,并与 Prometheus、Datadog 和 New Relic 等项目集成,进行分析以推动渐进式交付。

要将 Argo Rollouts 安装到集群中,请在终端窗口中运行以下命令:

kubectl create namespace argo-rollouts

kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/download/v1.2.2/install.yaml
...
clusterrolebinding.rbac.authorization.k8s.io/argo-rollouts created
secret/argo-rollouts-notification-secret created
service/argo-rollouts-metrics created
deployment.apps/argo-rollouts created

虽然这不是强制性的,但我们建议您安装 Argo Rollouts Kubectl 插件以可视化部署过程。请按照说明安装。一切就绪后,让我们部署 BGD 应用程序的初始版本。

Argo Rollouts 不使用标准的 Kubernetes Deployment 文件,而是使用一个名为 Rollout 的特定新 Kubernetes 资源。它类似于 Deployment 对象,因此支持其所有选项,但它添加了一些字段来配置滚动更新。

让我们部署应用程序的第一个版本。当 Kubernetes 执行滚动更新时,我们将定义金丝雀发布流程,本例中遵循以下步骤:

  1. 将 20%的流量转发到新版本。

  2. 等待人工决定是否继续该过程。

  3. 自动将 40%,60%,80%的流量转发到新版本,每次增加之间等待 30 秒。

创建一个名为bgd-rollout.yaml的新文件,内容如下:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: bgd-rollouts
spec:
  replicas: 5
  strategy:
    canary: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
      steps: ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      - setWeight: 20 ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
      - pause: {} ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
      - setWeight: 40
      - pause: {duration: 30s} ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
      - setWeight: 60
      - pause: {duration: 30s}
      - setWeight: 80
      - pause: {duration: 30s}
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: bgd-rollouts
  template: ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)
    metadata:
      creationTimestamp: null
      labels:
        app: bgd-rollouts
    spec:
      containers:
      - image: quay.io/rhdevelopers/bgd:1.0.0
        name: bgd
        env:
        - name: COLOR
          value: "blue"
        resources: {}

1

金丝雀发布

2

执行的步骤列表

3

设置金丝雀的比例

4

滚动更新已暂停

5

暂停滚动更新 30 秒

6

template部署定义

应用资源以部署应用程序。由于没有先前的部署,忽略金丝雀部分:

kubectl apply -f bgd-rollout.yaml

当前有五个 pod,如replicas字段所指定的那样:

kubectl get pods

NAME                           READY   STATUS    RESTARTS   AGE
bgd-rollouts-679cdfcfd-6z2zf   1/1     Running   0          12m
bgd-rollouts-679cdfcfd-8c6kl   1/1     Running   0          12m
bgd-rollouts-679cdfcfd-8tb4v   1/1     Running   0          12m
bgd-rollouts-679cdfcfd-f4p7f   1/1     Running   0          12m
bgd-rollouts-679cdfcfd-tljfr   1/1     Running   0          12m

并使用 Argo Rollout 的 Kubectl 插件:

kubectl argo rollouts get rollout bgd-rollouts

Name:            bgd-rollouts
Namespace:       default
Status:          ✔ Healthy
Strategy:        Canary
  Step:          8/8
  SetWeight:     100
  ActualWeight:  100
Images:          quay.io/rhdevelopers/bgd:1.0.0 (stable)
Replicas:
  Desired:       5
  Current:       5
  Updated:       5
  Ready:         5
  Available:     5

NAME                                     KIND        STATUS      AGE  INFO
⟳ bgd-rollouts                           Rollout     ✔ Healthy  13m
└──# revision:1
   └──⧉ bgd-rollouts-679cdfcfd          ReplicaSet  ✔ Healthy  13m  stable
      ├──□ bgd-rollouts-679cdfcfd-6z2zf  Pod         ✔ Running  13m  ready:1/1
      ├──□ bgd-rollouts-679cdfcfd-8c6kl  Pod         ✔ Running  13m  ready:1/1
      ├──□ bgd-rollouts-679cdfcfd-8tb4v  Pod         ✔ Running  13m  ready:1/1
      ├──□ bgd-rollouts-679cdfcfd-f4p7f  Pod         ✔ Running  13m  ready:1/1
      └──□ bgd-rollouts-679cdfcfd-tljfr  Pod         ✔ Running  13m  ready:1/1

部署新版本以触发金丝雀滚动更新。创建一个名为bgd-rollout-v2.yaml的新文件,内容与之前完全相同,但将环境变量COLOR的值改为green

...
name: bgd
env:
- name: COLOR
  value: "green"
resources: {}

应用先前的资源,并检查 Argo Rollouts 如何执行滚动更新。再次列出 pod 以检查 20%的 pod 是新版本,而其他 80%是旧版本:

kubectl get pods

NAME                           READY   STATUS    RESTARTS   AGE
bgd-rollouts-679cdfcfd-6z2zf   1/1     Running   0          27m
bgd-rollouts-679cdfcfd-8c6kl   1/1     Running   0          27m
bgd-rollouts-679cdfcfd-8tb4v   1/1     Running   0          27m
bgd-rollouts-679cdfcfd-tljfr   1/1     Running   0          27m
bgd-rollouts-c5495c6ff-zfgvn   1/1     Running   0          13s ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)

1

新版本的 pod

并使用 Argo Rollout 的 Kubectl 插件执行相同操作:

kubectl argo rollouts get rollout bgd-rollouts

...
NAME                                     KIND        STATUS      AGE    INFO
⟳ bgd-rollouts                           Rollout     ॥ Paused    31m
├──# revision:2
│  └──⧉ bgd-rollouts-c5495c6ff          ReplicaSet  ✔ Healthy  3m21s  canary
│     └──□ bgd-rollouts-c5495c6ff-zfgvn  Pod         ✔ Running  3m21s  ready:1/1
└──# revision:1
   └──⧉ bgd-rollouts-679cdfcfd          ReplicaSet  ✔ Healthy  31m    stable
      ├──□ bgd-rollouts-679cdfcfd-6z2zf  Pod         ✔ Running  31m    ready:1/1
      ├──□ bgd-rollouts-679cdfcfd-8c6kl  Pod         ✔ Running  31m    ready:1/1
      ├──□ bgd-rollouts-679cdfcfd-8tb4v  Pod         ✔ Running  31m    ready:1/1
      └──□ bgd-rollouts-679cdfcfd-tljfr  Pod         ✔ Running  31m    ready:1/1

请记住,滚动更新过程会暂停,直到操作员执行手动步骤以让过程继续。在终端窗口中运行以下命令:

kubectl argo rollouts promote bgd-rollouts

推广滚动更新并继续以下步骤,即每 30 秒用新版本替换旧版本的 pod:

kubectl get pods

NAME                           READY   STATUS    RESTARTS   AGE
bgd-rollouts-c5495c6ff-2g7r8   1/1     Running   0          89s
bgd-rollouts-c5495c6ff-7mdch   1/1     Running   0          122s
bgd-rollouts-c5495c6ff-d9828   1/1     Running   0          13s
bgd-rollouts-c5495c6ff-h4t6f   1/1     Running   0          56s
bgd-rollouts-c5495c6ff-zfgvn   1/1     Running   0          11m

滚动更新完成,新版本逐步部署到集群中。

讨论

Kubernetes 本身不原生实现高级部署技术。因此,Argo Rollouts 使用部署的 pod 数量来实现金丝雀发布。

如前所述,Argo Rollouts 与提供高级流量管理能力的 Kubernetes 产品(如Istio)集成。

使用 Istio,在基础设施级别正确进行流量分割,而不像第一个示例中玩弄副本数量。Argo Rollouts 与 Istio 集成,执行金丝雀发布,自动更新 Istio 的VirtualService对象。

假设您已了解 Istio,并且在安装了 Istio 的 Kubernetes 集群上,您可以通过将Rollout资源的trafficRouting设置为Istio来执行 Argo Rollouts 与 Istio 的集成。

首先,使用配置了 Istio 的Rollout文件:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: bgdapp
  labels:
    app: bgdapp
spec:
  strategy:
    canary: ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
      steps:
      - setWeight: 20
      - pause:
          duration: "1m"
      - setWeight: 50
      - pause:
          duration: "2m"
      canaryService: bgd-canary ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      stableService: bgd ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)
      trafficRouting:
        istio: ![4](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/4.png)
           virtualService: ![5](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/5.png)
            name: bgd ![6](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/6.png)
            routes:
            - primary ![7](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/7.png)
  replicas: 1
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: bgdapp
      version: v1
  template:
    metadata:
      labels:
        app: bgdapp
        version: v1
      annotations:
        sidecar.istio.io/inject: "true" ![8](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/8.png)
    spec:
      containers:
      - image: quay.io/rhdevelopers/bgd:1.0.0
        name: bgd
        env:
        - name: COLOR
          value: "blue"
        resources: {}

1

金丝雀部分

2

指向新服务版本的 Kubernetes 服务的参考

3

指向旧服务版本的 Kubernetes 服务的引用

4

配置 Istio

5

更新权重的 VirtualService 引用

6

VirtualService 的名称

7

VirtualService 内的路由名称

8

部署 Istio sidecar 容器

然后,我们创建了两个 Kubernetes 服务,它们指向相同的部署,用于将流量重定向到旧版本或新版本。

下面的 Kubernetes 服务在 stableService 字段中使用:

apiVersion: v1
kind: Service
metadata:
  name: bgd
  labels:
    app: bgdapp
spec:
  ports:
  - name: http
    port: 8080
  selector:
    app: bgdapp

而金丝雀版本则与之相同,但名称不同。它在 canaryService 字段中使用:

apiVersion: v1
kind: Service
metadata:
  name: bgd-canary
  labels:
    app: bgdapp
spec:
  ports:
  - name: http
    port: 8080
  selector:
    app: bgdapp

最后,创建 Istio Virtual Service,由 Argo Rollouts 更新以更新每个服务的金丝雀流量:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bgd
spec:
  hosts:
  - bgd
  http:
  - route:
    - destination:
        host: bgd ![1](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/1.png)
      weight: 100
    - destination:
        host: bgd-canary ![2](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/2.png)
      weight: 0
    name: primary ![3](https://github.com/OpenDocCN/ibooker-devops-zh/raw/master/docs/gitops-cb/img/3.png)

1

稳定的 Kubernetes 服务

2

金丝雀 Kubernetes 服务

3

路由名称

应用这些资源后,我们将使应用程序的第一个版本运行起来:

kubectl apply -f bgd-virtual-service.yaml
kubectl apply -f service.yaml
kubectl apply -f service-canary.yaml
kubectl apply -f bgd-isio-rollout.yaml

Rollout 对象上发生任何更新时,根据解决方案描述,金丝雀发布将开始。现在,Argo Rollouts 自动更新了 bgd virtual service 的权重,而不是操作 pod 数量。

参见

posted @ 2025-11-14 20:39  绝不原创的飞龙  阅读(11)  评论(0)    收藏  举报