Helm-学习指南-全-
Helm 学习指南(全)
原文:
zh.annas-archive.org/md5/61a626440d92a1f626ff89298c907b52译者:飞龙
前言
Helm 是 Kubernetes 的软件包管理器,这是流行的开源容器管理平台。
软件包管理器使得使用这些平台变得更加容易。要使用类似 Kubernetes 这样的平台,你需要在其上运行软件,而其中很多软件将是现成的或共享的。像 Helm 这样的软件包管理器使你能够快速安装和开始使用软件,而不需要弄清楚如何在平台上运行或使其良好运行,因为它已经以易于使用的方式打包好了。
如果你有想要与他人分享的软件,软件包管理器可以轻松实现。只有在平台上运行的软件多样性较大时,平台才更有用;开源项目和公司都希望能够轻松安装其软件,而 Helm 使这对 Kubernetes 成为可能。
软件包管理器不仅用于共享和消费其他人的软件。它们通常是其他系统(如 DevOps 工具)的一部分,也用作构建块。
几乎每个现代平台都有一个软件包管理器。操作系统、编程语言和云平台都有某种形式的软件包管理器。
在本书中,你将学习到 Helm,它为 Kubernetes 提供现代化的软件包管理,并且学习到你可以使用它的包(称为 charts)。你将学习如何使用 Helm,如何创建软件包,以及如何与其他平台共享这些软件包。
谁应该阅读这本书
有几种情况下你会发现这本书很有用。
如果你是 Kubernetes 的新手或者想学习如何安装现成的应用程序,本书将帮助你通过 Helm 学习如何做到这一点。通过 Helm 安装应用程序比手动学习如何在 Kubernetes 上安装要容易和快速得多。
如果你在一家希望以易于消费的方式向 Kubernetes 用户分发应用程序的公司(或项目)工作,本书将教你如何通过 Helm 实现这一点。能够快速安装你的应用程序可以使开始更容易,而 Helm 可以帮助你实现这一点。
这本书还适合那些希望在 DevOps 工具链中学习如何使用 Kubernetes 软件包管理的专业人士。Helm 提供了强大和高级功能,可以作为其他自动化的构建块使用。这些功能已被用于将大型复杂应用程序部署到 Kubernetes 上,本书将教你如何利用这些功能。
为什么我们写了这本书
我们作为 Helm 的维护者,决定写一本书来帮助那些对此有疑问的人。我们不只是想提供通常在文档中找到的技术细节;我们想要提供 Helm 的作用及其背后原因的背景和见解。
导航本书
前三章将介绍 Helm,并展示如何使用 Helm 客户端。这始于第一章,介绍了 Helm 在云原生生态系统中的位置及其架构概述。第二章 和 第三章 讨论了使用 Helm 客户端,从安装 Helm 开始,逐步进展到高级用法。
第四章 到 第六章 讨论了创建 Helm 包。从创建包开始(第四章),进入学习模板语法(第五章),并最终涉及高级功能(第六章)。如果您想为 Helm 创建包,这些章节适合您。
分享包,包括它们各自的发布版本,在第七章中有所介绍。如果您正在向他人分发软件或在 DevOps 过程中共享软件,分享是很重要的。
Helm 可以进行扩展,这在第八章中有所介绍。有机会定制 Helm,而无需分叉或向 Helm 贡献功能。
提供了两个附录,提供参考材料。附录 A 概述了当前和旧版包之间的差异,而 附录 B 则涵盖了用于共享包的存储库 API。
本书使用的约定
本书使用以下排版约定:
斜体
表示新术语、网址、电子邮件地址、文件名和文件扩展名。
等宽字体
用于程序清单,以及段落内用于引用程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
等宽粗体
显示用户应直接输入的命令或其他文本。
等宽斜体
显示应由用户提供的值或上下文确定的值替换的文本。
提示
此元素表示提示或建议。
注释
此元素表示一般注释。
警告
此元素表示警告或注意。
使用代码示例
补充材料(代码示例、练习等)可从https://github.com/masterminds/learning-helm下载。
如果您在使用代码示例时有技术问题或困难,请发送电子邮件至bookquestions@oreilly.com。
本书旨在帮助您完成工作。一般来说,如果本书提供示例代码,则可以在您的程序和文档中使用它。除非您复制了代码的大部分,否则无需联系我们以获得许可。例如,编写一个使用本书多个代码片段的程序不需要许可。出售或分发 O’Reilly 图书的示例代码则需要许可。引用本书并引用示例代码来回答问题无需许可。将本书中大量示例代码合并到您产品的文档中则需要许可。
我们欣赏,但通常不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“《Learning Helm》 由 Matt Butcher、Matt Farina 和 Josh Dolitsky(O’Reilly)撰写。版权所有 2021 年 Matt Butcher,Innovating Tomorrow 和 Blood Orange,978-1-492-08365-8。”
如果您觉得您对代码示例的使用超出了公平使用范围或以上给予的许可,请随时通过permissions@oreilly.com联系我们。
O’Reilly 在线学习
注意
超过 40 年来,O’Reilly Media为公司提供技术和商业培训、知识和洞察力,帮助它们取得成功。
我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专业知识。O’Reilly 的在线学习平台为您提供按需访问的实时培训课程、深入学习路径、交互式编码环境以及来自 O’Reilly 和 200 多个其他出版商的广泛的文本和视频集合。有关更多信息,请访问http://oreilly.com。
如何联系我们
请将有关本书的评论和问题发送至出版社:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938(在美国或加拿大)
-
707-829-0515(国际或本地)
-
707-829-0104(传真)
我们为本书提供了一个网页,列出勘误、示例和任何额外信息。您可以访问https://oreil.ly/learning-helm获取这个页面。
发送电子邮件至bookquestions@oreilly.com,以评论或询问有关本书的技术问题。
有关我们的图书和课程的新闻和信息,请访问http://oreilly.com。
在 Facebook 上找到我们:http://facebook.com/oreilly
在 Twitter 上关注我们:http://twitter.com/oreillymedia
在 YouTube 上观看我们:http://youtube.com/oreillymedia
致谢
本书受到我们的官方技术审阅人员关注:Taylor Thomas、Jonathan Johnson 和 Michael Hausenblas。
我们要特别感谢O’Reilly的每一位帮助完成这个项目的人员,特别是 John Devins 和 Jeff Bleiel。写作过程令人愉悦。
Helm 生态系统由来自全球各地的无数贡献者共同创造。个人、非政府组织和企业合作,打造出一项满足广泛需求的技术。从创建图表到贡献修复程序,再到帮助他人学习 Helm,个人们都投入了时间和精力来改善社区和代码。我们深表感激他们的工作。
最重要的是,我们要感谢我们的妻子和孩子在整个过程中给予我们的耐心和爱。
第一章:介绍 Helm
Helm 是 Kubernetes 的包管理器。这是 Helm 开发者自 Git 仓库的第一次提交以来就一直在描述的方式。而这句话也是本章的主题。
在本章中,我们将从概念上看云原生生态系统,其中 Kubernetes 是一个关键技术。我们将重新审视 Kubernetes 提供的功能,为描述 Helm 做好准备。
接下来,我们将探讨 Helm 旨在解决的问题。在本节中,我们将看看包管理的概念以及为什么我们模仿 Helm 的方式进行建模。我们还将探讨将软件包安装到像 Kubernetes 这样的集群管理工具中的一些独特方面。
最后,我们将通过高层次的方式来看 Helm 的架构,重点介绍图表、模板和发布的概念。通过本章的学习,你将理解 Helm 如何融入到更广泛的工具生态系统中,你将熟悉本书中将要使用的术语和概念。
云原生生态系统
云技术的出现显然改变了行业对硬件、系统管理、物理网络等的看法。虚拟机取代了物理服务器,存储服务取代了硬盘的讨论,自动化工具日益突出。这或许是行业在概念化云时的早期变化。但随着这种新方法的优劣逐渐清晰,设计应用程序和服务的实践也开始转变。
开发者和运维人员开始质疑构建大型单一二进制应用程序的实践,这些应用程序在强大的硬件上执行。他们意识到在不同应用程序之间共享数据并保持数据完整性的困难。分布式锁定、存储和缓存成为主流问题,而不是学术兴趣点。大型软件包被分解为更小的离散可执行文件。正如 Kubernetes 的创始人 Brendan Burns 经常所说,“分布式计算从高级话题变成了计算机科学 101。”
“云原生”这一术语捕捉到了我们对云架构视角的这种认知转变。当我们围绕云的能力和约束设计我们的系统时,我们正在设计云原生系统。
容器和微服务
云原生计算的核心理念是:更倾向于小型独立的单一服务,而不是做一切的大型单体服务。不再编写一个处理从生成用户界面到处理任务队列再到与数据库和缓存交互的单一大型应用程序。云原生的方法是编写一系列较小、相对特定目的的服务,然后将这些服务组合在一起以实现更高层次的目的。在这种模型中,一个服务可能是关系数据库的唯一用户。希望访问数据的服务将通过(通常是)表述状态转移(REST)API 联系该服务。然后,这些其他服务将使用基于 JavaScript 对象表示法(JSON)的 HTTP 查询和更新数据。
这种分解允许开发人员隐藏低级实现,而是提供一组特定于更广泛应用程序业务逻辑的功能。
微服务
曾经,一个应用程序由一个执行所有工作的单个可执行程序组成,云原生应用程序是分布式应用程序。虽然单独的程序分别负责一个或两个离散任务,但这些程序共同形成一个单一的逻辑应用程序。
有了所有这些理论,一个简单的例子可能更好地解释这是如何工作的。想象一个电子商务网站。我们可以认为一起组成这种网站的几个任务。有产品目录,用户账户和购物车,处理货币交易安全过程的支付处理器,以及顾客查看商品并选择购买的前端。还有一个管理界面,店主在其中管理库存和完成订单。
历史上,这样的应用程序曾经被构建为一个单一的程序。负责每个工作单元的代码都被编译到一个大型可执行文件中,然后通常在单个大型硬件上运行。
但是,云原生方法是将这个电子商务应用程序分解为多个部分。一个处理支付交易。另一个跟踪产品目录。再一个提供管理界面,等等。然后,这些服务通过网络使用明确定义的 REST API 彼此通信。
将应用程序分解为最小的组成部分,并且每个部分都是一个程序,这是微服务架构的极端情况。微服务站在单体应用程序的相反极端,负责处理整体应用程序处理的一个小部分。
微服务概念对云原生计算的演变产生了巨大影响。在容器计算的出现中,这一点表现得尤为明显。
容器
将容器与虚拟机进行比较和对比是很常见的。虚拟机在主机机器上的隔离环境中运行整个操作系统。相比之下,容器有自己的文件系统,但在主机的操作系统内核中执行。
但是有一种第二种概念化容器的方式——这种方式可能对当前的讨论更有益。正如其名称所示,容器提供了一种有用的方式,可以将单个程序的运行时环境打包起来,以便在将其从一个主机移动到另一个主机时,可以保证可执行文件的所有依赖项都得到满足。
这是一种更为哲学的方法,也许是因为它对容器施加了一些非技术性的限制。例如,一个容器可以打包十几个不同的程序,并同时执行它们。但是容器,至少按照 Docker 的设计初衷,是作为一个顶层程序的载体。
注意
当我们在这里讨论程序时,我们实际上在思考比“一个二进制文件”更高层次的抽象。大多数 Docker 容器至少有几个可执行文件,它们只是为了辅助主程序存在。但这些可执行文件是辅助于容器的主要功能的。例如,一个 Web 服务器可能需要一些其他本地实用工具来启动或执行低级任务(例如 Apache 有模块工具),但主要程序还是 Web 服务器本身。
容器和微服务从设计上来说是完美匹配的。小型独立程序可以打包,以及它们所有的依赖项,进入修长的容器中。并且这些容器可以从一个主机移动到另一个主机。在执行容器时,主机无需拥有执行程序所需的所有工具,因为所有这些工具都打包在容器内。主机只需要能够运行容器。
例如,如果一个程序是在 Python 3 中构建的,主机不需要安装 Python,配置它,然后安装程序所需的所有库。所有这些都打包在容器中。当主机执行容器时,正确版本的 Python 3 和每个所需的库已经存储在容器中。
进一步说,主机可以自由地执行具有竞争需求的容器。一个容器化的 Python 2 程序可以在与容器化的 Python 3 需求相同的主机上运行,主机管理员无需进行任何特殊工作来配置这些竞争需求!
这些例子说明了云原生生态系统的一个特性:管理员、运维人员和可靠性工程师(SREs)不再负责管理程序依赖关系。相反,他们可以自由地专注于更高级别的资源分配。运维人员不再为不同服务器上运行的 Python、Ruby 和 Node 的版本而烦恼,而是可以专注于网络、存储和 CPU 资源是否正确分配给这些容器化工作负载。
有时在完全隔离的环境中运行程序是有用的。但更常见的情况是,我们希望将容器的某些方面暴露给外部世界。我们希望它可以访问存储。我们希望允许它响应网络连接。我们希望根据我们目前的需求向容器中注入一些配置小贴士。所有这些任务(还有更多任务)由容器运行时提供。当容器声明其具有一个在端口 8080 上内部侦听的服务时,容器运行时可能会在主机端口 8000 上授予其访问权限。因此,当主机在端口 8000 上收到网络请求时,容器将其视为对其端口 8080 的请求。同样,主机可以将文件系统挂载到容器中,或在容器内设置特定的环境变量。通过这种方式,容器可以参与其周围更广泛的环境——包括不仅限于同一主机上的其他容器,还包括本地网络甚至互联网上的远程服务。
容器镜像和注册表
容器技术本身就是一个复杂而引人入胜的领域。但就我们的目的而言,在我们继续到云原生堆栈的下一层之前,我们只需要理解一些关于容器如何工作的更多信息。
正如我们在前一节中讨论的那样,容器是一个程序及其依赖项和环境的整体。这整个内容可以打包成一个称为容器镜像(通常简称为镜像)的可移植表示。镜像不是打包成一个大型二进制文件;而是打包成离散的层,每个层都有其自己的唯一标识符。当镜像被移动时,它们作为一组层移动,这提供了巨大的优势。如果一个主机有一个包含五层的镜像,而另一个主机需要相同的镜像,它只需获取它尚未具备的层。因此,如果它已经有了五层中的两层,它只需获取三层即可重建整个容器。
有一个关键的技术组件提供了移动容器镜像的能力。镜像注册表是一种专门的存储技术,用于存储容器,使它们对主机可用。主机可以将容器镜像推送到注册表,将层传输到注册表。然后另一个主机可以从注册表拉取镜像到主机的环境中,之后主机可以执行容器。
注册表管理这些层。当一个主机请求一个镜像时,注册表会告诉主机组成该镜像的哪些层。然后主机可以确定缺少的层(如果有的话),并从注册表中仅下载那些层。
注册表使用最多三个信息来标识特定的镜像:
名称
一个镜像的名称可以是简单的也可以是复杂的,这取决于存储镜像的注册表:nginx,servers/nginx,或者 example.com/servers/nginx。
标签
标签通常指安装的软件版本(v1.2.3),尽管标签实际上只是任意的字符串。标签 latest 和 stable 通常用于表示“最新版本”和“最新的生产就绪版本”,分别。
摘要
有时候拉取一个非常特定版本的镜像很重要。由于标签是可变的,不能保证在任何给定时间标签指向确切的软件版本。因此,注册表支持通过摘要来获取镜像,这是镜像层信息的 SHA-256 或 SHA-512 摘要。
在本书中,我们将看到使用前述三个信息引用的镜像。组合这些信息的规范格式是 name:tag@digest,其中只有 name 是必需的。因此,example.com/servers/nginx:latest 表示“给我命名为 example.com/servers/nginx 的镜像的标签 latest。”以及
example.com/my/app@sha256:
a428de44a9059feee59237a5881c2d2cffa93757d99026156e4ea544577ab7f3
告诉“给我example.com/my/app,并提供此处的确切摘要。”
虽然关于镜像和容器还有很多要学习的内容,但我们现在已经有足够的知识来继续下一个重要的主题:调度器。在那一部分,我们将了解 Kubernetes。
计划和 Kubernetes
在前一节中,我们看到容器封装了单独的程序及其所需的环境。容器可以在工作站上本地执行,也可以在服务器上远程执行。
随着开发人员开始将他们的应用程序打包成容器,并且操作人员开始使用容器作为部署的工件,出现了一系列新的问题。我们如何最好地执行大量容器?我们如何最好地支持需要共同工作的大量容器的微服务架构?我们如何明智地共享对像网络附加存储、负载均衡器和网关等的访问?我们如何管理向大量容器注入配置信息?也许最重要的是,我们如何管理内存、CPU、网络带宽和存储空间等资源?
更进一步,人们开始询问(基于他们对虚拟机的经验),如何在多个主机上管理分布式容器,如何在保证资源的合理使用的同时均匀分布负载?或者更简单地说,我们如何在运行尽可能少的主机的同时运行我们需要的尽可能多的容器?
2015 年,时机成熟:Docker 容器正在企业中取得进展。并且有一个明显的需求,即需要一种工具来管理跨主机的容器调度和资源管理。多种技术进入市场:Mesos 推出了 Marathon;Docker 创建了 Swarm;Hashicorp 发布了 Nomad;Google 则为其内部 Borg 平台创建了一个开源版本,并将此技术命名为 Kubernetes(希腊语中船长的意思)。
所有这些项目都提供了一个集群化容器管理系统的实现,可以调度容器并为托管复杂的微服务式分布式应用程序连接它们。
每个调度器都有其优点和缺点。但是 Kubernetes 引入了两个概念使其脱颖而出:声明性基础设施 和 协调循环。
声明性基础设施
考虑部署容器的情况。一个人可能会以如下方式处理部署容器的过程:我创建容器。我打开一个端口让它监听,并在文件系统的特定位置上附加一些存储。然后我等待所有初始化完成。然后我测试它以查看容器是否准备就绪。然后我标记它为可用。
在这种方法中,我们通过专注于设置容器的过程来过程化思考。但 Kubernetes 的设计是我们声明式地思考。我们告诉调度器(Kubernetes)我们的期望状态是什么,Kubernetes 负责将该声明转换为其内部的操作流程。
在 Kubernetes 上安装容器更像是说:“我希望这个容器在这个端口上运行,使用这么多的 CPU 并且在文件系统的这个位置上挂载一些存储。” Kubernetes 在幕后工作,根据我们声明的需求连接所有内容。
协调循环
Kubernetes 如何在幕后处理所有这些?当我们从过程的角度来看待事物时,那里有一定的操作顺序。Kubernetes 如何知道顺序?这就是 协调循环 的概念发挥作用的地方。
在协调循环中,调度器会说:“这是用户的期望状态。这是当前的状态。它们不同,所以我将采取步骤来调和它们。” 用户需要容器的存储。目前没有附加存储。因此 Kubernetes 创建了一个存储单元并将其附加到容器上。容器需要一个公共网络地址。目前不存在。因此将一个新的地址附加到容器上。Kubernetes 中的不同子系统会努力履行其用户期望状态的各个部分。
最终,Kubernetes 将会成功创建用户期望的环境,或者得出无法实现用户愿望的结论。与此同时,用户在观察 Kubernetes 集群并等待其成功或标记安装失败时扮演被动角色。
从容器到 pod、服务、部署等等。
虽然简洁,但上面的示例有些误导性。Kubernetes 并不一定把容器作为工作的单元。相反,Kubernetes 引入了一个更高级的抽象称为 pod。一个 pod 是一个抽象的信封,描述了一个独立的工作单元。一个 pod 描述的不仅仅是一个容器,还包括一个或多个容器(以及它们的配置和需求),这些容器一起执行一个工作单元:
apiVersion: v1 
kind: Pod
metadata:
name: example-pod
spec:
containers: 
- image: "nginx:latest"
name: example-nginx
前两行定义了 Kubernetes 类型(v1 Pod)。
一个 pod 可以有一个或多个容器。
大多数情况下,一个 pod 只有一个容器。但有时它们会有一些在主容器上线之前进行一些预配置的容器,然后退出。这些被称为 init containers。另外有时候还有运行在主容器旁边并提供辅助服务的容器。这些被称为 sidecar containers。它们都被视为同一个 pod 的一部分。
注意
在前面的代码中,我们编写了一个 Kubernetes Pod 资源的定义。当这些定义表达为 YAML 或 JSON 时,被称为 manifests。一个 manifest 可以包含一个或多个 Kubernetes 资源(也称为 对象 或 资源定义)。每个资源与 Kubernetes 的一个 类型 相关联,比如 Pod 或 Deployment。在本书中,我们通常使用 资源,因为 对象 这个词被重载了:YAML 将 对象 定义为一个命名的键/值结构。
Pod 描述了容器或容器需要的配置(如网络端口或文件系统挂载点)。在 Kubernetes 中,配置信息可以存储在 ConfigMaps 中,对于敏感信息,可以存储在 Secrets 中。Pod 的定义可能会将这些 ConfigMap 和 Secret 关联到每个容器内的环境变量或文件中。当 Kubernetes 看到这些关系时,它将尝试按照 Pod 的定义附加和配置配置数据:
apiVersion: v1 
kind: ConfigMap
metadata:
name: configuration-data
data: 
backgroundColor: blue
title: Learning Helm
在这种情况下,我们声明了一个 v1 ConfigMap 对象。
在 data 内部,我们声明了一些任意的名称/值对。
Secret 在结构上类似于 ConfigMap,不同之处在于 data 部分的值必须进行 Base64 编码。
Pod 通过 volumes 与配置对象(如 ConfigMap 或 Secret)关联。在本例中,我们采用了上面的 Pod 示例,并附加了上述 Secret:
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
volumes: 
- name: my-configuration
configMap:
name: configuration-data 
containers:
- image: "nginx:latest"
name: example-nginx
env: 
- name: BACKGROUND_COLOR 
valueFrom:
configMapKeyRef:
name: configuration-data 
key: backgroundColor 
volumes部分告诉 Kubernetes 此 Pod 需要哪些存储源。
名称configuration-data是我们在前面示例中创建的ConfigMap的名称。
env部分将环境变量注入到容器中。
环境变量在容器内将被命名为BACKGROUND_COLOR。
这是它将使用的ConfigMap的名称。如果我们希望将其作为文件系统卷使用,此映射必须在volumes中。
这是ConfigMap的data部分内键的名称。
一个Pod是可运行工作单元的“原始”描述,其中包含容器。但是 Kubernetes 引入了更高级别的概念。
考虑一个 Web 应用程序。我们可能不希望只运行一个此 Web 应用程序的实例。如果我们只运行一个实例,如果它失败了,我们的站点将会宕机。如果我们想升级它,我们将不得不想办法在不关闭整个站点的情况下进行升级。因此,Kubernetes 引入了Deployment的概念。Deployment描述了一个应用程序作为一组相同的Pod。Deployment由一些顶层配置数据组成,以及用于构建副本Pod的模板。
使用Deployment,我们可以告诉 Kubernetes 创建我们的应用程序一个单独的Pod。然后我们可以将其扩展到五个Pod,再缩减到三个Pod。我们可以附加HorizontalPodAutoscaler(另一种 Kubernetes 类型),并配置它根据资源使用情况扩展我们的Pod。当我们升级应用程序时,Deployment可以采用各种策略逐步升级单个Pod,而不会导致整个应用程序宕机:
apiVersion: apps/v1 
kind: Deployment
metadata:
name: example-deployment
labels:
app: my-deployment
spec:
replicas: 3 
selector:
matchLabels:
app: my-deployment
template: 
metadata:
labels:
app: my-deployment
spec:
containers:
- image: "nginx:latest"
name: example-nginx
这是一个apps/v1 Deployment对象。
在规范内部,我们要求三个副本的以下template。
模板指定每个副本Pod的外观应该如何。
当涉及将 Kubernetes 应用程序附加到网络上的其他内容时,Kubernetes 提供了Service定义。Service是一种持久的网络资源(类似于静态 IP),即使与其连接的Pod或Pods消失,它也会持续存在。这样,Kubernetes Pod可以来去自如,而网络层仍然可以继续将流量路由到相同的Service端点。虽然Service是一个抽象的 Kubernetes 概念,但在幕后它可以被实现为从路由规则到外部负载均衡器的任何内容:
apiVersion: v1 
kind: Service
metadata:
name: example-service
spec:
selector:
app: my-deployment 
ports:
- protocol: TCP 
port: 80
targetPort: 8080
类型是v1 Service。
这个Service将路由到具有app: my-deployment标签的Pod。
TCP 流量到这个 Service 的 80 端口将路由到与 app: my-deployment 标签匹配的 pods 的 8080 端口。
所描述的 Service 将流量路由到我们之前创建的 Deployment。
我们已经介绍了许多 Kubernetes 类型中的一些。还有很多我们可以覆盖的类型,但迄今为止使用最频繁的类型是 Pod, Deployment, ConfigMap, Secret 和 Service。在下一章中,我们将更直接地开始使用这些概念。但是现在,凭借一些通用信息,我们可以介绍 Helm。
Helm 目标
到目前为止,我们专注于更广泛的云原生生态系统及其在其中的角色。在本节中,我们将把焦点转向 Helm。
在前一节中,我们看到了几个不同的 Kubernetes 资源:一个 Pod,一个 ConfigMap,一个 Deployment 和一个 Service。每一个资源都扮演着不同的角色。但是一个 应用程序 通常需要其中多个。
例如,WordPress CMS 系统可以在 Kubernetes 内部运行。但通常需要至少一个 Deployment(用于 WordPress 服务器),一个 ConfigMap 用于配置,可能还需要一个 Secret(用于保存密码),几个 Service 对象,一个运行数据库的 StatefulSet,以及几个基于角色的访问控制(RBAC)规则。已经,一个基本的 WordPress 站点的 Kubernetes 描述将涵盖数千行 YAML。Helm 的核心理念之一是,所有这些对象可以打包在一起进行安装、更新和删除。
当我们编写 Helm 时,我们有三个主要目标:
-
让从“零到 Kubernetes”变得更容易
-
提供像操作系统那样的软件包管理系统
-
强调安全性和可配置性,用于部署应用到 Kubernetes
我们将审视这三个目标,并看看 Helm 在生命周期管理故事中的参与情况。
从零到 Kubernetes
Helm 项目始于 2015 年,比首届 KubeCon 会议早几个月。设置 Kubernetes 很困难,通常需要新用户编译 Kubernetes 源代码,然后使用一些 shell 脚本来运行 Kubernetes。一旦集群启动,新用户就被期望从头开始编写 YAML(正如我们在之前的章节中所做的)。基本示例很少,没有生产就绪的示例。
我们希望颠覆学习周期:不再要求用户从基本示例开始构建他们自己的应用程序,而是提供用户现成的生产就绪示例。用户可以安装这些示例,看到它们运行,然后学习 Kubernetes 如何工作。
这就是,直到今天,我们与 Helm 的首要任务:使其更容易与 Kubernetes 配合使用。在我们看来,一个新的 Helm 用户在现有的 Kubernetes 集群上应该能够在下载到安装应用程序不到五分钟内完成。
但 Helm 不仅仅是一个学习工具。它是一个软件包管理器。
软件包管理
Kubernetes 就像一个操作系统。在其基础上,操作系统为执行程序提供了环境。它提供了存储、执行和监视程序生命周期所需的工具。
它不是执行程序,而是执行容器。但与操作系统类似,它提供了存储、执行和监视这些容器所需的工具。
大多数操作系统都有一个软件包管理器。软件包管理器的工作是简化操作系统上程序的查找、安装、升级和删除。软件包管理器提供了打包程序成可安装应用程序的语义,并提供了存储和检索软件包以及安装和管理它们的方案。
当我们将 Kubernetes 视为一个操作系统时,我们很快意识到需要一个 Kubernetes 软件包管理器。从 Helm 源代码库的第一个提交开始,我们始终将软件包管理隐喻应用于 Helm:
-
Helm 提供了包仓库和搜索功能,用于查找可用的 Kubernetes 应用程序。
-
Helm 具有熟悉的安装、升级和删除命令。
-
Helm 定义了一种在安装前配置软件包的方法。
-
此外,Helm 还有工具用于查看已安装的内容及其配置。
最初,我们将 Helm 建模为 Homebrew(macOS 的软件包管理器)和 Apt(Debian 的软件包管理器)。但随着 Helm 的成熟,我们已经努力从尽可能多的不同软件包管理器中学习。
典型操作系统和 Kubernetes 之间存在一些差异。其中之一是,Kubernetes 支持运行多个相同应用程序的实例。而我可能只在工作站上安装 MariaDB 数据库一次,Kubernetes 集群可以运行数十、数百甚至数千个 MariaDB 安装实例,每个实例可能有不同的配置或不同的版本。
另一个在典型操作系统中罕见但在 Kubernetes 中很核心的概念是命名空间。在 Kubernetes 中,命名空间是一种任意的分组机制,用于定义命名空间内部和外部的事物之间的边界。有许多不同的方式可以通过命名空间组织资源,但通常它们被用作附着安全性的固定装置。例如,可能只有特定用户可以访问命名空间内的资源。
这些只是 Kubernetes 与传统操作系统不同的几种方式。这些以及其他差异在 Helm 的设计中提出了挑战。我们不得不建立 Helm 以利用这些差异,但又不放弃我们的软件包管理隐喻。
例如,Helm 安装命令不仅需要包的名称,还需要用户提供的名称,用于引用该包的安装版本。在下一章中,我们将看到这方面的例子。
同样,Helm 中的操作是命名空间敏感的。可以将同一个应用程序安装到两个不同的命名空间中,并且 Helm 提供工具来管理这些应用程序的不同实例。
总之,Helm 始终牢固地位于工具包管理类别中。
安全性、可重用性和可配置性
Helm 的第三个目标是专注于管理集群中应用程序的三个“必须具备的”功能:
-
安全性
-
可重用性
-
可配置性
简而言之,我们希望 Helm 对这些原则有足够的认识,以便 Helm 用户可以对他们使用的包有信心。用户应能够验证一个包来自可信任的来源(且未被篡改),重复使用同一个包多次,以及配置包以适应他们的需求。
Helm 的开发者可以直接控制前两个设计目标,而这个是独一无二的:Helm 只能为包作者提供合适的工具,并希望这些创作者选择实现这三个“必须具备的”目标。
安全性
安全性是一个广泛的类别。在这个上下文中,我们指的是当用户检查一个包时,用户有能力验证关于包的某些事项:
-
包来自可信任的来源。
-
拉取包的网络连接是安全的。
-
包没有被篡改。
-
用户可以轻松检查包,以了解其功能。
-
用户可以查看包的配置,以及了解不同的输入如何影响包的输出。
在本书中,特别是在第六章,我们将更详细地讨论安全性。但这五个功能是我们认为 Helm 已经提供的内容。
Helm 提供了来源功能,用于建立有关包的来源、作者和完整性的验证。Helm 支持安全套接字层/传输层安全性(SSL/TLS),用于安全地通过网络发送数据。并且 Helm 提供了干预运行、模板和清理命令,以检查包及其可能的排列组合。
可重用性
包管理的一个优点是可以重复和可预测地安装相同的内容。稍微扩展一下,使用 Helm,我们甚至可能希望将相同的内容(重复和可预测地)安装到同一个集群或同一个命名空间中。
Helm 图表是可重复使用的关键。一个图表提供了生成相同 Kubernetes 清单的模式。但图表还允许用户提供额外的配置(我们将在下一章讨论)。因此,Helm 提供了存储配置的模式,使得可以重复执行图表加其配置的组合。
这样,Helm 鼓励 Kubernetes 用户将他们的 YAML 打包成图表,以便可以重复使用这些描述。
在 Linux 世界中,每个 Linux 发行版都有自己的包管理器和仓库。而在 Kubernetes 世界中不是这样。Helm 被设计为所有 Kubernetes 发行版可以共享相同的包管理器,以及(极少数例外情况除外)相同的包。当两个不同的 Kubernetes 发行版之间存在差异时,可以使用模板(在第五章中详细讨论)与配置来适应这些差异。
可配置性
Helm 提供了将 Helm 图表与一些额外配置结合的模式。例如,我可以使用 Helm 安装一个网站,并在安装时设置该网站的名称。Helm 提供了在安装和升级过程中重新配置包的工具。但需要注意一点。
Helm 是一个包管理器。另一类软件处理配置管理。这类软件,以 Puppet、Ansible 和 Chef 为代表,专注于如何为其主机环境特定配置的给定软件(通常打包)。其责任是管理随时间变化的配置。
Helm 并非设计成一个配置管理工具,尽管包管理和配置管理之间至少存在一定的重叠。
包管理通常限制于实施三个动作:安装、升级和删除。配置管理则是一个更高层次的概念,专注于长期管理一个或多个应用程序。有时候这被称为“第二天运维”。
虽然 Helm 最初并非旨在成为配置管理工具,但有时会被用作此类工具。组织依赖于 Helm 不仅仅是为了安装、升级和删除,还用于跟踪时间变化、跟踪配置,并确定整个应用程序是否正在运行。Helm 可以在这方面进行扩展,但如果你需要强大的配置管理解决方案,可能需要利用 Helm 生态系统中的其他工具。像 Helmfile、Flux 和 Reckoner 等工具填补了更大配置管理故事中的细节。
注意
Helm 社区创建了大量与 Helm 兼容或增强 Helm 的工具。Helm 项目在官方文档中维护了这些工具的列表。
你将在 Helm 图表中注意到的一个常见主题是,配置选项经常设置成可以将相同的图表发布到开发环境中的最小版本,或者(通过不同的配置选项)发布到生产环境中的复杂版本。
Helm 的架构
在本章的最后一节中,我们将简要介绍 Helm 的高级架构。除了完成云原生 Kubernetes 应用程序和软件包管理的概念讨论外,本节还为 第二章 铺平了道路。
Kubernetes 资源
我们已经查看了几种 Kubernetes 资源类型。我们看到了几个 Pod 定义,一个 ConfigMap,一个 Deployment 和一个 Service。Kubernetes 还提供了几十种其他资源类型。您甚至可以使用自定义资源定义(CRD)来定义自己的自定义资源类型。主 Kubernetes 文档提供了关于每种资源类型的可访问指南和详细的 API 文档。
在本书中,我们将使用许多不同的 Kubernetes 资源类型。虽然我们在上下文中讨论它们,但您可能会发现在遇到新的资源定义时,浏览主 Kubernetes 文档是有益的。
正如我们之前讨论的,资源定义是声明式的。您作为用户描述了 Kubernetes 所需的资源的期望状态。例如,您可以把我们在本章早些时候创建的 Pod 定义理解为一种声明,“我希望 Kubernetes 为我创建一个具有这些特性的 Pod”。Kubernetes 将根据您的规范来配置和运行 Pod。
所有 Kubernetes 资源定义共享一个常见的元素子集。以下清单使用 Deployment 来说明资源定义的主要结构元素:
apiVersion: apps/v1 
kind: Deployment 
metadata: 
name: example-deployment 
labels: 
some-name: some-value
annotations: 
some-name: some-value
# resource-specific YAML
该资源的 API 家族和版本。
资源的类型。与 apiVersion 结合使用,我们得到“资源类型”。
metadata 部分包含有关资源的顶级数据。
几乎每种资源类型都需要一个名称。
标签用于为您的资源提供 Kubernetes 可查询的“句柄”。
注解提供了一种方式,供作者将他们自己的键和值附加到资源上。
特别注意,在 Kubernetes 中,资源类型 由三部分信息组成:
API 组(或家族)
像 Pod 和 ConfigMap 这样的几种基本资源类型省略了这个名称。
API 版本
表示为 v,后跟主版本号和可选的稳定性标记。例如,v1 是稳定的“版本 1”,而 v1alpha 表示不稳定的“版本 1 alpha 1”。
资源种类
API 组内特定资源的(大写)名称。
注意
完整的资源类型名称类似于 apps/v1 Deployment 或 v1 Pod(对于核心类型),Kubernetes 用户在谈论或写作时通常会省略组和版本。例如,在本书中,我们简单地写 Deployment 而不是 apps/v1 Deployment。完全合格的名称用于指定确切的版本或讨论在 CRD 中定义的资源类型。
因此,apps/v1 Deployment 表示 API 组 “apps” 有一个 “版本 1”(稳定的)资源类型称为 “Deployment”。
Kubernetes 支持两种主要格式来声明您所需的资源:JSON 和 YAML。严格来说,YAML 是 JSON 的 超集。所有的 JSON 文档都是有效的 YAML,但 YAML 增加了许多额外的功能。
在本书中,我们坚持使用 YAML 格式。我们发现它更易于阅读和编写,几乎所有 Helm 用户选择 YAML 而不是 JSON。但是,如果您有不同的偏好,Kubernetes 和 Helm 都支持纯粹的 JSON。
之前,我们介绍了术语 清单。清单只是 Kubernetes 资源序列化为其 JSON 或 YAML 格式。我们早先的 Pod、ConfigMap、Deployment 和 Service 每个都可以称为 Kubernetes 清单,因为它们是用 YAML 表示的资源。
图表
在本章中,我们已经讨论了 Helm 包。在 Helm 的术语中,一个包被称为 图表。这个名字是对 Kubernetes 的航海特性(希腊语中意为“船长”)和 Helm(船舶的驾驶机制)的一种演绎。图表规划了 Kubernetes 应用程序的安装方式。
一个图表是一组文件和目录,遵循图表规范来描述要安装到 Kubernetes 中的资源。第四章详细解释了图表结构,但在这里我们会介绍几个高级概念。
一个图表包含一个名为 Chart.yaml 的文件,描述了图表。它包含关于图表版本、图表名称和描述以及图表作者的信息。
一个图表也包含 模板。这些是 Kubernetes 清单(就像我们在本章早些时候看到的那样),可能带有模板化指令。我们将在第五章详细介绍这些内容。
一个图表还可能包含一个 values.yaml 文件,提供默认配置。这个文件包含参数,您可以在安装和升级期间进行覆盖。
这些是您在 Helm 图表中找到的基本内容,尽管还有其他内容我们将在第四章中介绍。然而,当您看到一个 Helm 图表时,它可能以解压或打包形式呈现。
解压缩的 Helm 图表只是一个目录。其中会包含一个Chart.yaml、一个values.yaml、一个templates/目录,以及可能的其他内容。打包的 Helm 图表包含与解压缩版本相同的信息,但它被打包成一个压缩文件(tar.gz)。
一个解压缩的图表表示为具有图表名称的目录。例如,名为mychart的图表将解压缩为一个名为mychart/的目录。相比之下,打包的图表具有图表的名称和版本,以及 tgz 后缀:mychart-1.2.3.tgz。
图表存储在图表仓库中,我们将在第七章中详细介绍。Helm 知道如何从仓库中下载和安装图表。
资源、安装和发布
为了将本节介绍的术语联系在一起,当将 Helm 图表安装到 Kubernetes 中时,将发生以下情况:
-
Helm 读取图表(必要时进行下载)。
-
它将这些值传递给模板,生成 Kubernetes 清单。
-
清单被发送到 Kubernetes。
-
Kubernetes 在集群内创建所请求的资源。
当安装 Helm 图表时,Helm 会根据需要生成多个资源定义。有些可能会创建一个或两个,而其他可能会创建数百个。当 Kubernetes 收到这些定义时,它将为它们创建资源。
Helm 图表可能包含多个资源定义。Kubernetes 将每个资源视为独立的实体。但在 Helm 的视角中,图表定义的所有资源都是相关联的。例如,我的 WordPress 应用可能有一个Deployment、一个ConfigMap、一个Service等等。但它们都属于同一个图表。当我安装它们时,它们都属于同一个安装。同一个图表可以安装多次(每次使用不同的名称)。因此,我可能会有同一个图表的多个安装,就像我可能会有同一种 Kubernetes 资源类型的多个资源一样。
这将引出最后一个术语。一旦安装我们的 WordPress 图表,我们就拥有了该图表的一个安装。然后我们使用 helm upgrade 升级该图表。现在,该安装就有了两个发布。每次我们使用 Helm 修改安装时,都会创建一个新的发布。
当我们安装 WordPress 的新版本时,将创建一个发布。但是,仅仅更改安装配置或回滚安装时也会创建一个发布。这是 Helm 的一个重要特性,我们将在第七章中再次看到。
Helm 2 简介
熟悉 Helm 2 的人可能会注意到本书中缺少某些概念。书中没有提到 Tiller 或 gRPC。这些东西已经从 Helm 3 中移除,而 Helm 3 则是本书的主题。此外,本书的这个版本侧重于 Helm Charts 的第 2 版。尽管这很令人困惑,但 Helm Chart 的版本增量与 Helm 版本是分开的。因此,Helm v2 使用 Helm Charts v1,而 Helm v3 使用 Helm Charts v2。这些与 Helm Charts 第 1 版在声明依赖项方式上有几个重要的不同。Helm 2 和 Helm Charts v1 被视为不推荐使用。
结论
这里的材料应该为你准备好接下来的章节。但我们也希望它能让你了解为什么我们按照这样的方式构建了 Helm。Helm 的成功仅在于它能够使 Kubernetes 对于初次使用者和长期操作团队以及每天使用 Helm 的 SRE 更加易用。本书的其余部分致力于通过大量示例来解释如何充分利用 Helm,以及如何在安全和习惯的情况下这样做。
第二章:使用 Helm
Helm 提供了一个命令行工具,名为 helm,可以使用所有与 Helm charts 相关的主要功能。在本章中,我们将探索 helm 客户端的主要功能。在此过程中,我们将了解 Helm 如何与 Kubernetes 交互。
我们将首先看看如何安装和配置 Helm,并逐步学习 Helm 的主要命令组。然后,我们将介绍如何查找和学习软件包,以及如何安装、升级和删除它们。
安装和配置 Helm 客户端
Helm 提供了一个能够执行所有主要 Helm 任务的单一命令行客户端。这个客户端名为 helm。虽然有许多其他工具可以处理 Helm charts,但这是由 Helm 核心维护者维护的官方通用工具,也是本章和下一章的主题。
helm 客户端是用一种名为 Go 的编程语言编写的。与 Python、JavaScript 或 Ruby 不同,Go 是一种编译语言。一旦 Go 程序编译完成,您就不需要任何 Go 工具来运行或处理二进制文件。
因此,我们将首先介绍下载和安装静态二进制文件的过程,然后简要介绍如果您希望的话,可以获取并从 Go 源代码编译的过程。
安装预编译二进制文件
每次 Helm 维护者发布新版本的 helm 时,项目都会提供针对多个常见操作系统和架构的新签名二进制版本的构建。截至撰写本文时,Helm 的预构建版本适用于从 64 位 Intel/AMD 到 ARM、s390 和 PPC 的 Linux、Windows 和 macOS 等各种架构。这意味着您可以在从树莓派到超级计算机的任何设备上运行 Helm。
Helm 发行版的详尽列表位于发布页面。发布页面将显示按时间顺序排列的发行版列表,最新发行版位于顶部。
Helm 版本号说明
直到 2020 年 11 月,仍在积极维护两个不同的 Helm 主要版本。当前稳定的 Helm 主要版本是版本 3。当您访问 Helm 下载页面时,您可能会看到可以下载的两个版本。由于版本按时间顺序列出,甚至可能会出现 Helm 2 的版本比最新的 Helm 3 版本更新的情况。您应该使用 Helm 3。
Helm 遵循称为语义化版本(SemVer)的版本控制约定。在语义化版本中,版本号传达了关于发布版中可期待内容的含义。由于 Helm 遵循此规范,用户可以仅仔细阅读版本号即可对发行版期望有所了解。
在其核心,语义版本具有三个数字组件和一个可选的稳定性标记(用于 alpha、beta 和候选发布版)。以下是一些示例:
-
v1.0.0 -
v3.3.2 -
v2.4.22-alpha.2
让我们首先讨论数值组件。
我们经常将这种格式概括为 X.Y.Z,其中 X 是 主版本,Y 是 次版本,Z 是 补丁发布:
-
主要的版本号往往不经常增加。它表示 Helm 发生了重大变化,并且其中一些变化可能会破坏与旧版本的兼容性。Helm 2 和 Helm 3 之间的差别很大,需要进行版本迁移的工作。
-
次要版本号表示功能添加。例如 3.2.0 和 3.3.0 之间的区别可能是增加了一些小的新功能。但是,版本之间没有 重大变更。(有一个例外:安全修复可能需要进行重大变更,但我们会明确宣布这种情况。)
-
补丁版本号表示在此版本与上一个版本之间仅进行了向后兼容的错误修复。建议始终保持在最新的补丁版本。
当您看到一个带有稳定性标记的发布版本,例如 alpha.1,beta.4 或 rc.2,附加到发布版本号时,这意味着该版本被视为预发布版本,尚未准备好用于主流生产环境。特别是在主要或次要更新之前,Helm 经常发布 候选版本。这给社区一个机会,在正式发布之前就稳定性、兼容性和新功能提供反馈。
有了这个理解,我们准备进行实际安装。
下载二进制文件
从仓库安装 Helm 的最简单方法是直接访问 发布页面,并下载最新的 Helm 3 版本。
在 Windows 上,下载文件是一个 ZIP 存档,包含一个 README.md 文本文件,一个 LICENSE 文本文件和 helm.exe。
在 macOS 和 Linux 上,下载将以一个可以用 tar -zxf 命令解压的 gzip 压缩的 tar 存档(.tar.gz)形式存在。与 Windows 版本类似,它将包含一个 README.md 文本文件,一个 LICENSE 文本文件和 helm 二进制文件。如果您正在使用 Windows Subsystem for Linux(WSL),您应该将 Linux AMD64 版本安装到您的 WSL 实例中。
无论您使用哪个操作系统,二进制文件是运行 Helm 所需的唯一文件,您可以将其放在系统上任何您喜欢的位置。它应该被预先标记为可执行文件,但在类 UNIX 环境中,您偶尔可能需要运行命令 chmod helm +x 来设置 Helm 为可执行文件。
注意
当使用像 Homebrew(macOS)、Snap(Linux)或 Chocolatey(Windows)这样的软件包管理器安装时,helm 将安装在标准位置,并立即通过命令行对您可用。
安装完毕 helm 后,您应该能够运行 helm help 命令并查看 Helm 帮助文本。
使用获取脚本进行安装
在 macOS 和 Linux 上,您可能更喜欢运行一个 shell 脚本,它会确定要安装的 Helm 版本,并自动完成安装。
通过这种方式安装的常规命令序列如下:
$ curl -fsSL -o get_helm.sh \
https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
上述命令会获取 get_helm.sh 脚本的最新版本,然后使用它来查找并安装 Helm 3 的最新版本。
对于自动安装 Helm 的系统,例如持续集成(CI)系统,如果始终拥有最新的 Helm 版本很重要,我们建议使用这种方法。
构建源代码的指南
除非您已经熟悉 Go 开发,否则从源代码构建 Helm 可能会是一项艰巨的任务。您需要一个版本的 make 命令。因为 Makefile 风格的构建脚本没有遵循单一标准,不是所有版本的 make 都能用于构建 Helm。GNU Make 是在 Linux 和 Mac 上最常用的,也是 Helm 核心开发人员最常用的,因此是一个安全的选择。您还需要 gcc 编译器和完整的 Go 工具链。
除此之外,Helm 还需要几个辅助工具。幸运的是,当您第一次运行 make 时,它将尝试安装任何缺失的附加工具。
虽然它们并非绝对必需,但您可能还需要 git 工具和 kubectl 命令。git 工具允许您直接使用 Helm 源代码仓库,而不是下载源代码包。当然,kubectl 是用于与您的 Kubernetes 集群交互的。虽然这不是 构建 Helm 所必需的,但在检查 Helm 是否按您希望的方式运行时,这无疑是必需的。
一旦安装和配置好工具,您只需切换到包含 Helm 源代码的文件夹(即包含 README.md 和 Makefile 文件的目录),然后运行 make build。第一次运行此命令时,至少需要几分钟。构建系统必须获取大量依赖项,包括 Kubernetes 的大部分源代码,并对其进行编译。
提示
编译 Helm 初次可能会令人望而却步,尤其是对于不熟悉 Go 编程语言的人来说。 Kubernetes 是一个复杂的平台,因此 Helm 的源代码庞大且难以构建。计划至少花上一两个小时准备一个新的环境来安装 Helm。
要验证 Helm 是否正常运行(特别是如果您修改了源代码),可以运行 make test。这将构建代码,运行各种检查器和 linter,并运行 Helm 的单元测试。如果您计划向 Helm 的核心维护者提交任何更改请求,在查看您请求的更改之前,此命令 必须 通过。
当 Helm 编译完成时,它将位于源代码旁边的一个名为 bin/ 的子目录中。它不会自动添加到可执行路径中,因此要执行您刚刚构建的版本,您可能需要指定相对或确切的路径(例如,./bin/helm 或 $GOPATH/src/helm.sh/helm/bin/helm)。
如果命令 helm version 正确执行,您可以放心地确认您已正确编译 Helm。
从这里,您可以按照详细的开发人员指南了解更多信息。和往常一样,如果遇到问题,可以在 Kubernetes Slack 服务器的 helm-users 频道中寻求帮助。
使用 Kubernetes 集群
Helm 与 Kubernetes API 服务器直接交互。因此,Helm 需要能够连接到 Kubernetes 集群。Helm 试图通过读取 kubectl(主要的 Kubernetes 命令行客户端)使用的相同配置文件来自动执行此操作。
Helm 将尝试通过读取环境变量 \(KUBECONFIG* 来查找这些信息。如果未设置,它将在与 `kubectl` 相同的默认位置查找(例如,在 UNIX、Linux 和 macOS 上为 *\)HOME/.kube/config)。
您还可以通过环境变量(HELM_KUBECONTEXT)和命令行标志(--kube-context)覆盖这些设置。可以通过运行 helm help 查看环境变量和标志的列表。
Helm 的维护者建议使用 kubectl 管理您的 Kubernetes 凭据,并让 Helm 仅仅自动检测这些设置。如果您尚未安装 kubectl,开始的最佳方法是参考官方 Kubernetes 安装文档。
开始使用 Helm
无论您是从源代码构建 Helm 还是使用上述方法之一进行安装,在这一点上您应该在系统上拥有 helm 命令可用。从这里开始,我们将假设可以使用 helm 命令执行 Helm(与之前部分讨论的完整或相对路径相反)。
接下来,我们将看看 Helm 最常见的启动工作流程:
-
添加一个图表存储库。
-
查找要安装的图表。
-
安装 Helm 图表。
-
查看已安装的内容列表。
-
升级您的安装。
-
删除安装。
然后,在下一章中,我们将深入探讨 Helm 的一些附加功能,并通过这样做了解更多有关 Helm 如何工作的信息。
添加图表存储库。
图表存储库是一个独立的主题,在第七章中我们将详细讨论它们。但任何使用 Helm 的人都必须了解有关图表存储库的一些基础知识。
Helm 图表是可以安装到您的 Kubernetes 集群中的独立包。在图表开发过程中,您通常只需使用存储在本地文件系统上的一个图表来工作。
但是当涉及到共享图表时,Helm 描述了一种标准格式,用于索引和共享有关 Helm 图表的信息。一个 Helm 图表仓库 简单地是一组文件,通过网络可访问,符合 Helm 规范以索引包。
注意
Helm 3 引入了一个实验性功能,用于将 Helm 图表存储在不同类型的仓库中:Open Container Initiative (OCI) 注册表(有时被称为 Docker 注册表)。在这个后端,一个 Helm 图表可以与 Docker 镜像并存储。虽然这个功能目前支持不广泛,但它可能成为 Helm 包存储的未来。这在 第七章 中更详细地讨论。
互联网上有许多——也许成千上万——图表仓库。找到流行的仓库最简单的方法是使用你的网络浏览器导航到 Artifact Hub。在那里,你将找到成千上万的 Helm 图表,每个都托管在适当的仓库上。
要开始,我们将安装流行的 Drupal 内容管理系统。这是一个很好的示例图表,因为它涵盖了许多 Kubernetes 类型,包括 Deployment、Service、Ingress 和 ConfigMap。
Helm 2 默认安装了一个 Helm 仓库。stable 图表仓库曾一度是生产准备好的 Helm 图表的官方来源。但我们意识到将图表集中到一个仓库对少数维护者来说过于繁重,并且对图表贡献者来说是令人沮丧的。
在 Helm 3 中,没有默认仓库。鼓励用户使用 Artifact Hub 找到他们需要的内容,然后添加他们偏好的仓库。
Drupal 的 Helm 图表位于最完善的图表仓库之一:Bitnami 的官方 Helm 图表中。你可以查看 Artifact Hub 上的 Drupal 图表的条目 以获取更多信息。
注意
小部分 Bitnami 开发人员是设计 Helm 仓库系统核心贡献者之一。他们为图表开发建立了 Helm 的最佳实践,并编写了许多被广泛使用的图表。
添加 Helm 图表可以使用 helm repo add 命令完成。几个 Helm 仓库命令被分组在 helm repo 命令组下:
$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
helm repo add 命令将添加一个名为 bitnami 的仓库,指向网址 https://charts.bitnami.com/bitnami。
现在,我们可以通过运行第二个 repo 命令来验证 Bitnami 仓库是否存在。
$ helm repo list
NAME URL
bitnami https://charts.bitnami.com/bitnami
这个命令显示了所有安装的 Helm 仓库。现在,我们只看到刚刚添加的 Bitnami 仓库。
一旦我们添加了一个仓库,其索引将被本地缓存,直到我们下次更新它(参见 第七章)。现在我们能做的一件重要的事情是搜索该仓库。
搜索图表仓库
尽管我们知道,在 Artifact Hub 上查看过 Drupal 图表存在于此存储库中,但仍然有必要从命令行搜索它。通常情况下,搜索不仅是查找可以安装的图表的有用方式,还可以查找可用的版本。
首先,让我们搜索 Drupal 图表:
$ helm search repo drupal
NAME CHART VERSION APP VERSION DESCRIPTION
bitnami/drupal 7.0.0 9.0.0 One of the most versatile open...
我们简单搜索了术语drupal。Helm 不仅会搜索包名称,还会搜索标签和描述等其他字段。因此,我们可以搜索content,看到 Drupal 因为它是内容管理系统而被列出:
$ helm search repo content
NAME CHART VERSION APP VERSION DESCRIPTION
bitnami/drupal 7.0.0 9.0.0 One of the most versa...
bitnami/harbor 6.0.1 2.0.0 Harbor is an an open...
bitnami/joomla 7.1.18 3.9.19 PHP content managemen...
bitnami/mongodb 7.14.6 4.2.8 NoSQL document-orient...
bitnami/mongodb-sharded 1.4.2 4.2.8 NoSQL document-orient...
虽然 Drupal 是第一个结果,但请注意还有许多其他图表在描述文本中包含content一词。
默认情况下,Helm 尝试安装图表的最新稳定版本,但您可以覆盖此行为并安装特定版本的图表。因此,查看图表的摘要信息以及确切的图表版本通常非常有用:
$ helm search repo drupal --versions
NAME CHART VERSION APP VERSION DESCRIPTION
bitnami/drupal 7.0.0 9.0.0 One of the most versatile op...
bitnami/drupal 6.2.22 8.9.0 One of the most versatile op...
bitnami/drupal 6.2.21 8.8.6 One of the most versatile op...
bitnami/drupal 6.2.20 8.8.5 One of the most versatile op...
bitnami/drupal 6.2.19 8.8.5 One of the most versatile op...
...
Drupal 图表有几十个版本。前面的示例已经被截断,仅显示了前几个顶级版本。
图表和应用版本
图表版本是 Helm 图表的版本。应用程序版本是打包在图表中的应用程序的版本。Helm 使用图表版本来做出版本决策,例如哪个包是最新的。正如我们在前面的示例中看到的,多个图表版本可能包含相同的应用程序版本。
安装一个包
在接下来的章节中,我们将深入探讨 Helm 中包安装的工作原理。不过,在本节中,我们将查看安装 Helm 图表的基本机制。
在 Helm 中,至少需要两个信息来安装图表:安装名称和您想安装的图表。
请记住,在上一章中,我们区分了安装和图表。这在安装和升级过程中是一个重要的区别。在操作系统包管理器中,我们可能请求安装某个软件包。但在操作系统上几乎不会需要多次安装完全相同的软件包。Kubernetes 集群则不同。在 Kubernetes 中说“我想为应用程序 A 安装一个 MySQL 数据库,并为应用程序 B 安装第二个 MySQL 数据库”是完全有道理的。即使两个数据库是完全相同版本并且具有相同的配置,为了适当地管理我们的应用程序,我们可能希望运行两个实例。
因此,Helm 需要一种方法来区分同一个图表的不同实例。因此,一个图表的安装是该图表的特定实例。一个图表可能有多个安装。当我们运行helm install命令时,我们需要给出一个安装名称以及图表名称。因此,最基本的安装命令看起来像这样:
$ helm install mysite bitnami/drupal
NAME: mysite
LAST DEPLOYED: Sun Jun 14 14:46:51 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
*******************************************************************
*** PLEASE BE PATIENT: Drupal may take a few minutes to install ***
*******************************************************************
1\. Get the Drupal URL:
You should be able to access your new Drupal installation through
http://drupal.local/
2\. Login with the following credentials
echo Username: user
echo Password: $(kubectl get secret --namespace default mysite-drupal...
之前将创建一个bitnami/drupal图表的实例,并将此实例命名为mysite。
当安装命令运行时,它将返回大量信息,包括有关如何开始使用 Drupal 的用户界面说明。
注意
在以后的helm install示例中,出于简洁起见,我们将省略返回的输出。但是,在使用 Helm 时,您将看到每个安装的输出。在下一章中,我们还将看到如何使用helm get命令再次查看该输出。
此时,集群中现在有一个名为mysite的实例。如果我们尝试重新运行前面的命令,我们将不会得到第二个实例。相反,我们会因为名称mysite已被使用而收到错误消息:
$ helm install mysite bitnami/drupal
Error: cannot re-use a name that is still in use
还需要进一步澄清一点。在 Helm 2 中,实例名称是集群范围的。您只能在每个集群中有一个名为mysite的实例。在 Helm 3 中,命名已更改。现在实例名称限定在 Kubernetes 命名空间内。只要它们分别位于不同的命名空间中,我们可以安装两个名为mysite的实例。
例如,在 Helm 3 中,以下操作是完全合法的,尽管在 Helm 2 中会生成致命错误:
$ kubectl create ns first
$ kubectl create ns second
$ helm install --namespace first mysite bitnami/drupal
$ helm install --namespace second mysite bitnami/drupal
这将在first命名空间中安装一个名为mysite的 Drupal 站点,并在second命名空间中安装一个配置相同的实例,也命名为mysite。起初可能会感到困惑,但当我们将命名空间视为名称的前缀时,情况就变得更清晰了。在这种情况下,我们有一个名为“first mysite”的站点和另一个名为“second mysite”的站点。
在 Helm 中始终使用命名空间标志
在处理命名空间和 Helm 时,您可以使用--namespace或-n标志来指定所需的命名空间。
安装时的配置
在前面的示例中,我们以几种不同的方式安装了相同的图表。在所有情况下,它们的配置是相同的。虽然默认配置有时很好,但更常见的是我们想将自己的配置传递给图表。
许多图表将允许您提供配置值。如果我们查看Drupal 的 Artifact Hub 页面,我们将看到一长串可配置参数。例如,我们可以通过设置drupalUsername值来配置 Drupal 管理员帐户的用户名。
注意
在下一章中,我们将学习如何使用helm命令获取此信息。
有几种方法可以告诉 Helm 您想要配置哪些值。最好的方法是创建一个包含所有配置覆盖的 YAML 文件。例如,我们可以创建一个设置drupalUsername和drupalEmail值的文件:
drupalUsername: admin
drupalEmail: admin@example.com
现在我们有一个文件(通常命名为values.yaml),其中包含所有配置信息。由于它在一个文件中,因此很容易复制相同的安装过程。您还可以将此文件检入到版本控制系统中,以跟踪值随时间的变化。Helm 核心维护者认为将配置值存储在 YAML 文件中是一个良好的实践。但请记住,如果配置文件包含敏感信息(如密码或身份验证令牌),则应采取措施确保这些信息不会泄露。
helm install和helm upgrade都提供了一个--values标志,该标志指向一个带有值覆盖的 YAML 文件:
$ helm install mysite bitnami/drupal --values values.yaml
NAME: mysite
LAST DEPLOYED: Sun Jun 14 14:56:15 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
*******************************************************************
*** PLEASE BE PATIENT: Drupal may take a few minutes to install ***
*******************************************************************
1\. Get the Drupal URL:
You should be able to access your new Drupal installation through
http://drupal.local/
2\. Login with the following credentials
echo Username: admin
echo Password: $(kubectl get secret --namespace default mysite-drupal -o js...
请注意,在前面的输出中,Username现在是admin而不是user。Helm 的一个很好的特性是,甚至可以使用您提供的值更新帮助文本。
注意
您可以多次指定--values标志。有些人使用此功能在一个文件中进行“常见”覆盖,而在另一个文件中进行特定的覆盖。
还有第二个标志可以用来向安装或升级添加单个参数。--set标志接受一个或多个直接值。它们不需要存储在 YAML 文件中:
$ helm install mysite bitnami/drupal --set drupalUsername=admin
这只设置了一个参数,drupalUsername。此标志使用简单的key=value格式。
配置参数可以是结构化的。也就是说,配置文件可以有多个部分。例如,Drupal 图表具有特定于 MariaDB 数据库的配置。这些参数都被分组到mariadb部分中。在我们之前的示例基础上,我们可以像这样覆盖 MariaDB 数据库名称:
drupalUsername: admin
drupalEmail: admin@example.com
mariadb:
db:
name: "my-database"
当使用--set标志时,子部分会更加复杂。您需要使用点分符号表示法:--set mariadb.db.name=my-database。当设置多个值时,这可能会变得冗长。
通常,Helm 核心维护者建议将配置存储在values.yaml文件中(请注意,文件名不一定需要是“values”),只有在绝对必要时才使用--set。这样,您可以在操作期间轻松访问使用的值(并可以随时间跟踪这些值),同时保持 Helm 命令的简洁。使用文件还意味着您不必像在命令行设置时那样转义很多字符。
在继续升级之前,我们将快速查看 Helm 命令中最有帮助的一个功能。
列出您的安装
我们已经看到,Helm 可以将许多内容安装到同一个集群中,甚至可以在同一个图表的多个实例之间进行安装。而且,在集群上有多个用户时,不同的人可能会将内容安装到同一个命名空间上。
helm list命令是一个简单的工具,帮助您查看安装并了解这些安装情况:
$ helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
mysite default 1 2020-06-14... deployed drupal-7.0.0 9.0.0
此命令将为您提供大量有用的信息,包括发布的名称和命名空间,当前修订号(在第一章中讨论,并在下一节中深入讨论),上次更新时间,安装状态以及图表和应用程序的版本。
与其他命令一样,helm list会考虑命名空间。默认情况下,Helm 使用 Kubernetes 配置文件设置的命名空间作为默认值。通常情况下,这个命名空间被命名为default。之前,我们将 Drupal 实例安装到first命名空间中。我们可以通过helm list --namespace first来查看。
当列出所有发布时,一个有用的标志是--all-namespaces标志,它将查询您具有权限的所有 Kubernetes 命名空间,并返回找到的所有发布:
$ helm list --all-namespaces
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
mysite default 1 2020-06-14... deployed drupal-7.0.0 9.0.0
mysite first 1 2020-06-14... deployed drupal-7.0.0 9.0.0
mysite second 1 2020-06-14... deployed drupal-7.0.0 9.0.0
升级安装
当我们谈论 Helm 中的升级时,我们指的是升级安装,而不是图表本身。安装是您集群中图表的特定实例。当您运行helm install时,它创建该安装。要修改该安装,请使用helm upgrade。
在当前上下文中,这是一个重要的区分,因为在 Helm 中升级安装可以包含两种不同类型的更改:
-
您可以升级图表的版本
-
您可以升级安装的配置
这两者并不是互斥的;您可以同时执行两者。但这确实引入了一个 Helm 用户在讨论其系统时引用的新术语:发布是安装的特定配置和图表版本的组合。
当我们首次安装图表时,我们创建了安装的初始发布。让我们称其为发布 1。当我们执行升级时,我们将创建相同安装的新发布:发布 2。当我们再次升级时,我们将创建发布 3。(在下一章中,我们将看到如何通过回滚也创建发布。)
然后,在升级过程中,我们可以使用新配置创建一个发布,使用新的图表版本,或者两者兼而有之。
例如,假设我们安装 Drupal 图表时,关闭了ingress。(这将有效阻止外部集群流量进入 Drupal 实例。)
请注意,我们在此处使用--set标志来保持示例的紧凑性,但在常规情况下建议使用values.yaml文件:
$ helm install mysite bitnami/drupal --set ingress.enabled=false
关闭ingress功能后,我们可以开始设置我们喜欢的站点。然后,当我们准备好时,我们可以创建一个启用ingress功能的新发布:
$ helm upgrade mysite bitnami/drupal --set ingress.enabled=true
在这种情况下,我们正在运行一个仅更改配置的upgrade。
在后台,Helm 将加载图表,生成该图表中的所有 Kubernetes 对象,然后查看这些对象与已安装图表版本的区别。它只会发送需要更改的 Kubernetes 对象。换句话说,Helm 将尝试仅修改最小必要的内容。
前面的示例只会更改 ingress 配置。数据库或者运行 Drupal 的 Web 服务器都不会发生任何变化。因此,不会重新启动或删除并重新创建任何内容。这有时会让新的 Helm 用户感到困惑,但这是有意设计的。Kubernetes 的哲学是以最简洁的方式进行更改,Helm 也致力于遵循这一哲学。
偶尔,您可能希望强制重启其中一个服务。这并不是您需要使用 Helm 的事情。您可以直接使用 kubectl 来重新启动。就像操作系统的软件包管理器不用于重新启动程序一样。同样,您不需要使用 Helm 来重新启动 Web 服务器或数据库。
当图表的新版本发布时,您可能希望升级现有的安装以使用新的图表版本。大多数情况下,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!⎈
$ helm upgrade mysite bitnami/drupal 
从图表仓库获取最新的包。
将 mysite 发布升级到使用 bitnami/drupal 的最新版本。
正如您所见,Helm 的默认策略是尝试使用图表的最新版本。如果您希望保留特定版本的图表,可以显式声明:
$ helm upgrade mysite bitnami/drupal --version 6.2.22
在这种情况下,即使发布了更新版本,也只会安装 bitnami/drupal 的 6.2.22 版本。
配置数值和升级
了解 Helm 的安装和升级最重要的一点是,每次发布都会全新应用配置数据。以下是一个快速说明:
$ helm install mysite bitnami/drupal --values values.yaml 
$ helm upgrade mysite bitnami/drupal 
使用配置文件进行安装。
不使用配置文件进行升级。
这一对操作的结果是什么?安装将使用 values.yaml 中提供的所有配置数据,但升级则不会。因此,某些设置可能会被改回其默认值。这通常不是您想要的结果。
检查数值
在下一章中,我们将介绍 helm get 命令。您可以使用 helm get values mysite 查看上次 helm install 或 helm upgrade 操作使用的值。
Helm 核心维护者建议您在每次安装和升级时提供一致的配置。要将相同的配置应用于两个发布,需要在每个操作中提供这些值:
$ helm install mysite bitnami/drupal --values values.yaml 
$ helm upgrade mysite bitnami/drupal --values values.yaml 
使用配置文件进行安装。
使用相同的配置文件进行升级。
我们建议将配置存储在 values.yaml 文件中,这样可以轻松复制这种模式。想象一下,如果您使用 --set 设置三到四个配置参数,这些命令将会变得多么繁琐!对于每个发布,您都需要记住要设置哪些内容。
虽然我们强烈建议使用此处讨论的模式,并每次指定--values,但也有一个升级快捷方式可供使用,它将只重用您发送的上一组值:
$ helm upgrade mysite bitnami/drupal --reuse-values
--reuse-values标志将告诉 Helm 重新加载上次设置的服务器端副本的值,然后使用这些值生成升级。如果您总是只重用相同的值,这种方法是可以接受的。然而,Helm 的维护者强烈建议不要尝试将--reuse-values与额外的--set或--values选项混合使用。这样做会使故障排除变得复杂,并且很快会导致不可维护的安装,其中没有人确切知道如何设置某些配置参数。虽然 Helm 保留了一些状态信息,但它不是配置管理工具。建议用户使用自己的工具管理配置,并在每次调用中显式传递该配置给 Helm。
到目前为止,我们已经学会了如何安装、列出和升级安装。在本章的最后一节中,我们将删除一个安装。
卸载安装
要删除 Helm 安装,请使用helm uninstall命令:
$ helm uninstall mysite
请注意,此命令不需要图表名称(bitnami/drupal)或任何配置文件。它只需要安装名称。在本节中,我们将看看删除如何工作,并简要介绍 Helm 2 和 Helm 3 之间的一个重大变化。
与install、list和upgrade一样,您可以提供--namespace标志来指定您希望从特定命名空间删除安装:
$ helm uninstall mysite --namespace first
前述内容将删除我们在本章早期在first命名空间中创建的站点。请注意,没有删除多个应用程序的命令。您必须卸载特定的安装。
删除可能需要一些时间。较大的应用程序可能需要几分钟,甚至更长时间,因为 Kubernetes 清理所有资源。在此期间,您将无法使用相同的名称重新安装。
Helm 如何存储发布信息
Helm 3 中的一个重大变化是如何删除有关安装的 Helm 自身数据。本节简要描述了安装是如何被跟踪的,然后总结了解释了为什么 Helm 在 2 和 3 版本之间发生了变化。
当我们首次使用 Helm 安装图表(例如使用helm install mysite bitnami/drupal),我们创建了 Drupal 应用程序实例,并且还创建了一个包含发布信息的特殊记录。默认情况下,Helm 将这些记录存储为 Kubernetes Secret(尽管还支持其他存储后端)。
我们可以使用kubectl get secret来查看这些记录:
$ kubectl get secret
NAME TYPE DATA AGE
default-token-vjhx2 kubernetes.io/service-account-token 3 58m
mysite-drupal Opaque 1 13m
mysite-mariadb Opaque 2 13m
sh.helm.release.v1.mysite.v1 helm.sh/release.v1 1 13m
sh.helm.release.v1.mysite.v2 helm.sh/release.v1 1 13m
sh.helm.release.v1.mysite.v3 helm.sh/release.v1 1 7m53s
sh.helm.release.v1.mysite.v4 helm.sh/release.v1 1 5m30s
我们可以在底部看到多个发布记录,每个修订版本都有一个。正如您所看到的,我们通过运行install和upgrade操作创建了mysite的四个修订版本。
在下一章中,我们将看到如何利用这些扩展记录来回滚到安装的先前版本。但我们现在指出这一点是为了说明helm uninstall的工作原理。
当我们运行命令helm uninstall mysite时,它将加载mysite安装的最新发布记录。从该记录中,它将组装一个应从 Kubernetes 中删除的对象列表。然后 Helm 将在返回并删除这四个发布记录之前删除所有这些对象:
$ helm uninstall mysite
release "mysite" uninstalled
helm list命令将不再显示mysite:
$ helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
现在我们没有任何安装。如果我们重新运行kubectl get secrets命令,我们还将看到mysite的所有记录都已被清除:
$ kubectl get secrets
NAME TYPE DATA AGE
default-token-vjhx2 kubernetes.io/service-account-token 3 65m
正如我们从这个输出中看到的,Drupal 图表创建的两个Secret不仅被删除了,还删除了四个发布记录。
在下一章中,我们将看到helm rollback命令。前面的解释应该让你了解为什么默认情况下无法回滚卸载。不过,可以删除应用程序但保留发布记录:
$ helm uninstall --keep-history
在 Helm 2 中,默认情况下保留了历史记录。在 Helm 3 中,更改为默认删除历史记录。不同的组织偏好不同的策略,但核心维护者发现,当大多数人卸载时,他们希望销毁所有安装的痕迹。
结论
在本章中,我们涵盖了安装和使用 Helm 的基础知识。在探讨了流行的 Helm 安装和配置方法之后,我们添加了一个图表仓库,并学习了如何搜索图表。然后,我们安装了、列出了、升级了,并最终卸载了bitnami/drupal图表。
在这个过程中,我们掌握了一些重要的概念。我们学习了安装和发布。我们初步了解了图表仓库,在第七章中将详细介绍。在本章的结尾,我们稍微了解了 Helm 如何存储关于我们安装的信息。
在下一章中,我们将回顾helm命令,学习 Helm 工具的其他功能。
第三章:使用 Helm 进行基础以外的探索
在上一章中,我们看了 Helm 最常用的命令。在本章中,我们将探索 Helm 工具提供的其他功能。我们将深入研究提供有关发布信息的命令,测试安装的命令,并跟踪历史记录的命令。最后,我们将重新审视安装和升级,这次涵盖高级情况。
我们将开始使用一些有助于故障排除和调试的工具。
模板化与干运行
当 Helm 安装一个发布时,程序将通过几个阶段。它加载图表,解析传递给程序的值,读取图表元数据等。在过程的中间部分,Helm 将编译图表中的所有模板(一次性全部编译),然后通过传递值(就像我们在前一章中看到的)渲染它们。在这个中间部分期间,它执行所有模板指令。一旦模板被渲染成 YAML,Helm 通过将其解析为 Kubernetes 对象来验证 YAML 的结构。最后,Helm 序列化这些对象并将它们发送到 Kubernetes API 服务器。
大致而言,过程如下:
-
加载整个图表,包括其依赖项。
-
解析值。
-
执行模板,生成 YAML。
-
将 YAML 解析为 Kubernetes 对象以验证数据。
-
将其发送到 Kubernetes。
举例来说,让我们来看看我们在前一章中发出的一个命令:
$ helm install mysite bitnami/drupal --set drupalUsername=admin
在第一阶段,Helm 将定位名为bitnami/drupal的图表并加载该图表。如果图表是本地的,则将从磁盘读取。如果给定 URL,则将从远程位置获取(可能使用插件来帮助获取图表)。
然后它将--set drupalUsername=admin转换为可以注入模板的值。此值将与图表的values.yaml文件中的默认值组合。Helm 对数据进行了一些基本检查。如果有问题解析用户输入,或者默认值损坏,它将以错误退出。否则,它将构建一个单一的大值对象,模板引擎可以用来进行替换。
生成的值对象是通过加载图表文件的所有值,叠加从文件加载的任何值(即使用-f标志),然后叠加使用--set标志设置的任何值来创建的。换句话说,--set值覆盖传递值文件中的设置,这反过来又覆盖图表默认的values.yaml文件中的任何设置。
在这一点上,Helm 将读取 Drupal 图表中的所有模板,然后执行这些模板,将合并的值传递给模板引擎。格式错误的模板将导致错误。但还有许多其他情况可能导致失败。例如,如果缺少必需的值,则在此阶段将返回错误。
需要注意的是,当执行时,某些 Helm 模板需要有关 Kubernetes 的信息。因此,在模板渲染期间,Helm 可能 会联系 Kubernetes API 服务器。这是一个我们将在稍后讨论的重要话题。
然后,将前一步的输出从 YAML 解析为 Kubernetes 对象。此时,Helm 将执行一些模式级验证,确保对象格式良好。然后,它们将被序列化为 Kubernetes 的最终 YAML 格式。
在最后阶段,Helm 将 YAML 数据发送到 Kubernetes API 服务器。这是kubectl和其他 Kubernetes 工具交互的服务器。
API 服务器将对提交的 YAML 运行一系列检查。如果 Kubernetes 接受 YAML 数据,Helm 将认为部署成功。但如果 Kubernetes 拒绝 YAML,则 Helm 将退出并显示错误。
后面,我们将详细讨论一旦对象发送到 Kubernetes 后会发生什么。特别是,我们将涵盖 Helm 如何将前面描述的过程与安装和修订相关联。但现在,我们已经有足够的工作流信息来理解两个相关的 Helm 特性:--dry-run标志和helm template命令。
--dry-run标志
像helm install和helm upgrade这样的命令提供了一个名为--dry-run的标志。当您提供此标志时,它将导致 Helm 依次执行前四个阶段(加载图表,确定值,渲染模板,格式化为 YAML)。但是当第四阶段完成时,Helm 将在标准输出中转储大量信息,包括所有渲染的模板。然后它将退出,而不会将对象发送到 Kubernetes,也不会创建任何发布记录。
例如,这是我们之前安装 Drupal 的版本,附加了--dry-run标志:
$ helm install mysite bitnami/drupal --values values.yaml --set \
drupalEmail=foo@example.com --dry-run
在输出的顶部,它会打印一些关于发布的信息:
NAME: mysite
LAST DEPLOYED: Tue Aug 11 11:42:05 2020
NAMESPACE: default
STATUS: pending-install
REVISION: 1
HOOKS:
前面告诉我们安装的名称是什么,上次部署时间是什么(在本例中是当前日期和时间),它将被部署到哪个命名空间,发布的阶段是什么(pending-install),以及修订号。由于这是一个安装过程,修订号是1。在升级时,它将是2或更高。
最后,如果此图表声明了任何挂钩,它们将在此处枚举。有关挂钩的更多信息,请参阅第六章和第七章。
乍一看,这个元数据条目似乎包含了许多不必要的数据。毕竟,如果我们实际上没有进行安装,LAST DEPLOYED有什么作用呢?事实上,这一信息块是 Helm 中使用的标准设置之一。它是发布记录的一部分:关于发布的一组信息。像helm get这样的命令使用这些相同的字段。
紧接着信息块之后,所有渲染的模板都会被转储到标准输出:
# Source: drupal/charts/mariadb/templates/test-runner.yaml
apiVersion: v1
kind: Pod
metadata:
name: "mysite-mariadb-test-afv3u"
annotations:
"helm.sh/hook": test-success
spec:
initContainers:
- name: "test-framework"
image: docker.io/dduportal/bats:0.4.0
...
渲染后的 Drupal 图表有数千行,因此前面只显示了输出的前几行。
最后,在干运行输出的底部,Helm 打印了面向用户的释放说明:
NOTES:
*******************************************************************
*** PLEASE BE PATIENT: Drupal may take a few minutes to install ***
*******************************************************************
1\. Get the Drupal URL:
You should be able to access your new Drupal installation through
http://drupal.local/
...
示例为了简洁起见被截断。
这个干运行特性为 Helm 用户提供了一种在发送到 Kubernetes 之前调试图表输出的方式。通过渲染所有模板,您可以检查到底会提交到集群的内容。并且通过发布数据,您可以验证释放是否按您期望的方式创建。
--dry-run 标志的主要目的是让人们有机会在发送到 Kubernetes 之前检查和调试输出。但不久之后,Helm 的维护者注意到用户中存在一种趋势。人们想要使用 --dry-run 将 Helm 作为模板引擎使用,然后使用其他工具(如 kubectl)将渲染后的输出发送到 Kubernetes。
但 --dry-run 并非为此用例而编写,这导致了一些问题:
-
--dry-run将非 YAML 信息与渲染模板混合在一起。这意味着在发送到kubectl等工具之前,数据必须被清理。 -
升级时的
--dry-run可能会生成与安装时不同的 YAML 输出,这可能会令人困惑。 -
它联系 Kubernetes API 服务器进行验证,这意味着即使只用于
--dry-run释放,Helm 也必须具有 Kubernetes 凭据。 -
它还将信息插入到模板引擎中,这些信息是特定于集群的。因此,某些渲染过程的输出可能是集群特定的。
为了解决这些问题,Helm 的维护者引入了一个完全独立的命令:helm template。
helm template 命令
虽然 --dry-run 设计用于调试,helm template 则旨在将 Helm 的模板渲染过程与安装或升级逻辑隔离开来。
早些时候,我们看过 Helm 安装或升级的五个阶段。template 命令执行前四个阶段(加载图表、确定值、渲染模板、格式化为 YAML)。但这需要考虑一些额外的注意事项:
-
在
helm template过程中,Helm 从不 联系远程 Kubernetes 服务器。 -
template命令始终像一个安装操作一样。 -
通常需要与 Kubernetes 服务器联系的模板函数和指令现在将仅返回默认数据。
-
图表仅有默认的 Kubernetes 种类访问权限。
关于最后一项,helm template 做了一个显著的简化假设。Kubernetes 服务器支持内置种类(Pod、Service、ConfigMap 等)以及由自定义资源定义(CRD)生成的自定义种类。在运行安装或升级时,Helm 在处理图表之前从 Kubernetes 服务器获取这些种类。
然而,helm template 在此步骤上的操作方式有所不同。当 Helm 编译时,它是针对特定版本的 Kubernetes 进行编译的。Kubernetes 库包含了该版本发布的内置种类列表。Helm 使用该内置列表,而不是从 API 服务器获取列表。因此,在 helm template 运行期间,Helm 没有访问任何 CRD,因为 CRD 安装在集群上,而不包括在 Kubernetes 库中。
注意
在使用旧版本的 Helm 运行针对使用新种类或版本的图表时,可能会在 helm template 过程中出现错误,因为 Helm 没有将最新的种类或版本编译进去。
由于这些决策,helm template 在每次运行后产生一致的输出。更重要的是,它可以在没有访问 Kubernetes 集群的环境中运行,比如持续集成(CI)流水线。
输出也不同于 --dry-run。以下是一个示例命令:
$ helm template mysite bitnami/drupal --values values.yaml --set \
drupalEmail=foo@example.com
---
# Source: drupal/charts/mariadb/templates/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysite-mariadb
labels:
app: "mariadb"
chart: "mariadb-7.5.1"
release: "mysite"
heritage: "Helm"
type: Opaque
# ... LOTS removed from here
volumes:
- name: tests
configMap:
name: mysite-mariadb-tests
- name: tools
emptyDir: {}
restartPolicy: Never
上述内容是输出的大大简化版本,仅显示了命令以及数据的开头和结尾的示例。需要注意的是,默认情况下仅打印 YAML 格式的 Kubernetes 清单。
由于 Helm 在 helm template 运行期间不联系 Kubernetes 集群,它不会对输出进行完整验证。在这种情况下,如果您希望有这种行为,可以选择使用 --validate 标志,但在这种情况下,Helm 需要一个带有集群凭据的有效 kubeconfig 文件。
helm template 命令有许多标志,与 helm install 中的标志相对应。因此,在许多情况下,您可以像执行 helm install 命令一样执行 helm template 命令,然后捕获 YAML 并将其与其他工具一起使用。
使用后渲染而不是 Helm Template
有时您希望拦截 YAML,使用自己的工具修改它,然后将其加载到 Kubernetes 中。Helm 提供了一种执行此外部工具的方式,而无需使用 helm template。在 install、upgrade、rollback 和 template 上使用 --post-renderer 标志会导致 Helm 将 YAML 数据发送到命令,然后再将结果读回到 Helm。这是与 Kustomize 等工具配合使用的一个很好的方式。
总结一下,helm template 是将 Helm charts 渲染为 YAML 的工具,而 --dry-run 标志是在不将数据加载到 Kubernetes 的情况下调试安装和升级命令的工具。
了解发布情况
在前一章中,我们简要介绍了 helm get 命令。现在,我们将深入了解该命令以及其他提供有关 Helm 发布信息的命令。
首先,让我们回顾一下前一节中 Helm 安装的五个阶段。它们是:
-
加载图表。
-
解析值。
-
执行模板。
-
渲染 YAML。
-
发送到 Kubernetes。
前四个阶段主要涉及数据的本地表示。也就是说,Helm 在运行 helm 命令的同一台计算机上执行所有处理。
然而,在最后一个阶段,Helm 将该数据发送到 Kubernetes。然后两者之间来回通信,直到发布被接受或拒绝。
在第五阶段期间,Helm 必须监控发布的状态。此外,由于许多人可能在同一个应用程序安装的副本上工作,因此 Helm 需要以多用户可以看到信息的方式监控状态。
Helm 提供了 发布记录 功能。
发布记录
当我们安装 Helm 图表(使用 helm install)时,新的安装会创建在您指定的命名空间中,或者默认命名空间中。我们在前一章已经看过了这个过程。
在那一章的结尾,我们还看到了 helm install 如何创建一种特殊类型的 Kubernetes Secret 来保存发布信息。我们还看到了如何使用 kubectl 检查这些 Secret:
$ kubectl get secret
NAME TYPE DATA AGE
default-token-g777k kubernetes.io/service-account-token 3 6m
mysite-drupal Opaque 1 2m20s
mysite-mariadb Opaque 2 2m20s
sh.helm.release.v1.mysite.v1 helm.sh/release.v1 1 2m20s
特别要注意的是最后一个 Secret,sh.helm.release.v1.mysite.v1。请注意,它使用了特殊类型(helm.sh/release.v1)来指示这是一个 Helm 密钥。Helm 自动生成此密钥来跟踪我们的 mysite 安装的版本 1(这是一个 Drupal 站点)。
每次我们升级 mysite 安装时,都会创建一个新的 Secret 来跟踪每个发布。换句话说,发布记录跟踪每个安装的 修订版本:
$ helm upgrade mysite bitnami/drupal
# Output omitted
$ helm upgrade mysite bitnami/drupal
# Output omitted
$ kubectl get secrets
NAME TYPE DATA AGE
default-token-g777k kubernetes.io/service-account-token 3 8m43s
mysite-drupal Opaque 1 5m3s
mysite-mariadb Opaque 2 5m3s
sh.helm.release.v1.mysite.v1 helm.sh/release.v1 1 5m3s
sh.helm.release.v1.mysite.v2 helm.sh/release.v1 1 20s
sh.helm.release.v1.mysite.v3 helm.sh/release.v1 1 8s
在上述例子中,我们已经升级了几次,现在我们在 mysite 的 v3 版本。默认情况下,Helm 跟踪每个安装的十个修订版本。一旦一个安装超过十个发布,Helm 将删除最旧的发布记录,直到最多保留指定数量。
每个发布记录包含足够的信息来重新创建该修订版本的 Kubernetes 对象(这对于 helm rollback 非常重要)。它还包含关于发布的元数据。
例如,如果我们使用 kubectl 查看发布,我们会看到类似于这样的内容:
apiVersion: v1
data:
release: SDRzSUFBQU... # Lots of Base64-encoded data removed
kind: Secret
metadata:
creationTimestamp: "2020-08-11T18:37:26Z"
labels: 
modifiedAt: "1597171046"
name: mysite
owner: helm
status: deployed
version: "3"
name: sh.helm.release.v1.mysite.v3
namespace: default
resourceVersion: "1991"
selfLink: /api/v1/namespaces/default/secrets/sh.helm.release.v1.mysite.v3
uid: cbb8b457-e331-467b-aa78-1e20360b5be6
type: helm.sh/release.v1
标签包含 Helm 元数据。
在这个例子中,已经删除了巨大的 Base64 编码数据以及一些其他不必要的字段。该数据块包含了图表和发布的 gzip 压缩表示。但是重要的是,Kubernetes 元数据中的 labels 部分包含了关于此发布的信息。
例如,我们可以看到,这些数据描述了名为 mysite 的发布,其当前修订版本号为 3,并且发布被标记为 deployed。如果我们查看版本 2,我们会看到发布的 status 是 superseded,这意味着它已被后续版本替换。
简而言之,这个密钥是存储在 Kubernetes 内部的,以便同一集群的不同用户可以访问相同的发布信息。
在发布的生命周期中,它可以通过几种不同的状态。以下是您可能会看到的顺序大致排列如下:
pending-install
在将清单发送到 Kubernetes 之前,Helm 通过创建一个状态设置为pending-install的发布(标记为版本 1)来声明安装。
deployed
一旦 Kubernetes 接受来自 Helm 的清单,Helm 会更新发布记录,将其标记为已部署。
pending-upgrade
当开始 Helm 升级时,将为安装创建一个新的发布(例如,v2),并且其状态设置为pending-upgrade。
superseded
当运行升级时,将更新最后部署的发布,标记为superseded,并且新升级的发布从pending-upgrade更改为deployed。
pending-rollback
如果创建了回滚操作,则会创建一个新的发布(例如,v3),并且其状态设置为pending-rollback,直到 Kubernetes 接受发布清单。然后它被标记为deployed,而上一个发布被标记为superseded。
uninstalling
当执行helm uninstall时,会读取最近的发布,然后其状态更改为uninstalling。
uninstalled
如果在删除过程中保留历史记录,则在helm uninstall完成后,上一个发布的状态将更改为uninstalled。
failed
最后,如果在任何操作期间,Kubernetes 拒绝了 Helm 提交的清单,Helm 将标记该发布为failed。
列出发布版本
状态消息出现在多个 Helm 命令中。我们已经看到pending-install如何在--dry-run中出现。在本节和下一节中,我们将看到这些消息出现的几个更多地方。
在前一章中,我们使用helm list查看我们已安装的图表。考虑到我们的状态覆盖,值得重新访问helm list。list命令是快速检查您发布状态的最佳工具。
例如,假设我们有一个同时安装drupal和wordpress图表的集群。这是helm list的输出:
NAME NAMESPACE REVISION UPDATED STATUS CHART APP V...
mysite default 3 2020-08-11... deployed drupal-7.0.0 9.0.0
wordpress default 2 2020-08-12... deployed wordpress-9.3.11 5.4.2
要显示失败的结果,我们可以运行一个我们知道会失败的升级命令:
$ helm upgrade wordpress bitnami/wordpress --set image.pullPolicy=NoSuchPolicy
Error: UPGRADE FAILED: cannot patch "wordpress" with kind Deployment:
Deployment.apps "wordpress" is invalid:
spec.template.spec.containers[0].imagePullPolicy: Unsupported value:
"NoSuchPolicy": supported values: "Always", "IfNotPresent", "Never"
正如错误消息所示,无法将拉取策略设置为NoSuchPolicy。此错误来自 Kubernetes API 服务器,这意味着 Helm 提交了清单,而 Kubernetes 拒绝了它。因此,我们的发布应该处于失败状态。
我们可以通过再次运行helm ls来验证这一点:
$ helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VER...
mysite default 3 2020-08-11 deployed drupal-7.0.0 9.0.0
wordpress default 3 2020-08-12 failed wordpress-9.3.11 5.4.2
值得再次注意的是,我们新失败的wordpress安装的REVISION字段已从2增加到3。即使失败的发布也附有修订版本。我们将看到为什么这一点在“历史和回滚”中很重要。
使用helm get查找发布的详细信息
虽然helm list提供了安装的摘要视图,但helm get命令集提供了有关特定发布的更详细信息。
有五个helm get子命令(hooks、manifests、notes、values和all)。每个子命令检索 Helm 为发布跟踪的一部分信息。
使用 helm get notes
helm get notes子命令打印发布说明:
$ helm get notes mysite
NOTES:
*******************************************************************
*** PLEASE BE PATIENT: Drupal may take a few minutes to install ***
*******************************************************************
1\. Get the Drupal URL:
You should be able to access your new Drupal installation through
http://drupal.local/
...
这个输出应该看起来很熟悉,因为helm install和helm upgrade都会在成功操作结束时打印发布说明。但是helm get notes提供了一个方便的方式来随需获取这些说明。在您忘记 Drupal 站点的 URL 是什么的情况下,这是很有用的。
使用 helm get values
一个有用的子命令是values。您可以使用它来查看上次发布时提供的值。在前一节中,我们升级了一个 WordPress 安装并导致其失败。我们可以使用helm get values查看导致其失败的值:
$ helm get values wordpress
USER-SUPPLIED VALUES:
image:
pullPolicy: NoSuchPolicy
我们知道修订版本 2 成功了,但修订版本 3 失败了。因此,我们可以查看早期的值以查看发生了什么变化:
$ helm get values wordpress --revision 2
USER-SUPPLIED VALUES:
image:
tag: latest
通过这个,我们可以看到一个值被移除,一个值被添加。这样的功能旨在帮助 Helm 用户更容易地识别错误的来源。
这个命令也对了解发布配置的总体状态很有用。我们可以使用helm get values查看该发布当前设置的所有值。为此,我们使用--all标志:
$ helm get values wordpress --all
COMPUTED VALUES:
affinity: {}
allowEmptyPassword: true
allowOverrideNone: false
customHTAccessCM: null
customLivenessProbe: {}
customReadinessProbe: {}
externalDatabase:
database: bitnami_wordpress
host: localhost
password: ""
port: 3306
...
当指定--all标志时,Helm 将获取完整的计算值集,按字母顺序排序。这是一个查看发布配置的确切状态的好工具。
查看默认值
尽管helm get values没有显示默认值的方法,但您可以使用helm inspect values CHARTNAME查看这些值。这会检查图表本身(而不是发布)并打印出文档化的默认values.yaml文件。因此,我们可以使用helm inspect values bitnami/wordpress来查看 WordPress 图表的默认配置。
使用 helm get manifest
我们将要介绍的最后一个helm get子命令是helm get manifest。这个子命令检索 Helm 使用图表模板生成的确切 YAML 清单:
$ helm get manifest wordpress
# Source: wordpress/charts/mariadb/templates/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: wordpress-mariadb
labels:
app: "mariadb"
chart: "mariadb-7.5.1"
release: "wordpress"
heritage: "Helm"
type: Opaque
...
有关此命令的一个重要细节是,它不会返回所有资源的当前状态。它返回从模板生成的清单。在前面的示例中,我们看到一个名为wordpress-mariadb的Secret。如果我们使用kubectl查询该Secret,metadata部分如下所示:
$ kubectl get secret wordpress-mariadb -o yaml
apiVersion: v1
kind: Secret
metadata:
annotations:
meta.helm.sh/release-name: wordpress
meta.helm.sh/release-namespace: default
creationTimestamp: "2020-08-12T16:45:00Z"
labels:
app: mariadb
app.kubernetes.io/managed-by: Helm
chart: mariadb-7.5.1
heritage: Helm
release: wordpress
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
...
kubectl的输出包含当前在 Kubernetes 中存在的记录。自模板输出以来已添加了几个字段。一些字段(如注释)由 Helm 本身管理,其他字段(如managedFields和creationTimestamp)由 Kubernetes 管理。
再次强调,Helm 提供了设计用于简化调试的工具。在 helm get manifest 和 kubectl get 之间,您可以使用工具比较 Kubernetes 认为是当前对象与图表生成的对象之间的差异。当由 Helm 管理的资源在 Helm 外部手动编辑(例如使用 kubectl edit)时,这尤其有帮助。
使用 helm get,我们可以仔细检查单个发布。但接下来我们将介绍的工具将为我们提供发布版本的进展视图。在接下来的部分中,我们将查看 helm history 和 helm rollback。
历史和回滚
在本书中,我们区分了安装和修订版本。在本章中,我们使用了一个名为 mysite 的安装和另一个名为 wordpress 的安装。当我们之前运行 helm list 时,我们看到每个安装都有三个发布版本。此外,我们看到 wordpress 处于失败状态:
$ helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VER...
mysite default 3 2020-08-11 deployed drupal-7.0.0 9.0.0
wordpress default 3 2020-08-12 failed wordpress-9.3.11 5.4.2
我们可以调查 WordPress 的发布历史记录以查看发生了什么。为此,我们将使用 helm history:
$ helm history wordpress
REVISION UPDATED STATUS CHART APP VER DESCRIPTION
1 Wed Aug 12... superseded wordpress-9.3.11 5.4.2 Install complete
2 Wed Aug 12... deployed wordpress-9.3.11 5.4.2 Upgrade complete
3 Wed Aug 12... failed wordpress-9.3.11 5.4.2 Upgrade \
"wordpress" failed: cannot patch "wordpress" with kind Deployment: \
Deployment.apps "wordpress" is invalid: \
spec.template.spec.containers[0].imagePullPolicy: Unsupported value: \
"NoSuchPolicy": supported values: "Always", "IfNotPresent", "Never"
此命令的输出为我们提供了 wordpress 发布的良好历史记录。首先安装了它,然后升级并标记为已部署(这意味着升级成功)。但当再次升级时,该升级失败了。helm history 命令甚至给出了 Kubernetes 在标记发布为“失败”时返回的错误消息。
根据错误信息,我们知道发布失败是因为我们提供了无效的镜像拉取策略。所以当然,我们可以通过运行另一个 helm upgrade 来纠正这个问题。但想象一种情况,即错误的原因不容易获取。在诊断问题时,而不是将应用程序留在失败状态,最好是简单地回退到之前工作正常的发布版本。
helm rollback 的作用是这样的:
$ helm rollback wordpress 2
Rollback was a success! Happy Helming!
此命令告诉 Helm 检索 wordpress 版本 2 的发布,并将该清单重新提交给 Kubernetes。回滚不会恢复到集群的先前快照。Helm 并未跟踪足够的信息来执行此操作。它所做的是重新提交先前的配置,然后 Kubernetes 尝试重置资源以匹配该配置。
现在我们可以再次使用 helm history 来查看发生了什么:
REVISION UPDATED STATUS CHART APP VER DESCRIPTION
1 Wed Aug 12... superseded wordpress-9.3.11 5.4.2 Install complete
2 Wed Aug 12... superseded wordpress-9.3.11 5.4.2 Upgrade complete
3 Wed Aug 12... failed wordpress-9.3.11 5.4.2 Upgrade \
"wordpress" failed: cannot patch "wordpress" with kind Deployment: \
Deployment.apps "wordpress" is invalid: \
spec.template.spec.containers[0].imagePullPolicy: Unsupported value: \
"NoSuchPolicy": supported values: "Always", "IfNotPresent", "Never"
4 Wed Aug 12... deployed wordpress-9.3.11 5.4.2 Rollback to 2
回滚操作创建了一个新的修订版本(4)。由于回滚成功(并且 Kubernetes 接受了更改),该发布被标记为“已部署”。请注意,修订版本 2 现在被标记为“已取代”,而失败的发布 3 仍被标记为“失败”。
因为 Helm 保留了历史记录,所以即使回滚到已知良好的配置后,您仍然可以检查失败的发布。
在大多数情况下,helm rollback 是从灾难中恢复的好方法。但是,如果手动编辑了由 Helm 管理的资源,则可能会出现有趣的问题。回滚有时可能会导致一些意外行为,特别是如果 Kubernetes 资源已被用户手动编辑。如果手动编辑不与回滚冲突,Helm 和 Kubernetes 将尝试保留这些手动编辑。本质上,回滚将生成当前资源状态、失败的 Helm 发布和回滚到的 Helm 发布之间的三向差异。在某些情况下,生成的差异可能会导致回滚手工编辑的内容,而在其他情况下,这些差异将被合并。在最坏的情况下,一些手工编辑可能会被覆盖,而其他相关编辑会被合并,导致配置的不一致。这是 Helm 核心维护者建议不要手动编辑资源的许多原因之一。如果所有编辑都通过 Helm 进行,那么您可以有效地使用 Helm 工具,而无需猜测。
保留历史记录和回滚
在前一章中,我们看到helm uninstall命令有一个名为--keep-history的标志。通常,删除事件将销毁与该安装相关联的所有发布记录。但是,如果指定了--keep-history,即使已删除,您也可以查看安装的历史记录:
$ helm uninstall wordpress --keep-history
release "wordpress" uninstalled
$ helm history wordpress
REVISION UPDATED STATUS CHART APP V DESCRIPTION
1 Wed Aug 12... superseded wordpress-9.3.11 5.4.2 Install complete
2 Wed Aug 12... superseded wordpress-9.3.11 5.4.2 Upgrade complete
3 Wed Aug 12... failed wordpress-9.3.11 5.4.2 Upgrade \
"wordpress" failed: cannot patch "wordpress" with kind Deployment: \
Deployment.apps "wordpress" is invalid: \
spec.template.spec.containers[0].imagePullPolicy: Unsupported value: \
"NoSuchPolicy": supported values: "Always", "IfNotPresent", "Never"
4 Wed Aug 12... uninstalled wordpress-9.3.11 5.4.2 Uninstall complete
注意,最后一个发布现在标记为uninstalled。当保留历史记录时,您可以回滚已删除的安装:
$ helm rollback wordpress 4
Rollback was a success! Happy Helming!
现在我们可以看到一个新部署的发布5:
$ helm history wordpress
REVISION UPDATED STATUS CHART APP VER DESCRIPTION
1 Wed Aug... superseded wordpress-9.3.11 5.4.2 Install complete
2 Wed Aug... superseded wordpress-9.3.11 5.4.2 Upgrade complete
3 Wed Aug... failed wordpress-9.3.11 5.4.2 Upgrade \
"wordpress" failed: cannot patch "wordpress" with kind Deployment: \
Deployment.apps "wordpress" is invalid: \
spec.template.spec.containers[0].imagePullPolicy: Unsupported value: \
"NoSuchPolicy": supported values: "Always", "IfNotPresent", "Never"
4 Wed Aug... uninstalled wordpress-9.3.11 5.4.2 Uninstall complete
5 Wed Aug... deployed wordpress-9.3.11 5.4.2 Rollback to 4
但是如果没有--keep-history标志,这将无法工作:
$ helm uninstall wordpress
release "wordpress" uninstalled
$ helm history wordpress
Error: release: not found
$ helm rollback wordpress 4
Error: release: not found
深入探讨安装和升级
在第二章中,我们首次介绍了安装和升级 Helm 包,并在本章中探讨了帮助我们处理 Helm 安装的工具。为了结束这一章,我们将回到安装和升级,并看看一些高级特性。
--generate-name和--name-template标志
Kubernetes 工作方式的一个微妙危险与命名有关。Kubernetes 假设名称具有某些唯一性属性。例如,一个Deployment对象必须在其命名空间内具有唯一名称。也就是说,在命名空间mynamespace中,我不能有两个名为myapp的Deployment。但是我可以有一个名为myapp的Deployment和一个名为myapp的Pod。
这使得某些任务变得更加复杂。例如,自动部署事物的 CI 系统必须能够确保它给这些事物的名称在命名空间内是唯一的。解决此问题的一种方法是让 Helm 提供一个生成唯一名称的工具。(另一种方法是如果名称已经存在则始终覆盖。请参阅下一节了解该方法。)
Helm 为helm install提供了--generate-name标志:
$ helm install bitnami/wordpress --generate-name
NAME: wordpress-1597689085
LAST DEPLOYED: Mon Aug 17 11:31:27 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
使用 --generate-name 标志,我们不再需要在 helm install 的第一个参数中提供名称。Helm 根据图表名称和时间戳的组合生成一个名称。在前面的输出中,我们可以看到为我们生成的名称:wordpress-1597689085。
在 Helm 2 中,“友好名称”是使用形容词和动物名称生成的。由于有人抱怨发布名称不专业,因此在 Helm 3 中删除了该功能。目前没有重新启用此功能的方法。
然而,还有一个额外的标志允许您指定一个命名模板。--name-template 标志允许您像这样做:
$ helm install bitnami/wordpress --generate-name \
--name-template "foo-{{ randAlpha 9 | lower }}"
NAME: foo-yejpiyjmp
LAST DEPLOYED: Mon Aug 17 11:46:04 2020
NAMESPACE: default
在本例中,我们使用了名称模板 foo-{{ randAlpha 9 | lower }}。这使用 Helm 模板引擎为您生成一个名称。我们将在接下来的几章中介绍 Helm 模板引擎。但这里的名称模板的作用是:{{ 和 }} 标志着模板的开始和结束。在模板内部,我们调用 randAlpha 函数,请求从 a-z, A-Z 字符范围中获取一个 9 字符的随机字符串。然后,我们通过第二个函数 (lower) 将结果转换为小写。
查看前面示例的输出,{{ randAlpha 9 | lower }} 的结果是 yejpiyjmp。因此整个名称模板的结果是 foo-yejpiyjmp。
--create-namespace 标志
Kubernetes 中关于命名的另一个考虑与命名空间有关。前面,我们看到在同一命名空间内,同一种类的两个对象不能具有相同的名称。但 Kubernetes 也有全局名称的概念。CRD 和命名空间各自都有全局名称。
因此,命名空间在整个集群中必须是唯一的。
每当 Helm 遇到全局唯一名称时,它会采取一种防御性姿态。在后面的章节中,我们将看到图表如何处理全局唯一名称。但在这里,值得指出的是,Helm 3 默认假定如果您尝试将图表部署到一个命名空间中,那么该命名空间应已经存在。
例如,在一个新的集群上,这将失败:
$ helm install drupal bitnami/drupal --namespace mynamespace
Error: create: failed to create: namespaces "mynamespace" not found
它失败的原因是 mynamespace 尚未创建,并且Helm 不会自动创建命名空间。它不会创建命名空间,因为命名空间是全局的,安全的假设是,在创建命名空间时,可能需要访问控制(如 RBAC)和其他分配给它的事物,然后才能安全地在生产中使用。简而言之,它认为静默地创建命名空间是意外创建安全漏洞的机会。
然而,Helm 允许您通过明确声明要创建一个命名空间来覆盖此考虑:
$ helm install drupal bitnami/drupal --namespace mynamespace --create-namespace
NAME: drupal
LAST DEPLOYED: Mon Aug 17 11:59:29 2020
NAMESPACE: mynamespace
STATUS: deployed
通过添加 --create-namespace,我们已告知 Helm 我们知晓可能没有具有该名称的命名空间,并且我们只想要创建一个。当然,如果您在生产实例上使用此标志,请确保您有其他机制来确保对该新命名空间的安全性。
在 helm uninstall 上并没有类似的 --delete-namespace。这背后的原因在于 Helm 对全局对象的保护性措施。一旦创建了命名空间,可以向其中放入任意数量的对象,其中并非全部由 Helm 管理。当删除命名空间时,该命名空间中的所有对象也会被删除。因此,Helm 不会自动删除通过 --create-namespace 创建的命名空间。要删除命名空间,请使用 kubectl delete namespace(当然,在确保该命名空间中不存在重要对象之后)。
使用 helm upgrade --install
一些系统,如 CI 流水线,被用来在每次发生重要事件时自动安装或升级图表。例如,许多组织都有流水线,每当新代码上传到版本控制系统(如 Git)时触发。GitHub,一个流行的 Git 托管服务,甚至提供了工具,可以在代码更改合并时自动部署。
此类系统通常在无法查询 Kubernetes 的无状态平台上运行基本脚本。这类系统的用户请求 Helm 功能,允许在单个命令中支持 "安装或升级"。
为了促进这种行为,Helm 的维护者们在 helm upgrade 命令中添加了 --install 标志。helm upgrade --install 命令将在不存在该名称的发布时安装一个发布,或者在找到该名称的发布时升级一个发布。在幕后,它通过查询 Kubernetes 是否存在具有给定名称的发布来工作。如果该发布不存在,则切换出升级逻辑并进入安装逻辑。
例如,我们可以使用完全相同的命令顺序运行安装和升级:
$ helm upgrade --install wordpress bitnami/wordpress
Release "wordpress" does not exist. Installing it now.
NAME: wordpress
LAST DEPLOYED: Mon Aug 17 13:18:14 2020
NAMESPACE: default
STATUS: deployed
...
$ helm upgrade --install wordpress bitnami/wordpress
Release "wordpress" has been upgraded. Happy Helming!
NAME: wordpress
LAST DEPLOYED: Mon Aug 17 13:18:43 2020
NAMESPACE: default
STATUS: deployed
正如我们在输出的第一行中所见,第一次运行命令时进行了安装,而第二次运行命令时进行了升级。
然而,这个命令确实存在一些危险。Helm 无法确定您在 helm upgrade --install 中提供的安装名称是否属于您打算升级的发布版本,或者只是碰巧与您想要安装的内容同名。对这个命令的粗心使用可能导致用一个安装覆盖另一个安装。这就是为什么这不是 Helm 的默认行为。
--wait 和 --atomic 标志
helm install 和 helm upgrade 的另一对重要标志修改了 Helm 操作的成功标准。这些是 --wait 和 --atomic 标志。
--wait标志在几个方面修改了 Helm 客户端的行为。首先,在 Helm 运行安装时,它会保持活动状态一段固定的时间窗口(可以使用--timeout标志修改),在此期间它会监视 Kubernetes。它会轮询 Kubernetes API 服务器以获取有关由图表创建的所有 Pod 运行对象的信息。例如,DaemonSet、Deployment和StatefulSet都会创建 Pod。因此,使用--wait的 Helm 将跟踪这些对象,直到它们创建的 Pod 被 Kubernetes 标记为Running。
在正常安装或升级中,Helm 会在 Kubernetes API 服务器接受清单后立即将发布标记为成功。这类似于将包成功安装视为将包内容写入正确存储位置的软件包管理器。
但是使用--wait时,安装的成功标准会发生变化。除非(1)Kubernetes API 服务器接受清单,并且(2)图表创建的所有 Pod 在 Helm 超时到期之前达到Running状态,否则不会认为图表安装成功。
因此,使用--wait进行安装可能会因多种原因而失败,包括网络延迟、慢调度器、繁忙节点、慢镜像拉取以及容器无法启动的明确失败。
这种行为被视为一种理想的结果,操作员使用helm install --wait确保图表不仅成功安装,而且生成的应用程序正确启动。然而,这在故障排除时引入了一些复杂因素。瞬态故障可能导致 Helm 失败,但稍后由 Kubernetes 解决。例如,延迟的镜像拉取可能导致 Helm 发布被标记为失败,尽管几分钟后镜像拉取完成并且应用程序可以启动。
考虑到这一点,helm install --wait是确保发布完全运行的好工具。但在自动化系统(如 CI)中使用时,可能会导致偶发失败。建议在 CI 中使用--wait的一个方法是设置较长的--timeout(五到十分钟),以确保 Kubernetes 有足够时间解决任何瞬态故障。
第二种策略是使用--atomic标志,而不是--wait标志。该标志引起的行为与--wait相同,除非发布失败。然后,它不会将发布标记为failed并退出,而是自动回滚到上次成功的发布。在自动化系统中,--atomic标志对于故障更具抵抗力,因为其最终结果不太可能是失败。(请注意,回滚成功也无法保证。)
就像--wait可以因 Kubernetes 自身解决的传递原因将发布标记为失败一样,--atomic可能因同样的原因触发不必要的回滚。因此,建议在与 CI 系统一起使用--atomic时使用更长的--timeout持续时间。
使用--force和--cleanup-on-fail进行升级
我们将讨论的最后两个标志修改了 Helm 处理升级细节的方式。
当 Helm 升级管理 pods 的资源(如Pod、Deployment和StatefulSet)时,--force标志会修改 Helm 的行为。通常情况下,当 Kubernetes 收到修改此类对象的请求时,它会确定是否需要重新启动此资源管理的 pods。例如,一个Deployment可能运行五个 pod 的副本。但如果 Kubernetes 收到Deployment对象的更新,它只会在修改了特定字段时重新启动那些 pods。
有时候,Helm 用户想要确保 pod 被重新启动。这时就需要用到--force标志。与修改Deployment(或类似对象)不同,它会删除并重新创建。这会强制 Kubernetes 删除旧的 pods 并创建新的。设计上,使用--force会导致停机时间。尽管通常只有几秒钟的停机时间,但仍然会有停机。建议只在情况明确需要时使用--force,而不是默认选项。例如,核心维护者不建议在部署到生产环境的 CI 流水线中使用--force。
修改升级行为的另一种方法是使用--cleanup-on-fail标志。与--force类似,此标志指示 Helm 执行额外的工作。
考虑一种情况,您安装了一个创建一个 Kubernetes Secret 的图表。创建了图表的新版本,并创建了第二个Secret。但在安装过程中的某个时候,Helm 遇到错误并标记了发布为失败。第二个Secret有可能被悬而未决。如果使用了--wait或--atomic,这种情况更有可能发生,因为这些操作可能在 Kubernetes 接受清单并创建资源后失败。
在失败时,--cleanup-on-fail标志将尝试修复此情况。它会请求删除在升级期间新创建的每个对象。使用它可能会稍微增加调试的难度(特别是如果失败是由于新创建的对象导致的),但如果您不想冒在失败后留下未使用的对象的风险,这是非常有用的。
结论
Helm 命令行工具提供了许多有用的命令。虽然基本命令已在前一章介绍过,本章重点介绍了 Helm 中的其他一些有用命令。最后,我们还重新审视了安装和升级命令,了解了与这些命令一起使用的一些更复杂的特性。
然而,并非所有命令都在这里讨论过。在接下来的章节中,我们将查看用于创建和打包图表的命令,用于签名和验证软件包的命令,以及更多处理仓库的命令。
第四章:构建一个图表
图表是 Helm 的核心。除了将它们安装到 Kubernetes 集群中或管理已安装的图表实例外,您还可以创建新图表或更改现有图表。在接下来的三章中,我们将详细介绍有关图表的许多细节,包括创建图表、图表内的元素、为 Kubernetes 清单编写模板、测试图表、依赖关系等。
在本章中,您将学习如何创建一个新图表,并了解图表的许多部分。这将包括使用几个内置命令来帮助您进行图表开发过程。
图表是 Helm 处理的包。它们在概念上类似于 APT 用于 Debian 包或 Homebrew 用于 macOS 的 Formula。但概念上的相似之处仅此而已。图表旨在针对具有其独特风格的平台 Kubernetes。图表的核心是模板,用于生成可以在集群中安装和管理的 Kubernetes 清单。
在我们深入讨论第五章中的模板之前,让我们先创建一个基本的完全功能图表。为此,我们将涵盖一个名为anvil的示例图表。通过使用这个图表,您将了解如何使用 Helm 生成图表、图表及其内部文件的结构、打包图表以及检查图表的问题。有关此图表的在线来源,请参考https://github.com/Masterminds/learning-helm/tree/main/chapter4/anvil。
图表创建命令
Helm 包括create命令,使您能够轻松创建自己的图表,这是一个很好的起点。该命令会创建一个新的 Nginx 图表,您可以自行命名,遵循图表布局的最佳实践。由于 Kubernetes 集群可以使用不同的方法来公开应用程序,因此该图表使得 Nginx 如何暴露给网络流量可配置,以便在各种集群中进行公开。
create命令会为您创建一个图表,并包含所有所需的图表结构和文件。这些文件被记录下来,帮助您了解所需内容,提供的模板展示了多个 Kubernetes 清单如何协同工作以部署应用程序。此外,您可以立即安装和测试此图表。
在本章的整个过程中,我们将看一个名为anvil的示例应用程序。这是一个简单的应用程序,将向您展示图表的结构,并为您提供改变不同应用程序的图表的机会。要创建新图表,请从命令提示符中运行以下命令:
$ helm create anvil
这将在当前目录的子目录下创建一个名为anvil的新图表。
新图表是一个包含多个文件和文件夹的目录。这并不包括每个文件和文件夹——您将在接下来的几章中发现更多内容。这些是实现图表功能所需的基本文件。
anvil
├── Chart.yaml 
├── charts 
├── templates 
│ ├── NOTES.txt 
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml 
└── values.yaml 
Chart.yaml 文件包含图表的元数据和一些功能控制。
依赖图表可以选择性地存放在charts目录中。图表依赖关系在第 6 章中介绍。目前这将是一个空目录。
用于生成 Kubernetes 清单的模板存储在templates目录中。
NOTES.txt 文件是一个特殊的模板。安装图表时,会渲染并显示NOTES.txt模板,而不是将其安装到集群中。
模板可以包含测试,这些测试不会作为install或upgrade命令的一部分安装。此图表包含一个通过helm test命令使用的测试。有关测试的详细信息,请参阅第 6 章。
Helm 渲染清单时传递给模板的默认值存储在values.yaml文件中。在实例化图表时,可以覆盖这些值。
您可以通过运行以下命令安装此新创建的图表,无需任何修改:
$ helm install myapp anvil
运行此命令时,Helm 将在集群中使用当前配置的连接和上下文创建一个名为myapp的图表实例。Helm 使用的配置与你使用kubectl命令行应用程序时的配置相同。在该命令中,anvil的最后一个参数是图表所在目录。
此命令的输出包含通过渲染NOTES.txt模板生成的内容,如下所示:
NAME: myapp
LAST DEPLOYED: Sun Apr 5 08:12:59 2020
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=anvil,app.kubernetes.io/instance=myapp" ↵
-o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:80
NOTES 部分包含有关连接到应用程序的信息。根据实例化图表时传递的值,此信息可能会有所不同。此图表可以配置为使用 ClusterIP、NodePort、LoadBalancer 和 Ingress 公开应用程序。默认情况下使用 ClusterIP。
如果按照备注中的说明进行操作,您将看到默认的 Nginx 网页,以展示它正在运行,如图 4-1 所示。

图 4-1. 访问运行中应用程序时显示的默认 Nginx 网页
公开应用程序的方法与内置的 Kubernetes 资源类型相关,而不是应用程序的功能。这使它们可以轻松移植到您的自定义应用程序中。公开应用程序的方法包括:
ClusterIP
Kubernetes Service 资源类型的配置选项,用于在集群级内部 IP 地址上公开服务。
NodePort
Kubernetes Service 资源的替代选项,用于在每个节点的静态端口上公开服务。同时会自动创建 ClusterIP。
LoadBalancer
Kubernetes Service 配置选项,使用主机提供商提供的负载均衡器在外部公开应用程序。
Ingress
Ingress 资源是 Service 的附加资源,通过 HTTP 和 HTTPS 公开服务。要使其工作,需要一个 Ingress 控制器,比如 ingress-nginx。
如果你安装了这个图表到你的集群来测试它,你可以通过运行以下命令从你的集群中删除该实例:
$ helm delete myapp
注意
当图表安装时,Nginx 使用的镜像默认是来自 Docker Official Images 的最新版本。如果你所使用的 Kubernetes 集群无法访问 hub.docker.com,你将无法安装该镜像。你需要将镜像设置为你的集群可以访问的镜像。
现在一个可工作的图表已经被搭建好了,让我们来看看里面有什么,并修改为 Anvil 应用程序。
Chart.yaml 文件
在 anvil 目录中查看,你会找到一个名为 Chart.yaml 的文件。Chart.yaml 文件告诉 Helm 和其他工具有关你的图表的信息。其他工具包括 Kubeapps(本地目录和应用程序安装程序)、Artifact Hub(云原生工件列表)等等。
当你打开 Chart.yaml 文件时,你将看到示例 Example 4-1 中显示的内容。
示例 4-1. 生成的 Chart.yaml 文件
apiVersion: v2 
name: anvil 
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into ↵
versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer.↵
They're included as
# a dependency of application charts to inject those utilities and functions ↵
into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be ↵
deployed.
type: application
# This is the chart version. This version number should be incremented each ↵
time you make changes
# to the chart and its templates, including the app version.
version: 0.1.0 
# This is the version number of the application being deployed. This version ↵
number should be
# incremented each time you make changes to the application. Versions are not ↵
expected to
# follow Semantic Versioning. They should reflect the version the application ↵
is using.
appVersion: 1.16.0
apiVersion 告诉 Helm 图表使用的结构。apiVersion 的值为 v2 是为 Helm v3 设计的。
名称用于在各个地方标识图表。
图表可以有多个版本。Helm 使用版本信息来排序和识别图表。
这个 Chart.yaml 文件包含许多键,其中只有三个是必需的。apiVersion 属性告诉 Helm 这是哪个版本的图表。Helm v3 可以处理 apiVersion 是 v1 或 v2 的图表。v1 图表是设计用于 Helm 的早期版本。如果你的图表设计用于 Helm v3 或更新版本,你应该将其设置为 v2。name 的值通常用作 Kubernetes 资源名称的一部分。这意味着名称仅限于小写字母数字、- 和 . 字符,并且必须以字母数字字符开头和结尾。名称通常为小写字母数字字符。最后一个必需的键是 version,包含图表的版本。版本应遵循语义化版本控制,这在 Chapter 2 中有所涵盖。
你可能会注意到 Chart.yaml 文件的样式与 Kubernetes 清单文件类似但略有不同。Chart.yaml 文件与自定义资源的格式不同,但包含一些相同的属性。最初设计 Chart.yaml 文件是在 Kubernetes 自定义资源定义出现之前的 2015 年。尽管 Helm 在主要版本上有所进展,但随着时间的推移,它保持了一定程度的向后兼容性,以避免过度打扰用户。这导致了 Chart.yaml 文件格式与 Kubernetes 清单之间的差异。
Chart.yaml 文件还包含描述信息,这在用户界面中呈现时非常有用。示例 4-1 中的 description 字段就是这样一个字段,但你可以添加额外的字段,比如以下内容:
-
home是图表或项目主页的 URL。 -
icon是一个图像(如 PNG 或 SVG 文件),以 URL 形式表示。 -
maintainers包含维护者列表。列表中的每个维护者可以有姓名、电子邮件和 URL。 -
keywords可以包含有关项目的关键字列表。 -
sources是项目或图表源代码的 URL 列表。
Chart.yaml 文件中属性的完整描述可以参考 附录 A。
生成的 Chart.yaml 文件可以用于修改 Anvil 应用程序。以下修改更新了所需字段,添加了一些描述性文件并删除了注释:
apiVersion: v2
name: anvil
description: A surprise to catch something speedy.
version: 0.1.0
appVersion: 9.17.49
icon: https://wile.example.com/anvil.svg
keywords:
- road runner
- anvil
home: https://wile.example.com/
sources:
- https://github.com/Masterminds/learning-helm/tree/main/chapter4/anvil
maintainers:
- name: ACME Corp
email: maintainers@example.com
- name: Wile E. Coyote
email: wile@example.com
生成的 Chart.yaml 文件中的一个属性,但在 Anvil 的文件中没有的是 type。Anvil 是一个应用程序,type 字段的默认值是应用程序,因此 type 字段不是必需的。另一种类型的图表是库图表,详细说明在 第七章 中。
appVersion 属性是独特的。它既是描述性的,也经常在模板中使用。appVersion 属性代表主要或组合应用程序的版本。例如,如果打包的应用程序是 WordPress,则它将是 WordPress 的版本。
提示
icon 属性是一个 URL,可以包含数据 URL。数据 URL 允许你在 URL 形式中嵌入小文件。如果 logo 是一个小的 SVG 文件,这尤其有用。如果图表可能在空隔离的本地环境中运行,或者你不希望用户界面不断从你的 Web 服务器下载文件,数据 URL 是一个有用的选择。
修改模板
要修改这个图表以适应 Anvil 应用程序或你自己的定制应用程序,你需要理解并修改模板。默认情况下,helm create 命令创建的模板将 Nginx 作为一个无状态应用程序运行。在我们正在处理的示例中,Nginx 将需要被 Anvil 替换。
Helm 是用 Go 编程语言编写的,Go 包含模板包。Helm 利用文本模板包作为其模板的基础。这种模板语言类似于其他模板语言,包括循环、if/then 逻辑、函数等。下面是一个 YAML 文件的示例模板:
product: {{ .Values.product | default "rocket" | quote }}
在这个 YAML 文件中,有一个名为 product 的键。该值是使用模板生成的。{{ 和 }} 是进入和退出模板逻辑的开头和结尾括号。模板逻辑由三部分组成,通过 | 分隔。这称为管道,在类 Unix 系统中的管道中起作用。左侧函数的值或输出作为管道中下一个项目的最后一个参数传递。在这种情况下,管道从 .Values.product 属性中的值开始。这来自在渲染模板时传递的数据对象。将此数据的值作为最后一个参数传递给 default 函数,这是 Helm 提供的函数之一。如果传入的值为空,则 default 函数使用默认值 "rocket",确保有一个值。然后将其发送到 quote 函数,该函数确保字符串在写入模板之前被包装在引号中。
在 .Values.product 开头的 . 很重要。这被认为是当前作用域中的根对象。.Values 是根对象上的一个属性。
部署
Helm 图表可以包含任何你可能使用的 Kubernetes 资源类型的模板。这包括 StatefulSet、Job、PersistentVolumeClaim、Service 等等。使用 helm create 创建的图表旨在作为 Kubernetes 部署运行无状态服务。我们在这里使用的示例应用程序是无状态应用程序,这意味着它将作为部署工作得很好。
要理解 Deployment 模板,我们可以查看图表的 templates 目录中的 deployment.yaml 文件。以下是 Deployment 的模板版本,包括 spec 部分:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "anvil.fullname" . }}
labels:
{{- include "anvil.labels" . | nindent 4 }}
这看起来非常类似于 Kubernetes 清单的开头。它具有 apiVersion、kind 和 metadata。一旦进入 metadata,你会注意到开始进行模板化。
提示
如果你对 Kubernetes 部署的结构不熟悉,可以在 Kubernetes 文档 中阅读相关内容。
include 模板函数可以在一个模板中包含另一个模板的输出,这在管道中起作用。include 函数的第一个参数是要使用的模板的名称。作为第二个参数传入的 . 是根对象。这样传入是为了在调用的模板中可以使用根对象上的属性和函数。
anvil.fullname 和 anvil.labels 是通过 _helpers.tpl 文件在图表中包含的两个可重用模板。(名称开头的下划线会使它在目录列表中排在顶部,因此您可以轻松找到它,尽管 Helm 不会将它们渲染为 Kubernetes 清单,但确实使这些模板可供使用。)anvil.fullname 提供基于实例化图表时选择的名称,而 anvil.labels 遵循 Kubernetes 最佳实践提供标签。这些函数在 第五章 中有更详细的介绍。
在模板的 metadata 部分之后是 spec 部分,内容如下:
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "anvil.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "anvil.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "anvil.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default↵
.Chart.AppVersion }}" 
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
容器镜像的位置和版本可以通过值进行配置。
spec 部分完成了部署。大部分内容填充在 .Values 的属性中。有一些元素是硬编码的,例如用于公开应用程序的端口。Anvil 在端口 80 上通过 HTTP 公开,因此我们不需要更改端口。如果您的容器公开在不同的端口上,您需要在这里进行更改。
容器的 image 值是使用值设置的。您不会在这里找到硬编码的镜像位置。这对于在实例化图表时需要将镜像位置设置为不同位置的情况非常有用。这意味着我们需要在默认值中更改位置。
.Values 上的属性是基于多个因素计算的。默认值和起点基于图表中 values.yaml 文件提供的值。values.yaml 文件将在下一节中介绍。这些值可以被在实例化图表时传入的值覆盖。helm CLI 有用于直接传递值的标志(即 --set、--set-file 和 --set-string),或者传递一个包含值的文件(即 -f 或 --values)。这些值被合并在一起,后传入的值优先。
模板是一个广泛的主题,通常占据图表的大部分。第五章 专门讨论模板。
使用 Values 文件
当有人在 Kubernetes 集群中从图表实例化应用程序时,并不需要提供模板中使用的所有值。如果他们这样做了,会导致用户体验困难。这就是 values.yaml 文件的作用所在。
图表包括一个 values.yaml 文件,与根目录中的 Chart.yaml 文件并列。values.yaml 文件包含图表使用的默认值,并且它是可以传递给图表的自定义值的一种形式文档化。
values.yaml是一个非结构化的 YAML 文件。有一些常见和有用的做法,很快将介绍,但格式中没有必需的内容。这使图表创建者能够提供适合他们的结构和信息。values.yaml文件可以包含许多内容,从用于 Kubernetes 清单属性的简单替换到需要特定于应用程序的业务逻辑元素。
容器镜像
helm create创建的values.yaml文件的开头部分包含有关图像信息以及一些开头文档和副本信息:
# Default values for anvil.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: ghcr.io/masterminds/learning-helm/anvil-app 
pullPolicy: IfNotPresent 
# Overrides the image tag whose default is the chart version.
tag: "" 
imagePullSecrets: [] 
镜像的位置。已更新以反映 Anvil 的位置。
IfNotPresent策略意味着将使用正在使用的版本在 Kubernetes 集群中缓存镜像。Always是另一个选项,它会绕过缓存并始终从存储库下载。
默认情况下,此图表使用appVersion作为标记。如果指定了镜像标记,则使用它而不是appVersion。
当需要凭据访问受用户名和密码保护的容器注册表位置时,使用拉取凭据列表。
此图表及其值代表打包为单个镜像的应用程序。在values.yaml文件中使用的模式考虑到这一点。例如,只有一个镜像位置。如果您的应用程序有多个镜像,则每个镜像将有一个包含大部分信息的部分。这包括replicaCount,这是创建Deployment时 Kubernetes 将使用的副本数。
image部分包含有关镜像的详细信息。repository包含要使用的镜像位置,而pullPolicy告诉 Kubernetes 多久获取或缓存镜像。如果使用像stable这样的移动标签,则应将pullPolicy设置为Always,以便捕获更改。由于正在使用版本,因此默认的pullPolicy设置为IfNotPresent,以便在可用时可以使用缓存版本。tag属性提供了设置与Chart.yaml文件中设置的appVersion不同的标记的机会。
您可能注意到在获取镜像时没有设置摘要的方法。当图像位于不同的存储库中时,摘要可能不同。例如,如果将 Anvil 镜像从 Docker Hub 复制到 Quay,另一个图像存储库,则对于相同的图像,即使标签和内容保持不变,摘要也会更改。如果需要,在图表中添加对摘要的支持的示例将在第五章中提供。
如果需要从带有访问控制的容器注册表中拉取图像,Kubernetes 需要知道如何执行此操作。这通过使用拉取密钥来完成。imagePullSecrets允许您列出具有访问私有注册表权限的拉取密钥的名称。参考创建拉取密钥的文档。
生成的图表中内置了一些安全考虑,可以启用或者进行其他配置。图表实例的服务账户默认创建,而其他选项则是自选的。以下是helm create生成的内容:
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname ↵
template
name:
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
您会注意到配置中的大多数属性都是注释并且是非活动状态的。当图表以注释值渲染时,这些属性没有值。值为空。通过将结构和值作为注释,图表记录了结构和默认值,可以使用但没有打开这些功能。
公开服务
values.yaml文件的下一部分处理将应用程序暴露给其他人消费的问题:
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
在 Kubernetes 中,有两个内置对象可以用来公开应用程序。第一个是Service。service属性允许您选择使用的Service类型。默认情况下使用ClusterIP,还可以使用其他选项,如NodePort和LoadBalancer。在service部分的少量 YAML 行与生成的service.yaml模板配对,用于创建上传到 Kubernetes 的完整 Service 清单。
第二个内置对象是Ingress清单,可以与一个Service配对,而且图表有能力生成它们。Ingress配置提供了展示图表中常见模式的一种方式:使用enabled属性来控制功能的开启和关闭。在这种情况下,ingress.enabled被设置为false。当 Helm 渲染模板并看到 false 的值时,Ingress清单将被跳过。这是由于在生成的ingress.yaml文件中Ingress模板中使用了if逻辑语句。
资源限制
在生产环境中运行应用程序时,设置资源限制是一个好习惯。例如,防止一个容器中的内存泄漏影响其他容器。当图表作者创建一个他人将要使用的图表时,他们可能不知道它将安装在何处以及那里会有多少资源可用。这可能会安装在开发人员的笔记本电脑上,或者在大型生产服务器上安装?为了处理环境中的这种变化,建议设置资源限制,然后将其转换为注释。这可以在values.yaml文件的下一部分找到:
resources: {}
# We usually recommend not to specify default resources and to leave this as
# a conscious choice for the user. This also increases chances charts run on
# environments with little resources, such as Minikube. If you do want to
# specify resources, uncomment the following lines, adjust them as necessary,
# and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
安装应用程序的人使用这些数字作为实例化图表时的推荐值。这些数字是为一个简单的 Nginx 设置而设置的默认值。它们适用于 Anvil 应用程序。如果你的应用程序需要不同的值,你需要更新这些值。
工作负载有能力通过设置节点选择器、容忍性和亲和性来指定它们在集群中执行的详细信息。尽管这些高级特性经常不被使用,但将它们包含在图表中对于那些需要它们的人是一个好主意。生成的 values.yaml 文件和模板考虑了这一点。以下示例生成了这些高级特性的 YAML 键。默认情况下,这些值为空,期望安装图表的人根据他们的安装情况设置适当的值:
nodeSelector: {}
tolerations: []
affinity: {}
打包图表
你可以将图表的文件和目录打包到单个存档文件中。这对许多情况都很有用,包括:
-
分发给其他人。包管理器的一个强大之处在于,了解如何运行应用程序的人能够打包它,以便其他没有平台或应用程序详细知识的人也能运行它。
-
当一个应用程序的版本需要经过多环境测试流程时。这种流程的一个示例是存在开发、质量保证(QA)和生产环境,应用程序需要在进入生产之前通过 QA。
-
当开发多服务应用程序时,开发人员需要在其开发设置中运行由其他人构建或处理的服务。
在这些情况下,将图表作为单个文件传递比传递目录结构通常更为简单。
图表版本带来了分发和消费图表的另一个考虑因素。你或者消费你的图表的人可能需要使用图表的不同版本。因此,使用图表仓库或开放容器倡议(OCI)注册表来存储和共享不同版本是很有用的,详见第七章。在这些环境中,为每个版本存储和共享多个文件的目录结构远非简单。
Helm 能够构建图表存档。每个图表存档都是一个带有扩展名 .tgz 的经过 gzip 压缩的 TAR 文件。任何能够创建、提取和处理 gzip 压缩的 TAR 文件的工具都可以与 Helm 的图表存档一起使用。
当 Helm 生成存档文件时,它们的命名模式为 chart name-version.tgz。Helm 在处理这些存档时期望使用相同的模式。chart name 是你在 Chart.yaml 文件中找到的名称,version 是图表的版本。这样可以使相同图表的多个版本并存。你可以通过运行以下命令将 Anvil 打包为存档文件:
$ helm package anvil
在这种情况下,anvil是位于anvil图表源位置的路径。默认情况下,当您运行命令时,helm package命令将把存档放置在您所在的目录中。
在打包图表时,您可以使用一些有用的标志:
--dependency-update (-u)
在创建存档之前,告诉 Helm 更新依赖图表。这将更新Chart.lock文件并将依赖图表的副本放置在chart目录中。有关详细信息,请参阅第六章。
--destination (-d)
允许您设置放置图表存档的位置,如果该位置与当前工作目录不同的话。
--app-version
可用于设置Chart.yaml文件的appVersion属性。如果您为容器内运行的应用程序的每个新版本创建图表的新版本而且没有其他更改,则特别有用。自动化过程可以使用类似此标志作为构建新版本的一部分。
--version
更新图表的版本。如果您正在使用命令行更新appVersion作为打包图表过程的一部分,则这很有用。
用于 Pretty Good Privacy(PGP)签署图表的标志
Helm 图表可以进行加密签名和验证。package命令具有用于签名过程的标志,而install和upgrade等命令具有用于验证过程的标志。有关此过程的详细信息,请参阅第六章。
有时您会在图表目录中有不想包含在图表存档中的文件。在图表目录中,可以选择有一个.helmignore文件。这类似于 Git 的.gitignore文件。之前使用的helm create命令创建了一个具有以下内容的文件:
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
这些扩展和模式中的许多可能看起来很熟悉,因为它们来自各种版本控制系统和代码编辑器。
当创建图表存档时,通常不希望包含版本控制系统数据等元素。.helmignore文件提供了一个地方来指定要跳过的内容。此文件需要位于图表的顶层目录中。
Helm 旨在与存档文件的方式相同地与目录结构一起工作。像helm install和helm lint这样的命令可以传递一个存档文件,方式与传递目录相同,稍后会详细介绍。
Linting Charts
在开发图表时,特别是在使用 YAML 模板时,很容易出错或遗漏某些内容。为帮助您捕捉错误、漏洞、样式问题和其他可疑元素,Helm 客户端包含一个 linter。此 linter 可用于图表开发以及任何测试过程的一部分。
要使用 linter,请在作为目录或打包存档的图表上使用lint命令:
$ helm lint anvil
==> Linting anvil
1 chart(s) linted, 0 chart(s) failed
第一行是您运行的命令,而以下几行是 Helm 输出的内容。在这种情况下,未发现任何问题。您可以在像前一节中的存档文件上使用此命令。为此,请将 anvil 参数更改为图表的目录位置,并设置为存档文件 anvil-0.1.0.tgz。
此命令能够在单个命令中检查多个图表。例如,如果您有第二个名为 mychart 的图表,并希望与 anvil 一起进行检查,您可以运行以下命令:
$ helm lint anvil mychart
Helm 提供的有关图表的三个可操作反馈级别是信息、警告和错误。信息级反馈是信息性的;可以使用信息级反馈安装图表。信息级反馈使 Helm 的退出代码为 0。错误级反馈意味着图表存在问题。如果图表生成了 Kubernetes 的无效清单,比如 YAML 无效,Helm 将生成错误。错误会导致 Helm 的退出代码为非零,这对于捕获自动化测试工具中的问题非常有用。中间是警告消息。这些消息涉及可能导致问题的发现。默认情况下,警告消息会使 Helm 的退出代码为 0,但 Helm 添加了一个 --strict 标志,使退出代码为非零。您可以选择如何在自动化中处理这些消息。
在这种情况下,anvil 图表没有发现任何问题。由 helm create 创建的默认图表会有一个关于 Chart.yaml 文件中缺少 icon 属性的信息提示。这是一个信息级别的通知,以便人们知道它确实缺少。缺少图标不会影响图表的操作,但会影响其在用户界面中的显示方式。
结论
当您使用 helm create 命令创建应用程序的简单图表时,过程非常简单。即使您的应用程序更复杂,图表的结构也能够容纳它们,并且 helm create 命令可以帮助您。通过本章中进行的一些小修改,您可以使用 helm install 安装 Anvil 图表,并在群集中查看自定义应用程序运行情况。您可以使用相同的流程创建自己的图表。
在下一章中,您将了解如何创建模板,重点介绍模板语言的工作原理以及如何将其应用于存储在图表中的 Kubernetes 模板。模板通常是图表中最大的部分,您在创建模板时将花费最多的时间。了解您在创建模板时可用的内容将使开发过程更快、更轻松。
第五章:开发模板
模板是 Helm 图表的核心,它们占据了图表中大部分文件和内容。这些文件存放在templates目录中。当您运行helm install和helm upgrade等命令时,Helm 会渲染这些模板并将它们发送到 Kubernetes。如果使用helm template命令,则会将模板渲染并显示为输出(即发送到标准输出)。
模板引擎支持多种构建模板的方式。在简单情况下,您可以用用户提供或values.yaml文件中传递的值替换 Kubernetes 清单 YAML 文件中的值。在更复杂的情况下,您可以构建逻辑到模板中,简化图表消费者需要输入的内容。或者您可以构建能够配置应用程序本身的功能。
在本章中,您将学习如何开发模板以及理解模板语法的工作原理。我们还将介绍一些 Helm 添加到模板中的酷炫功能,使您能够与 YAML 一起工作并与 Kubernetes 交互。在此过程中,我们将看一些您可以应用于自己模板的模式。
模板语法
Helm 使用 Go 标准库提供的 Go 文本模板引擎。该语法用于kubectl(Kubernetes 的命令行应用程序)模板,Hugo(静态站点生成器)以及许多其他使用 Go 构建的应用程序。模板引擎在 Helm 中的设计是为了处理各种类型的文本文件。
您不需要了解 Go 编程语言来开发模板。模板引擎中有一些 Go 特有的东西,但如果您不了解 Go,可以将它们视为模板语言的细微差别。在学习开发模板时,我们会特别指出它们。
动作
逻辑、控制结构和数据评估由{{ 和 }} 包裹。这些称为动作。任何不在动作内的内容都会被复制到输出中。
当花括号用于开始和结束动作时,它们可以伴随 - 以删除前导或尾随空格。以下示例说明了这一点:
{{ "Hello" -}} , {{- "World" }}
此生成的输出为“Hello,World.” 从 - 一直到下一个非空白字符,侧面的空白已被删除。- 和其余动作之间需要有 ASCII 空格。例如,{{–12}} 评估为 –12,因为 - 被视为数字的一部分而不是括号的一部分。
在动作中,您可以利用各种功能,包括管道、if/else 语句、循环、变量、子模板和函数等。将它们结合使用提供了一种强大的模板编程方式。
Helm 传递给模板的信息
当 Helm 渲染模板时,它将一个数据对象传递给模板,您可以访问该对象中的信息。在模板中,该对象表示为 .(即一个句点)。它被称为点。此对象具有广泛的可用信息。
在 第四章 中,您已经看到 values.yaml 文件中的值如何作为 .Values 上的属性可用。.Values 上的属性完全基于 values.yaml 文件中的值以及传递到图表中的值,每个图表的属性都不具有模式,并且因图表而异。
除了值之外,关于发布的信息,正如 第二章 中首次描述的那样,可以作为 .Release 的属性访问。此信息包括:
.Release.Name
发布名称。
.Release.Namespace
包含正在发布到的命名空间。
.Release.IsInstall
设置为 true 时,表示发布的是一个工作负载。
.Release.IsUpgrade
设置为 true 时,表示发布是升级或回滚。
.Release.Service
列出执行发布的服务。当 Helm 安装图表时,此值设置为 "Helm"。不同的应用程序,即构建在 Helm 上的应用程序,可以将此值设置为它们自己的值。
Chart.yaml 文件中的信息也可以在 .Chart 数据对象中找到。此信息确实遵循 Chart.yaml 文件的模式。包括:
.Chart.Name
包含图表的名称。
.Chart.Version
图表的版本。
.Chart.AppVersion
应用程序版本(如果已设置)。
.Chart.Annotations
包含注释的键/值列表。
可以在 Chart.yaml 文件中的每个属性都可以访问。名称不同之处在于它们在 Chart.yaml 中以小写字母开头,但作为 .Chart 对象属性时以大写字母开头。
如果要从 Chart.yaml 文件传递自定义信息到模板,则需要使用注释。.Chart 对象仅包含在模式中的 Chart.yaml 文件中的字段。您不能添加新字段以传递它们,但可以将自定义信息添加到注释中。
不同的 Kubernetes 集群可以具有不同的能力。这可能取决于您使用的 Kubernetes 版本或安装的自定义资源定义(CRD)。Helm 作为 .Capabilities 的属性提供了有关集群能力的一些数据。Helm 在部署应用程序时会查询您部署的集群以获取此信息。包括:
.Capabilities.APIVersions
包含集群中可用的 API 版本和资源类型。您将在稍后学习如何使用此信息。
.Capabilities.KubeVersion.Version
完整的 Kubernetes 版本。
.Capabilities.KubeVersion.Major
包含主要的 Kubernetes 版本。由于 Kubernetes 的主要版本未增加,因此设置为 1。
.Capabilities.KubeVersion.Minor
集群中使用的 Kubernetes 的次要版本。
当使用helm template时,Helm 不会像对helm install或helm upgrade那样对集群进行调查。处理helm template时提供给模板的能力信息是 Helm 已经知道的符合 Kubernetes 集群的默认信息。Helm 之所以这样工作,是因为template命令预期仅用于处理模板,并且以一种不会意外泄露配置的方式进行处理。
图表可以包含自定义文件。例如,您可以通过ConfigMap或Secret将配置文件作为图表中的文件传递给应用程序。在图表中未列在.helmignore文件中的非特殊文件在模板中可以通过.Files访问。这不会让您访问模板文件。
传递给模板的最后一个数据片段是关于当前正在执行的模板的详细信息。Helm 传递:
.Template.Name
包含模板的命名空间文件路径。例如,在第四章的anvil图中,路径将是anvil/templates/deployment.yaml。
.Template.BasePath
当前图表的templates目录的命名空间路径(例如,anvil/templates)。
在本章后面,您将了解在某些情况下如何更改.的范围。当范围发生变化时,像.Capabilities.KubeVersion.Minor这样的属性将无法在该位置访问。当模板执行开始时,.被映射到$,并且$不会改变。即使范围发生变化,$.Capabilities.KubeVersion.Minor和其他传入的数据仍然可访问。您会发现在范围发生变化时,通常仅使用$。
现在您已经了解了传递到模板的数据,我们将看看如何在模板中使用和操作该数据。
流水线
流水线是一系列命令、函数和变量链接在一起。变量的值或函数的输出作为流水线中下一个函数的输入。流水线的最后一个元素的输出是流水线的输出。以下是一个简单流水线的示例:
character: {{ .Values.character | default "Sylvester" | quote }}
这个流水线分为三部分,每部分由|分隔。第一部分是.Values.character,这是character的计算值。这可以是来自values.yaml文件的值,或者在使用helm install、helm upgrade或helm template渲染图表时传入的值。此值作为最后一个参数传递给default函数。如果值为空,default将使用“Sylvester”的值代替。default的输出作为quote函数的输入,确保该值用引号包裹。quote的输出从操作中返回。
流水线是一个强大的工具,您可以在模板中使用它来转换所需的数据。它们可用于各种目的,从创建强大的转换到防止简单错误。您能在以下 YAML 输出中发现错误吗?
id: 12345e2
id的值看起来像一个字符串,但它不是。唯一的字母是e,其余都是数字。包括 Kubernetes 在内的 YAML 解析器将其解释为科学记数法中的数字。这将导致错误。当您从 Git 获取摘要或提交 ID 的缩短版本时,这样的简短字符串是常见的输出。一个简单的解决方法是将值用引号括起来:
id: "12345e2"
当值被引号包裹时,YAML 解析器将其解释为字符串。这是使用管道末尾的quote函数可以修复或避免错误的情况之一。
函数模板
在操作和流水线中,您可以使用模板函数。您已经看到了其中一些,包括本章前面描述的default和quote函数。函数提供了一种将您拥有的数据转换为所需呈现格式或生成不存在数据的方式。
大多数函数由 Helm 提供,并且设计用于在构建图表时提供帮助。这些函数从简单的函数如indent和nindent(用于缩进输出)到能够访问集群并获取当前资源和资源类型信息的复杂函数,功能齐全。
为了说明函数,我们可以查看图表中常用的一种模式,以提高可读性。当运行helm create时,正如您在第四章中看到的,作为图表的一部分创建了一个 Kubernetes Deployment模板。Deployment模板包括一个用于安全上下文的部分:
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
提示
从第四章的完整图表读取,在https://github.com/Masterminds/learning-helm/tree/main/chapter4/anvil。
在values.yaml文件中,为podSecurityContext有一个 YAML 条目。这意味着在securityContext的template部分传递的确切 YAML。在模板内,values.yaml文件中的信息不再是 YAML。而是一个数据对象。toYaml函数将数据转换为 YAML 格式。
securityContext下的 YAML 需要正确缩进,否则部署清单将由于某个部分未正确缩进而产生 YAML 错误。这通过使用两个函数来实现。在toYaml的左边,使用了一个带有{{的-,以删除直到前一行上的:之前的所有空白。toYaml的输出传递给nindent。此函数在接收到的文本开头添加一个换行符,然后对每一行进行缩进。
为了增强可读性,使用 nindent 而不是 indent 函数。indent 函数不在开头添加换行符。使用 nindent 是为了让 securityContext 下的 YAML 能够在新行上。这是模板中另一个常见的模式。
提示
除了 toYaml,Helm 还有将数据转换为 JSON 的函数 toJson 和转换为 TOML 的函数 toToml。toYaml 经常用于创建 Kubernetes 清单,而 toJson 和 toToml 更常用于创建通过 Secret 和 ConfigMap 传递给应用程序的配置文件。
函数中传递参数的顺序是有意义的。当使用管道时,一个函数的输出作为管道中下一个函数的最后一个参数传递。在前面的例子中,toYaml 的输出作为 nindent 的最后一个参数传递,nindent 接受两个参数。函数参数的顺序设计用于常见的管道用例。
在模板中有超过 百个函数 可供使用。这些包括处理数学、字典和列表、反射、哈希生成、日期函数等等。
方法
到目前为止,您已经看到了模板函数。Helm 还包括检测 Kubernetes 集群能力和处理文件的函数方法。
.Capabilities 对象具有方法 .Capabilities.APIVersions.Has,接受一个参数用于检查您想要检查的 Kubernetes API 或类型的存在。它返回 true 或 false 来告知您该资源在您的集群中是否可用。您可以检查一个组和版本,例如 batch/v1 或资源类型如 apps/v1/Deployment。
提示
在处理自定义资源定义和多版本 Kubernetes 资源类型时,检查资源和 API 组的存在非常有用。随着 Kubernetes API 版本从 alpha 到 beta,再到发布版本的变化,您希望使用资源类型的最新版本,因为 alpha 和 beta 将会被废弃并从 Kubernetes 中移除。如果您的应用程序将安装在广泛的 Kubernetes 版本上,支持所有这些集群中的 API 版本将非常有用。
警告
当使用 helm template 时,Helm 将使用符合 Kubernetes 集群的默认 API 版本集,而不是与您的集群交互以生成已知能力。
您将找到方法的另一个地方是在 .Files 上。它包括以下方法来帮助您处理文件:
.Files.Get name
提供了一种将文件内容作为字符串获取的方法。在这种情况下,name 是从图表根目录开始的包含文件路径的名称。
.Files.GetBytes
与 .Files.Get 类似,但返回的是文件作为字节的数组。在 Go 术语中,这是一个字节切片(即 []byte)。
.Files.Glob
接受一个 glob 模式,并返回另一个files对象,其中仅包含文件名与模式匹配的文件。
.Files.AsConfig
接受文件组并将其作为适合包含在 Kubernetes ConfigMap清单的data部分中的扁平化 YAML 返回。当与.Files.Glob配对使用时很有用。
.Files.AsSecrets
与.Files.AsConfig类似。它不是返回扁平化的 YAML 而是返回可以包含在 Kubernetes Secret清单的data部分中的数据格式。它是 Base64 编码的。当与.Files.Glob配对使用时很有用。例如,{{ .Files.Glob("mysecrets/**").AsSecrets }}。
.Files.Lines
有一个文件名参数,返回文件内容作为以新行分隔的数组(即\n)。
为了说明这些的使用,以下模板来自一个example图表。它读取图表的config子目录中的所有文件,并将每个文件嵌入一个Secret中:
apiVersion: v1
kind: Secret
metadata:
name: {{ include "example.fullname" . }}
type: Opaque
data:
{{ (.Files.Glob "config/*").AsSecrets | indent 2 }}
如 Helm 的以下示例输出所示,每个文件可以在其自己的键中找到:
apiVersion: v1
kind: Secret
metadata:
name: myapp
type: Opaque
data:
jetpack.ini: ZW5hYmxlZCA9IHRydWU=
rocket.yaml: ZW5hYmxlZDogdHJ1ZQ==
在图表中查询 Kubernetes 资源
Helm 包含一个模板函数,使您能够在 Kubernetes 集群中查找资源。lookup模板函数能够返回单个对象或对象列表。在执行不与集群交互的命令时,此函数返回空响应。
以下示例查找在anvil命名空间中名为runner的Deployment,并使元数据注释可用:
{{ (lookup "apps/v1" "Deployment" "anvil" "runner").metadata.annotations }}
lookup函数传入四个参数:
API 版本
这是任何对象的版本,无论是包含在 Kubernetes 中还是作为插件的一部分安装的。此类示例看起来像"v1"和"apps/v1"。
对象的种类
这可以是任何资源类型。
要查找对象的命名空间
这可以留空以查找您有权限访问的所有命名空间或全局资源,例如 Namespace。
要查找的资源的名称
这可以留空以返回资源列表而不是特定的一个。
当返回资源列表时,您需要循环遍历结果以访问每个单独对象的数据。当对象的查找返回一个dict时,列表对象的查找返回一个list。这两种类型是 Helm 在模板中提供的。
当返回列表时,对象位于items属性中:
{{ (lookup "v1" "ConfigMap" "anvil" "").items }}
可以使用循环迭代这些项,您将在本章后面学习。以下示例返回在anvil命名空间中的所有ConfigMap,假设您有权限访问该命名空间。
在使用此函数时应该小心。例如,当作为干预运行时,它将返回与升级运行时不同的结果。干预运行不会与集群交互,因此此函数将不返回结果。当运行升级时它将返回结果。
安装或升级各个集群时返回的结果也可能不同。例如,在开发环境和生产环境中,安装在集群中的资源将有可能导致不平等的响应。
if/else/with
Go 模板有if和else语句,以及类似但略有不同的with。if和else的工作方式与大多数编程语言中的相同。为了说明if语句,我们可以查看使用helm create命令生成的图表中的模式,该命令在第四章中涵盖了。在该图表中,values.yaml文件包含一个关于ingress的部分,其中包含一个 enabled 属性。看起来像:
ingress:
enabled: false
在创建用于 Kubernetes 的Ingress资源的ingress.yaml文件中,第一行和最后一行是实现此目的的if语句:
{{- if .Values.ingress.enabled -}}
...
{{- end }}
在这种情况下,if语句评估if语句后面管道的输出是真还是假。如果为真,则评估内部内容。为了知道块的结尾在哪里,您需要一个end语句。这很重要,因为缩进或更典型的括号可能是您想呈现的材料的一部分。
使用if语句是通常实现的常见enabled模式。
if语句可以有一个else语句,如果if语句评估为 false,则执行该语句。以下示例在Ingress未启用时将 YAML 注释打印到输出中:
{{- if .Values.ingress.enabled -}}
...
{{- else -}}
# Ingress not enabled
{{- end }}
有时,您将希望通过将它们与and或or语句结合来评估多个元素的if语句。在模板中,这与您习惯的方式有所不同。考虑来自模板的以下片段:
{{- if and .Values.characters .Values.products -}}
...
{{- end }}
在这种情况下,and被实现为具有两个参数的函数。这意味着and出现在使用的两个项目之前。相同的想法适用于or的使用,它也作为一个函数实现。
当要与and或or一起使用的元素之一是函数或管道时,可以使用括号。以下示例中的一个参数是or的相等检查:
{{- if or (eq .Values.character "Wile E. Coyote") .Values.products -}}
...
{{- end }}
使用eq函数实现的相等性检查的输出作为or的第一个参数传递。括号使您能够将元素分组在一起以构建更复杂的逻辑。
with类似于if,但with块内的作用域会发生变化。继续使用来自Ingress的示例,以下块显示了作用域的更改:
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
如果传入with的值为空,则跳过该块。如果值不为空,则执行块,并且块内的.的值是.Values.ingress.annotations。在这种情况下,块内的作用域已更改为with检查的值。
提示
使用 with 检查值并使用 toYaml 和 nindent 函数将其发送到输出的模式,在 values.yaml 文件中是常见的,您希望直接在模板中输出它们。这通常用于镜像拉取密钥、节点选择器等。
与 if 语句一样,with 可以有一个伴随的 else 块,当值为空时可以使用。
变量
在模板中,您可以创建自己的变量,并将它们用作函数参数传递、输出打印等。变量以 $ 开头并且有类型。一旦为一个类型(如字符串)创建了变量,就不能将其值设置为另一种类型(如整数)。
使用 := 创建和初始化变量具有特殊语法,例如以下示例:
{{ $var := .Values.character }}
在这种情况下,创建一个新变量并将 .Values.character 的值分配给它。此变量可以在其他地方使用;例如:
character: {{ $var | default "Sylvester" | quote }}
$var 的值与之前章节中传递 .Values.character 的方式相同,传递给 default。
创建具有初始值的变量的方法与更改现有变量的值的方法不同。当您为现有变量分配新值时,使用 =。例如:
{{ $var := .Values.character }}
{{ $var = "Tweety" }}
在这种情况下,变量在另一个操作中更改。变量在模板执行的整个生命周期内存在,并且可以在模板的后续相同或不同的操作中使用。
注意
变量处理反映了 Go 编程语言中使用的语法和风格。它通过 :=、= 和类型来遵循相同的语义。
循环
使用循环是简化用户与图表交互的常用方法。例如,您可以使用循环来收集在公开 Web 应用程序时使用的主机列表,并循环遍历该列表以创建更复杂的 Kubernetes Ingress 资源。
模板中的循环语法与许多编程语言中的不同。不是使用 for 循环,而是使用 range 循环来迭代 dicts(也称为映射)和列表。
下面的示例说明了 dicts 和 lists:
# An example list in YAML
characters:
- Sylvester
- Tweety
- Road Runner
- Wile E. Coyote
# An example map in YAML
products:
anvil: They ring like a bell
grease: 50% slippery
boomerang: Guaranteed to return
您可以将列表视为数组,而映射具有键名和值,类似于 Python 中的字典或 Java 中的 HashMap。在 Helm 模板中,您可以使用 dict 和 list 函数创建自己的字典和列表。
您可以使用 range 函数的两种方式。以下示例遍历 characters 并更改范围,即 . 的值:
characters:
{{- range .Values.characters }}
- {{ . | quote }}
{{- end }}
在这种情况下,range 遍历列表中的每个项,并将 . 的值设置为 Helm 在迭代项时的每个项的值。在此示例中,该值被传递到管道中的 quote。. 的范围在块中更改到 end,它作为循环的闭合括号或语句。
此代码段的输出是:
characters:
- "Sylvester"
- "Tweety"
- "Road Runner"
- "Wile E. Coyote"
另一种使用range的方式是让它为键和值创建新变量。这将适用于列表和字典。下一个示例创建了您可以在块中使用的变量:
products:
{{- range $key, $value := .Values.products }}
- {{ $key }}: {{ $value | quote }}
{{- end }}
$key变量包含映射或字典中的键和列表中的数字。$value包含值。如果这是复杂类型(如另一个字典),那么它将作为$value可用。新变量在range块的末尾处于作用域中,这由相应的end动作表示。此示例的输出是:
products:
- anvil: "They ring like a bell"
- boomerang: "Guaranteed to return"
- grease: "50% slippery"
命名模板
有时候,当您需要在 Kubernetes 清单模板中调用模板时,例如,当您通过一些复杂逻辑生成的值或者当您有一个在众多 Kubernetes 清单中重复的部分时,您可以创建自己的模板。Helm 不会自动渲染这些模板,但可以在 Kubernetes 清单的模板中使用它们。
一个例子是当您运行helm create生成图表时。默认情况下,Helm 会创建几个带有共享元素(如标签)的 Kubernetes 清单。为了保持标签的一致性,并且只需在一个地方更新它们,Helm 会生成一个模板,然后每次需要标签时调用该模板。
模板中使用的标签有两种类型。一种是用于高级资源(如Deployment)的标签,另一种是用于与更新选择器配对的规范中的标签。这些标签需要有所区别,因为规范和选择器中使用的标签通常是不可变的。这意味着你不希望它们包含诸如应用程序版本之类的元素,因为这些元素可能会随着应用程序升级而变化,但规范和选择器不能更新为新版本。
下面的模板选择包含用于生成生成模板中规范和选择器部分的选择器标签。名称anvil来自于第四章生成的图表:
{{/*
Selector labels 
*/}}
{{- define "anvil.selectorLabels" -}} 
app.kubernetes.io/name: {{ include "anvil.name" . }} 
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}} 
在定义函数之前的注释。动作中的注释以/*开头,并以*/结尾。
你可以使用define语句定义一个模板,后跟模板的名称。
模板的内容就像任何其他模板的内容一样。
通过与define语句匹配的end语句关闭模板的定义。
此模板包含了一些您应考虑在自己的模板中使用的有用内容:
-
描述模板的注释。在模板渲染时会被忽略,但在代码注释中同样有用。
-
名称使用
.作为分隔符进行命名空间处理,以包含图表名称。在第六章中,您将了解库图表和依赖图表。在模板名称上使用命名空间使得能够使用库图表,并避免在依赖图表上发生冲突。 -
define和end调用使用操作来移除它们之前和之后的空格,以便它们的使用不会向最终输出的 YAML 添加额外的行。
此模板在资源的spec部分被称为,例如anvil图表中的Deployment:
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "anvil.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "anvil.selectorLabels" . | nindent 8 }}
此处的matchLabels部分是不可变的,因此不能更改,并且它查找template部分中的labels。
有两个函数可用于在您的模板中包含另一个模板。template函数是一个基本函数,用于包含另一个模板。它不能在管道中使用。然后是include函数,以类似的方式工作,但可以在管道中使用。在上述示例中,include用于调用另一个模板,并将该模板的输出传递给nindent,以确保输出具有正确的缩进级别。由于每次调用的输出具有不同的缩进级别,因此缩进级别不能作为定义其自身的模板的一部分。
include函数接受两个参数。第一个是要调用的模板的名称。这需要是完整的名称,包括任何命名空间。第二个是要传递的数据对象。这可以是您自己创建的对象,使用dict函数,或者它可以是模板内部使用的全局对象的全部或部分。在这种情况下,传递整个全局对象。
Helm 创建的模板函数用于生成更广泛的标签选择,用于标签为可变的高级资源的标签。它具有调用其他用户定义模板的用户定义模板:
{{/*
Common labels
*/}}
{{- define "anvil.labels" -}}
helm.sh/chart: {{ include "anvil.chart" . }}
{{ include "anvil.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
因为这些标签是可变的,因此包含了这里将因各种原因而更改的有用标签。为了不重复用于选择器的标签,在这里也包括通过调用生成它们的函数来包含这些标签。
另一种可能遇到的情况是,命名模板很有用的情况,是当您希望封装复杂逻辑时。为了说明这个想法,考虑一个图表,您希望能够将容器版本作为标签、摘要或者回退到应用程序版本作为默认值传递。接受包含容器图像的 Pod 规范的部分是一个单独的行。为了提供这三个选项,您需要许多行逻辑:
{{- define "anvil.getImage" -}}
{{- if .Values.image.digest -}}
{{ .Values.image.repository }}@{{ .Values.image.digest }}
{{- else -}}
{{ .Values.image.repository }}:
{{- .Values.image.tag | default .Chart.AppVersion }}
{{- end -}}
{{- end -}}
这个新的getImage模板能够处理摘要、标签,并且如果前两者都不存在,默认使用应用程序版本。首先会检查并使用摘要。摘要是不可变的,是指定要使用的镜像修订版本的最精确方法。如果没有传入摘要,则会检查标签。标签是指向摘要的指针,可以更改。如果找不到标签,则使用AppVersion作为标签。
此函数针对anvil图表的结构,最初是为第四章创建的。预期图像细节位于该图表及其values.yaml文件的结构中。
在Deployment的模板中,使用新函数引用镜像:
image: "{{ include "anvil.getImage" . }}"
模板可以像软件程序中的函数一样工作。这是一种您可以拆分复杂逻辑和共享功能的有用方式。
为了保持模板的可维护性
在templates目录中,对模板的强制结构有限。多个 Kubernetes 清单可以位于同一 YAML 文件中,这意味着多个 Kubernetes 清单的模板也可以位于同一文件中。命名模板可以存在于任何模板文件中,并且可以在其他文件中引用。NOTES.txt模板是向用户显示的特殊文件,并且测试有特殊处理方式。测试在第六章中进行了覆盖。除此之外,这是一个空白画布,供您创建模板。
为了帮助创建易于维护且易于导航的模板,Helm 维护者建议采用几种模式。这些模式之所以有用,有以下几个原因:
-
您可能长时间不会对图表中的模板进行结构性更改,然后再回来。能够快速重新发现布局将加快流程。
-
其他人将查看图表中的模板。这些可能是创建图表的团队成员或使用它的人。消费者有时可能会打开图表以检查它,以便在安装之前或作为复制的一部分进行操作。
-
在调试图表时(下一节会讲到),如果模板有一定结构,会更容易进行调试。
第一个模式是每个 Kubernetes 清单应该在其自己的模板文件中,并且该文件应该有一个描述性的名称。例如,如果只有一个部署,则命名模板为deployment.yaml。如果有多个相同类型的清单,例如使用主数据库和副本部署时,可以使用类似statefulset-primary.yaml和statefulset-replica.yaml的命名。
第二个指导原则是将命名模板放入您自己模板的文件中,命名为 _helpers.tpl。 因为这些本质上是您其他模板的辅助模板,所以名称很描述性。 如前所述,名称开头的 _ 会导致它在目录列表的顶部显示,因此您可以在模板中轻松找到它。
当您使用 helm create 命令启动一个新的图表时,默认情况下,模板的内容已经遵循这些模式。
调试模板
在开发模板时调试模板非常有用。 Helm 提供了三个功能,您可以在开发工作流中使用这些功能来查找问题。 这些功能是除了测试之外,测试在第六章中介绍。
干冷运行
安装、升级、回滚和卸载 Helm 图表的命令都有一个标志来启动干冷运行,并模拟过程但不完全执行该过程。 这是通过这些命令上的 --dry-run 标志来实现的。 例如,如果您在 anvil 图表上的 install 命令上使用 --dry-run 标志,则可以使用命令 helm install myanvil anvil --dry-run。 Helm 将渲染模板,检查模板以确保发送到 Kubernetes 的内容格式正确,并将其发送到输出。 输出看起来类似于正常安装时的输出,但会有两个额外的部分:
NAME: myanvil
LAST DEPLOYED: Tue Jun 9 06:58:58 2020
NAMESPACE: default
STATUS: pending-install
REVISION: 1
HOOKS:
...
MANIFEST:
...
NOTES:
1\. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default ↵
-l "app.kubernetes.io/name=anvil,app.kubernetes.io/instance=myanvil" ↵
-o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:80
两个新部分是 HOOKS 和 MANIFEST 部分,它们将包含 YAML Helm 通常会传递给 Kubernetes 的内容。 而不是发送到输出。 为了简洁起见,不包含完整生成的 YAML,因为这将非常长。
如果模板中有问题,响应将会大不相同。 为了说明这一点,请尝试从 anvil 图表的 deployment.yaml 文件中删除第一个 },然后再次执行干冷运行安装。 删除 } 将导致解析模板中的操作时出错。 Helm 不会输出状态,而会输出如下错误:
Error: parse error at (anvil/templates/deployment.yaml:4): unexpected "}" in
operand
此处提供了一个提示,用于查找问题所在的地方。 它包括:
-
出错的文件。 anvil/templates/deployment.yaml,在这种情况下。
-
文件中发生错误的行号。 在这里是第 4 行。
-
一个关于问题的提示错误消息。 错误消息通常不会显示问题所在,而是显示解析器出现问题的地方。 在这种情况下,一个单独的
}是意外的。
Helm 将检查模板语法中的错误超过错误。 它还将检查输出语法的语法。 为了说明这一点,在相同的 deployment.yaml 文件中删除开头的 apiVersion:。 确保补充缺失的 },以修复操作。 现在文件的开头看起来像:
apps/v1
kind: Deployment
执行干冷运行安装将产生以下输出:
Error: YAML parse error on anvil/templates/deployment.yaml: error converting
YAML to JSON: yaml: line 2: mapping values are not allowed in this context
也许你想知道为什么在 YAML 和 JSON 之间转换时会出错。这是 Helm 和 Kubernetes 使用的 YAML 解析库的产物。错误消息中有用的部分是从line 2开始的部分。第一行不完整,因此第二行处于错误的上下文中,尽管它格式良好。文件不是有效的 YAML,Helm 告诉你从哪里开始查找问题。如果你将同样的 YAML 部分在在线 YAML 验证器中测试,你会得到相同的错误。
Helm 也能够验证 Kubernetes 资源的模式。这是因为 Kubernetes 为其清单提供了模式定义。为了说明这一点,在deployment.yaml中将apiVersion更改为foo:
foo: apps/v1
kind: Deployment
执行 dry-run 安装将产生以下输出:
Error: unable to build kubernetes objects from release manifest: error
validating "": error validating data: apiVersion not set
部署不再有效,Helm 能够提供关于缺失内容的具体反馈。在这种情况下,apiVersion属性未设置。
使用 dry-run 并不是你获取此功能的唯一方式。helm template命令提供了类似的体验,但没有完整的调试功能集。template命令会将template命令转换为 YAML。在此阶段,如果生成的 YAML 无法解析,它会提供一个错误。但template命令不会根据 Kubernetes 模式验证 YAML。当使用template命令时,Helm 不会警告您如果apiVersion被转换为foo。这是因为在使用template命令时,Helm 不会与 Kubernetes 集群通信以获取验证模式。
获取已安装的清单
有时候你在集群中安装一个应用程序,然后其他东西会在之后改变清单。这导致你声明的内容与实际运行的内容之间存在差异。一个例子是服务网格自动向由你的 Helm 图表创建的Pod添加一个 Sidecar 容器。
您可以使用helm get manifest命令获取由 Helm 部署的原始清单。该命令将检索释放时 Helm 安装的清单。它能够为历史中仍然可用的任何版本的释放检索此信息,如使用helm history命令查找的那样。
继续myanvil示例,要检索此anvil图表实例的清单,您可以运行:
$ helm get manifest myanvil
输出将包括所有清单,每个新清单的开头都有---。以下是输出的前 15 行:
---
# Source: anvil/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: myanvil-anvil
labels:
helm.sh/chart: anvil-0.1.0
app.kubernetes.io/name: anvil
app.kubernetes.io/instance: myanvil
app.kubernetes.io/version: "9.17.49"
app.kubernetes.io/managed-by: Helm
---
# Source: anvil/templates/service.yaml
apiVersion: v1
kind: Service
...
---用作 YAML 文档之间的分隔符。除此之外,Helm 还会添加一个 YAML 注释,注明生成清单所使用的源模板。
检查图表
您将遇到的一些问题不会显示为 API 规范的违规,并且也不是模板中的问题。例如,Kubernetes 资源需要具有可用作域名的名称。这限制了名称中可以使用的字符及其长度。Kubernetes 提供的 OpenAPI 模式未提供足够的信息来检测发送到 Kubernetes 时将失败的名称。前文在 第四章 中提到的 lint 命令能够检测到此类问题,并告诉您问题出现的位置。
为了说明这一点,您可以修改 anvil 图表,在 deployment.yaml 中将 Wile 添加到部署名称的末尾:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "anvil.fullname" . }}-Wile
运行 helm lint anvil 将生成一个错误,告知您存在的问题:
$ helm lint anvil
==> Linting anvil
[ERROR] templates/deployment.yaml: object name does not conform to Kubernetes
naming requirements: "test-release-anvil-Wile"
Error: 1 chart(s) linted, 1 chart(s) failed
在这种情况下,helm lint 正在指向一个问题,并告诉您它发生的地方。
结论
在图表中包含的模板能够强大地在 Kubernetes 内创建资源。这类似于模板周围的编程语言。模板系统具有逻辑、内置函数、自定义模板和调试功能。这意味着您可以通过值收集所需的输入,并生成所需的 Kubernetes 清单。
这些图表还包括依赖项、测试、值文件模式等更多内容。第六章将详细介绍图表的功能和应用。
第六章:高级图表特性
图表不仅仅是关于图表的元数据和一组模板。图表可以有依赖关系,值可以有模式,Helm 具有生命周期钩子,您可以签名图表,等等。在本章中,您将学习有关图表的其他元素,超越模板。
这些功能提供了解决构建软件包时常见问题的强大解决方案。本章从覆盖依赖关系开始。依赖关系是几乎每个软件包管理解决方案的关键部分,因为它们让您在解决方案中利用现有的软件包并建立在他人的工作之上。然后它继续覆盖模式和验证,当您希望帮助图表用户在覆盖 Helm 执行自定义操作的过程中避免问题时,这些都是有用的。本章还涵盖了测试和测试——在开发中测试非常重要,因为它们确保您的软件按预期运行。Helm 提供了安全功能,有助于减轻一些常见的威胁路径,接下来将对其进行覆盖。本章最后讨论了如何使用图表来扩展 Kubernetes API。
在整个本章中,您将看到图表作为您可以参考的示例,位于https://github.com/masterminds/learning-helm/blob/main/chapter6。它们展示了本章涵盖的不同特性以及 Helm 仓库。
图表依赖项。
依赖关系是包管理器及其软件包的常见元素。图表可以依赖于其他图表。这使得在图表中封装服务、重用图表以及将多个图表一起使用成为可能。
为了说明依赖关系,考虑一个安装 WordPress 的图表,这是一款流行的博客软件。WordPress 依赖于一个符合 MySQL 标准的数据库,用于存储博客内容、用户和其他配置。一个符合 MySQL 标准的数据库可以被其他应用程序使用,并且可以作为一个服务来消费。处理 WordPress 中使用 MySQL 的一种方式是将其清单放在 WordPress 图表中。另一种处理方式是有一个独立的 MySQL 图表,而 WordPress 图表则依赖于它。将符合 MySQL 标准的数据库作为独立图表使其能够被多个应用程序使用,并且可以独立构建和测试。
依赖关系在Chart.yaml文件中指定。以下是名为rocket的图表的Chart.yaml文件中的dependencies部分。
dependencies:
- name: booster 
version: ¹.0.0 
repository: https://raw.githubusercontent.com/Masterminds/learning-helm/main/
chapter6/repository/ 
仓库中依赖图表的名称。
图表的版本范围字符串。
从仓库中检索图表的位置。
Helm 图表使用语义版本作为它们的版本方案。用于依赖项的version字段接受版本范围,有一些用于这些范围的简写语法。例如,¹.2.3是>= 1.2.3, < 2.0.0的简写。Helm 支持包括=, !=, <, ⇐, >, >=, ^, ~和-在内的范围。不同的范围可以使用空格或逗号组合在一起,以支持逻辑and组合,|以支持逻辑or组合。Helm 还支持使用X或*作为通配符字符。如果省略版本的一部分,例如省略补丁部分,Helm 将假定缺失部分是通配符。
范围是指定所需版本的首选方法。马上您将学习如何锁定到指定范围内的特定依赖项版本。通过指定范围,可以使用 Helm 命令自动更新到该范围内的最新版本。如果您想要引入修复 bug 或安全更新依赖项,则这非常有用。
repository字段是您指定的图表存储库位置,用于从中提取依赖项。您可以通过以下两种方式之一指定此字段:
-
Helm 存储库的 URL。
-
要使用
helm repo add命令设置的存储库名称。这个名称需要在@之前加上引号(例如,"@myrepo")。
通常使用完整 URL 来指定位置。这将确保在使用图表的每个环境中检索相同的依赖项。
一旦您指定了具有所需版本范围的依赖项,您需要使用 Helm 将这些依赖项锁定到特定版本并检索它们。如果您打算将您的图表打包成图表存档,如第四章中所述,您需要在打包之前锁定并获取依赖项。
解析指定范围内依赖项的最新版本并检索它,您可以使用以下命令:
$ helm dependency update .
运行该命令后,您将看到以下输出:
Saving 1 charts
Downloading booster from repo https://raw.githubusercontent.com/Masterminds/
learning-helm/main/chapter6/repository/
Deleting outdated charts
运行此命令会导致执行几个步骤。
首先,Helm 解析了booster图表的最新版本。它使用存储库中的元数据来了解可用的图表版本。从元数据和指定的版本范围中,Helm 找到了最佳匹配。
将解析后的信息写入Chart.lock文件。Chart.lock文件中不再是版本范围,而是包含要使用的依赖项的具体版本。这对于可重复性非常重要。Chart.lock文件由 Helm 管理。用户的更改将在下次运行helm dep up(简写语法)时被覆盖。这类似于其他平台上依赖管理器的锁文件。
一旦 Helm 知道要使用的特定版本,它将下载依赖的图表并将其放入 charts 子目录中。依赖的图表放置在 charts 目录中非常重要,因为这是 Helm 获取它们内容以渲染模板的地方。图表可以以其存档或目录形式存在于 charts 目录中。当 Helm 从存储库下载它们时,它们以存档形式存储。
如果您有一个 Chart.lock 文件但 charts 目录中没有内容,则可以通过运行命令 helm dependency build 重新构建 charts 目录。这将使用锁定文件以其已确定版本检索依赖项。
一旦您有了依赖项,当您运行诸如 helm install 或 helm upgrade 的命令时,Helm 将渲染它们的资源。
当您指定依赖项时,您可能还希望将配置从父或主图表传递到依赖图表。如果我们回顾 WordPress 的示例,这可以用于设置要使用的数据库名称。Helm 提供了一种方法,在父图表的值中执行此操作。
在主图表的 values.yaml 文件中,您可以创建一个以依赖图表名称命名的新部分。在此部分中,您可以设置要传递的值。您只需设置您想要更改的部分,因为包含在 values.yaml 文件中的依赖图表将作为默认值。
在 rocket 图表的 values.yaml 文件中,有一个部分如下所示:
booster:
image:
tag: 9.17.49
Helm 知道此部分是用于 booster 图表。在这种情况下,它将图像标签设置为特定值。可以以这种方式设置依赖图表中的任何值。当运行诸如 helm install 的命令时,您可以使用标志设置依赖项的值(例如 --set)以及主图表的值。
如果您在同一个图表上有两个依赖项,可以选择在 Chart.yaml 文件中使用 alias 属性。此属性放置在您想要使用替代名称的每个依赖项旁边,例如 name、version 和其他属性。通过 alias,您可以为每个依赖项提供一个唯一的名称,在其他地方引用它们,例如 values.yaml 文件中。
条件标志以启用依赖项
Helm 提供了通过配置启用或禁用依赖项的功能。为了说明这个想法,考虑这样一个情况:您希望提供一个 WordPress 博客解决方案,但是给安装 WordPress 的人员选择是否使用作为服务的数据库或包含的数据库的选项。如果安装图表的人选择使用数据库作为服务,则他们将提供该服务的 URL,并且不需要安装数据库。可以通过两种不同的方式在配置中实现这一点。
当您希望通过依赖项控制单个功能的启用或禁用时,可以在依赖项上使用condition属性。为了说明这一点,我们将查看Chart.yaml文件中条件图表的dependencies部分:
dependencies:
- name: booster
version: ¹.0.0
condition: booster.enabled
repository: https://raw.githubusercontent.com/Masterminds/learning-helm/main/
chapter6/repository/
依赖项具有一个condition键,其值告诉 Helm 在值中查找以确定是否应启用或禁用它。在values.yaml文件中,相应的部分如下所示:
booster:
enabled: false
在这种情况下,默认值是禁用依赖项。当某人安装图表时,可以通过传递一个值来启用该依赖项。
当您有多个涉及依赖关系的要启用或禁用的功能时,可以使用tags属性。像condition一样,这个属性在描述依赖关系时与name和version并列。它包含一个依赖项的标签列表。为了说明这一点,我们可以看一下另一个名为tag的图表的依赖项:
dependencies:
- name: booster
tags:
- faster
version: ¹.0.0
repository: https://raw.githubusercontent.com/Masterminds/learning-helm/main/
chapter6/repository/
- name: rocket
tags:
- faster
version: ¹.0.0
repository: https://raw.githubusercontent.com/Masterminds/learning-helm/main/
chapter6/repository/
在这里,您将看到两个带有tags部分的依赖项。标签是相关标签的列表。在图表的values.yaml文件中,您使用tags属性:
tags:
faster: false
tags是具有特殊含义的属性。这里的值告诉 Helm 默认情况下禁用具有标签faster的依赖项。当图表的用户在安装或升级时传递一个 true 值时,它们可以被启用。
从子图表导入到父图表的值
有时您可能希望将子图表的值导入或拉到父图表中。Helm 提供了两种方法来做到这一点。一种是在子图表明确将一个值导出以供父图表导入的情况下,另一种是在子图表未导出值的情况下。
导出属性
exports属性是values.yaml文件中的一个特殊顶层属性。当子图表声明了一个export属性时,其内容可以直接导入到父图表中。
例如,考虑来自子图表values.yaml文件的以下内容:
exports:
types:
foghorn: rooster
当父图表声明子图表为依赖项时,可以从导出中导入值,如下所示:
dependencies:
- name: example-child
version: ¹.0.0
repository: https://charts.example.com/
import-values:
- types
在父计算的值中,现在可以在顶层访问这些类型。在 YAML 中,这相当于:
foghorn: rooster
子父格式
当父图表想从子图表导入一个值,但子图表没有导出该值时,有一种方法告诉 Helm 将子值拉入到父图表中。
为了说明这一点,请考虑一个子图表,在其values.yaml文件中指定了以下值:
types:
foghorn: rooster
这些值没有被导出,但父图表仍然可以导入它们。当在父级中声明依赖项时,可以使用child和parent文件导入这些值,如以下示例:
dependencies:
- name: example-child
version: ¹.0.0
repository: https://charts.example.com/
import-values:
- child: types
parent: characters
在导入的两种方法中,使用的是import-values属性。Helm 知道如何区分不同的格式,您可以混合使用这两种格式。
在子图表中,当 types 的顶级属性在其计算的值中不可用于父图表中的 characters 的顶级属性时,可以表示为 YAML:
characters:
foghorn: rooster
此格式允许访问嵌套值,除了使用点作为分隔符的顶级属性。例如,如果子图表具有以下格式,则 import-values 上的 child 属性可以读取 data.types:
data:
types:
foghorn: rooster
库图表
您可能会遇到创建多个相似图表的情况——这些图表共享许多相同的模板。对于这些情况,有库图表。
库图表在概念上类似于软件库。它们提供可重用的功能,可以被其他图表导入和使用,但本身不能被安装。
如果您使用 helm create 创建一个新的库图表,第一步是删除 templates 目录和 values.yaml 文件的内容,因为这两者都不会被使用。然后,您需要告诉 Helm 这是一个库图表。在 Chart.yaml 文件中将 type 设置为 library。为了说明这一点,这里是名为 mylib 的图表的 Chart.yaml 文件:
apiVersion: v2
name: mylib
type: library
description: an example library chart
version: 0.1.0
当未设置时,type 的默认值为应用程序。仅当您的图表是库时才需要设置它。
在 templates 目录中以下划线(即 `_`)开头的文件不应该渲染为发送到 Kubernetes 的清单。约定是帮助模板和片段在 _.tpl* 和 _.yaml* 文件中。
为了说明可重用模板的工作原理,以下是在 mylib 图表文件中创建 ConfigMap 的模板,命名为 _configmap.yaml:
{{- define "mylib.configmap.tpl" -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mylib.fullname" . }} 
labels:
{{- include "mylib.labels" . | nindent 4 }} 
data: {}
{{- end -}}
{{- define "mylib.configmap" -}} 
{{- template "mylib.util.merge" (append . "mylib.configmap.tpl") -}}
{{- end -}}
fullname 函数与 helm create 生成的函数相同。
labels 函数生成 Helm 推荐在图表中使用的常见标签。
定义了一个特殊的模板,知道如何合并模板在一起。
大部分此定义看起来类似于你会放入 templates 目录中的其他模板。 define 是一个函数,用于定义在其他地方使用的模板。此文件中定义了两个模板。 mylib.configmap.tpl 包含一个资源的模板。这将看起来类似于其他模板。它提供了一个蓝图,旨在被包含此库的图表中的调用者覆盖。 mylib.configmap 是一个特殊的模板。这是另一个图表将使用的模板。它将 mylib.configmap.tpl 与另一个尚未定义的模板(包含覆盖)合并为一个输出。 mylib.configmap 使用一个实用函数来处理合并,并方便重用。该函数是:
{{- /*
mylib.util.merge will merge two YAML templates and output the result.
This takes an array of three values:
- the top context
- the template name of the overrides (destination)
- the template name of the base (source)
*/ -}}
{{- define "mylib.util.merge" -}}
{{- $top := first . -}}
{{- $overrides := fromYaml (include (index . 1) $top) | default (dict ) -}}
{{- $tpl := fromYaml (include (index . 2) $top) | default (dict ) -}}
{{- toYaml (merge $overrides $tpl) -}}
{{- end -}}
此函数接受一个上下文(考虑在第五章中涵盖的.数据),一个包含覆盖项的模板以及要覆盖的基础模板函数。当您看到它的使用方式时,这个函数将变得更加清晰。
注意
在官方将库图表包含在 Helm 中之前,已经开发了库图表的概念。merge函数由 Adnan Abdulhussein 开发,作为其通过一个名为Common的图表开发这一概念的一部分。
为了说明使用此库函数,以下模板来自另一个名为mychart的图表。在使用其定义的资源之前,需要将其作为依赖项添加,就像任何其他依赖项一样。mychart中包含一个模板用于创建ConfigMap:
{{- include "mylib.configmap" (list . "mychart.configmap") -}} 
{{- define "mychart.configmap" -}} 
data: 
myvalue: "Hello Bosko"
{{- end -}}
包括并使用库图表的功能来生成ConfigMap。
定义了一个新模板,其中仅包含要覆盖库提供的模板的部分。
提供data部分供ConfigMap使用。
这个模板一开始可能看起来令人困惑,因为其中涉及的内容很多。
第一行包括来自库图表的ConfigMap模板。将一个新列表传递给它,其中包含两个条目。第一个是当前的数据对象,第二个是另一个模板的名称,该模板包含要覆盖库图表提供的元素。
文件的其余部分是包含覆盖项的模板。在库图表提供的模板中,data部分未提供任何内容。它是空的。函数mychart.configmap提供了一个data部分。
此模板的 Helm 渲染输出为:
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/instance: example
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: mychart
helm.sh/chart: mychart-0.1.0
name: example-mychart
data:
myvalue: Hello Bosko
此输出是来自库和使用库的图表的合并输出。这个概念可以扩展到其他资源,包括那些更长和更复杂的资源。
对值文件进行模式化处理
由values.yaml文件定义的值是无模式的。并不存在所有values.yaml文件需要遵循的固定结构。不同的图表具有不同的结构。这使您可以将值结构化到您使用图表部署的应用程序或工作负载中。
模式提供了许多有用的好处,包括验证内容的能力,您可以执行各种操作,如生成用户界面。
Helm 提供了每个图表可选的能力,使用JSON Schema为其值提供自己的模式。JSON Schema 提供了描述 JSON 文件的词汇。YAML 是 JSON 的超集,您可以在这两种文件格式之间转换内容。这使得可以使用 JSON 模式验证 YAML 文件的内容。
当您运行 helm install、helm upgrade、helm lint 和 helm template 命令时,Helm 将根据 values.schema.json 文件验证值。Helm 验证的值是计算出的值。它们包括图表提供的值以及安装图表的人提供的值。values.schema.json 文件与图表根目录下的 values.yaml 文件相邻。该文件可以描述所有或部分值。
考虑来自 values.yaml 文件的以下部分:
image:
repository: ghcr.io/masterminds/learning-helm/anvil-app
pullPolicy: IfNotPresent
tag: ""
可以使用以下 JSON Schema 进行检查:
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"image": {
"type": "object", 
"properties": {
"pullPolicy": {
"type": "string", 
"enum": ["Always", "IfNotPresent"] 
},
"repository": {
"type": "string"
},
"tag": {
"type": "string"
}
}
}
}
}
image 是一个对象。如果将 image 作为非对象的内容传递给 Helm,将会抛出错误。
pullPolicy 是一个字符串。当传递其他类型,比如整数时,将抛出错误。这可以捕捉到一些微妙的问题。
pullPolicy 必须是列出的值之一。当传入其他值,甚至拼写错误时,将抛出错误。
为了说明这一点,我们可以使用 booster 图表。如果您在图表根目录下运行该命令,您将看到一个错误:
$ helm lint . --set image.pullPolicy=foo
以下错误告诉您值与模式不匹配的位置:
==> Linting .
[ERROR] templates/: values don't meet the specifications of the schema(s) in the
following chart(s):
booster:
- image.pullPolicy: image.pullPolicy must be one of the following: "Always",
"IfNotPresent"
Error: 1 chart(s) linted, 1 chart(s) failed
JSON Schema 提供了几种描述属性的方法。最灵活的方法(一个通用的方法)是为字符串使用正则表达式。例如,可以使用 ^(Always|IfNotPresent)$ 的 pattern 来替代 enum。这种模式不会像描述性那么强。错误会指出该值不符合模式。在没有其他方法描述属性值时,可以使用模式。
Schema 是图表的有用补充,可以捕捉并修正在安装图表时可能出现的一些微妙问题。
Hooks
Helm 提供了一种方法来钩入发布过程中的事件并采取行动。如果您想要将动作捆绑为发布的一部分,例如,在升级过程中构建数据库备份的能力,并确保备份在升级 Kubernetes 资源之前发生,这将非常有用。
Hooks 类似于常规模板,它们封装的功能通过运行在 Kubernetes 集群中的容器提供给应用程序的其他资源。钩子与其他资源的区别在于设置了特殊的注解时。当 Helm 看到 helm.sh/hook 注解时,它将资源用作钩子,而不是作为图表安装的一部分要安装的资源。表 6-1 包含一些钩子及其执行时机的列表。
表 6-1. Helm 钩子
| 注解值 | 描述 |
|---|---|
| pre-install | 在资源被渲染但上传到 Kubernetes 之前执行。 |
| post-install | 执行发生在将资源上传到 Kubernetes 后。 |
| pre-delete | 执行发生在删除请求之前,任何资源从 Kubernetes 中被删除。 |
| post-delete | 执行发生在从 Kubernetes 中删除所有资源之后。 |
| pre-upgrade | 执行发生在渲染资源之后但在更新 Kubernetes 资源之前。 |
| post-upgrade | 执行发生在 Kubernetes 中的资源升级后。 |
| pre-rollback | 执行发生在渲染资源之后但在 Kubernetes 中任何资源被回滚之前。 |
| post-rollback | 执行发生在 Kubernetes 中的资源被回滚后。 |
| test | 执行发生在运行 helm test 命令时。测试将在下一节中讨论。 |
单个资源可以通过将它们列为逗号分隔列表来实现多个钩子。例如:
annotations:
"helm.sh/hook": pre-install,pre-upgrade
钩子可以被加权,并在运行后指定资源的删除策略。权重允许为同一事件指定多个钩子,并指定它们运行的顺序。这使您能够确保确定性顺序。因为 Kubernetes 资源用于执行钩子,所以即使执行完成后,这些资源也会存储在 Kubernetes 中。删除策略允许您在何时从 Kubernetes 中删除这些资源时具有一些额外控制。
下面的代码提供了指定所有三个值的示例注释:
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "1"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
使用 helm.sh/hook-weight 注释键指定的权重是一个表示为字符串的数字。它应始终为字符串。权重可以是正数或负数,并且默认值为 0。在执行钩子之前,Helm 将它们按升序排序。
使用注释键 helm.sh/hook-delete-policy 设置的删除策略是一个逗号分隔的策略选项列表。三种可能的删除策略可以在 表 6-2 中找到。
表 6-2. Helm 钩子删除策略
| 策略值 | 描述 |
|---|---|
| before-hook-creation | 在启动新钩子实例之前删除先前的资源。这是默认行为。 |
| hook-succeeded | 在成功运行钩子之后删除 Kubernetes 资源。 |
| hook-failed | 如果在执行钩子时失败,则删除 Kubernetes 资源。 |
默认情况下,Helm 会保留用于钩子的 Kubernetes 资源,直到再次运行钩子。这提供了在运行后检查日志或查看钩子其他信息的能力。设置常见策略的一个例子是前面示例中使用的策略。这将保留钩子资源,除非它们成功完成。当钩子失败时,资源及其日志仍然可供检查,否则它们将被删除。
下面的 Pod 是一个运行 post-install 钩子的示例:
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-post-install"
labels:
{{- include "mychart.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": post-install
"helm.sh/hook-weight": "-1"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
containers:
- name: wget
image: busybox
command: ["/bin/sleep","{{ default "10" .Values.sleepTime }}"]
restartPolicy: Never
如果您正在运行 helm install 等 Helm 命令,并希望跳过运行钩子,可以使用 --no-hooks 标志。此标志适用于具有钩子的命令,并将导致 Helm 跳过执行它们。钩子是一种选择退出的功能。
向图表添加测试
测试是软件开发的一个重要部分,Helm 通过 test 钩子和 Kubernetes 资源提供了测试图表的能力。这意味着测试在 Kubernetes 集群中与工作负载并行运行,并且可以访问图表安装的组件。除了 Helm 内置的图表测试功能外,Helm 项目还提供了一个名为 Chart Testing 的额外测试工具。由于 Chart Testing 建立在 Helm 客户端的功能之上,我们首先看一下 Helm 客户端内置的功能。
Helm 测试
Helm 有一个 helm test 命令,用于在图表的运行实例上执行测试钩子。实现这些钩子的资源可以检查数据库访问、数据库架构是否正确放置、工作负载之间的工作连接以及其他操作细节。
如果测试失败,Helm 将以非零退出代码退出,并向您提供失败的 Kubernetes 资源的名称。非零退出代码在与某些自动化测试系统配对时非常有用,这些系统通过这种方式检测失败。当您有 Kubernetes 资源的名称时,可以查看日志以查看失败的原因。
测试通常位于 templates 目录的 tests 子目录中。将测试放置在此目录中提供了有用的分离。这是一种约定,但不是测试运行的必需条件。
为了说明一个测试,我们将看一下booster 图表。在 templates/tests 目录中,有一个名为 test-connection.yaml 的单个测试,其中包含以下测试钩子:
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "booster.fullname" . }}-test-connection"
labels:
{{- include "booster.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "booster.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
此测试是在运行 helm create 时为 Nginx 创建的默认测试。它也恰好适用于测试与 booster 应用程序的连接。这个简单的测试说明了测试的结构。
注意
如果您查看一些现有图表中的测试,可能会发现它们使用的钩子是 test-success 而不是 test。在 Helm 版本 2 中,有一个名为 test-success 的钩子用于运行测试。Helm 版本 3 提供了向后兼容性,并将此钩子名称作为测试运行。
运行测试有两个步骤。第一步是安装图表,以便其实例正在运行。您可以使用 helm install 命令来执行此操作。以下命令安装 booster 图表,并假设您从图表的根目录运行它:
$ helm install boost .
当图表的实例正在运行时,您可以运行 helm test 命令来执行测试:
$ helm test boost
Helm 在执行期间会输出测试的状态,完成后会提供关于测试和发布的信息。对于之前的测试,它将返回:
Pod boost-booster-test-connection pending
Pod boost-booster-test-connection pending
Pod boost-booster-test-connection running
Pod boost-booster-test-connection succeeded
NAME: boost
LAST DEPLOYED: Tue Jul 21 06:47:05 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: boost-booster-test-connection
Last Started: Tue Jul 21 06:47:12 2020
Last Completed: Tue Jul 21 06:47:17 2020
Phase: Succeeded
NOTES:
1\. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l
"app.kubernetes.io/name=booster,app.kubernetes.io/instance=boost"
-o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:80
当图表有依赖项且这些依赖项有测试时,这些测试也将被运行。例如,如果在本章中使用的 rocket 图表的测试被运行,则将运行 booster 图表的测试和 rocket 图表的测试。
提示
如果您需要在测试的一部分安装配置,则可以将测试挂钩放在 Kubernetes 的 Secret 或 ConfigMap 上,以便与其他测试资源一起安装。
测试图表是确保图表内容能够在 Kubernetes 中运行工作负载并捕捉可能破坏该内容的变化的绝佳方式。
Chart Testing 工具
Helm 项目提供了一个基于 helm test 建立的附加测试工具,提供了更高级的测试能力。它包括的一些附加功能有:
-
能够在图表安装时测试不同的、互斥的配置选项。
-
Chart.yaml 模式验证包括自定义模式规则。
-
添加了可配置规则的 YAML 检查工具。例如,您可以确保 YAML 文件中的缩进保持一致。
-
当源代码存储在 Git 中时,可以检查 Chart.yaml 文件中的
version属性是否已正确递增。 -
能够处理图表集合,并仅测试已更改的图表。
Chart Testing 工具设计用于在持续集成系统工作流中使用,并且其中一些功能直接针对此情况。
Chart Testing 能够测试具有不同、互斥配置的图表,需要知道这些配置。这些配置捆绑在图表的 ci 目录中。
在 ci 目录中,您可以为每种测试情况创建一个值文件。在命名每个文件时,您需要使用 glob 命名模式 **-values.yaml*。例如,您可以使用类似 minimal-values.yaml 和 full-values.yaml 的文件名。
Chart Testing 将分别测试每个配置。例如,在进行图表的语法检查时,将分别检查每个案例。使用 --values 标志将自定义值传递给 helm lint。当运行时测试图表时,相同的想法和标志适用,因为这是最终用户安装图表时提供其自定义配置的方式。
如果您想使用各种配置进行测试,但不希望将这些配置作为图表存档的一部分发布,您可以将 ci 目录放入 .helmignore 文件中。当 Helm 打包图表时,将忽略 ci 目录。
Chart Testing 可以以各种方式安装和使用。例如,您可以将其作为开发系统上的二进制应用程序使用,或者在持续集成系统中的容器中使用。了解更多关于 如何在您的情况下使用和设置它的信息,请查看项目页面。
安全考虑
一些最大和最值得信赖的技术组织曾经通过软件更新受到用户的攻击。软件更改和用于更新甚至安装软件的机制提供了一个攻击通道。
Helm 提供了一种选择加入的方式来检查图表的来源和完整性。来源 提供了验证图表来自公司或个人等的方式,而 完整性 则提供了检查您是否收到了未经更改的预期内容的方法。这种功能使您和使用您图表的人可以验证它们的来源,并且内容没有改变。
为了实现这一点,Helm 使用了 Pretty Good Privacy (PGP)、哈希和一个坐落在图表归档文件旁边的来源文件。例如,如果您有一个名为 mylib-1.0.0.tgz 的图表归档文件,则可以有一个名为 mylib-1.0.0.tgz.prov 的来源文件。该文件包含了一个带有 Chart.yaml 文件内容以及图表归档的哈希的 PGP 消息。Helm 可以为您生成这些文件。以下示例是 mylib-1.0.0.tgz 的来源文件:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
apiVersion: v2
description: an example library chart
name: mylib
type: library
version: 0.1.0
...
files:
mylib-0.1.0.tgz: sha256:d312aea39acf7026f39edebd315a11a52d29a9
6a8d68737ead110ac0c0a1163d
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCgAdFiEEcR8o1RDh4Ly9X2v+lDboC/ukaQkFAl8yiesACgkQlDboC/uk
aQkG2BAAlIEgGI7uu9Kr8j4ZIxDseLmgphhPM1kgnIMPriLieBxFXSJQxciN3+dx
OQpIfdsFQvW98EnJ4781Pm+leHY2iI/L08O1cQWUtzKhfPEWC65YQJPXkTKpHnC2
wXYVUVYWvhx6BJ77RiS/f+hoXiC+i1aBqqS0TAG+AqXuwARO2tY/L7cF6EHjsUwD
pPuTNpYZ/OEWqh1KEYZYVDvLm6uN6QjV4pNTFfAgnvMckfoDLQ+kOPQVqCeUWG3F
tZO3sBzUg+Ak2dDviSTOFQ7TCifc3tOOaWS1XtcooSOkUENmTeeWV56jZnhK1rT4
yaIGT16zXZIdmkZ1t5o9VccuAhQ1Us2FhipdGqpD8yDoJABVz/ee9d2zoX8anfR7
LZ7fwecgQ/THnj54RroyQlzf2aottFiL9ZV4MjUqs0CSoA9+SZ/CcJDd/rxBGI8C
yxRqo0VoNdjT8Kr9hha13krfwD8IpLH8bv4kWt3Ckh6rgphjUL19xyTHJY7w2toY
bAeZMl3Y05Ca76EA7XDdoltE57SUS1Zzd+wDRzRD0IZO8KVk+Z5/PzzvV4l9lnDJ
X63fptInbJpyk0xYKLMFquOY7Yy5mlI9de7424CScePo9Nua3GAakfi4zk3i4Auz
2eaoU/S5uXt605OydkSLLz99BAyJwmazzf/qPyYcPWMw/b+gHxw=
=pRcC
-----END PGP SIGNATURE-----
来源文件是一个具有特定结构的 PGP 签名消息。消息中的哈希由 Helm 用于验证完整性,PGP 签名用于验证来源。
使用来源文件有两个步骤。首先,您需要生成它们。为此,您需要有一个 PGP 密钥对。
在使用 helm package 命令创建包时,可以告诉 Helm 对包进行签名:
$ helm package --sign --key 'bugs@acme.example.com' \
--keyring path/to/keyring mychart
附加标志将告诉 Helm 创建来源文件。--sign 标志选择签名,--key 标志指定要使用的私钥名称,--keyring 标志指定要使用的包含用于签名的私钥的密钥环的位置。当 Helm 创建图表的归档时,它还将创建与之并列的 .prov 文件。
然后应上传来源文件,与图表归档一起提供下载,放置在图表存储库中。
验证是反向进行的,并内置到命令中,如 helm install、helm upgrade 和 helm pull,以及 helm verify 命令中。
Helm 能够处理您本地同时拥有归档和来源文件以及图表位于远程存储库的情况。
为了说明同时拥有这两个文件的情况,我们可以使用 helm verify 命令:
$ helm verify --keyring path/to/keyring mychart-0.1.0.tgz
verify 命令将告诉 Helm 检查哈希和签名。--keyring 标志告诉 Helm 公钥环的位置,该环中包含与图表签名使用的私钥匹配的公钥。这可以是公钥环或非 ASCII-Armored 版本的公钥。Helm 将查找 mychart-0.1.0.tgz.prov 文件,并使用它执行检查。
在 mylib 图表上运行 verify 命令如下所示:
$ helm verify mylib-0.1.0.tgz --keyring public.key
这将输出:
Signed by: Matthew Farina
Using Key With Fingerprint: 672C657BE06B4B30969C4A57461449C25E36B98E
Chart Hash Verified: sha256:d312aea39acf7026f39edebd315a11a52d29a96a8d68737ead11
0ac0c0a1163d
如果在 Helm 仓库中有一个图表,Helm 在下载图表时会同时下载验证文件。例如:
$ helm install --verify --keyring public.key myrepo/mychart
当 Helm 获取图表存档时,还会下载验证文件、验证签名并验证哈希值。
提示
公钥应通过不同的渠道与图表和验证文件共享。
如果在验证过程中出现问题,Helm 将提供错误消息并以非零退出代码退出。
确保图表来自预期之处并且内容未更改,是确保软件供应链安全的有用步骤。
自定义资源定义
Kubernetes 自定义资源定义(CRD)提供了扩展 Kubernetes API 的方法,而 Helm 提供了将它们作为图表的一部分安装的方法。
有两种基于 Helm 的方法来管理图表使用的 CRD。选择使用的方法通常取决于需要安装您的图表的人员的要求和环境配置。
首先,crds目录是您可以添加到图表中以保存您的 CRD 的特殊目录。Helm 会在安装其他资源之前安装 CRD。这确保了 CRD 在图表中的任何自定义资源或控制器使用之前可用。
crds目录中的 CRD 与 Helm 安装的其他资源不同。这些文件没有模板化。这对我们稍后将要涵盖的 CRD 管理工作流程非常有用。Helm 不会像处理其他资源那样升级或删除 CRD。升级 CRD 会更改集群中所有自定义资源实例的 API 表面,而删除 CRD 会移除所有用户的所有自定义资源。在处理这些集群范围的更改时,您需要使用伴随工具,如 Kubernetes 的命令行工具kubectl。
因为 CRD 改变了 Kubernetes API,安装您的图表的人可能没有权限安装、升级或删除它们。如果将应用程序捆绑用于分发给其他公司或一般公众,则情况是如此。一些集群管理员通过安全访问控制限制对这些功能的访问。
crds目录中的 CRD 可以从图表中提取,并可以直接与kubectl等工具一起使用。这样,如果安装图表的人没有权限,可以将提取的 CRD 传递给有权限安装它们的人。提取的 CRD 也可以使用其他工具在集群中升级 CRD。
第二种基于 Helm 的管理 CRD 的方法是使用第二个图表来安装 CRD,以确保在使用自定义资源之前安装 CRD。这种方法通过 Helm 提供了更加精细的控制。
使用第二个图表可以让您:
-
使用 Helm 模板和正常的模板目录来管理 CRD。
-
Helm 将管理 CRD 的生命周期,包括卸载和升级。如果您希望在卸载图表后保留 CRD 安装状态,可以设置注解
"helm.sh/resource-policy": keep,告诉 Helm 跳过卸载资源的步骤。 -
如果您在应用程序中遇到问题,并使用卸载和重新安装方法尝试修复问题,则独立图表中的 CRD 将不会被删除。
可以通过松耦合或紧耦合安装第二个图表。如果将持有 CRD 的图表设置为依赖项,则使用情况应该是仅安装一次,因为它正在设置集群范围的资源。
在 Helm 管理 CRD 时,需要特别注意处理升级和删除的情况。例如,如果安装了 CRD 的图表的两个版本,则需要确保旧版本不会覆盖新版本,并且新版本不会破坏集群中其他使用旧版本的用户的功能。如果两个不同版本的安装 CRD 的图表由两个不同的人安装,则可能会发生这种情况。在多租户集群中,集群的不同用户可能不知道彼此的存在,因此确保一个集群用户不会破坏另一个集群用户的工作负载非常重要。
在安装和使用 CRD 时,Helm 开发人员建议在整个生命周期的所有步骤中特别小心,以确保图表的用户不会意外地破坏生产工作负载。
结论
Helm 图表不仅仅是模板的集合。它们处理依赖关系,可以包含模式,提供事件钩子机制,可以包含测试,并具有安全特性。这些特性使 Helm 成为解决软件包管理问题的强大且可靠的解决方案的一部分。
第七章:图表存储库
没有一种软件包管理器能够完整而不具备发布和分发软件包的方式。组织和供应商必须有一种方法来发布供最终用户下载和使用的软件包。同样,最终用户必须有一种从多种来源获取软件包的共同方式。
Helm 通过称为图表存储库的系统实现软件包分发。图表存储库是简单的 HTTP(S) Web 服务,用户可以从中发现和下载可用的图表。从概念上讲,图表存储库在设计上类似于 Debian 软件包存储库、Fedora 软件包数据库或 Comprehensive Perl Archive Network (CPAN)。
在本章中,我们将首先深入探讨图表存储库的内部工作原理。我们将讨论存储库索引及如何使用新的图表版本更新它。之后,我们将展示如何从头开始设置图表存储库,如何确保其安全,并展示一个使用 GitHub Pages 为开源项目托管公共图表存储库的实际示例。之后,我们将详细介绍各种helm repo命令及其有效使用方法。
在本章的末尾,我们将介绍使用 Helm 实验性 Open Container Initiative(OCI)支持的下一代图表存储库。这一前沿功能是在 Helm 3 中添加的,允许用户将 Helm 图表存储在容器注册表中,与其容器镜像并存。
最后,我们将简要描述与图表存储库相关的 Helm 生态系统中的一些项目。
存储库索引
所有的图表存储库都包含一个名为index.yaml的特殊存储库索引文件,列出了所有可用的图表及其各自的下载位置。
注意
详细描述index.yaml格式的更多细节,请参见附录 B。
这里是一个非常基本的index.yaml文件示例:
apiVersion: v1
entries:
superapp:
- apiVersion: v2
appVersion: 1.16.0
created: "2020-04-27T17:46:52.60919-05:00"
description: A Helm chart for Kubernetes
digest: cd1f8d949aeb6a7a3c6720bfe71688d4add794881b78ad9715017581f7867db4
name: superapp
type: application
urls:
- superapp-0.1.0.tgz
version: 0.1.0
generated: "2020-04-27T17:46:52.607943-05:00"
注意entries部分,其中列出了所有图表及其版本。此index.yaml示例仅列出了一个图表superapp,具有一个版本 0.1.0。
图表存储库索引示例
通常,图表存储库会列出许多图表及其所有可用的版本。以下是一个更真实的图表存储库索引示例,包含多个图表及其版本:
apiVersion: v1
entries:
cert-manager:
- apiVersion: v1
appVersion: v0.14.2
created: "2020-04-08T11:38:26.281Z"
description: A Helm chart for cert-manager
digest: 160e1bd4906855b91c8ba42afe10af2d0443b184916e4534175890b1a7278f4e
home: https://github.com/jetstack/cert-manager
icon: https://raw.githubusercontent.com/jetstack/cert-manager/master/logo/
logo.png
keywords:
- cert-manager
- kube-lego
- letsencrypt
- tls
maintainers:
- email: dev@jetstack.io
name: jetstack-dev
name: cert-manager
sources:
- https://github.com/jetstack/cert-manager
urls:
- charts/cert-manager-v0.14.2.tgz
version: v0.14.2
- apiVersion: v1
appVersion: v0.14.1
created: "2020-03-25T18:30:16.354Z"
description: A Helm chart for cert-manager
digest: 629150400487df41af6c7acf2a3bfd8e691f657a930bc81e1dcf3b9d23329baf
home: https://github.com/jetstack/cert-manager
icon: https://raw.githubusercontent.com/jetstack/cert-manager/master/logo/
logo.png
keywords:
- cert-manager
- kube-lego
- letsencrypt
- tls
maintainers:
- email: dev@jetstack.io
name: jetstack-dev
name: cert-manager
sources:
- https://github.com/jetstack/cert-manager
urls:
- charts/cert-manager-v0.14.1.tgz
version: v0.14.1
tor-proxy:
- apiVersion: v1
created: "2018-11-16T09:23:13.538Z"
description: A Helm chart for Kubernetes
digest: 1d2fd11e22ba58bf0a263c39777f0f18855368b099aed7b03123ca91e55343e4
name: tor-proxy
urls:
- charts/tor-proxy-0.1.1.tgz
version: 0.1.1
generated: "2020-04-23T17:43:41Z"
上述示例显示了两个可用的图表:cert-manager和tor-proxy。共有三个可用的图表版本:cert-manager v0.14.1、cert-manager v0.14.2(最新版本)和tor-proxy 0.1.1(最新版本)。运行helm search时将显示存储库中每个图表的最新版本。
通常,图表归档(.tgz文件)本身是从与存储库索引相同的位置提供的,但索引也可能链接到完全不同域上的远程位置。以下是一个从index.yaml中引用图表归档的片段,位于单独域上(请注意绝对网址):
...
- appVersion: 2.10.1
created: 2019-01-14T23:25:37.125126859Z
description: A simple, powerful publishing platform that allows you to share
your stories with the world
digest: dcadf39f81253a9a016fcab1b74aba1d470e015197152affdaeb1b337221cc5c
engine: gotpl
home: http://www.ghost.org/
icon: https://bitnami.com/assets/stacks/ghost/img/ghost-stack-220x234.png
keywords:
- ghost
- blog
- http
- web
- application
- nodejs
- javascript
maintainers:
- email: containers@bitnami.com
name: Bitnami
name: ghost
sources:
- https://github.com/bitnami/bitnami-docker-ghost
urls:
- https://charts.example.com/ghost-6.2.3.tgz 
version: 6.2.3
...
绝对图表 URL
每个条目中还包含在Chart.yaml中描述的图表元数据的其他字段,例如description,以及包含图表归档的安全哈希算法(SHA-256)摘要字段。在第四章中,我们详细讨论了图表元数据和Chart.yaml。
此外,在顶层还有一个generated字段,描述索引的创建时间(RFC 3339 格式),以及一个apiVersion字段,描述图表存储库的 API 版本。目前只有一个图表存储库的 API 版本。此字段应始终为v1。
生成索引
存储库索引可以通过自定义程序生成,或者手动输入。Helm 还提供了生成存储库索引的内置功能。
让我们创建一个空目录charts/,它将作为我们图表仓库的根目录:
$ mkdir -p charts/
要在charts/目录中生成存储库索引,请运行以下命令:
$ helm repo index charts/
这将在charts/index.yaml创建一个文件。让我们来看一下:
$ cat charts/index.yaml
apiVersion: v1
entries: {}
generated: "2020-04-28T09:55:29.517285-05:00"
你会注意到entries是空的。这是预期的,因为我们在charts/目录中还没有任何图表。
让我们创建一个示例图表,并将其打包到charts/目录中:
$ helm create superapp
Creating superapp
$ helm package superapp/ --destination charts/
Successfully packaged chart and saved it to: charts/superapp-0.1.0.tgz
现在让我们再次尝试生成索引:
$ helm repo index charts/
$ cat charts/index.yaml
apiVersion: v1
entries:
superapp:
- apiVersion: v2
appVersion: 1.16.0
created: "2020-04-28T10:12:22.507943-05:00"
description: A Helm chart for Kubernetes
digest: 46f9ddeca12ec0bc257a702dac7d069af018aed2a87314d86b230454ac033672
name: superapp
type: application
urls:
- superapp-0.1.0.tgz
version: 0.1.0
generated: "2020-04-28T10:12:22.507289-05:00"
现在我们在entries部分看到了我们的图表。
添加到现有索引
在某些场景下(例如持续集成/持续部署[CI/CD]),你可能只能访问现有的index.yaml文件和新打包的图表归档。Helm 提供了一个使用--merge选项构建现有索引内容的机制。
让我们模拟这种情况。创建一个名为workspace/的新目录,它将代表 CI/CD 管道中的新工作目录:
$ mkdir -p workspace/
将现有的索引文件复制到workspace/目录,并使用新名称,比如index-old.yaml:
$ cp charts/index.yaml workspace/index-old.yaml
在实际场景中,您可能从某个远程位置(例如 Amazon S3)获取现有的索引文件。
接下来让我们创建另一个 Helm 图表,并将其打包到workspace/目录中:
$ helm create duperapp
Creating duperapp
$ helm package duperapp/ --destination workspace/
Successfully packaged chart and saved it to: workspace/duperapp-0.1.0.tgz
运行以下命令,将根据index-old.yaml中现有条目的组合以及workspace/目录中的任何.tgz文件创建一个新的index.yaml文件:
$ helm repo index workspace/ --merge workspace/index-old.yaml
最后,将workspace/目录中的文件移动到charts/目录中,覆盖旧的索引文件:
$ mv workspace/duperapp-0.1.0.tgz charts/
$ mv workspace/index.yaml charts/
现在索引文件的新版本应该包含两个图表的条目:
$ cat charts/index.yaml
apiVersion: v1
entries:
duperapp:
- apiVersion: v2
appVersion: 1.16.0
created: "2020-04-28T11:34:26.780267-05:00"
description: A Helm chart for Kubernetes
digest: 30ea14a4ce92e0d1aea7626cb30dfbac68a87dca360d0d76a55460b004d62f52
name: duperapp
type: application
urls:
- duperapp-0.1.0.tgz
version: 0.1.0
superapp:
- apiVersion: v2
appVersion: 1.16.0
created: "2020-04-28T10:12:22.507943-05:00"
description: A Helm chart for Kubernetes
digest: 46f9ddeca12ec0bc257a702dac7d069af018aed2a87314d86b230454ac033672
name: superapp
type: application
urls:
- superapp-0.1.0.tgz
version: 0.1.0
generated: "2020-04-28T11:34:26.779758-05:00"
在不一定具有包含所有图表存档的目录访问权限的环境中,此方法非常有用。
请记住,如果此合并同时在多个系统上发生,您可能会遇到一个竞争条件,其中一个或多个图表可能会从索引中消失。可以通过确保此过程仅同步执行(例如,单个 CI 作业负责为存储库创建index.yaml)来减轻此问题。解决此问题的另一种方法是使用负责生成index.yaml内容的动态 web 服务器。本章稍后描述的ChartMuseum项目在此目的上是一个这样的例子,它是一个动态图表存储库服务器。
设置图表存储库
图表存储库的一个好处是它们可以完全静态化——这意味着您可以将文件放在诸如 Apache 或 Nginx 之类的简单 web 服务器后面,并按原样提供服务。您甚至可以使用对象存储提供者,例如 Amazon S3。例如,当客户端请求index.yaml时,服务器端不需要进行任何重要的计算。静态 web 服务器只需打开文件并将原始内容发送回客户端。
使用 Python 的简单图表存储库
为了本例,我们将使用 Python 的内置静态 web 服务器启动一个本地测试存储库。请注意,几乎所有编程语言的标准库都支持启动 web 服务器并提供静态文件的服务。选择 Python 仅仅是因为它预装在大多数基于 Unix 的系统上,并且因为它提供了一个简单的一行命令来启动静态 web 服务器。
按照上一节的说明(“生成索引”)创建charts/目录,其中包含文件index.yaml,superapp-0.1.0.tgz和duperapp-0.1.0.tgz。运行以下命令中的一个以在http://localhost:8080启动本地 web 服务器。
使用 Python 3(首先尝试这个):
$ ( cd charts/ && python3 -m http.server --bind 127.0.0.1 8080 )
使用 Python 2:
$ ( ch charts/ && python -m SimpleHTTPServer 8080 )
小心
这个命令的 Python 2 版本监听所有接口(0.0.0.0),而不仅仅是环回接口(127.0.0.1)。根据您的系统,这将允许网络中的其他设备连接。在运行此命令之前,请注意charts/目录中存在哪些文件。
现在,在另一个终端窗口中,尝试使用curl获取index.yaml:
$ curl -sO http://localhost:8080/index.yaml
$ ls *.yaml
index.yaml
现在让我们验证一下我们是否可以获取图表存档:
$ curl -sO http://localhost:8080/superapp-0.1.0.tgz
$ curl -sO http://localhost:8080/duperapp-0.1.0.tgz
$ ls *.tgz
duperapp-0.1.0.tgz superapp-0.1.0.tgz
如果curl命令成功,您的图表存储库已准备好与 Helm 一起使用。
保护图表存储库
在许多情况下,您可能希望限制对图表存储库的访问,或者保持哪些用户访问了哪些资源的审计跟踪。Helm 内置支持,允许用户对由基本身份验证或 mTLS 保护的图表存储库进行身份验证。
基本身份验证
图表存储库可以通过基本访问认证或基本认证进行保护。这需要用户提供有效的用户名和密码组合以访问服务器上的资源。
服务器可以通过在处理请求之前首先检查Authorization头部来实现基本认证。传入的基本认证头部类似于以下内容:
Authorization: Basic bXl1c2VyOm15cGFzcw== 
此处的不透明字符串是username + “:” + password 的 Base64 编码。
注意
Authorization头部的内容不加密,因此强烈建议在提供基本认证凭据时也使用 HTTPS。
当首次添加存储库时,您可以在命令行上提供用户名和密码组合,这将指示 Helm 在与该存储库进行请求时使用基本认证:
$ helm repo add mycharts http://localhost:8080 --username myuser \
--password mypass
"mycharts" has been added to your repositories
客户端证书
大多数通过 HTTPS 进行的客户端-服务器通信允许客户端根据服务器提供的 SSL 证书验证服务器的身份。通过双向 TLS 认证(mTLS),服务器还可以根据客户端在 TLS 握手期间提供的单独 SSL 证书验证客户端的身份。
这是一个简单的 Nginx 服务器配置,用于启用图表存储库的 mTLS,假设静态文件(即index.yaml,.tgz文件)位于服务器上的目录/chartrepo中:
events { }
http {
server {
root /chartrepo;
listen 443 ssl;
server_name charts.example.com;
ssl_certificate /certs/server.crt; 
ssl_certificate_key /certs/server.key; 
ssl_client_certificate /certs/client-ca.pem; 
ssl_verify_client on;
proxy_set_header SSL_CLIENT_CERT $ssl_client_cert;
}
}
服务器的 SSL 证书
服务器的私钥
客户端身份验证的证书颁发机构(CA)——只有由此 CA 签名的证书的客户端请求将被接受
获得客户端证书的第一步是生成新的私钥和证书签名请求(CSR):
$ mkdir -p ~/client-certs/
$ cd ~/client-certs/
$ openssl genrsa -out francis.key 4096
$ openssl req -new -key francis.key -out francis.csr
在生成证书签名请求(CSR)时,当提示“通用名称”时,您必须输入一个值。使用能够识别客户端的内容(例如“francis”)。其他字段在技术上可以留空,尽管鼓励您填写它们。
接下来,使用服务器上配置的证书颁发机构(client-ca.pem)和相关私钥(client-ca.key),从 CSR 生成新的客户端证书:
$ openssl x509 -req -in francis.csr \
-CA /certs/client-ca.pem -CAkey /certs/client-ca.key \
-out francis.crt -sha256
现在您可以通过在添加新图表存储库时指定--cert-file和--key-file选项来使用此证书进行身份验证:
$ helm repo add client-cert-repo https://charts.example.com \
--cert-file ~/client-certs/francis.crt --key-file ~/client-certs/francis.key
"client-cert-repo" has been added to your repositories
如果您的服务器使用自签名证书,您还可以指定--ca-file选项,指向一个受信任的证书或证书包:
$ helm repo add client-cert-repo-selfsigned https://charts.example.com \
--cert-file ~/client-certs/francis.crt --key-file ~/client-certs/francis.key
--ca-file /certs/server.crt
"client-cert-repo-selfsigned" has been added to your repositories
注意
用于--cert-file,--key-file和--ca-file的路径都存储在与存储库相关的 Helm 缓存中。不要移动这些文件,否则将由于客户端需要的缺失文件而导致对存储库的未来请求失败。
有关更多关于 mTLS 的信息,请参阅互联网工程任务组(IETF)RFC 8446,“传输层安全性(TLS)协议版本 1.3”。
真实示例:使用 GitHub Pages
GitHub 提供了一个免费的静态托管解决方案,称为 GitHub Pages。如果您不介意将您的图表公开给世界,GitHub Pages 是托管图表存储库的绝佳选择,因为您不需要支付任何费用。
更好的是,GitHub Pages 允许您使用指向您的 GitHub Pages 站点的自定义域名。本节将展示如何使用 GitHub Pages 轻松设置一个公共 Helm 图表存储库。
GitHub Pages 存在一些限制(如带宽),因此在使用此方法之前,请根据您的图表存储库的性能要求与 GitHub Pages 功能文档进行比较。
创建一个新的 Git 存储库
第一步是在 GitHub 上创建一个全新的 Git 存储库,专门用于您的图表存储库。您理论上可以将图表存储库与其他内容一起托管,但为了简单起见,我们将使用一个专用的 Git 存储库。图 7-1 显示了如何设置一个新的存储库。

图 7-1. 在 GitHub 中创建新的公共存储库
一旦您登录 GitHub,点击屏幕右上角,选择“新建存储库”。为 Git 存储库命名任何您喜欢的名称。在本示例中,我们将使用名称 mycharts。确保选择将存储库标记为“Public”,这是使用 GitHub Pages 的先决条件。选择“Initialize this repository with a README” 的选项,这将允许我们立即克隆存储库。随意选择许可证,如“MIT License”,表示该存储库中的源代码可自由使用和重新用途。最后,点击“Create repository”。
注意
在这个背景下,需要注意 Helm 存储库(或图表存储库)与托管在 GitHub 上的 Git 存储库之间的区别,后者用于版本控制。
启用 GitHub Pages
转到存储库的设置面板。在主设置中,向下滚动到名为 GitHub Pages 的部分(参见 图 7-2)。对于 Source 选项,请选择“main branch”。这将导致 GitHub 在您提交新的提交到主分支时重新部署您的 GitHub Pages 站点。点击保存。

图 7-2. 在您的存储库上启用 GitHub Pages
可选:使用自定义域名
GitHub Pages 上的站点默认作为 github.io 域的子域托管。例如,您的站点的 URL 将类似于 https://yourusername.github.io/mycharts/。
如果你有一个要使用的自定义域名,在你的注册商的网页控制台(或者替代方案,你设置了用于你授权域名服务器的服务的控制台),创建一个新的 DNS 记录,指向yourusername.github.io。如果使用根域名,使用ALIAS记录类型;否则,对于子域名,使用CNAME记录类型。
返回 GitHub 中的仓库设置。如图 7-3,在“自定义域名”输入框中,输入你为其设置了 DNS 记录的域名。

图 7-3. 使用 GitHub Pages 的自定义域名
GitHub 可能需要最多一个小时来为你的域生成 TLS 证书。一旦准备好,你应该在设置中看到一些文本显示“你的站点发布在 https://example.com。”一旦看到这个,确保启用“强制 HTTPS”选项,这样你的站点只能通过 HTTPS 访问,而不是普通的 HTTP。
添加图表仓库文件
在 GitHub UI 中找到你仓库的克隆 URL(通常在屏幕右侧)。将你的新 GitHub 仓库克隆到本地系统,以便我们添加一些文件将其转换成一个真正的图表仓库:
$ git clone git@github.com:youruser/mycharts.git
Cloning into 'mycharts'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 7 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (7/7), done.
输入你的 Git 仓库的目录:
$ cd mycharts/
接下来,让我们在一个新的src/目录中创建一个名为pineapple的图表,将其打包成一个存档文件放在仓库的根目录,并创建一个index.yaml文件:
$ mkdir -p src/
$ helm create src/pineapple
Creating src/pineapple
$ helm package src/pineapple/
Successfully packaged chart and saved it to: /home/user/mycharts/
pineapple-0.1.0.tgz
$ helm repo index .
完成后,让我们把所有这些新文件都提交并推送回 GitHub:
$ git add .
$ git commit -m "Add pineapple chart v0.1.0"
[main 9bba19d] Add pineapple chart v0.1.0
13 files changed, 395 insertions(+)
create mode 100644 index.yaml
create mode 100644 pineapple-0.1.0.tgz
create mode 100644 src/pineapple/.helmignore
create mode 100644 src/pineapple/Chart.yaml
create mode 100644 src/pineapple/templates/NOTES.txt
create mode 100644 src/pineapple/templates/_helpers.tpl
create mode 100644 src/pineapple/templates/deployment.yaml
create mode 100644 src/pineapple/templates/hpa.yaml
create mode 100644 src/pineapple/templates/ingress.yaml
create mode 100644 src/pineapple/templates/service.yaml
create mode 100644 src/pineapple/templates/serviceaccount.yaml
create mode 100644 src/pineapple/templates/tests/test-connection.yaml
create mode 100644 src/pineapple/values.yaml
$ git push origin main
Enumerating objects: 20, done.
Counting objects: 100% (20/20), done.
Delta compression using up to 12 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (19/19), 9.29 KiB | 4.64 MiB/s, done.
Total 19 (delta 0), reused 0 (delta 0)
To github.com:youruser/mycharts.git
4964b76..9bba19d main -> main
返回浏览器中的 GitHub。在你推送改变和这些改变在 GitHub Pages 网站上可用之间会有一小段延迟。点击右侧边栏中的“环境”项。这会告诉你你的站点上一次部署的时间。如果你看到引用了你刚刚推送的提交(在前面的例子中是9bba19d),你的 GitHub Pages 站点已经可以使用了。
将你的 GitHub Pages 站点用作图表仓库
一旦你上传了一个index.yaml文件到你的 Git 仓库,并且站点已经使用最新的提交上线,你可以像使用任何其他图表仓库一样使用它。
将你的 GitHub Pages 图表仓库添加到你的本地仓库:
$ helm repo add gh-pages https://yourusername.github.io/mycharts/
或者,如果你正在使用自定义域名:
$ helm repo add gh-pages https://example.com
使用图表仓库
一旦你有了一个工作的图表仓库(参见前面的章节),你可以使用 Helm CLI 来利用它。
顶级helm repo子命令下提供了几个命令来处理图表仓库。本节将重点介绍如何有效地使用每个命令。
添加一个仓库
使用图表仓库的第一步是给它分配一个唯一的名称(如mycharts),并将其添加到 Helm 已知的仓库列表中。当你第一次添加一个仓库时,Helm 会从提供的 URL 获取index.yaml并将其存储在本地。
使用helm repo add命令添加你的仓库:
$ helm repo add mycharts http://localhost:8080
"mycharts" has been added to your repositories
如果您正在运行 Python 示例,请查看您的图表存储库日志,您应该会看到有关GET /index.yaml的传入请求:
127.0.0.1 - - [06/May/2020 15:31:07] "GET /index.yaml HTTP/1.1" 200 -
下载图表
要从存储库直接下载图表,请使用helm pull命令:
$ helm pull mycharts/superapp
根据语义版本,Helm 将自动找到最新版本。您还可以指定一个版本:
$ helm pull mycharts/superapp --version 0.1.0
这将在您的工作空间中生成一个新的图表存档(.tgz文件):
$ ls *.tgz
superapp-0.1.0.tgz
然后可以直接安装此存档:
$ helm install superapp-dev superapp-0.1.0.tgz
您还可以直接从添加的存储库安装图表:
$ helm install superapp-dev mycharts/superapp
列出存储库
知道已经添加到您系统中的哪些图表存储库通常是有帮助的。这可能会帮助您决定是否要使用其中一个来下载图表,或者彻底从系统中移除其中一个。
使用helm repo list命令列出添加到您系统的所有图表存储库:
$ helm repo list
NAME URL
mycharts http://localhost:8080
如果需要,您还可以利用--output / -o选项以机器可读格式获取这些内容。
通过添加-o yaml来获取 YAML 格式的列表:
$ helm repo list -o yaml
- name: mycharts
url: http://localhost:8080
通过添加-o json来获取 JSON 格式的列表:
$ helm repo list -o json
[{"name":"mycharts","url":"http://localhost:8080"}]
更新存储库
一旦发布新的图表版本,存储库所有者就会将.tgz包添加到存储库存储中,并使用新条目更新index.yaml。
为了获取存储库索引的最新版本,请使用helm repo update命令:
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "mycharts" chart repository
Update Complete. Happy Helming!
如果您正在运行 Python 示例,则应该注意到图表存储库的输出日志中会有一个关于GET /index.yaml的传入请求:
127.0.0.1 - - [06/May/2020 15:31:07] "GET /index.yaml HTTP/1.1" 200 -
无论存储库索引是否已更改内容(我们没有向myrepo添加更多图表),该文件都会被获取并下载到缓存中,覆盖先前保存的版本。
删除存储库
为了移除存储库,您可以使用helm repo remove:
$ helm repo remove mycharts
"mycharts" has been removed from your repositories
这将删除 Helm 缓存中存储的有关此存储库的所有引用。
实验性 OCI 支持
警告
Helm 的 OCI 支持仍然被认为是高度实验性的。尽管这一领域的开发仍在积极进行,但本节描述的语法可能很快就会过时。
图表存储库系统旨在易于使用。在大多数情况下,这个系统已被证明是足够的——使全球各地的组织能够共享和分发他们的 Helm 图表。
然而,图表存储库确实存在一些关键挑战:
-
它们没有命名空间的概念;存储库中的所有图表都列在单个索引中。
-
它们没有细粒度的访问控制;您要么可以访问存储库中的所有图表,要么不能访问任何图表。
-
图表包使用不同名称但完全相同的原始内容存储两次。
-
存储库索引可能变得非常庞大,导致 Helm 消耗大量内存。
与尝试添加功能以解决当前图表存储库模型的所有这些问题不同,将下一代图表存储库构建在符合OCI 分发规范的注册表上是更为合理的选择。
OCI 代表 Open Container Initiative。从 https://opencontainers.org 网站上得知,OCI 的定义如下:
一个开放的治理结构,旨在围绕容器格式和运行时创建开放行业标准。
OCI 定义的标准之一是 分发规范。该规范描述了用于分发容器镜像的 HTTP API。有趣的是,此 API 是通用的,并且适用于不是容器镜像的各种内容,比如 Helm 图表!
从 Helm 3.0.0 开始,添加了实验性支持,可以推送和拉取基于 OCI 的容器注册表中的图表。
启用 OCI 支持
目前为止,Helm 的 OCI 支持仍然被视为实验性质。
目前,请在您的环境中设置以下内容以启用 OCI 支持:
$ export HELM_EXPERIMENTAL_OCI=1
运行本地注册表
Docker Distribution 项目(也称为 Docker 注册表)是 Docker 的 Registry v2 API 的原始实现。它原生支持 Helm 图表。
如果已安装 docker,可以使用以下命令在端口 5000 上的容器中轻松运行本地注册表:
$ docker run -d --name oci-registry -p 5000:5000 registry
要查看注册表的日志,请运行以下命令(按 Ctrl-C 退出):
$ docker logs -f oci-registry
要停止注册表,请运行以下命令:
$ docker rm -f oci-registry
Docker 注册表有关于认证、存储等方面的 多个配置选项。
如果希望使用单个用户名密码组合配置基本认证,首先创建一个 .htpasswd 文件:
$ htpasswd -cB -b auth.htpasswd myuser mypass
然后启动注册表,挂载 .htpasswd 文件并设置 REGISTRY_AUTH 环境变量:
$ docker run -d --name oci-registry -p 5000:5000 \
-v $(pwd)/auth.htpasswd:/etc/docker/registry/auth.htpasswd \
-e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry \
auth.htpasswd}}" registry
有关 Docker Distribution 的更多信息,请访问 项目 GitHub 页面。
登录到注册表
要对注册表进行身份验证,请使用 helm registry login 命令(系统会提示您手动输入密码):
$ helm registry login -u myuser localhost:5000
Password:
Login succeeded
它会使用凭据在注册表上的路径 /v2/ 发出简单的 GET 请求,以确定它们是否有效。如果有效,凭据将存储在 Helm 配置文件中。如果启用了任何 Docker 凭据存储(例如 macOS 上的 osxkeychain),用户名和密码将安全地存储在那里。
注意
在 localhost:5000 运行本地注册表的示例未使用认证。如果尚未在注册表上启用认证,则将接受任何登录凭据的组合。
从注册表注销
要从系统中删除特定注册表的凭据,请使用 helm registry logout 命令:
$ helm registry logout localhost:5000
Logout succeeded
将图表存储到缓存中
在将图表上传到注册表之前,必须先将其保存到缓存中。这将把图表从其正常状态转换为内容可寻址的块,并为其提供唯一标识符。
使用 helm chart save 将图表存储到缓存中:
$ helm chart save mychart/ localhost:5000/myrepo/mychart
ref: localhost:5000/myrepo/mychart:2.7.0
digest: 1b251d38cfe948dfc0a5745b7af5ca574ecb61e52aed10b19039db39af6e1617
size: 2.4 KiB
name: mychart
version: 2.7.0
2.7.0: saved
注意,在图表引用中使用的标签基于 Chart.yaml 中的版本(2.7.0)。
您还可以通过在图表引用后的冒号(:)之后指定自定义标签,如 stable,来使用自定义标签:
$ helm chart save mychart/ localhost:5000/myrepo/mychart:stable
ref: localhost:5000/myrepo/mychart:stable
digest: 1b251d38cfe948dfc0a5745b7af5ca574ecb61e52aed10b19039db39af6e1617
size: 2.4 KiB
name: mychart
version: 2.7.0
stable: saved
在缓存中列出图表
使用 helm chart list 来显示当前存储在缓存中的所有图表:
$ helm chart list
REF VERSION DIGEST SIZE
localhost:5000/myrepo/mychart:2.7.0 2.7.0 84059d7 454 B
localhost:5000/stable/acs-engine-autoscaler:2.2.2 2.2.2 d8d6762 4.3 KiB
localhost:5000/stable/aerospike:0.2.1 0.2.1 4aff638 3.7 KiB
localhost:5000/stable/airflow:0.13.0 0.13.0 c46cc43 28.1 KiB
localhost:5000/stable/anchore-engine:0.10.0 0.10.0 3f3dcd7 34.3 KiB
从缓存中导出图表
如果您希望在缓存中存在的图表中提取源文件,必须先将其导出到本地目录。使用 helm chart export 命令来导出图表:
$ helm chart export localhost:5000/myrepo/mychart:2.7.0
ref: localhost:5000/myrepo/mychart:2.7.0
digest: 1b251d38cfe948dfc0a5745b7af5ca574ecb61e52aed10b19039db39af6e1617
size: 2.4 KiB
name: mychart
version: 2.7.0
Exported chart to mychart/
图表的名称将用于确定输出路径(例如,mychart/)。
将图表推送到注册表
将图表推送(即上传)到注册表允许其他人使用它。一旦您已经登录到注册表,并且要推送的图表已保存到缓存中,请使用 helm chart push 命令来推送图表:
$ helm chart push localhost:5000/myrepo/mychart:2.7.0
The push refers to repository [localhost:5000/myrepo/mychart]
ref: localhost:5000/myrepo/mychart:2.7.0
digest: 1b251d38cfe948dfc0a5745b7af5ca574ecb61e52aed10b19039db39af6e1617
size: 2.4 KiB
name: mychart
version: 2.7.0
2.7.0: pushed to remote (1 layer, 2.4 KiB total)
从注册表中拉取图表
一旦图表被推送到注册表,其他用户就可以拉取(即下载)它们。从注册表拉取图表会将它们放入本地缓存中。要从注册表拉取现有的图表,请使用 helm chart pull 命令:
$ helm chart pull localhost:5000/myrepo/mychart:2.7.0
2.7.0: Pulling from localhost:5000/myrepo/mychart
ref: localhost:5000/myrepo/mychart:2.7.0
digest: 1b251d38cfe948dfc0a5745b7af5ca574ecb61e52aed10b19039db39af6e1617
size: 2.4 KiB
name: mychart
version: 2.7.0
Status: Downloaded newer chart for localhost:5000/myrepo/mychart:2.7.0
从缓存中移除图表
要从本地缓存中移除图表,请使用 helm chart remove 命令:
$ helm chart remove localhost:5000/myrepo/mychart:2.7.0
2.7.0: removed
相关项目
Helm 的图表存储库系统已经衍生出一系列开源工具,进一步增强了这一体验。以下小节介绍了与图表存储库相关的一些项目。
ChartMuseum
项目主页:https://github.com/helm/chartmuseum
ChartMuseum 是一个简单的图表存储库 Web 服务器。配置它指向包含图表包的存储位置,它会动态生成 index.yaml。它还提供了一个 HTTP API,用于从存储中上传、查询和删除图表包。此外,它还具有用于身份验证、多租户和缓存的多个其他配置设置,使其成为托管私有或内部图表存储库的热门选择。
Harbor
项目主页:https://github.com/goharbor/harbor
Harbor 是一个功能齐全的注册表,具有增强的安全性和管理功能。它提供了一个用于 Helm 图表的 UI,并在后端利用 ChartMuseum 作为多租户图表存储库。它还支持 Helm 的实验性 OCI 功能集。
类似于 Helm,Harbor 是一个毕业生,顶级 CNCF 项目。
Chart Releaser
项目主页:https://github.com/helm/chart-releaser
Chart Releaser,或者 cr,是一个命令行工具,利用 GitHub 发布来托管图表包。它能够检测 Git 仓库中的图表,打包它们,并将每个版本命名为唯一的图表版本后上传到 GitHub 发布的工件。
使用 cr 上传图表后,该工具还可以基于 GitHub 发布和相关工件的内容生成一个 index.yaml 文件。这个仓库索引可以静态托管在 GitHub Pages 或其他地方。
S3 插件
项目主页:https://github.com/hypnoglow/helm-s3
S3 插件是 Helm 的一个插件,允许你将私有 Amazon S3 存储桶作为一个图表仓库使用。
GCS 插件
项目主页:https://github.com/hayorov/helm-gcs
GCS 插件是 Helm 的一个插件,允许你将私有 Google Cloud 存储桶作为一个图表仓库使用。
Git 插件
项目主页:https://github.com/aslafy-z/helm-git
Git 插件是 Helm 的一个插件,允许你将包含图表源文件的 Git 仓库作为一个图表仓库使用。它支持子路径、自定义引用和 HTTPS 和 SSH Git URL。
第八章:Helm 插件和启动器
正如我们在本书中所见,Helm 具有丰富的功能和方法,有助于在 Kubernetes 上交付应用程序。但是,还可以自定义和扩展 Helm 提供的功能。
在本章中,我们将讨论两种进一步增强和自定义您使用 Helm 的方式:插件和启动器。
插件允许您为 Helm 添加额外功能,并与 CLI 无缝集成,因此对于具有独特工作流需求的用户而言,它们是一个受欢迎的选择。在线上有许多第三方插件可供常见用例使用,如秘密管理。此外,对于独特的一次性任务,构建自己的插件也非常容易。
启动器扩展了使用 helm create 生成新的 Helm 图表的可能性。例如,您可能已经为一个内部微服务构建了一个完全符合未来微服务需求的 Helm 图表示例。您可以将该图表转换为一个启动器,然后在启动新项目时每次使用。
通过利用插件和启动器,我们可以构建在 Helm 的开箱即用功能之上,简化和自动化日常工作流任务。
插件
Helm 插件是可以直接从 Helm CLI 访问的外部工具。它们允许您向 Helm 添加自定义子命令,而无需对 Helm 的 Go 源代码进行任何修改。这在设计上类似于其他工具中实现插件系统的方式,比如 kubectl(Kubernetes CLI)。
此外,下载器插件允许您指定用于与图表仓库通信的自定义协议。如果您有某种自定义身份验证方法,或者需要修改 Helm 从仓库获取图表的方法,这将非常有用。
安装第三方插件
许多第三方插件是开源的,并且在 GitHub 上公开可用。其中许多插件使用“helm-plugin”标签/主题,使其易于查找。请参阅 GitHub 上的 Helm 插件文档。
安装插件后,获取其版本控制 URL。这将用作获取 plugin.yaml 和插件源代码的正确版本的手段。
目前支持 Git、SVN、Bazaar (Bzr) 和 Mercurial (Hg) 的 URL。对于 Git,版本控制 URL 看起来像 https://example.com/myorg/myrepo.git。
例如,有一个简单的插件用于管理位于 git 仓库中的 Helm 启动器,位于 https://github.com/salesforce/helm-starter。此插件的版本控制 URL 为 https://github.com/salesforce/helm-starter.git。
要安装此插件,请运行 helm plugin install,将版本控制 URL 作为第一个参数传递:
$ helm plugin install https://github.com/salesforce/helm-starter.git
Installed plugin: starter
如果安装成功,您可以继续使用该插件:
$ helm starter --help
Fetch, list, and delete helm starters from github.
Available Commands:
helm starter fetch GITURL Install a bare Helm starter from Github
(e.g., git clone)
helm starter list List installed Helm starters
helm starter delete NAME Delete an installed Helm starter
--help Display this text
要列出所有安装的插件,请使用helm plugin list命令:
$ helm plugin list
NAME VERSION DESCRIPTION
starter 1.0.0 This plugin fetches, lists, and deletes helm starters from github.
要尝试更新插件,请使用helm plugin update命令:
$ helm plugin update starter
Updated plugin: starter
如果您希望卸载系统中的插件,请使用helm plugin remove命令:
$ helm plugin remove starter
Uninstalled plugin: starter
除非另有规定,Helm 将在安装插件时使用位于 Git 存储库默认分支上的plugin.yaml和源代码。如果您希望指定要使用的 Git 标签,请在安装时使用--version标志:
$ helm plugin install https://github.com/databus23/helm-diff.git --version v3.1.0
也可以直接从 tarball URL 安装插件。Helm 将下载 tarball 并解压到插件目录:
$ helm plugin install https://example.com/archives/myplugin-0.6.0.tar.gz
此外,您还可以从本地目录安装插件:
$ helm plugin install /path/to/myplugin
Helm 不会复制文件,而是会创建到原始文件的符号链接:
$ ls -la "$(helm env HELM_PLUGINS)"
total 8
drwxrwxr-x 2 myuser myuser 4096 Jul 3 21:49 .
drwxrwxr-x 4 myuser myuser 4096 Jul 1 21:38 ..
lrwxrwxrwx 1 myuser myuser 21 Jul 3 21:49 myplugin -> /path/to/myplugin
例如,如果您正在积极开发插件,则可能会很有用。在调用符号链接插件时,对plugin.yaml和其他源文件进行更改将立即被识别。
自定义子命令
插件具有许多有用的功能,可以与现有的 Helm 用户体验无缝集成。Helm 插件最显著的特点可能是每个插件为 Helm 提供一个自定义的顶级子命令。这些子命令甚至可以利用 Shell 完成(本章后面讨论)。
安装插件后,将根据插件名称为您提供一个新的命令可供使用。这个新命令直接集成到 Helm 中,甚至会显示在helm help中。
例如,假设我们安装了一个名为inspect-templates的插件,它可以为我们提供关于图表中找到的 YAML 模板的额外信息。此插件将为您提供一个额外的 Helm 命令:
$ helm inspect-templates [args]
这将执行inspect-templates插件,将任何参数或标志传递给插件调用时执行的基础工具。插件的作者指定了一些命令,Helm 应在每次调用插件时作为子进程运行(有关如何指定此内容的更多信息,请参阅“构建插件”)。
插件为增强 Helm 现有功能集提供了一个令人满意的替代方案,而无需对 Helm 本身进行任何修改。
构建插件
构建 Helm 插件是一个非常简单的过程。根据插件的要求和整体复杂性,可能需要一些编程知识;但是,许多插件仅运行基本的 Shell 命令。
底层实现
考虑以下 Bash 脚本,inspect-templates.sh,这是我们示例inspect-templates插件的底层实现:
#!/usr/bin/env bash
set -e
# First argument on the command line, a relative path to a chart directory
CHART_DIRECTORY="${1}"
# Fail if no chart directory provided or is invalid
if [[ "${CHART_DIRECTORY}" == "" ]]; then
echo "Usage: helm inspect-templates <chart_directory>"
exit 1
elif [[ ! -d "${CHART_DIRECTORY}" ]]; then
echo "Invalid chart directory provided: ${CHART_DIRECTORY}"
exit 1
fi
# Print a summary of the chart's templates
cd "${CHART_DIRECTORY}"
cd templates/
echo "----------------------"
echo "Chart template summary"
echo "----------------------"
echo ""
total="$(find . -type f -name '*.yaml' -maxdepth 1 | wc -l | tr -d '[:space:]')"
echo " Total number: ${total}"
echo ""
echo " List of templates:"
for filename in $(find . -type f -name '*.yaml' -maxdepth 1 | sed 's|^\./||'); do
kind=$(cat "${filename}" | grep kind: | head -1 | awk '{print $2}')
echo " - ${filename} (${kind})"
done
echo ""
当用户运行helm inspect-templates时,Helm 将在幕后执行此脚本。
注意
插件的底层实现并不需要使用 Bash、Go 或任何特定的编程语言编写。对于此插件的最终用户,它应该看起来只是 Helm CLI 的另一部分。
插件清单
每个插件由一个名为plugin.yaml的 YAML 文件定义。此文件包含插件元数据和有关在调用插件时运行的命令的信息。
下面是plugin.yaml的基本示例,适用于inspect-templates插件:
name: inspect-templates 
version: 0.1.0 
description: get a summary of a chart's templates 
command: "${HELM_PLUGIN_DIR}/inspect-templates.sh" 
插件的名称。
插件的版本。
插件的基本描述。
调用此插件时运行的命令。
手动安装
首先检查插件存储根目录的配置路径:
$ HELM_PLUGINS="$(helm env HELM_PLUGINS)"
$ echo "${HELM_PLUGINS}"
/home/myuser/.local/share/helm/plugins
使用自定义根目录进行插件操作
插件的根目录可以通过在当前环境中提供HELM_PLUGINS环境变量的自定义路径进行覆盖。
在插件存储根目录内创建与插件名称(inspect-templates)匹配的目录:
$ PLUGIN_ROOT="${HELM_PLUGINS}/inspect-templates"
$ mkdir -p "${PLUGIN_ROOT}"
接下来,将plugin.yaml和inspect-templates.sh复制到新目录,并确保脚本可执行:
$ cp plugin.yaml "${PLUGIN_ROOT}"
$ cp inspect-templates.sh "${PLUGIN_ROOT}"
$ chmod +x "${PLUGIN_ROOT}/inspect-templates.sh"
最终结果
下面展示了我们的inspect-templates插件的工作示例:
$ helm inspect-templates
Usage: helm inspect-templates <chart_directory>
Error: plugin "inspect-templates" exited with error
$ helm inspect-templates nonexistant/
Invalid chart directory provided: nonexistant/
Error: plugin "inspect-templates" exited with error
$ helm create mychart
Creating mychart
$ helm inspect-templates mychart/
----------------------
Chart template summary
----------------------
Total number: 5
List of templates:
- serviceaccount.yaml (ServiceAccount)
- deployment.yaml (Deployment)
- service.yaml (Service)
- hpa.yaml (HorizontalPodAutoscaler)
- ingress.yaml (Ingress)
注意提供的命令行参数(即mychart/)直接传递给脚本。这使得插件作者能够构建接受任意数量参数或自定义标志的独立工具变得容易。
plugin.yaml
plugin.yaml是描述插件及其调用命令等重要细节的插件清单文件的名称。
下面是一个包含所有可为插件指定的选项的plugin.yaml示例:
name: myplugin 
version: 0.3.0 
usage: "helm myplugin --help" 
description "a plugin that belongs to me" 
platformCommand: 
- os: windows
arch: amd64
command: "bin/myplugin.exe"
command: "bin/myplugin" 
ignoreFlags: false 
hooks: 
install: "scripts/install-hook.sh"
update: "scripts/update-hook.sh"
delete: "scripts/delete-hook.sh"
downloaders: 
- command: "bin/myplugin-myp-downloader"
protocols:
- "myp"
- "myps"
插件的名称。
插件的版本。
此插件的使用说明。
插件的描述。
特定平台的命令。如果客户端匹配特定的操作系统/架构组合,则运行该命令而不是默认命令。
调用此插件时运行的命令。
当作为插件的参数传递时,是否抑制传递给插件的 Helm 全局标志(例如--debug)。
插件钩子(参见“Hooks”)。
下载器及其相关协议(参见“Downloader Plugins”)。
插件的name将作为从 Helm CLI 调用此插件的子命令(例如helm myplugin)。因此,插件名称不应与任何现有的 Helm 子命令(如install、repo等)匹配。名称只能包含字符 a–z、A–Z、0–9、_ 和-。
插件的version应该是一个有效的 SemVer 2 版本。
插件的usage和description将在运行helm help和helm help myplugin时显示。但是,插件本身必须处理其自身的标志解析,例如helm myplugin --help。
当调用此插件时,command是 Helm 在子进程中执行的命令。如果定义了platformCommands部分,Helm 首先会检查系统是否与提供的os(操作系统)和arch(架构)匹配,如果匹配,则 Helm 将使用匹配条目中定义的command。arch字段是可选的,如果缺失,则仅检查os。
这是 Helm 确定在基于plugin.yaml内容和运行时环境时运行插件时要运行的命令的确切顺序:
-
如果存在
platformCommand,将首先搜索它。 -
如果
os和arch都与当前平台匹配,则搜索将停止并执行特定于平台的命令。 -
如果
os匹配并且没有更具体的匹配项,则将执行特定于平台的命令。 -
如果没有找到
os/arch匹配项,则将执行顶层默认的command。 -
如果顶层没有
command且platformCommand中没有匹配项,则 Helm 将以错误退出。
钩子
插件钩子允许在安装、更新或删除插件时采取额外的操作。
例如,您的插件的底层实现可能是一个平台特定的二进制文件,必须从互联网下载。该二进制文件的 URL 根据用户的操作系统而变化。
根据操作系统处理此逻辑的脚本可能如下所示:
#!/usr/bin/env bash
set -e
URL=""
EXTRACT_TO=""
if [[ "$(uname)" = "Darwin" ]]; then
URL="https://example.com/releases/myplugin-mac"
EXTRACT_TO="myplugin"
elif [[ "$(uname)" = "Linux" ]]; then
URL="https://example.com/releases/myplugin-linux"
EXTRACT_TO="myplugin"
else
URL="https://example.com/releases/myplugin-windows"
EXTRACT_TO="myplugin.exe"
fi
mkdir -p bin/
curl -sSL "${URL}" -o "bin/${EXTRACT_TO}"
通过为我们的插件定义安装钩子,我们可以使得此脚本在用户安装此插件时运行。
要定义一个钩子,请向您的plugin.yaml添加一个hooks部分,为您希望响应的每个事件定义命令:
...
hooks:
install: "scripts/install-hook.sh" 
update: "scripts/update-hook.sh" 
delete: "scripts/delete-hook.sh" 
在运行helm plugin install命令时执行。
在运行helm plugin update命令时执行。
在运行helm plugin remove命令时执行。
下载器插件
一些插件具有特殊功能,允许它们作为下载图表的替代方案使用。
如果您以与纯图表存储方式不同的方式存储图表,或者如果您的图表存储库实现具有额外的要求,则此方法很有用。
下载器插件定义了一个(或多个)协议,如果在命令行中检测到,则指示 Helm 使用插件而不是 Helm 的内部下载机制下载index.yaml或 chart .tgz包。
下面是一个名为“super-secure”的下载器插件的plugin.yaml示例,它注册了ss://协议:
name: super-secure
version: 0.1.0
description: a super secure chart downloader
command: "${HELM_PLUGIN_DIR}/super-secure.sh"
downloaders:
- command: "super-secure-downloader.sh" 
protocols: 
- "ss"
插件作为下载器时调用的命令
此插件声明的自定义协议
注意
请记住,包括下载器插件在内的所有插件都定义了一个自定义的顶级命令(即 helm super-secure)。插件下载器的命令可以与 command 字段相同;但请注意,如果希望将插件同时用作标准插件和下载器,确定其用途可能会有一些挑战。您可以通过检查命令是否以四个命令行参数被调用来确定插件是否被用作下载器的一种方法。
下载器命令总是使用以下参数调用:
<command> certFile keyFile caFile full-URL
certFile、keyFile 和 caFile 参数来自一个 YAML 配置文件中的条目,其路径由 $(helm env HELM_REPOSITORY_CONFIG) 返回,并且在使用 helm repo add 添加仓库时设置(详见 第七章 了解更多背景)。full-URL 参数是正在下载的资源的完整 URL,可以是一个 index.yaml 文件,或者是一个图表 .tgz/.prov 文件。
让我们来看看由 super-secure 插件定义的 ss:// 协议下载器的实现:
#!/usr/bin/env bash
set -e
# The fourth argument is the URL to the resource to download from the repo
URL="${4}"
# Replace "ss://" with "https://"
URL="$(echo ${URL} | sed 's/ss:/https:/')"
# Request the resource using the token, outputting contents to stdout
echo "Downloading $(basename ${URL}) using super-secure plugin..." 1>&2
curl -sL -H "Authorization: Bearer ${SUPER_SECURE_TOKEN}" "${URL}"
此下载器允许我们使用使用令牌/承载者认证保护的图表仓库。它期望环境变量 SUPER_SECURE_TOKEN 被设置,该令牌将用于构造请求图表仓库资源时使用的 Authorization: Bearer <token> 头部。
注意
虽然 super-secure 插件是一个简单的下载器插件的很好示例,但是 Helm 的未来版本可能会直接支持 bearer token auth。
下载器插件期望将资源的内容输出到 stdout,因此任何额外的日志等应该打印到 stderr。这就是为什么在以 echo 开头的行中,我们使用 1>&2 将此消息重定向到 stderr。
安装此插件后,这是我们如何添加一个使用令牌认证保护的图表仓库的方法:
$ export SUPER_SECURE_TOKEN="abc123"
$ helm repo add my-secure-repo ss://secure.example.com
Downloading index.yaml using super-secure plugin...
"my-secure-repo" has been added to your repositories
此仓库 URL 现在将显示在本地仓库列表中,包含 ss:// 协议:
$ helm repo list
NAME URL
my-secure-repo ss://secure.example.com
现在该仓库可以像任何其他仓库一样使用,用于下载远程图表包:
$ export SUPER_SECURE_TOKEN="abc123"
$ helm pull my-secure-repo/superapp
Downloading superapp-0.1.0.tgz using super-secure plugin...
$ ls
superapp-0.1.0.tgz
下载器插件为 Helm 用户提供了一种通过定义自定义协议来扩展与图表仓库工作的传输机制的方式。当 Helm 检测到使用自定义协议时,它将尝试定位一个安装的插件来处理它,然后将资源请求推迟给该插件。
执行环境
由于插件旨在扩展 Helm 的功能,它们可能需要访问一些 Helm 的内部配置文件,或者在命令行上提供的全局标志。
为了让插件在运行时能够访问到这类信息,一系列已知的环境变量会被提供给插件。
这是当前所有插件可用的环境变量列表,按字母顺序排列:
HELM_BIN
正在执行的 Helm 命令的路径
HELM_DEBUG
全局布尔 --debug 选项设置的值(“true” 或 “false”)
HELM_KUBECONTEXT
全局 --kube-context <context> 选项设置的值
HELM_NAMESPACE
全局 --namespace <namespace> 选项设置的值
HELM_PLUGIN_DIR
当前插件的根目录
HELM_PLUGIN_NAME
当前插件的名称
HELM_PLUGINS
包含所有插件的顶层目录
HELM_REGISTRY_CONFIG
仓库配置的根目录
HELM_REPOSITORY_CACHE
仓库缓存的根目录
HELM_REPOSITORY_CONFIG
仓库配置的根目录
Shell 自动完成
Helm 对于 Bash 和 Z shell(Zsh)都有内置的 shell 自动完成支持(参见 helm completion --help)。这在你无法记住正在尝试使用的子命令或标志名称时非常有帮助。
插件还可以通过两种方法(静态自动完成和动态完成)提供自定义的 shell 自动完成。
静态自动完成
通过在插件目录的根目录中包含名为 completion.yaml 的文件,Helm 插件可以静态指定插件可用的所有预期标志和命令。
这是一个虚构的 zoo 插件的 completion.yaml 示例:
name: zoo 
flags: 
- disable-smells
- disable-snacks
commands: 
- name: price 
flags:
- kids-discount
- name: animals
commands:
- name: list
validArgs: 
- birds
- reptiles
- cats
- name: describe
flags:
- format-json
validArgs:
- birds
- reptiles
- cats
此完成文件所属插件的名称
可用标志的列表(注意:这些标志不应包含 - 或 -- 前缀)
可用子命令的列表
单个子命令的名称
第一个子命令后面参数的有效选项列表
在顶层 commands 部分下面,可以为嵌套子命令指定另一个 commands 部分(以及递归指定多次)。每个 commands 部分中的命令都可以包含自己的 flags 和 validArgs 列表。
Helm 的全局标志,如 --debug 或 --namespace,已经由 Helm 内置的 shell 自动完成处理,因此不需要在 flags 下列出这些。
如果我们开始尝试运行示例 zoo 插件,然后按 Tab 键,它应该显示所有可用的子命令:
$ helm zoo # (click tab)
animals price
现在,如果我们做同样的操作,但在按下 Tab 键之前加上 --disable-s 后缀,我们应该能看到我们的标志:
$ helm zoo --disables-s # (click tab)
--disable-smells --disable-snacks
使用静态完成,我们能够与 Helm 现有的 shell 完成达到一致,使插件在 Helm 用户体验中感觉更加紧密集成。
提示
如果您正在开发一个插件,必须打开一个新的终端窗口以刷新静态 shell 自动完成。
或者,您可以运行以下命令之一,在当前终端获取最新的完成:
source <(helm completion bash) # for Bash
source <(helm completion zsh) # for Z shell
动态完成
在某些情况下,给定命令的有效参数可能事先不知道。例如,您可能希望为集群中的 Helm 发布名称提供作为插件有效参数的列表。这可以通过动态完成来实现。
要启用动态完成,请在插件目录的根目录中包含一个名为 plugin.complete 的可执行文件。此文件可以是任何类型的可执行文件,例如 Shell 脚本或二进制文件。
对于包含 plugin.complete 文件的插件,在请求完成时(例如按下 Tab 键),Helm 将运行此可执行文件,并将需要完成的文本作为第一个参数传递。该程序应返回一系列可能的结果,每个结果由新行分隔,并成功退出(即返回代码 0)。
您甚至可以决定将此完成功能作为主要插件程序的一部分提供,使用简单的包装脚本通过诸如 --complete 标志触发它。以下是执行此操作的基本 plugin.complete 可执行文件示例:
#!/usr/bin/env sh
$HELM_PLUGIN_DIR/my-plugin-program --complete "$@"
延续 zoo 插件示例,假设可用动物类别的列表不断变化,并存储在用户主目录中的名为 animals.txt 的文件中。以下是 animals.txt 可能的样子:
birds
reptiles
cats
我们希望能够根据此文件的内容动态提供完成功能。以下是一个示例,展示了一个可用于提供动态完成的 plugin.complete 可执行文件(Bash 脚本):
#!/usr/bin/env bash
set -e
INPUT="${@}"
if [[ "${INPUT}" == "animals list"* ]]; then
INPUT="$(echo "${INPUT}" | sed -e 's/^animals list //')"
for flag in $(cat "${HOME}/animals.txt"); do
if [[ "${flag}" == "${INPUT}"* ]]; then
echo "${flag}"
fi
done
fi
现在,如果我们运行插件并输入 animals list,然后按 Tab 键,它应该显示所有可用的动物类别列表:
$ helm zoo animals list # (press Tab key)
birds cats reptiles
为了确保它是动态的,让我们在 animals.txt 中添加一个额外的类别“monkeys”,然后再试一次:
$ echo "monkeys" >> "${HOME}/animals.txt"
$ helm zoo animals list # (press Tab key)
birds cats monkeys reptiles
它有效!
这只是使用动态完成的简单示例,但请记住,您还可以查询一些远程资源,例如 Kubernetes 集群中的资源,使其成为插件的强大功能。
注意
如果已经使用 completion.yaml 文件进行静态完成,则不使用动态完成,即使插件的根目录中存在 plugin.complete 可执行文件。
起始程序
起始程序或起始包类似于 Helm 图表,不同之处在于它们被设计为新图表的模板。
当您使用 helm create 命令创建新图表时,它会生成一个使用 Helm 内置起始程序的新图表,这是一个使用最佳实践的通用图表。
要指定自定义起始程序,可以在创建新图表时使用 --starter 选项:
$ helm create --starter basic-webapp superapp
使用起始程序可以利用先前为类似目的的应用构建的图表。这对于即时准备部署到您的 Kubernetes 环境中的新项目非常有用。
将图表转换为起始程序
任何 Helm 图表都可以转换为启动器。唯一区别标准图表和启动器之间的唯一事物是启动器模板中对图表名称的动态引用的存在。
要将标准图表转换为启动器,请用字符串<CHARTNAME>替换对图表名称的任何硬编码引用。
为了演示,让我们从一个名为mychart的图表中获取这个简单的 ConfigMap 模板:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
data:
hello: {{ .Values.hello | quote }}
这是模板在启动器中的样子:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "<CHARTNAME>.fullname" . }}
labels:
{{- include "<CHARTNAME>.labels" . | nindent 4 }}
data:
hello: {{ .Values.hello | quote }}
注意
此图表必须仍包含一个Chart.yaml文件才能工作;但是,它将被生成器覆盖。
使启动器可用于 Helm
在使用启动器之前,你必须首先为其决定一个唯一的名称,例如一个包含基本 Web 应用程序的启动器的“basic-webapp”。
要使此启动器在命令行上指定--starter标志时成为有效选项,它必须作为目录存在于文件路径$(helm env HELM_DATA_HOME)/starters中。
如果这是您添加的第一个启动器,请确保顶级starters目录首先存在:
$ export HELM_STARTERS="$(helm env HELM_DATA_HOME)/starters"
$ mkdir -p "${HELM_STARTERS}"
然后只需将整个basic-webapp目录复制到该顶级目录中:
cp -r basic-webapp "${HELM_STARTERS}"
使用启动器
一旦有了一个启动器,你可以通过在命令行上引用其名称来基于它生成新的图表:
$ helm create --starter basic-webapp superapp
Creating superapp
新生成的图表的结构将与启动器完全相同。启动器模板中所有对<CHARTNAME>的引用都将替换为新图表的名称(即superapp)。
这是基于一个启动器生成的图表的示例目录结构:
$ tree superapp/
superapp/
├── Chart.yaml
├── templates
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ └── service.yaml
└── values.yaml
从这里,你可以将这个新图表检入版本控制,并开始进行自定义以适应特定的应用程序。
进一步扩展 Helm
在本章中,我们讨论了如何使用插件和启动器扩展 Helm。然而,还有另一种方式可以扩展 Helm:通过开源贡献。
本书中的所有内容都是对 Helm 项目数千次开源贡献的反映。虽然大部分工作是由维护者(过去和现在)执行的,但大多数贡献来自全球各地的个人。这不仅包括对 Go 源代码的更改,还包括测试和文档更新。
您有想为 Helm 项目做出贡献的吗?请转到Helm 社区登陆页面了解更多信息!
附录 A. 图表 API 版本
本附录涵盖了图表 API 版本 2 和 1(传统)之间的区别。
每个图表的Chart.yaml文件中指定了图表 API 版本,并且被 Helm 用来确定如何解析图表以及提供哪些功能集。
对于新图表,通常应使用 API 版本 2。然而,许多公开可用的图表是在 API 版本 2 生成之前创建的,并且使用 1,即传统的 API 版本。在这里,我们将详细介绍这两个 API 版本及其不同之处。
API 版本 2
Chart API 版本 2 是在 Helm 3 中引入的当前 API 版本。这是使用helm create创建新图表时使用的默认 API 版本。
使用 API 版本 2 的图表保证受 Helm 3 支持,但不一定受 Helm 2 支持。如果您只计划支持 Helm 3 及以上版本,则建议仅使用此 API 版本。
Chart.yaml文件
以下是使用 API 版本 2 的图表的Chart.yaml文件示例:
apiVersion: v2 
name: lemon
version: 1.2.3
type: application
description: When life gives you lemons, do the DevOps
appVersion: 2.0.0
home: https://example.com
icon: https://example.com/img/lemon.png
sources:
- https://github.com/myorg/mychart
keywords:
- fruit
- citrus
maintainers:
- name: Carly Jenkins
email: carly@mail.cj.example.com
url: https://cj.example.com
- name: William James Spode
email: william.j@mail.wjs.example.com
url: https://wjs.example.com
deprecated: false
annotations:
sour: 1
kubeVersion: ">=1.14.0"
dependencies:
- name: redis
version: ~10.5.7
repository: https://kubernetes-charts.storage.example.com/
condition: useCache,redis.enabled
- name: postgresql
version: 8.6.4
repository: @myrepo
tags:
- database
- backend
表示图表 API 版本 2 的字段
此文件中的顶级字段将在以下子节中详细描述。
字段:apiVersion
必需
此图表的 API 版本。
此字段应始终设置为v2。
字段:名称
必需
图表的名称。
在大多数情况下,这应与您的应用程序名称相对应(即lemon)。如果您的应用程序分为多个可安装组件,则将此名称后缀为组件的描述是常见的做法;例如,lemon-frontend、lemon-backend等。
图表名称必须由小写字母、数字和破折号(-)组成。
字段:版本
必需
图表的当前版本,严格使用语义化版本 2 格式化。
字段:类型
必需
指定图表类型,可以是以下两种类型之一:
应用程序
典型的可安装图表
库
包含常见定义的不可安装图表,意味着作为依赖图表包含。
此字段是 API 版本 2 独有的。在 API 版本 1 中,所有图表都被视为应用程序图表。有关库图表的更多信息,请参阅第六章。
字段:描述
图表的简单、一句话描述。
字段:appVersion
表示图表代表的应用程序的版本。
此字段应与您正在部署的软件版本匹配,而不是图表本身。例如,如果您正在创建一个新的内部图表来部署自定义配置的 Nginx 1.18.0,则appVersion字段将是1.18.0,而version字段则可能更像是0.1.0(初始版本)。
字段:主页
图表和/或应用程序主页的绝对 URL。
字段:图标
图表的图标可以使用作为此图表图标的图像的绝对 URL。
这个字段通常被像 Artifact Hub 这样的服务用来显示可供下载的图表的适当图像。
字段:sources
一个或多个图表源代码的绝对 URL(如果可用)。
字段:keywords
一个图表所代表的关键字或主题列表。
这些字段被像 Artifact Hub 这样的服务用来按类别分组图表或进一步增强搜索功能。
字段:maintainers
一组用于维护图表的人员的姓名/电子邮件/URL 组合列表。
字段:deprecated
图表是否已被弃用。
这个字段被像 Artifact Hub 这样的服务用来决定何时移除图表列表。
字段:annotations
额外映射的图表信息,由 Helm 未解释,并提供给其他应用程序检查。
注意:这个字段与 Kubernetes 注解没有任何有意义的联系;但是,根据您如何决定使用这个字段,您可以选择将其定义为 Kubernetes 特定的注解。
字段:kubeVersion
一个 SemVer 约束,指定图表正确安装所需的最低 Kubernetes 版本。
一些图表可能使用的 Kubernetes 资源类型和 API 组仅在特定版本的 Kubernetes 上可用。如果操作员尝试在与目标集群的 kubeVersion 不兼容的情况下安装图表,则在任何 Kubernetes 资源被配置之前将发生错误。
字段:dependencies
一个图表的依赖列表。
当您运行 helm dependency update 时,这里列出的图表依赖关系将被协商并适当地放置到 charts/ 子目录中。
至少,dependencies 块下的每个条目应包含一个 name 子字段,以及一个 repository 或一个 alias 子字段。repository 应是指向有效图表仓库(服务 /index.yaml)的绝对 URL。alias 应该是字符 “@” 后跟以前添加的图表仓库的名称(例如,@myrepo)。
关于如何使用图表依赖关系的更多信息,请参阅 “图表依赖关系”。
Chart.lock 文件
当一个图表在 Chart.yaml 的 dependencies 字段下列出依赖关系时,会生成一个名为 Chart.lock 的特殊文件,并在每次运行 helm dependency update 命令时更新。当一个图表包含 Chart.lock 文件时,操作员可以运行 helm dependency build 来生成 charts/ 目录,而无需重新协商依赖关系。
这里是一个基于 Chart.yaml 示例中指定的依赖关系生成的 Chart.lock 文件的示例:
dependencies:
- name: redis
repository: https://kubernetes-charts.storage.example.com/
version: 10.5.7
- name: postgresql
repository: https://charts.example.com/
version: 8.6.4
digest: sha256:529608876e9f959460d0521eee3f3d7be67a298a4c9385049914f44bd75ac9a9
generated: "2020-07-17T11:10:34.023896-05:00"
类似 conditions 和 tags 的动态字段被剥离,这个文件简单地包含了在更新过程中为每个依赖项解析的 repository、name 和 version,以及一个 digest(SHA-256)和一个 generated 时间戳。
注意,PostgreSQL 依赖项的alias: "@myrepo"设置已转换为repository: https://charts.example.com/。这意味着在更新依赖项之前,通过以下命令添加了一个图表仓库:
$ helm repo add myrepo https://charts.example.com/
API 版本 1(遗留)
图表 API 版本 1 是最初的 API 版本,也是 Helm 2 所认可的唯一版本。Chart.yaml 中的apiVersion字段首次在 Helm 3 中引入,Helm 2 不认可该字段。在 Helm 2 中,默认假定所有图表都遵循 API 版本 1。在 Helm 3 中,apiVersion 是严格必需的。
使用 API 版本 1 的图表确保同时受到 Helm 2 和 Helm 3 的支持,但可能无法支持 Helm 3 未来可能提供的某些功能。
Chart.yaml 文件
用于使用 API 版本 1 的图表的Chart.yaml文件格式几乎与使用 API 版本 2 的图表相同,有一些显著差异。
以下是使用 API 版本 1 的图表的Chart.yaml文件示例:
apiVersion: v1 
name: lemon
version: 1.2.3
description: When life gives you lemons, do the DevOps
appVersion: 2.0.0
home: https://example.com
icon: https://example.com/img/lemon.png
sources:
- https://github.com/myorg/mychart
keywords:
- fruit
- citrus
maintainers:
- name: Carly Jenkins
email: carly@mail.cj.example.com
url: https://cj.example.com
- name: William James Spode
email: william.j@mail.wjs.example.com
url: https://wjs.example.com
deprecated: false
annotations:
sour: 1
kubeVersion: ">=1.14.0"
tillerVersion: ">=2.12.0"
engine: gotpl
表示图表 API 版本 1 的字段
与 v2 的差异
与 API 版本 2 的Chart.yaml示例相比,有一些细微差异:
-
apiVersion字段设置为v1(注:在 Helm 2 中,此字段不是严格必需的)。 -
type字段缺失。在 API 版本 1 中不存在库图表的概念。 -
dependencies字段缺失。在 API 版本 1 中,图表依赖项在名为requirements.yaml的专用文件中指定(稍后在本节中描述)。 -
存在两个额外字段:
tillerVersion和engine。
注意
在许多方面,这两个图表 API 版本实质上可以被视为 Helm 2 图表(v1)与 Helm 3 图表(v2)的对比。特别是自从 Helm 3 发布时引入了图表 API 版本 2 以来。
这些版本没有被命名为v2和v3(标示 Helm 版本)是因为图表的 API 版本独立于 Helm CLI 的 API 版本。
例如,如果 Helm 4 发布,可能仍然使用图表 API 版本 2。同样地,如果出于某些原因确定图表 API 版本 2 不足,可能会在另一个重大 Helm 发布之前引入新的图表 API 版本 3。
字段:tillerVersion(遗留)
指定安装图表所需的 Tiller 版本的 SemVer 约束。
Tiller 是仅在 Helm 2 中使用的遗留 Helm 服务器端组件。在使用 Helm 3 时,此字段完全被忽略。
字段:engine(遗留)
要使用的模板引擎的名称。默认为gotpl。
requirements.yaml 文件(遗留)
在 API 版本 1 中,还有一个名为requirements.yaml的额外文件,指定图表的依赖关系。该文件的格式与 API 版本 2 中定义的dependencies字段完全相同。
这里是一个独立的requirements.yaml文件示例:
dependencies:
- name: redis
version: ~10.5.7
repository: https://kubernetes-charts.storage.example.com/
condition: redis.enabled
- name: postgresql
version: 8.6.4
repository: "@myrepo"
tags:
- database
- backend
关于每个子字段的详细描述,请参阅标题为“字段:dependencies”的子节,位于 API 版本v2下。在 API 版本v2中,此文件的内容直接在Chart.yaml中定义。
requirements.lock文件(遗留)
在 API 版本 1 中,图表依赖锁定文件的名称为requirements.lock。此文件在格式和用途上与 API 版本 2 中描述的Chart.lock文件完全相同,只是名称不同。有关更多信息,请参阅标题为“Chart.lock 文件”的子节,位于 API 版本 2 下。
附录 B. 图表仓库 API
在 第七章 中,我们涵盖了图表仓库。本附录简要介绍了图表仓库 API,这是使 Helm 能够与图表仓库配合工作的基础规范。
图表仓库 API 很轻量,因为只需要实现一个必需的 HTTP 端点:GET /index.yaml。
在 99% 的情况下,图表仓库还会提供图表包 tarballs (.tgz) 和任何相关的确证文件 (.prov)。但是,也可以将这些文件托管在不同的域上。
如 第七章 中详细描述的那样,index.yaml 表示仓库索引,包含仓库中所有可用图表版本的完整列表。此文件的格式特定于 Helm,并且目前仅支持 API 版本 1。
index.yaml
在实现图表仓库 API 时,您的服务必须提供相对于提供的仓库 URL 的 HTTP GET /index.yaml 路由。该请求的响应必须返回状态码 200 OK,响应正文必须是一个有效的 index.yaml,如下所述。
注意
GET /index.yaml 端点不需要位于 URL 路径的根目录下。例如,给定提供的仓库 URL,如 https://example.com/charts,GET /index.yaml 路由必须在 https://example.com/charts/index.yaml 可访问。
index.yaml 格式
以下是一个简单且有效的 index.yaml,仅包含一个图表版本(superapp-0.1.0):
apiVersion: v1 
entries: 
superapp:
- apiVersion: v2
appVersion: 1.16.0
created: "2020-04-28T10:12:22.507943-05:00" 
description: A Helm chart for Kubernetes
digest: 46f9ddeca12ec0bc257a702dac7d069af018aed2a87314d86b230454ac033672 
name: superapp
type: application
urls: 
- superapp-0.1.0.tgz
version: 0.1.0
generated: "2020-04-28T11:34:26.779758-05:00" 
仓库 API 版本(必须始终为 v1)。
一个映射,将仓库中唯一的图表名称映射到所有可用版本的列表。
使用 helm package 创建 tarball 的时间戳。
tarball 的 SHA-256 摘要。
可以下载图表的 URL 列表。这些 URL 可以是绝对的,甚至可以托管在不同的域上。如果提供相对路径,则视为相对于 index.yaml。通常每个图表版本仅提供一个 URL 条目,但可以提供多个,如果前一个不可访问,Helm 将尝试下载列表中的下一项。
生成此 index.yaml 文件的时间戳,以 RFC 3339 格式。
除了 created、digest 和 urls 字段外,每个单独的图表版本上的所有字段均由图表 API(name、version 等)定义。请参阅 附录 A 获取更多信息。
何时下载 index.yaml?
当 Helm 下载或重新下载仓库索引时,有五种值得注意的情况:
-
当最初添加图表仓库时:
$ helm repo add myrepo https://charts.example.com -
更新所有图表仓库时:
$ helm repo update -
更新依赖项时(使用
--skip-refresh标志禁用):$ helm dependency update -
从锁文件构建依赖项时(使用
--skip-refresh标志禁用):$ helm dependency build -
使用
--dependency-update标志安装具有依赖项的本地图表时:$ helm install myapp . --dependency-update
何时使用缓存版本的 index.yaml?
下载 index.yaml 后,它将存储在本地缓存中,并在引用与仓库关联的唯一名称时使用(例如,“myrepo”)。
当 Helm 利用本地缓存的仓库索引时,有五种值得注意的情况:
-
从仓库拉取图表时:
helm pull myrepo/mychart -
从仓库安装图表时:
helm install myapp myrepo/mychart -
根据来自仓库的图表升级发布时:
helm upgrade myapp myrepo/mychart -
当搜索要使用的图表时:
helm search repo myrepo/ -
使用
--skip-refresh标志更新依赖项(如果依赖项包含"@myrepo"等alias子字段时):helm dependency update --skip-refresh
.tgz 文件
仓库中的 .tgz 文件代表以压缩 tarball 形式打包的单个图表版本。
对于这些文件,没有 URL 路径的要求,因为它们是托管在仓库中的;但是,当 Helm 请求时,它们必须能够被下载。响应的状态码必须是 200 OK,响应体应为二进制形式的 .tgz 内容。
何时下载 .tgz 文件?
当 Helm 下载图表包 .tgz 文件时,有三种值得注意的情况:
-
从仓库拉取图表时:
helm pull myrepo/mychart -
从仓库安装图表时:
helm install myapp myrepo/mychart -
根据来自仓库的图表升级发布时:
helm upgrade myapp myrepo/mychart
.prov 文件
仓库中的 .prov 文件代表使用 GNU Privacy Guard 签名的图表版本签名文件,这些文件是 可选的,用于验证目的。
与 .tgz 文件不同,.prov 文件具有唯一的 URL 路径要求。它们必须在与关联的 .tgz 后缀 .prov 文件位于的路径可访问的位置。例如,如果一个 .tgz 文件位于 https://charts.example.com/superapp-0.1.0.tgz,则 .prov 文件必须位于 https://charts.example.com/superapp-0.1.0.tgz.prov。
响应的状态码必须是 200 OK,响应体应为二进制形式的 .prov 内容。
何时下载 .prov 文件?
当 Helm 下载图表签名 .prov 文件时,有三种值得注意的情况:
-
使用
--verify标志从仓库拉取图表时:helm pull myrepo/mychart --verify -
使用
--verify标志从仓库安装图表时:helm install myapp myrepo/mychart --verify -
根据来自带有
--verify标志的仓库图表升级发布时:helm upgrade myapp myrepo/mychart --verify


浙公网安备 33010602011771号