Helm-Kubernetes-资源管理指南-全-
Helm Kubernetes 资源管理指南(全)
原文:
annas-archive.org/md5/32a5024a2581d77f5f2644c674845a3c译者:飞龙
前言
容器化目前被认为是实现 DevOps 的最佳方式之一。尽管 Docker 引入了容器并改变了 DevOps 时代,但 Google 开发了一个广泛的容器编排系统——Kubernetes,它如今被认为是行业标准。在本书的帮助下,你将探索使用 Helm 管理 Kubernetes 上运行的应用程序的高效性。
本书首先简要介绍 Helm 及其对使用容器和 Kubernetes 的用户的影响,接着深入讲解 Helm 图表的基本概念及其整体架构和应用场景。之后,你将学习如何编写 Helm 图表,以实现 Kubernetes 上应用程序的自动化部署,并逐步深入到更高级的策略。这些面向企业的模式关注的是超越基础的概念,帮助你充分利用 Helm,包括与自动化、应用程序开发、交付、生命周期管理和安全性相关的主题。
本书结束时,你将学会如何利用 Helm 构建、部署和管理 Kubernetes 上的应用程序。
本书适合的读者
本书适合对 Kubernetes 开发或管理感兴趣,并希望学习 Helm 以为 Kubernetes 上的应用开发提供自动化的开发人员或管理员。虽然不需要先前的 Helm 知识,但具有 Kubernetes 应用程序开发的基本知识将会有所帮助。
本书内容
第一章,理解 Kubernetes 和 Helm,在这一章你将学习到部署 Kubernetes 应用程序时遇到的挑战,以及 Helm 如何简化部署过程。
第二章,准备 Kubernetes 和 Helm 环境,在这一章你将学习如何配置本地开发环境。你将下载 Minikube 和 Helm,并学习基本的 Helm 配置。
第三章,使用 Helm 安装第一个应用程序,通过让你部署第一个 Helm 图表,教授你 Helm 主要命令的使用。
第四章,创建新的 Helm 图表,讲解了 Helm 图表的结构,并帮助你创建自己的 Helm 图表。
第五章,Helm 依赖管理,在这一章你将学习如何管理和使用依赖项来构建和管理复杂的应用程序部署。
第六章,理解 Helm 模板,探讨 Helm 模板以及如何动态生成 Kubernetes 资源。
第七章,Helm 生命周期钩子,讲解生命周期钩子以及如何在不同的 Helm 生命周期阶段部署任意资源。
第八章,发布 Helm 图表到 Helm 图表仓库,讲解了 Helm 图表仓库及其如何用于发布 Helm 图表。
第九章,测试 Helm 图表,讨论了在 Helm 图表开发过程中测试 Helm 图表的不同策略。
第十章,使用 CD 和 GitOps 自动化 Helm,探讨了如何使用持续交付和 GitOps 方法自动化 Helm 部署。
第十一章,在 Operator 框架中使用 Helm,介绍了如何使用 operator-sdk 工具包创建一个 Helm 操作符。
第十二章,Helm 安全性考虑事项,讨论了与 Helm 发布、图表和仓库相关的不同安全话题。
为了充分利用本书的内容
为了充分利用本书,你应该按照以下表格中列出的技术进行安装,并跟随示例进行操作。虽然这些是写作过程中使用的版本,但最新版本也应该可以正常使用。
| 书中涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Minikube v1.22.0 | Windows、macOS 或 Linux |
| VirtualBox 6.1.26 | Windows、macOS 或 Linux |
| Kubectl v1.21.2 | Windows、macOS 或 Linux |
| Helm v3.6.3 | Windows、macOS 或 Linux |
如果你使用的是本书的电子版,我们建议你自己输入代码,或者从本书的 GitHub 仓库中访问代码(下一节中会提供链接)。这样可以帮助你避免与复制粘贴代码相关的潜在错误。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件:github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm。如果代码有更新,它会在 GitHub 仓库中同步更新。
我们还提供了其他代码包,来自我们丰富的书籍和视频目录,你可以在 github.com/PacktPublishing/ 查找。快来看看吧!
下载彩色图片
我们还提供了一个 PDF 文件,包含本书中使用的截图和图表的彩色图片。你可以在这里下载:packt.link/zeDY0。
使用的约定
本书中使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号名。举个例子:“注意,冒号和 LearnHelm 字符串之间缺少一个空格。”
一段代码块按如下方式书写:
configuration: |
server.port=8443
logging.file.path=/var/log
当我们希望你特别注意代码块的某部分时,相关的行或项会加粗显示:
$ cd ~
$ git clone <repository URI>
任何命令行输入或输出均按如下方式书写:
$ helm dependency update chapter8/guestbook
$ helm package guestbook chapter8/guestbook
粗体:表示一个新术语、一个重要的词或你在屏幕上看到的单词。例如,菜单或对话框中的词会以粗体显示。举个例子:“点击生成令牌按钮来创建令牌。”
提示或重要说明
如此呈现。
与我们联系
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件联系 customercare@packtpub.com,并在邮件主题中注明书名。
勘误:尽管我们已尽力确保内容的准确性,但难免会有错误发生。如果您在本书中发现错误,我们将不胜感激,若您能向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。
盗版:如果您在互联网上发现我们作品的任何非法复制品,我们将不胜感激,若您能提供该地址或网站名称。请通过 copyright@packt.com 联系我们,并附上相关资料的链接。
如果您有兴趣成为作者:如果您在某个领域有专长,并且有兴趣写书或为书籍做贡献,请访问 authors.packtpub.com。
分享您的想法
一旦您阅读完 使用 Helm 管理 Kubernetes 资源,第二版,我们很想听听您的想法!请点击此处直接前往本书的亚马逊评论页面,并分享您的反馈。
您的评论对我们和技术社区非常重要,将帮助我们确保提供高质量的内容。
第一部分:介绍与设置
Kubernetes 是一个功能强大的系统,具有复杂的配置。在 第一部分,您将学习 Helm 如何通过提供包管理器界面来解决这些复杂性。通过本部分的学习,您将通过部署第一个 Helm 图表获得实践经验。
在本部分中,我们将涵盖以下主题:
-
第一章**,理解 Kubernetes 和 Helm
-
第二章**,准备 Kubernetes 和 Helm 环境
-
第三章**,使用 Helm 安装您的第一个应用程序
第一章:理解 Kubernetes 和 Helm
感谢你选择本书《学习 Helm》。如果你对本书感兴趣,那么你可能已经意识到现代应用所带来的挑战。团队面临巨大的压力,必须确保应用是轻量且可扩展的。应用还必须具有高度可用性,能够承受不同的负载。历史上,应用通常以单体或大型单一层次的应用形式部署在单一系统上。随着时间的推移,行业逐渐转向微服务架构,或者将应用拆分成多个小型的多层次应用,部署在多个系统上。行业中开始采用容器技术,并利用 Kubernetes 等工具来编排和扩展其容器化的微服务。
然而,Kubernetes 也带来了自己的挑战。虽然它是一个有效的容器编排工具,但它有一个陡峭的学习曲线,团队可能难以克服。这时,Helm 就能帮助简化在 Kubernetes 上运行工作负载的挑战。Helm 让用户可以更加简单地部署和管理 Kubernetes 应用的生命周期。它抽象了配置 Kubernetes 应用时的许多复杂性,使团队在该平台上变得更加高效。
本书将带你探索 Helm 所提供的每一项优势,并发现 Helm 如何简化在 Kubernetes 上的应用部署。你将首先扮演终端用户的角色,使用社区编写的 Helm charts,并学习如何作为包管理器利用 Helm 的最佳实践。随着本书的深入,你将转变为 chart 开发者,学习如何以易于使用且高效的方式打包 Kubernetes 应用。在本书的最后,你将了解关于应用管理和安全的 Helm 高级模式。
本章将涵盖以下主要内容:
-
从单体架构到现代微服务
-
什么是 Kubernetes?
-
部署 Kubernetes 应用
-
资源管理的方法
-
资源配置挑战
-
Helm 来拯救我们!
从单体架构到现代微服务
软件应用是现代科技中大多数技术的核心组成部分。无论它们是文字处理软件、网页浏览器还是流媒体服务,应用程序都使得用户能够完成一个或多个任务。应用程序有着悠久而丰富的历史,从电子数值积分与计算机(ENIAC)——第一台通用计算机——到载人登月的阿波罗太空任务,再到万维网(WWW)、社交媒体和在线零售的兴起。
这些应用程序可以在广泛的各种平台和系统上运行,利用物理或虚拟计算资源。根据它们的目的和资源需求,整个机器可能专门用于为应用程序提供计算和/或存储需求。幸运的是,部分归功于摩尔定律的实现,微处理器的性能和处理能力最初随着每年的推移不断提高,同时与所使用的物理资源相关的总体成本也在降低。尽管这种趋势在近年来有所减缓,但这一趋势的出现及其在处理器存在的前 30 年中的持续性对技术进步起到了重要作用。
软件开发人员充分利用了这一机会,将更多的功能和组件捆绑到他们的应用程序中。结果,单一应用程序可能包含多个小型组件,每个组件本身都可以作为单独的服务来编写。最初,将组件捆绑在一起带来了多个好处,包括简化的部署过程。然而,随着行业趋势的变化,企业越来越关注更快速交付功能的能力,单一可部署应用程序的设计带来了许多挑战。每当需要更改时,整个应用程序及其所有基础组件都需要重新验证,以确保更改不会引入不良特性。这个过程可能需要多个团队之间的协调,从而减缓了整体功能交付的速度。
提供更快速的功能交付,尤其是在组织内部的传统部门之间,也是组织所期望的。这种快速交付的概念是开发运维(DevOps)的基础,该概念在 2010 年左右开始流行。DevOps 鼓励对应用程序进行更多的迭代性更改,而不是在开发之前进行大量的规划。为了在这种新模式中保持可持续性,架构从单一的大型应用程序演变为更倾向于多个小型应用程序,这些应用程序可以更快速地交付。由于这种思维方式的变化,传统的应用程序设计被标记为单体式。这种将组件拆分成独立应用程序的新方法为这些组件创造了一个名字:微服务。微服务应用程序所固有的特征带来了几个可取的特点,包括能够并行开发和部署各个服务,并且可以独立地进行扩展(增加实例数量)。
从单体架构到微服务架构的转变,还导致了重新评估如何在运行时打包和部署应用程序。传统上,整个机器要么专门用于一个应用程序,要么专门用于两个应用程序。而现在,随着微服务减少了单个应用程序所需的资源,将整台机器专门用于一两个微服务变得不再可行。
幸运的是,一种名为容器的技术应运而生,并在填补许多创建微服务运行环境所需缺失的功能方面变得非常流行。Red Hat 将容器定义为“一组与系统其余部分隔离的进程,并包括运行所需的所有文件”(https://www.redhat.com/en/topics/containers/whats-a-linux-container#:~:text=A Linux® container is,testing%2C and finally to production.)。容器化技术在计算领域有着悠久的历史,追溯到 1970 年代。许多基础的容器技术,包括chroots(能够将进程及其子进程的根目录更改到文件系统中的新位置)和jails,至今仍在使用。
简单且可移植的打包模型与能够在每台物理机器或虚拟机(VM)上创建多个隔离沙箱的能力相结合,推动了容器在微服务领域的快速采用。2010 年代中期容器受欢迎的增长,亦可归因于 Docker,它通过简化的打包和运行时,使得容器技术得以在 Linux、macOS 和 Windows 上普及。容器镜像的分发变得更加容易,推动了容器技术的普及。这是因为初次使用者无需了解如何创建镜像,而是可以使用其他人创建的现成镜像。
容器和微服务成就了天作之合。应用程序有了打包和分发机制,同时可以共享相同的计算资源,并且能享受彼此隔离的好处。然而,随着越来越多的容器化微服务被部署,整体管理也成为了一个问题。如何确保每个运行中的容器的健康状况?如果容器失败,应该怎么办?如果底层机器没有足够的计算能力该怎么办?于是,Kubernetes 应运而生,帮助解决了容器编排的需求。
在下一节中,我们将讨论 Kubernetes 如何工作以及它如何为企业创造价值。
什么是 Kubernetes?
Kubernetes,常简称为k8s(发音为kaytes),是一个开源的容器编排平台。Kubernetes 起源于谷歌的专有编排工具 Borg,该项目在 2015 年开源并更名为 Kubernetes。随着 2015 年 7 月 21 日 v1.0 版本的发布,谷歌与 Linux 基金会合作,成立了云原生计算基金会(CNCF),该基金会是 Kubernetes 项目的当前维护者。
Kubernetes这个词源自希腊语,意思是舵手或驾驶员。舵手是负责操控船只的人,他与船上的官员密切合作,以确保航行安全和稳妥,同时保障船员的整体安全。Kubernetes 在容器和微服务方面承担着类似的责任,负责容器的编排和调度。它负责引导这些容器到合适的工作节点,以便能够处理其工作负载。Kubernetes 还通过提供高可用性(HA)和健康检查来确保这些微服务的安全。
让我们回顾一下 Kubernetes 如何简化容器化工作负载的管理。
容器编排
Kubernetes 最显著的特性是容器编排。这是一个相当复杂的术语,因此我们将其拆解为不同的部分。
容器编排是根据容器的需求,将容器部署到一组计算资源中的特定机器上。容器编排的最简单用例是将容器部署到能够满足其资源需求的机器上。在下图中,某个应用程序请求 2 Gibibytes(Gi)的内存(Kubernetes 资源请求通常使用二次幂值,在本例中大致相当于 2 gigabytes(GB))和一个中央处理单元(CPU)核心。这意味着容器将从其被调度到的底层机器上分配 2 Gi 内存和 1 个 CPU 核心。Kubernetes 负责跟踪哪些机器或节点拥有所需的资源,并将传入的容器调度到这些机器上。如果某个节点没有足够的资源来满足请求,容器将不会被调度到该节点。如果集群中没有任何节点有足够的资源来运行该工作负载,容器将不会被部署。一旦某个节点有足够的空闲资源,容器将被部署到具有足够资源的节点上:

图 1.1 – Kubernetes 编排与调度
容器编排让你不再需要跟踪机器上可用资源的工作。Kubernetes 和其他监控工具提供了这些指标的洞察。因此,开发者只需声明容器预计使用的资源数量,Kubernetes 会在后台处理剩下的工作。
高可用性(HA)
Kubernetes 的另一个好处是,它提供了帮助处理冗余和高可用性的功能。高可用性(HA)是一种防止应用程序停机的特性。它通过负载均衡器来执行,将传入的流量分配到应用程序的多个实例上。高可用性的前提是,如果某个应用实例出现故障,其他实例仍然可以接受传入的流量。在这种情况下,可以避免停机,最终用户——无论是人类还是另一个微服务——都不会意识到有一个应用实例出现了故障。Kubernetes 提供了一种称为服务的网络机制,允许应用程序进行负载均衡。我们将在本章的部署 Kubernetes 应用程序部分详细讨论服务。
可扩展性
鉴于容器和微服务的轻量级特性,开发者可以使用 Kubernetes 快速扩展他们的工作负载,进行水平和垂直扩展。
水平扩展是部署更多容器实例的过程。如果一个团队在 Kubernetes 上运行其工作负载并预计负载增加,他们可以简单地告诉 Kubernetes 部署更多实例的应用程序。由于 Kubernetes 是一个容器编排工具,开发者无需担心应用程序将部署在哪些物理基础设施上。它会在集群中找到一个具有可用资源的节点,并在那里部署额外的实例。每个额外的实例都会被添加到负载均衡池中,从而使应用程序继续保持高可用性。
垂直扩展是为应用程序分配额外的内存和 CPU。开发者可以在应用程序运行时修改其资源需求。这将促使 Kubernetes 重新部署正在运行的实例,并将它们重新调度到可以支持新资源需求的节点上。根据配置的方式,Kubernetes 可以重新部署每个实例,避免在新实例部署期间发生停机。
活跃的社区
Kubernetes 社区是一个非常活跃的开源社区。因此,Kubernetes 经常收到补丁和新特性。社区还对文档做出了许多贡献,包括官方 Kubernetes 文档以及专业或业余博客网站。除了文档外,社区还积极参与全球范围内的规划、聚会和会议,这有助于提高对平台的教育和创新。
Kubernetes 大型社区的另一个好处是,许多不同的工具被构建出来以增强所提供的能力。Helm 就是其中一个工具。正如我们在本章和本书中将看到的那样,Helm——一个由 Kubernetes 社区成员构建的工具——通过简化应用程序部署和生命周期管理,极大地改善了开发人员的体验。
了解了 Kubernetes 在管理容器化工作负载中的优势后,接下来我们将讨论如何在 Kubernetes 中部署应用程序。
在 Kubernetes 中部署应用程序
在 Kubernetes 上部署应用程序的基本方式与在 Kubernetes 外部部署应用程序类似。所有应用程序,无论是否容器化,都必须考虑以下配置细节:
-
网络
-
持久存储和文件挂载
-
资源分配
-
可用性与冗余
-
运行时配置
-
安全性
在 Kubernetes 上配置这些细节是通过与 Kubernetes 应用程序编程接口(API)进行交互来完成的。Kubernetes API 作为一组端点,可以通过这些端点查看、修改或删除不同的 Kubernetes 资源,其中许多资源用于配置应用程序的不同细节。
Kubernetes 有许多不同的 API 资源,但下表展示了一些最常见的资源:
| 资源名称 | 定义 |
|---|---|
| Pod | Kubernetes 中最小的可部署单元。封装一个或多个容器。 |
Deployment |
用于部署和管理一组 Pod。保持所需数量的 Pod 副本(默认 1 个)。 |
StatefulSet |
类似于 Deployment 资源,不同之处在于 StatefulSet 为每个 Pod 副本维护一个粘性身份,并且可以为每个 Pod 提供独特的 PersistentVolumeClaim 资源(在下文表格中进一步解释)。 |
Service |
用于在 Pod 副本之间进行负载均衡。 |
Ingress |
提供集群内部服务的外部访问。 |
ConfigMap |
存储应用程序配置,以将配置与代码解耦。 |
Secret |
用于存储敏感数据,如凭证和密钥。存储在 Secrets 资源中的数据仅通过 Base64 编码进行模糊化,因此管理员必须确保实施了适当的访问控制。 |
PersistentVolumeClaim |
用户请求的存储。用于为运行中的 Pod 提供持久化。 |
Role |
表示一组允许在 Kubernetes API 上执行的权限。 |
RoleBinding |
将角色中定义的权限授予一个或一组用户。 |
表 1.1 – 常见的 Kubernetes 资源
创建资源是部署和管理 Kubernetes 上应用程序的核心,但用户需要做些什么来创建这些资源呢?我们将在下一部分进一步探讨这个问题。
资源管理方法
为了在 Kubernetes 上部署一个应用程序,我们需要与 Kubernetes API 交互来创建资源。kubectl是我们用来与 Kubernetes API 沟通的工具。kubectl是一个命令行界面(CLI)工具,用于将 Kubernetes API 的复杂性抽象化,让最终用户更高效地在平台上工作。
让我们讨论一下如何使用kubectl来管理 Kubernetes 资源。
命令式和声明式配置
kubectl工具提供了一系列子命令,以命令式的方式创建和修改资源。以下是这些命令的一小部分:
-
create -
describe -
edit -
delete
kubectl命令遵循一种常见格式,如下所示:
kubectl <verb> <noun> <arguments>
verb指的是kubectl子命令中的一个动词,noun指的是特定的 Kubernetes 资源。例如,以下命令可以用于创建一个部署:
kubectl create deployment my-deployment --image=busybox
这将指示kubectl与部署 API 端点通信,使用来自 Docker Hub 的busybox镜像创建一个名为my-deployment的新部署。
你可以使用kubectl通过describe子命令获取关于已创建部署的更多信息,如下所示:
kubectl describe deployment my-deployment
该命令将获取关于部署的信息,并以易读的格式显示结果,允许开发人员检查 Kubernetes 上正在运行的my-deployment部署。
如果需要更改部署,开发人员可以使用edit子命令在原地修改它,如下所示:
kubectl edit deployment my-deployment
该命令会打开文本编辑器,允许你修改该部署。
当需要删除资源时,用户可以运行delete子命令,如下所示:
kubectl delete deployment my-deployment
这将调用相应的 API 端点来删除my-deployment部署。
Kubernetes 资源一旦创建,就会以JavaScript 对象表示法(JSON)资源文件的形式存在于集群中,这些文件可以导出为YAML 不是标记语言(YAML)格式文件,以提高人类可读性。以下是 YAML 格式的示例资源:
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: main
image: busybox
args:
- sleep
- infinity
上述 YAML 格式展示了一个非常基础的用例。它从 Docker Hub 部署busybox镜像,并运行sleep命令,保持 Pod 处于运行状态。
尽管使用我们刚刚描述的kubectl子命令以命令式方式创建资源可能更为简单,但 Kubernetes 也允许你以声明式方式直接管理 YAML 资源,从而获得更高的资源创建控制力。kubectl子命令并不总是允许你配置所有可能的资源选项,但直接创建 YAML 文件则能让你更灵活地创建资源,并填补kubectl子命令可能存在的空白。
在声明式创建资源时,用户首先以 YAML 格式编写要创建的资源。然后,他们使用 kubectl 工具将该资源应用到 Kubernetes API 上。而在命令式配置中,开发人员使用 kubectl 子命令来管理资源,声明式配置则主要依赖一个子命令——apply。
声明式配置通常呈现以下形式:
kubectl apply -f my-deployment.yaml
该命令向 Kubernetes 提供一个包含资源规范的 YAML 资源,尽管也可以使用 JSON 格式。Kubernetes 根据资源是否存在来推断执行的操作(创建或修改)。
可以通过以下步骤声明性地配置应用:
-
首先,用户可以创建一个名为
deployment.yaml的文件,并提供一个 YAML 格式的部署规范。我们将使用之前的相同示例,如下所示:apiVersion: apps/v1 kind: Deployment metadata: name: busybox spec: replicas: 1 selector: matchLabels: app: busybox template: metadata: labels: app: busybox spec: containers: - name: main image: busybox args: - sleep - infinity -
可以使用以下命令来创建部署:
kubectl apply –f deployment.yaml
执行该命令后,Kubernetes 将尝试按照你指定的方式创建部署。
-
如果你想通过将副本数更改为
2来修改部署,首先需要修改deployment.yaml文件,如下所示:apiVersion: apps/v1 kind: Deployment metadata: name: busybox spec: replicas: 2 selector: matchLabels: app: busybox template: metadata: labels: app: busybox spec: containers: - name: main image: busybox args: - sleep - infinity -
然后,你可以使用
kubectl apply应用更改,如下所示:kubectl apply –f deployment.yaml
执行该命令后,Kubernetes 会将提供的部署声明应用到之前已应用的部署上。此时,应用将从副本数量 1 扩展到 2。
-
在删除应用时,Kubernetes 文档实际上推荐采用命令式方式,即使用
delete子命令而不是apply,如图所示:kubectl delete –f deployment.yaml
如你所见,delete 子命令使用 –f 标志从指定的文件中删除资源。
了解了 Kubernetes 资源的创建方式后,我们现在来讨论一些资源配置中涉及的挑战。
资源配置挑战
在前面的部分中,我们讨论了 Kubernetes 有两种不同的配置方法——命令式和声明式。一个需要考虑的问题是:在使用命令式和声明式方法创建 Kubernetes 资源时,用户需要意识到哪些挑战?
让我们讨论一些最常见的挑战。
Kubernetes 资源的多种类型
首先,正如在 部署 Kubernetes 应用 部分所述,Kubernetes 中有许多不同类型的资源。为了在 Kubernetes 上有效运行,开发人员需要能够确定部署应用所需的资源,并且需要在足够深入的层次上理解它们,以便适当配置。这需要大量的平台知识和培训。虽然理解并创建资源可能听起来已经是一个很大的障碍,但这实际上只是许多不同操作挑战的开始。
保持实时和本地状态同步
我们建议的 Kubernetes 资源配置方法是将其配置保存在源代码管理中,供团队编辑和共享,这也使得源代码管理仓库成为“真理之源”。源代码管理中定义的配置(称为本地状态)通过应用到 Kubernetes 环境中来创建,然后这些资源变得活跃,或进入可以称之为活跃状态的状态。听起来似乎很简单,但当开发者需要对其资源进行修改时会发生什么?正确的做法是修改源代码管理中的文件并应用更改,以便将本地状态与活跃状态同步。然而,实际情况往往不是这样。短期内,更简单的做法是直接在活跃资源上使用kubectl edit或kubectl patch进行修改,完全跳过修改本地文件。这会导致本地状态与活跃状态之间的不一致,从而使得在 Kubernetes 上进行扩展变得困难。
应用程序生命周期难以管理
生命周期管理是一个含义丰富的术语,但在此背景下,我们将其定义为安装、升级和回滚应用程序的概念。在 Kubernetes 的世界里,安装包括部署和配置应用程序的 API 资源。初次安装将创建我们这里称为应用程序版本 1 的内容。
升级可以被看作是对一个或多个 Kubernetes 资源的修改。每一批编辑可以看作是一次单独的升级。开发者可以修改单一的服务资源,这样版本号就会提升到版本 2。开发者随后可以同时修改部署、配置映射和服务,将版本号提升到版本 3。
随着新版应用程序不断在 Kubernetes 上发布,跟踪相关 API 资源发生的变化变得更加困难。Kubernetes 在大多数情况下没有内建的方式来保留变更历史。虽然这使得升级更难跟踪,但也使得恢复先前版本的应用程序变得更加困难。例如,假设开发者之前在某个资源上进行了错误编辑。团队如何知道应该回滚到哪个版本?n-1的情况相对容易解决,因为它是最近的版本。然而,如果最新的稳定版本是在五个版本之前呢?团队往往会因为无法快速识别之前曾经有效的最新稳定配置而陷入混乱。
资源文件是静态的
这是一个主要影响声明式配置风格应用 YAML 资源的挑战。遵循声明式方法的困难部分在于,Kubernetes 资源文件本身并不是为了参数化而设计的。资源文件大多设计为在应用之前先完全编写好,且文件内容在修改之前始终是事实来源(SOT)。在处理 Kubernetes 时,这可能是一个令人沮丧的现实。一些 API 资源可能非常长,包含许多不同的可定制字段,编写和配置完整的 YAML 资源可能非常繁琐。
静态文件容易变成模板。模板指的是在不同但相似的上下文中基本保持一致的文本或代码。如果开发者管理多个不同的应用程序,并且可能管理多个不同的部署资源、多个不同的服务等,那么这就成为了一个问题。在比较不同应用程序的资源文件时,你可能会发现它们之间有大量相似的 YAML 配置。
以下截图展示了两个资源示例,它们之间有大量相似的模板配置。蓝色文本表示模板行,红色文本表示独特的行:

图 1.2 – 两个资源的模板示例
注意,在这个例子中,两个文件几乎完全相同。在管理如此相似的文件时,模板成为了团队以声明式方式管理应用程序的一大难题。
Helm 来救场了!
随着时间的推移,Kubernetes 社区发现创建和维护 Kubernetes 资源以部署应用程序是一项困难的任务。这促使了一个简单但强大的工具的开发,该工具可以帮助团队克服在 Kubernetes 上部署应用程序所带来的挑战。这个工具叫做 Helm。Helm 是一个开源工具,用于在 Kubernetes 上打包和部署应用程序。由于其与任何你最喜欢的操作系统(OS)中的包管理器类似,它通常被称为 Kubernetes 包管理器。Helm 在 Kubernetes 社区中被广泛使用,是一个 CNCF 毕业项目。
鉴于 Helm 与传统包管理器的相似性,让我们先从回顾包管理器的工作原理开始,来探索 Helm。
理解包管理器
包管理器用于简化安装、升级、回滚和删除系统应用程序的过程。这些应用程序被定义为包含目标软件及其依赖关系元数据的包。
包管理器背后的思想很简单。首先,用户将软件包的名称作为参数传递。然后,包管理器会对仓库进行查找,看看该包是否存在。如果找到,包管理器会安装由该软件包定义的应用程序及其依赖项,并将其安装到系统中指定的位置。
包管理器使得软件管理变得非常容易。举个例子,假设你想要在 Fedora 系统上安装 htop,一个 Linux 系统监控工具。安装这个软件只需要输入一条命令,如下所示:
dnf install htop --assumeyes
这条命令指示 Fedora 包管理器 dnf 在 Fedora 包仓库中查找 htop 并安装它。dnf 还会处理安装 htop 包的依赖项,因此你不必担心提前安装其要求。在 dnf 从上游仓库找到 htop 包后,它会询问你是否确定要继续操作。--assumeyes 标志会自动回答“是”,并对 dnf 可能提出的其他任何提示作出回应。
随着时间的推移,htop 的新版本可能会出现在上游仓库中。dnf 和其他包管理器允许用户高效地升级到软件的最新版本。允许用户使用 dnf 升级的子命令是 upgrade,如下所示:
dnf upgrade htop --assumeyes
这条命令指示 dnf 将 htop 升级到最新版本,同时也将其依赖项升级到包元数据中指定的版本。
尽管向前推进通常更好,但包管理器也允许用户回退并在必要时将应用程序恢复到先前的版本。dnf 使用 downgrade 子命令来实现这一点,如下所示:
dnf downgrade htop --assumeyes
这是一个强大的过程,因为包管理器允许用户在报告出关键错误或漏洞时快速回滚。
如果你想完全删除一个应用程序,包管理器也可以帮你处理。dnf 提供了 remove 子命令来实现这一目的,如下所示:
dnf remove htop --assumeyes
在本节中,我们回顾了如何使用 Fedora 上的 dnf 包管理器来管理软件包。作为 Kubernetes 的包管理器,Helm 与 dnf 相似,无论是其目的还是功能。dnf 用于管理 Fedora 上的应用程序,而 Helm 用于管理 Kubernetes 上的应用程序。接下来我们将深入探讨这一点。
Kubernetes 包管理器
由于 Helm 的设计目标是提供类似于包管理器的体验,dnf或类似工具的经验用户将立即理解 Helm 的基本概念。然而,谈到具体的实现细节时,事情变得更加复杂。dnf操作的是RPM 包管理器(RPM)包,这些包提供可执行文件、依赖关系信息和元数据。而 Helm 则使用图表。可以将 Helm 图表视为 Kubernetes 包。图表包含了部署应用所需的声明性 Kubernetes 资源文件。类似于 RPM 包,它还可以声明应用运行所需的一个或多个依赖项。
Helm 依赖于仓库来提供对图表的广泛访问。图表开发人员创建声明性的 YAML 文件,将其打包成图表,并将其发布到图表仓库。最终用户然后使用 Helm 搜索现有的图表,并将其部署到 Kubernetes,类似于dnf的最终用户搜索 RPM 包并将其部署到 Fedora。
让我们看一个基本示例。Helm 可以通过使用来自上游仓库的图表将 Redis(一个内存缓存)部署到 Kubernetes。可以使用 Helm 的install命令来执行此操作,如下所示:
helm install redis bitnami/redis --namespace=redis
这将从bitnami仓库安装redis图表到名为redis的 Kubernetes 命名空间。此安装将被称为初始修订版,或者 Helm 图表的初始安装。
如果redis图表发布了新版本,用户可以使用upgrade命令升级到新版本,如下所示:
helm upgrade redis bitnami/redis --namespace=redis
这将升级redis,以满足新版redis图表定义的规范。
对于操作系统,如果发现 bug 或漏洞,用户应该关注回滚问题。Kubernetes 上的应用也存在相同的担忧,Helm 提供了rollback命令来处理这种用例,如下所示:
helm rollback redis 1 --namespace=redis
此命令将redis回滚到其第一个修订版。
最后,Helm 提供了使用uninstall命令完全删除redis的功能,如下所示:
helm uninstall redis --namespace=redis
比较dnf和 Helm 的子命令,以及它们在下表中所执行的功能。请注意,dnf和 Helm 提供了类似的命令,提供类似的用户体验(UX):
dnf子命令 |
Helm 子命令 | 目的 |
|---|---|---|
install |
Install |
安装应用程序及其依赖项。 |
upgrade |
Upgrade |
将应用程序升级到较新版本。根据目标软件包的要求升级依赖项。 |
downgrade |
rollback |
将应用程序恢复到先前的版本。根据目标软件包的要求恢复依赖项。 |
remove |
uninstall |
删除应用程序。每个工具对处理依赖项有不同的哲学。 |
表 1.2 – dnf 和 Helm 子命令的目的
了解 Helm 作为包管理器的功能后,让我们更详细地讨论 Helm 为 Kubernetes 带来的好处。
Helm 的好处
在本章之前,我们回顾了 Kubernetes 应用程序是如何通过管理 Kubernetes 资源来创建的,并讨论了其中的一些挑战。以下是 Helm 如何克服这些挑战的几种方式。
抽象 Kubernetes 资源的复杂性
假设开发人员被要求将 WordPress 实例部署到 Kubernetes 上。开发人员需要创建配置其容器、网络和存储所需的资源。要从头开始配置此类应用程序所需的 Kubernetes 知识很高,这是新手甚至中级 Kubernetes 用户需要跨越的一个大障碍。
使用 Helm,负责部署 WordPress 实例的开发人员可以简单地从上游 chart 仓库中搜索 WordPress chart。这些 chart 已经由社区中的 chart 开发者编写,并且已经包含了部署 WordPress 和后端数据库所需的声明式配置。供应商拥有的 chart 仓库通常也会得到很好的维护,因此使用这些仓库中的 chart 的团队无需担心保持 Kubernetes 资源的最新状态。在这方面,承担此类任务的开发人员将像使用任何其他包管理器一样,充当简单的终端用户,消费 Helm。
维护修订历史的持续更新
Helm 有一个叫做发布历史的概念。当 Helm chart 第一次安装时,Helm 会将该初始修订版本添加到历史记录中。随着修订版本通过升级不断增加,历史记录会进一步修改,保留不同修订版本下应用程序配置的快照。
以下图表展示了修订历史的持续过程。蓝色的方框表示已经从先前版本修改过的资源:

Figure 1.3 – 修订历史的示例
跟踪每次修订的过程提供了回滚的机会。Helm 中的回滚非常简单。用户只需将 Helm 指向先前的修订版本,Helm 会将实时状态还原为所选修订版本的状态。Helm 允许用户根据需要将应用程序回滚到任何时间点,甚至是最初的安装版本。
以动态方式配置声明式资源
创建声明式资源时最大的麻烦之一是 Kubernetes 资源是静态的,无法参数化。如您从之前所述,资源在应用程序和类似配置之间变得一成不变,这使得团队更难将应用程序配置为代码。Helm 通过引入值和模板来缓解这些问题。
值可以被视为图表的参数。模板是基于一组给定的值动态生成的文件。这两个构造为图表开发人员提供了根据最终用户提供的值生成 Kubernetes 资源的能力。通过这样做,Helm 管理的应用程序变得更加灵活,减少了冗余代码,并且更易于维护。
值和模板允许用户执行类似以下操作:
-
参数化常见字段,例如部署中的镜像名称和服务中的端口。
-
根据用户输入生成长篇的 YAML 配置,例如在部署中的卷挂载或 ConfigMap 中的数据。
-
根据用户输入包含或排除资源。
动态生成声明性资源文件的能力使得创建基于 YAML 的资源变得更加简单,同时确保应用程序以易于重现的方式进行部署。
简化本地状态和实时状态的同步。
包管理器防止用户需要管理应用程序及其依赖关系的所有复杂细节。Helm 也遵循相同的理念。使用 Helm 的values构造,用户可以通过管理少量参数而不是多个完整的 YAML 资源,提供应用程序生命周期中的配置更改。当本地状态(值/参数)更新时,Helm 会将配置更改传播到 Kubernetes 中相关的资源。这种工作流程使 Helm 控制 Kubernetes 细节的管理,并鼓励用户在本地管理状态,而不是直接更新实时资源。
以智能的顺序部署资源。
Helm 通过预定的顺序简化了应用程序部署,该顺序规定了 Kubernetes 资源需要创建的先后顺序。这一顺序确保依赖的资源优先部署。例如,Secret 实例和 ConfigMap 实例应在部署之前创建,因为部署很可能会将这些资源作为卷使用。Helm 在无需用户干预的情况下执行这一顺序,因此这种复杂性被抽象化,防止用户需要理解资源应当以何种顺序应用。
提供自动化的生命周期挂钩。
与其他包管理器类似,Helm 提供了定义生命周期挂钩的能力。生命周期挂钩是在应用程序生命周期的不同阶段自动执行的操作。它们可以用于执行以下任务:
-
在升级时执行数据备份。
-
在回滚时恢复数据。
-
在安装之前验证 Kubernetes 环境。
生命周期钩子非常有价值,因为它们抽象了可能不是 Kubernetes 特有的任务的复杂性。例如,Kubernetes 用户可能不熟悉备份数据库的最佳实践,或者不知道何时应该执行此类任务。生命周期钩子允许专家编写自动化程序,处理各种生命周期任务,避免用户需要自行处理这些任务。
总结
本章开始时,我们探讨了采用基于微服务的架构将单体应用拆解为更小型应用的趋势。创建更加轻量、易于管理的微服务,推动了容器作为打包和运行时格式的使用,从而实现了更频繁的发布。通过采用容器,引入了额外的运维挑战,并通过使用 Kubernetes 作为容器编排平台来管理容器生命周期,解决了这些挑战。
我们讨论了 Kubernetes 应用程序可以配置的不同方式。这些资源可以通过两种不同的应用配置风格来表达:命令式和声明式。这两种配置风格各自带来了部署 Kubernetes 应用程序时所面临的一些挑战,包括理解 Kubernetes 资源如何工作的知识量,以及管理应用生命周期的挑战。
为了更好地管理构成应用程序的各项资产,引入了 Helm 作为 Kubernetes 的包管理器。通过其丰富的功能集,可以轻松管理应用程序的完整生命周期,包括安装、升级、回滚和删除。
在下一章中,我们将介绍 Helm 安装过程,并准备一个可以跟随本书示例的环境。
进一步阅读
有关构成应用程序的 Kubernetes 资源的更多信息,请参阅 Kubernetes 文档中的理解 Kubernetes 对象页面:kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/。
为了强化本章中讨论的 Helm 的一些优势,请参阅 Helm 文档中的使用 Helm页面:helm.sh/docs/intro/using_helm/。(此页面还深入探讨了 Helm 的一些基本用法,本书将在后续章节中详细讨论。)
问题
以下是一些测试你对本章知识掌握情况的问题:
-
单体应用和微服务应用有什么区别?
-
什么是 Kubernetes?它是为解决什么样的问题而设计的?
-
在向 Kubernetes 部署应用程序时,常用的
kubectl命令有哪些? -
在向 Kubernetes 部署应用程序时通常会遇到哪些挑战?
-
Helm 如何作为 Kubernetes 包管理器运作?它如何解决 Kubernetes 带来的挑战?
-
假设你想回滚一个部署在 Kubernetes 上的应用程序。哪个 Helm 命令可以执行这个操作?Helm 如何跟踪你的更改以使得回滚成为可能?
-
Helm 的四个主要命令是什么?
第二章:准备 Kubernetes 和 Helm 环境
Helm 是一个工具,提供了多种帮助用户更轻松部署和管理 Kubernetes 应用程序的好处。在用户开始体验这些好处之前,必须满足几个前提条件。首先,用户必须能够访问 Kubernetes 集群。接下来,用户应该拥有 Kubernetes 和 Helm 的命令行工具。最后,用户应了解 Helm 的基本配置选项,以便尽可能减少摩擦,提高生产力。
在本章中,我们将概述开始使用 Helm 所需的工具和概念。本章将涉及以下主题:
-
使用 minikube 准备本地 Kubernetes 环境
-
设置 kubectl
-
设置 Helm
-
配置 Helm
技术要求
在本章中,您需要在本地工作站上安装以下技术:
-
minikube
-
VirtualBox
-
Helm
-
kubectl
这些工具可以通过包管理器安装,也可以直接从源代码下载。我们将提供在 Windows 上使用 Chocolatey 包管理器、在 macOS 上使用 Homebrew 包管理器、在基于 Debian 的 Linux 发行版上使用 apt-get 包管理器、在基于 RPM 的 Linux 发行版上使用 dnf 包管理器的安装说明。
使用 minikube 准备本地 Kubernetes 环境
如果没有访问 Kubernetes 集群的权限,Helm 将无法部署应用程序。因此,我们讨论了一个选项,用户可以在自己的机器上运行本地集群——minikube。
minikube 是一个社区驱动的工具,使用户能够轻松地将一个小型单节点的 Kubernetes 集群部署到本地机器上。使用 minikube 创建的集群运行在容器或 虚拟机 (VM) 中,方便创建和后续丢弃。minikube 为我们提供了一个绝佳的方式来实验 Kubernetes,它也可以与本书中提供的示例一起用来学习 Helm。
在接下来的几节中,我们将介绍如何安装和配置 minikube,以便在学习如何使用 Helm 时,您可以拥有一个可用的 Kubernetes 集群。有关更全面的说明,请参阅官方 minikube 网站上的 入门! 页面,网址:https://minikube.sigs.k8s.io/docs/start/
安装 minikube
与本章中将安装的其他工具一样,minikube 为 Windows、macOS 和 Linux 操作系统编译了二进制文件。安装 minikube 最新版本在 Windows 和 macOS 上最简单的方式是通过包管理器,如 Windows 上的 Chocolatey 和 macOS 上的 Homebrew。Linux 用户可以通过从 minikube 的 GitHub 发布页面下载最新的 minikube 二进制文件来轻松安装,虽然这种方法也可以在 Windows 和 macOS 上使用。
以下步骤描述了如何根据您的机器和安装偏好安装 minikube。请注意,本书编写时使用的是 minikube 版本 v1.22.0,并且用于开发本书中的示例。
要通过包管理器安装(适用于 Windows 和 macOS),请根据您的操作系统运行以下命令之一:
-
对于 Windows,运行以下命令:
choco install minikube -
对于 macOS,运行以下命令:
brew install minikube
以下步骤展示了如何使用直接下载链接安装 minikube(适用于 Windows、macOS 和 Linux):
-
访问 GitHub 上 minikube 的 releases 页面:
github.com/kubernetes/minikube/releases/。 -
找到 Assets 部分,其中包含给定版本的 minikube 二进制文件:

图 2.1 – 来自 GitHub 发布页面的 minikube 二进制文件片段
-
在
minikube下。如果您正在下载 Linux 二进制文件,例如,您将运行以下命令:mv minikube-linux-amd64 minikube -
要执行 minikube,Linux 和 macOS 用户可能需要通过运行
chmod命令添加可执行权限:chmod u+x minikube -
然后,
minikube应该被移动到由PATH变量管理的位置,以便可以在命令行的任何位置执行。PATH变量包含的位置会有所不同,具体取决于您的操作系统。对于 macOS 和 Linux 用户,可以通过在终端运行以下命令来确定这些位置:echo $PATH
Windows 用户可以通过在 PowerShell 中运行以下命令来确定 PATH 变量的位置:
$env:PATH
-
使用
mv命令将minikube二进制文件移动到PATH位置。以下示例将minikube移动到 Linux 上的常见PATH位置:mv minikube /usr/local/bin/ -
您可以通过运行
minikube version来验证 minikube 是否已正确安装,并确保显示的版本与下载的版本相符:$ minikube version minikube version: v1.22.0 commit: a03fbcf166e6f74ef224d4a63be4277d017bb62e
下一步是安装容器或虚拟机管理器,以便运行本地 Kubernetes 集群。本书中,我们将选择通过 VirtualBox 在虚拟机中运行 Kubernetes,因为它灵活且可在 Windows、macOS 和 Linux 操作系统上使用。接下来我们将解释如何安装 VirtualBox。
安装 VirtualBox
像 minikube 一样,VirtualBox 也可以通过 Chocolatey 或 Homebrew 轻松安装:
-
使用以下命令在 Windows 上安装 VirtualBox:
choco install virtualbox -
使用以下命令在 macOS 上安装 VirtualBox:
brew install --cask virtualbox
VirtualBox 也可以通过 Linux 包管理器安装,但您需要先从 VirtualBox 的官方网站(https://www.virtualbox.org/wiki/Linux_Downloads)下载一个包:

图 2.2 – VirtualBox 包下载链接
下载完分发包后,您可以通过 apt-get 或 dnf 安装 VirtualBox:
-
使用以下命令在基于 Debian 的 Linux 系统上安装 VirtualBox:
apt-get install ./virtualbox-*.deb -
使用以下命令在基于 RPM 的 Linux 上安装 VirtualBox:
dnf install ./VirtualBox-*.rpm
可以在其官方下载页面 https://www.virtualbox.org/wiki/Downloads 上找到安装 VirtualBox 的替代方法。
安装了 VirtualBox 后,minikube 必须配置为使用 VirtualBox 作为默认的虚拟化程序。我们将在下一部分进行配置。
配置 VirtualBox 为默认驱动程序
可以通过将 driver 选项指定为 virtualbox 来将 VirtualBox 设置为 minikube 的默认驱动程序:
minikube config set driver virtualbox
请注意,此命令可能会产生以下警告:
❗ These changes will take effect upon a minikube delete and then a minikube start
如果你的机器上没有活动的 minikube 集群,可以安全忽略此消息。
可以通过检查 driver 配置选项的值来确认是否已更改为 VirtualBox:
minikube config get driver
如果配置更改成功,将显示以下输出:
virtualbox
除了配置默认驱动程序外,你还可以配置分配给 minikube 实例的资源,我们将在下一部分进行讨论。
配置 minikube 资源分配
默认情况下,minikube 会为虚拟机分配 2 个 CPU 和 2 GB 内存,但如果你的机器资源允许,我们建议将内存分配增加到 4 GB。这样可以避免在执行练习时遇到内存限制。
运行以下命令将虚拟机内存分配增加到 4 GB:
minikube config set memory 4000
可以通过运行以下命令来验证此更改:
minikube config get memory.
让我们继续通过讨论 minikube 的基本用法来深入了解它。
探索 minikube 的基本用法
本书中,我们将介绍一些典型的 minikube 操作中常用的关键命令。理解这些命令非常重要,尤其是在执行本书提供的示例时。幸运的是,minikube 是一个非常容易上手的工具。
minikube 有三个主要的子命令:
-
start -
stop -
delete
start 子命令用于创建一个单节点 Kubernetes 集群。它会创建一个虚拟机并在其中初始化集群。集群准备好后,命令会结束:
$ minikube start
😄 minikube v1.22.0 on Redhat 8.4
✨ Using the virtualbox driver based on user configuration
👍 Starting control plane node minikube in cluster minikube
🔥 Creating virtualbox VM (CPUs=2, Memory=4000MB, Disk=20000MB) ...
🐳 Preparing Kubernetes v1.21.2 on Docker 20.10.6 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔎 Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟 Enabled addons: storage-provisioner, default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
stop 子命令用于关闭集群和虚拟机。集群和虚拟机的状态会保存到磁盘中,允许用户再次运行 start 子命令快速恢复工作,而不必从头开始构建新的虚拟机。你应该养成在完成集群工作并希望稍后继续时运行 minikube stop 的习惯:
$ minikube stop
✋ Stopping node "minikube" ...
🛑 1 nodes stopped.
delete 子命令用于删除集群和虚拟机。此命令会清除集群和虚拟机的状态,释放先前分配的磁盘空间。下次执行 minikube start 时,将创建一个全新的集群和虚拟机:
$ minikube delete
🔥 Deleting "minikube" in virtualbox ...
💀 Removed all traces of the "minikube" cluster.
还有更多的 minikube 子命令可用,但这些是你需要了解的主要子命令。
在本地机器上安装并配置了 minikube 后,你现在可以安装 kubectl,即 Kubernetes 命令行工具,并满足使用 Helm 所需的其余前提条件。
设置 kubectl
正如我们在 第一章 中提到的,理解 Kubernetes 和 Helm,Kubernetes 是一个暴露不同 API 端点的系统。这些 API 端点用于对集群执行各种操作,例如创建、查看或删除资源。为了提供更简便的用户体验,开发人员需要一种与 Kubernetes 交互的方式,而无需管理底层的 API 层。
虽然你将在本书中主要使用 Helm 命令行工具来安装和管理应用程序,但 kubectl 是执行常见任务的必备工具。
继续阅读以了解如何在本地工作站上安装 kubectl。请注意,撰写本文时使用的 kubectl 版本是 v1.21.2。
安装 kubectl
kubectl 可以通过 minikube 安装,也可以通过包管理器获取或通过直接下载获取。首先,让我们描述一下如何通过 minikube 获取 kubectl。
通过 minikube 安装 kubectl
通过 minikube 安装 kubectl 非常简单。minikube 提供了一个名为 kubectl 的子命令,它会为你下载 kubectl 二进制文件。首先,使用 minikube kubectl 运行一个 kubectl 命令:
minikube kubectl version
此命令将 kubectl 安装到 $HOME/.minikube/cache/linux/v1.21.2 目录中。请注意,路径中包含的 kubectl 版本将取决于所使用的 minikube 版本。安装完 kubectl 后,使用以下语法访问 kubectl:
minikube kubectl -- <subcommand> <flags>
这是一个示例命令:
$ minikube kubectl -- version --client
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.2", GitCommit:"092fbfbf53427de67cac1e9fa54aaa09a28371d7", GitTreeState:"clean", BuildDate:"2021-06-16T12:59:11Z", GoVersion:"go1.16.5", Compiler:"gc", Platform:"linux/amd64"}
虽然使用 minikube kubectl 调用 kubectl 可行,但语法比直接调用 kubectl 更繁琐。可以通过将 kubectl 可执行文件从本地 minikube 缓存复制到由 PATH 变量管理的位置来克服这一问题。在每个操作系统中执行此操作的方式相似,下面是如何在 Linux 机器上实现这一点的示例:
$ sudo cp ~/.minikube/cache/linux/v1.21.2/kubectl /usr/local/bin/
完成后,kubectl 可以作为独立的二进制文件调用,如下所示:
$ kubectl version –client
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.2", GitCommit:"092fbfbf53427de67cac1e9fa54aaa09a28371d7", GitTreeState:"clean", BuildDate:"2021-06-16T12:59:11Z", GoVersion:"go1.16.5", Compiler:"gc", Platform:"linux/amd64"}
kubectl 也可以在没有 minikube 的情况下安装,正如我们在接下来的章节中将看到的那样。
在没有 minikube 的情况下安装 kubectl
Kubernetes 上游文档提供了多种不同的机制来实现这一目标,适用于各种目标操作系统,具体内容请参见 https://kubernetes.io/docs/tasks/tools/install-kubectl/。
使用包管理器
另一种不通过 minikube 安装 kubectl 的方式是使用本地包管理器。以下列表演示了在不同操作系统上如何实现:
-
使用以下命令在 Windows 上安装 kubectl:
choco install kubernetes-cli -
使用以下命令在 macOS 上安装 kubectl:
brew install kubernetes-cli -
使用以下命令在基于 Debian 的 Linux 上安装 kubectl:
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubectl -
使用以下命令在基于 RPM 的 Linux 上安装 kubectl:
cat <<EOF > /etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=1repo_gpgcheck=1gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpgEOF yum install -y kubectl
接下来我们将讨论最终的 kubectl 安装方法。
直接从链接下载
也可以通过直接从下载链接下载 kubectl。以下列表解释了如何下载版本 v1.21.2,这是本书中将使用的 kubectl 版本:
-
从
storage.googleapis.com/kubernetes-release/release/v1.21.2/bin/windows/amd64/kubectl.exe下载适用于 Windows 的 kubectl。 -
从
storage.googleapis.com/kubernetes-release/release/v1.21.2/bin/darwin/amd64/kubectl下载适用于 macOS 的 kubectl。 -
从
storage.googleapis.com/kubernetes-release/release/v1.21.2/bin/linux/amd64/kubectl下载适用于 Linux 的 kubectl。
然后,kubectl 二进制文件可以移动到 PATH 变量管理的路径中。在 macOS 和 Linux 操作系统上,确保授予文件可执行权限:
chmod u+x kubectl
可以通过运行以下命令来验证安装是否成功。
$ kubectl version --client
Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.2", GitCommit:"092fbfbf53427de67cac1e9fa54aaa09a28371d7", GitTreeState:"clean", BuildDate:"2021-06-16T12:59:11Z", GoVersion:"go1.16.5", Compiler:"gc", Platform:"linux/amd64"}
现在我们已经介绍了如何设置 kubectl,接下来可以深入本书的核心技术——Helm。
设置 Helm
一旦 minikube 和 kubectl 安装完成,下一个需要配置的工具就是 Helm。请注意,本书写作时使用的 Helm 版本为 v3.6.3。
安装 Helm
Helm 的安装包可以通过 Chocolatey 和 Homebrew 获取,从而让您在 Windows 或 macOS 上轻松安装它。在这些系统上,可以运行以下命令使用适当的软件包管理器安装 Helm:
-
使用以下命令在 Windows 上安装 Helm:
> choco install kubernetes-helm -
使用以下命令在 macOS 上安装 Helm:
$ brew install helm
Linux 用户,或希望从直接下载链接安装 Helm 的用户,可以通过以下步骤从 Helm 的 GitHub 发布页面下载归档文件:
- 在 Helm 的 GitHub 发布页面上找到 安装与升级 部分,网址为 https://github.com/helm/helm/releases:

图 2.3 – Helm GitHub 发布页面上的安装与升级部分
-
下载与您的操作系统相关的归档文件。
-
下载完成后,文件需要解压。可以通过在 PowerShell 中使用
Expand-Archivecmdlet 或在 Bash 中使用tar工具来实现:
对于 Windows/PowerShell,请使用以下示例代码:
Expand-Archive -Path helm-v3.6.3-windows-amd64.zip -DestinationPath $DEST
对于 Linux,请使用以下示例代码:
tar -zxvf helm-v3.6.3-linux-amd64.tar.gz
对于 Mac,请使用以下示例代码:
tar -zxvf helm-v3.6.3-linux-amd64.tar
helm 二进制文件可以在解压后的文件夹中找到,应该将其移动到 PATH 变量管理的路径中。
以下示例展示了如何将 helm 二进制文件移动到 Linux 系统中的 /usr/local/bin 文件夹:
sudo mv ~/Downloads/linux-amd64/helm /usr/local/bin
无论 Helm 是通过何种方式安装的,都可以通过运行helm version命令进行验证。如果结果输出类似于以下代码,则表示 Helm 已成功安装:
$ helm version
version.BuildInfo{Version:"v3.6.3", GitCommit:"d506314abfb5d21419df8c7e7e68012379db2354", GitTreeState:"clean", GoVersion:"go1.16.5"}
在你的机器上安装了 Helm 后,让我们学习基本的 Helm 配置主题。
配置 Helm
Helm 是一个具有合理默认设置的工具,允许用户在安装后无需执行大量操作即可提高生产力。话虽如此,用户可以更改或启用几种不同的选项,以修改 Helm 的行为。我们将在以下各节中介绍这些选项,首先从配置上游仓库开始。
添加上游仓库
用户可以开始配置他们的 Helm 安装的一种方法是通过添加上游图表仓库。在 第一章,理解 Kubernetes 和 Helm 中,我们描述了图表仓库包含 Helm 图表,这些图表可以更广泛地供使用。作为 Kubernetes 包管理器,Helm 可以连接到各种图表仓库以安装 Kubernetes 应用程序。
Helm 提供了repo子命令,允许用户管理已配置的图表仓库。此子命令包含其他子命令,可用于对指定的仓库执行操作。
以下是五个repo子命令:
-
add:添加一个图表仓库 -
list:列出图表仓库 -
remove:删除一个图表仓库 -
update:从图表仓库本地更新可用图表的信息 -
index:根据包含打包图表的目录生成索引文件
使用前面的列表作为指南,可以通过使用add repo子命令来添加一个图表仓库,如下所示:
$ helm repo add $REPO_NAME $REPO_URL
在安装包含的图表之前,需要添加图表仓库。本书将在后续章节中详细讨论从仓库安装图表的具体步骤。
你可以通过使用list repo子命令来确认仓库是否已成功添加:
$ helm repo list
NAME URL
bitnami https://charts.bitnami.com
已添加到 Helm 客户端的仓库将在此输出中显示。前面的示例显示名为bitnami的图表仓库已被添加,因此它出现在 Helm 客户端已知仓库的列表中。如果添加了其他仓库,它们也会出现在此输出中。
随着时间的推移,图表的更新将发布并发布到这些仓库中。仓库的元数据会被本地缓存。因此,Helm 不会自动意识到图表何时更新。你可以通过运行update repo子命令来指示 Helm 检查每个配置仓库的更新。一旦执行了此命令,你将能够从每个仓库安装最新的图表:
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈ Happy Helming!⎈
你可能还需要删除先前配置的仓库。可以通过使用repo remove子命令来完成:
$ helm repo remove bitnami
"bitnami" has been removed from your repositories
最后一个剩余的 repo 子命令形式是 index。该子命令由仓库和图表维护者用于发布新的或更新的图表。此任务将在 第八章,发布到 Helm 图表仓库 中进行更详细的讲解。
接下来,我们将讨论 Helm 插件的配置。
添加插件
插件是可供 Helm 使用的附加功能,提供额外的特性。大多数用户不需要担心 Helm 插件及其管理。Helm 本身就是一个强大的工具,具备了完整的功能集。尽管如此,Helm 社区仍然维护着多种插件,可以在核心代码之外扩展 Helm 的能力。部分流行的 Helm 插件在 Helm 文档中列出,网址为 https://helm.sh/docs/community/related/。
Helm 提供了一个 plugin 子命令用于管理插件,插件包含进一步的子命令,具体内容如下表所示:
| 插件子命令 | 描述 | 用法 |
|---|---|---|
install |
安装一个或多个 Helm 插件 | helm plugin install $URL |
list |
列出已安装的 Helm 插件 | helm plugin list |
uninstall |
卸载一个或多个 Helm 插件 | helm plugin uninstall $PLUGIN |
update |
更新一个或多个 Helm 插件 | helm plugin update $PLUGIN |
表 2.1 – Helm 插件子命令
插件可以提供多种不同的生产力增强功能。
以下是一些上游插件的示例:
-
Helm Diff:执行已部署发布与提议的 Helm 升级之间的差异比较
-
Helm Secrets:用于帮助隐藏 Helm 图表中的密钥
-
Helm Monitor:用于监控发布并在发生某些事件时执行回滚
-
Helm Unittest:用于对 Helm 图表执行单元测试
我们将继续讨论 Helm 配置选项,通过回顾可以设置的不同环境变量来改变 Helm 行为的各个方面。
环境变量
Helm 依赖环境变量的存在来配置一些底层选项。你可以配置许多变量,每个变量都可以在 helm help 输出中查看。
有几个环境变量用于存储 Helm 元数据:
-
HELM_CACHE_HOME``XDG_CACHE_HOME:设置 Helm 缓存文件的替代存储位置 -
HELM_CONFIG_HOME``XDG_CONFIG_HOME:设置 Helm 配置的替代存储位置 -
HELM_DATA_HOME``XDG_DATA_HOME:设置 Helm 数据的替代存储位置
Helm 遵循 XDG 基本目录规范,该规范旨在提供一种标准化方式来定义操作系统文件系统中不同文件的位置。基于 XDG 规范,Helm 会根据需要在每个操作系统上自动创建三个不同的默认目录:
| 操作系统 | 缓存路径 | 配置路径 | 数据路径 |
|---|---|---|---|
| Windows | %TEMP%\helm |
%APPDATA%\helm |
%APPDATA%\helm |
| macOS | $HOME/Library/Caches/helm | $HOME/Library/Preferences/helm |
$HOME/Library/helm |
|
| Linux | $HOME/.cache/helm | $HOME/.config/helm |
$HOME/.local/share/helm |
表 2.2 – Helm 元数据的默认位置
Helm 使用 helm repo update 命令。
配置路径 用于保存存储库信息,如 URL 和身份验证所需的凭据。当安装一个 chart 但该 chart 尚未位于本地缓存中时,Helm 使用配置路径查找 chart 存储库的 URL,然后从该 URL 下载 chart。
helm plugin install 命令,该插件本身存储在此位置。
除了 Helm 元数据路径之外,还有其他环境变量用于配置常规的 Helm 使用:
-
secret,它以 Base64 编码 Kubernetessecret中发布的状态。其他选项包括configmap,它将状态存储在明文的 Kubernetes 配置映射中,memory,它将状态存储在本地进程的内存中,以及sql,它将状态存储在关系型数据库中。 -
HELM_NAMESPACE环境变量用于设置 Helm 操作所发生的命名空间。这个环境变量非常方便,因为它避免了在每次调用 Helm 时都需要传递--namespace或-n标志。 -
KUBECONFIG环境变量用于设置用于 Kubernetes 集群身份验证的文件。如果未设置,该值默认为~/.kube/config。在大多数情况下,用户无需修改该值。
Helm 的另一个可以配置的组件是 Tab 补全,如下所述。
Tab 补全
Bash、Zsh 和 Fish 用户可以启用 Tab 补全功能,从而简化 Helm 使用。Tab 补全允许在按下 Tab 键时自动完成 Helm 命令,使用户可以更快地执行任务,并帮助防止输入错误。
这个过程类似于大多数现代终端仿真器默认的行为。当按下 Tab 键时,终端会通过观察命令和环境的状态来猜测接下来需要的参数。例如,cd /usr/local/b 输入可以在 Bash shell 中通过自动补全变为 cd /usr/local/bin。类似地,像 helm upgrade hello- 这样的输入可以通过自动补全变为 helm upgrade hello-world。
通过运行以下命令,可以根据所选的 shell 启用 Tab 补全:
-
对于 Bash 用户,运行以下命令:
source <(helm completion bash) -
对于 Zsh 用户,运行以下命令:
source <(helm completion zsh) -
对于 Fish 用户,运行以下命令:
helm completion fish | source
请注意,自动补全功能只会在执行了前述命令的终端窗口中生效,因此其他打开的窗口也需要运行该命令才能体验到自动补全功能。
身份验证
Helm 需要能够与 Kubernetes 集群进行身份验证,以便部署和管理应用程序。它通过引用一个 kubeconfig 文件进行身份验证,该文件指定了不同的 Kubernetes 集群及如何与它们进行交互。
如果你使用的是 minikube,则无需配置身份验证,因为 minikube 每次创建新集群时都会自动配置 kubeconfig 文件。如果你没有使用 minikube,则可能需要创建一个 kubeconfig 文件,或者根据你使用的 Kubernetes 发行版提供一个。kubeconfig 文件由三个主要组件组成:
-
clusters: 主机名或 IP 地址,以及证书颁发机构
-
users: 身份验证详细信息
-
contexts: 集群、用户与活动命名空间之间的绑定
kubeconfig 文件及其三个主要组件可以通过三个不同的 kubectl 命令创建:
-
第一个命令是
set-cluster:kubectl config set-cluster
set-cluster 命令将在 kubeconfig 文件中定义一个 cluster 条目。set-context 用于将凭证与集群关联。一旦凭证与集群之间建立了关联,你就可以使用凭证的身份验证方法进行集群身份验证。
-
下一个命令是
set-credentials:kubectl config set-credentials
set-credentials 命令将定义用户的名称以及其身份验证方法和详细信息。此命令可以配置用户名和密码对、客户端证书、承载令牌或身份验证提供程序,以允许用户和管理员指定不同的身份验证方法。
-
然后,我们有
set-context命令:kubectl config set-context
如前所述,set-context 命令指定了 cluster、credential(用户)和活动命名空间之间的名称映射。所有引用 kubeconfig 文件的调用都指向特定的上下文。
kubectl config view 命令可用于查看 kubeconfig 文件。请注意,kubeconfig 文件的 clusters、contexts 和 user 部分如何与之前描述的命令对应,如下例所示:
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority: /home/helm-user/.minikube/ca.crt
extensions:
- extension:
last-update: Mon, 13 Dec 2021 17:26:45 EST
provider: minikube.sigs.k8s.io
version: v1.22.0
name: cluster_info
server: https://192.168.49.2:8443
name: minikube
contexts:
- context:
cluster: minikube
extensions:
- extension:
last-update: Mon, 13 Dec 2021 17:26:45 EST
provider: minikube.sigs.k8s.io
version: v1.22.0
name: context_info
namespace: default
user: minikube
name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
user:
client-certificate: /home/helm-user/.minikube/profiles/minikube/client.crt
client-key: /home/helm-user/.minikube/profiles/minikube/client.key
启动 minikube 实例将自动填充 kubeconfig 文件的内容。一旦此文件存在,kubectl 和 Helm 就能够与 Kubernetes 集群进行交互。
在下一节中,我们将讨论如何处理对 Kubernetes 集群的授权。
授权/RBAC
身份验证是确认身份的手段,而授权定义了身份验证用户可以执行的操作。Kubernetes 使用 基于角色的访问控制 (RBAC) 来执行 Kubernetes 的授权。RBAC 是一个设计角色和权限的系统,可以分配给特定的用户或用户组。用户在 Kubernetes 上被允许执行的操作取决于其被分配的角色。
Kubernetes 提供了多种不同的角色。这里列出了三个常见的角色:
-
cluster-admin:允许用户对集群中的任何资源执行任何操作。 -
edit:允许用户对命名空间或 Kubernetes 资源的逻辑分组内的大多数资源进行读写操作。 -
view:这会阻止用户修改现有资源。它仅允许用户读取命名空间内的资源。
由于 Helm 使用 kubeconfig 文件中定义的凭证进行 Kubernetes 身份验证,因此 Helm 拥有相同级别的访问权限。如果允许 edit 访问权限,通常可以认为 Helm 拥有足够的权限来安装应用程序。对于 view 访问权限,Helm 将无法安装应用程序,因为这种访问级别是只读的。它可能也无法列出已安装版本的详细信息,因为 secrets 被用作默认存储驱动程序。
运行 minikube 的用户在实例创建后默认被赋予 cluster-admin 角色。虽然在生产环境中这种访问级别不是最佳实践,但对于学习和实验来说是可以接受的。如果您在使用 Minikube,您不需要担心配置授权即可跟随本书中的概念和示例。如果您与没有使用 minikube 的 Kubernetes 用户一起工作,您需要确保他们至少被授予 edit 角色,以便能够使用 Helm 部署应用程序。可以通过请求管理员运行以下命令来实现此目的(其中 $USER 是您的 Kubernetes 用户):
kubectl create clusterrolebinding $USER-edit --clusterrole=edit --user=$USER
关于 RBAC 的最佳实践将在第十二章,Helm 安全性注意事项中进行讨论,我们将在该章节详细讨论与安全性相关的概念,包括如何正确地应用角色,以防止集群中的错误和恶意行为。
总结
开始使用 Helm 需要具备多种不同的组件。在本章中,您学习了如何安装 minikube 以提供一个本地的 Kubernetes 集群,这个集群可以在本书中使用。您还学习了如何安装 kubectl,这是与 Kubernetes API 交互的官方工具。最后,您学习了如何安装 Helm 客户端,并探索了 Helm 可以配置的各种方式,包括添加仓库和插件、修改环境变量、启用标签补全以及配置与 Kubernetes 集群的身份验证和授权。
现在您已经安装了所需的工具,可以开始学习如何使用 Helm 部署第一个应用程序。在下一章中,您将从上游图表仓库安装 Helm 图表,并学习生命周期管理和应用程序配置。在完成该章节后,您将了解 Helm 如何作为 Kubernetes 的包管理器。
进一步阅读
查看以下链接,了解有关 minikube、kubectl 和 Helm 的安装选项:
-
Helm:
helm.sh/docs/intro/install/
我们已经介绍了多种安装后配置 Helm 的方法。查看以下链接,了解更多关于这些主题的信息:
-
仓库管理:
helm.sh/docs/intro/quickstart/#initialize-a-helm-chart-repository -
环境变量和
helm help输出:helm.sh/docs/helm/helm/ -
标签补全:
helm.sh/docs/helm/helm_completion/ -
通过
kubeconfig文件进行身份验证和授权:kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/
问题
回答以下问题,以测试你对本章内容的理解:
-
Helm 如何进行 Kubernetes 集群身份验证?
-
为 Helm 客户端提供授权的机制是什么?管理员如何管理这些权限?
-
helm repo add命令的作用是什么? -
用于存储 Helm 元数据的三个文件路径是什么?每个路径包含什么内容?
-
Helm 如何管理状态?有哪些选项可以更改状态存储方式?
第三章:使用 Helm 安装你的第一个应用
在本书早些时候,我们提到 Helm 是 Kubernetes 包管理器,并将其与操作系统的包管理器进行比较。包管理器允许用户快速、轻松地安装各种复杂度的应用,并管理应用可能依赖的其他组件。Helm 的工作方式类似。
用户只需确定要在 Kubernetes 上部署的应用,Helm 会完成其余的工作。一个 Helm chart——Kubernetes 资源的封装——包含了安装应用所需的逻辑和组件,允许用户在不需要了解特定资源的情况下执行安装。用户还可以向 Helm chart 传递参数(称为值),以定制应用的不同方面。在本章中,你将通过利用 Helm 作为包管理器来将 WordPress 实例部署到 Kubernetes 上,探索这些功能。
在本章中,我们将涵盖以下主要内容:
-
理解 WordPress 应用
-
查找 WordPress chart
-
创建一个 Kubernetes 环境
-
安装 WordPress chart
-
选择
--set和--values之间的区别 -
访问 WordPress 应用
-
升级 WordPress 发布版本
-
回滚 WordPress 发布版本
-
卸载 WordPress 发布版本
-
关闭你的环境
技术要求
本章将使用以下软件技术:
-
minikube
-
kubectl
-
Helm
我们假设这些组件已在你的系统上安装。有关每个工具的更多信息,包括它们的安装和配置,请参考 第二章,准备 Kubernetes 和 Helm 环境。
理解 WordPress 应用
WordPress 是一个开源的 内容管理系统(CMS),用于创建网站和博客。它有两个不同的版本——WordPress.com 和 WordPress.org。WordPress.com 是 CMS 的 软件即服务(SaaS)版本,意味着 WordPress 应用及其组件由 WordPress 托管和管理。在这种情况下,用户无需担心安装 WordPress 实例,他们只需访问已提供的实例即可。而 WordPress.org 则是自托管选项,它要求用户自行部署 WordPress 实例,并且需要一定的专业知识来维护。
由于 WordPress.com 更易于上手,可能看起来是一个更理想的选择。然而,这个 SaaS 版本的 WordPress 相比自托管的 WordPress.org,有许多缺点,如下所示:
-
它没有提供像 WordPress.org 那样多的功能
-
它没有给用户完全的控制权来管理他们的网站
-
它要求用户支付高级功能的费用
-
它不提供修改网站后端代码的能力
而自托管的 WordPress.org 版本则让用户对他们的网站和 WordPress 实例拥有完全的控制权。它提供了完整的 WordPress 功能集,从安装插件到修改后台代码。
自托管的 WordPress 实例要求用户部署一些不同的组件。WordPress 需要 MySQL 或 MariaDB 数据库来保存网站和管理数据,而 WordPress UI 则作为 PHP 前端进行部署。在 Kubernetes 中,部署这些组件意味着要创建各种不同的资源:
-
用于数据库和管理员控制台认证的
Secrets -
一个用于外部化数据库配置的
ConfigMap -
用于网络的
Services -
用于数据库存储的
PersistentVolumeClaim -
用于以有状态的方式部署数据库的
StatefulSet -
用于部署前端的
Deployment
创建这些 Kubernetes 资源需要具备 WordPress 和 Kubernetes 的专业知识。需要 WordPress 专业知识,因为用户需要了解所需的物理组件以及如何配置它们。需要 Kubernetes 专业知识,因为用户需要了解如何将 WordPress 依赖项部署为 Kubernetes 资源。鉴于所需组件的复杂性和数量,在 Kubernetes 上部署 WordPress 可能是一项艰巨的任务。
这一任务所面临的挑战非常适合使用 Helm。与其关注创建和配置我们所描述的每一个 Kubernetes 资源,用户可以利用 Helm 作为包管理器来部署和配置 Kubernetes 上的 WordPress。首先,我们将探索一个名为Artifact Hub的平台,寻找一个合适的 WordPress Helm chart。然后,我们将使用 Helm 将 WordPress 部署到你的 Kubernetes 集群中,并在过程中探索基本的 Helm 功能。
查找 WordPress chart
Helm charts 可以通过将它们发布到 chart 仓库来供用户使用。chart 仓库是存储和共享打包好的 charts 的位置。仓库通常作为 HTTP 服务器托管,可以采取多种形式,包括 GitHub Pages、Amazon S3 桶或简单的 Web 服务器,如 Apache HTTPD。最近,仓库还可以采取 OCI 注册表的形式,允许用户从托管服务(如 Docker Hub 和 Quay)保存和检索 Helm charts。
要使用仓库中的 charts,Helm 需要配置为使用该仓库。这可以通过使用helm repo add来添加仓库来实现。添加仓库时的一个挑战是,市场上有许多不同的 chart 仓库可供使用,可能很难找到适合你使用场景的特定仓库。为了更容易地找到 chart 仓库(以及其他与 Kubernetes 相关的工件),Kubernetes 社区创建了一个名为 Artifact Hub 的平台。
Artifact Hub 是上游 Kubernetes 艺术品(如 Helm 图表、操作员、插件等)的集中位置。在本章中,我们将使用 Artifact Hub 平台来搜索 WordPress Helm 图表。一旦找到合适的图表,我们将添加此图表所属的存储库,以便安装。
可以通过命令行或 Web 浏览器完成与 Artifact Hub 的交互。当使用命令行搜索 Helm 图表时,返回的结果提供了一个到 Artifact Hub 的 URL,该 URL 可以用于查找有关图表的更多信息以及如何添加其图表存储库的说明。
让我们按照这个工作流程来添加一个包含 WordPress 图表的图表存储库。
从命令行搜索 WordPress 图表
一般来说,Helm 包含两个不同的搜索命令,以帮助我们查找 Helm 图表:
-
要在 Artifact Hub 中搜索图表,请使用以下命令:
$ helm search hub
请注意,在 Helm 的先前版本中,helm search hub引用的是由 Helm 社区维护的 Helm Hub 的集中管理公共图表存储库,而不是 Artifact Hub。通过使用--endpoint参数保留了向后兼容性,该参数允许用户指定任何基于单眼的实例位置,这是支持 Helm Hub 的网络搜索和发现 Web 应用程序。
-
要搜索图表中存在的关键字,请使用以下命令:
$ helm search repo
如果之前未添加过存储库,则用户应运行helm search hub命令来定位所有公共图表存储库中可用的 Helm 图表。一旦添加了存储库,用户可以运行helm search repo来跨这些存储库搜索。
让我们在 Artifact Hub 中搜索现有的 WordPress 图表。Artifact Hub 中的每个图表都有一组可以针对其进行搜索的关键字。执行以下命令来查找包含wordpress关键字的图表:
$ helm search hub wordpress
运行此命令后,应显示类似以下内容的输出:

Figure 3.1 – 运行 helm search hub wordpress 的输出
此命令返回的输出每一行都是来自 Artifact Hub 的一个图表。输出显示了每个图表 Artifact Hub 页面的 URL。还显示了图表版本,该版本表示 Helm 图表的最新版本,以及应用程序版本,默认情况下部署该图表的应用程序版本。helm search hub命令还打印了每个图表的简要描述。
正如您可能已经注意到的那样,返回的某些值已被截断。这是因为helm search hub的默认输出是table,导致结果以表格格式返回。默认情况下,超过 50 个字符宽度的列会被截断。可以通过使用--max-col-width=0标志来避免此截断。
尝试运行以下命令,并包括 --max-col-width 标志,以表格格式查看未截断的结果:
$ helm search hub wordpress --max-col-width=0
或者,用户可以传递 --output 标志,并指定 yaml 或 json,这样可以完整打印搜索结果。
尝试再次运行先前的命令,并使用 --output yaml 标志:
$ helm search hub wordpress --output yaml
结果将以 YAML 格式显示,类似于此处显示的输出:

图 3.2 – helm search hub wordpress --output yaml 命令的输出
在本例中,我们将选择安装在前述示例输出中返回的第二个图表。要了解更多关于该图表及其安装方式的信息,请访问 artifacthub.io/packages/helm/bitnami/wordpress。我们将在下一节中深入探讨这个链接。
在浏览器中查看 WordPress 图表
使用 helm search hub 是在 Artifact Hub 上搜索图表的最快方法。然而,它没有提供安装所需的所有详细信息。具体来说,用户需要知道图表的仓库 URL,以便添加该仓库并安装图表。图表的 Artifact Hub 页面可以提供此 URL,以及其他安装细节。
一旦您将 WordPress 图表的 URL 输入到浏览器窗口中,将显示类似以下内容的页面:

图 3.3 – 来自 Artifact Hub 的 WordPress Helm 图表
来自 Artifact Hub 的 WordPress 图表页面提供了许多详细信息,包括图表的维护者(Bitnami,这是一家提供可部署到不同环境的软件包的公司)以及图表的简要介绍(说明该图表将把 WordPress 实例部署到 Kubernetes,并且作为依赖项包含 Bitnami MariaDB 图表)。该网页还提供了安装细节,包括图表支持的值,这些值用于配置安装,同时还提供了 Bitnami 的图表仓库 URL。这些安装细节使用户能够添加该仓库并安装 WordPress 图表。
在 helm repo add 命令下。这是您需要运行的命令,用来添加 Bitnami 图表仓库,这是包含我们感兴趣安装的 WordPress 图表的仓库。
Bitnami 仓库图表保留策略
最近,Bitnami Helm 社区的变化导致图表在发布后 6 个月被移除出 Bitnami 仓库。与最新版本的软件包保持一致是一种推荐的做法,这样可以包含最新的功能和安全修复。然而,由于其余的练习指定了特定的图表版本,以支持已测试集成的稳定性,因此必须使用一个替代的仓库。
幸运的是,已经提供了另一个仓库索引,其中包括所有 Bitnami 图表,而不受默认索引的保留政策限制,具体内容将在下一节中介绍。你将通过在第七章《Helm 生命周期钩子》中创建和管理自己的仓库,了解更多关于仓库索引的信息。
添加完整的 Bitnami 仓库
了解了与 Bitnami 仓库中的图表相关的注意事项后,让我们添加一个允许我们指定特定图表版本的仓库,而不必担心它们将来可能会被删除。在 helm repo add 命令中唯一的区别是仓库的 URL。
现在让我们添加仓库,并验证我们是否可以与其内容进行交互:
-
添加完整的 Bitnami 图表仓库:
$ helm repo add bitnami https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami -
通过运行
helm repo list命令,验证该图表是否已被添加:$ helm repo list NAME URL bitnami https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
现在我们已经添加了仓库,可以做更多的操作。
-
运行以下命令,查看本地配置的仓库中包含
bitnami关键字的图表:$ helm search repo bitnami --output yaml
以下是返回结果的简化列表,显示在下列输出中:

图 3.4 – helm search repo bitnami --output yaml 命令的输出
与 helm search hub 命令类似,helm search repo 命令接受关键字作为参数。使用 bitnami 作为关键字,将返回 bitnami 仓库中的所有图表,以及可能包含 bitnami 关键字的其他仓库中的图表。
为确保你现在可以访问 WordPress 图表,请运行以下带有 wordpress 参数的 helm search repo 命令:
$ helm search repo wordpress
输出将显示你在 Artifact Hub 上找到并在浏览器中查看的 WordPress 图表:

图 3.5 – helm search repo wordpress 命令的输出
在斜杠 (/) 前面的 NAME 字段中的值表示包含返回 Helm 图表的仓库名称。在写作时,bitnami 仓库中的最新版本 WordPress 图表为版本 12.1.6。这是将用于安装的版本。通过在 search 命令中传递 --versions 标志,可以查询以前的版本:
helm search repo wordpress --versions
然后你应该能看到每个可用 WordPress 图表的新行:

图 3.6 – bitnami 仓库中 WordPress 图表的版本列表
现在已经识别出 WordPress 图表并添加了该图表的仓库,我们将探索如何使用命令行进一步了解该图表,以便安装它。
显示命令行中的 WordPress 图表信息
您可以在 Helm 图表的 Artifact Hub 页面找到很多重要细节。一旦图表的仓库被本地添加,这些信息(以及更多)也可以通过以下四个 helm show 子命令从命令行查看:
-
显示图表的元数据(或图表定义):
helm show chart -
显示图表的 README 文件:
helm show readme -
显示图表的值:
helm show values -
显示图表的定义、README 文件和值:
helm show all
让我们使用这些命令与 Bitnami 的 WordPress 图表。在每个命令中,图表应该引用为 bitnami/wordpress。请注意,我们将传递 --version 标志来获取该图表版本 12.1.6 的信息。如果省略此标志,将返回图表最新版本的信息。
运行 helm show chart 命令以检索图表的元数据:
$ helm show chart bitnami/wordpress --version 12.1.6
此命令的结果将是 WordPress 图表的 图表定义。图表定义描述了诸如图表的版本、依赖项、关键词和维护者等信息:

图 3.7 – WordPress 图表定义
运行 helm show readme 命令以从命令行查看图表的 README 文件:
$ helm show readme bitnami/wordpress --version 12.1.6
该命令的结果可能看起来很熟悉,因为图表的 README 文件也显示在其 Artifact Hub 页面上。从命令行使用此选项提供了一种快速查看 README 文件的方式,而无需打开浏览器:

图 3.8 – 在命令行中显示的 WordPress 图表的 README 文件
我们可以使用 helm show values 来查看图表的值。值作为用户可提供的参数,用于自定义图表的安装。我们将在本章 创建配置文件 部分安装 WordPress 图表时运行此命令。
最后,helm show all 将之前三个命令的信息聚合在一起。如果您想一次性查看图表的所有详细信息,可以使用此命令。
现在我们已经找到并检查了一个 WordPress 图表,让我们设置一个 Kubernetes 环境,之后可以在其中安装该图表。
创建 Kubernetes 环境
为了在本章创建 Kubernetes 环境,我们将使用 minikube。我们已经在 第二章,准备 Kubernetes 和 Helm 环境 中学习了如何安装 minikube。
按照以下步骤设置 Kubernetes:
-
通过运行以下命令启动 Kubernetes 集群:
$ minikube start -
几分钟后,您应该会看到输出中类似以下内容的一行:
Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default -
一旦 minikube 集群启动并运行,创建一个专用的命名空间用于本章的练习。运行以下命令创建一个名为
chapter3的命名空间:$ kubectl create namespace chapter3
现在集群设置完成,让我们开始将 WordPress 图表安装到 Kubernetes 集群中的过程。
安装 WordPress 图表
安装 Helm 图表是一个简单的过程,应该从检查图表的值开始。在接下来的部分,我们将检查 WordPress 图表中可用的值,并描述如何创建一个文件来定制安装过程。最后,我们将安装图表并访问 WordPress 应用程序。
为配置创建一个 values 文件
你可以通过提供一个 YAML 格式的 values 文件来覆盖图表中定义的值。要创建 values 文件,你需要检查图表所支持的值。这可以通过运行之前解释过的 helm show values 命令来完成。
运行以下命令检查 WordPress 图表的值:
$ helm show values bitnami/wordpress --version 12.1.6
这个命令的结果应该是一个包含你可以设置的各种可能值的长列表,其中许多值已经设置了默认值:

图 3.9 – 运行 helm show values 返回的值列表
上面的输出显示了部分 WordPress 图表的值。这些属性中的许多值已经设置了默认值,这意味着如果这些值没有被覆盖,它们将代表图表的配置。例如,如果 image 映射下的值没有被覆盖,WordPress 图表将使用来自 docker.io 注册表的 bitnami/wordpress 容器镜像,标签为 5.8.0-debian-10-r24。
图表中以井号(#)开头的行是注释。注释可以用来解释某个值或一组值,或者用于取消设置这些值。如前面的示例所示,注释用于记录与图像相关的每个值。
如果我们进一步探索 helm show values 输出,我们可以找到与配置 WordPress 博客的元数据相关的值:

图 3.10 – 运行 helm show values 命令返回的值
如你所见,这些值用于创建一个 WordPress 用户并为你的博客命名。我们可以通过创建一个 values 文件来覆盖这些值。请在你的机器上创建一个名为 wordpress-values.yaml 的新文件。在该文件中输入以下内容:
wordpressUsername: helm-user
wordpressPassword: my-password
wordpressEmail: helm-user@example.com
wordpressFirstName: Helm_is
wordpressLastName: Fun
wordpressBlogName: Learn Helm!
如果你愿意,可以更加灵活地使用这些值。在继续查看 helm show values 的值列表时,有一个重要的值应该在开始安装之前添加到你的 values 文件中,如下所示:

图 3.11 – 运行 helm show values 后返回的 LoadBalancer 值
为了简化安装,我们将更新此值(称为 service.type)为 NodePort。我们也可以将其保持为 LoadBalancer,但那样需要使用 minikube tunnel 命令才能访问该服务。将其设置为 NodePort,你将能够直接通过本地端口访问 WordPress。
将此值添加到你的 wordpress-values.yaml 文件中:
service:
type: NodePort
一旦这个值被添加到 values 文件中,你完整的 wordpress-values.yaml 文件应如下所示:
wordpressUsername: helm-user
wordpressPassword: my-password
wordpressEmail: helm-user@example.com
wordpressFirstName: Helm_is
wordpressLastName: Fun
wordpressBlogName: Learn Helm!
service:
type: NodePort
现在 values 文件已经完成,让我们开始安装。
运行安装
我们使用 helm install 来安装 Helm 图表。标准语法如下:
helm install [NAME] [CHART] [flags]
NAME 参数是你希望给 Helm 发布指定的名称。发布 捕获了使用图表安装的 Kubernetes 资源,并跟踪应用程序的生命周期。我们将在本章中探讨发布的工作原理。
CHART 参数是要安装的 Helm 图表的名称。可以使用 <repo name>/<chart name> 来安装仓库中的图表。
helm install 中的 flags 选项允许你进一步自定义安装。flags 允许用户定义和覆盖值,指定要操作的命名空间等。可以通过运行 helm install --help 查看所有可用的标志列表。我们还可以将 --help 参数传递给其他命令,以查看它们的用法和支持的选项。
现在我们已经正确理解了 helm install 的用法,让我们运行以下命令:
$ helm install wordpress bitnami/wordpress --values=wordpress-values.yaml --namespace chapter3 --version 12.1.6
此命令使用 bitnami/wordpress Helm 图表安装一个名为 wordpress 的新发布。它使用 wordpress-values.yaml 文件中定义的值来定制安装,并将图表安装到 chapter3 命名空间。安装的是图表的 12.1.6 版本,具体由 --version 标志定义。如果没有该标志,Helm 会安装 Helm 图表的最新缓存版本。
如果图表的安装成功,你应该会看到以下输出:

图 3.12 – 成功安装 WordPress 图表的输出
该输出显示了有关安装的信息,包括发布的名称、部署时间、安装的命名空间、部署状态(即 deployed)以及修订号(由于这是发布的初始安装,因此设置为 1)。
输出还会显示与安装相关的说明列表。说明用于向用户提供有关其安装的附加信息。对于 WordPress 图表,这些说明提供了如何访问 WordPress 应用程序的信息。虽然这些说明会在安装后直接显示,但你可以随时通过运行 helm get notes 命令来检索这些说明,具体方法将在下一节中说明。
完成第一次 Helm 安装后,让我们检查发布内容,观察所应用的资源和配置。
检查您的发布
检查发布并验证其安装的最简单方法之一是列出给定命名空间中的所有 Helm 发布。为此,Helm 提供了 list 子命令。
运行以下命令查看 chapter3 命名空间中的发布列表:
$ helm list --namespace chapter3
您应该只在此命名空间中看到一个发布,如下所示:

图 3.13 – helm list 命令的输出,列出了 Helm 发布
list 子命令提供以下信息:
-
发布名称
-
发布命名空间
-
发布的最新修订号
-
最新修订的时间戳
-
发布状态
-
图表名称
-
应用程序版本
请注意,前面的输出中,状态、图表名称和应用程序版本已被截断。
虽然 list 子命令对于提供高层次的发布信息很有用,但用户可能还想了解某个特定发布的其他项目。Helm 提供了 get 子命令以提供更多关于发布的信息。
以下命令可用于提供一组详细的发布信息:
-
若要返回命名发布的所有钩子,请运行以下命令:
helm get hooks -
若要返回命名发布的清单,请运行以下命令:
helm get manifest -
若要返回命名发布的注释,请运行以下命令:
helm get notes -
若要返回命名发布的值,请运行以下命令:
helm get values -
若要返回命名发布的所有信息,请运行以下命令:
helm get all
上述命令列表中的第一个命令,helm get hooks,用于显示给定发布的钩子。钩子将在构建和测试 Helm 图表时在第七章中更详细地介绍,Helm 生命周期钩子。暂时可以将钩子理解为 Helm 在应用程序生命周期的某些阶段执行的操作。此 WordPress 安装未创建任何钩子,因此我们继续查看下一个命令。
helm get manifest 命令可用于获取作为安装一部分创建的 Kubernetes 资源列表。运行以下命令:
$ helm get manifest wordpress --namespace chapter3
执行此命令后,您将看到以下 Kubernetes 清单:
-
两个用于 MariaDB 和 WordPress 凭据的
Secrets -
两个
ConfigMaps(第一个用于配置 WordPress 应用程序,第二个用于测试,由图表开发人员执行,可以忽略)。 -
一个用于持久化 MariaDB 数据的
PersistentVolumeClaim。 -
两个用于 MariaDB 和 WordPress 的
Services -
一个用于 WordPress 的
Deployment -
一个用于 MariaDB 的
StatefulSet -
一个 MariaDB 用的
ServiceAccount
从此输出中,您可以观察到在配置 Kubernetes 资源时,您的值是如何产生影响的。一个值得注意的例子是 WordPress 服务中,type已设置为NodePort:

图 3.14 – 将服务类型设置为 NodePort
您还可以观察到我们为 WordPress 用户设置的其他值。这些值作为环境变量定义在 WordPress 部署中,如下截图所示:

图 3.15 – 设置为环境变量的值
图表提供的其余默认值没有被更改。这些默认值已经应用于 Kubernetes 资源,并且可以通过helm get manifest命令查看。如果这些值被更改,Kubernetes 资源将会被不同地配置。
让我们继续执行下一个get命令。helm get notes命令用于显示 Helm 发布的注释。如您所记得,在安装 WordPress 图表时曾显示过发布说明。说明是特定于图表的,对于 WordPress 而言,提供了有关如何访问应用程序的重要信息,您可以再次通过运行以下命令来显示这些说明:
$ helm get notes wordpress --namespace chapter3
下一个命令是helm get values,它对于回顾给定发布所使用的值非常有用。运行以下命令以查看在wordpress发布中提供的值:
$ helm get values wordpress --namespace chapter3
该命令的结果应该很熟悉,因为它们应该与wordpress-values.yaml文件中指定的值匹配:

图 3.16 – WordPress 发布中的用户提供的值
虽然回顾用户提供的值很有用,但有时可能需要返回发布所使用的所有值,包括默认值。这可以通过传入额外的--all标志来完成,如以下命令所示:
$ helm get values wordpress --all --namespace chapter3
对于这个图表,输出将会非常长。前几个值显示在以下输出中:

图 3.17 – WordPress 发布的所有值的子集
最后,Helm 提供了一个helm get all命令,可以用于汇总来自不同helm get命令的所有信息:
$ helm get all wordpress –n chapter3
在前面的命令中,我们偷偷地将-n标志替换了--namespace。从现在开始,我们将使用-n标志来提供 Helm 应该操作的命名空间。
除了 Helm 提供的命令外,kubectl命令行工具也可以用于更仔细地检查安装。例如,您可以通过运行以下命令来返回 Helm 创建的部署:
$ kubectl get deployments -l app.kubernetes.io/name=wordpress -n chapter3
您将会发现以下部署存在于chapter3命名空间中:

图 3.18 – Chapter3 命名空间中的 WordPress 部署
在上述命令中,我们通过使用 -l app.kubernetes.io/name=wordpress 参数来过滤部署。许多 Helm 图表会在它们创建的资源上添加 app.kubernetes.io/name 标签(或类似标签)。您可以使用这个标签来过滤资源,使用 kubectl 命令,只返回 Helm 创建的资源。
选择 --set 和 --values 之间的区别
当我们之前安装 WordPress 时,我们使用了 --values 标志将参数传递给 Helm 图表。然而,传递值有两种方式:
-
要显式地从命令行传递一个值,请使用以下命令:
--set -
要从 YAML 文件或 URL 中指定值,请使用以下命令:
--values
在本书中,我们将 --values 标志视为配置图表值的首选方法。这样做的原因是,当值包含在 YAML 文件中时,配置多个值会更容易。维护一个 values 文件还可以简化将这些资源保存在 Git 中,从而使得安装可以轻松复现。然而,请注意,敏感值(如密码)绝不应存储在源代码控制仓库中。当需要提供机密值时,推荐的方法是使用 --set 标志,以防止它们被提交到源代码控制中。我们将在第十二章中详细讨论安全问题,Helm 安全考虑。
--set 标志用于直接从命令行传递值。这是处理简单值的可接受方法,适用于需要配置的值数量较少的情况。当需要输入复杂值,如列表和映射时,使用 --set 标志可能会比较困难,因此不推荐使用此方法。还有其他相关的标志,如 --set-file 和 --set-string。--set-file 标志用于传递包含已配置值的文件,文件格式为 key1=val1 和 key2=val2,而 --set-string 标志则用于将所有提供的值(格式为 key1=val1 和 key2=val2)设置为字符串。
现在,让我们来探索我们刚刚安装的 WordPress 应用程序。
访问 WordPress 应用程序
WordPress 图表的发布说明提供了四个命令,您可以运行这些命令来访问您的 WordPress 应用程序(您可以使用 helm get notes wordpress -n chapter3 查看完整的发布说明)。请按照发布说明中提供的四个命令运行:
-
对于 macOS 或 Linux,请运行以下命令:
export NODE_PORT=$(kubectl get --namespace chapter3 -o jsonpath="{.spec.ports[0].nodePort}" services wordpress) export NODE_IP=$(kubectl get nodes --namespace chapter3 -o jsonpath="{.items[0].status.addresses[0].address}") echo "WordPress URL: http://$NODE_IP:$NODE_PORT/" echo "WordPress Admin URL: http://$NODE_IP:$NODE_PORT/admin" -
对于 Windows PowerShell,请运行以下命令:
$NODE_PORT = kubectl get --namespace chapter3 -o jsonpath="{.spec.ports[0].nodePort}" services wordpress | Out-String $NODE_IP = kubectl get nodes --namespace chapter3 -o jsonpath="{.items[0].status.addresses[0].address}" | Out-String echo "WordPress URL: http://$NODE_IP:$NODE_PORT/" echo "WordPress Admin URL: http://$NODE_IP:$NODE_PORT/admin"
在基于一系列kubectl查询定义了这两个环境变量后,生成的echo命令将显示访问 WordPress 的 URL。第一个 URL 是查看主页的地址,访客将通过此链接访问你的网站。第二个 URL 是进入管理员控制台的地址,网站管理员可以通过它配置和管理站点内容。
将第一个 URL 粘贴到浏览器中。你将看到类似以下内容的页面:

图 3.19 – WordPress 博客页面
这页面的几个部分可能对你来说很熟悉。首先,在屏幕顶部,博客的标题是安装时提供的wordpressBlogName值。你也可以在页面的左下角看到该值。
另一个影响主页自定义的值是wordpressUsername。如果你点击helm-user:

图 3.20 – “Hello world!” 帖子
如果你提供了不同的wordpressUsername值,那么这里的作者名称也会有所不同。
上一组命令提供的第二个链接用于访问管理员控制台。将第二个echo命令中的链接粘贴到浏览器中,你将看到以下登录页面:

图 3.21 – WordPress 管理控制台登录页面
要登录管理员控制台,输入安装过程中提供的wordpressUsername和wordpressPassword值。如果你使用了我们之前指定的相同值,那么用户名将是helm-user,密码将是my-password。
一旦你完成身份验证,管理员控制台仪表板将显示,如下所示:

图 3.22 – WordPress 管理控制台页面
如果你是 WordPress 管理员,便可以在此配置你的网站、编写文章并管理插件。如果你点击右上角的链接,进入helm-user个人资料页面,你将看到在安装过程中提供的其他几个值,如下图所示:

图 3.23 – WordPress 个人资料页面
wordpressFirstname、wordpressLastname 和 wordpressEmail 这些 Helm 值。
随时继续探索你的 WordPress 实例。完成后,继续下一节,了解如何升级 Helm 版本。
升级 WordPress 版本
升级版本是指修改版本的值或将图表更新到较新版本的过程。在本节中,我们将通过添加更多值来升级 WordPress 版本。
修改 Helm 值
在将应用程序部署到 Kubernetes 时,你通常希望运行多个副本以提供高可用性,并减少单个实例的负载。Helm charts 通常提供某种与副本相关的值,用于配置要部署的 Pod 副本数。快速浏览helm show values bitnami/wordpress --version 12.1.6命令的输出,你可以看到可以通过使用replicaCount值来增加 WordPress 的副本数:

图 3.24 – helm show values 命令中的 replicaCount
将以下行添加到你的wordpress-values.yaml文件中,以将副本数从1增加到2:
replicaCount: 2
让我们再添加一个值来设置资源请求。在查看helm show values输出时,你可以看到此 chart 在其值集中提供了一个resources映射:

图 3.25 – 资源部分下的值
像resources这样的嵌套值是 YAML 映射(或对象),它们有助于提供属性的逻辑分组。在resources映射下是一个requests映射,用于配置 Kubernetes 将分配给 WordPress 应用程序的memory和cpu值。让我们修改这些值,将内存请求减少到256Mi(256 MiB)和 CPU 请求减少到100m(100 毫核)。将这些修改添加到wordpress-values.yaml文件中,如下所示:
resources:
requests:
memory: 256Mi
cpu: 100m
在定义这两个新值后,你的整个wordpress-values.yaml文件将如下所示:
wordpressUsername: helm-user
wordpressPassword: my-password
wordpressEmail: helm-user@example.com
wordpressFirstName: Helm_is
wordpressLastName: Fun
wordpressBlogName: Learn Helm!
service:
type: NodePort
replicaCount: 2
resources:
requests:
memory: 256Mi
cpu: 100m
一旦wordpress-values.yaml文件更新了这些新值,你可以运行helm upgrade命令来升级发布,如我们将在下一节中讨论的那样。
运行升级
helm upgrade命令在基本语法上几乎与helm install命令相同,如下例所示:
helm upgrade [RELEASE] [CHART] [flags]
虽然helm install要求你提供一个新发布的名称,但helm upgrade要求你提供一个已经存在的发布名称进行升级。或者,你可以传递--install标志,如果你提供的发布名称不存在,它会指示 Helm 执行安装操作。
在values文件中定义的值可以通过--values标志提供,这与helm install命令相同。运行以下命令以使用新的值集升级 WordPress 发布:
$ helm upgrade wordpress bitnami/wordpress --values wordpress-values.yaml -n chapter3 --version 12.1.6
执行此命令后,你应该看到与helm install类似的输出。你还应该注意到REVISION字段现在显示为2:

图 3.26 – helm upgrade 的输出
如果你运行以下命令,你还应该看到wordpress Pod 已经重启:
$ kubectl get pods -n chapter3
在 Kubernetes 中,当修改其 pod 模板时,会创建新的 pod。在 Helm 中也可以观察到相同的行为。升级过程中添加的值引入了对 WordPress pod 模板的配置更改。因此,新的 WordPress pods 被创建,并使用更新后的配置。可以使用本章前面提到的 helm get manifest 和 kubectl get deployment 命令来观察这些更改。
在接下来的章节中,我们将进行更多的升级操作,以演示在升级过程中值有时会有不同的表现。
在升级过程中重用和重置值
除了在 helm install 和 helm upgrade 中都存在的 --set 和 --values 参数,helm upgrade 命令还包括两个与值相关的附加标志。
现在我们来看看这些标志:
-
--reuse-values:在升级时,重用上次发布的值 -
--reset-values:在升级时,将值重置为 chart 的默认值
如果在升级时没有通过 --set 或 --values 标志提供值,则默认会应用 --reuse-values 标志。换句话说,如果没有提供任何值,将使用上次发布时的相同值进行升级。或者,如果通过 --set 或 --values 提供了至少一个值,则默认会应用 --reset-values 标志。让我们通过一个示例来演示:
-
运行另一个不指定任何值的
upgrade命令:$ helm upgrade wordpress bitnami/wordpress -n chapter3 --version 12.1.6 -
运行
helm get values命令检查升级中使用的值:$ helm get values wordpress -n chapter3
请注意,显示的值与上次升级时的值完全相同:

图 3.27 – helm 获取值命令的输出
在升级过程中提供值时,可以观察到不同的行为。如果通过 --set 或 --values 标志传递了值,则所有未提供的 chart 值将被重置为默认值。让我们来看一下实际操作。
-
通过提供一个较小的值集来运行另一次升级,使用
--set标志:$ helm upgrade wordpress bitnami/wordpress --set replicaCount=1 --set wordpressUsername=helm-user --set wordpressPassword=my-password -n chapter3 --version 12.1.6 -
升级完成后,运行
helm get values命令:$ helm get values wordpress -n chapter3
输出将声明你仅提供了三个值,而不是你最初在 wordpress-values.yaml 文件中声明的多个值:

图 3.28 – 更新后的用户提供值
为了防止在升级过程中产生混淆,并简化值的管理方式,建议将所有值管理在一个 values 文件中。这提供了一种声明式的方法,并明确每次升级时将应用哪些值。
如果你一直在跟随本章提供的每个命令,你现在应该在你的环境中有四个 WordPress 发布的修订版。第四个修订版并不是我们希望应用程序配置的样子,因为大多数值已经被最近的升级重置为默认值。在下一节中,我们将探讨如何将 WordPress 发布回滚到包含所需值的稳定版本。
回滚 WordPress 发布
虽然前进通常是首选,但在某些情况下,返回到应用程序的先前版本更为合理。helm rollback 命令就是为了满足这个需求。让我们来描述如何将 WordPress 发布回滚到先前的状态。
检查 WordPress 历史
每个 Helm 发布都有一个历史记录(HELM_DRIVER 环境变量)。这使得不同的用户可以在 Kubernetes 集群中管理和交互 Helm 发布,前提是他们拥有包含修订版的适当 Secrets,可以使用 kubectl 从 chapter3 命名空间中获取它们:
$ kubectl get secrets -n chapter3
此命令将返回命名空间中的所有机密,但你应该在输出中看到这四个:
sh.helm.release.v1.wordpress.v1
Sh.helm.release.v1.wordpress.v2
sh.helm.release.v1.wordpress.v3
sh.helm.release.v1.wordpress.v4
每个 Secrets 都对应发布修订历史中的一个条目,可以通过运行 helm history 命令来查看:
$ helm history wordpress -n chapter3
此命令将显示每个修订版的表格,类似于以下内容(为了可读性,部分列已省略):
| 修订版 | 状态 | 描述 |
|---|---|---|
| 1 | 被取代 | 安装完成 |
| 2 | 被取代 | 升级完成 |
| 3 | 被取代 | 升级完成 |
| 4 | 已部署 | 升级完成 |
表 3.1 – 表格标题
在此输出中,每个修订版都有一个编号,以及更新时间、状态、图表名称、应用版本和描述。状态为 superseded 的修订版不再是最新的,而状态为 deployed 的修订版是当前已部署的版本。其他状态包括 pending 和 pending_upgrade,表示安装或升级正在进行中。failed 表示某个修订版安装或升级失败,unknown 表示遇到了一个 bug,可能需要提交问题或通知维护者。你不太可能遇到 unknown 状态的发布。
前面描述的 helm get 命令可以通过指定 --revision 标志来针对某个修订版进行使用。对于这次回滚,我们来确定哪个发布包含了完整的所需值。正如你可能记得的,当前的修订版 修订版 4 仅包含我们所需值的一个子集,但 修订版 3 应该包含所有我们需要的值。可以通过运行带有 --revision 标志的 helm get values 命令来验证这一点:
$ helm get values wordpress --revision 3 -n chapter3
可以通过检查此修订版来查看所有值的完整列表:

图 3.29 – 检查特定修订版的输出
可以针对修订号执行其他 helm get 命令,以进行进一步检查。如果需要,还可以针对 revision 3 执行 helm get manifest 命令,以查看将要恢复的 Kubernetes 资源的状态。
在下一节中,我们将执行回滚操作。
执行回滚
helm rollback 命令的语法如下:
helm rollback <RELEASE> [REVISION] [flags]
用户提供发布的名称和期望的修订版本号,以将 Helm 发布回滚到先前的时间点。执行以下命令,将 WordPress 回滚到 revision 3:
$ helm rollback wordpress 3 -n chapter3
rollback 子命令提供了简单的输出,打印出以下消息:
Rollback was a success! Happy Helming!
可以通过运行 helm history 命令,在发布历史中查看此回滚:
$ helm history wordpress -n chapter3
在发布历史中,你会注意到增加了第五个修订版本,状态为 deployed,描述为 Rollback to 3。当应用被回滚时,它会向发布历史中添加一个新的修订版本。请不要将其与升级混淆。最高修订版本号只是表示当前已部署的发布。务必检查修订版本的描述,以确定它是由升级还是回滚操作创建的。
你可以通过再次运行 helm get values 命令获取该发布的值,以确保回滚时使用的是期望的值:
$ helm get values wordpress -n chapter3
输出将显示最新稳定版本的值:

图 3.30 – 最新修订版的值
你可能会注意到,我们没有在 rollback 子命令中明确设置 chart 版本或发布的值。这是因为 rollback 子命令并不设计为接受这些输入。它是为了将 chart 回滚到先前的修订版,并利用该修订版的 chart 版本和值。请注意,rollback 子命令不应成为日常 Helm 操作的一部分,它应该仅限于紧急情况下使用,当应用的当前状态不稳定,需要回滚到先前稳定的状态时使用。
如果你成功地回滚了 WordPress 版本,那么你就接近本章练习的结束。最后一步是通过使用 uninstall 子命令从 Kubernetes 集群中移除 WordPress 应用,我们将在下一节中详细描述该步骤。
卸载 WordPress 发布
卸载 Helm 发布意味着删除它管理的 Kubernetes 资源。此外,uninstall 命令还会删除发布的历史记录。虽然这通常是我们想要的效果,但如果指定 --keep-history 标志,Helm 会保留发布的历史记录。
uninstall 命令的语法非常简单:
helm uninstall RELEASE_NAME [...] [flags]
通过运行 helm uninstall 命令卸载 WordPress 发布:
$ helm uninstall wordpress -n chapter3
一旦 WordPress 被卸载,你将看到以下消息:
release "wordpress" uninstalled
你还会注意到,wordpress 发布版本在 chapter3 命名空间中不再存在:
$ helm list -n chapter3
输出将是一个空表。你还可以通过尝试使用 kubectl 获取 WordPress 部署来确认发布版本不再存在:
$ kubectl get deployments -l app.kubernetes.io/name=wordpress -n chapter3
No resources found in chapter3 namespace.
如预期的那样,已经没有可用的 WordPress 部署了。然而,仍然有一个 PersistentVolumeClaim 留下:
$ kubectl get pvc -n chapter3
NAME STATUS
data-wordpress-mariadb-0 Bound
PersistentVolumeClaim 没有被删除,因为它是由 MariaDB StatefulSet 在后台创建的。在 Kubernetes 中,StatefulSet 创建的 PersistentVolumeClaim 资源如果 StatefulSet 被删除,是不会自动删除的。在 helm uninstall 过程中,StatefulSet 被删除了,但相关的 PersistentVolumeClaim 并没有被删除,正如预期的那样。可以使用以下命令手动删除 PersistentVolumeClaim:
$ kubectl delete pvc data-wordpress-mariadb-0 -n chapter3
现在我们已经完成了使用 Helm 安装、升级、回滚和卸载应用程序的示例,接下来让我们关闭 Kubernetes 环境。
关闭你的环境
首先,你可以通过运行以下命令来删除本章的命名空间:
$ kubectl delete namespace chapter3
在删除 chapter3 命名空间后,停止 minikube 虚拟机:
$ minikube stop
这将关闭虚拟机,但会保留其状态,以便你在下一个练习中可以迅速开始工作。
总结
本章中,你学会了如何安装 Helm chart 并管理其生命周期。我们从在 Artifact Hub 搜索并安装 WordPress chart 开始。在找到一个 chart 后,我们按照 Artifact Hub 页面上的说明将包含该 chart 的仓库添加到 Helm 中。接着,我们检查了 WordPress chart,创建了一组覆盖其默认值的值。这些值被保存在一个名为 wordpress-values.yaml 的 values 文件中,并在安装时提供。
在安装 chart 后,我们使用 helm upgrade 通过提供额外的值来升级发布版本。然后我们使用 helm rollback 执行回滚,将 chart 恢复到先前的状态。最后,在练习结束时,我们使用 helm uninstall 删除了 WordPress 发布版本。
本章介绍了如何作为最终用户使用 Helm,以及如何使用已有的 Helm chart。在下一章,我们将更详细地探讨 Helm chart 的概念和结构,开始学习如何创建我们自己的 Helm charts。
深入阅读
若要了解更多关于本地添加仓库、检查 charts 和使用本章中提到的四个生命周期命令(install、upgrade、rollback 和 uninstall),请访问 helm.sh/docs/intro/using_helm/。
问题
-
什么是 Artifact Hub?用户如何与其互动以查找 charts 和 chart 仓库?
-
helm get和helm show命令有什么区别? -
helm install和helm upgrade命令中的--set和--values参数有什么区别?使用其中一个相较于另一个有什么好处? -
可以使用什么命令来提供发布的版本列表?
-
默认情况下,当你升级一个发布而没有提供任何值时会发生什么?当你为升级提供了值时,这种行为有何不同?
-
假设你有五个版本的发布。将发布回滚到
revision 3后,helm history命令会显示什么? -
假设你想查看部署到某个 Kubernetes 命名空间中的所有发布。你应该运行什么命令?
-
假设你运行
helm repo add来添加一个 chart 仓库。你可以运行什么命令来列出该仓库中的所有 charts?
第二部分:Helm Chart 开发
你已经从公共仓库部署了第一个 Helm chart。现在,是时候通过学习 Helm 模板和 Helm chart 结构,开发自己的 Helm chart 了。
在本部分,我们将涵盖以下主题:
-
第四章**,搭建一个新的 Helm Chart
-
第五章**,Helm 依赖管理
-
第六章**,理解 Helm 模板
-
第七章**,Helm 生命周期钩子
-
第八章**,发布到 Helm Chart 仓库
-
第九章**,测试 Helm Charts
第四章:搭建一个新的 Helm Chart
在上一章中,你从最终用户的角度学习了如何使用 Helm,利用它作为包管理器将应用程序安装到 Kubernetes 中。以这种方式使用 Helm 需要你理解如何使用 Helm 生命周期命令(install、upgrade、rollback 和 uninstall),但并不需要你理解 Helm chart 本身是如何构建的。虽然理解 Helm CLI 命令对于安装和管理由 Helm 安装的应用程序是必要的,但仅凭这些知识并不足以让你将自己的应用程序打包成 Helm charts。
在本书的第二部分,从本章开始,我们将不再作为 Helm chart 的最终用户,而是转向成为 Helm chart 的开发者。我们将通过在接下来的几章中从零开始构建一个 Helm chart 来完成这一目标,部署一个 Guestbook 应用程序实例,这是 Kubernetes 社区中常用的示例应用程序。到第二部分结束时,你将理解编写稳健的 Helm charts 所需的概念和经验。
在本章中,我们将开始探索 Helm chart 开发,讨论以下主题:
-
理解 Guestbook 应用程序
-
理解 YAML 格式
-
使用
helm create来搭建一个新的 Helm chart -
部署搭建好的 Guestbook chart
-
探索
Chart.yaml文件 -
更新
Chart.yaml文件
技术要求
本节需要在本地机器上安装 minikube 和 helm 二进制文件。这些工具的安装和配置可以在 第二章,准备 Kubernetes 和 Helm 环境 中找到。
理解 Guestbook 应用程序
由于本书的第二部分将集中于开发 Helm chart 来部署 Guestbook 应用程序,首先让我们了解一下这个应用程序的功能以及它的架构。
Guestbook 应用程序是一个简单的 PHP: 超文本预处理器(PHP)前端,旨在将消息持久化到 Redis 后端。前端由一个对话框和一个 提交 按钮组成,如下图所示:

图 4.1 – Guestbook PHP 前端
要与该应用程序交互,用户可以按照以下步骤操作:
-
在 消息 对话框中输入一条消息。
-
点击 提交 按钮。
-
当点击 提交 按钮时,消息将被保存到 Redis 数据库并显示在页面底部,如下图所示:

图 4.2 – 提交新消息后,Guestbook 前端界面
Redis 是一个内存中的键值数据存储,在我们的 Helm 图表中,它将被集群化以提供数据复制。该集群将由一个领导节点组成,Guestbook 前端将向该节点写入数据。数据持久化后,领导节点会在每个跟随节点之间进行复制,Guestbook 副本将从这些节点读取数据,以检索并显示之前提交的消息列表。
以下图示说明了前端与 Redis 的交互方式:

图 4.3 – Guestbook 前端与 Redis 的交互
在了解应用程序如何工作后,让我们专注于启动我们的 Guestbook Helm 图表。我们将从 YAML 格式的入门开始,因为作为 Helm 图表开发人员,你将与许多使用这种格式的文件交互。
理解 YAML 格式
YAML 不是标记语言(YAML)是一种用于创建人类可读配置的文件格式。它是配置 Kubernetes 资源时最常用的文件格式,也是 Helm 图表中许多文件使用的格式。
YAML 文件遵循键值对格式来声明配置。让我们来探讨一下 YAML 的键值构造。
定义键值对
一个最基本的 YAML 键值对示例如下所示:
name: LearnHelm
在前面的示例中,name 键被赋予了 LearnHelm 的值。在 YAML 中,键和值通过冒号(:)分隔。冒号左边的字符代表键,冒号右边的字符代表值。
在 YAML 格式中,空格非常重要。以下行不是一个有效的键值对:
name:LearnHelm
请注意,冒号和 LearnHelm 字符串之间缺少空格。这将导致解析错误。冒号和值之间必须有空格。
虽然前面的示例展示了一个简单的键值对,但 YAML 允许用户配置更复杂的配对,这些配对包含嵌套元素,称为映射。下面是一个示例:
resources:
limits:
cpu: 100m
memory: 512Mi
上面的示例展示了一个包含两个键值对的 resources 对象:
| 键 | 值 |
|---|---|
resources.limits.cpu |
100m |
resources.limits.memory |
512Mi |
键是通过在 YAML 块中遵循缩进来确定的。每次缩进都会在键的名称中添加一个点(.)分隔符。当 YAML 块中不再有任何缩进时,即表示已经到达了键的值。根据常规做法,YAML 中的缩进应使用两个空格,但用户可以根据需要使用任意数量的空格,只要文档中的缩进一致即可。
重要提示
制表符在 YAML 中不受支持,使用它会导致解析错误。
了解了 YAML 键值对后,让我们现在来探索一下值可以定义为的常见类型。
值类型
YAML 文件中的值可以是不同类型的。最常见的类型是字符串,它是文本值。字符串可以通过将值用引号括起来来声明,但这并不总是必要的。如果一个值包含至少一个字母或特殊字符,那么这个值被认为是字符串,无论是否用引号括起来。多行字符串可以通过使用管道符号(|)来设置,如下所示:
configuration: |
server.port=8443
logging.file.path=/var/log
值也可以是整数。当一个值是数字字符并且没有用引号括起来时,它就是整数。以下 YAML 声明了一个整数值:
replicas: 1
将此与以下 YAML 进行比较,该 YAML 将 replicas 设置为字符串值:
replicas: "1"
布尔值也常常被使用,可以通过 true 或 false 来声明:
ingress:
enable: true
这个 YAML 将 ingress.enable 设置为 true 布尔值。其他可接受的布尔值包括 yes、no、on、off、y、n、Y 和 N。
值也可以设置为更复杂的类型,例如列表。YAML 中列表的项通过破折号(-)符号来标识。
以下演示了一个 YAML 列表:
servicePorts:
- 8080
- 8443
这个 YAML 将 servicePorts 设置为整数列表(例如 8080 和 8443)。这种语法也可以用来描述一个对象列表:
deployment:
env:
- name: MY_VAR
value: MY_VALUE
- name: SERVICE_NAME
value: MY_SERVICE
在这种情况下,env 设置为一个包含 name 和 value 字段的对象列表。列表在 Kubernetes 和 Helm 配置中经常使用,理解它们对于充分利用 Helm 非常有价值。
虽然 YAML 因其易于阅读在 Kubernetes 和 Helm 的世界中更为常见,但 JavaScript 对象表示法(JSON)格式也可以使用。让我们简要描述一下这个格式。
JSON 格式
YAML 是另一种广泛使用格式——JSON 的超集。这是一串键值对,类似于 YAML。主要的区别是,YAML 依赖于空格和缩进来正确配置键值对,而 JSON 则依赖于大括号和方括号。
以下示例将之前的 YAML 示例转换为 JSON 格式:
{
"deployment": {
"env": [
{
"name": "MY_VAR",
"value": "MY_VALUE"
},
{
"name": "SERVICE_NAME",
"value": "MY_SERVICE"
}
]
}
JSON 中的所有键都被引号括起来,并且位于冒号之前:
-
花括号(
{)表示一个块,类似于 YAML 中缩进表示块的方式。 -
方括号(
[)表示一个列表,类似于破折号在 YAML 中表示列表的方式。
YAML 和 JSON 格式有更多的构造,但这个介绍提供了足够的信息,帮助理解它们如何在 Helm 图表中使用。
在下一节中,我们将开始开发我们的 Guestbook Helm 图表,首先学习如何构建 Helm 图表。
构建 Guestbook Helm 图表
在从头开始开发一个新的 Helm 图表时,通常有用的是运行 helm create 命令,语法如下:
helm create NAME [flags]
helm create 命令为你的 Helm 图表提供一个新的项目文件夹。在这个文件夹中,包含了一个基本的 Helm 图表骨架,你可以用它开始开发你的图表。
让我们运行 helm create 命令来构建我们的 Guestbook Helm 图表:
-
在终端中,进入您希望存储 Helm charts 的目录,然后运行
helm create命令:$ helm create guestbook Creating guestbook -
查看已创建的文件列表:
$ ls –al guestbook . .. charts/ Chart.yaml .helmignore templates/ values.yaml $ ls –l guestbook/templates/ deployment.yaml _helpers.tpl hpa.yaml ingress.yaml NOTES.txt serviceaccount.yaml service.yaml tests/
如您所见,helm create 命令为您生成了一个名为 guestbook 的新文件夹,代表了 Helm chart 的名称。这个文件夹不一定非得叫做 guestbook,但由于这是我们要创建的 Helm chart 的名称,所以确保文件夹名称与之匹配是个好主意。
在 guestbook 文件夹下,有几个不同的文件和文件夹组成了您的 Helm chart。刚开始时可能会让人感觉有些复杂,但随着我们在接下来的章节中逐步开发 Guestbook chart,您会更深入地了解每个组件。到第二部分结束时,这些文件将变得更加清晰,您将能够轻松地开始下一个 Helm chart 的开发!
现在,让我们从高层次了解一下 helm create 为我们生成的每个文件。正如您将在下表中看到的那样,虽然一些文件在新建 Helm chart 时并不是严格要求的,但 helm create 为我们提供了它们作为最佳实践:
| 文件/目录 | 定义 | 是否必需? |
|---|---|---|
charts/ |
一个包含父 chart 所依赖的依赖项或 Helm charts 的目录。 | 否 |
Chart.yaml |
包含 Helm chart 元数据的文件。 | 是的 |
.helmignore |
包含应该从 Helm chart 打包中排除的文件和目录的文件。 | 否 |
templates/ |
一个包含 Golang 模板的目录,主要用于生成 Kubernetes 资源。 | 是的,除非 chart 包含依赖项 |
templates/*.yaml |
用于生成 Kubernetes 资源的模板文件。 | 是的,除非 chart 包含依赖项 |
templates/_*.tpl |
包含模板助手的文件。 | 否 |
templates/NOTES.txt |
用于在 chart 安装后生成使用说明的模板文件。 | 否 |
templates/tests/(或更通用地,templates/*/) |
用于分组不同模板的文件夹。这个目录纯粹是为了美观,不会影响 Helm chart 的运行方式——例如,templates/tests 用于分组用于测试的模板。 |
否 |
values.yaml |
包含 chart 默认值的文件。 | 否,但每个 chart 应该包含此文件作为最佳实践 |
表 4.2 – 使用“helm create”命令创建的文件
除了前面表格中列出的文件外,Helm chart 还可以包含一些 helm create 没有为我们包含的其他文件。让我们在以下表格中高层次地了解这些文件:
| 文件/目录 | 定义 | 是否必需? |
|---|---|---|
Chart.lock |
用于保存或 锁定 先前应用的依赖项版本的文件。 | 否 |
crds/ |
一个包含 templates/ 的目录。 |
否 |
README.md |
一个包含 Helm chart 安装和使用信息的文件。 | 不是,但每个 Helm chart 都应该包含这个文件作为最佳实践 |
LICENSE |
一个包含 chart 许可证的文件,提供有关使用和再分发权的信息。 | 否 |
values.schema.json |
一个包含 chart 值模式的 JSON 格式文件,用于提供输入验证。 | 否 |
表 4.3– 其他 Helm chart 文件
同样,我们将在本章及接下来的几章中,当这些文件变得与我们的讨论主题相关时,深入探讨每个文件的详细内容。
现在,让我们专注于helm create为我们生成的guestbook目录中的内容。你可能会感到惊讶的是,运行helm create后,你已经在guestbook文件夹中拥有一个完全可用的 Helm chart!让我们以当前状态安装 Guestbook chart,看看它部署了什么。
部署生成的 Guestbook chart
在安装 chart 之前,让我们按照以下步骤设置你的 Kubernetes 环境:
-
通过运行
minikube start命令启动 Minikube:$ minikube start -
创建一个名为
chapter4的新命名空间:$ kubectl create namespace chapter4
现在,让我们继续安装你生成的 chart,并查看已部署的资源。在第三章,使用 Helm 安装你的第一个应用中,我们通过提供bitnami/wordpress这个名称安装了一个远程仓库中的 Helm chart,这个名称引用了远程仓库和其中包含的 chart。或者,你也可以通过提供一个有效的 Helm chart 项目文件夹的本地路径来安装一个 chart。这使得你可以轻松测试你的 Helm charts 并查看进展,而无需先将 chart 发布到仓库中。
-
通过运行以下命令安装你的 chart,其中
./guestbook表示由helm create生成的文件夹:$ helm install guestbook ./guestbook -n chapter4 NAME: guestbook LAST DEPLOYED: Sun Sep 19 10:39:40 2021 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: 1\. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=guestbook,app.kubernetes.io/instance=guestbook" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT -
运行
helm get manifest来查看已部署的资源:$ helm get manifest guestbook –n chapter4
如输出所示,你生成的 Guestbook chart(应用了默认值)包含一个服务账户、一个服务和一个部署。如果仔细查看部署,你会发现有关已部署的镜像有些有趣的地方:
image: "nginx:1.16.0"
确实,使用helm create生成的新的 Helm chart 从一个基本的 NGINX chart 开始。NGINX是一个流行的开源 Web 服务器和反向代理。由于它的安装需要许多与其他 Kubernetes 应用程序相同的资源,它是编写新 Helm charts 时的一个很好的起点。
继续访问 NGINX 应用程序,以验证它是否正确安装。
-
由于 Helm chart 创建了一个
ClusterIP服务,让我们运行kubectl port-forward,以便我们可以访问 NGINX pod。请记住,尽管我们的 Guestbook chart 已安装 NGINX,但部署的资源仍被称为guestbook,因为那是我们 chart 的名称:$ kubectl -n chapter4 port-forward svc/guestbook 8080:80 -
在新的终端窗口中(因为当前窗口会在
kubectl port-forward命令激活时阻塞),使用curl命令访问 NGINX:$ curl localhost:8080 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1>
如你所见,我们已经成功访问了 NGINX。现在,让我们继续清理这个 Helm 发布。
-
在运行
kubectl port-forward命令的终端窗口中,按 Ctrl + c。或者,你可以关闭窗口。 -
卸载
guestbookHelm 发布:$ helm uninstall guestbook –n chapter4 release "guestbook" uninstalled -
接下来,你可以删除
chapter4命名空间,因为在本章余下部分我们不再需要它:$ kubectl delete namespace chapter4 namespace "chapter4" deleted -
最后,你可以关闭你的
minikube集群:$ minikube stop
你可能会在完成这项练习后发现,我们的 Guestbook 图表与本章开始时展示的架构并不完全相符。然而,通过提供一个我们将用作起点的框架,我们已经在为部署所需架构创建 Helm 图表的过程中迈出了重要一步。在下一章,我们将继续完善我们的 Guestbook 图表,并学习如何管理依赖关系。届时,我们将声明一个依赖关系来安装 Redis 以及 Guestbook 架构的后端。
在接下来的部分中,我们将深入探讨 Helm 图表中最重要的文件之一,Chart.yaml。然后,在本章末尾,我们将更新此文件,以提供与我们的 Guestbook 图表对齐的新设置。
理解 Chart.yaml 文件
Chart.yaml 文件,也称为图表定义文件,用于存储有关 Helm 图表的不同元数据。此文件是必需的,如果图表中没有包含它,你将收到以下错误:
Error: Chart.yaml file is missing
当你运行 helm create 时,系统会为你创建一个 Chart.yaml 文件。我们可以通过运行以下命令来查看此文件:
$ cat guestbook/Chart.yaml
将显示如下输出:

图 4.4 – Guestbook Chart.yaml 文件
一个 Chart.yaml 文件可以包含许多不同的字段,其中一些是必需的,而大多数其他字段是可选的。让我们仔细看看我们 Guestbook 图表的 Chart.yaml 文件中提供的每个字段。
| 字段 | 描述 | 是否必需 |
|---|---|---|
apiVersion |
图表的 API 版本 | 是 |
name |
Helm 图表的名称 | 是 |
description |
Helm 图表的简短描述 | 否 |
type |
Helm 图表的类型(可以是 Application 或 Library) |
否 |
version |
Helm 图表的版本,使用 SemVer 格式。 | 是 |
appVersion |
Helm 图表部署的应用程序版本。无需使用 SemVer 格式。 | 否 |
表 4.4 – 从生成的 Chart.yaml 文件中提取的字段
正如你从 Guestbook 的 chart 定义中看到的那样,我们的 chart 的 apiVersion 值设置为 v2。apiVersion 为 v2 的 chart 仅与 Helm 3 兼容。另一个可能的 apiVersion 值是 v1,但这是针对 Helm 2 的旧版本。apiVersion 为 v1 的 chart 在处理依赖项时方式不同,且不支持 library charts(我们将在本书后面更详细地讨论这些话题)。Helm 3 事实上与 apiVersion v1 向后兼容,但为了利用 Helm 的最新功能并避免弃用,新的 Helm chart 应使用 apiVersion v2 创建。
根据 Chart.yaml 文件,我们搭建的 Helm chart 类型是 application chart。Helm chart 可以是 application chart 或 library chart。application chart 用于将应用部署到 Kubernetes,而 library chart 用于提供可重用的帮助模板。我们将在第六章《理解 Helm 模板》中详细讨论 library charts。Chart.yaml 文件中的 type 字段是可选的,默认值为 application。
在我们的 chart 定义中,name、description、version 和 appVersion 字段用于提供标识我们 chart 的元数据。例如,回想一下在 第三章《用 Helm 安装你的第一个应用》中,我们从命令行使用 Artifact Hub 搜索 WordPress charts。我们运行了 helm search hub wordpress 命令,并看到了如下输出:

图 4.5 – 名称、版本、appVersion 和描述的示例
这些字段来自于 Chart.yaml 文件中的对应字段。请注意,你也可以在任何 chart 的 Artifact Hub 页面上查看这些信息。
除了我们搭建的 Chart.yaml 文件中包含的字段外,还有许多其他字段用于描述你的 chart,如下表所示:
| 字段 | 描述 | 是否必填? |
|---|---|---|
kubeVersion |
兼容的 Kubernetes 版本范围,采用 SemVer 格式。 | 否 |
keywords |
描述 Helm chart 的关键词列表。关键词也用于为 helm search 命令提供搜索词。 |
否 |
home |
Helm chart 的主页 URL。 | 否 |
sources |
一个 URL 列表,指向 Helm chart 使用的源代码。 | 否 |
dependencies |
列出你的 Helm chart 所依赖的其他 chart。 | 否 |
maintainers |
Helm chart 的维护者列表。 | 否 |
icon |
用于表示 Helm chart 的图标,格式可以是 SVG 或 PNG。会在 chart 的 Artifact Hub 页面上显示。 | 否 |
deprecated |
表示该 Helm chart 是否已被弃用。 | 否 |
annotations |
用于提供自定义元数据的注释列表。 | 否 |
表 4.5 – 其他 Chart.yaml 字段
kubeVersion 字段用于针对目标 Kubernetes 集群版本提供验证。如果您的 Helm 图表使用的资源仅与某些 Kubernetes 版本兼容,则此字段非常有用。您可以将 kubeVersion 设置为诸如 >= 1.18.0 < 1.20.0 的字符串,Helm 将确保仅在 Kubernetes 版本大于或等于 1.18.0 且小于(排除)1.20.0 时安装该图表。您还可以使用 OR(||)运算符,例如 >= 1.15.0 <= 1.17.0 || >= 1.18.0 < 1.20.0。
dependencies 字段是这些可选字段中最实用的。在 dependencies 字段下添加的图表将与您的 Helm 图表资源一起安装。我们将在下一章中更详细地探讨依赖关系。
正如前面展示的 name、version、appVersion 和 description 字段一样,Chart.yaml 的每个其他属性也会影响 Helm 图表在 Artifact Hub 中的显示。查看以下截图,取自 Bitnami 的 WordPress 页在 Artifact Hub 中的展示:

图 4.6 – 在 Artifact Hub 上显示的 Chart.yaml 元数据
让我们将其与 WordPress 的 Chart.yaml 文件进行比较,通过运行 helm show values bitnami/wordpress 获取:

图 4.7 – Bitnami/WordPress 的 Chart.yaml 文件
注意 Chart.yaml 中的 home、sources、maintainers、dependencies 和 keywords 也在 Artifact Hub 中显示。
虽然不需要提供所有 Chart.yaml 字段的完整内容,但如果要将您的图表发布到 Artifact Hub 或其他可以显示图表元数据的存储库中,则这样做是件好事。否则,可以自行决定并提供您认为相关且必要的字段。除了 apiVersion、name 和 version 外,我们建议至少提供 appVersion 和 description,因为这些字段提供了应用程序的高级概览。如果您正在编写供公共使用的 Helm 图表,还应考虑添加 maintainers、home 和 sources,如果要发布到 Artifact Hub,则还应包括 keywords,以便轻松发现图表。
了解了 Chart.yaml 字段后,让我们通过更新我们的脚手架图表定义来更好地适应我们的 Guestbook 应用程序,来完成本章。
更新 Guestbook 的 Chart.yaml 文件
通过 helm create 生成的脚手架 Chart.yaml 文件围绕 NGINX 而不是我们期望的 Guestbook 进行了定制。让我们进行一些快速更改来改进内容:
-
首先,让我们更新图表描述,以更好地描述我们的图表将部署的应用程序。将
Chart.yaml的description字段更新为以下内容(或随意提供您自己的内容):description: An application used for keeping a running record of guests -
接下来,让我们提供一个更合适的
appVersion设置,以更好地适应我们的 chart 将要部署的 Guestbook 版本。Guestbook 的最新版本是v5,所以我们将这个版本作为我们的appVersion:appVersion: v5
我们的Chart.yaml文件现在应该像这样(去除了注释):

图 4.8 – 更新后的 Guestbook Chart.yaml 文件
欢迎添加其他的Chart.yaml字段,但这些更改至少能让我们处于一个良好的状态,其中Chart.yaml元数据准确地反映了我们将要部署的应用程序。
我们将在下一章中重新审视Chart.yaml字段,当我们添加一个用于部署 Redis 的 chart 依赖时。
摘要
在这一章中,我们开始窥探 Helm chart 开发的世界,通过介绍 Helm chart 和 chart 定义结构。一个 Helm chart 由 chart 定义(一个Chart.yaml文件)和用于生成 Kubernetes 资源的模板文件组成。chart 定义用于为 chart 提供身份信息,包括元数据,如 chart 名称、版本、描述以及 chart 所部署的应用程序版本。
我们还介绍了一个名为 Guestbook 的应用程序,并开始编写一个 Helm chart,用于部署此应用程序。我们运行了helm create命令来生成一个新的 Helm chart,并更新了Chart.yaml文件,以更好地反映我们将要部署的应用程序。在下一章中,当我们为安装 Redis(我们的 Guestbook 前端依赖的后端服务)添加依赖项时,我们将再次回到Chart.yaml文件。
进一步阅读
要了解更多关于 Helm chart 结构和Chart.yaml文件的信息,请访问 Helm 文档:helm.sh/docs/topics/charts/。有关 Guestbook 应用程序的更多信息,请访问kubernetes.io/docs/tutorials/stateless-application/guestbook/。
问题
-
Kubernetes 和 Helm 中最常用的文件格式是什么?
-
用于生成新 Helm chart 的命令是什么?
-
Helm chart 的名称和版本在哪里定义?
-
Chart.yaml文件中需要的三个必填字段是什么? -
Helm charts 可以由许多不同的文件组成。哪些文件是必需的?
-
哪个 Helm chart 文件夹用于包含 Kubernetes 资源模板?
-
哪个
Chart.yaml字段用于描述 Helm chart 所部署的应用程序版本?
第五章:Helm 依赖管理
正如您可能记得的,第四章,《搭建一个新的 Helm 图表》中,我们正在开发的 Helm 图表 guestbook 将部署两个主要组件。第一个是 Redis 后端,用于持久化消息列表。第二个组件是前端,用户在其中输入消息。在本章中,我们将重点更新 Helm 图表,部署第一个主要组件——Redis。
要部署 Redis,您可能会认为我们需要对现有的 guestbook 图表进行大量修改。然而,事实并非如此。Artifact Hub 上有许多 Redis Helm 图表,因此我们可以利用 Helm 的依赖管理功能,将其中一个图表声明为依赖项。然后,当 guestbook 图表安装到 Kubernetes 集群中时,依赖项也会被安装。通过将 Redis 声明为依赖项,我们可以减少为应用程序创建后端所需的工作量。
在本章中,我们将探讨 Helm 如何管理依赖项。然后,我们将利用所学的内容将 Redis 依赖项加入到我们的 Helm 图表中。
在本章中,我们将涵盖以下主题:
-
声明图表依赖项
-
依赖项映射
-
下载图表依赖项
-
条件依赖项
-
修改依赖项名称和值
-
更新 guestbook Helm 图表
-
清理工作
技术要求
本章需要以下工具:
-
minikube -
kubectl -
helm -
git
我们将在本章中使用 minikube 来探索几个示例,现在是启动您的 minikube 环境的好时机:
$ minikube start
一旦 minikube 启动,请为本章创建一个新的命名空间:
$ kubectl create namespace chapter5
在本章中,我们将跟随几个示例,以更好地理解图表依赖项在实践中的工作原理。确保您克隆了示例仓库,以便跟随示例进行操作。克隆仓库时,请运行以下命令:
$ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git
仓库克隆完成后,让我们继续到下一部分,开始学习 Helm 图表依赖项。
声明图表依赖项
图表依赖项用于安装另一个图表的资源,例如 Helm 图表(称为 wordpress 图表,用于安装 WordPress 应用实例和 MariaDB 后端)。您可能会惊讶地发现,安装的 MariaDB 数据库并不是一个本地的 WordPress 图表资源——它是一个依赖项!我们可以通过运行 helm show chart 命令来确认这一点,查看 wordpress Chart.yaml 文件中声明的依赖项:
$ helm show chart bitnami/wordpress --version 12.1.4
在输出中,您将看到如下的 dependencies 映射:
dependencies:
- condition: mariadb.enabled
name: mariadb
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 9.x.x
- condition: memcached.enabled
name: memcached
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 5.x.x
- name: common
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
tags:
- bitnami-common
version: 1.x.x
在这里,你可以看到mariadb是第一个列出的依赖项。第二个依赖项,memcached,是一个内存中的键/值对数据库,在我们部署 WordPress 时没有安装 第三章 ,使用 Helm 安装你的第一个应用,因为它依赖于将memcached.enabled值设置为true(默认情况下该值为false)。第三个依赖项,common,是一个库图表。我们将在 第六章 ,理解 Helm 模板中更详细地探讨库图表。
之前列出的 WordPress 依赖项是你可能在其他Chart.yaml文件中看到的例子。让我们来看一下所有可能的与依赖项相关的Chart.yaml字段,以理解如何声明图表依赖项。
依赖项映射
Chart.yaml中的dependencies映射支持许多不同的字段来声明依赖项并改变它们的行为。让我们来看一下这个映射中包含的字段:
| 字段 | 描述 | 是否必需 |
|---|---|---|
| 名称 | 依赖项图表的名称 | 是 |
| 仓库 | 依赖项图表所在的位置 | 是 |
| 版本 | 图表依赖项的版本 | 是 |
| 条件 | 一个布尔值,决定是否包含该依赖项 | 否 |
| 标签 | 一个布尔值列表,用来决定图表是否应当被包含 | 否 |
| import-values | 源值到父级值的映射 | 否 |
| 别名 | 给依赖项指定的替代名称 | 否 |
表 5.1 – Chart.yaml 中的依赖项字段
dependencies映射中必需的最小字段是name、repository和version。我们可以看到这些字段在 WordPress 的Chart.yaml文件中被使用,其中第一个依赖项叫做mariadb,仓库是protect-eu.mimecast.com/s/ax_4C5lrwTkYXJhOWg2e?domain=raw.githubusercontent.com,版本是9.x.x。我们将在下载图表依赖项一节中进一步了解这三个字段。
condition和tags字段用于根据特定值的设置有条件地包含依赖项。WordPress 图表的第一个依赖项mariadb将condition字段设置为mariadb.enabled,而其第三个依赖项(common)使用了一个名为bitnami-common的标签。我们将在条件依赖项一节中使用这些设置来探讨条件依赖项。
剩余的字段,alias和import-values,提供了操控依赖项图表值的方法。我们将在更改依赖项名称和值一节中进一步了解这些字段。
现在我们已经概述了与依赖项相关的每个字段,让我们学习如何下载在Chart.yaml中声明的依赖项。然后,我们将深入探讨如何使用每个字段,并涵盖多个示例场景。
正在下载图表依赖
可以使用以下表格中列出的helm dependency子命令查看并下载图表依赖:
| 命令 | 描述 |
|---|---|
helm dependency list |
列出给定图表的依赖项。 |
helm dependency update |
下载Chart.yaml中列出的依赖并生成Chart.lock文件。 |
helm dependency build |
下载Chart.lock中列出的依赖。如果找不到Chart.lock文件,则此命令将模拟helm dependency update命令的行为。 |
让我们通过使用本书 GitHub 仓库中chapter5/examples文件夹中的示例 Helm 图表来探索这些命令;我们在本章开始时克隆了这些图表。我们将从使用basic-fields图表开始:
-
使用位于
chapter5/examples/basic-fields中的basic-fields图表,列出图表声明的依赖:$ helm dependency list chapter5/examples/basic-fields
你将看到类似于以下的输出:

图 5.1 – 示例 helm 依赖列表输出
helm dependency list命令用于快速查看图表声明的依赖及其下载状态。从前面的输出中,你可以看到basic-fields图表声明了一个依赖mariadb,并且其状态当前为missing。当状态标记为missing时,表示你还没有下载该依赖,因此图表无法安装。现在,让我们下载这个依赖。
-
使用
helm dependency update命令下载basic-fields图表的依赖:$ helm dependency update chapter5/examples/basic-fields
你将看到以下输出:

图 5.2 – helm dependency update 的输出
- 运行
helm dependency list命令确认依赖已下载。为了简洁,你可以使用helm dep list来运行此命令,因为helm dep是helm dependency的简写。为了清晰起见,本书将继续使用helm dependency,但你可以自由使用简写来减少输入工作。
回到手头的任务,让我们通过运行以下命令来确认下载是否成功:
$ helm dependency list chapter5/examples/basic-fields
你将看到与我们之前看到的输出类似,只不过状态已更新为ok:

图 5.3 – 更新后的 helm 依赖列表状态
当helm dependency update成功时,你将看到依赖的状态变为ok,并且你还会看到几个新文件出现在图表目录中。首先,你会看到依赖图表已下载到新创建的charts/文件夹下,并且你还会看到一个Chart.lock文件。
让我们来看一下这些新文件。
-
使用
ls命令查看已下载的依赖:$ ls chapter5/examples/basic-fields/charts mariadb-9.5.0.tgz
如你所见,依赖项以 .tgz 文件扩展名的 gzip 压缩包形式下载。文件名包含了依赖名称和其版本。
-
使用
cat命令查看生成的Chart.lock文件:$ cat chapter5/examples/basic-fields/Chart.lock
你将看到如下输出:

图 5.4 – 生成的 Chart.lock 文件
Chart.lock 文件是在运行 helm dependency update 时生成的,包含了一个依赖列表,类似于 Chart.yaml 文件。然而,与 Chart.yaml 不同,Chart.lock 文件用于锁定依赖版本,以确保在其他机器上可以下载到相同版本的依赖。
在 basic-fields chart 中,Chart.lock 的影响不大,因为 MariaDB 版本已经静态设置为 9.5.0。然而,看看位于 chapter5/examples/wildcard-version 下的 wildcard-version chart。在这个目录中,MariaDB 的版本被设置为 9.x.x,如下所示的代码片段:
dependencies:
- name: mariadb
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 9.x.x
版本 9.x.x 是一个通配符,它告诉 Helm 下载 9 主版本下的最新次版本和修补版本,假设采用 major.minor.patch 的语义化版本格式。如果版本指定为 9.5.x,Helm 将下载 9 主版本和 5 次版本下的最新修补版本。
让我们使用 wildcard-version chart 来理解通配符在 Chart.lock 文件中的重要作用。
-
使用
helm dependency update命令下载wildcard-versionchart 的依赖:$ helm dependency update chapter5/examples/wildcard-version -
查看生成的
Chart.lock文件:$ cat chapter5/examples/wildcard-version/Chart.lock
请注意,Chart.lock 中的 MariaDB 版本 9.8.1 与 Chart.yaml 中的版本 9.x.x 不同:

图 5.5 – 使用通配符依赖版本时的 Chart.lock 文件
在这里,你可以更清楚地看到 Chart.lock 的影响。由于在 Chart.yaml 文件中指定了 9.x.x 版本,Helm 下载了最新的 9.x.x 版本,并生成了 Chart.lock 来锁定版本 9.8.1,这是在运行 helm dependency update 时的最新版本。然而,如果需要重新下载依赖项,或者需要重新生成 charts/ 文件夹会发生什么呢?如果再次运行 helm dependency update,就有可能出现最新的 9.x.x 版本与 9.8.2 不同的情况,这可能导致兼容性问题。为了解决这一风险,你可以使用 helm dependency build 命令。让我们看看这个命令的实际效果:
-
删除
wildcard-version下的charts/目录:$ rm –rf chapter5/examples/wildcard-version/charts -
运行
helm dependency build重新下载Chart.lock中指定的 MariaDB 版本:$ helm dependency build chapter5/examples/wildcard-version -
验证版本 9.8.1 是否已重新下载到
charts/目录:$ ls chapter5/examples/wildcard-version/charts mariadb-9.8.1.tgz
在本节中,我们介绍了使用helm dependency子命令下载依赖项。然而,到目前为止,我们看到的例子总是导致依赖项被下载。有时,你可能需要根据用户输入有条件地包含或排除依赖项。我们将在下一节探讨这一概念。
创建条件
可以通过使用dependencies映射中的condition和tags字段来创建条件依赖项。condition字段用于列出Boolean值,如果这些值存在,将切换依赖项的包含。我们首先通过查看位于chapter5/examples/condition-example的condition-example chart 来探索这个字段:
-
查看位于
chapter5/examples/condition-example/Chart.yaml的Chart.yaml文件:$ cat chapter5/examples/condition-example/Chart.yaml <output omitted> dependencies: - name: mariadb repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami version: 9.5.0 condition: mariadb.enabled
请注意,前面代码段中的Chart.yaml的最后一行使用了condition: mariadb.enabled设置。这个设置允许用户将一个名为mariadb.enabled的值设置为true或false。如果该值为true,MariaDB 依赖项将被包含。如果为false,则 MariaDB 将不会被包含。默认情况下,如果mariadb.enabled不存在,那么此条件将不起作用,MariaDB 将被包含。
设置条件的最佳实践是遵循chartname.enabled值格式,其中每个依赖项都有一个独特的条件,具体取决于依赖项的名称。这种方式可以实现更直观的值架构。然而,如果有必要,你可以通过使用逗号分隔的表达式来为一个条件指定多个值,如下所示:
condition: example.enabled, example2.enabled
当条件是一个以逗号分隔的列表时,如果第一个值存在,则使用第一个值,其余值将被忽略。否则,如果第一个值不存在,则会使用列表中的后续值作为后备。
让我们继续这个例子,看看condition属性的实际应用。
-
查看
condition-examplechart 的values.yaml文件,该文件默认包含mariadb.enabled值:$ cat chapter5/examples/condition-example/values.yaml <output omitted> mariadb: enabled: true
如你所见,mariadb.enabled默认设置为true,因此我们可以预期在 Helm 的输出中看到 MariaDB 资源。让我们来验证这一点。
-
使用
helm dependency update命令下载condition-examplechart 的依赖项:$ helm dependency update chapter5/examples/condition-example -
在你的 minikube 集群中安装
condition-examplechart:$ helm install conditional-example chapter5/examples/condition-example –n chapter5 -
验证安装过程中是否创建了 MariaDB 相关资源:
$ helm get manifest conditional-example –n chapter5 | grep mariadb
你应该看到一长串包含mariadb的字符串输出。
正如预期的那样,MariaDB 被安装了,因为mariadb.enabled值被设置为true。接下来,我们将这个值设置为false,并验证 MariaDB 是否已被排除。
-
通过将
mariadb.enabled设置为false来升级conditional-example发布:$ helm upgrade conditional-example chapter5/examples/condition-example --set mariadb.enabled=false –n chapter5 -
升级后验证 MariaDB 相关资源是否已被排除:
$ helm get manifest conditional-example –n chapter5 | grep mariadb
你不应该看到任何输出。
condition 设置是最常用的在 Helm 图表中有条件地包含依赖项的方式。然而,还有另一种设置可以使用,我们将在此展示,称为 tags。condition 最适用于使用 chartname.enabled 格式启用单个依赖项,而 tags 用于通过将每个依赖项与描述性标签关联,来启用或禁用一个或多个依赖项。
让我们使用位于 chapter5/examples/tags-example 的 tags-example 图表来了解标签如何定义条件依赖。
-
查看位于
chapter5/examples/tags-example/Chart.yaml的tags-example图表的Chart.yaml文件:<output omitted> dependencies: - name: mariadb repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami version: 9.5.0 tags: - backend - database - name: memcached repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami version: 5.15.6 tags: - backend - cache
如您所见,tags-example 图表定义了两个不同的依赖项:mariadb 和 memcached。mariadb 和 memcached 都共享 backend 标签,而 mariadb 还具有 database 标签;memcached 则单独具有 cache 标签。让我们通过查看图表的 values.yaml 文件来探索这些标签的使用。
-
查看
tags-example图表的values.yaml文件。注意文件末尾使用的tags映射:$ cat chapter5/examples/tags-example/values.yaml <output omitted> tags: backend: true
根据 tags-example 图表的值文件,您可以看到已启用后端标签。由于 mariadb 和 memcached 都共享相同的 backend 标签,因此这两个依赖项默认都启用(类似地,如果省略了 tags 映射,这两个依赖项也会被包含)。为了验证这一点,我们可以使用 tags-example 图表升级之前的 conditional-example 发布。
-
使用
helm upgrade命令通过tags-example图表中的内容升级conditional-example:$ helm upgrade conditional-example chapter5/examples/tags-example –n chapter5 -
验证
mariadb和memcached是否都已安装:$ helm get manifest conditional-example –n chapter5 | grep mariadb $ helm get manifest conditional-example –n chapter5 | grep memcached
虽然这两个命令应该显示大量输出,但匹配项的存在表明这两个依赖项都已安装。
通过在多个依赖项中使用相同的标签,您可以方便地在图表中包含或排除依赖项。然而,假设您只想在 tags-example 图表中包含 mariadb。虽然直觉上可能认为只需将 tags.database 设置为 true,将 tags.cache 设置为 false,但这不会产生任何效果,因为 tags.backend 默认已设置为 true。如果某个标签为 true,即使其他标签设置为 false,该依赖项也会被包含。
为了解决此问题,您可以将 tags.backend 重写为 false。
-
升级
conditional-example发布,以便它包含mariadb并排除memcached:$ helm upgrade conditional-example chapter5/examples/tags-example --set tags.backend=false --set tags.database=true –n chapter5
注意,我们首先传递了 --set tags.backend=false,以确保 memcached 的条件不会被评估为 true。
-
验证升级过程中是否包含了
mariadb:$ helm get manifest conditional-example –n chapter5 | grep mariadb
该命令应返回大量输出。
-
验证升级过程中是否排除了
memcached:$ helm get manifest conditional-example –n chapter5 | grep memcached
该命令不应返回任何输出。
condition和tags字段都提供了强大的选项集,用于在你的 Helm 图表中有条件地包含依赖项。请记住,你也可以将这两个选项一起使用,但condition始终会覆盖tags。这意味着,如果所有标签的评估结果为真,而任何条件评估结果为假,则条件将覆盖标签,依赖项将不会被包含。
在进入下一个话题之前,卸载conditional-example发布:
$ helm uninstall conditional-example –n chapter5
接下来,让我们讨论如何更改引用依赖项及其值的选项。
更改依赖项的名称和值
当你在图表中包含依赖项时,你很可能需要更改其一些值。更改依赖项值的一种方式是通过一个映射来覆盖这些值,映射的根名称与依赖项的名称相同。
例如,考虑位于chapter5/examples/basic-fields下的basic-fields图表。这个图表在Chart.yaml文件中包含一个依赖项:
dependencies:
- name: mariadb
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 9.5.0
要覆盖mariadb图表中的值,你可以采用类似以下的值结构:
mariadb:
image:
registry: my-registry.example.com
repository: my-mariadb
tag: my-tag
这将覆盖mariadb图表中的image.registry、image.repository和image.tag值。
让我们通过完成一个实际示例来尝试覆盖依赖项值:
-
安装位于
chapter5/examples/basic-fields的basic-fields图表。覆盖 MariaDB 的image.tag值,以便为mariadb镜像部署不同于默认的标签:$ helm install override-example chapter5/examples/basic-fields --set mariadb.image.tag=latest –n chapter5 -
验证是否应用了
latest标签:$ helm get manifest override-example –n chapter5 | grep latest image: docker.io/bitnami/mariadb:latest -
卸载 Helm 发布:
$ helm uninstall override-example –n chapter5
以这种方式嵌套值是覆盖依赖项值最简单和最常见的方法。然而,dependencies映射提供了一个配置,用于更改根名称——alias。
让我们通过一个示例来了解如何使用alias。
查看位于chapter5/examples/alias-example/Chart.yaml的alias-example图表的Chart.yaml文件:
$ cat chapter5/examples/alias-example/Chart.yaml
<output omitted>
dependencies:
- name: mariadb
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 9.5.0
alias: db1
- name: mariadb
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 9.5.0
alias: db2
从前面的Chart.yaml片段中,你可以看到alias-example有两个几乎相同的 MariaDB 依赖项。这是使用alias的最佳场景。由于有多个 MariaDB 依赖项,Helm 需要能够区分它们。通过使用alias,你可以为每个相同的依赖项赋予一个唯一的名称。然后,你可以覆盖每个特定依赖项的值。让我们探索alias:
-
通过覆盖每个 MariaDB 实例的
image.tag值来安装 Helm 图表:$ helm install alias-example chapter5/examples/alias-example --set db1.image.tag=latest --set db2.image.tag=10.4 -n chapter5 -
验证每个数据库的标签是否已应用:
$ helm get manifest alias-example –n chapter5 | grep latest image: docker.io/bitnami/mariadb:latest $ helm get manifest alias-example –n chapter5 | grep 10.4 image: docker.io/bitnami/mariadb:10.4 -
卸载 Helm 发布:
$ helm uninstall alias-example –n chapter5
一般来说,当你处理唯一的依赖项时,不需要使用alias。但是,当你处理同一依赖项的多个调用时,alias是管理和覆盖每次调用的值的绝佳方法。
除了 alias 外,Chart.yaml 文件中的 dependencies 映射还提供了一个额外的属性来改变依赖项值的管理方式——import-values。import-values 设置用于改变依赖值如何传播到父图表。它有两种不同的格式:exports 和 child-parent。exports 格式仅在依赖图表的值文件中包含 exports 映射时适用。假设一个依赖图表包含以下值:
exports:
image:
registry: my-registry
repository: my-repository
tag: my-tag
使用父图表上的 import-values 设置,你可以导入每个位于 exports 下的图像相关值:
dependencies:
- name: dependency
repository: http://localhost:8080
version: 1.0.0
import-values:
- image
使用 import-values 以 exports 格式时,图像相关的值将被传播,如下所示:
registry: my-registry
repository: my-repository
tag: my-tag
将此与默认情况下这些依赖值的传播方式进行比较,import-values 未使用时的情况:
dependency:
exports:
image:
registry: my-registry
repository: my-repository
tag: my-tag
如你所见,使用 import-values 使得传播变得更加简洁,减少了深层嵌套的值。
import-values 的另一种格式是 child-parent 格式。此格式不需要依赖图表(称为 exports),并且对于导入深层嵌套的值特别有用。考虑以下依赖图表,其中包含以下值:
common:
deployment:
image:
registry: my-registry
repository: my-repository
tag: my-tag
在父图表中,你可以使用 import-values 的 child-parent 格式导入图像相关的值:
dependencies:
- name: dependency
repository: http://localhost:8080
version: 1.0.0
import-values:
- child: common.deployment.image
parent: image
这将传播依赖值,使得 common.deployment.image 下的每个值都直接映射到父图表中的 image 下:
image:
registry: my-registry
repository: my-repository
tag: my-tag
再次通过使用 import-values 设置,你可以简化依赖值如何传播到父图表中的过程。
一个需要注意的重要细节是,当使用 import-values 时,你无法覆盖正在导入的值。如果需要覆盖来自依赖项的值,那么这些值不应该使用 import-values 导入。
现在我们已经探讨了涉及 Helm 依赖管理的不同设置,让我们通过将 Redis 依赖项更新到 guestbook 图表中,以创建后端来结束本章内容。
更新 guestbook Helm 图表
类似于我们在第三章《使用 Helm 安装你的第一个应用》中如何搜索 Artifact Hub 以定位 WordPress 图表,我们需要搜索一个 Redis 图表,以便将其作为依赖项使用。让我们来搜索 Redis 图表:
-
执行以下命令从 Artifact Hub 搜索 Redis 图表:
$ helm search hub redis -
显示的第一个图表是 Bitnami 的 Redis 图表。我们将使用这个图表作为我们的依赖项。如果你在第三章《使用 Helm 安装你的第一个应用》中没有添加
bitnami图表库,现在可以通过使用helm repo add命令添加该图表库:$ helm repo add bitnami https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami -
接下来,确定你希望使用的 Redis 图表版本。可以通过运行以下命令找到版本号列表:
$ helm search repo redis --versions
你将看到类似以下的输出:

图 5.6 – Redis 图表版本
对于我们的依赖项,我们使用通配符版本 15.5.x,这样我们可以锁定当前可用的最新补丁 15.5.1,同时也能方便地在未来下载更新的补丁版本。
让我们还使用 condition 属性,这样 Redis 就可以根据需要启用或禁用。虽然我们的 guestbook 图表确实需要 Redis,但 condition 会让用户禁用内置的 Redis 选项,以便在需要时使用自己的 Redis。
-
更新你的
guestbook图表的Chart.yaml文件以声明 Redis 依赖项。更新后的Chart.yaml文件可以在本书的 GitHub 仓库中的chapter5/guestbook/Chart.yaml找到,供参考:dependencies: - name: redis repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami version: 15.5.x condition: redis.enabled
你的完整 Chart.yaml 文件应如下所示:
apiVersion: v2
name: guestbook
description: An application used for keeping a running record of guests
type: application
version: 0.1.0
appVersion: v5
dependencies:
- name: redis
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 15.5.x
condition: redis.enabled
更新了 Chart.yaml 文件后,使用 helm dependency update 下载 Redis 依赖项。现在,我们可以部署 guestbook 图表,以确保依赖项已正确安装。
-
运行
helm dependency update下载最新的 Redis15.5.x版本:$ helm dependency update guestbook -
在你的 minikube 环境中安装
guestbook图表:$ helm install guestbook guestbook -n chapter5 -
验证 Redis StatefulSets 是否已创建:
$ kubectl get statefulsets –n chapter5 NAME READY AGE guestbook-redis-master 1/1 3m24s guestbook-redis-replicas 3/3 3m24s
如果你看到类似于此处显示的 StatefulSets 输出,那么你已经成功创建了 Redis 依赖项!正如你所看到的,通过使用 Helm 的依赖管理,部署后端所需的工作量相对较低。在下一章中,我们将继续开发 guestbook 图表,编写用于创建前端资源的模板。
在我们结束之前,让我们清理 minikube 环境。
清理环境
首先,删除 chapter5 命名空间:
$ kubectl delete namespace chapter5
现在,你可以关闭你的 minikube 环境。
$ minikube stop
现在,让我们总结一下这一章。
小结
依赖项可以大大减少在 Kubernetes 中部署复杂应用所需的工作量。正如我们在 guestbook 图表中所看到的,要部署一个 Redis 后端,我们只需要在 Chart.yaml 文件中添加五行 YAML。相比之下,从头开始编写一个完全独立的 Redis 图表,将需要较高的 Kubernetes 和 Redis 专业知识。
Helm 依赖管理支持多种不同的配置来声明和配置依赖项。要声明依赖项,可以在 Chart.yaml 文件中的 dependencies 映射下指定图表的 name、version 和 repository。你可以使用 condition 和 tags 属性允许用户切换启用或禁用每个依赖项。在引入相同依赖项的多个实例时,可以使用 alias 为每个实例提供唯一标识符,处理具有复杂值的依赖项时,可以使用 import-values 简化值从依赖项传递到父图表的方式。要列出并下载依赖项,Helm 提供了一组 helm dependency 子命令,这些子命令在管理图表依赖项时会经常使用。
在下一章中,我们将深入探讨 Helm 图表开发领域中的下一个关键主题——模板。
进一步阅读
要了解更多关于 Helm 依赖管理的信息,请访问 Helm 文档中的Chart Dependencies部分,网址为helm.sh/docs/topics/charts/#chart-dependencies。
问题
回答以下问题,测试您对本章的知识掌握情况:
-
声明图表依赖关系所用的文件是什么?
-
helm dependency update和helm dependency build命令之间有什么区别? -
Chart.yaml和Chart.lock文件之间有什么区别? -
假设您希望允许用户在您的图表中启用或禁用依赖项,可以使用什么
dependencies属性? -
如果您需要声明同一依赖项的多个调用,应该使用什么
dependencies属性? -
如果您的依赖项具有复杂值,可以使用哪个
dependencies属性来简化传播的值? -
如何覆盖依赖项的值?
-
作为图表开发者,使用图表依赖项有什么价值?
第六章:理解 Helm 模板
Helm 的一个基本功能是创建和维护构成应用程序的 Kubernetes 资源。Helm 通过一个名为 模板 的概念来实现这一点。模板代表 Helm Charts 的核心组件,因为它们用于根据给定的一组 值 配置 Kubernetes 资源。
在 第四章中,搭建新的 Helm Chart,你使用 helm create 命令搭建了一个新的 Helm Chart,并在 chart 的 templates/ 文件夹下创建了基本模板。在本章中,我们将深入探讨 Helm 模板的世界,最后我们将回顾搭建的模板,进行改进并部署 Guestbook 前端。在本章结束时,你的 Helm Chart 将能够部署完整的 Guestbook 架构——从 第五章 中添加的 Redis 后端,到本章稍后将添加的前端。
以下是本章的主要内容:
-
Helm 模板基础
-
模板值
-
内置对象
-
Helm 模板函数
-
Helm 模板控制结构
-
生成发布说明
-
Helm 模板变量
-
Helm 模板验证
-
通过命名模板和库 chart 启用代码重用
-
创建 自定义资源定义 (CRDs)
-
后渲染
-
更新并部署 Guestbook Chart
技术要求
本章需要以下工具:
-
minikube
-
kubectl
-
Helm
-
Git
我们将使用 minikube 在本章中探索多个示例,所以请随时通过运行以下命令启动你的 minikube 环境:
$ minikube start
一旦 minikube 启动完成,为本章创建一个新的命名空间,如下所示:
$ kubectl create namespace chapter6
如果你在前面的章节中尚未克隆示例 Git 仓库,请通过运行以下命令进行克隆:
$ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git
现在你的环境已经设置好了,让我们来探索本章的第一个主题——Helm 模板。
Helm 模板基础
Helm 模板用于动态生成 Kubernetes helm create 命令,生成一组启动模板。在之前克隆的 Git 仓库中,这些模板位于 chapter6/guestbook/templates/。以下是 chapter6/guestbook/templates/deployment.yaml 文件中 Helm 模板的一个简短片段:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "guestbook.fullname" . }}
labels:
{{- include "guestbook.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "guestbook.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "guestbook.selectorLabels" . | nindent 8 }}
你可能会觉得前面的代码片段的语法有些奇怪,因为它类似于 YAML 文件,但包含了一些在 YAML 规范中无效的字符。要理解这种语法,我们首先需要谈谈Go。Go 是 Google 在 2009 年开发的一种编程语言。它是 Kubernetes、Helm 以及许多容器社区工具使用的编程语言。Go 编程语言的核心组件之一是模板,它们用于生成多种不同格式的文件。Helm 的模板引擎基于 Go 构建,可以看作是 Go 模板的超集。Go 模板提供了基本的语法和控制结构,而 Helm 添加了额外的功能,以增强模板引擎的能力。
Helm 模板包含各种不同的操作或字符串,它们以两个左花括号( {{ )开始,并以两个右花括号( }} )结束。操作标记了数据处理发生的地方或控制结构(如条件语句和循环)被实现的位置。你可以在代码片段中以及 templates/ 目录下的其他 Helm chart 模板中看到不同的操作。尽管操作出现在本地模板文件中,但它们在处理过程中会被处理并移除,例如在安装或升级过程中,以生成有效的 Kubernetes YAML 资源。
在 Helm chart 模板中,你可以利用许多不同的组件,如对象、函数和控制结构来编写操作。我们将在本章中探讨这些内容。让我们从讨论如何在 chart 模板中使用 values 组件开始。
模板值
在前几章中,我们将值描述为用于配置 Helm chart 的参数。现在,我们将理解值是如何集成到 chart 模板中的,从而动态生成 Kubernetes 资源。
这是来自 Git 仓库的基本 ConfigMap 模板,位于 chapter6/examples/values-example/templates/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: values-example
data:
config.properties: |-
chapterNumber={{ .Values.chapterNumber }}
chapterName={{ .Values.chapterName }}
该模板的最后两行包含 {{ .Values.chapterNumber }} 和 {{ .Values.chapterName }} 操作,这些操作用作 chapterNumber 和 chapterName 值的占位符。这样,ConfigMap 就可以基于默认 chart 值和用户在安装或升级过程中提供的值进行参数化。
让我们来看一下位于 chapter6/examples/values-example/values.yaml 的默认 chart 值。你可以在这里看到这些内容:
chapterNumber: 6
chapterName: Understanding Helm Templates
给定这个 Values 文件,我们预计默认的 ConfigMap 会像这样渲染:
apiVersion: v1
kind: ConfigMap
metadata:
name: values-example
data:
config.properties: |-
chapterNumber=6
chapterName=Understanding Helm Templates
你可以通过运行 helm install 命令来自行验证这一点,正如我们在前几章中所演示的那样,但使用一个新的命令 helm template 可能更为方便,该命令用于本地渲染模板资源,而不是将它们安装到 Kubernetes 集群中。helm template 命令,如下所示,语法与 helm install 相同:
helm template <RELEASE_NAME> <CHART_NAME> [flags]
让我们使用此命令在本地渲染 values-example 图表模板。请按以下步骤操作:
-
运行
helm template命令,指向<CHART_NAME>参数为values-example文件夹,如下所示:$ helm template example chapter6/examples/values-example -
你应该看到配置图表按之前的方式渲染,其中的操作被
chapterNumber和chapterName的值替换,示例如下代码片段所示:<skipped for brevity> data: config.properties: |- chapterNumber=6 chapterName=Understanding Helm Templates
除非我们打算将资源安装到 minikube 环境中,否则我们将在本章中使用 helm template 命令快速演示模板构造。这样,你就不必担心每个练习后的清理工作。我们将在本章末尾安装更新版的 Guestbook Helm 图表时,返回使用 helm install。
如你在前面的示例中所看到的,每次使用占位符表示图表值时,模板都会引用一个名为 .Values 的构造。.Values 是你作为 Helm 图表开发者可以使用的几个内建对象之一。接下来我们将进一步探索这些内建对象。
内建对象
内建对象是你编写 Helm 图表时可以使用的基本构建块。如前所述,它们通过 .Values 对象提供对图表值的访问,但还有许多其他对象可供探索,它们提供对额外信息和功能的访问。
下表列出了这些内建对象:
| 对象 | 定义 |
|---|---|
.Values |
用于访问 values.yaml 文件中的值或通过 --values 和 --set 标志提供的值 |
.Release |
用于访问 Helm 发布的元数据,如名称、命名空间和修订号 |
.Chart |
用于访问有关 Helm 图表的元数据,如其名称和版本 |
.Template |
用于访问有关图表模板的元数据,如文件名和路径 |
.Capabilities |
用于访问有关 Kubernetes 集群的信息 |
.Files |
用于访问 Helm 图表目录中的任意文件 |
. |
根对象 |
表 6.1 – 内建 Helm 对象
每个对象包含可以通过点表示法访问的字段和函数。点表示法用于访问对象的属性。例如,假设提供了以下 Values 文件:
books:
harryPotter:
- The Sorcerer's Stone
- The Chamber of Secrets
- The Prisoner of Azkaban
lotr:
- The Fellowship of the Ring
- The Two Towers
- Return of the King
.Values 对象现在将包含以下属性:
-
.Values.books.harryPotter(字符串列表) -
.Values.books.lotr(字符串列表)
在 Helm(以及 Go 模板)中,点(.)也用于表示对象作用域。点表示全局作用域,从中可以访问所有对象。点后跟对象名称会限制该对象的作用域。例如,.Values 作用域限制了对图表值的可见性,而 .Release 作用域限制了对发布元数据的可见性。作用域在循环和控制结构中起着重要作用,接下来我们将在本章中详细探讨。
虽然 .Values 对象是你在 Helm chart 开发过程中最常用的对象,但还有其他一些内置对象我们将会讨论。接下来我们将从 .Release 对象开始。
.Release 对象
.Release 对象用于检索关于正在安装的 Helm release 的元数据。从 .Release 对象中常用的两个属性是 .Release.Name 和 .Release.Namespace,它们允许 chart 开发者在 chart 模板中替换 release 的名称和命名空间。
请考虑以下示例模板,该模板位于 Git 仓库中的 chapter6/examples/release-example/templates/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
config.properties: |-
namespace={{ .Release.Namespace }}
在模板中,我们将 ConfigMap 的名称设置为 Helm release 的名称,并将命名空间属性设置为 release 的命名空间。
在运行 Helm 的 install、upgrade 或 template 命令时,你可以看到 {{ .Release.Name }} 和 {{ .Release.Namespace }} 操作被替换为它们的实际值,如下代码片段所示:
$ helm template release-example chapter6/examples/release-example
---
# Source: release-example/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-example
data:
config.properties: |-
namespace=default
如你所见,ConfigMap 的名称被生成为 release-example,namespace 属性被生成为 default(如果我们使用 -n 标志选择了其他命名空间,则该值会被相应反映)。通过使用 .Release 对象,我们能够利用在调用 Helm 时提供的名称和命名空间,而不是在 values.yaml 中为名称和命名空间创建重复的值。
在 .Release 下,除了 name 和 namespace 之外,还有多个其他对象可以在你的 chart 模板中使用。下表列出了每个 .Release 对象,描述内容引用自 Helm 文档:helm.sh/docs/chart_template_guide/builtin_objects/#helm:
| 对象 | 描述 |
|---|---|
.Release.Name |
release 名称 |
.Release.Namespace |
要发布到的命名空间 |
.Release.IsUpgrade |
如果当前操作是升级或回滚,则设置为 true |
.Release.IsInstall |
如果当前操作是安装,则设置为 true |
.Release.Revision |
此 release 的修订号 |
.Release.Service |
渲染模板的服务(这个值始终等于字符串 "Helm") |
表 6.2 – .Release 对象
接下来我们将探索 .Chart 对象。
.Chart 对象
.Chart 对象用于从正在安装的 Helm chart 的 Chart.yaml 文件中检索元数据。它通常用于为 chart 资源标注 chart 名称和版本。我们来看一下 Git 仓库中 chapter6/examples/chart-example/templates/configmap.yaml 的示例模板。你可以在这里查看:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
data:
config.properties: |-
chapterNumber={{ .Values.chapterNumber }}
chapterName={{ .Values.chapterName }}
正如你在 metadata.labels 部分看到的,模板正在使用 {{ .Chart.Name }}、{{ .Chart.Version }} 和 {{ .Chart.AppVersion }} 动作,这些动作从 Chart.yaml 文件中检索 name、version 和 appVersion 字段。在这里,你可以看到这个示例图表的 Chart.yaml 文件:
apiVersion: v2
name: chart-example
description: A Helm chart for Kubernetes
type: application
version: 1.0.0
appVersion: 0.1.0
当我们使用 helm template 命令在本地渲染此模板时,我们看到来自 Chart.yaml 文件的字段被用在了 ConfigMap 资源中,如下所示:
$ helm template chart-example chapter6/examples/chart-example
---
# Source: chart-example/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: chart-example
labels:
helm.sh/chart: chart-example-1.0.0
app.kubernetes.io/version: 0.1.0
data:
config.properties: |-
chapterNumber=6
chapterName=Understanding Helm Templates
.Chart 对象可以引用 Chart.yaml 文件中的任何字段。有关 Chart.yaml 字段的完整列表,请参考 第四章,创建新的 Helm 图表。
.Template 对象
.Template 对象用于检索当前正在渲染的模板的元数据。它是最简单的内置对象(除了 .Values),并且只有两个下级对象,如下所示:
-
.Template.Name—正在渲染的模板的文件路径(例如mychart/templates/mytemplate.yaml) -
.Template.BasePath—指向templates目录的路径(例如mychart/templates)
根据我们的经验,.Template 对象很少使用,但如果您需要在图表中引用模板的文件路径,它会很有用。
.Capabilities 对象
.Capabilities 对象用于获取关于目标 Kubernetes 集群的信息。.Capabilities 下有许多对象,但最常见的是 .Capabilities.APIVersions.Has 和 .Capabilities.KubeVersion。
.Capabilities.APIVersions.Has 对象是一个函数,根据 Kubernetes 集群是否具有给定的 应用程序编程接口(API)版本返回布尔值。以下是一个示例调用:
{{ .Capabilities.APIVersions.Has "batch/v1" }}
该操作将根据集群是否包含 "batch/v1” API 版本返回 true 或 false。.Capabilities.APIVersions.Has 最常用于条件逻辑中,只在集群包含特定 API 版本时才安装资源。条件逻辑将在本章后续的 Helm 模板控制结构 部分中介绍。
另一个常用的 .Capabilities 对象是 .Capabilities.KubeVersion。使用该属性可以检索 Kubernetes 集群的版本。例如,以下操作将返回一个 v1.21.2 字符串(或类似的字符串,具体取决于使用的 Kubernetes 版本):
{{ .Capabilities.KubeVersion }}
其他 .Capabilities 对象,如 .Capabilities.KubeVersion.Major 和 .Capabilities.KubeVersion.Minor,允许图表开发人员仅获取 Kubernetes 集群的主版本或次版本(与整个 .Capabilities 相对,访问 Helm 文档 helm.sh/docs/chart_template_guide/builtin_objects/#helm)。
.Files 对象
有时,你可能会遇到需要在图表模板中包含文件内容的情况。你可以使用 .Files 对象来包含文件内容。这主要用于 ConfigMap 和 Secret 资源,其中 data 部分由单独的配置文件提供或补充。请注意,文件必须位于 Helm 图表目录内(但在 templates/ 文件夹外),才能通过 .Files 引用。
.Files 对象下包含了几个其他的对象。最基础的是 .Files.Get,这是一个函数,用于检索提供的文件名的内容。假设有如下的 ConfigMap 模板(此模板也位于 Git 仓库中的 chapter6/examples/files-example/get 目录下):
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
config.properties: |-
{{ .Files.Get "files/config.properties" }}
示例中的 .Files.Get 函数用于获取 files/config.properties 文件的内容,这个路径是相对于 Helm 图表根目录的。该文件位于 Git 仓库中的 chapter6/examples/files-example/get/files/config.properties,其内容如下:
chapterNumber=6
现在,当我们渲染此模板时,我们将看到以下输出:
$ helm template basic-files-example chapter6/examples/files-example/get
---
# Source: files-example/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: basic-files-example
data:
config.properties: |-
chapterNumber=6
.Files 下有三个其他重要的对象:.Files.Glob、.Files.AsConfig 和 .Files.AsSecret。首先,.Files.Glob 是一个函数,返回与提供的 * 模式匹配的文件对象列表。例如,files/* 通配符将匹配 files/ 文件夹下的每个文件。
.Files.Glob 对象通常与 .Files.AsConfig 和 .Files.AsSecrets 对象一起使用。.Files.AsConfig 是一个函数,用于将文件作为 YAML 字典返回,其中键是文件名,值是文件内容。它被称为 AsConfig,因为它在格式化不同的 ConfigMap 数据条目时非常有用。.Files.AsSecrets 函数类似,但除了将文件作为 YAML 映射返回,AsSecrets 还会对文件内容进行 Base64 编码——这对于创建 Kubernetes Secrets 数据非常有用。请记住,敏感文件绝不应该以明文形式被提交到 Git 仓库中(尽管我们在示例 Git 仓库中有一个这样的文件用于演示目的)。
以下模板展示了这些对象的用法,并且也位于 Git 仓库中的 chapter6/examples/files-example/glob 目录下:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
{{ (.Files.Glob "files/chapter*").AsConfig | indent 2 }}
---
kind: Secret
<skipped for brevity>
Data:
{{ (.Files.Glob "files/secret*").AsSecrets | indent 2 }}
files 文件夹包含以下文件:
-
chapter.properties -
secret.properties
当渲染此模板时,两个文件的内容会作为 YAML 映射生成在 ConfigMap 的 data 下,如以下代码片段所示:
$ helm template glob-example chapter6/examples/files-example/glob
---
# Source: files-example/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: glob-example
data:
chapter.properties: |
chapterNumber=6
chapterName=Understanding Helm Templates
---
# Source: files-example/templates/secret.yaml
apiVersion: v1
kind: Secret
<skipped for brevity>
secret.properties: dXNlcm5hbWU9bXl1c2VyMTIzCnBhc3N3b3JkPW15cGFzczEyMwo=
在前面的示例中,你可能注意到了 | indent 2 的使用。这代表着一个管道操作和一个函数,我们将在下一节中详细探讨,Helm 模板函数。现在,你只需关心输出会被缩进两个空格,以便生成格式正确的 ConfigMap 和 Secret 资源。
其他.Files对象包括.Files.GetBytes,它返回一个字节数组形式的文件,以及.Files.Lines,用于逐行遍历文件。
Helm 模板函数
任何模板语言的一个常见特征是能够转换数据。到目前为止,当我们提到.Values或 Helm 中的其他内置对象时,我们只是引用了资源本身,并未进行任何数据处理。Helm 真正展现其强大之处的是其通过模板函数和管道在模板中执行复杂数据处理的能力。
由于 Helm 使用 Go 作为其模板语言的基础,它继承了 Go 提供的函数能力。Go 模板函数类似于你在其他编程语言中可能接触过的任何函数。函数包含逻辑,旨在消耗特定的输入并根据提供的输入提供输出。
使用 Go 模板时,函数使用以下语法:
functionName arg1 arg2 . .
在 Helm 图表中常用的一个函数是quote函数,它会将输入字符串用引号括起来。以下是来自 Git 存储库中chapter6/examples/functions-example/templates/configmap.yaml的 ConfigMap 示例:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
annotations:
{{- toYaml .Values.annotations | nindent 4 }}
data:
path: {{ .Values.fs.path }}
config.properties: |-
{{- (.Files.Get "files/chapter-details.cfg") | nindent 4}}
上述 ConfigMap 中的path属性表示应用程序使用的文件系统位置,如以下代码片段所示。该属性引用的值位于values.yaml文件中:
fs:
path: /var/props/../configs/my app/config.cfg
渲染后的模板如下所示(为了简洁,省略了一些字段):
...
data:
path: /var/props/../configs/my app/config.cfg
...
如果消费应用程序未包含适当的逻辑来管理输入中是否可能存在空格,那么可能会导致潜在的下游问题。
为了避免这些潜在问题,添加quote函数,它会将属性用引号括起来,如以下代码片段所示:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
annotations:
{{- toYaml .Values.annotations | nindent 4 }}
data:
path: {{ quote .Values.fs.path }}
...
使用helm template命令在本地渲染图表以查看其功能,方法如下:
$ helm template functions-example chapter6/examples/functions-example
模板渲染后的结果是/var/props/../configs/my app/config.cfg字符串,这不仅增强了属性的可读性,还保护了任何使用该属性的应用程序。
quote是 Helm 中超过 60 个函数之一。虽然一些函数来自 Go 模板,但大多数函数是Sprig模板库的一部分。Sprig库包含用于在图表中实现更复杂功能的函数,如执行数学公式、转换操作以及管理数据结构,包括列表和字典。
从 Go 和 Sprig 继承的函数可以在 Go 文档中找到,链接为pkg.go.dev/text/template#hdr-Functions,在 Sprig 模板库中也可以找到,链接为masterminds.github.io/sprig/。
Helm 中最近添加的一个功能是通过使用lookup函数从正在运行的 Kubernetes 环境中查询资源。Helm chart 开发人员可以引用单个资源或跨命名空间或集群的指定类型的引用,并将结果注入到他们的模板中。
lookup函数的形式如下:
lookup <apiVersion> <kind> <namespace> <name>
例如,要查询chapter6命名空间中的名为props的 ConfigMap,请使用以下函数:
lookup "v1" "ConfigMap" "chapter6" "props"
lookup函数的结果是一个字典,可以根据需要进一步导航,以检索返回资源上的单个属性。
所以,要提取一个名为author的 ConfigMap 中的属性,该属性包含所有 WordPress 文章的默认作者姓名,可以在 Helm 模板中添加以下代码:
{{ (lookup "v1" "ConfigMap" "chapter6" "props").data.author }}
如你所见,我们首先检索包含props ConfigMap 的值的字典,然后导航到 ConfigMap 数据结构中的author属性。
使用lookup函数时,你不仅限于查询单个资源,还可以在单一命名空间内或所有命名空间内搜索所有给定类型的资源。这可以通过将命名空间和/或资源名称替换为空引号来实现,如下模板所示:
lookup "v1" "ConfigMap" "chapter6" ""
使用lookup函数时的一个重要注意事项是,它仅在资源被部署到 Kubernetes 集群时才能使用,例如通过helm install和helm upgrade命令。这是因为执行过程中需要与 Kubernetes 集群保持活动连接。对于像helm template这样的命令,模板是在本地渲染的,并且没有与 Kubernetes 集群的交互,因此lookup函数不会返回任何有意义的结果。
Helm 函数及其对 Helm 模板命令的影响只是向 chart 模板中添加更多动态机制的第一步。多个模板命令也可以通过使用管道(pipelines)连接在一起,执行一系列复杂的操作。
管道是一个借用自 Unix 的概念,其中一个命令的结果作为另一个命令的输入。你可以在以下代码片段中看到这个概念的示例:
cat file.txt | grep helm
命令通过管道(|)字符分隔(因此得名管道),在这种情况下,file.txt文件的内容作为输入提供给grep命令。grep处理输入,过滤掉输入中任何helm的出现,并将其作为输出打印到屏幕上。
管道可以以类似的方式应用于 Helm。让我们回到之前的示例,我们介绍了 quote 函数来为文件系统路径添加引号。与其直接将 value 属性作为函数参数,不如反过来将值的内容通过管道传入 quote 函数,如下所示:
{{ .Values.fs.path | quote }}
无论是直接调用函数还是使用管道方法,最终结果是相同的。然而,在实际应用中,你会发现,管道通常是首选的方式,因为它具有扩展性,可以连锁模板命令。
你可能还注意到,fs.path 值中包括对相对路径(由 .. 表示)的引用。如果某些人不熟悉语法,这可能会让他们感到难以阅读或理解。幸运的是,Sprig 库中包含了一个名为 clean 的函数,可以完全解析路径并自动去除任何相对路径。可以将此函数添加到现有管道中,如下所示:
{{ .Values.fs.path | clean | quote }}
在 Git 仓库中的 functions-example Helm chart 的 ConfigMap 中,应用之前的更改,然后使用 helm template 命令查看更改后的效果。实例化后,渲染的模板将如下所示:
"/var/configs/my app/config.cfg"
函数和管道在 Helm 中被广泛使用,作为一个 chart 开发者,了解可用选项非常重要,这样才能有效地设计 charts。让我们花点时间来看一些更常用的函数。
如我们所见,Values 文件包含一个键/值对的字典。虽然可以引用单个的键/值对,但在许多情况下,可能需要注入一个深度嵌套的结构。幸运的是,有几个 Helm 函数可以在这种情况下提供帮助。
正如你记得的那样,YAML 语言对内容的缩进和间距非常讲究。为了应对这一点,Helm 提供了 toYaml 函数,它允许提供一个值的字典,并根据需要正确地格式化它,无论它嵌套得多深。在我们这一节中一直在使用的 ConfigMap 中,你可以看到一个从 Values 文件中定义的属性中注入的注解字典,以下代码片段展示了这一点:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
annotations:
{{- toYaml .Values.annotations | nindent 4 }}
data:
…
以下内容在 chart 的 values.yaml 文件中定义:
annotations:
publisher: "Packt Publishing"
title: "Managing Kubernetes Resources Using Helm"
你可能还会注意到,toYaml 函数的结果被传递到另一个名为 nindent 的函数中。使用该函数是管理内容格式化的必要要求;否则会发生渲染错误。indent 和 nindent 都提供通过缩进一定数量的空格来格式化内容的功能,这在处理 YAML 时至关重要。indent 和 nindent 的区别在于,nindent 在每行输入后会添加一个换行符,这在我们的使用场景中是必需的,因为 Values 文件中定义了多个注解属性。
使用 helm template 命令处理图表,查看这些值如何被渲染,如下所示:
$ helm template functions-example chapter6/examples/functions-example
apiVersion: v1
kind: ConfigMap
metadata:
name: functions-example
labels:
helm.sh/chart: functions-example-0.1.0
app.kubernetes.io/version: 1.16.0
annotations:
publisher: Packt Publishing
title: Managing Kubernetes Resources Using Helm
我们将详细讨论的最后一个 Helm 函数用于执行更复杂的模板渲染。在本章前面,你已经了解了如何在图表中引用外部文件,并使用内置的 .Files 对象将它们的值注入模板。虽然 Helm 的模板功能可以用于评估模板文件中的资源,但也有需要对外部源文件进行评估的情况。请再次查看 ConfigMap,并注意 config.properties 键:
config.properties: |-
{{- (.Files.Get "files/chapter-details.cfg") | nindent 4}}
值并非直接包含在 ConfigMap 中,而是来自位于 files/chapter-details.cfg 的文件,如下代码片段所示:
chapterNumber={{ .Values.book.chapterNumber }}
chapterName={{ .Values.book.chapterName }}
然而,当使用 helm template 渲染图表时,期望的值并未如预期替换,正如我们在这里所见:
config.properties: |-
chapterNumber={{ .Values.book.chapterNumber }}
chapterName={{ .Values.book.chapterName }}
这种情况发生是因为模板处理默认只在 templates 文件夹中的文件内进行,而不会处理任何外部源的内容。要将模板应用于引入模板的外部源,可以使用 tpl 函数,如下所示:
...
config.properties: |-
{{- tpl (.Files.Get "files/chapter-details.cfg") . | nindent 4}}
当你查看更新后的 ConfigMap 内容时,可能会对管道前的句点(.)感到疑惑。这个字符表示将传递给模板引擎的作用域。我们将在下一节中详细介绍这个话题。
使用 helm template 命令确认由于包含了 tpl 函数,值被适当地替换,如下所示:
$ helm template functions-example chapter6/examples/functions-example
...
config.properties: |-
chapterNumber=6
chapterName=Understanding Helm Templates
本节讨论的模板函数仅仅是 Helm 提供的函数的冰山一角。下表列出了一些图表开发人员应该了解的其他重要函数,以便充分利用 Helm 的功能:
| 函数 | 描述 | 示例 |
|---|---|---|
printf |
根据格式字符串和参数返回一个字符串 | printf "一只名叫 %s 的猫有 %d 条命。" $name $numLives |
default |
如果 $value 的内容为 nil 或为空,则赋值为字符串 “placeholder” | default "placeholder" $value |
|
list |
基于一系列输入返回一个新列表 | list "ClusterIP" "NodePort" "LoadBalancer" |
has |
确定列表中是否存在某个元素 | has 4 $list |
b64enc/b64dec |
使用 Base64 编码或解码。适用于处理 Secrets。 | b64enc $mySecret |
atoi |
将字符串转换为整数 | atoi $myIntegerString |
add |
添加一组整数 | add 1 2 3 |
upper/lower |
将整个字符串转换为大写或小写 | upper $myString |
now |
获取当前日期和时间 | Now |
date |
以指定格式格式化日期 | now | date "2006-01-02" |
表 6.3 – 常见 Helm 函数列表
在更好地理解如何使用 Helm 操作和格式化模板中的内容后,让我们来看看如何引入流程控制来管理将要渲染的内容。
Helm 模板控制结构
模板的生成方式可以通过 chart 开发者管理,这得益于控制结构提供的功能。控制结构作为Go模板中的actions组件的一部分,使得能够精细控制流,以确定应生成哪些资源及其渲染方式。
以下控制结构关键字可用:
-
if/else—为资源生成创建条件块 -
with—修改正在生成的资源的作用域 -
range—遍历一组资源
在某些情况下,模板的某些部分需要根据某种条件被包含或排除。在这种情况下,可以使用if/else操作。以下是一个基本示例,用于条件性地确定是否将就绪探针作为部署资源的一部分:
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
{{- end }}
readinessProbe部分仅在条件求值为true时才会包含。然而,值得注意的是,该条件实际上是一个管道,可以将多个语句链在一起,以帮助创建复杂的条件。if/else操作背后的逻辑也可以这样理解:
{{ if PIPELINE }}
# Do something
{{ else if OTHER PIPELINE }}
# Do something else
{{ else }}
# Default case
{{ end }}
条件语句及其关联的if/else操作对任何具有编程经验的人来说应该都很熟悉。但是,确定管道是true还是false的逻辑是什么呢?
当返回以下内容时,管道未能求值为true:
-
一个
false布尔值 -
一个数字
0 -
一个空字符串
-
nil(无论是空的还是null) -
一个空集合
因此,在前面的场景中,当条件逻辑应用于就绪探针时,只有在值为readinessProbe.enabled=true时,探针才会被包含。
条件嵌套也可以在模板中应用。以下代码片段演示了如何使用条件判断来确定应应用于readinessProbe的探针类型:
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
{{- if eq .Values.readinessProbe.type "http" }}
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
{{- else }}
tcpSocket:
port: 8080
{{- end }}
{{- end }}
当readinessProbe.type属性等于"http"时,将应用httpGet探针类型。否则,将使用传输控制协议(TCP)探针。
eq(等于的缩写)在if语句中是用于测试两个参数是否相等的布尔函数之一。当readinessProbe.type等于http时,将应用httpGet探针类型。否则,将使用 TCP 探针类型。
提供了完整的可用布尔函数列表:
-
and -
or -
not -
eq(等于的缩写) -
ne(不等于的缩写) -
lt(小于的缩写) -
le(小于或等于的缩写) -
gt(大于的缩写) -
ge(大于或等于的缩写)
图表开发者可以使用的另一种流控制方法是修改正在渲染的资源的作用域。句点(.)表示当前作用域,到目前为止,我们一直在根作用域或顶级作用域下操作。本章前面介绍的每个内置对象都可以在这个级别上使用。然而,当处理具有深度嵌套结构的对象时,可能需要修改应用的作用域,以避免较为笨重的属性引用。
with动作提供了这些必要的功能来修改当前作用域。
查看位于 Git 仓库中chapter6/examples/flowcontrol-example的flowcontrol-example Helm 图表。在values.yaml文件中包含了一个深度嵌套的属性字典,如下所示:
book:
chapter6:
props:
chapterNumber: 6
chapterName: Understanding Helm Templates
这些值应该看起来很熟悉,因为在本章中它们已经被多次使用,但请注意,它们现在被放置在一个深度嵌套的字典中。它们可以通过以下方式引用:
chapterNumber: {{ .Values.book.chapter6.props.chapterNumber }}
chapterName: {{ .Values.book.chapter6.props.chapterName }}
然而,通过使用with动作,当前作用域会发生变化,从而使得该块内的引用从.Values.book.chapter6.props开始,大大提高了可读性并减少了复杂性。你可以在以下代码片段中看到这个示例:
{{- with .Values.book.chapter6.props }}
chapterNumber: {{ .chapterNumber }}
chapterName: {{ .chapterName }}
{{- end }}
这在位于chapter6/examples/flowcontrol-example/templates/configmap.yaml的 ConfigMap 中进行了说明。使用helm template命令渲染图表,以确认 ConfigMap 中的值是否正确生成,如下所示:
$ helm template flowcontrol-example chapter6/examples/flowcontrol-example
修改作用域时有一个非常重要的注意事项,那就是图表开发者在尝试引用任何内置对象(如.Release或.Chart)时,可能会感到意外,尤其是在作用域发生变化的块中。
尝试在 ConfigMap 中使用以下模板时,在实例化时会导致错误:
{{- with .Values.book.chapter6.props }}
chapterNumber: {{ .chapterNumber }}
chapterName: {{ .chapterName }}
ChartName: {{ .Chart.Name }}
{{- end }}
Error: template: flowcontrol-example/templates/configmap.yaml:12:22: executing "flowcontrol-example/templates/configmap.yaml" at <.Chart.Name>: nil pointer evaluating interface {}.Name
这是因为with语句中的当前作用域不再位于根作用域,根作用域是内置对象所在的位置。幸运的是,Helm 提供了一种通过使用$引用根作用域的方法。通过在.Chart.Name引用前加上$,就不会再发生渲染错误。你可以在以下代码片段中看到这个用法:
{{- with .Values.book.chapter6.props }}
chapterNumber: {{ .chapterNumber }}
chapterName: {{ .chapterName }}
ChartName: {{ $.Chart.Name }}
{{- end }}
图表开发人员需要注意的最终流控制操作是range—当对列表和字典进行foreach风格的迭代时,这个操作非常有用。与with操作类似,range操作也会修改正在渲染的资源范围。
例如,假设以下内容作为values.yaml文件中的值,表示与服务资源相关联的端口:
service:
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
通过使用range操作,这些值可以应用于服务,如以下示例所示:
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
spec:
type: ClusterIP
ports:
{{- range .Values.service.ports }}
- port: {{ .port }}
targetPort: {{ .targetPort }}
protocol: TCP
name: {{ .name }}
{{- end }}
selector:
app: {{ .Release.Name }}
range操作的作用类似于with操作,在块内,当前范围代表ports列表中每个端口在每次迭代中的值,并可以相应地引用它。这个实践示例可以在 Git 仓库中的flowcontrol-example图表中找到,路径是chapter6/examples/flowcontrol-example。
生成发布说明
Helm 模板的一种特殊类型是NOTES.txt文件,位于 Helm 图表的templates/文件夹中。这个文件用于在通过 Helm 安装应用程序后动态生成使用说明(或其他细节)。
NOTES.txt文件使用与 Kubernetes 资源模板完全相同的模板语法,如以下示例所示:
Follow these instructions to access your application.
{{- if eq .Values.serviceType "NodePort" }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{.Release.Name }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo "URL: http://$NODE_IP:$NODE_PORT
{{- else }}
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Name }} wordpress --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
echo "URL: http://$SERVICE_IP"
{{- end }}
这些示例将提供有关如何访问由图表部署的应用程序的说明。在安装、升级和回滚阶段,这些信息将会显示出来,并且可以通过运行helm get notes命令来调用。通过提供一个NOTES.txt文件,图表开发人员可以为刚刚部署的应用程序提供更多的使用建议。
在下一节中,我们将讨论 Helm 模板变量。
Helm 模板变量
除了利用值和其他内置对象外,图表开发人员还可以在图表模板中创建自己的变量,以提供额外的处理选项。这种方法的常见用例是流控制,但模板变量也可以用于其他用例。
图表模板中的变量定义如下:
{{ $myvar := "Hello World!" }}
上述示例创建了一个名为myvar的变量,并将其值设置为等于Hello World!的字符串。变量也可以赋值给对象,如图表的值,如这里所示:
{{ $myvar := .Values.greeting }}
一旦定义了变量,可以通过以下方式引用它:
data:
greeting.txt: |-
{{ $myvar }}
使用变量的另一个例子是在range块中,变量捕获列表迭代的索引和值,如以下代码片段所示:
data:
greetings.txt
{{- range $index, $value := .Values.greetings }}
Greeting {{ $index }}: {{ $value }}
{{- end }}
index表示当前循环迭代的编号,而value表示迭代中来自列表的值。前面的代码片段渲染结果如下:
data:
greetings.txt
Greeting 0: Hello
Greeting 1: Hola
Greeting 2: Hallo
变量还可以简化映射迭代的处理,如下所示:
data:
greetings.txt
{{- range $key, $val := .Values.greetings }}
Greeting in {{ $key }}: {{ $val }}
{{- end }}
可能的结果可能如下所示:
data:
greetings.txt
Greeting in English: Hello
Greeting in Spanish: Hola
Greeting in German: Hallo
Helm 变量的另一个常见用例是引用当前范围之外的值。
考虑以下with块:
{{- with .Values.application.configuration }}
My application is called {{ .Release.Name }}
{{- end }}
像这样的模板会失败处理,因为.Release.Name不在.Values.application.configuration的作用域下。解决这个问题的一种方法是在with块上方设置一个变量为.Release.Name,如下所示:
{{ $appName := .Release.Name }}
{{- with .Values.application.configuration }}
My application is called {{ $appName }}
{{- end }}
虽然这是一个可能的解决方案,但使用美元符号($)引用全局作用域的方法更为推荐,因为它配置的行数较少,随着复杂度增加,它的可读性更好。在这种情况下,这个模板可以重写为如下:
{{- with .Values.application.configuration }}
My application is called {{ $.Release.Name }}
{{- end }}
接下来我们将探索模板验证。
Helm 模板验证
当使用 Kubernetes 和 Helm 时,输入验证会在创建新资源时由 Kubernetes API 服务器自动执行。这意味着如果 Helm 创建了一个无效资源,API 服务器会返回错误信息,导致安装失败。尽管 Kubernetes 执行输入验证,仍然可能存在一些情况下,图表开发人员希望在资源到达 API 服务器之前执行验证,例如返回简单的错误信息或限制用户的选择范围。
在 Helm 中,输入验证指的是验证用户提供的值,以确保用户提供了适当的值集。你可以通过以下三种方式(或它们的组合)来执行这种验证:
-
使用
fail函数 -
使用
required函数 -
使用
values.schema.json文件
让我们首先通过查看fail函数来开始探索输入验证。
fail函数
fail函数用于立即使 Helm 安装失败,通常用于用户提供了无效值的情况。在本节中,我们将探索一个fail函数的示例用例,该函数限制用户输入,并在用户提供的值不在预期值集之外时停止安装。
许多 Helm 图表支持为设置 Kubernetes 服务类型提供值。用户可以选择多种服务类型,以下是一些常见类型:
-
ClusterIP:为 Service 分配一个Internet 协议(IP)地址,仅在集群内可访问。 -
NodePort:在每个 Kubernetes 节点上公开一个端口。集群外部可以访问。 -
LoadBalancer:如果适用,创建一个负载均衡器,在 Kubernetes 部署的云提供商上创建。
假设我们希望限制用户只能创建ClusterIP或NodePort类型的 Service。如果 Service 类型不是这两种类型中的任何一种,我们可以使用fail函数使其失败并提供错误信息。
位于chapter6/examples/fail-example的 Git 仓库中的示例演示了这个用例。在values.yaml文件中,我们看到如下的值:
service:
type: ClusterIP
在service.yaml模板(位于图表的templates/文件夹中)中,我们看到以下代码行:
{{- $serviceTypes := list "ClusterIP" "NodePort" }}
{{- if has .Values.service.type $serviceTypes }}
type: {{ .Values.service.type }}
{{- else }}
{{- fail "value 'service.type' must be either 'ClusterIP' or 'NodePort'" }}
{{- end }}
在前面的模板片段中,我们首先创建了一个名为 serviceTypes 的变量,并将其设置为一个字符串列表,包含 ClusterIP 和 NodePort 类型。然后,在 if 操作中,我们使用 has 函数来确定 service.type 值是否包含在 serviceTypes 列表中,表示允许的值集。如果找到提供的值,则我们假设输入有效,并渲染服务类型并继续安装。否则,将调用 fail 函数,失败安装,并向用户显示解释失败原因的消息。
由于默认服务已经是 ClusterIP(如在 values.yaml 文件中所见),我们知道运行 helm template 或 helm install 而不提供任何额外值将会成功。但让我们看看如果尝试将 service.type 值设置为无效设置(例如 LoadBalancer)会发生什么:
$ helm template fail-example chapter6/examples/fail-example --set service.type=LoadBalancer
Error: execution error at (fail-example/templates/service.yaml:10:6): value 'service.type' must be either 'ClusterIP' or 'NodePort'
如您在错误消息中所见,fail 函数导致渲染提前失败,并显示在服务模板中编码的错误消息。
让我们看一下执行输入验证的下一个方式 —— required 函数。
required 函数
与 fail 不同,required 函数也用于停止模板渲染。不同之处在于,与 fail 不同,required 函数用于确保在渲染图表模板时不会留空值。它被命名为此,因为当指定时它要求用户提供一个值。
看一下图表中 chapter6/examples/required-example 的 values.yaml 文件的片段:
service:
type:
在此图表的 service.yaml 模板中,我们看到以下输出:
spec:
type: {{ required "value 'service.type' is required" .Values.service.type }}
此次调用的 required 检查service.type 值所表示的字符串是否为空。如果为空,则渲染失败并显示错误消息。否则,它将渲染 service.type 值。
通过使用 helm template 命令,我们可以看到它的实际效果,如下所示:
$ helm template required-example chapter6/examples/required-example
Error: execution error at (required-example/templates/service.yaml:6:11): value 'service.type' is required
正如预期的那样,我们收到了一个错误消息,指出 service.type 值是必需的。用户可以通过使用 --set 或 --values 标志为 service.type 提供一个值来解决此错误。
让我们探索一下我们将涉及的最终验证方法 —— values.schema.json 文件。
values.schema.json 文件
values.schema.json 文件用于定义和强制执行图表值的模式。而 required 和 fail 函数是从图表模板内调用的,values.schema.json 文件允许您在单个位置设置值要求和约束。此文件还添加了其他验证功能,如设置整数值的最小值和最大值。
values.schema.json 文件基于JSON Schema词汇。关于 JSON Schema 的详细概述超出了本书的范围,但您可以访问 json-schema.org/specification.html 来探索词汇表。
让我们回顾一下位于 Git 仓库 chapter6/examples/schema-example 中的示例 values.schema.json 文件。您可以在这里看到它的表示:

图 6.1 – 示例 values.schema.json 文件
此模式为.Values下的以下对象提供验证:
| 对象 | 验证 |
|---|---|
.Values.image |
确保image对象存在 |
.Values.image.repository |
确保image.repository值存在并且是字符串类型 |
.Values.image.tag |
确保image.tag值存在并且是字符串类型 |
.Values.service |
确保服务对象存在 |
.Values.service.type |
确保service.type值存在并设置为ClusterIP或NodePort |
.Values.service.port |
确保service.port值存在且大于或等于8080 |
表 6.4 – 在示例 values.schema.json 文件中验证的值
如前表所示,通过提供values.schema.json文件,执行了大量的强大验证。可以向模式文件中添加更多值,但我们这里只包含了少量内容用于演示。有时,将所有支持的值都包含在values.schema.json文件中是有用的,目的是为了自我文档化或确保所有值都经过严格验证。
使用values.schema.json文件时,错误消息会自动处理。例如,我们可以看到如果尝试将service.type设置为LoadBalancer(这是模式中枚举器(enum)未支持的值)会发生什么。以下是结果:
$ helm template schema-example chapter6/examples/schema-example --set service.type=LoadBalancer
Error: values don't meet the specifications of the schema(s) in the following chart(s):
schema-example:
- service.type: service.type must be one of the following: "ClusterIP", "NodePort"
请注意,我们不需要指定返回给用户的具体错误信息—JSON Schema 库会自动提供。
在本节中,我们回顾了三种不同的输入验证策略。接下来,我们将探讨如何通过命名模板和库图表启用模板重用。
通过命名模板和库图表启用代码重用
在创建模板文件时,可能会在不同的 Kubernetes 资源之间出现冗余或重复的 YAML 代码块。
例如,您可能会努力为每个资源使用一致的标签集,如下所示:
labels:
"app.kubernetes.io/instance": {{ .Release.Name }}
"app.kubernetes.io/managed-by": {{ .Release.Service }}
"helm.sh/chart": {{ .Chart.Name }}-{{ .Chart.Version }}
"app.kubernetes.io/version": {{ .Chart.AppVersion }}
之前的标签可以手动复制粘贴到您的模板中,但这会显得繁琐,特别是当您想在未来更新这些标签时。为了帮助减少冗余代码并启用重用,Helm 提供了一个构造机制,称为命名模板。
命名模板与常规的 Kubernetes 模板一样,都是在templates/目录下定义的。它们以下划线开头,并以.tpl文件扩展名结尾。许多图表(包括我们的 Guestbook 图表)利用一个名为_helpers.tpl的文件,其中包含这些命名模板,尽管文件不需要叫做helpers。使用helm create命令创建新图表时,这个文件将包含在生成的资源集中。
要创建一个命名模板,图表开发者可以利用define操作。以下示例创建了一个命名模板,用于封装资源标签:
{{- define "mychart.labels" }}
labels:
"app.kubernetes.io/instance": {{ .Release.Name }}
"app.kubernetes.io/managed-by": {{ .Release.Service }}
"helm.sh/chart": {{ .Chart.Name }}-{{ .Chart.Version }}
"app.kubernetes.io/version": {{ .Chart.AppVersion }}
{{- end }}
define操作接受模板名称作为参数。在上面的示例中,模板名称称为mychart.labels。命名模板的常见约定是$CHART_NAME.$TEMPLATE_NAME,其中$CHART_NAME是 Helm 图表的名称,$TEMPLATE_NAME是描述模板用途的简短描述性名称。mychart.labels名称意味着该模板是mychart Helm 图表特有的,并且将为应用它的资源生成标签。
要在 Kubernetes YAML 模板中使用命名模板,可以使用include函数,其用法如下所示:
include [TEMPLATE_NAME] [SCOPE]
TEMPLATE_NAME参数是应该处理的命名模板的名称。SCOPE参数是应该处理值和内置对象的范围。大多数情况下,这个参数是一个点(.),表示当前顶层范围,但可以提供任何范围,包括美元符号($),如果命名模板引用当前范围外的值,则应使用美元符号。
以下示例演示了如何使用include函数来处理命名模板:
metadata:
name: {{ .Release.Name }}
{{- include "mychart.labels" . | indent 2 }}
此示例首先将资源的名称设置为发布的名称。然后,它使用include函数来处理标签,并将每行缩进两个空格,如流水线所声明的。处理完成后,渲染的资源可能会如下所示,用于名为template-demonstration的发布:
metadata:
name: template-demonstration
labels:
"app.kubernetes.io/instance": template-demonstration
"app.kubernetes.io/managed-by": Helm
"helm.sh/chart": mychart-1.0.0
"app.kubernetes.io/version": 1.0
Helm 还提供了一个template操作,可以展开命名模板。这个操作与include的用法相同,但有一个主要限制——它不能在流水线中用于提供额外的格式化和处理能力。template操作用于简单地内联显示数据。因此,图表开发者应该优先使用include函数而不是template操作,因为include不仅具有与template相同的功能对等性,还提供了额外的处理选项。
命名模板对于减少单个 Helm chart 中的样板代码非常有用,但假设你想在多个 Helm chart 中共享通用的样板代码(如标签)。为此,你可以利用在 Chart.yaml 中将 type 字段设置为 library。库 chart 与其他 chart 的不同之处在于它不能被安装——库 chart 的目的是提供一组帮助模板,然后可以通过依赖管理在不同的应用程序 chart 中导入这些模板。
一个库 chart 的示例是 Bitnami 的 common chart,可以在以下链接找到:github.com/bitnami/charts/tree/master/bitnami/common。在这里,你会发现该 chart 的每个模板实际上都是一个包含命名模板的 tpl 文件。以下是来自 Bitnami common 库 chart 的一个简化列表:
-
_affinities.tpl -
_capabilities.tpl -
_errors.tpl -
_images.tpl
这些命名模板可以通过将以下依赖项添加到任何应用程序 Helm chart 中来使用:
dependencies:
- name: common
version: 0.x.x
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
然后,任何导入此依赖项的 chart 都可以通过使用 include 函数引用模板名称来使用任何模板——例如,{{ include "common.names.fullname" . }}。
在接下来的部分,我们将探讨 Helm 如何处理 Kubernetes 自定义资源(CR)的创建。
创建 CRD(自定义资源定义)
虽然 Helm 通常用于创建传统的 Kubernetes 资源,但它也可以用于创建 CRD 和 CR。CRD 用于定义 Kubernetes API 中没有的资源。你可能希望使用这个功能来扩展 Kubernetes 提供的能力。CR 是实现 CRD 规范的资源。因此,确保在创建实现它的 CR 之前总是先创建 CRD 非常重要。
当 CRD 被包含在 Helm chart 的 crds/ 文件夹中时,Helm 能够确保在 CR 包含在 Helm chart 中之前,CRD 已经在 Kubernetes 中创建并注册。该文件夹下定义的所有 CRD 会在 templates/ 文件夹中的 CRD 之前创建。
下面是一个 crds/ 文件夹的示例:
crds/
my-custom-resource-crd.yaml
my-custom-resource-crd.yaml 文件可能包含以下内容:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: my-custom-resources.learnhelm.io
spec:
group: learnhelm.io
names:
kind: MyCustomResource
listKind: MyCustomResourceList
plural: MyCustomResources
singular: MyCustomResource
scope: Namespaced
version: v1
然后,templates/ 目录可以包含 MyCustomResource 资源的一个实例(即 CR),如下所示:
templates/
my-custom-resource.yaml
在使用 Helm 创建 CRD 时,有几个重要的注意事项。首先,CRD 不能被模板化,因此它们会按在 CRD 文件夹中定义的方式创建。其次,CRD 不能使用 Helm 删除,因此也无法升级或回滚。第三,创建 CRD 需要 Kubernetes 集群中的 cluster-admin 权限。请注意,这些限制仅适用于 CRD,而不是 CR。由于 CR 是在 templates/ 文件夹中创建的,它们会被 Helm 视为普通的 Kubernetes 资源模板。CR 通常也不需要提升到 cluster-admin 级别的权限,因此通常可以由普通用户安装。
在本章中,我们讨论了如何使用模板通过 Helm 渲染 Kubernetes 资源。在下一节中,我们将讨论高级 Helm 图表用户如何在运行安装时进一步处理 Kubernetes 资源。
后渲染
在开发 Helm 图表时,你应仔细考虑图表中需要包含的每一个不同值。例如,如果你知道用户可能需要修改 Service 模板中的 Service 类型,那么你应该暴露一个值来实现这一点,以保持图表的灵活性。对于镜像名称、资源、健康检查以及其他用户需要根据使用案例配置的设置,采用相同的思路。
然而,有时用户仍然需要一些 Helm 图表未提供的额外灵活性。这时,后渲染功能就派上用场了。后渲染是 Helm 的一个高级功能,它允许用户在安装图表时对渲染后的模板进行进一步修改。如果用户需要的修改是 Helm 图表不支持的,后渲染通常被视为最后的解决方案。
后渲染通过将 --post-renderer 标志添加到 install、upgrade 或 template 命令来应用。以下是一个示例:
$ helm install <release-name> <path-to-chart> --post-renderer <path-to-executable>
<path-to-executable> 参数是一个可执行文件,它调用后渲染器。该可执行文件可以是从 Go 程序到调用其他工具(如 Kustomize)的 shell 脚本。Kustomize 是一个用于修补 YAML 文件的工具,因此它通常用于后渲染。
我们不会深入讨论 Kustomize,因为它超出了本书的范围。然而,我们在 Git 仓库中提供了一个使用 Kustomize 作为后渲染器的示例,位置在 chapter6/examples/post-renderer-example,可以按照以下方式调用,假设本地机器上已安装 kustomize 命令行工具:
$ cd chapter6/examples/post-renderer-example/post-render
$ helm template nginx ../nginx --post-renderer ./hook.sh
hook.sh 文件调用 Kustomize,它将使用自定义环境变量和在 kustomization.yaml 文件中定义的 NodePort 服务类型来修补部署和服务 YAML 资源。
在本节中,我们讨论了后渲染。在我们结束这个话题之前,有一点需要说明:后渲染不应被视为 Helm 的常规使用方式。作为图表开发者,你应确保你的图表足够灵活,让用户可以开箱即用,直接利用图表。而作为图表用户,除非绝对必要,否则应该尽量避免使用后渲染。这是因为你需要记得在每次 Helm 升级时使用 --post-renderer 标志,否则补丁可能会被遗漏。后渲染还需要用户额外的维护工作,因为可能需要工具或其他先决条件。
在本章中,我们已经介绍了 Helm 模板的每个关键组件。接下来,我们将通过返回到我们的 Guestbook 图表来将这些内容整合在一起。我们将对脚手架生成的 values.yaml 文件和 deployment.yaml 模板进行小幅更新,并部署我们的 Guestbook Helm 图表。
更新并部署 Guestbook 图表
为了成功部署我们的 Guestbook 应用程序,我们需要添加配置以下详细信息的值:
-
配置 Redis 服务名称并禁用 Redis 身份验证
-
创建环境变量以定义 Redis 主节点和从节点的名称
我们将首先处理 Redis 的值。
更新 Redis 值
在 第五章,《Helm 依赖管理》中,我们为创建后端创建了一个 Redis 依赖项。现在,我们将在 values.yaml 文件中添加一些值,以完成配置。
我们需要添加的值位于 Git 仓库的 github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter6/guestbook/values.yaml#L5-L8 中,并显示在以下代码片段中:
redis:
fullnameOverride: redis
auth:
enabled: false
redis.fullnameOverride 值用于确保部署的 Redis 实例以 redis 为前缀。这将帮助确保 Guestbook 应用程序始终与一致命名的实例进行通信。
将 redis.auth.enabled 设置为 false 将禁用 Redis 身份验证。这样做是必要的,因为 Guestbook 前端没有配置与 Redis 的身份验证。
这就是 Redis 所需的所有更改。接下来,让我们更新 Guestbook 的值和模板。
更新 Guestbook 的部署模板和 values.yaml 文件
我们在 第四章,《创建新的 Helm 图表》中使用的 helm create 命令为我们提供了几乎所有此应用程序所需的模板功能,但为了部署 Guestbook,有一个缺口需要填补。我们需要能够在 Guestbook 部署中设置环境变量,以告诉前端如何连接到 Redis。
如果我们查看 github.com/GoogleCloudPlatform/kubernetes-engine-samples/blob/main/guestbook/php-redis/guestbook.php 中的 Guestbook 源代码,我们可以看到需要设置的三个不同环境变量,具体如下:
-
GET_HOSTS_FROM:通知 Guestbook 是否应该从环境中检索 Redis 主机名。我们将其设置为env,以便从接下来定义的两个环境变量中检索主机名。 -
REDIS_LEADER_SERVICE_HOST:提供 Redis 主节点的主机名。由于我们使用的 Redis 依赖项指定主节点为redis-master,我们将把该值设置为redis-master。 -
REDIS_FOLLOWER_SERVICE_HOST:提供 Redis 从节点的主机名。我们正在使用的 Redis 依赖项指定从节点为redis-replicas,因此我们将这个值设置为redis-replicas。
由于框架生成的 deployment.yaml 模板未能创建环境变量,我们需要将这一逻辑自己写入模板。我们可以通过添加位于github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter6/guestbook/templates/deployment.yaml#L50-L51的代码行来实现这一点,这里也展示了这些代码:
env:
{{- toYaml .Values.env | nindent 12 }}
在这里,我们添加了一个新的 env 对象。在其下方,我们使用 toYaml 函数将 env 值(我们稍后会添加)格式化为 YAML 对象。然后,我们使用管道和 nindent 函数形成新的一行,并缩进 12 个空格。
接下来,我们需要将带有相关内容的 env 对象添加到我们的 values.yaml 文件中。一个示例位于github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter6/guestbook/values.yaml#L10-L16并且这里也展示了这个示例:
env:
- name: GET_HOSTS_FROM
value: env
- name: REDIS_LEADER_SERVICE_HOST
value: redis-master
- name: REDIS_FOLLOWER_SERVICE_HOST
value: redis-replicas
随着 Guestbook 图表的值和模板更新,我们可以进入下一部分。
部署 Guestbook 图表
现在,是时候使用我们的 Helm 图表部署一个功能完整的 Guestbook 实例了!首先,启动你的 minikube 环境并为本章创建一个新的命名空间,如下所示。如果你在本章开始时已经创建了 chapter6 命名空间,你可以跳过接下来的两个命令:
$ minikube start
$ kubectl create namespace chapter6
然后,使用 helm install 命令来部署 Guestbook 实例,如下所示。你还应该监视该命名空间中的 pod,等待 Redis pod 准备就绪:
$ helm install guestbook chapter6/guestbook -n chapter6
$ kubectl get pods –n chapter6 –w
一旦 Redis 实例准备就绪,按 Ctrl+C 停止监视资源,然后使用 kubectl port-forward 命令将 Guestbook 前端暴露到本地 8080 端口:
$ kubectl port-forward svc/guestbook 8080:80 -n chapter6
一旦 Guestbook 服务被暴露,你可以在浏览器中导航到 http://localhost:8080 统一资源定位符(URL)。Guestbook 前端页面应该会出现。尝试输入一条信息,例如 Hello world!,并且一条信息应出现在蓝色 Submit 按钮下方,如下所示:

图 6.2 – 提交 Hello world! 消息后的 Guestbook 前端
如果你能够在浏览器中加载页面并输入消息,那么恭喜你!你已经成功构建并部署了你的第一个 Helm 图表!然而,和任何软件产品一样,你总是可以继续改进。我们将在下一章中通过添加升级和回滚钩子来增强这个图表,从而实现 Redis 数据库的备份和恢复。
现在,当你完成工作后,可以随时清理你的环境并停止 minikube 实例,操作如下:
$ kubectl delete ns chapter6
$ minikube stop
本节到此结束。
总结
模板代表了 Helm 功能的核心。它们通过启用动态 YAML 生成,允许你创建各种不同的 Kubernetes 资源配置。基于 Go 模板的 Helm 模板引擎包含多个内置对象,图表开发者可以基于这些对象构建图表,例如 .Values 和 .Release 对象。模板还提供了许多不同的函数,用于提供强大的格式化和操作功能,以及启用条件逻辑和循环的控制结构。除了渲染 Kubernetes 资源外,模板还可以通过使用命名模板和库图表来抽象化模板代码。
通过结合模板提供的功能,我们能够在本章末尾对 Guestbook 图表进行小的修改,通过增强值和 deployment.yaml 图表模板的内容,成功部署了 Guestbook 应用程序。在下一章中,我们将继续探索模板,并通过学习和利用生命周期钩子来增强我们的 Helm 图表。
进一步阅读
要了解更多关于创建 Helm 图表的基础知识,请参考 Helm 文档中的图表模板开发者指南页面,网址:helm.sh/docs/chart_template_guide/。
问题
看看你是否能回答以下问题:
-
你可以利用哪种 Helm 模板构造来生成重复的 YAML 部分?
-
with动作的目的是什么? -
Helm 模板中有哪些不同的内置对象?
-
Kubernetes 资源模板与命名模板有什么不同?
-
应用图表与库图表有什么区别?
-
图表开发者可以做些什么来执行输入验证?
-
在 Helm 模板中常用的不同函数有哪些例子?
-
模板变量和数值之间有什么区别?
第七章:Helm 生命周期钩子
Helm 发布在其生命周期中经历多个不同的阶段。第一个阶段是安装,当 Helm 图表首次安装时发生。第二个阶段是升级,当通过更新值或 Helm 图表本身来更新 Helm 发布时发生。在稍后的某个时刻,Helm 用户可能需要执行回滚阶段,这将 Helm 发布恢复到先前的状态。最后,如果用户需要删除 Helm 发布及其关联的资源,则必须执行卸载阶段。
每个阶段本身都非常强大,但为了在发布生命周期中提供额外的功能,Helm 提供了一个钩子机制,允许在发布周期的不同点执行自定义操作。例如,您可以使用钩子来执行以下操作:
-
在升级后对数据库执行操作,例如备份,或者在回滚期间从先前的快照恢复图表。
-
在安装图表后从秘密管理引擎获取秘密。
-
卸载图表后清理外部资源。
在本章中,我们将探索 Helm 钩子,并了解它们如何增强 Helm 图表的功能。然后,我们将在 Guestbook Helm 图表中实现钩子,以便在 Helm 发布升级和回滚时备份和恢复 Redis 数据库。
在本章中,我们将涵盖以下主题:
-
Helm 钩子的基础
-
钩子生命周期
-
钩子清理
-
在 Guestbook Helm 图表中编写钩子
-
清理
技术要求
本章中,您将需要以下工具:
-
minikube
-
kubectl
-
Helm
-
Git
我们将在本章中使用 minikube 探索多个示例,因此请随时使用以下命令启动你的 minikube 环境:
$ minikube start
一旦 minikube 启动完成,创建一个新的命名空间以供本章使用:
$ kubectl create namespace chapter7
如果您在前几章中还没有克隆示例的 git 仓库,请运行以下命令来克隆仓库:
$ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git
接下来,让我们了解 Helm 钩子的基础知识,并探索一个执行钩子的示例。
Helm 钩子的基础知识
钩子是在发布生命周期中的指定时间点作为一次性操作执行的。钩子作为 Kubernetes 资源实现,更具体地说,是在容器内实现的。虽然 Kubernetes 中的大多数工作负载是设计为长时间运行的进程,例如提供 API 请求的应用程序,但钩子由一个单独的任务或一组任务组成,返回 0 表示成功,返回非 0 表示失败。
在 Kubernetes 环境中创建短期任务时,通常使用的是裸restartPolicy属性。默认情况下,该字段配置为Always,这意味着如果 Pod 完成(无论是成功还是失败),Pod 将会被重启。尽管有些场景下会运行裸 Pod,但更推荐将生命周期钩子作为作业运行,这相比裸 Pod 有优势,包括当节点故障或不可用时,可以重新调度钩子。
由于钩子仅被定义为 Kubernetes 资源,因此它们像其他 Helm 模板一样创建,并放置在templates/文件夹中。然而,钩子有所不同,它们总是带有helm.sh/hook注解。钩子使用此注解来确保在标准处理过程中不会像其他资源那样被渲染。相反,它们的渲染和应用是基于helm.sh/hook注解中指定的值,该值决定了钩子在 Kubernetes 中作为 Helm 发布生命周期的一部分何时执行。
以下是一个钩子的示例。这个示例也可以在本书的 GitHub 仓库中找到,路径为chapter7/examples/hook-example/templates/hooks/job.yaml:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-hook
annotations:
"helm.sh/hook": post-install
spec:
template:
metadata:
name: {{ .Release.Name }}-hook
spec:
restartPolicy: Never
containers:
- name: {{ .Release.Name }}-hook
command: ["/bin/sh", "-c"]
args:
- echo "Hook executed at $(date)"
image: alpine
这个简单的例子在图表安装后打印出当前的日期和时间。这类钩子的一个使用场景是与审计系统集成,用于跟踪应用程序在 Kubernetes 环境中安装的时间。请注意,尽管钩子保存在template/hooks/文件夹下,但它也可以直接保存在templates/下。额外的hooks/子目录仅用于将特定应用的模板与钩子模板分开。
为了演示 Helm 钩子的行为,我们通过安装位于chapter7/examples/hook-example中的 Helm 图表来查看这个钩子:
-
首先,安装
hook-exampleHelm 图表:$ helm install my-app chapter7/examples/hook-example –n chapter7
请注意,这个命令的执行时间可能会比过去调用的 Helm 命令要长。这是因为 Helm 正在等待钩子被创建并完成后才返回。
接下来,查看命名空间中的 Pod。你将看到两个 Pod,第一个是应用程序,第二个是钩子:
$ kubectl get pods –n chapter7
NAME READY STATUS
my-app-hook-example-6747bfbb6-dd9xz 1/1 Running
my-app-hook-p8rrd 0/1 Completed
-
状态为
Completed的 Pod 是钩子。让我们查看 Pod 的日志,查看钩子输出的结果:$ kubectl logs jobs/my-app-hook –n chapter7 Hook executed at Mon Jan 17 21:40:38 UTC 2022
正如你所看到的,钩子在 Helm 图表安装后记录了时间。
-
卸载发布并检查剩余的 Pod。你会看到钩子仍然存在于命名空间中:
$ helm uninstall my-app –n chapter7 $ kubectl get pods –n chapter7 NAME READY STATUS my-app-hook-p8rrd 0/1 Completed
一旦钩子被创建并执行,它们就变得不可管理。(除非应用了helm.sh/hook-delete-policy注解。我们将在本章的高级钩子概念部分详细讲解。)因此,我们需要自己清理钩子。现在我们通过删除作业来完成此操作:
$ kubectl delete job my-app-hook –n chapter7
此时,所有与图表安装相关的资源已被清理。
由于钩子可能包含长时间运行的任务,因此发布可能会超时。默认情况下,Helm 设置了 5 分钟的超时值来完成与发布相关的所有步骤。当希望使用不同的值时,可以在执行 helm install 或 helm upgrade 时使用 --timeout 标志来控制与发布相关的超时。如果钩子需要较长时间运行,且可能超出默认的超时值,则可能需要修改此值。
现在我们对 Helm 钩子有了基本的理解,让我们来看一下不同的钩子生命周期选项。
Helm 钩子生命周期
正如你在上一节中看到的关于作业钩子的内容,作业执行的时机是基于 helm.sh/hook 注解的值。由于指定了 post-install,作业在所有相关资源作为发布的一部分被创建后执行。post-install 选项表示 Helm chart 生命周期中的一个执行钩子的时机。
下表描述了 helm.sh/hook 注解的可用选项。每个钩子的描述可以在官方 Helm 文档中找到,文档地址为helm.sh/docs/topics/charts_hooks/#the-available-hooks:
| 注解值 | 描述 |
|---|---|
pre-install |
在模板渲染后,但在 Kubernetes 中创建任何资源之前执行。 |
post-install |
在 Kubernetes 中创建所有资源后执行。 |
pre-delete |
在 Kubernetes 中删除任何资源之前,由删除请求触发执行。 |
post-delete |
在删除所有发布资源后,由删除请求触发执行。 |
pre-upgrade |
在模板渲染后,但在更新任何资源之前,由升级请求触发执行。 |
post-upgrade |
在所有资源已升级后,由升级请求触发执行。 |
pre-rollback |
在模板渲染后,但在回滚资源之前,由回滚请求触发执行。 |
post-rollback |
在所有资源已修改后,由回滚请求触发执行。 |
test |
在调用 helm test 子命令时执行。详情请参见第九章,测试 Helm Charts。 |
有时,你可能会有多个资源使用相同的helm.sh/hook设置。例如,你可能会有一个 ConfigMap 资源和一个作业资源,都标记为在同一阶段(如pre-upgrade)运行钩子。在这种情况下,你可以通过使用helm.sh/weight注解来定义这些资源的创建顺序。此注解用于为每个标记为在同一阶段执行的钩子资源分配权重值。权重按升序排序,因此权重最低的资源首先执行。如果没有应用权重,而 Helm 图表包含多个在同一阶段执行的钩子,那么 Helm 会通过按资源类型和名称的字母顺序对模板进行排序来推断执行顺序。
以下示例展示了如何将钩子权重的注解值设置为0:
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/weight": "0"
该钩子将在图表升级过程中执行,并且在必要的资源被渲染之后,但在它们应用到 Kubernetes 集群之前执行。
除了能够将钩子定位在单一生命周期阶段外,我们还可以使用helm.sh/hook注解来指定多个阶段。这可以通过指定一个用逗号分隔的生命周期阶段列表来完成。以下示例定义了一个钩子,应该在图表安装前后都执行:
annotations:
"helm.sh/hook": pre-install,post-install
在此示例中,当选择了pre-install和post-install选项时,helm install命令将如下执行:
-
用户启动 Helm 图表的安装(例如,运行
helm install wordpress bitnami/wordpress)。 -
如果
crds/文件夹中存在任何 CRD,它们将被安装到 Kubernetes 环境中。 -
图表模板会被验证,并且资源被渲染。
-
pre-install钩子按权重排序,然后被渲染并应用到 Kubernetes 环境中。 -
Helm 会等待直到钩子资源被创建,并且对于 Pods 和作业,会报告它们已处于
Completed或Error状态。 -
模板资源会被渲染并应用到 Kubernetes 环境中。
-
post-install钩子按权重排序后执行。 -
Helm 会等待直到
post-install钩子执行完成。 -
helm install命令的结果被返回。
在本节中,我们回顾了在不同生命周期阶段运行钩子的选项。接下来,我们将讨论与钩子相关的资源清理过程。
Helm 钩子清理
在Helm 钩子基础部分,我们提到,默认情况下,Helm 钩子不会在执行helm uninstall命令时与其他资源一起删除。相反,我们必须手动清理这些资源。幸运的是,有几种策略可以在发布生命周期中自动删除钩子。这些选项包括配置删除策略和为作业设置生存时间(TTL)。
helm.sh/hook-delete-policy注解用于设置钩子资源的删除策略。该注解决定了何时将资源从 Kubernetes 中移除。下表列出了可用的选项。你可以在 Helm 文档中找到这些选项的详细说明,网址为helm.sh/docs/topics/charts_hooks/#hook-deletion-policies:
| 注解值 | 描述 |
|---|---|
before-hook-creation |
在钩子启动之前删除先前的资源(这是默认选项) |
hook-succeeded |
如果钩子成功执行,则删除资源 |
hook-failed |
如果钩子执行失败,则删除资源 |
如果没有提供helm.sh/hook-delete-policy注解,则默认应用before-hook-creation策略。这意味着如果有任何现有的钩子资源被删除(如果存在),在执行钩子时会重新创建它们。这对于作业尤其有用,因为如果作业使用相同的名称被重新创建,可能会发生冲突。通过使用before-hook-create注解,我们可以避免这种情况。
还有一些情况可以使用其他类型的钩子清理策略。例如,你可能希望应用hook-succeeded值,该值在钩子成功执行后清理钩子,以避免保留过多的资源。然而,如果在执行钩子时发生错误,资源将会保留,以便帮助排查错误原因。hook-failed清理类型,顾名思义,在钩子执行失败时会删除关联的钩子资源。如果你不希望保留与钩子相关的资源,无论它是成功执行还是失败,这个选项可能会很有用。与helm.sh/hook注解类似,可以通过设置helm.sh/hook-delete-policy注解并使用逗号分隔的字符串来应用多个删除策略:
annotations:
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
Helm 删除策略是清理钩子后资源的最全面方式,但你也可以利用作业的ttlSecondsAfterFinished配置来定义作业在自动删除前应保留的 TTL。这将限制作业在完成后在命名空间中保留的时间。以下代码展示了如何使用ttlSecondsAfterFinished作业设置:
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-hook
annotations:
"helm.sh/hook": post-install
spec:
ttlSecondsAfterFinished: 60
<omitted>
在这个例子中,作业将在完成或失败后 60 秒被删除。
在本节中,我们讨论了自动清理资源的方法以及常规图表资源(即不与钩子关联的资源)在执行 helm uninstall 时如何自动删除。然而,有些情况下,你可能希望某些特定的图表资源遵循钩子的行为,即使在执行 helm uninstall 命令时仍然保留在集群中。这种情况的一个常见用例是,当你的图表通过独立的 PersistentVolumeClaim 资源(而不是由 StatefulSet 对象管理的 PersistentVolumeClaim 资源)创建了持久存储时。你可能希望这些存储在发布的正常生命周期之外被保留。你可以通过将 helm.sh/resource-policy 注解应用于该资源来启用这种行为,如下所示:
annotations:
"helm.sh/resource-policy": keep
请注意,当在非钩子资源上使用此注解时,如果重新安装图表,可能会发生命名冲突。
到目前为止,我们已经介绍了 Helm 钩子以及与之相关的各种选项。现在,让我们通过在 Guestbook Helm 图表中编写钩子来看看钩子带来的强大功能。
在 Guestbook Helm 图表中编写钩子
正如你可能记得的,Guestbook Helm 图表使用 Redis 数据库来持久化用户创建的消息。通过使用 Helm 钩子,我们可以在图表生命周期的不同阶段创建一个过程,执行 Redis 数据库的简单备份和恢复操作。让我们来看一下在本节中我们将创建的两个钩子:
-
第一个钩子将在
pre-upgrade生命周期阶段触发。这个阶段发生在执行helm-upgrade命令之后,但在修改任何 Kubernetes 资源之前。这个钩子将用于在升级执行之前对 Redis 数据库进行数据快照,确保在升级过程中出现错误时能够备份数据库。 -
第二个钩子将在 pre-rollback 生命周期阶段触发。这个阶段发生在执行
helm-rollback命令之后,但在任何 Kubernetes 资源被回滚之前。此钩子将 Redis 数据库恢复到先前拍摄的快照,并确保 Kubernetes 资源被回滚,以便它们与拍摄快照时的配置保持一致。
到本节结束时,你将对生命周期钩子和一些可以用钩子执行的强大功能更加熟悉。请务必记住,本节中创建的钩子是简单的,仅用于演示目的。建议不要直接在可能使用 Redis 的应用程序中使用这些钩子。
我们首先来创建pre-upgrade钩子。
创建 pre-upgrade 钩子以进行数据快照
在 Redis 中,数据快照保存在名为 dump.rdb 的文件中。我们可以通过创建一个 hook,将 Kubernetes 命名空间中创建一个新的 PVC(持久卷声明)来存储数据库备份内容,从而备份该文件。接着,hook 可以创建一个 Job 资源,将 dump.rdb 文件复制到新创建的 PersistentVolumeClaim 中。
尽管 helm create 命令生成了一些强大的资源模板,能够快速创建初始的 guestbook chart,但它并没有自动生成可以用于此任务的任何 hooks。因此,你可以按照以下步骤从头开始创建 pre-upgrade hook:
- 首先,你应该创建一个新的文件夹,用于存放 hook 模板。虽然这不是一个技术要求,但它有助于你组织 Helm chart 的结构,将 hook 模板与常规的 chart 模板分开。它还允许你按功能对 hook 模板进行分组(备份与恢复)。
在你的 guestbook Helm chart 中创建一个新的文件夹 templates/backup,如图所示:
$ mkdir -p guestbook/templates/backup
- 接下来,你应该创建两个执行备份所需的模板文件。第一个模板是
PersistentVolumeClaim模板,因为它将用于存放备份的dump.rdb文件。第二个模板是一个 job 模板,用于执行复制操作。
创建两个空的模板文件作为占位符,如下所示:
$ touch guestbook/templates/backup/persistentvolumeclaim.yaml
$ touch guestbook/templates/backup/job.yaml
你可以通过参考本书的 GitHub 仓库来再次检查你的工作。文件结构应当与github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/tree/main/chapter7/guestbook/templates/backup中的示例一致。
-
现在,让我们填写
persistentvolumeclaim.yaml模板的内容。由于模板的内容相对较长,我们将从 GitHub 仓库中复制每个模板,并深入探讨它们的创建过程。 -
将以下截图所示文件的内容复制到你的
backup/persistentvolumeclaim.yaml文件中。你可以在 https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter7/guestbook/templates/backup/persistentvolumeclaim.yaml 找到这个文件:

图 7.1 – 备份/persistentvolumeclaim.yaml 模板
-
backup/persistentvolumeclaim.yaml文件的第 1 行和第 17 行是一个if动作。由于这个动作封装了整个文件,它表示只有在redis.master.persistence.enabled值设置为true时,才会包含此资源。该值在 Redis chart 中默认为true,可以通过helm show values命令查看。 -
第 5 行确定了新备份 PVC(
PersistentVolumeClaim)的名称。此名称基于发布名称、Redis 名称和从中备份的修订号。注意sub函数的使用,它有助于计算修订号。该函数用于从修订号中减去1,因为helm upgrade命令在模板渲染之前会增加该值。 -
第 9 行创建了一个注释,用于声明该资源为
pre-upgrade钩子。最后,第 10 行创建了一个helm.sh/hook-weight注释,用于确定该资源与其他升级前钩子相比的创建顺序。权重按升序执行,因此此资源将在其他升级前资源之前创建。
- 现在,
persistentvolumeclaim.yaml文件已经创建,我们必须创建最终的升级前模板——即job.yaml。将以下内容复制到您之前创建的backup/job.yaml文件中。也可以从本书的 GitHub 仓库复制该内容,链接如下:github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter7/guestbook/templates/backup/job.yaml:

图 7.2 – backup/job.yaml 模板
-
再次强调,第 9 行定义了该模板为升级前钩子,第 10 行将钩子权重设置为
1,表示该资源将在persistentvolumeclaim.yaml模板之后创建。 -
第 11 行设置了
helm.sh/hook-delete-policy注释,用于指定何时删除该任务。在这里,我们应用了两种不同的策略。第一种是before-hook-creation,表示如果该任务已存在于命名空间中,则在后续的helm upgrade命令中会删除该任务,以便创建一个新的任务替代它。第二种策略是hook-succeeded,即任务成功完成后将删除该任务。我们本可以添加另一种策略hook-failed,即任务失败时删除该任务。然而,考虑到我们希望保留失败记录以便于故障排除,因此没有实现这一策略。 -
第 19 行至第 22 行包含了用于备份 Redis 数据库的命令。首先,使用
redis-cli保存当前状态。然后,将dump.rdb文件从主节点复制到在backup/persistentvolumeclaim.yaml模板中创建的备份 PVC 中。 -
最后,第 29 行至第 35 行定义了引用主节点和备份 PVC 的卷。
在这一部分中,我们创建了两个pre-upgrade钩子——一个用于创建备份 PVC,另一个用于将 Redis 的dump.rdb文件复制到 PVC 中。在下一部分中,我们将创建pre-rollback钩子,用于恢复 Redis 到先前的备份。之后,我们将部署guestbook图表,看看这些钩子的实际效果。
创建 pre-rollback 钩子以恢复数据库
与 pre-upgrade 钩子将 dump.rdb 文件从 Redis 主 PVC 复制到备份 PVC 不同,pre-rollback 钩子可以执行相反的操作——即将数据库恢复到之前的快照。
这个钩子可以通过将 dump.rdb 文件从备份 PVC 复制到主 Redis 实例来实现。然后,必须执行 Redis 的滚动更新操作,以创建新的 Redis 副本 pod。当副本重新连接到主节点时,它们将加载备份的 dump.rdb 文件,供 Guestbook 前端读取。
按照以下步骤创建 pre-rollback 钩子:
-
创建
templates/restore文件夹,用于存放pre-rollback钩子:$ mkdir guestbook/templates/restore -
接下来,构建这个钩子所需的模板。我们需要创建一个
serviceaccount.yaml模板和一个rolebinding.yaml模板,然后再创建一个job.yaml模板来执行恢复任务:$ touch guestbook/templates/restore/serviceaccount.yaml $ touch guestbook/templates/restore/rolebinding.yaml $ touch guestbook/templates/restore/job.yaml
你可以通过参考本书的 GitHub 仓库来检查是否已正确创建结构,链接为:github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/tree/main/chapter7/guestbook/templates/restore。
- 现在,让我们创建第一个
pre-rollback钩子,serviceaccount.yaml。将以下屏幕截图中的内容复制到restore/serviceaccount.yaml文件中。该代码也可以在本书的 GitHub 仓库中找到,链接为:github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter7/guestbook/templates/restore/serviceaccount.yaml:

图 7.3 – restore/serviceaccount.yaml 模板
第 8 行将此模板定义为 pre-rollback 钩子。由于钩子的权重为 0(第 10 行),因此它将在其他 pre-rollback 模板之前创建。
- 上一个模板创建了一个 ServiceAccount,我们将在后续的 job 中使用它,但现在,我们需要授予 ServiceAccount 权限,以便它与 Kubernetes API 通信时能够部署新的 Redis 副本 pod。为了简单起见,在本例中,我们将为 ServiceAccount 在
chapter7命名空间中授予edit权限。
将以下屏幕截图中的内容复制到 restore/rolebinding.yaml 模板中。该代码也可以在本书的 GitHub 仓库中找到,链接为:github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter7/guestbook/templates/restore/rolebinding.yaml:

图 7.4 – 还原/rolebinding.yaml 模板
第 11 行到第 14 行引用了我们希望授予的 edit ClusterRole,而第 15 行到第 19 行则针对我们将发布到的命名空间中的 ServiceAccount(即 chapter7 命名空间)。
- 最后,我们需要向
job.yaml文件添加内容。将以下内容复制到你的restore/job.yaml模板中。该内容也可以在github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter7/guestbook/templates/restore/job.yaml中找到:

图 7.5 – 还原/job.yaml 钩子
这个 job.yaml 模板是核心回滚逻辑所在的地方。第 18 行到第 29 行定义了一个 initContainer,它将备份的 dump.rdb 文件复制到 Redis 主节点,并执行重载操作,恢复主节点的状态,正如备份的 dump.rdb 文件所表示的那样。initContainer 是在任何列在 containers 部分下的容器运行之前完成执行的容器。我们首先创建这个容器,以确保主节点在我们继续下一步之前已经被恢复。
第 30 行到第 37 行表示回滚的下一步。在这里,我们重启 Redis 副本的 StatefulSet。当副本重新连接到主节点时,它们将提供由备份 dump.rdb 文件表示的数据。
创建了 pre-upgrade 和 pre-rollback 钩子后,让我们在 minikube 环境中查看它们的实际运行情况。
执行生命周期钩子
要运行你创建的生命周期钩子,必须通过运行 helm install 命令来安装你的 chart:
$ helm install guestbook chapter7/guestbook -n chapter7 --dependency-update
当每个 pod 报告 1/1 Ready 状态时,通过运行以下 port-forward 命令来访问你的访客簿应用:
$ kubectl port-forward svc/guestbook 8080:80 –n chapter7
接下来,访问 http://localhost:8080 上的访客簿前端,写一条消息,并点击 提交。以下截图显示了示例消息:

图 7.6 – 在访客簿前端输入消息
一旦输入了消息,运行 helm upgrade 命令以触发 pre-upgrade 钩子。helm upgrade 命令会短暂挂起,直到备份完成:
$ helm upgrade guestbook guestbook –n chapter7
当命令返回时,你应该会看到一个新创建的 PVC,其中包含备份。这个 PVC 名为 guestbook-redis-backup-1:
$ kubectl get pvc -n chapter7
NAME STATUS
redis-data-redis-master-0 Bound
redis-redis-backup-1 Bound
备份完成后,我们继续添加另一条消息。我们计划在输入此消息后进行回滚。以下截图显示了添加了两条消息后的 guestbook 应用示例:

图 7.7 – 在访客簿前端输入第二条消息
现在,让我们运行 helm rollback 命令,回滚到第一个修订版本。此命令会暂时挂起,直到 helm rollback 命令返回:
$ helm rollback guestbook 1 –n chapter7
当此命令完成时,Redis 副本应该会滚动部署。可以通过以下命令观察此滚动部署:
$ kubectl get pods –n chapter7 –w
一旦新副本部署完成,刷新浏览器中的 Guestbook 前端。你将看到在升级后你添加的消息消失,如下图所示:

图 7.8 – 回滚前阶段的 Guestbook 前端
希望这个示例能让你更好地理解 Helm 钩子。我们再次强调,这只是一个简单的示例,并不是一个生产级的解决方案。
请注意,虽然本章专注于开发和运行生命周期钩子,但通过在 helm install、helm upgrade、helm rollback 和 helm delete 命令中添加 --no-hooks 标志,可以跳过钩子。添加此标志将导致 Helm 跳过与执行的生命周期阶段相关的钩子。
让我们通过清理 minikube 环境来结束本章内容。
清理
首先,删除 chapter7 命名空间,以删除 guestbook 发布和相关的 PVC:
$ kubectl delete ns chapter7
接下来,停止 minikube 环境:
$ minikube stop
到此为止,一切都已经清理完毕。
总结
生命周期钩子通过允许图表开发人员在不同生命周期阶段安装资源,开启了额外的功能。钩子通常包括执行钩子内操作的作业资源,但它们也经常包含其他资源,如 ServiceAccounts、包括 RoleBindings 的策略以及 PersistentVolumeClaims。在本章结尾,我们为 Guestbook 图表添加了生命周期钩子,并演示了 Redis 数据库的备份与恢复。
在下一章中,我们将讨论如何将 Helm 图表发布到图表仓库。
深入阅读
要了解更多关于生命周期钩子的内容,请访问 Helm 文档:helm.sh/docs/topics/charts_hooks/。
问题
请回答以下问题,测试你对本章内容的理解:
-
生命周期钩子有哪九种不同类型?
-
用于定义钩子的注解是什么?
-
用于定义钩子创建顺序的注解是什么?
-
图表开发人员可以添加什么内容来确保钩子在成功后总是被删除?
-
Helm 用户如何跳过生命周期钩子?
-
最常用来执行生命周期钩子的 Kubernetes 资源是什么?
第八章:发布到 Helm 图表存储库
Helm如果没有Helm chart repository的概念,不能被视为Kubernetes的包管理工具。存储库用于将 Helm 图表发布到社区。在本章中,我们将了解创建 Helm 图表存储库的不同方法。随后,我们将通过将我们的 Guestbook Helm 图表发布到基于 HTTP 的存储库和 OCI 注册表,进行不同存储库实现的实践操作。
本章将涉及以下主题:
-
理解 Helm 图表存储库
-
发布到 HTTP 存储库
-
发布到 OCI 注册表
技术要求
对于本章内容,您需要一个 GitHub 账户。如果您已有 GitHub 账户,可以在github.com/login登录。否则,您可以在github.com/join创建一个新账户。
您还应该将 Packt Git 存储库克隆到本地:
$ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git
首先,让我们了解 Helm 图表存储库的基本概念。
理解 Helm 图表存储库
Helm 图表存储库用于发布 Helm 图表,并使其对广泛的 Helm 用户社区可用。这在概念上类似于 Linux 包管理中使用的存储库,如 RPM 或 Debian 存储库,包通过dnf或apt-get等工具进行安装。公共 Helm 图表存储库可以在 Artifact Hub 上找到(artifacthub.io)。
Helm 图表存储库是通过两种高层次实现之一创建的:
-
一个 HTTP 服务器
-
一个 OCI 注册表
使用 HTTP 服务器是发布图表的最常见实现方式,因为它是支持时间最长的。要使用 HTTP 服务器创建 Helm 图表存储库,可以使用如Apache httpd、NGINX、Amazon S3和GitHub Pages等工具。ChartMuseum(github.com/helm/chartmuseum)是另一个受欢迎的选择,因为它提供了一个 API 来执行更高级的操作。在发布到 HTTP 存储库部分,我们将使用 GitHub Pages 来创建我们的存储库。
使用 HTTP 服务器创建的存储库必须包含以下组件:
-
打包为
.tgz归档的 Helm 图表 -
一个
index.yaml文件,包含有关存储库中图表的元数据。
基本的图表存储库要求维护者使用helm repo index命令生成index.yaml文件,稍后我们将探讨这个命令,但更高级的解决方案,如 ChartMuseum,可以在新图表上传到存储库时自动生成index.yaml文件。
除了 HTTP 之外,Helm chart 维护者可以将 chart 发布到的另一个类型的仓库是 开放容器倡议(OCI)注册表。OCI 是一个开放治理结构,用于创建容器运行时和格式的开放标准。Artifacts 是一个 OCI 项目,允许你在容器注册表中存储和提供额外的内容,如 Helm charts,除了容器镜像之外。由于镜像及其注册表已成为 Kubernetes 和 Helm 中的基本构件,因此能够利用同一个注册表存储容器镜像和 Helm charts,减少了 Helm 维护者发布 charts 所需的工作量。我们将在 发布到 OCI 注册表 部分更详细地探讨如何使用 OCI 注册表发布 Helm charts。
在接下来的部分,我们将把我们的 Guestbook Helm chart 发布到 GitHub Pages。在这里,你将了解如何创建和交互操作一个基础的 Helm chart 仓库。
发布到 HTTP 仓库
GitHub Pages 是 GitHub 中的一个功能,允许你从一个仓库提供静态内容。在这一部分,我们将创建一个启用了 Pages 的新 GitHub 仓库,以发布我们的 Guestbook Helm chart。
要跟随本节的示例,你必须拥有一个 GitHub 账户。创建 GitHub 账户或登录现有账户的步骤已在 技术要求 部分提供。
一旦你登录到 GitHub,继续到下一个部分来创建你的仓库。
创建一个 GitHub Pages 仓库
按照以下步骤创建一个 GitHub Pages 仓库:
-
访问
github.com/new,进入 创建新仓库 页面。 -
为你的 chart 仓库提供一个名称。我们建议使用
Chart-Repository-Example。 -
选中 初始化这个仓库并添加 README 复选框。这是必需的,因为如果仓库中没有任何内容,GitHub 不允许你创建静态网站。你可以将其余设置保留为默认值。请注意,为了使用 GitHub Pages,除非你拥有付费的 GitHub Pro 账户,否则必须将隐私设置保持为 公开。
你的 创建新仓库 页面现在应该如下所示:
![图 8.1 – 创建一个新的 GitHub 仓库]
图 8.1 – 创建一个新的 GitHub 仓库
- 点击 创建仓库 按钮以创建你的仓库。
虽然你的仓库已经创建,但在启用 GitHub Pages 之前,它不能用于提供 Helm charts。
- 点击仓库中的 设置 选项卡以访问仓库设置。从那里,选择左侧栏中的 Pages 选项卡。然后,在 源 下拉列表中,选择 main 选项。最后,点击 保存 按钮。这将指示 GitHub 创建一个静态网站,提供你的主分支内容。
此时,你的界面应该类似于以下内容:

图 8.2 – 查找 GitHub Pages 设置
现在你已经配置了 GitHub 仓库,应该将其克隆到本地机器,以便稍后将 Guestbook Helm 图表添加到其中。按照以下步骤克隆仓库:
-
通过选择页面顶部的 Code 标签,导航到仓库的根目录。
-
选择绿色的Clone or download按钮。点击后会显示你的 GitHub 仓库的 URI(请注意,这与静态网站的 URL 不同)。你可以参考以下截图来找到你的仓库 URI:

图 8.3 – 查找 git URI
-
一旦你获得了仓库的 URI,克隆该仓库到你的本地机器。你可能希望确保首先进入你的主目录,以避免不小心克隆到一个已有的 git 仓库:
$ cd ~ $ git clone <repository URI>
现在你已经克隆了仓库,你可以发布你的 Guestbook 图表。
发布 Guestbook 图表
将 Helm 图表发布到 HTTP 仓库包括三个步骤:
-
将 Helm 图表打包成
.tgz压缩包 -
生成
index.yaml文件 -
将
.tgz压缩包和index.yaml文件上传到服务器
Helm 提供了几个不同的命令来简化发布过程。我们将在本节中讲解这些命令。
首先,你可以使用 helm package 命令将图表打包成 .tgz 压缩包。我们将使用位于 Packt Git 仓库 chapter8/guestbook 的 Guestbook 图表作为此示例的一部分:
$ helm dependency update chapter8/guestbook
$ helm package guestbook chapter8/guestbook
如果这些命令成功执行,将会创建一个名为 guestbook-0.1.0.tgz 的文件。
请注意,我们在运行 helm package 之前执行了 helm dependency update 命令。这是因为 Guestbook 图表必须先下载依赖项才能包含在归档文件中。为了简化这一过程,我们可以通过为 helm package 命令提供额外的标志,将前两个命令合并为一个步骤:
$ helm package chapter8/guestbook --dependency-update
这将确保依赖项被包含在最终的包中。
一旦你的图表被打包,生成的 .tgz 文件应当被复制到本地的 GitHub Pages 仓库克隆中。可以通过使用 cp 命令来完成此操作:
$ cp guestbook-0.1.0.tgz <GitHub Pages repository clone>
当复制此文件时,你可以使用 helm repo index 命令生成 index.yaml 文件。该命令需要你的图表仓库克隆的位置作为参数。运行以下命令生成 index.yaml 文件:
$ helm repo index <GitHub Pages repository clone>
命令会静默成功执行,但你会在 GitHub Pages 克隆目录中看到新的 index.yaml 文件。该文件的内容提供了 Guestbook 图表的元数据,内容如下:
apiVersion: v1
entries:
guestbook:
- apiVersion: v2
appVersion: v5
created: "2022-02-20T04:13:36.052015-05:00"
dependencies:
- condition: redis.enabled
name: redis
repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
version: 15.5.x
description: An application used for keeping a running record of guests
digest: 983dee22d05be37fb73cf6a06fa5a2b2c320c1678ad6a8 df3d198a403f467343
name: guestbook
type: application
urls:
- guestbook-0.1.0.tgz
version: 0.1.0
generated: "2022-02-20T04:13:36.045492-05:00"
如果该仓库中添加了其他图表,它们的元数据也将列在此文件中。
你的 Helm chart 仓库现在应包含 .tgz 压缩包和 index.yaml 文件,内容类似于以下命令的输出:
$ ls <GitHub Pages repository clone>
README.md guestbook-0.1.0.tgz index.yaml
为了完成发布过程,你应该使用以下命令将这些文件提交并推送到 GitHub:
$ cd <GitHub Pages repository clone>
$ git add --all
$ git commit –m "publishing the guestbook helm chart"
$ git push origin main
一旦你将 chart 推送到远程仓库,你的 Guestbook Helm chart 将通过 GitHub Pages 静态站点提供服务。我们可以通过添加我们的仓库并执行搜索来验证它是否正常工作。
首先,找到你的 GitHub Pages 站点的 URL。该 URL 在 https://<github username>.github.io/Chart-Repository-Example/ 中显示。一旦确定了 URL,就可以使用它来添加 chart 仓库:
$ helm repo add example <GitHub Pages Site URL>
该命令将允许 Helm 与你的仓库进行交互。你可以通过在本地配置的仓库中搜索 Guestbook chart 来验证 chart 是否已成功发布。这可以通过运行以下命令完成:
$ helm search repo guestbook
你应该能在搜索结果中找到返回的示例 Guestbook chart。
恭喜!你已经将 Guestbook chart 发布到 Helm chart 仓库。请注意,尽管我们在本章中将 chart 发布到了一个未经身份验证的仓库,但我们将在第十二章中详细探讨身份验证和安全性,Helm 安全性考虑。
在下一部分,我们将探讨 OCI 注册表的支持,并将我们的 Guestbook chart 发布到一个容器注册表。
将 chart 发布到 OCI 注册表
将 Helm chart 发布到 OCI 注册表的流程与处理标准容器镜像时的流程类似。像 docker login、docker pull 和 docker push 这样的命令,在 Helm 中有类似的命令。下表中列出了这些命令及其描述:
| 命令 | 描述 |
|---|---|
helm registry login |
登录到一个注册表 |
helm registry logout |
从注册表登出 |
helm push |
将打包好的 chart 推送到一个注册表 |
helm pull |
从注册表拉取一个 chart |
表 8.1 – OCI 管理的 Helm 命令
请注意,OCI 基础的 charts 从 3.8.0 版本开始获得完全支持。在此版本之前,它作为实验性功能提供,并需要设置环境变量来启用该功能。如果你使用的是 3.8.0 之前的版本,则必须设置 HELM_EXPERIMENTAL_OCI=1 环境变量,如下所示:
$ export HELM_EXPERIMENTAL_OCI=1
helm pull 命令只是一个例子,展示了基于 OCI 的 charts 可以与来自不同位置(例如 HTTP 仓库或本地文件系统)的 charts 互换使用。可以以这种方式使用的其他 Helm 命令包括:
-
helm show -
helm template -
helm install -
helm upgrade
基于 OCI 的图表可以通过在图表位置中指定 OCI 协议(oci://)来与其他来源区分。例如,源自注册表localhost:5000/helm-charts/mychart的图表,在 Helm 中被引用为oci://localhost:5000/helm-charts/mychart。
还需要注意的是,虽然 OCI 制品可以与容器镜像一起存储在同一注册表中,但并非每个注册表都完全支持 OCI 制品规范,因此无法存储基于 OCI 的 Helm 图表。请事先查阅注册表分发的文档。
为了演示如何与基于 OCI 的 Helm 图表进行交互,我们可以使用 Guestbook 图表并将其存储在 OCI 注册表中。首先,我们必须确定应该存储图表的注册表。由于我们不仅使用 GitHub 来存储图表的原始源代码,而且 GitHub 还充当我们的 Helm 仓库,因此作为 GitHub 包服务的一部分提供的容器注册表可以作为 Helm 图表的 OCI 注册表。容器注册表完全支持 OCI 制品,这意味着我们不必再为此担忧。
要将内容发布到容器注册表,必须创建个人账户令牌(PAT)。请按照以下步骤创建一个具有推送和拉取镜像所需权限的 PAT:
-
登录 GitHub 后,在页面右上角选择您的个人资料图片,然后从下拉菜单中点击设置。
-
点击开发者设置并选择个人账户令牌。
-
点击生成新令牌按钮以启动令牌创建过程。
-
为令牌输入一个唯一名称,例如
Learn Helm。 -
选择令牌的过期日期。
-
选择将授予令牌的作用域(权限)。以下作用域是管理容器注册表内内容所需的:
-
read:packages -
write:packages -
delete:packages
-
-
点击生成令牌按钮以创建令牌。
请确保复制生成的令牌,因为一旦离开此页面,就无法再次获取该令牌。
在与容器注册表交互之前,重要的是要了解注册表中内容的组织方式。虽然这些细节特定于 GitHub 服务,但这些概念可以应用于任何容器注册表。内容按ghcr.io/<OWNER>/<ARTIFACT>格式存储。OWNER在此表示用户账户或 GitHub 组织的名称。
这些细节如此重要的主要原因是 Helm 对基于 OCI 的图表施加了严格的命名约定。与将其他制品发布到容器注册表不同,后者可以指定仓库名称和标签,基于 OCI 的图表的仓库名称和标签是根据图表名称和语义版本(在Chart.yaml文件中定义)自动确定的。例如,一个名为mychart、版本为 0.1.0 的图表,将存储在 GitHub 容器注册表中,属于用户名为jdoe的用户,地址为ghcr.io/jdoe/mychart:0.1.0。
现在我们了解了图表在 OCI 注册表中的组织方式,接下来让我们将之前创建的图表推送到 GitHub 注册表。第一步是使用之前创建的 PAT 登录注册表,通过 helm registry login 命令实现。
helm registry login 命令的格式如下:
$ helm registry login <registry>
要登录 GitHub 注册表,请执行以下命令:
$ helm registry login ghcr.io
在提示时输入你的 GitHub 用户名和 PAT 作为密码。可以使用–-username,并结合--password或--password-stdin标志来进行非交互式认证。
现在我们已经登录到远程注册表,可以使用helm push命令将之前创建的 Helm 图表推送到远程注册表。helm push要求提供已经打包的图表的位置,以及目标注册表,如下所示:
$ helm push <location_of_tgz_helm_package> <registry/reference>
也支持签名图表,只要源文件(.prov)位于与打包图表相同的目录中。无需为 helm push 命令添加额外的配置或标志。源文件和图表签名将在 第十二章 中详细讨论,Helm 安全性考虑。
将打包好的 Guestbook 图表推送到 GitHub 的容器注册表:
$ helm push guestbook-0.1.0.tgz oci://ghcr.io/<OWNER>
Pushed: ghcr.io/<OWNER>/guestbook:<version>
Digest: sha256:<SHA>
一旦图表被推送,它可以通过用户个人资料中的Packages选项卡在 GitHub 内查看。在 GitHub 的任何页面上,选择页面右上角的个人资料图片,然后选择Your Profile。Packages选项卡位于页面顶部。
点击Packages选项卡后,之前推送的图表应该能够看到,如下图所示:

图 8.4 – GitHub 中的 Packages 选项卡
默认情况下,新创建的包是私有的,其他人无法访问。可以通过选择该包并在Details页面中选择右侧的Package Settings来更改此配置。在Danger Zone部分选择Change Visibility,然后选择Public。
或者,如果你希望限制访问 Helm 图表,可以指定个人用户或团队,而不是更改可见性。
拉取 OCI Guestbook 图表
从 OCI 注册表拉取 Helm 图表和发布图表一样简单,可以使用 helm pull 命令来执行。helm pull 命令中的选项与在图表仓库或文件系统中找到的图表一样。多种可用选项中,--version 标志允许用户指定要接收的图表。否则,将选择符合 SemVer 约定的最新版本。
从 GitHub 容器注册表中拉取之前发布的图表,如下所示:
$ helm pull oci://ghcr.io/<OWNER>/guestbook --version 0.1.0
将从注册表中检索到图表的最新版本,并将打包的图表(.tgz)存储在本地。
总结
在经过一番辛苦的努力开发 Helm chart 后,最终将你的 Helm chart 发布到一个仓库供全世界查看的感觉是无与伦比的!在这一章中,我们了解了 HTTP 和 OCI Helm chart 仓库。HTTP 仓库允许你将 chart 发布到简单的 Web 服务器,而 OCI 注册表允许你将 Helm chart 与容器镜像一起发布。作为练习,我们将 Guestbook Helm chart 发布到了 GitHub Pages(一个 HTTP 服务器)和 GitHub 的容器注册表(一个 OCI 注册表)。
在下一章中,我们将学习有关 Helm chart 测试的工具和策略。
进一步阅读
要了解更多关于 Helm chart 仓库的内容,请访问 Helm 文档中的 Chart Repository Guide 部分:helm.sh/docs/topics/chart_repository/#helm。
要了解更多关于 OCI 支持的内容,请访问文档中的 Registries 部分:helm.sh/docs/topics/registries/。
问题
回答以下问题以测试你对本章的知识掌握情况:
-
创建 HTTP 仓库的三种不同工具是什么?
-
你可以运行什么命令以确保依赖项始终包含在
.tgz压缩包中? -
发布到 HTTP 服务器时需要哪些文件?
-
将 Helm chart 发布到 HTTP 仓库与发布到 OCI 注册表的过程有何不同?
-
用于发布到 OCI 注册表的 Helm 命令是什么?
-
用于从 OCI 注册表下载 Helm chart 的 Helm 命令是什么?
第九章:测试 Helm Charts
测试是工程师在软件开发过程中必须执行的常见任务。测试用于验证产品的功能,并防止产品在演进过程中出现回归问题。经过充分测试的软件更易于维护,并且使开发人员能够自信地向最终用户提供新版本。
应该适当地测试 Helm chart,以确保它提供的功能达到预期的质量标准。在本章中,我们将讨论如何将测试应用于 Helm charts,以验证预期的能力。
在本章中,我们将讨论以下主题:
-
设置您的环境
-
验证 Helm 模板
-
在实时集群中进行测试
-
使用 Chart Testing 工具改进 chart 测试
-
清理
技术要求
本章中,您将需要以下内容:
-
minikube -
kubectl -
helm -
git -
yamllint -
yamale -
ct(chart-testing)
此外,您应该在本地克隆 Packt 的 GitHub 仓库:$ git clone github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git。
在本章中,我们将广泛使用minikube环境。在下一节中,您将设置该环境。
设置您的环境
执行以下步骤来设置您的minikube环境:
-
通过运行
minikube start命令来启动minikube:$ minikube start -
然后,创建一个名为
chapter9的新命名空间:$ kubectl create namespace chapter9
在准备好minikube环境后,让我们首先讨论如何测试 Helm charts。我们将从概述您可以使用的方法来验证 Helm 模板开始。
验证 Helm 模板
Helm 的主要用途之一是创建Kubernetes资源。因此,在开发和测试 Helm chart 时,您应该确保资源正确生成。我们将在下一节中讨论实现这一目标的多种方法。
使用 helm template 在本地验证模板生成
验证 chart 模板的第一种方法是使用helm template命令。我们在第六章中首次介绍了此命令,理解 Helm 模板。在本章中,我们将描述如何使用helm template命令在本地渲染 chart 模板。
helm template命令的语法如下:
$ helm template [NAME] [CHART] [flags]
您可以通过演示以下命令来查看其实际效果,该命令适用于 Packt 仓库中chapter9/guestbook目录下的 Helm chart:
$ helm template my-guestbook guestbook
此命令的结果将显示如果将它们应用于集群,将创建的每个 Kubernetes 资源,如下所示:

图 9.1 – helm 模板输出
上述输出显示了helm template输出的开始部分。如您所见,显示了一个完全渲染的ServiceAccount,以及另一个将在发布时创建的ServiceAccount的开头部分。渲染这些资源使您能够了解如果在 Kubernetes 集群中安装发布时,资源将如何被创建。
在图表开发过程中,您可能需要定期使用helm template命令验证 Kubernetes 资源是否正确生成。
以下是您可能希望在整个过程中验证的一些常见图表开发方面:
-
参数化字段已成功替换为默认值或覆盖值
-
控制结构如
if、range和with成功地根据提供的值生成 YAML。 -
资源包含适当的空格和缩进。
-
函数和管道被正确使用,以正确格式化和操作 YAML。
-
输入验证机制,如
required和fail函数或values.schema.json文件,能够根据用户输入正确验证值。 -
依赖项已正确声明,并且它们的资源定义出现在
helm template输出中。
在下一部分中,我们将讨论如何在使用helm template渲染资源时启用服务器端验证。
为图表渲染添加服务器端验证
虽然helm template命令在图表开发过程中非常重要,应该经常使用来验证图表渲染效果,但它也有一个关键的限制。helm template命令的主要目的是提供客户端渲染,这意味着它不会与 Kubernetes API 服务器通信以进行资源验证。如果您希望在生成资源后确保它们有效,可以使用--validate标志指示helm template与 Kubernetes API 服务器进行通信:
$ helm template my-release <chart_name> --validate
使用--validate标志时,任何生成的模板如果没有生成有效的 Kubernetes 资源,都会输出错误信息。例如,假设使用了一个错误的apiVersion的部署模板。看起来在本地是有效的,但应用--validate标志后会被认为是无效的。以下是 Kubernetes 在遇到无效内容时,通过--validate标志触发的示例错误消息:
Error: unable to build kubernetes objects from release manifest: unable to recognize "": no matches for kind "Deployment" in version "v1"
虽然helm template确实提供了通过--validate标志进行服务器端验证的功能,但这并不是判断图表是否生成有效 Kubernetes 资源的唯一方法。作为替代方法,您可以在install、upgrade、rollback和uninstall命令中使用--dry-run标志。以下是使用此标志与install命令的示例:
$ helm install my-chart <chart_name> --dry-run
--dry-run 标志主要供最终用户在执行安装之前进行检查,以确保提供了正确的值,并且安装会产生预期的结果。这是一个很好的最后一道防线,可以用来验证在执行相关命令之前不会抛出错误。
虽然验证模板是否按预期生成是必要的,但执行代码检查(linting)以确保 Helm 图表和生成的资源遵循最佳格式化实践也同样重要。有几种方法可以实现这一目标。我们来看看。
对 Helm 图表和模板进行 linting
对 Helm 图表进行 linting 包括两个高层步骤:
-
确保 Helm 图表有效
-
确保 Helm 图表遵循一致的格式化实践
为了确保 Helm 图表有效,我们可以使用helm lint命令,其语法如下:
$ helm lint <chart-name> [flags]
helm lint命令用于验证 Chart.yaml 文件,确保 Helm 图表不包含任何重大问题。请注意,该命令不会验证渲染的资源或执行 YAML 风格的 linting。
你可以对位于 Packt 仓库中的 guestbook 图表运行 helm lint 命令,如下所示:
$ helm lint chapter9/guestbook
==> Linting chapter9/guestbook
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
上述输出显示该图表有效,0 chart(s) failed消息说明了这一点。[INFO] 消息报告指出 Chart.yaml 文件中的图标字段是推荐的,但不是必需的。其他类型的消息包括 [WARNING],表示图表不符合约定,以及 [ERROR],表示图表在安装时会失败。
让我们通过几个例子来演示每种可能的结果。考虑位于chapter9/no-chart-yaml中的图表,它包含以下文件结构:
no-chart-yaml/
templates/
Values.yaml
正如你可能从名称中猜到的,这个图表缺少一个Chart.yaml定义文件。当我们对这个图表运行helm lint时,会出现一个错误:
$ helm lint chapter9/no-chart-yaml
==> Linting chapter9/no-chart-yaml
Error unable to check Chart.yaml file in chart: stat chapter9/no-chart-yaml/Chart.yaml: no such file or directory
Error: 1 chart(s) linted, 1 chart(s) failed
这个错误表明 Helm 找不到Chart.yaml文件,导致图表无效。
如果我们添加一个空的 Chart.yaml 文件,可以看到不同的错误。让我们在 chapter9/empty-chart-yaml 图表上运行 helm lint:
$ helm lint chapter9/empty-chart-yaml
==> Linting chapter9/empty-chart-yaml
[ERROR] Chart.yaml: name is required
[ERROR] Chart.yaml: apiVersion is required. The value must be either "v1" or "v2"
[ERROR] Chart.yaml: version is required
[INFO] Chart.yaml: icon is recommended
[ERROR] templates/: validation: chart.metadata.name is required
[ERROR] : unable to load chart
validation: chart.metadata.name is required
Error: 1 chart(s) linted, 1 chart(s) failed
输出会列出 Chart.yaml 文件中缺少的所有必需字段。
Linter 还会检查其他文件的存在,如 values.yaml 文件和 templates 目录。它还确保 templates 目录下的文件具有有效的 .yaml、.yml、.tpl 或 .txt 文件扩展名。
helm lint命令非常适合检查您的图表是否包含适当的内容,但它并不会对您的图表的 YAML 格式进行全面的语法检查。要执行此类语法检查,您可以使用另一种工具yamllint,它可以在github.com/adrienverge/yamllint找到。该工具可以通过pip3(或 pip)包管理器在多个操作系统上安装,使用以下命令:
$ pip3 install yamllint –user
它也可以通过您系统的包管理器安装,详细信息可以参考yamllint快速入门指南:yamllint.readthedocs.io/en/stable/quickstart.html。
要在您的图表资源上使用yamllint,您必须将其与helm template命令结合使用,将渲染模板的输出作为输入传递给yamllint。让我们对chapter9/guestbook Helm 图表运行yamllint:
$ helm template my-guestbook chapter9/guestbook | yamllint -
这里展示了部分结果:

图 9.2 – yamllint 输出
左侧提供的行号反映了整个helm template的输出,这可能使得确定yamllint输出中的哪一行对应于模板文件中的哪一行变得困难。您可以通过将helm template输出重定向来简化这一点,以确定其行号:
$ cat -n <(helm template my-guestbook chapter9/guestbook)
yamllint工具根据许多不同的规则进行语法检查,包括以下内容:
-
缩进
-
行长度
-
行尾空格
-
空行
-
注释格式
您可以通过在以下文件之一中编写规则来定义自己的规则:
-
当前工作目录中的
.yamllint、.yamllint.yaml或.yamllint.yml -
$XDG_CONFIG_HOME/yamllint/config -
~/.config/yamllint/config
一个示例的.yamllint.yaml文件可以在chapter9/yamllint-override中找到。在这里,我们定义了以下内容:
rules:
indentation:
indent-sequences: whatever
这个示例创建了一个规则,指示yamllint不强制执行任何特定的缩进方法。
深入配置yamllint规则的内容超出了本章的范围,但您可以参考yamllint文档中的规则部分,了解更多信息:yamllint.readthedocs.io/en/stable/rules.html。
在本节中,我们讨论了如何通过使用helm template、helm lint和yamllint命令验证您 Helm 图表的本地渲染。然而,这并不能验证您的图表功能或应用程序是否能正常运行。在下一节中,我们将通过学习如何在实时 Kubernetes 集群中创建测试来解决这个问题。
在实时集群中进行测试
了解如何在实时 Kubernetes 集群中执行测试是开发和维护 Helm 图表的重要组成部分。实时测试有助于确保你的图表按预期功能运行,并能帮助防止随着图表中新增内容而引入的回归问题。
测试可能涉及但不限于以下两种不同的结构:
-
就绪探针和
helm install --wait命令 -
测试钩子和
helm test命令
就绪探针是一种 Kubernetes 中的健康检查类型,在成功时,会将 Pod 标记为Ready,使其能够接收入口流量。一个就绪探针的示例位于chapter9/guestbook/templates/deployment.yaml中:
readinessProbe:
httpGet:
path: /
port: http
当 HTTP GET请求成功访问/路径时,这个就绪探针将把 Pod 标记为Ready。
就绪探针可以与--wait标志一起使用,该标志强制 Helm 仅在探针通过时才返回成功。如果就绪探针超时,Helm 将返回退出代码1,表示安装未成功。默认情况下,超时发生在安装开始后的 5 分钟内。这个超时时间可以通过--timeout标志进行配置。
以下是使用--wait标志调用helm install的示例:
$ helm install my-guestbook chapter9/guestbook --wait
其他也支持--wait标志的命令包括upgrade、rollback和uninstall。但是,当与uninstall一起使用时,Helm 会等待每个资源被删除。
除了就绪探针,Helm 中的测试还可以通过使用测试钩子和helm test命令来执行。测试钩子是安装 Helm 图表后执行自定义测试的 Pod,以确认它们成功执行。它们定义在templates目录下,并包含helm.sh/hook: test注解。当运行helm test命令时,带有测试注解的模板会被创建并执行它们定义的函数。
我们可以在chapter9/guestbook/templates/tests/test-connection.yaml中看到一个示例测试:
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "guestbook.fullname" . }}-test-connection"
labels:
{{- include "guestbook.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "guestbook.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
如我们所见,这个测试尝试向 guestbook 前端发起请求。
让我们在我们的minikube环境中运行这个 Helm 测试。
运行图表测试
要运行图表的测试,必须使用helm install命令将图表安装到 Kubernetes 环境中。因为guestbook图表包含前端和 Redis 实例的就绪探针(由 Redis 依赖提供),我们可以在helm命令中添加--wait标志,以阻止直到所有 Pod 准备就绪。运行以下命令以安装guestbook图表:
$ helm install guestbook chapter9/guestbook -n chapter9 –wait
一旦图表安装完成,你可以使用helm test命令来执行测试生命周期钩子。helm test命令的语法如下:
helm test [RELEASE] [flags]
运行helm test命令对guestbook发布进行测试:
$ helm test guestbook –n chapter9
如果你的测试成功,你将在输出中看到以下结果:
NAME: guestbook
LAST DEPLOYED: Sun Mar 13 17:18:51 2022
NAMESPACE: chapter9
STATUS: deployed
REVISION: 1
TEST SUITE: guestbook-test-connection
Last Started: Sun Mar 13 17:26:00 2022
Last Completed: Sun Mar 13 17:26:03 2022
Phase: Succeeded
在运行测试时,你还可以使用--logs标志显示测试 pod 的日志。让我们再次运行测试,并通过添加--logs标志来检查日志:
$ helm test guestbook --logs –n chapter9
<skipped>
POD LOGS: guestbook-test-connection
Connecting to guestbook:80 (10.98.198.86:80)
saving to 'index.html'
index.html 100% |********************************| 920 0:00:00 ETA
'index.html' saved
从我们测试 pod 的日志中可以看出,我们的应用程序已成功启动并运行!作为最后一步,你可以使用helm uninstall删除你的发布版本:
$ helm uninstall guestbook –n chapter9
在本节中,我们运行了一个测试钩子,作为对 chart 安装的冒烟测试。在下一部分,我们将讨论如何通过利用一个名为ct的工具来改进测试过程。
使用 Chart Testing 工具改进 chart 测试
上一部分描述的测试方法足够确定 Helm chart 是否可以成功安装。然而,标准的 Helm 测试过程固有的一些关键限制仍然需要讨论。
第一个需要考虑的限制是测试 chart 值中可能出现的不同组合的难度。因为helm test命令不提供修改发布版本的值的功能,超出安装或升级时设置的值,因此在对不同值运行helm test时,必须遵循以下工作流程:
-
使用初始值集安装你的 chart。
-
对你的发布版本运行
helm test。 -
删除你的发布版本。
-
使用不同的值集安装 chart。
-
重复第 2 步到第 4 步,直到测试了大量的值组合。
每一个手动步骤都存在出错的风险。
除了测试不同的值组合外,你还应该确保在修改 chart 时不会发生回归。防止回归的最佳方法是将helm upgrade的使用纳入你的测试工作流程:
-
安装之前的 chart 版本。
-
将你的发布版本升级到更新的 chart 版本。
-
删除发布版本。
-
安装更新的 chart 版本。
应该针对每一组值重复此工作流程,以确保没有回归或无意的破坏性变化。
这些过程听起来可能很繁琐,但想象一下在维护 Helm chart 单一仓库时,chart 开发者所面临的额外压力,因为多个 chart 需要同时进行测试和维护。当多个不同的工件或模块包含在同一个仓库中时,仓库就被认为是单一仓库。单一仓库设计是 chart 开发者或组织开发和维护其 chart 的最常见方式。
一个 Helm chart 单一仓库可能具有以下文件结构:
helm-charts/
guestbook/
Chart.yaml
templates/
README.md
values.yaml
redis/ # Contains the same file structure as 'guestbook'
wordpress/ # Contains the same file structure as 'guestbook'
在一个维护良好的单一仓库中的 Helm chart 应遵循适当的MAJOR.MINOR.PATCH版本格式。使用以下列表作为如何增加 SemVer 版本的指南:
-
如果你对 chart 做了破坏性修改,应该增加
MAJOR版本。破坏性修改指的是与先前的 chart 版本不兼容的修改。 -
如果你正在添加功能但不进行破坏性更改,则应增加
MINOR版本。如果你所做的更改与以前的 chart 版本向后兼容,则应增加此版本。 -
如果你正在修复 bug 或解决安全漏洞,而这些更改不会导致破坏性更改,则应增加
PATCH版本。如果该更改与之前的 chart 版本向后兼容,则应增加此版本。
由于 chart 测试和版本管理的责任,Helm chart 的维护者可能会发现,确保 charts 正确测试并按时增加版本越来越困难,特别是当维护一个包含多个 Helm charts 的 monorepo 时。这个挑战促使 Helm 社区创建了一个名为 ct 的工具,以便为 Helm charts 的测试和维护提供结构化和自动化的支持。接下来我们将讨论这个工具。
介绍 Chart Testing 项目
Chart Testing 项目可以在 github.com/helm/chart-testing 找到,旨在用于 Git monorepo 中的 charts,执行自动化的 lint 检查、验证和测试。这个自动化测试是通过 Git 来检测 charts 相对于指定分支的变化来实现的。已更改的 charts 应该进行测试,而未更改的 charts 不需要测试。
该项目的 命令行界面 (CLI),ct,提供了四个主要命令:
-
lint:对已修改的 charts 进行 lint 检查和验证 -
install:在运行的 Kubernetes 集群中安装 chart,并对已修改的 charts 运行测试钩子 -
lint-and-install:结合了lint和install命令 -
list-changed:列出已修改的 charts
lint-and-install 命令是 ct 的主要功能。它执行 lint 检查,安装你 Kubernetes 集群中的 charts,并运行任何存在的测试钩子。它还会检查你是否在 Chart.yaml 文件中增加了已修改 chart 的 version 字段。此验证帮助维护者强制执行 Helm charts 的适当版本控制。
ct 工具还允许你对多个不同的 values 文件进行 Helm charts 测试。在执行 lint、install 和 lint-and-install 命令时,ct 会遍历每个测试 values 文件,并根据提供的不同 values 组合进行 lint 检查和测试。ct 使用的测试 values 文件位于 ci/ 文件夹下,并以 values.yaml 格式结尾。以下是包含 ci 文件夹的 Helm chart 结构示例:
guestbook/
Chart.yaml
ci/
nodeport-service-values.yaml
ingress-values.yaml
templates/
values.yaml
ci/ 下的每个 values 文件应根据其测试功能命名。例如,nodeport-service-values.yaml 可能用于确保 ingress-values.yaml 会测试 Ingress。
你最常使用的 ct 命令可能是lint-and-install命令。当这个命令运行时,会执行一系列步骤:
-
检测在 Git 单一仓库中已修改的图表。确保图表的版本已递增。
-
对于每个已修改的图表,检查图表及
ci/文件夹下的每个values文件。 -
对于每个已修改的图表,在 Kubernetes 集群中安装该图表并等待就绪探针通过。一旦探针通过,运行测试钩子(如果存在)。对于
ci/文件夹中的每个values文件,重复第 3 步。 -
卸载 Helm 发布。
如你所见,这个命令执行了多种步骤,以确保你的图表已正确检查和测试。然而,默认情况下,lint-and-install命令不会检查向后兼容性。通过添加--upgrade标志,可以启用此功能。
当提供--upgrade标志时,ct 会检查图表版本的MAJOR版本号是否已递增。如果不期望出现重大更改,ct 会先部署先前的图表版本,然后升级到新版本。这有助于确保没有回归问题。接着,ct 会直接使用标准发布安装新版本。我们建议在使用lint-and-install命令时添加--upgrade标志。
让我们继续在本地安装 ct 及其依赖项。然后,我们将看一个如何使用 ct 的示例。
安装 Chart Testing 工具
要使用 ct,你必须在本地机器上安装以下工具:
-
helm -
git(版本 2.17.0 或更高) -
yamllint -
yamale -
kubectl
安装helm和kubectl的说明已在第二章《准备 Kubernetes 和 Helm 环境》中提供,而yamllint在本章前面已经安装。现在,我们将安装yamale,这是一款用于验证 YAML 模式的工具。ct 使用它来验证Chart.yaml文件。
yamale可以通过pip3包管理器安装,如下所示:
$ pip3 install yamale –user
你也可以通过从github.com/23andMe/Yamale/archive/master.zip下载档案手动安装 Yamale。下载后,解压档案并运行setup.py脚本:
$ python3 setup.py install
一旦你安装了必要的工具,应该从项目的 GitHub 发布页面下载 ct,地址是github.com/helm/chart-testing/releases。每个发布版本都包含一个Assets部分,其中列出了与每个发布版本相关的档案。
下载与本地机器平台类型对应的档案。v3.5.1 版本是本书使用的版本:

图 9.3 – GitHub 上的 Helm 发布页面
从 GitHub 发布页面下载适当的压缩包后,解压该发布包。一旦解压,你会看到以下内容:
LICENSE
README.md
etc/chart_schema.yaml
etc/lintconf.yaml
ct
LICENSE 和 README.md 文件可以删除,因为它们不需要。
etc/chart_schema.yaml 和 etc/lintconf.yaml 文件可以移动到本地机器上的 $HOME/.ct/ 或 /etc/ct/ 位置。这些文件提供了用于 lint 检查和模式验证的 yamllint 和 yamale 规则。将其移动到建议位置后,它们为任何 ct 调用提供默认规则,无论它们在文件系统中的位置如何。
你还应该将 ct 移动到系统的 PATH 变量中包含的位置。可以通过以下命令移动 ct 以及位于 etc 目录下的文件:
$ mkdir $HOME/.ct
$ mv $HOME/Downloads/etc/* $HOME/.ct/
$ mv $HOME/Downloads/ct /usr/local/bin/
现在所有必需的工具已经安装好,让我们克隆 Packt 仓库——如果你之前没有克隆过的话。我们将与这个仓库交互,演示如何使用 ct:
$ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git
克隆完成后,你会注意到这个仓库包含了几个与 ct 相关的文件:
-
lintconf.yaml:这是与 ct 压缩包中包含的相同文件的副本。添加到仓库后,ct 使用此本地引用,而不是位于$HOME/.ct/的默认文件。 -
chart_schema.yaml:这是与 ct 压缩包中包含的相同文件的副本。添加到仓库后,ct 使用此本地引用,而不是位于$HOME/.ct/的默认文件。 -
ct.yaml:此文件包含 ct 的配置。
以下是包含在 ct.yaml 文件中的几个配置:
chart-dirs:
- helm-charts/charts
chart-repos:
- bitnami=https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
chart-dirs 字段表示相对于 ct.yaml 文件的 helm-charts/charts 目录是 Helm 图表单仓库的根目录。chart-repos 字段提供了 ct 应该添加以下载依赖项的仓库列表。可以向此文件添加多种其他配置,以自定义 ct 的执行。可用选项的完整列表可以在 github.com/helm/chart-testing 上的 Chart Testing 文档中查看。
现在,让我们通过运行 lint-and-install 命令来看看 ct 的实际效果。
运行 lint-and-install 命令
在 helm-charts/charts 文件夹中,这是我们 Helm 图表单仓库的位置,我们有两个图表:
-
在 第二部分 中我们编写的
guestbook图表。 -
helm create用于部署nginx反向代理。
guestbook 和 nginx Helm 图表是将与 ct 一起测试的图表。首先,让我们导航到 Git 仓库的顶层:
$ cd Managing-Kubernetes-Resources-using-Helm
$ ls
LICENSE chapter4 chapter6 chapter8 chart_schema.yaml helm-charts
README.md chapter5 chapter7 chapter9 ct.yaml lintconf.yaml
由于 ct 应该在与 ct.yaml 文件相同的文件夹中运行,我们可以简单地从仓库的顶层运行 ct lint-and-install:
$ ct lint-and-install
运行此命令后,你应该会看到以下消息:
Linting and installing charts...
--------------------------------------------
No chart changes detected.
--------------------------------------------
All charts linted and installed successfully
由于没有修改任何图表,ct 并未对您的图表进行任何测试。我们应该至少修改 helm-charts/charts 目录中的一个图表,以便进行测试。由于正常的开发流程可能涉及功能分支,让我们创建一个新的 Git 分支,在该分支上进行修改。通过运行以下命令创建一个名为 chart-testing-example 的新分支:
$ git checkout –b chart-testing-example
修改可以是任何大小和类型,因此在本例中,我们将仅修改 nginx 图表的 Chart.yaml 文件。修改 helm-charts/charts/nginx/Chart.yaml 文件中的描述字段,使其如下所示:
description: Deploys an NGINX instance to Kubernetes
之前,该值为 A Helm chart for Kubernetes。通过运行 git status 命令验证 nginx 图表是否已被修改:
$ git status
您应该看到类似以下的输出:

图 9.4 – Git 状态,显示了 Chart.yaml 的变化
现在,尝试再次运行 lint-and-install 命令:
$ ct lint-and-install
这次,ct 显示了在 monorepo 中发生变化的图表:
Linting and installing charts...
----------------------------------------------------------------
Charts to be processed:
----------------------------------------------------------------
nginx => (version: "1.0.0", path: "helm-charts/charts/nginx")
----------------------------------------------------------------
然而,过程稍后失败,因为 nginx 图表版本没有被修改:

图 9.5 – 当图表版本未更新时,ct 输出
这可以通过递增 nginx 图表的版本来修复。由于此更改并未引入新特性,我们将递增 PATCH 版本。在 Chart.yaml 文件中将 nginx 图表的版本修改为 1.0.1:
version: 1.0.1
一旦版本更新,重新运行 lint-and-install 命令:
$ ct lint-and-install
现在,图表版本已经递增,lint-and-install 命令将按照完整的测试流程进行。您将看到 nginx 图表已被 lint 检查并部署到自动创建的命名空间(尽管可以通过使用 --namespace 标志来指定特定的命名空间)。一旦部署的 Pod 被报告为已准备好,ct 将自动运行由带有 helm.sh/hook test 注解的资源表示的测试钩子。ct 还将打印每个测试 Pod 的日志以及命名空间事件。
您可能会注意到 nginx 图表被多次部署。这是因为 nginx 图表包含一个 ci/ 文件夹,该文件夹位于 helm-charts/charts/nginx/ci 目录中。该文件夹包含两个不同的值文件,因此 nginx Helm 图表被安装了两次,以测试这两组值。这可以从 lint-and-install 的输出中看到:
Linting chart with values file 'nginx/ci/nodeport-values.yaml'...
Linting chart with values file 'nginx/ci/ingress-values.yaml'...
Installing chart with values file 'nginx/ci/nodeport-values.yaml'...
Installing chart with values file 'nginx/ci/ingress-values.yaml'...
虽然这个过程对测试更新后的图表功能非常有用,但它并未验证升级到新版本是否会成功。要进行此验证,我们需要提供 --upgrade 标志。再次运行 lint-and-install 命令,但这次我们将添加 --upgrade 标志:
$ ct lint-and-install --upgrade
这一次,将对 ci/ 目录下的每个 values 文件进行就地升级。这可以从以下输出中看到:
Testing upgrades of chart 'nginx => (version: "1.0.1", path: "nginx")' relative to previous revision 'nginx => (version: "1.0.0", path: "ct_previous_revision216728160/nginx")'...
记住,只有当版本之间的MAJOR版本相同,才会测试就地升级。如果指定了--upgrade标志并且MAJOR版本发生了变化,你将看到类似以下的消息:
Skipping upgrade test of 'nginx => (version: "2.0.0", path: "helm-charts/charts/nginx")' because: 1 error occurred:
* 2.0.0 does not have same major version as 1.0.0
现在你已经了解了如何稳健地测试 Helm 图表,接下来我们将通过清理minikube环境来结束。
清理
如果你已经完成本章中的示例,你可以从minikube集群中删除chapter9命名空间:
$ kubectl delete ns chapter9
最后,通过运行minikube stop来关闭你的minikube集群。
总结
在本章中,你学习了不同的方法来测试你的 Helm 图表。测试图表的最基本方式是使用helm template命令针对本地图表目录运行,并确定是否生成了相应的资源。你还可以使用helm lint命令来确保图表遵循 Helm 资源的正确格式,并且可以使用yamllint命令来检查图表中使用的 YAML 格式。
除了本地模板化和代码检查外,你还可以使用helm test命令和 ct 工具在 Kubernetes 环境中执行实时测试。除了执行基本的图表测试功能外,ct 还提供了一些功能,使得在单一代码库中维护 Helm 图表变得更加容易。
在下一章节中,你将学习如何在持续交付(CD)和 GitOps 环境中使用 Helm。
进一步阅读
关于helm template和helm lint命令的更多信息,请参考以下资源:
-
helm template:helm.sh/docs/helm/helm_template/ -
helm lint:helm.sh/docs/helm/helm_lint/
以下是 Helm 文档中讨论图表测试和helm test命令的相关页面:
-
helm test命令:helm.sh/docs/helm/helm_test/
最后,查看 Chart Testing GitHub 仓库,获取关于 ct CLI 的更多信息:github.com/helm/chart-testing
问题
回答以下问题以测试你对本章内容的理解:
-
helm template命令的目的是什么?它与helm lint命令有何不同? -
可以利用什么工具来检查渲染后的 Helm 模板的 YAML 格式?
-
如何创建图表测试?如何执行图表测试?
-
helm test和ct lint-and-install之间有什么区别? -
当与 ct 工具一起使用时,
ci/文件夹的目的是什么? -
--upgrade标志如何改变ct lint-and-install命令的行为?
第三部分:高级部署模式
Helm 命令行界面 (CLI) 是一个强大的工具包,但通过自动化可以进一步提高效率。在 第三部分 中,您将学习如何将 Helm 融入行业标准的部署方法。您还将深入了解在日常 Helm 使用过程中需要考虑的重要安全因素。
在本部分中,我们将涵盖以下主题:
-
第十章**,使用 CD 和 GitOps 自动化 Helm
-
第十一章**,使用 Helm 与 Operator 框架
-
第十二章**,Helm 安全性考虑
第十章:使用 CD 和 GitOps 自动化 Helm
在本书中,我们展示了如何使用不同的 Helm 命令来管理 Kubernetes 资源和应用。虽然这些命令(即 install、upgrade、rollback 和 uninstall)在执行各自的任务时非常有效,但我们一直是在命令行中手动调用它们。手动调用在管理多个不同的应用时可能成为痛点,也会使企业难以扩展。因此,我们应该探索自动化 Helm 部署的机会。
在本章中,我们将研究与持续交付(CD)和GitOps相关的概念。这些方法论涉及自动调用 Helm 命令行界面(CLI)来基于 Git 仓库的内容执行自动化图表安装。通过实施 CD 和 GitOps 概念,你可以进一步提高使用 Helm 的效率。
在本章中,我们将涵盖以下主题:
-
理解 CI/CD 和 GitOps
-
设置你的环境
-
安装 Argo CD
-
从 Git 仓库部署 Helm 图表
-
从远程 Helm 图表仓库部署 Helm 图表
-
将 Helm 图表部署到多个环境
-
清理
技术要求
本章要求你在本地计算机上安装以下技术:
-
minikube
-
Helm
-
kubectl
-
Git
除了这些工具外,你还可以在 GitHub 上找到包含本章示例相关资源的 Packt 仓库,地址是 github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm。在本章中,我们将会引用该仓库。
理解 CI/CD 和 GitOps
到目前为止,我们已经讨论了手动调用 Helm CLI 来安装和管理 Helm 图表。虽然在开始使用 Helm 时这种方式是可以接受的,但当你在类似生产的环境中管理图表时,你需要考虑一些问题,包括以下内容:
-
我如何确保强制执行 Helm 图表部署的最佳实践?
-
参与部署过程的协作者会有什么影响?
你可能已经熟悉了部署 Helm 图表的最佳实践和流程;然而,任何新的协作者或团队成员可能没有相同的知识或专业技能。更不用说,随着你管理的应用数量增加,你在为他人提供支持时可能会受到限制。通过使用自动化和可重复的流程,我们可以解决这些挑战。
本章将主要关注 CD 和 GitOps,但如果我们不介绍与 CD 常常搭配的持续集成(CI),那将是一个遗憾。我们将在下一节讨论持续集成/持续交付(CI/CD)和 GitOps。
CI/CD
随着企业多年来努力加速软件开发生命周期,自动化开发过程的需求应运而生,进而催生了 CI。CI 通过使用编排工具自动构建和测试应用程序代码来实现。当新的提交被推送到 Git 仓库时,编排工具会自动检索源代码,并执行预定的一系列步骤来构建应用程序(包括代码质量扫描、漏洞扫描等任务)。通过在新的提交添加时进行自动构建,可以在软件开发生命周期的早期发现回归和破坏性变更。CI 还通过提供一个共同的构建环境,帮助解决 “它在我的机器上能工作” 这一问题。
将 CI 的许多概念应用到整个软件开发生命周期中,随着应用程序向生产环境推进,最终导致了 CD 的诞生。CD 是一组定义好的步骤,用于推动软件通过发布过程。CD 已经在许多组织中获得了接受和流行,尤其是在执行适当的变更控制并要求批准才能让软件推进到下一个阶段的组织中。由于 CI/CD 相关的许多概念都以可重复的方式自动化,团队可以着手完全消除手动审批步骤,一旦他们确信已经建立了可靠的框架。
实现一个完全自动化的构建、测试、部署和发布过程而无需人工干预的过程,称为持续部署。虽然许多软件项目可能永远无法完全实现持续部署,但能够实施 CI/CD 强调的概念的团队,比那些采用较少自动化方法的团队能够更快地创造实际的商业价值。
在接下来的章节中,我们将介绍 GitOps 作为一种机制,以改善应用程序及其配置的管理方式。
使用 GitOps 将 CI/CD 提升到一个新水平
Kubernetes 是一个采用声明式配置的平台。虽然应用程序可以使用命令式的 kubectl 命令进行管理,但我们在第一章《理解 Kubernetes 和 Helm》中提到的首选方法是通过使用清单来声明性地指定资源。就像应用程序通过 CI/CD 管道运行一样,Kubernetes 清单也可以实现许多相同的 CI/CD 模式。像应用程序代码一样,Kubernetes 清单也应存储在源代码仓库中,如 Git,并且可以进行相同类型的构建、测试和部署实践。
将 Kubernetes 应用程序和集群配置的生命周期管理放入 Git 仓库的做法逐渐流行,这也促生了 GitOps 的概念。GitOps 首次由软件公司 WeaveWorks 于 2017 年提出,随着 GitOps 被作为一种管理 Kubernetes 配置的方式,逐渐获得越来越多的关注。虽然 GitOps 在 Kubernetes 背景下最为人知,但其原则也可以应用于任何类型的环境。
类似于 CI/CD,已经开发出了一些工具来管理 GitOps 流程。这些工具包括 Argo CD(来自 Intuit)和 Flux(来自 WeaveWorks)。不过,您不需要使用专门为 GitOps 设计的工具,因为任何自动化工具或 CI/CD 协调器都可以使用。传统的 CI/CD 工具和为 GitOps 设计的工具之间的主要区别在于,GitOps 工具能够持续监控目标环境的状态,并在实时状态与 Git 中定义的清单所规定的期望状态不匹配时应用期望的配置。在 Kubernetes 的背景下,这些工具利用了控制器模式,这也是 Kubernetes 本身的基础。
由于 Helm charts 最终会被渲染为 Kubernetes 资源,它们也可以用于参与 GitOps 流程。在本章中,我们将利用 Argo CD 以 GitOps 方式将 Helm chart 资源部署到 Kubernetes。请注意,这并不是 Argo CD 的全面概述,但它会让您了解如何将 Argo CD 与 Helm 集成,从而为 Helm 部署提供 GitOps 方法。
设置您的环境
在本章中,我们将创建几个命名空间来安装 Argo CD,并在不同的命名空间中部署示例 Helm chart。
运行以下命令以准备您的环境:
-
通过运行
minikube start命令启动minikube:$ minikube start -
接着,创建一个新的命名空间
argo,稍后我们将在该命名空间中安装 Argo CD:$ kubectl create namespace argo -
接下来,创建一个名为
chapter10的命名空间,在这个命名空间中我们将从 Argo CD 部署一个示例的 Helm chart:$ kubectl create namespace chapter10 -
最后,创建两个命名空间,分别为
chapter10-dev和chapter10-prod。我们将使用这些命名空间来演示如何使用 Argo CD 在多个环境中部署 Helm chart:$ kubectl create namespace chapter10-dev $ kubectl create namespace chapter10-prod
在您的 minikube 环境准备好并创建好命名空间后,让我们从部署 Argo CD 开始。然后,我们将通过示例展示如何使用 Argo CD 使用 Helm 将应用程序部署到 Kubernetes。
安装 Argo CD
可以通过使用一组清单文件或安装 Helm chart 来在 Kubernetes 中安装 Argo CD。当然,我们将选择使用社区提供的 Helm chart 来安装 Argo CD。
Argo CD Helm chart 的仓库 URL 为 argoproj.github.io/argo-helm(该链接可以在 Artifact Hub 中找到,具体过程见 第三章,使用 Helm 安装您的第一个应用)。
让我们使用 Helm CLI 添加这个仓库:
$ helm repo add argo https://argoproj.github.io/argo-helm
一旦仓库已添加,您可以进行安装。我们提供了一个 values 文件,您可以在 Packt 仓库中用于安装,地址为 github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter10/argo-values/values.yaml。提供的 values 文件禁用了 Dex(一个 OpenID Connect 提供者)以及 Argo 的通知系统,因为我们在本章中不会使用这些组件。
让我们通过运行以下命令在 argo 命名空间中安装 Argo CD:
$ helm install argo argo/argo-cd –-version 4.5.0 --values chapter10/argo-values/values.yaml -n argo
Helm 图表会在 argo 命名空间中安装以下组件:
-
Application自定义资源。当创建Application资源时,Argo CD 会将资源创建到目标集群和命名空间。 -
ApplicationSet自定义资源。ApplicationSet提供了一种便捷的方式来部署多个不同但相关的Application资源。在演示如何将 Helm 图表部署到多个不同环境或命名空间时,我们将使用 ApplicationSets。 -
Redis,用于缓存后端数据。
-
Argo CD Repo Server,提供克隆 Git 仓库的本地实例。
-
Argo CD Server,提供与 Argo CD 交互的 API。此组件还提供前端 Web 界面。
一旦 argo 命名空间中的每个 pod 报告 1/1 就绪状态(通过运行 kubectl get pods –n argo 可以看到),我们就可以访问 Argo CD Web UI。首先,我们需要获取在 Helm 安装期间随机生成的管理员密码。我们可以通过访问 argo 命名空间中的 Kubernetes secret 来实现:
$ kubectl get secret argocd-initial-admin-secret –n argo –o jsonpath='{.data.password}' | base64 –d
访问 Web UI 的用户名将是 admin,密码是从 the argocd-initial-admin-secret 中获取的字符串。
最后,我们可以运行 port-forward 来访问 Web UI。在一个单独的终端中,运行以下 port-forward 命令:
$ kubectl port-forward svc/argo-argocd-server 8443:443 –n argo
运行 port-forward 命令后,在 Web 浏览器中导航到 localhost:8443。接受自签名证书异常;您将看到 Argo CD 登录页面:

图 10.1 – Argo CD 登录页面
对于用户名,输入 admin,对于密码,提供在获取密码时显示的字符串,来自 argocd-initial-admin-secret。成功登录后,您应该看到以下标题为 APPLICATIONS TILES 的页面:

图 10.2 – Argo CD 应用程序面板页面
本页面可用于创建新应用,这些应用表示应用部署(或任何一组 Kubernetes 资源)。然而,遵循更具声明式配置方法的精神,我们将在本章中通过应用 Application YAML 资源来进行部署。话虽如此,当我们使用 kubectl 直接应用 Application 资源时,UI 将会填充应用图块。可以在本章中随时参考此 UI,查看其如何可视化。
Argo CD 成功部署后,让我们继续从 Git 仓库部署 Helm 图表。
从 Git 仓库部署 Helm 图表
按照真正的 GitOps 风格,Argo CD 可用于从 Git 仓库部署 Helm 图表。下图展示了使用 Argo CD 从 Git 仓库部署 Helm 图表时的流程:

图 10.3 – 从 Git 仓库部署 Helm 图表
在这里,你可以看到 Argo CD 克隆了包含所需 Helm 图表的 Git 仓库。然后,Argo CD 将该仓库解释为一个包含 Helm 图表的仓库,因为它注意到 Chart.yaml 文件和周围的 Helm 图表结构的存在。从那里,Argo CD 继续渲染 Helm 图表清单并将其应用到指定的 Kubernetes 命名空间。
请注意,Argo CD 渲染 Helm 图表模板并将其应用,而不是直接安装 Helm 图表。这是因为 Argo CD 只会应用 Kubernetes 清单,因此它首先会在内部运行 helm template 以根据提供的 Helm 值生成完整的 Kubernetes 清单。如果你在使用 Argo CD 部署图表后运行 helm list 命令,你将看不到任何发布项。不过,你仍然可以看到应用的资源。
你可能会好奇,渲染的 Kubernetes 清单在应用回滚时的影响,因为无法使用 helm rollback 命令。在 GitOps 思想中,你理想的回滚方式是通过在 Git 仓库中进行更改来撤销这些操作。然后,Argo CD 会检测到任何对目标分支的新提交,并应用所需的更改。话虽如此,Argo CD 确实具有本地回滚功能,可以回滚到以前的历史 ID。这使得用户无需回滚到 Git 仓库即可进行回滚。
让我们通过观察位于 Packt 仓库的 chapter10/local-chart/application.yaml 中的 Application 资源,开始从 Git 部署 Helm 图表 (github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter10/local-chart/application.yaml)。我们可以将此资源拆分为不同的组件:
-
首先,我们必须定义资源的
kind并提供资源的元数据:apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: nginx namespace: argo finalizers: resources-finalizer.argocd.argoproj.io
请注意最终器resources-finalizer.argocd.argoproj.io。最终器在 Kubernetes 中用于触发管理控制器的删除前操作。通过这个最终器,我们告诉应用程序控制器,如果我们删除此应用程序资源,控制器应该首先删除渲染的 Kubernetes 资源。如果省略了最终器,应用程序控制器将直接删除应用程序资源,而不删除渲染的 Kubernetes 资源。
-
接下来,我们必须定义应用程序源。在此,我们指定 Git 仓库 URL 和 Helm 图表的路径:
source: path: helm-charts/charts/nginx/ repoURL: https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git targetRevision: HEAD helm: values: |- resources: limits: cpu: 50m memory: 128Mi requests: cpu: 50m memory: 128Mi
从配置中可以看到,Argo CD 将在最新的提交(HEAD)处克隆仓库(https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git)。克隆完成后,它会导航到helm-charts/charts/nginx路径,该路径包含一个 Nginx Helm 图表。
-
在这里,我们还指定了一组 Helm 值,在
helm.values部分设置了资源限制和请求。值也可以通过使用helm.parameters设置来提供,如下所示:source: helm: parameters: - name: resources.limits.cpu value: 50m - name: resources.limits.memory value: 128Mi
这类似于在命令行中传递--set标志。
最后,值也可以通过helm.valueFiles设置提供。我们将在将 Helm 图表部署到多个环境部分中更详细地描述此用例。
-
Application资源的最后部分定义了目标和同步(sync)策略:destination: server: https://kubernetes.default.svc namespace: chapter10 syncPolicy: automated: prune: true selfHeal: true
destination定义了目标集群的 Kubernetes 服务器 API 以及资源应该部署到的命名空间。syncPolicy决定了应用程序应该如何同步。在此上下文中,sync表示将集群中的资源应用或更新为应用源中的资源。同步可以手动进行,但在此示例中,我们选择自动化同步,这样当应用程序资源创建时,Nginx 就会自动部署。
在syncPolicy.automated部分下,可以指定几个其他配置项。prune字段是一个布尔值,决定了如果 Kubernetes 资源从源中移除,是否应该删除该资源。selfHeal设置指示 Argo CD 确保期望状态和实际状态的一致性。如果 Kubernetes 集群中的资源被修改,selfHeal将导致 Argo CD 还原该修改,使其与源配置匹配。
现在我们理解了定义 Nginx 应用程序的应用程序资源,我们可以使用kubectl apply命令安装此Application资源:
$ kubectl apply –f chapter10/local-chart/application.yaml -n argo
结果会在argo命名空间中创建Application资源,在那里 Argo CD 能够看到应用程序资源。
创建应用程序资源后,可以通过运行以下命令查看部署状态:
$ kubectl get applications –n argo
NAME SYNC STATUS HEALTH STATUS
nginx Synced Healthy
SYNC STATUS 显示期望状态是否已与实时状态同步,而 HEALTH STATUS 显示发布是否已完成,或者 pods 是否仍在启动中。由于 nginx 图表和相关资源正在集群中安装,SYNC STATUS 和 HEALTH STATUS 可能需要一段时间才能显示这些值。我们可以通过更传统的方式查看部署状态——即,在 chapter10 命名空间中运行 kubectl get pods 命令:
$ kubectl get pods –n chapter10
NAME READY STATUS RESTARTS AGE
nginx-7bf8646cff-qjv9h 1/1 Running 0 2m5s
当 Application 显示为已同步并且健康时,说明你已经成功通过 Argo CD 从 Git 仓库部署了 Helm 图表!接下来,让我们删除该应用资源(这也会从 chapter10 命名空间中移除 nginx pod):
$ kubectl delete –f chapter10/remote-registry/application.yaml
在我们离开这个话题之前,需要注意的是,正如 GitOps 模型所预期的那样,对 Git 中 nginx Helm 图表的更改将自动传播到 Kubernetes 环境。如果你更新了 nginx 图表并将新提交发布到目标分支的 Git 仓库中,Argo CD 会在下次轮询间隔中注意到这个变化,并根据仓库中定义的期望状态更新 Kubernetes 命名空间。为了更快地同步,可以在 Git 仓库上配置 webhook,以事件驱动的方式触发 Argo CD 同步。有关配置 webhook 的信息,请参见 Argo CD 文档:argo-cd.readthedocs.io/en/stable/operator-manual/webhook/。
接下来,我们将学习如何使用 Argo CD 从远程图表仓库部署 Helm 图表。
从远程 Helm 图表仓库部署应用程序
在安装 Helm 图表时,用户通常会与远程仓库进行交互。同样,我们可以使用 Argo CD 从指定的 Helm 图表仓库部署应用程序。
以下图表显示了通过 Argo CD 从远程 Helm 图表仓库部署应用程序的流程:

图 10.4 – 从远程 Helm 图表仓库部署应用程序
首先,Argo CD 下载在 Application 资源中配置的 Helm 图表。然后,它渲染该 Helm 图表并将清单应用到目标集群和命名空间。
我们在 Packt 仓库的 chapter10/remote-registry/application.yaml 中提供了一个示例 Application 资源 (github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter10/remote-registry/application.yaml)。该资源的配置与上一节中的 Application 类似,但我们可以在 source 部分观察到一个关键的不同点:
source:
chart: nginx
targetRevision: 9.7.6
repoURL: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami
在这里,我们没有提供 Git 仓库,而是提供了远程 Helm 图表仓库的位置,以及图表的名称和版本。如您所见,此Application将指示 Argo CD 从 Bitnami 图表仓库部署nginx图表的版本9.7.6。
从图表仓库部署 Helm 图表的过程与从 Git 仓库部署是相同的——只需将Application资源应用到argo命名空间。您可以自由地按照前一节提供的相同步骤,将应用程序部署到chapter10命名空间。
当我们讨论将 Helm 图表部署到多个环境时,过程会有所变化,这个话题我们将在下一节中讨论。
将 Helm 图表部署到多个环境
在前面的章节中,我们使用 Argo CD 将 Helm 图表部署到单一环境(或命名空间)。然而,在企业中部署应用程序时,您通常会期望将应用程序部署到多个不同的环境,达到类似下图所示的过程:

图 10.5 – 部署到多个命名空间
您可能希望将图表部署到不同的环境(独立的命名空间甚至是独立的集群),原因多种多样,包括高可用性,或是将应用程序部署到管道的多个阶段,如开发、测试和生产环境。幸运的是,我们可以在 Argo CD 中使用ApplicationSet构建来实现这一目标。
假设我们有两个独立的命名空间——一个用于开发(dev),另一个用于生产(prod)。我们可以创建两个独立的Application资源,每个资源在目标部分中指向一个独立的命名空间:
-
Dev 环境将如下所示:
destination: server: https://kubernetes.default.svc namespace: dev -
生产环境(Prod)将非常相似,但我们会在
namespace属性中指定prod:destination: server: https://kubernetes.default.svc namespace: prod
使用两个不同的Application资源——每个环境一个——是处理这种类型部署的完全有效的选项。然而,Argo CD 引入了ApplicationSet,作为一种将多个Application实例包装成单个资源的方法,使您可以在不管理多个资源 YAML 文件的情况下定义多个目标。
ApplicationSet的示例可以在 Packt 仓库中的chapter10/multiple-envs/applicationset.yaml找到(github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter10/multiple-envs/applicationset.yaml)。让我们来分析一下该资源的不同部分:
-
ApplicationSet的第一个组件是generators部分,它生成用于后续动态配置应用程序详细信息(如源和目标)的参数。生成器有许多不同类型,可以在 Argo CD 文档中获得更详细的说明,文档链接为argocd-applicationset.readthedocs.io/en/stable/Generators/。在我们的示例中,我们使用了list生成器,它允许提供一组简单的键值对:generators: - list: elements: - env: dev - env: prod
如你所见,定义我们不同环境的两个元素已经指定——dev和prod。我们将在ApplicationSet的其余部分中引用这些环境,将nginx图表部署到我们的部署管道的两个阶段。
-
接下来,我们必须定义
Application模板。我们首先提供一个名称,ApplicationSet将把该名称注入到生成的Applications中:metadata: name: nginx-{{ env }}
{{ env }}语法表示一个占位符,它将被generators部分中描述的env元素替换。因此,当我们创建ApplicationSet时,我们可以期望会创建两个不同的应用程序:– nginx-dev和nginx-prod。
-
现在我们已经指定了将要创建的应用程序的名称,接下来可以配置源。这将与我们在之前的章节中看到的内容类似:
source: path: chapter10/multiple-envs/nginx repoURL: https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git targetRevision: HEAD helm: releaseName: nginx valueFiles: - values/common-values.yaml - values/{{ env }}/values.yaml
这个源表示 Argo CD 将部署位于github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git的chapter10/multiple-envs/nginx下的 Helm 图表。然而,我们并不是在每个环境中部署相同的配置,而是会根据环境应用稍微不同的值。这可以在helm.valueFiles设置中看到,它提供了一个值文件列表供应用(类似于在命令行中使用--values标志)。无论环境如何,我们始终会应用在values/common-values.yaml文件中定义的通用值集,但根据环境的不同,我们还会应用values/dev/values.yaml文件或values/prod/values.yaml文件。
这些值文件可以在 chapter10/multiple-envs/nginx/values 目录中查看(github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/tree/main/chapter10/multiple-envs/nginx/values)。请注意,由于 Argo CD 已配置为使用 chapter10/multiple-envs/nginx chart 路径,因此值文件必须位于此路径下。同样需要注意的是,这种应用值文件的方法仅适用于从 Git 部署 Helm chart。当从远程 Helm chart 仓库部署时,可以使用 helm.values 或 helm.parameters 方法提供值,正如 从 Git 仓库部署 Helm chart 章节中所描述的那样。
-
最后,我们必须定义 Argo CD 应该将资源部署到的目标:
destination: server: https://kubernetes.default.svc namespace: chapter10-{{ env }}
这将把 Helm chart 部署到 chapter10-dev 和 chapter10-prod 命名空间。在此示例中,我们通过命名空间将环境分开,出于简化的考虑,但您也可以通过参数化 destination.server 部分来指示 Argo CD 部署到不同的集群,就像我们参数化命名空间一样。
现在我们已经知道了如何创建 ApplicationSet,让我们将其应用到我们的 Kubernetes 集群中,将 Helm chart 部署到不同的环境。首先,应用位于 chapter10/multiple-envs/applicationset.yaml 的 ApplicationSet(github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/blob/main/chapter10/multiple-envs/applicationset.yaml):
$ kubectl apply –f chapter10/multiple-envs/applicationset.yaml -n argo
不久后,我们应该在 argo 命名空间中看到两个不同的应用:
$ kubectl get applications –n argo
NAME SYNC STATUS HEALTH STATUS
nginx-dev Synced Healthy
nginx-prod Synced Healthy
我们还可以观察到在不同环境中运行的部署:
$ kubectl get pods –n chapter10-dev
NAME READY STATUS RESTARTS AGE
nginx-6d948d7f48-kkr4j 1/1 Running 0 75s
$ kubectl get pods –n chapter10-prod
NAME READY STATUS RESTARTS AGE
nginx-6d948d7f48-76p22 1/1 Running 0 107s
nginx-6d948d7f48-bf76x 1/1 Running 0 107s
nginx-6d948d7f48-rcq4z 1/1 Running 0 107s
您可以在 chapter10-prod 中观察到三个不同的 nginx pod,因为 chapter10/multiple-envs/nginx/values/prod/values.yaml 中的值文件指定了三个副本,而 dev values 文件仅指定了一个副本。
如果您在 minikube 环境中观察到类似于之前展示的输出,恭喜您!您已经成功地通过 Argo CD 以 GitOps 方式将 Helm chart 部署到多个环境中。
让我们通过清理环境来结束这一章。
清理环境
首先,让我们删除这一章和 Argo 安装中的命名空间:
$ kubectl delete namespace chapter10-prod
$ kubectl delete namespace chapter10-dev
$ kubectl delete namespace chapter10
$ helm uninstall argo –n argo
$ kubectl delete namespace argo
然后,您可以使用 minikube stop 命令停止 minikube 集群。
总结
CD 和 GitOps 提供了可扩展的方法,进一步抽象化了 Helm 所提供的功能,允许通过 Git 仓库或远程 Chart 仓库的内容来控制部署。在本章中,我们介绍了 CI/CD 和 GitOps 的概念,并通过使用 Argo CD 作为实现这些模型的解决方案,探索了在 Helm 环境下的实现方式。接着,我们学习了如何安装 Argo CD 以及如何创建Application和ApplicationSet资源,它们是启用 Argo CD 部署并与指定的 Helm 图表和配置同步的基本组件。最后,我们学习了如何在多个不同环境中部署 Helm 图表。
在下一章中,我们将探索另一个抽象概念——Helm 操作符。
问题
请回答以下问题,测试你对本章内容的掌握情况:
-
CI 和 CD 有什么区别?
-
CD 和 GitOps 之间有什么关系?
-
Argo CD的Application和ApplicationSet之间有什么区别? -
在 Argo CD 中,如何等价于在命令行中传递
--values标志? -
在 Argo CD 中,如何等价于在命令行中传递
--set标志? -
什么是
ApplicationSet生成器?为什么在部署到多个环境时,生成器非常有用?
第十一章:使用 Helm 与操作符框架
使用 Helm 的一个优势是能够声明式地定义应用程序的期望状态。通过 Helm,期望的状态通过模板和 Helm 值进行管理,当通过 install 或 upgrade 命令提供时,这些值会应用于同步 Kubernetes 集群中的实时状态。在前面的章节中,这是通过手动调用这些命令来执行的。最近,在 第十章,使用 CD 和 GitOps 自动化 Helm 中,我们使用 Argo CD 作为一种状态同步的方法。
另一种将更改同步到 Kubernetes 集群的方法是使用一个控制器,该控制器定期检查期望的状态是否与环境中的当前配置匹配。如果状态不匹配,应用程序可以自动修改环境以匹配期望的状态。这个控制器是应用程序的基础,通常被称为Kubernetes 操作符。
在本章中,我们将创建一个基于 Helm 的操作符,帮助确保期望的状态始终与集群的实时状态匹配。如果不匹配,操作符将执行相应的 Helm 命令以协调环境的状态。
本章将涵盖以下主题:
-
理解 Kubernetes 操作符
-
理解 Guestbook 操作符控制循环
-
使用 Helm 管理操作符,自定义资源定义(CRDs)和自定义资源(CRs)
-
清理
技术要求
本章你需要在本地机器上安装以下技术:
-
minikube
-
Helm
-
kubectl
除了这些工具之外,你还应该在 GitHub 上找到包含与示例相关资源的 Packt 仓库,网址为 github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm。本章将会多次引用这个仓库。
理解 Kubernetes 操作符
Kubernetes 的基本原则之一是集群中资源的当前状态与期望状态相匹配,这个过程被称为控制循环。控制循环是一种持续不断的模式,通过使用控制器监控集群的状态。Kubernetes 包含多个原生控制器,例子包括从拦截请求的准入控制器到确保 Pod 副本配置的复制控制器。
随着 Kubernetes 兴趣的不断增长,提供用户扩展平台功能的能力,以及围绕管理应用生命周期的更多智能方式,促使了几个重要功能的创建,以支持 Kubernetes 开发。首先,引入 CRD 使用户能够扩展默认的 Kubernetes API,从而创建并注册新的资源类型。注册新的 CRD 会在 Kubernetes API 服务器上创建一个新的资源路径。例如,注册一个名为 kubectl get Guestbook 的对象类型 CRD,现在可以用来查看当前所有可用的 Guestbook 对象。随着这一新能力的实现,开发者现在可以创建自己的控制器来监控这些 CR 类型,并通过使用 CRD 来管理应用的生命周期。
另一个有助于塑造 Kubernetes 中开发者体验的重要功能是对部署到 Kubernetes 的应用类型的改进。与小型和简单的应用程序不同,越来越多复杂且有状态的应用程序被频繁部署。通常,这些类型的高级应用程序需要更高水平的管理和维护,例如包括备份、恢复和升级等 第二天 活动。这些任务超出了 Kubernetes 原生控制器的典型类型,因为需要将与被管理应用程序相关的深入知识嵌入其中。使用 CR 来管理应用及其组件的模式被称为 操作符 模式。该模式最早由软件公司 CoreOS 于 2016 年提出,操作符旨在捕获人类操作员管理应用程序生命周期时所具备的知识。操作符作为普通的容器化应用程序打包,部署在 pod 中,针对 CR 的 API 变更做出反应。
操作符通常使用名为 kubebuilder 的框架编写,该框架包含简化 CR 创建和与 Kubernetes 环境交互的功能。此后,已创建几个额外的框架,以进一步扩展支持操作符开发的能力。其中一个流行的工具包是 Operator Framework,它为最终用户提供了使用以下三种技术之一创建操作符的能力:
-
Go
-
Ansible
-
Helm
基于 Go 的操作符利用 Go 编程语言实现控制循环逻辑。基于 Ansible 的操作符利用 Ansible CLI 工具和 Ansible 剧本来管理资源的状态。Ansible 是一个流行的开源配置管理工具。
在本章中,我们将重点讨论基于 Helm 的操作符。Helm 操作符将其控制循环逻辑基于 Helm 图表和 Helm CLI 提供的子集功能。因此,它们为 Helm 用户提供了一种简单的方式来实现自己的操作符。
理解了 operators 后,让我们继续通过使用 Helm 创建我们自己的 operator。
理解 Guestbook operator 控制循环
在本章中,我们将编写一个基于 Helm 的 operator,用于安装 Guestbook Helm 图表。该图表可以在 Packt 仓库中找到,网址为 github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/tree/main/helm-charts/charts/guestbook。
下图演示了 Guestbook operator 部署后将如何运作:

图 11.1 – Guestbook operator 控制循环
Guestbook operator 会不断监控 Guestbook CR 的创建、删除或修改。当创建一个 Guestbook CR 时,operator 会安装 Guestbook Helm 图表;当 CR 被修改时,它会相应地升级发布,以确保集群的状态与 CR 定义的目标一致。同样,当 CR 被删除时,operator 会卸载该发布。
了解了 operator 的控制循环如何运作后,让我们设置一个可以构建和部署 operator 的环境。
准备本地开发环境
要创建 Helm operator,您至少需要以下 CLI 工具:
-
operator-sdk -
一个容器管理工具,如
docker、podman或buildah
operator-sdk CLI 是一个用于帮助开发 Kubernetes operator 的工具包。它包含简化 operator 开发过程的内在逻辑。在底层,operator-sdk 需要一个容器管理工具来构建 operator 镜像。支持的容器管理工具有 docker、podman 和 buildah。
安装 operator-sdk CLI 非常简单,您可以从 GitHub 下载一个版本,网址为 github.com/operator-framework/operator-sdk/releases。然而,安装 docker、podman 或 buildah 的过程因操作系统不同而大不相同;更重要的是,Windows 用户将无法原生使用 operator-sdk 工具包。
幸运的是,可以将 minikube operator-sdk 工具包安装到 minikube 中,并使用 minikube 虚拟机作为创建 operator 的环境。
首先,启动 minikube 虚拟机:
$ minikube start
一旦虚拟机启动,请按照以下步骤操作:
-
通过运行
minikube ssh命令访问虚拟机:$ minikube ssh -
一旦进入虚拟机,您需要下载
operator-sdkCLI。可以使用curl命令来完成此操作。请注意,写本文时使用的operator-sdk版本是v1.20.0。
要下载此版本的operator-sdk CLI,请运行以下命令:
$ curl –o operator-sdk –L https://github.com/operator-framework/operator-sdk/releases/download/v1.20.0/operator-sdk_linux_amd64
-
下载后,您需要更改
operator-sdk二进制文件的权限,使其可由用户执行。运行chmod命令进行此修改:$ chmod u+x operator-sdk -
接下来,将
operator-sdk二进制文件移动到PATH变量管理的目录中,例如/usr/local/bin。由于此操作需要 root 权限,你需要使用sudo运行mv命令:$ sudo mv operator-sdk /usr/local/bin -
最后,通过运行
operator-sdk version命令验证你的operator-sdk安装:$ operator-sdk version operator-sdk version: "v1.20.0", commit: "deb3531ae20a5805b7ee30b71f13792b80bd49b1", kubernetes version: "1.23", go version: "go1.17.9", GOOS: "linux", GOARCH: "amd64" -
作为额外的步骤,你还应该在 minikube 虚拟机中克隆 Packt 仓库,因为稍后我们需要它来构建我们的 Helm 操作器。运行以下命令来安装
git并在虚拟机中克隆仓库(注意,我们还将安装make,它对于稍后构建操作器镜像是必要的):$ sudo apt update $ sudo apt install git make $ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git
现在你已经在 minikube 虚拟机中创建了本地开发环境,让我们开始编写 Guestbook 操作器。请注意,操作器代码的示例位于 Packt 仓库中,链接为github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm/tree/main/chapter11/guestbook-operator。
搭建操作器文件结构
与 Helm 图表本身类似,通过operator-sdk二进制文件构建的 Helm 操作器具有特定的文件结构,必须遵循该结构。文件结构在下表中有说明:
| 文件/文件夹 | 定义 |
|---|---|
Dockerfile |
用于构建操作器镜像 |
Makefile |
提供了一组便捷的目标,用于构建操作器镜像并将其部署到 Kubernetes |
PROJECT |
提供operator-sdk的元数据 |
config/ |
包含 CRD、CR 和操作器实例的 Kubernetes 资源清单文件 |
helm-charts/ |
包含操作器负责安装的 Helm 图表 |
watches.yaml |
定义了操作器负责监视的 CRs |
表 11.1 – operator-sdk 文件结构
这个文件结构可以通过使用operator-sdk init和operator-sdk create api命令轻松创建。让我们一起走一遍这个过程,创建一个Guestbook类型的自定义 API 版本demo.example.com/v1alpha1:
-
首先,为操作器创建一个新文件夹并
cd进入新创建的目录:$ mkdir guestbook-operator $ cd guestbook-operator -
接下来,使用
operator-sdk init命令初始化项目:$ operator-sdk init --plugins helm --domain example.com
注意使用了--plugins helm参数。它指定我们的项目是一个 Helm 操作器,并提供了必要的项目框架。–domain example.com参数指定了将用于 CR 的 Kubernetes API 组。然而,这个命令还没有创建 Guestbook CRD 和控制循环逻辑,接下来的步骤将处理这一部分。
-
运行
operator-sdk create api命令来创建 Guestbook CRD 和相关的清单文件:$ operator-sdk create api --group demo --version v1alpha1 --kind Guestbook --helm-chart ../Managing-Kubernetes-Resources-using-Helm/helm-charts/charts/guestbook
你可能会看到关于 RBAC 规则的警告,但对于这个示例来说,可以安全忽略。实际上,你应该始终确保 RBAC 规则遵循最小权限原则。
通过成功创建 Guestbook 操作员框架,让我们构建操作员并将其推送到容器注册表,稍后我们将在那里拉取镜像以进行部署。
构建操作员镜像
operator-sdk生成的文件之一是Makefile,它包含了构建操作员镜像并将其推送到容器注册表的目标。然而,在我们构建镜像之前,需要先访问一个容器注册表。
在第八章中,发布到 Helm Chart 仓库,我们使用了位于ghcr.io的 GitHub 容器注册表来发布镜像。我们将使用相同的注册表来发布我们的 Guestbook 操作员。
为了发布到ghcr.io,你需要先创建一个个人访问令牌(PAT)。如果你已经在第八章中创建过一个令牌,即发布到 Helm Chart 仓库,则不需要重新创建(除非令牌已经过期或你丢失了令牌)。
如果确实需要创建 PAT,你可以按照以下步骤操作:
-
登录 GitHub。登录后,在页面的右上角选择你的个人资料图片,并从下拉菜单中点击设置。
-
点击开发者设置并选择个人账户令牌。
-
点击生成新令牌按钮,开始创建令牌。
-
为令牌输入一个唯一的名称,例如
Learn Helm。 -
选择令牌的过期日期。建议指定过期日期,因为这符合安全最佳实践。
-
选择授予令牌的作用域(权限)。以下作用域是管理容器注册表内容所必需的:
-
read:packages -
write:packages -
delete:packages
-
-
点击生成令牌按钮来创建令牌。
在离开页面之前,请务必复制令牌。如果在记下令牌内容之前离开了页面,令牌可以随时重新生成。但是,之前指定的值将不再有效。
一旦你创建了 PAT 并复制了访问令牌,你可以通过在 minikube 虚拟机中使用docker login命令登录ghcr.io注册表。在Username提示框中输入你的 GitHub 用户名,在Password提示框中粘贴 PAT 令牌:
$ docker login ghcr.io
Username: <user>
Password: <Paste your PAT token here>
一旦你登录到注册表,就可以继续构建并部署操作员镜像。为此,我们可以使用make工具运行不同的 Makefile 目标:
-
首先,我们需要定义镜像名称。Makefile 默认的镜像名称是
controller:latest。我们可以通过设置IMG环境变量来为其指定一个更具描述性的名称:$ export IMG=ghcr.io/<GITHUB_USERNAME>/guestbook-operator:1.0.0
设置IMG变量时,请确保替换为你的 GitHub 用户名。
-
接下来,我们可以使用
docker-buildMakefile 目标开始镜像构建:$ make docker-build
如果构建成功,你将看到 Successfully tagged 消息,后面跟着容器镜像的名称和标签。另外,你可以使用 docker images 命令验证镜像是否创建成功:
$ docker images
REPOSITORY TAG
Ghcr.io/<GITHUB_USERNAME>/guestbook-operator 1.0.0
-
最后,我们可以使用
docker-push目标推送我们的镜像:$ make docker-push . . . 1.0.0: digest: sha256:1f73c8f37afea7c7f4eabaa741d5505880b 5f1bda4de4fad15862acd7d16fb23 size: 1779
默认情况下,成功推送到 ghcr.io 后,您的镜像将是私有的。为了避免需要指定 Kubernetes 拉取密钥来访问镜像,我们可以更新镜像设置,使镜像公开可用。
首先,在 GitHub 上,选择页面右上角的个人头像,选择 guestbook-operator 镜像(在截图中可以看到来自 第八章 的 Guestbook 镜像,发布到 Helm 图表仓库,也可以看到):

图 11.2 – GitHub Packages 页面
接下来,选择 guestbook-operator 包。在屏幕的最右侧,选择 Package Settings,然后更新 Change Visibility 设置为 Public。
如果你已经成功将镜像的可见性更新为 Public,那么你就成功推送了镜像,现在可以在无需凭证的情况下访问它。接下来,让我们继续将操作器部署到 Kubernetes。
部署 Guestbook 操作器
类似于构建操作器,我们可以使用一组 Makefile 目标来部署我们的 Guestbook 操作器。由 operator-sdk 生成的 Makefile 包含四个与操作器安装或移除相关的目标:
-
install: 该目标将 CRD 安装到 Kubernetes 集群中。此目标会将 Guestbook API 添加到集群中。 -
uninstall: 该目标卸载 Kubernetes 集群中的 CRD。此目标会从集群中移除 Guestbook API。 -
deploy: 该目标安装 CRD 并将 Guestbook 操作器部署到 Kubernetes 集群中。稍后我们将使用此目标进行部署。 -
undeploy: 该目标从 Kubernetes 集群中撤销(或移除)CRD 和 Guestbook 操作器实例。
在后台,每个目标都使用 kubectl 和一个名为 config 文件夹的配置管理工具。Kustomize 是一个高层次的工具,使用 kustomization.yaml 文件来指定将要应用的 Kubernetes 清单文件。此外,它还会向每个清单添加补丁和公共配置,例如目标命名空间和资源名称。
config 文件夹的内容如下面的表格所示:
| 文件夹 | 定义 |
|---|---|
config/crd/ |
包含用于扩展 Kubernetes API 的 CRD。对于我们的 Guestbook 操作器,只有一个 CRD。 |
config/default/ |
包含一个父级 kustomization.yaml 文件,用于应用 CRD、RBAC 和操作器(也称为 manager)资源。 |
config/manager/ |
包含创建操作器(或 manager)实例的部署资源。 |
config/manifests/ |
config/default/ 文件夹的超集。在这里,config/manifests 应用 CRD、RBAC 和操作器资源,还应用了一个示例 Guestbook CR 和一个 scorecard,用于测试操作器。 |
config/prometheus/ |
包含用于跟踪指标的 Prometheus ServiceMonitor 资源。默认情况下禁用,但可以在位于 config/default/ 下的 kustomization.yaml 文件中启用。 |
config/rbac/ |
包含 Role、RoleBinding 和 ServiceAccount 资源。这些资源授予操作器管理 Guestbook 资源的权限。它们还为 Kubernetes 集群中的用户创建 Guestbook 编辑和查看角色。 |
config/samples/ |
包含一个示例 Guestbook 清单。 |
config/scorecard/ |
包含用于测试操作器的清单。默认情况下未使用。 |
图 11.4 – 配置文件夹的内容
当我们运行 make deploy 命令时,Kustomize 会定位 config/default/ 中的 kustomization.yaml 文件,并应用 config/crd/、config/manager/ 和 config/rbac/ 目录中的资源。然后,当操作器安装完成后,我们将应用位于 config/samples/demo_v1alpha1_guestbook.yaml 的 Guestbook CR。让我们来看一下 demo_v1alpha1_guestbook.yaml 文件中的一个片段:
apiVersion: demo.example.com/v1alpha1
kind: Guestbook
metadata:
name: guestbook-sample
spec:
# Default values copied from <project_dir>/helm-charts/guestbook/values.yaml
affinity: {}
autoscaling:
enabled: false
maxReplicas: 100
minReplicas: 1
targetCPUUtilizationPercentage: 80
env:
- name: GET_HOSTS_FROM
value: env
- name: REDIS_LEADER_SERVICE_HOST
value: redis-master
- name: REDIS_FOLLOWER_SERVICE_HOST
value: redis-replicas
fullnameOverride: ""
image:
pullPolicy: IfNotPresent
repository: gcr.io/google_samples/gb-frontend
tag: ""
上述的 YAML 看起来熟悉吗?spec 部分下的每个条目引用了 Guestbook chart 中 values.yaml 文件中的默认值。这是使用 Helm 操作器时提供值的方式。用户并不提供一个 values.yaml 文件,而是在 Guestbook CR 中写入值。然后,当资源应用时,操作器会消费这些值并相应地部署应用程序。
基于对操作器 config/ 文件夹和 Makefile 目标的基本理解,我们可以通过以下步骤部署 Guestbook 操作器:
- 为了部署 Guestbook 操作器,我们需要认证到 Kubernetes 集群。由于 minikube 虚拟机中没有安装
kubectl,也没有kubeconfig,我们可以用来进行身份验证,因此从主机机器部署操作器会更简单。
通过运行以下命令退出 minikube 虚拟机:
$ exit
-
我们在 minikube 虚拟机中创建的资源也位于 Packt 仓库中的
chapter11/guestbook-operator/文件夹。你可以克隆该仓库并通过运行以下命令进入guestbook-operator文件夹:$ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git $ cd Managing-Kubernetes-Resources-using-Helm/chapter11/guestbook-operator
来自 Packt 仓库的文件与你在 minikube 虚拟机中创建的文件相同,唯一的例外是。如你可能从前面的章节中回忆的那样,Guestbook Helm 图表包含用于备份和恢复 Redis 数据库的钩子。这些钩子要求操作符具有管理 Job 和 PersistentVolumeClaim 资源的权限。由于 operator-sdk 生成的角色不包含这些资源,因此我们在位于 chapter11/guestbook-operator/config/rbac/role.yaml 的角色定义末尾添加了它们。以下是我们添加的权限:
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- batch
resources:
- jobs
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
-
接下来,我们将使用
make命令将 Guestbook CRD 和操作符部署到 Kubernetes 集群中。请注意,Windows 用户可能需要先安装make,可以通过使用 Chocolatey 包管理器来完成:$ choco install make
通过设置 IMG 环境变量并运行以下 make 命令,继续操作符部署:
$ export IMG=ghcr.io/<GITHUB_USERNAME>/guestbook-operator:1.0.0
$ make deploy
-
操作符已安装在名为
guestbook-operator-system的命名空间中。请验证该 Pod 是否已成功部署到此命名空间:$ kubectl get pods –n guestbook-operator-system NAME READY STATUS guestbook-operator-controller-manager... 2/2 Running
现在操作符已被部署,接下来让我们使用它来安装 Guestbook Helm 图表。
部署 Guestbook 应用
通常使用 Helm 作为独立 CLI 工具时,你会通过运行 helm install 命令来安装 Helm 图表。使用 Helm 操作符时,你可以通过创建 CR 来安装 Helm 图表。
首先,为我们的部署创建一个新的命名空间:
$ kubectl create namespace chapter11
然后,使用位于 Packt 仓库的 chapter11/guestbook-operator/config/samples/demo_v1alpha1_guestbook.yaml 文件中的 CR 安装 Guestbook Helm 图表:
$ kubectl apply –f chapter11/guestbook-operator/config/samples/demo_v1alpha1_guestbook.yaml -n chapter11
安装完成后,你将看到每个 Pod 都处于就绪状态:
$ kubectl get pods –n chapter11
NAME READY STATUS
guestbook-sample-76d48ccddb-dfrkr 1/1 Running
redis-master-0 1/1 Running
redis-replicas-0 1/1 Running
redis-replicas-1 1/1 Running
redis-replicas-2 1/1 Running
当你创建 CR 时,操作符会执行 helm install 命令来安装 Guestbook Helm 图表。你可以通过运行 helm list 来确认发布是否已经创建:
$ helm list –n chapter11
NAME NAMESPACE REVISION
guestbook-sample chapter11 1
升级通过重新应用 CR 并使用一组不同的值来执行。修改后的 CR 位于 Packt 仓库的 chapter11/guestbook-operator/config/samples/upgrade-example.yaml 文件中。在此文件中,replicaCount 的值已从原始 CR 中的 1 修改为 2。
通过运行以下命令应用更新后的 CR:
$ kubectl apply –f chapter11/guestbook-operator/config/samples/upgrade-example.yaml -n chapter11
这个 Guestbook CR 的修改使得操作符触发了针对 guestbook-sample 发布的 helm upgrade。正如你可能从 第七章 中回忆的那样,Helm 生命周期钩子,Guestbook 图表包含一个升级钩子,用于启动 Redis 备份。如果你在修改 CR 后观察 chapter11 命名空间中的 Pod,你将看到备份任务开始,然后很快会看到两个 Guestbook Pod 出现。你还会注意到发布的修订号增加到了 2:
$ helm list –n chapter11
NAME NAMESPACE REVISION
guestbook-sample chapter11 2
尽管修订版本号已增加到2,但在撰写时,Helm 操作符的一个限制是你无法像使用 CLI 时那样启动回滚到先前的版本。如果你尝试对 guestbook-sample 发布运行 helm history,你会发现发布历史中只包含当前的修订版本:
$ helm history guestbook-sample –n chapter11
REVISION UPDATED STATUS CHART
2 Sun May 8 22:44:41 2022 deployed guestbook-0.1.0
这是使用 Helm 与 CLI 常规操作和使用 Helm 作为操作符之间的一个重要区别。由于不会保留发布历史,Helm 操作符不允许你执行显式的回滚。然而,在升级失败的情况下,helm rollback 会被隐式执行。这也会触发 chart 中可能定义的任何回滚钩子。
尽管 Helm 操作符不会保留发布历史,但它们在同步应用程序的期望状态和实时状态方面表现出色。这是因为操作符不断监视 Kubernetes 环境的状态,并确保应用程序始终配置为与 CR 匹配。换句话说,如果 Guestbook 应用程序的某个资源被修改,操作符会立即将其更改恢复,以便与 CR 中定义的配置同步。你可以通过修改一个实时资源来看它的实际效果。例如,我们将 Guestbook 部署的副本数从 2 更改为 3,并观察操作符立即将其恢复为 2,以重新同步状态与 CR 匹配。运行以下 kubectl patch 命令,将部署的副本数从 2 更改为 3:
$ kubectl patch deployment guestbook-sample –p '{"spec":{"replicas":3}}' -n chapter11
通常,这将创建一个额外的 Guestbook pod 副本。然而,由于 Guestbook CR 当前只定义了 2 个副本,操作符会迅速将副本数改回 2,并终止已创建的额外 pod。如果你真的希望将副本数增加到 3,你需要更新 CR 中的 replicaCount 值。这个过程的优点是确保期望的状态始终与实时状态匹配。
卸载由 Helm 操作符创建的发布非常简单,只需删除 CR 即可。删除 guestbook-sample CR 以卸载发布:
$ kubectl delete –f chapter11/guestbook-operator/config/samples/demo_v1alpha1_guestbook.yaml -n chapter11
你还可以删除 Guestbook 操作符及其资源,因为在下一节中我们不再需要它们。你可以通过运行另一个 make 命令来完成此操作:
$ make undeploy
通常,在删除操作符之前,你应该始终确保先删除 CR。如果你在 CR 之前删除了操作符,操作符将无法自动运行 helm uninstall,你必须手动从命令行运行它。
在本章过程中,你创建了一个 Helm 操作符,并学习了如何使用基于操作符的方法安装 Helm chart。在接下来的部分,我们将继续讨论操作符,探讨如何通过 Helm 管理操作符。
使用 Helm 管理操作符、CRD 和 CR
在本章中,我们使用由operator-sdk生成的Makefile实例来安装了访客留言操作员和 CRD。然后,我们使用kubectl apply安装了一个访客留言 CR。虽然这是创建这些资源的可接受方法,但我们也可以使用 Helm 图表来安装和管理操作员提供一个更可重复的解决方案,来安装操作员、CRD 和 CR。
Helm 允许您通过将它们添加到 Helm 图表中名为crds/的目录来创建 CRD。Helm 在定义了templates/文件夹下的任何其他资源之前创建 CRD,从而简化了安装依赖于 CRD 的操作员应用程序等应用程序。
以下是用于安装访客留言操作员的 Helm 图表的文件结构:
guestbook-operator/
Chart.yaml
crds/
guestbooks_crd.yaml
templates/
deployment.yaml
role_binding.yaml
role.yaml
service_account.yaml
values.yaml
安装时,这个 Helm 图表将首先安装访客留言定制资源定义(CRD)。如果 CRD 已经存在于集群中,它将跳过 CRD 的创建并直接安装模板。请注意,虽然 CRD 可以方便地包含在 Helm 图表中,但有几个限制需要注意。首先,Helm 不允许 CRD 包含任何 Go 模板化内容,因此 CRD 无法像典型资源那样从参数化中受益。此外,CRD 不能被升级、回滚或删除。最后,包括 CRD 在您的图表中将要求用户在 Kubernetes 集群中拥有提升的集群级权限。通常,执行操作员安装的是管理员,因此这可能是一个可接受的方法。
我们之前描述的 Helm 图表可以用于安装访客留言操作员,但这只是方程的一半,因为最终用户仍然必须能够创建部署访客留言应用程序的 CR。为解决这个限制,可以创建一个单独的 Helm 图表,用于模板化访客留言 CR。这种类型的 Helm 图表的示例布局如下所示:
guestbook/
Chart.yaml
templates/
Guestbook.yaml
values.yaml
与 CRD 不同,位于templates/文件夹下的 CR 从 Go 模板化和生命周期管理中受益,与所有其他资源一样。当 CR 包含复杂字段或必须与 CR 并行安装其他资源时,这种方法提供了最大的价值。您还可以使用此方法管理 CR 的生命周期,并维护修订版本的历史记录。
然而,用户需要被授予安装访客留言定制资源的权限,因为这个权限在 Kubernetes 默认情况下不包含在内。可以通过在操作员的config/rbac/文件夹下应用guestbook_editor_role.yaml文件来轻松添加这些权限。然后,您可以创建一个RoleBinding资源将编辑角色分配给适当的用户或组。
现在您已经了解了如何使用 Helm 管理操作员、CRD 和 CR,让我们通过清理 Kubernetes 环境来结束本章。
清理
首先,删除chapter11命名空间:
$ kubectl delete namespace chapter11
最后,运行 minikube stop 命令来停止你的 minikube 虚拟机。
总结
operator 对于确保期望状态始终与实时状态匹配非常重要。这样的成就使得用户更容易维护资源配置的真实来源。用户可以利用 Helm operator 提供这种资源调解,而且它很容易上手,因为它使用 Helm charts 作为部署机制。当 CR 被创建时,Helm operator 会安装相关的 Helm chart 来创建一个新的发布版本。之后,当 CR 被修改时,会进行升级,而当 CR 被删除时,发布版本会被卸载。
为了管理 operator,集群管理员可以创建一个单独的 Helm chart 来创建 operator 的资源和 CRD。此外,最终用户可以创建一个单独的 Helm chart 来创建 CR 和其他相关资源。
在下一章中,我们将讨论 Helm 生态系统中的最佳实践和安全性相关话题。
进一步阅读
-
要了解更多关于 operator 及其起源的信息,请查看 Kubernetes 文档:
kubernetes.io/docs/concepts/extend-kubernetes/operator/。 -
要发现社区中已开发的其他操作符,请访问 OperatorHub:
operatorhub.io或 ArtifactHub 的 Operators 部分:artifacthub.io。
问题
-
什么是 operator?在高层次上,operator 是如何工作的?
-
使用 Helm CLI 安装 Helm chart 与使用 Helm operator 安装 Helm chart 有什么区别?
-
你可以使用什么工具包来创建 Helm operator?
-
install、upgrade、rollback和uninstall钩子在 Helm operator 中如何工作? -
Helm chart 中
crds/文件夹的作用是什么?
第十二章:Helm 安全性考虑
正如你在本书中可能已经意识到的,Helm 是一个强大的工具,使得在 Kubernetes 上部署应用程序变得简单高效。话虽如此,我们需要确保在使用 Helm 时不忽视安全最佳实践。幸运的是,Helm 提供了多种方式来将良好的安全实践融入到日常使用中,这些做法从下载 Helm CLI 的那一刻起,到将 Helm 图表安装到 Kubernetes 集群中的每一步都可以轻松实现。
在本章中,我们将覆盖以下主题:
-
数据来源和数据完整性
-
开发安全且稳定的 Helm 图表
-
配置 RBAC 规则
技术要求
本章将使用以下技术:
-
minikube -
kubectl -
helm -
gpg(GNU 隐私保护)
你已经学会了如何在第二章《准备 Kubernetes 和 Helm 环境》中安装和配置minikube、kubectl和helm命令行接口(CLIs)。
我们还将使用位于github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm的 Packt 仓库中的 guestbook 图表,作为本章后续练习的一部分。如果你还没有克隆该仓库,可以使用以下命令进行克隆:
$ git clone https://github.com/PacktPublishing/Managing-Kubernetes-Resources-using-Helm.git
让我们从讨论数据来源和数据完整性开始。
数据来源和数据完整性
在处理任何类型的数据时,应考虑两个常被忽视的问题:
-
数据来自可靠的源吗?
-
数据是否包含你预期的所有内容?
第一个问题与数据来源的话题有关。数据来源是指确定数据的起源,并确定数据来自何处。
第二个问题涉及到数据完整性的话题。数据完整性是指确定你从远程位置接收到的内容是否与预期收到的内容一致。它有助于判断数据在传输过程中是否被篡改。
数据来源和数据完整性都可以通过一个叫做数字签名的概念来验证。作者可以基于加密技术创建一个唯一的签名来签署数据,数据的消费者可以使用加密工具来验证签名的真实性。如果真实性得到验证,那么消费者就可以确保数据来源于预期的源头,并且在传输过程中没有被篡改。
作者可以使用多种工具创建数字签名。一个方法是使用Pretty Good Privacy(PGP)。在此背景下,PGP 指的是 OpenPGP,它是一组基于加密的标准。PGP 的重点是建立非对称加密,基于使用两个不同的密钥——私钥和公钥。
私钥是用来保密的,而公钥是用于共享的。在 PGP 中,私钥用于加密数据,公钥则由消费者用于解密数据。PGP 密钥对通常通过一个名为 GPG 的工具来创建,这是一个实现 OpenPGP 标准的开源工具。
要开始使用 PGP,第一步是创建密钥对,这将生成一组公钥和私钥。一旦 PGP 密钥对被创建,作者就可以使用 GPG 对数据进行签名。当数据被签名时,GPG 在后台执行以下步骤:
-
基于数据的内容计算哈希。输出结果是一个固定长度的字符串,称为消息摘要。
-
消息摘要是使用作者的私钥进行加密的。输出结果即为数字签名。
为了验证签名,消费者必须使用作者的公钥来解密它。此验证过程也可以使用 GPG 来执行。
数字签名在 Helm 中扮演着两个角色:
-
首先,每个 Helm 二进制文件都有一个数字签名,该签名由其中一位 Helm 维护者拥有。此签名可以用来验证下载的来源以及其完整性。
-
其次,Helm charts 也可以进行数字签名,以便享受相同形式的验证。Helm charts 的作者可以在打包时对 chart 进行签名,chart 用户则可以使用作者的公钥验证 chart 的真实性。
现在你已经理解了数据来源和完整性如何与数字签名相关联,在下一部分中,你将会在本地机器上创建一个 GPG 密钥对,用来详细说明前面提到的概念。
创建一个 GPG 密钥对
要创建一个密钥对,你必须在本地机器上安装 GPG。请按照以下说明作为指南,在你的机器上安装 GPG。请注意,本章基于gpg版本 2.3.6:
-
对于 Windows,你可以使用 Chocolatey 包管理器:
> choco install gnupg
你还可以从gpg4win.org/download.html下载 Windows 安装程序。
-
对于 macOS,你可以使用 Homebrew 包管理器,通过以下命令来安装:
$ brew install gpg
你还可以从sourceforge.net/p/gpgosx/docu/Download/下载基于 macOS 的安装程序。
-
对于基于 Debian 的 Linux 发行版,你可以使用
apt包管理器:$ sudo apt install gnupg -
对于基于 RPM 的 Linux 发行版,你可以使用
dnf包管理器:$ sudo dnf install gnupg
一旦你安装了 GPG,你就可以创建你自己的 GPG 密钥对,我们将在整个数据来源和完整性的讨论中使用它。
按照以下步骤配置你的密钥对:
-
首先,我们需要通过运行
gpg --full-generate-key命令来开始生成过程:$ gpg --full-generate-key -
对于
Please select what kind of key you want提示,选择(1) RSA and RSA:Please select what kind of key you want: (1) RSA and RSA (2) DSA and Elgamal (3) DSA (sign only) (4) RSA (sign only) (9) ECC (sign and encrypt) *default* (10) ECC (sign only) (14) Existing key from card Your selection? 1
我们使用 RSA 而不是默认选项(ECC)的原因是 ECC 不被 Helm 源代码中使用的加密库支持。
-
接下来,系统将提示您输入密钥大小。在本示例中,我们可以简单地选择默认值,因此按Enter键继续:
RSA keys may be between 1024 and 4096 bits long. What keysize do you want? (3072) <enter> Requested keysize is 3072 bits -
在您输入密钥大小后,系统将询问密钥的有效期。由于此密钥仅用于通过示例,我们建议设置较短的过期时间,例如 1 周(
1w):Please specify how long the key should be valid. 0 = key does not expire <n> = key expires in n days <n>w = key expires in n weeks <n>m = key expires in n months <n>y = key expires in n years Key is valid for? (0) 1w Key expires at Sun May 22 12:26:09 2022 EDT Is this correct? (y/N) y -
现在,系统将提示您输入姓名和电子邮件地址。这些信息将用于标识您为密钥对的拥有者,并且将在接收您公钥的人那里显示您的姓名和电子邮件地址。系统还会提示您提供一个评论,您可以将其留空:
GnuPG needs to construct a user ID to identify your key. Real name: John Doe Email address: jdoe@example.com Comment: <enter> You selected this USER-ID: "John Doe <jdoe@example.com>" -
按下O键继续。
-
最后,系统将提示您输入私人密钥的密码短语。提供强密码短语对于保护您的身份至关重要,以防您的私人密钥被盗。因为每次您尝试访问密钥时,都必须提供密码短语。
为了保持示例简洁,我们将创建一个空字符串密码短语,以避免密码短语提示。虽然在本演示中这样做是可以接受的,但您应该在实际使用中为任何私人密钥设置强密码进行保护。
要继续,只需按Enter键提交一个空的密码短语。当提示时,选择<是的,不需要保护>。
一旦您的 GPG 密钥对创建完成,您将看到类似以下内容的输出:
pub rsa3072 2022-05-15 [SC] [expires: 2022-05-22]
D2557B1EDD57BBC41A5D4DA7161DADB1C5AC21B5
uid John Doe <jdoe@example.com>
sub rsa3072 2022-05-15 [E] [expires: 2022-05-22]
上述输出显示了公钥(pub)和私钥(sub)的信息,以及公钥的指纹(输出的第二行)。指纹是一个唯一的标识符,用于识别您为该密钥的所有者。第三行以uid开头,显示了您在生成 GPG 密钥对时输入的姓名和电子邮件地址。
密钥对创建完成后,让我们继续下一部分,了解如何验证 Helm 二进制文件。
验证 Helm 下载
如在第二章中讨论的,准备 Kubernetes 和 Helm 环境,Helm 可以通过从 GitHub 下载存档来安装。这些存档可以通过访问 Helm 的 GitHub 发布页面(github.com/helm/helm/releases)并选择以下截图中显示的链接来安装:

图 12.1 – Helm GitHub 发布页面的安装和升级部分
在前面截图的底部,你会看到一段说明,解释了该版本已被签名。每个 Helm 发布版本都会由一个 Helm 维护者签名,并且可以通过与下载版本对应的数字签名进行验证。每个签名位于Assets部分,如下所示:

图 12.2 – Helm GitHub 发布页面的 Assets 部分
要验证 Helm 下载的来源和完整性,除了下载二进制文件本身外,你还应该下载相应的 .asc 文件。请注意,sha256 文件仅用于验证完整性。在此示例中,我们将下载 .tar.gz.asc 文件,它用于验证来源和完整性。
让我们演示如何验证一个 Helm 发布版本。首先,我们应该下载一个 Helm 压缩包,并获取相应的 .asc 文件:
-
下载与你操作系统相对应的 Helm 压缩包。对于本示例,我们将使用 3.8.2 版本。如果你使用的是基于 AMD64 的 Linux 系统,可以从 GitHub 发布页面下载该版本,或者使用以下
curl命令:$ curl -LO https://get.helm.sh/helm-v3.8.2-linux-amd64.tar.gz -
接下来,下载与你操作系统相对应的
.asc文件。当运行基于 AMD64 的 Linux 系统时,helm-v3.8.2-linux-amd64.tar.gz.asc将是下载的文件。你可以从 GitHub 发布页面下载此文件,或使用以下curl命令:$ curl -LO https://github.com/helm/helm/releases/download/v3.8.2/helm-v3.8.2-linux-amd64.tar.gz.asc
下载完这两个文件后,你应该能够在命令行中看到它们位于同一目录中:
$ ls –l
helm-v3.8.2-linux-amd64.tar.gz
helm-v3.8.2-linux-amd64.tar.gz.asc
下一步是将 Helm 维护者的公钥导入到你的本地 GPG 密钥环中。这将允许你解密 .asc 文件中的数字签名,从而验证你下载的二进制文件的来源和完整性。GPG 公钥保存在公钥服务器中,例如 keyserver.ubuntu.com 和 pgp.mit.edu。因此,我们可以使用 gpg --recv-key 命令从公钥服务器下载维护者的密钥。
让我们导入维护者的公钥并继续验证过程:
-
首先,回想一下维护者的公钥指纹(见图 12.1):
672C657BE06B4B30969C4A57461449C25E36B98E -
使用
gpg --recv-key命令下载并导入公钥到你的本地密钥链中:$ gpg --recv-key 672C657BE06B4B30969C4A57461449C25E36B98E gpg: key 461449C25E36B98E: public key "Matthew Farina <matt@mattfarina.com>" imported gpg: Total number processed: 1 gpg: imported: 1 -
现在公钥已经导入,你可以使用 GPG 的
--verify子命令来验证 Helm 发布版本。该命令的语法为gpg --verify <signature> <data>:$ gpg --verify helm-v3.8.2-linux-amd64.tar.gz.asc helm-v3.8.2-linux-amd64.tar.gz
该命令解密 .asc 文件中的数字签名。如果成功,说明该 Helm 下载(以 .tar.gz 结尾的文件)是由你预期的人(本次版本为 Matt Farina)签署的,并且下载文件没有被修改或篡改。成功的输出类似于以下内容:
gpg: Signature made Wed Apr 13 14:00:32 2022 EDT
gpg: using RSA key 711F28D510E1E0BCBD5F6BFE9436E80BFBA46909
gpg: Good signature from "Matthew Farina <matt@mattfarina.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 672C 657B E06B 4B30 969C 4A57 4614 49C2 5E36 B98E
Subkey fingerprint: 711F 28D5 10E1 E0BC BD5F 6BFE 9436 E80B FBA4 6909
经过进一步检查,你可能会注意到 WARNING 消息,表示密钥尚未被认证,这可能会让你怀疑验证是否成功。在这种情况下,验证确实成功,但你还没有认证维护者的密钥,所以 GPG 返回了这个警告。
This key is not certified 消息通常不是问题,但如果你希望确保将来不再出现此警告,可以按照以下步骤认证维护者的公钥:
-
检查公钥的指纹(也称为
gpg --verify输出中的主密钥)是否与 Helm 发布页面上显示的指纹匹配。如你所记得,图 12.1 中已经显示了该指纹,如下所示:This release was signed with 672C 657B E06B 4B30 969C 4A57 4614 49C2 5E36 B98E and can be found at @mattfarina keybase account. -
因为我们导入的密钥与 GitHub 上显示的指纹匹配,我们知道可以信任该密钥。通过使用
gpg --sign-key子命令,可以将信任与此密钥关联:$ gpg --sign-key 672C657BE06B4B30969C4A57461449C25E36B98E
在 Really sign? 提示中,输入 y。
现在你已经签署了维护者的公钥,该密钥已被认证。下次你使用该密钥进行验证时,应该不再看到警告消息:
$ gpg --verify helm-v3.8.2-linux-amd64.tar.gz.asc helm-v3.8.2-linux-amd64.tar.gz
gpg: assuming signed data in 'helm-v3.8.2-linux-amd64.tar.gz'
gpg: Signature made Wed Apr 13 14:00:32 2022 EDT
gpg: using RSA key 711F28D510E1E0BCBD5F6BFE9436E80BFBA46909
gpg: Good signature from "Matthew Farina <matt@mattfarina.com>" [full]
数字签名在签署和验证 Helm 图表中也起着重要作用。我们将在下一节中探讨这一主题。
签署和验证 Helm 图表
与 Helm 维护者签署发布版本的方式类似,你也可以签署你的 Helm 图表,以便用户可以验证其来源并确认其内容符合预期。要签署一个图表,你必须在本地工作站上拥有一对 GPG 密钥(我们在 创建 GPG 密钥对 部分中已创建了 GPG 密钥对)。
在我们开始签署图表之前,有一个重要的警告需要注意。如果你使用的是 GPG 版本 2 或更高版本,你必须将公钥和私钥导出为旧版格式。早期版本的 GPG 将密钥环存储在 .gpg 文件格式中,这是 Helm 所期望的密钥环格式(在撰写本文时)。较新的 GPG 版本将密钥环存储在 .kbx 文件格式中,这在撰写本文时并不兼容。
幸运的是,我们可以通过以下步骤将密钥导出为 .gpg 格式:
-
首先,通过运行以下命令找到你的 GPG 版本:
$ gpg --version gpg (GnuPG) 2.3.6 libgcrypt 1.10.1 Copyright (C) 2021 Free Software Foundation, Inc. -
如果你的 GPG 版本是 2 或更高版本,使用
gpg --export和gpg --export-secret-keys命令导出你的公钥和私钥:$ gpg --export > ~/.gnupg/pubring.gpg $ gpg --export-secret-keys > ~/.gnupg/secring.gpg
一旦你的密钥环已被导出,你将能够使用 helm package 命令签署图表。helm package 命令提供了三个关键标志,允许你签署并打包图表:
-
--sign:这允许你使用 PGP 私钥签署图表。 -
--key:签名时使用的密钥名称。 -
--keyring:包含 PGP 私钥的密钥环位置。
让我们运行 helm package 命令,签署来自 Packt 仓库的 guestbook Helm 图表:
$ helm package --sign --key <key_name> --keyring ~/.gnupg/secring.gpg helm-charts/charts/guestbook
<key_name>占位符指的是与所需密钥相关的电子邮件、名称或指纹。这些细节可以通过使用gpg --list-keys命令找到。
如果helm package命令成功,你将在当前目录中看到以下文件:
guestbook-0.1.0.tgz
guestbook-0.1.0.tgz.prov
guestbook-0.1.0.tgz文件是包含 Helm chart 的归档文件。无论是否签署 chart,这个文件总是由helm package创建的。
guestbook-0.1.0.tgz.prov文件称为证明文件。证明文件包含一个证明记录,记录内容包括以下内容:
-
文件中的 chart 元数据
-
guestbook-0.1.0.tgz文件的 sha256 哈希值 -
PGP 数字签名
Helm chart 的使用者利用证明文件来验证他们下载的 chart 的数据来源和完整性。因此,chart 开发人员应确保将.tgz归档文件和.tgz.prov证明文件都发布到 Helm chart 仓库中。
虽然你已经成功签署了 guestbook chart 并创建了.tgz.prov文件,但这还不足以让用户验证 chart,因为他们仍然需要访问你的公钥来解密你的签名。你可以通过gpg --send-key命令将此密钥发布到 PGP 密钥服务器,以便用户获取:
$ gpg --send-key <key_name>
最终用户可以使用gpg --recv-key命令下载并导入此密钥:
$ gpg --recv-key <key_name>
一旦用户导入了你的公钥(并将其导出到~/.gnupg/pubring.gpg密钥环,如本节前面所示),他们可以通过使用helm verify命令验证你的 Helm chart,前提是.tgz chart 归档文件和.tgz.prov证明文件都已下载到同一目录:
$ helm verify --keyring ~/.gnupg/pubring.gpg guestbook-0.1.0.tgz
Signed by: John Doe <jdoe@example.com>
Using Key With Fingerprint: D2557B1EDD57BBC41A5D4DA7161DADB1C5AC21B5
Chart Hash Verified: sha256:c8089c7748bb0c8102894a8d70e641010b90abe9bb45962a 53468eacfbaf6731
如果验证成功,你将看到签名者、签名者的公钥和 chart 已经被验证。否则,会返回一个错误。验证可能失败的原因包括:
-
.tgz和.tgz.prov文件不在同一目录中。 -
.tgz或.tgz.prov文件已损坏。 -
文件哈希不匹配,表示完整性丧失。
-
用于解密签名的公钥与最初用于加密签名的私钥不匹配。
helm verify命令旨在运行在本地下载的 charts 上,因此用户可能会发现使用helm install --verify命令更为方便,该命令在一个命令中执行验证和安装,前提是.tgz和.tgz.prov文件都可以从 chart 仓库下载。
以下命令描述了如何使用helm install --verify命令:
$ helm install guestbook <chart_repo>/guestbook --verify --keyring ~/.gnupg/pubring.gpg
通过本节中描述的方法,chart 开发人员和使用者可以确保内容来自可信的来源,并且未被篡改。
了解数据溯源和完整性在 Helm 中的作用后,让我们继续讨论 Helm 安全性问题,转到下一个话题——关于 Helm 图表及其开发的安全性。
开发安全且稳定的 Helm 图表
虽然溯源和完整性在 Helm 安全性中扮演着重要角色,但它们并不是你需要考虑的唯一问题。在开发过程中,图表开发者应确保他们遵循安全最佳实践,以防止在用户将图表安装到 Kubernetes 集群时引入漏洞。在本节中,我们将讨论与 Helm 图表开发相关的许多安全问题,以及作为开发者,你可以做些什么,将安全性作为编写 Helm 图表的优先事项。
我们将首先讨论关于 Helm 图表可能引用的任何容器镜像的安全性。
使用安全的镜像
由于 Helm(和 Kubernetes)的目标是部署容器镜像,因此镜像本身涉及多个安全方面的考虑。首先,图表开发者应该了解镜像标签和镜像摘要之间的区别。
标签是对给定镜像的可读引用,为开发人员和使用者提供了一个便捷的方式来引用镜像。然而,标签可能带来安全隐患,因为无法保证给定标签的内容始终保持不变。镜像所有者可能会选择使用相同的标签推送更新的镜像,这会导致在运行时执行的是不同的底层镜像(即使标签相同)。在相同标签下执行这些操作,会引入回归的可能性,这可能会给最终用户带来意想不到的不良影响。与其通过标签引用镜像,不如通过摘要来引用镜像。镜像摘要是一个计算出的SHA-256值,它不仅提供了一个不可变的标识符来精确标识镜像,还允许容器运行时验证从远程注册表获取的镜像的完整性。这消除了部署包含意外回归的镜像的风险,同时也能消除中间人攻击的风险,其中标签的内容被恶意篡改。
举个例子,图表模板中引用的镜像quay.io/bitnami/redis:5.0.9可以通过摘要引用为quay.io/bitnami/redis@sha256:70b816f2127afb5d4af7ec9d6e8636b2f0f973a3cd8dda7032f9dcffa38ba11f。请注意,镜像名称后面不是指定标签的名称,而是明确指定了SHA-256摘要。这确保了即使标签发生变化,镜像内容也不会改变,从而增强了你的安全性。
随着时间的推移,某个标签或摘要可能变得不适合部署,因为最终可能会发现底层包或基础组件存在漏洞。有许多不同的方法可以确定给定镜像是否存在漏洞。一种方法是利用该镜像所属镜像库的原生功能。许多不同的镜像库解决方案都包含有关镜像漏洞扫描的功能,可以帮助提供何时镜像存在漏洞的洞察。
例如,Quay 容器镜像库可以在指定的时间间隔内自动扫描镜像,以确定给定镜像中可能存在的漏洞。Nexus 和 Artifactory 容器镜像库也是具备类似功能的容器镜像库的例子。在容器镜像库提供的原生扫描功能之外,还可以使用其他工具,如Vuls和OpenSCAP。当你的镜像库或独立扫描工具报告镜像存在漏洞时,应立即将图表中的镜像更新为较新版本(如果有的话),以防止漏洞进入用户的 Kubernetes 集群。
为了简化更新容器镜像的过程,你可以制定一个定期检查镜像更新的计划。这有助于防止目标镜像中出现漏洞,进而使其不适合部署。许多团队和组织也将镜像的来源限制为受信任的镜像库,以减少运行包含漏洞的镜像的风险。此设置可以在容器运行时层面配置,或在应用到 Kubernetes 集群的策略中进行配置。具体的位置和配置会根据具体的实现方式有所不同。
除了镜像漏洞扫描和内容来源外,你还应该避免部署以root或privileged身份运行的镜像。以 root 用户(UID 0)身份运行容器是危险的,因为如果容器能够突破隔离,它将获得对底层主机的 root 访问权限。你的应用程序可能并不需要 root 提供的权限级别,因此你应该改为以非 root 用户身份运行容器,以限制其可用的权限。
虽然以 root 身份运行容器是危险的,但由于容器提供的进程隔离功能,它默认并不会授予所有可用的 Linux 能力。因此,一些用户往往错误地通过以特权模式运行容器来进一步提升权限。以特权模式运行容器会授予容器所有能力,允许它在容器内部与底层主机进行交互。如果你的应用程序确实需要额外的能力,可以通过在 pod 的 securityContext 中选择所需的具体能力列表,而不是以特权模式运行容器。可以在 Linux 手册页的CAPABILITIES(7) 页面中找到能力的列表(man7.org/linux/man-pages/man7/capabilities.7.html)。
除了已经部署的容器镜像外,chart 开发者应关注已授予应用程序的资源,以确保 Kubernetes 集群的完整性。我们将在下一节中深入探讨这个话题。
设置资源请求和限制
Pods 从它们运行的主机(节点)中消耗资源。资源通过请求(分配的最小资源量)和限制(pod 允许使用的最大资源量)来定义。没有定义请求的 pods 有可能被调度到不能满足其最小资源需求的节点上。没有定义限制的 pods 有可能会耗尽节点资源,导致 pod 被驱逐,并与其他工作负载发生资源争用。由于未设置资源请求和限制可能会导致的问题,chart 开发者应确保他们的 charts 定义了这些资源约束,并允许用户根据需要通过 Helm 值进行覆盖。
例如,作为一个 chart 开发者,你可能会编写你的 chart,使其包括以下默认值来配置资源:
resources:
limits:
cpu: "1"
memory: 4Gi
requests:
cpu: 500m
memory: 2Gi
然后,如果没有明确设置应用程序的资源需求,chart 被安装时会应用这些默认值,以避免集群资源的过度或不足利用。
除了资源请求和限制外,Kubernetes 管理员还可以创建 LimitRange 和 ResourceQuota 对象,以限制在命名空间内应用程序请求和消耗的资源数量。LimitRange 和 ResourceQuota 对象通常与 Helm 分开应用,通常作为命名空间配置过程的一部分。
LimitRanges 用于限制容器或 pod 在给定命名空间内允许消耗的资源数量。它们也用于为那些没有已定义资源限制的容器设置默认资源。以下是一个 LimitRange 定义的示例:
apiVersion: v1
kind: LimitRange
metadata:
name: limits-per-container
spec:
limits:
- max:
cpu: 4
memory: 16Gi
default:
cpu: 500m
memory: 2Gi
defaultRequest:
cpu: 50m
memory: 128Mi
type: Container
在该示例中,LimitRange 将允许的最大容器资源限制设置为 4 个 CPU 核心和 16 Gi 内存。对于资源限制未定义的容器,将自动应用 500 毫核 CPU 和 2 Gi 内存的限制。对于资源请求未定义的容器,将自动应用 50 毫核 CPU 和 128 Mi 内存的请求。通过将 type 字段设置为 Pod,LimitRanges 也可以在 pod 级别应用。此设置确保 pod 中所有容器的资源使用总和满足指定的限制。
除了设置对 CPU 和内存使用率的限制外,您还可以配置 LimitRange 来限制存储消耗,通过将 type 字段设置为 PersistentVolumeClaim。以下是一个 LimitRange 示例,用于将存储请求限制为 8 Gi 或更少:
apiVersion: v1
kind: LimitRange
metadata:
name: limits-per-pvc
spec:
- max:
storage: 8Gi
type: PersistentVolumeClaim
虽然 LimitRange 对象用于限制 Container、Pod 或 PersistentVolumeClaim 级别的资源,ResourceQuotas 则由集群管理员在命名空间级别限制资源使用。它们用于定义一个命名空间可以使用的最大资源数量,并限制可以创建的 Kubernetes 对象的数量,例如 Secrets 和 ConfigMaps。以下是一个 ResourceQuota 定义示例:
apiVersion: v1
kind: ResourceQuota
metadata:
name: pod-and-pvc-quota
spec:
hard:
limits.cpu: "32"
limits.memory: 64Gi
requests.cpu: "24"
requests.memory: 48Gi
requests.storage: 20Gi
该 ResourceQuota 确保所有 CPU 和内存请求及限制的总和保持在定义的限制范围内。它还设置了在命名空间内可以请求的 PersistentVolumeClaims 存储的限制。
通过为资源限制设置合理的默认值,并使用 LimitRange 和 ResourceQuotas,您可以确保使用您的 Helm charts 的用户不会耗尽集群资源。您还可以帮助确保应用程序请求足够的最小资源以确保正常运行。
了解资源请求和限制后,让我们进入下一个话题——在 Helm charts 中处理密钥。
Helm charts 中的密钥处理
处理密钥是使用 Helm charts 时常见的关注点。考虑到 第三章 中的 WordPress 应用程序,使用 Helm 安装第一个应用程序,在此过程中,您需要提供密码以配置管理员用户。此密码默认不在 values.yaml 文件中提供,因为如果您忘记覆盖 password 值,应用程序将变得易受攻击。Chart 开发者应该养成不为密钥值(如密码)提供默认值的习惯,而应要求用户提供显式值。这可以通过利用 required 函数轻松实现。Helm 还可以使用 randAlphaNum 函数生成随机字符串。
注意
注意,randAlphaNum函数每次升级图表时都会生成一个新的随机字符串。因此,开发人员应该设计图表时预期用户会提供自己的密码或其他密钥,并使用required函数作为门槛,确保提供了一个值。
在使用原生 Kubernetes 资源存储秘密信息时,图表开发人员应确保将这些敏感资产保存在 Kubernetes Secret 中,而不是 ConfigMap 中。Secrets 和 ConfigMaps 类似,但 Secrets 是专门用于存储敏感数据的。由于秘密数据和非秘密数据存储在不同的对象中,集群管理员可以相应地设置 RBAC 策略,限制对秘密数据的访问,同时允许对非秘密数据的访问(RBAC 将在稍后的配置 RBAC 规则部分进一步说明)。
图表用户应该确保像凭证这样的秘密值以安全的方式提供。值通常通过--values标志提供,其中属性在values文件中配置。当处理非秘密值时,这是一种合适的方法,但在处理秘密时,应该谨慎使用此方法。用户应确保包含秘密的values文件没有被检查到 Git 仓库或其他公开位置,这样这些秘密就不会被暴露。用户避免暴露秘密的一种方法是利用--set标志从命令行传递秘密。这降低了凭证暴露的风险,但用户应该注意,这可能会在 bash 历史中暴露凭证。
用户避免暴露秘密的另一种方法是利用加密工具加密包含秘密的values文件。此方法仍然允许用户使用--values标志,并使values文件能够存储在远程位置,如 Git 仓库中。然后,只有拥有适当解密密钥的用户才能解密values文件,其他用户则保持加密状态,仅允许可信实体访问数据。用户可以简单地使用 GPG 加密values文件,或者使用一个专门的工具,如Secrets OPerationS(SOPS)。SOPS(github.com/mozilla/sops)是一个设计用来加密 YAML 或 JSON 文件中的值,但保留键未加密的工具。以下代码展示了一个来自 SOPS 加密文件的秘密键/值对:
password: ENC[AES256_GCM,data:xhdUx7DVUG8bitGnqjGvPMygpw==,iv:3LR9KcttchCvZNpRKqE5L cXRyWD1I00v2kEAIl1ttco=,tag:9HEwxhT9s1pxo9lg19wyNg==,type:str]
注意,password键是未加密的,但其值是加密的。这使得你可以轻松查看文件中包含的值的类型,而不会暴露其敏感值。
还有其他工具可以加密包含秘密的values文件。一些示例包括--set标志。
现在你已经理解了安全性在 Helm 图表开发中的作用,接下来让我们讨论如何在 Kubernetes 中应用基于角色的访问控制(RBAC),以为用户提供更高的安全性。
配置 RBAC 规则
在 Kubernetes 中,经过身份验证的用户执行操作的能力由一组 RBAC 策略来管理。如在第二章《准备 Kubernetes 和 Helm 环境》中介绍的,策略(即角色)可以与用户或服务账户关联,Kubernetes 在任何安装中都包含多个角色。自版本 1.6 起,Kubernetes 默认启用 RBAC。当在 Helm 使用的背景下考虑 Kubernetes RBAC 时,需要考虑两个因素:
-
安装 Helm 图表的用户
-
与运行工作负载的 pod 关联的服务账户
在大多数情况下,负责安装 Helm 图表的个人与 Kubernetes 用户相关联。然而,Helm 图表也可以通过其他方式安装,例如由具有关联服务账户的 Kubernetes 操作员安装。
默认情况下,用户和服务账户在 Kubernetes 集群中具有最小权限。通过使用角色(这些角色已被限定在特定命名空间内)或集群角色(授予集群级别的访问权限),可以授予额外的权限。然后,使用角色绑定或集群角色绑定将这些角色与用户或服务账户关联,具体取决于目标策略的类型。虽然 Kubernetes 包含几个内置角色,但可以创建 Role 或 ClusterRole,只授予应用程序所需的必要权限。由于大多数 Kubernetes 集群的典型用户无法在集群级别创建资源,我们将创建一个角色,应用于 Helm 图表部署所在的命名空间。
可以使用 kubectl create role 命令来创建一个 Kubernetes Role。或者,可以使用 YAML 定义来创建 Role 和 RoleBinding 资源。基本角色包含两个关键元素:
-
对 Kubernetes API 执行的操作类型(动词)
-
要目标的 Kubernetes 资源列表
作为示例,为了演示如何在 Kubernetes 中配置 RBAC,我们将配置一组 RBAC 规则,允许经过身份验证的用户查看命名空间中的 Pods:
-
首先,请确保启动您的 minikube 集群并为此练习创建一个新的命名空间:
$ minikube start $ kubectl create namespace chapter12 -
接下来,使用
kubectl命令行工具创建一个名为pod-viewer的新角色:$ kubectl create role pod-viewer --resource=pods --verb=get,list –n chapter12
创建了这个新角色后,需要将其与用户或服务账户关联。由于我们希望将角色与 Kubernetes 中运行的应用程序关联,因此我们将角色应用于服务账户。为了遵循最小权限原则,建议为应用程序创建一个唯一的服务账户(否则将使用默认服务账户)。这样可以确保同一命名空间中没有其他工作负载被意外地继承相同的权限。
-
通过运行以下命令创建一个名为
example的新服务账户:$ kubectl create sa example –n chapter12 -
最后,创建一个名为
pod-viewers的RoleBinding,并将其与example服务账户关联:$ kubectl create rolebinding pod-viewers --role=pod-viewer --serviceaccount=chapter12:example –n chapter12
创建了角色和角色绑定后,example 服务账户具有列出和获取 pods 的适当权限。为了验证这个假设,我们可以使用 kubectl auth can-i 命令:
$ kubectl auth can-i list pods --as=system:serviceaccount:chapter12:example –n chapter12
yes
--as 标志利用了 Kubernetes 中的用户模拟功能,允许你调试授权策略。
为了确认服务账户无法访问它不应访问的资源,例如列出 Deployments,你可以运行以下命令:
$ kubectl auth can-i list deployments --as=system:serviceaccount:chapter12:example –n chapter12
no
从 no(列出 Deployments)和 yes(列出 pods)的输出中可以看出,预期的策略已生效。该服务账户现在可以被 Helm chart 引用。或者,可以编写一个 Helm chart 来原生创建 ServiceAccount、Role 和 RoleBinding 资源,前提是安装该 chart 的用户拥有所需的适当权限。
如果使用得当,Kubernetes RBAC 有助于为 Helm chart 开发者提供必要的工具,以执行最小权限访问,保护用户和应用程序免受潜在错误或恶意行为的威胁。
为了清理本次练习,你可以使用 kubectl delete ns chapter12 删除你的命名空间,并使用 minikube stop 停止你的 minikube 集群。
接下来,让我们讨论如何访问安全的 Helm chart 仓库。
访问安全的 chart 仓库
Chart 仓库允许你发现 Helm charts 并将其安装到你的 Kubernetes 集群中。仓库在第八章,发布到 Helm Chart 仓库中进行了探讨。在那里,你学习了如何使用 HTTP 服务器(通过 GitHub Pages 演示)和 OCI 注册表(通过 GitHub 的容器注册表 ghcr.io 演示)提供 charts。
大多数 chart 仓库都可以供有兴趣的人访问并且是开放的。然而,chart 仓库和托管服务确实为与存储在仓库中的内容进行交互提供了额外的安全措施,包括以下内容:
-
身份验证
-
传输层安全性 (TLS)
基于 HTTP(S)的图表仓库支持基本认证和基于证书的认证。对于基本认证,可以在使用helm repo add命令添加仓库时,通过使用--username和--password标志提供用户名和密码。例如,如果您想访问一个通过基本认证保护的仓库,添加该仓库的命令将如下所示:
$ helm repo add <repo_name> <repo_url> --username <username> --password <password>
在某些情况下,您可能还需要在--username和--password之外使用--pass-credentials标志。回想一下,index.yaml文件包含了图表仓库中所有 Helm 图表的索引。在index.yaml文件中,有一个名为urls的属性字段,指向相关 Helm 图表的位置。通常,urls字段包含图表仓库中的相对路径,但在某些情况下,可以指定一个完全不同域名的位置。如果没有--pass-credentials标志,Helm 不会将您的用户名和密码传递给这些域名,这是 Helm 3.6.1 中实现的安全功能,用来防止您的信息被暴露。但是,如果您确实需要将凭证传递到另一个域以对其进行身份验证,您可以在使用helm repo add命令时提供--pass-credentials标志。
OCI 注册表也支持使用helm registry login命令进行基本认证。用户名通过--username标志提供,但有两种方式可以指定密码:
-
--password:将密码作为参数提供。这可能会在 bash 历史记录中以明文形式泄露密码。 -
--password-stdin:通过stdin提供密码。这允许您通过从stdin重定向密码来隐藏密码,避免其出现在 bash 历史记录中。
建议使用--password-stdin标志以避免密码暴露在 bash 历史记录中。通过这种方式,您可以使用以下命令对 OCI 注册表进行基本认证:
$ cat <password_file> | helm registry login <registry_host> --username <username> --password-stdin
虽然基本认证是最常用的方式,但基于证书的认证也是验证客户端身份的另一种方式。当前 Helm 并未提供 OCI 证书认证的标志,但对于传统的 Helm 仓库,helm repo add命令提供了--cert-file和--key-file标志,用于分别指定客户端证书和密钥。
在图表仓库本身启用基本认证和证书认证取决于所使用的仓库实现。例如,--basic-auth-user和--basic-auth-pass标志,可以在启动时配置基本认证的用户名和密码。它还提供--tls-ca-cert标志,用于配置证书颁发机构(CA)证书,以便进行证书认证。其他图表仓库实现可能会提供类似或额外的功能。
即使在启用身份验证的情况下,Helm 客户端与图表仓库之间发送的数据包也必须使用基于 TLS 的加密进行安全传输。虽然对于基于证书的身份验证,这一点是理所当然的,因为它本身就使用 TLS,但使用基本身份验证的仓库仍然可以通过加密网络流量来获得安全性。为图表仓库配置 TLS 取决于所使用的仓库实现,但对于 ChartMuseum,可以使用 --tls-cert 和 --tls-key 标志来提供证书链和密钥文件。更常见的 Web 服务器,如 NGINX,通常需要一个配置文件,提供证书和密钥文件在服务器上的位置。像 GitHub Pages 这样的服务已经配置了 TLS。
到目前为止,我们使用的每个 Helm 仓库都使用了由公开的 CA 签发的证书,这些证书存储在你的 Web 浏览器和操作系统中。因此,我们不需要特别去信任它们的证书。然而,许多大型组织有自己的 CA,用于为图表仓库生成证书。由于该证书可能不是来自公开的 CA,Helm CLI 可能无法信任该证书,从而导致以下错误:
Error: looks like "<repo_url>" is not a valid chart repository or cannot be reached: Get <repo_url>/index.yaml: x509: certificate signed by unknown authority
要使 Helm CLI 信任图表仓库的证书,可以将 CA 证书或包含证书链的 CA 包添加到操作系统的信任存储中。或者,对于 HTTPS 图表仓库,可以在 helm repo add 命令中添加 --ca-file 标志。
最后,根据图表仓库的配置方式,可以获取额外的指标,以执行请求级别的审计和日志记录,确定谁尝试访问该仓库。
通过使用身份验证和 TLS,可以实现额外的功能,以增强 Helm 仓库的安全性。
概述
本章中,你学习了与 Helm 使用相关的各种安全主题。首先,你学习了如何验证 Helm 发布和 Helm 图表的数据来源和完整性。接着,你学习了 Helm 图表的安全性,以及图表开发者如何利用安全最佳实践来编写稳定且安全的 Helm 图表。然后,我们重点介绍了如何使用 RBAC 创建基于最小权限原则的环境,最后,我们讨论了如何使用身份验证和 TLS 来保护图表仓库的安全。现在,通过应用这些概念,你已经具备了创建安全 Helm 架构和工作环境的能力。
感谢阅读 使用 Helm 管理 Kubernetes 资源!我们希望本书能帮助你在使用 Helm 在 Kubernetes 生态系统中工作时充满信心和高效。
进一步阅读
要了解更多本章所涵盖的主题,请查阅以下资源:
-
若要了解有关 Helm chart 上下文中数据来源和完整性的更多信息,请访问
helm.sh/docs/topics/provenance/。 -
若要了解更多关于 Kubernetes RBAC 的信息,请查看 Kubernetes 文档中的Using RBAC Authorization部分:
kubernetes.io/docs/reference/access-authn-authz/rbac/。 -
查看 Helm 文档中的 chart 仓库指南,了解更多关于 chart 仓库的信息:
helm.sh/docs/topics/chart_repository/。 -
最后,访问 Helm 文档中的Registries页面,了解更多关于 OCI 支持的信息:
helm.sh/docs/topics/registries/。
问题
回答以下问题以测试你对本章节的理解:
-
数据来源和数据完整性是什么?数据来源和数据完整性有什么区别?
-
用户可以运行哪些命令来验证 Helm chart 的数据来源和完整性?需要哪些文件才能实现这个过程?
-
使用和维护安全容器镜像时需要考虑哪些因素?
-
为什么在 Helm chart 中利用资源请求和限制很重要?还有哪些其他 Kubernetes 资源可以用来配置请求和限制?
-
最小权限原则是什么?哪些 Kubernetes 资源允许你配置授权并实现最小权限?
-
你可以使用哪些标志来验证 HTTP(S) 仓库的身份?
-
你可以使用哪些标志来验证 OCI 注册表的身份?


浙公网安备 33010602011771号