Kubernetes-认证应用开发者-CKAD-学习指南-全-
Kubernetes 认证应用开发者(CKAD)学习指南(全)
原文:
zh.annas-archive.org/md5/83ed194a434f40962097048c4b1eeb85译者:飞龙
序言
微服务架构是当今应用开发的热门领域之一,特别是用于基于云的企业规模应用程序。使用小型、单一用途的服务构建应用程序的好处已被充分记录。但是管理可能会有大量容器化服务的任务并不容易,需要添加一个“编排器”来保持所有服务的整体协调。Kubernetes 是最流行和广泛使用的工具之一,因此能够作为应用程序开发人员使用、调试和监控 Kubernetes 的能力需求很高。为了为求职者和雇主提供一种标准手段来展示和评估在 Kubernetes 环境中开发的能力,Cloud Native Computing Foundation(CNCF)制定了Certified Kubernetes Application Developer (CKAD) 计划。要获得这一认证,您需要通过一项考试。
CKAD 与Certified Kubernetes Administrator (CKA)不容混淆。虽然有一些主题重叠,但 CKA 主要专注于 Kubernetes 集群管理任务,而不是开发在集群中运行的应用程序。
在这份学习指南中,我将探讨 CKAD 考试涵盖的主题,以充分准备您通过认证考试。我们将探讨何时以及如何应用 Kubernetes 的核心概念来管理应用程序。我们还将深入研究kubectl命令行工具,这是 Kubernetes 工程师的主要工具。我还将提供一些技巧,帮助您更好地准备考试,并分享我个人为准备考试的全部方面的经验。
CKAD 与其他认证的典型多选题格式不同。它完全基于绩效,并要求您在巨大的时间压力下展示对手头任务的深入了解。您准备好在第一次尝试中通过考试了吗?
第一章:读者对象
本书适合希望准备 CKAD 考试的开发人员。内容涵盖考试大纲的所有方面,尽管需要具备 Kubernetes 架构及其概念的基本知识。
如果你对 Kubernetes 完全陌生,建议你首先阅读由 Brendan Burns、Joe Beda、Kelsey Hightower 和 Lachlan Evenson(O'Reilly 出版社)编写的《Kubernetes: Up and Running》,或者 Marko Lukša(Manning Publications)编写的《Kubernetes in Action》。
你将学到什么
这本书的内容总结了 CKAD 考试的最重要部分。考虑到 Kubernetes 中众多的配置选项,要在不重复官方文档的情况下覆盖所有用例和场景是不可能的。鼓励考生参考Kubernetes 文档,作为更广泛了解的主要手册。
本书的大纲遵循 CKAD 课程。尽管通常学习 Kubernetes 有更自然的、有教育性的结构,但课程大纲将帮助考生集中精力准备考试,专注于特定主题。因此,根据你的现有知识水平,你将会参考本书的其他章节。请注意,本书仅涵盖与 CKAD 考试相关的概念。如果你希望深入了解,请参考 Kubernetes 文档或其他书籍。
在通过考试中,对 Kubernetes 的实际经验至关重要。每一章都包含一个名为“样例练习”的部分,包含练习问题。这些问题的解决方案可以在附录 A 中找到。
第二版的新内容
CNCF 定期更新所有 Kubernetes 认证,以跟上该领域的最新发展。2021 年 9 月,CKAD 课程经历了重大改革。现有主题的组织方式已经改变,并添加了新主题,使认证更加与 Kubernetes 实践者在真实场景中的应用相关。
与书的第一版相比,大约 80%的内容没有改变。新课程包括以下主题:
部署策略
部署策略的涵盖范围超出了直接由 Deployment 原语支持的范围。你需要理解如何实现和管理蓝/绿部署和金丝雀部署。
Helm
Helm 是一个工具,通过将配置文件组合成一个可重复使用的单一包,自动化捆绑、配置和部署 Kubernetes 应用程序。你需要了解如何使用 Helm 来发现和安装现有的 Helm 图表。
API 废弃
你需要了解 Kubernetes 的发布流程及其对已弃用和移除 API 的使用意义。你将学习如何处理需要切换到更新或替换的 API 版本的情况。
自定义资源定义 (CRDs)
CRD 允许通过创建自定义资源类型来扩展 Kubernetes API。你需要了解如何创建 CRD,以及如何管理基于 CRD 类型的对象。
认证、授权和准入控制
对 Kubernetes API 的每个调用都需要进行身份验证。作为日常使用 kubectl 的应用程序开发者,你需要理解如何管理和使用你的凭据。一旦身份验证成功,请求还需要通过授权阶段。你需要大致了解基于角色的访问控制(RBAC)的概念,该概念保护对 Kubernetes 资源的访问。准入控制是 CKA 和 Certified Kubernetes 安全专家 (CKS) 考试涵盖的主题,因此我只会浅尝辄止。
入口
你需要将在 Kubernetes 中运行的面向客户的应用程序暴露给外部消费者。Ingress 原语将 HTTPS 流量路由到多个 Service 后端之一。现在 Ingress 已成为 CKAD 课程的一部分。
此外,我在 附录 B 中包含了一个“考试复习指南”,该指南将课程主题与书籍中对应章节以及 Kubernetes 文档中的覆盖内容进行了映射。
CKAD 课程的早期版本包括两个已移除的主题:“创建和配置基本 Pod”和“了解如何使用标签、选择器和注释”。本书涵盖了这两个主题,尽管它们没有明确提到。Pod 对于在 Kubernetes 中运行工作负载至关重要,因此你绝对需要了解它们。第五章 解释了其详细内容。考虑到标签和标签选择对于理解 Deployment、Service 和 Network Policies 等原语非常重要,我保留了一个专门章节名为 标签和注释。
本书使用的约定
本书使用以下排版约定:
Italic
指示新术语、URL、电子邮件地址、文件名和文件扩展名。
Constant width
用于程序清单,以及段落内引用程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
Constant width bold
显示用户需要按照文字输入的命令或其他文本。
Constant width italic
显示应被用户提供值或根据上下文确定值的文本。
提示
此元素表示提示或建议。
注意
此元素表示一般说明。
警告
This element indicates a warning or caution.
Using Code Examples
The source code for all examples and exercises in this book is available on GitHub. The repository is distributed under the Apache License 2.0. The code is free to use in commercial and open source projects. If you encounter an issue in the source code or if you have a question, open an issue in the GitHub issue tracker. I’m happy to have a conversation and fix any issues that might arise.
This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission.
We appreciate, but generally do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Certified Kubernetes Application Developer (CKAD) Study Guide, by Benjamin Muschko (O’Reilly). Copyright 2024 Automated Ascent, LLC, 978-1-098-16949-7.”
If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com.
O’Reilly Online Learning
Note
For more than 40 years, O’Reilly Media has provided technology and business training, knowledge, and insight to help companies succeed.
Our unique network of experts and innovators share their knowledge and expertise through books, articles, and our online learning platform. O’Reilly’s online learning platform gives you on-demand access to live training courses, in-depth learning paths, interactive coding environments, and a vast collection of text and video from O’Reilly and 200+ other publishers. For more information, visit http://oreilly.com.
How to Contact Us
Please address comments and questions concerning this book to the publisher:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938 (in the United States or Canada)
-
707-827-0515 (international or local)
-
707-829-0104 (fax)
We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at https://oreil.ly/ckad-2ed.
Email bookquestions@oreilly.com to comment or ask technical questions about this book.
欲了解我们的书籍和课程的最新消息,请访问http://oreilly.com。
在 YouTube 上观看我们:http://youtube.com/oreillymedia
在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media
在 Twitter 上关注作者:https://twitter.com/bmuschko
在 GitHub 上关注作者:https://github.com/bmuschko
关注作者的博客:https://bmuschko.com
致谢
每本书项目都是一段漫长的旅程,没有编辑部和技术审阅者的帮助是不可能完成的。特别感谢 Jonathan Johnson、Bilgin Ibryam、Vladislav Bilay、Andrew Martin 和 Michael Levan,感谢他们详细的技术指导和反馈。我还要感谢 O'Reilly Media 的编辑 John Devins 和 Virginia Wilson,他们的持续支持和鼓励。
第一章:考试详情和资源
本章回答了准备成功通过 Certified Kubernetes Application Developer (CKAD) 考试的候选人最常问的问题。后续章节将为您总结 Kubernetes 的优势和架构 以及如何使用 kubectl 与 Kubernetes 集群交互。
Kubernetes 认证学习路径
CNCF 提供四种不同的 Kubernetes 认证。图 1-1 按目标受众对其进行分类。

图 1-1。Kubernetes 认证学习路径
中级认证的目标受众是云和 Kubernetes 的初学者。中级认证考试采用多项选择题格式。您不需要在交互环境中与 Kubernetes 集群进行交互。
从业者级别认证适用于具有预先 Kubernetes 经验的开发人员和管理员。此类别中的考试要求您在多个 Kubernetes 环境中解决问题。您会发现 CKAD 面向应用程序开发人员,不需要任何其他认证作为先决条件。
让我们简要查看每个认证,看看 CKAD 是否适合您。
Kubernetes 与云原生联合开发者(KCNA)
KCNA 是一项面向任何对云原生应用开发、运行时环境和工具感兴趣的入门级认证项目。虽然考试涵盖 Kubernetes,但不要求您亲自操作集群。此考试包括多项选择题,适合对生态系统有广泛了解的候选人。
Kubernetes 与云原生安全联合开发者(KCSA)
该认证侧重于安全概念的基础知识及其在 Kubernetes 集群中的应用。该计划的广度、深度和格式与 KCNA 相当。
Certified Kubernetes Application Developer(CKAD)
CKAD 考试专注于验证您构建、配置和部署基于微服务的应用程序的能力。您不必实际实现应用程序;但是,此考试适合熟悉应用架构、运行时和编程语言等主题的开发人员。
Certified Kubernetes Administrator (CKA)
CKA 考试的目标受众是 DevOps 从业者、系统管理员和站点可靠性工程师。此考试测试您在 Kubernetes 管理员角色中执行任务的能力,包括集群、网络、存储和初级安全管理,重点放在故障排除场景上。
Certified Kubernetes Security Specialist(CKS)
CKS 考试扩展了 CKA 考试验证的主题。通过 CKA 考试是参加 CKS 考试的先决条件。对于这一认证,您需要对 Kubernetes 安全性有更深入的了解。课程涵盖了如何应用构建容器化应用程序的最佳实践以及确保安全的 Kubernetes 运行时环境等主题。
考试目标
本书侧重于为你准备 CKAD 考试。在剖析考试重要主题之前,我将简要介绍 Kubernetes 对应用开发人员的重要性背景。
越来越多的应用开发人员发现自己在从单块架构模型过渡到微服务的齐整、凝聚力和容器化项目中。这两种方法都有其优缺点,但 Kubernetes 已成为部署和操作应用程序的事实标准运行平台,无需担心底层物理基础设施。
不再是管理员或发布经理独有的责任,在目标运行环境中部署和监控其应用程序。应用开发人员需要从开发到运营全程参与其应用程序。像 Netflix 这样的一些组织完全奉行这种文化,因此作为应用开发人员,您需要全权负责做出设计决策并修复生产中的问题。现在更重要的是了解 Kubernetes 的能力,如何正确应用相关概念以及如何与该平台进行交互。
这个考试专为需要在 Kubernetes 上设计、构建、配置和管理云原生应用程序的应用开发人员设计。
考试期间使用的 Kubernetes 版本
在撰写本文时,考试基于 Kubernetes 1.28 版本。本书所有内容都将遵循该版本的特性、API 和命令行支持。未来版本可能会破坏向后兼容性。在准备认证时,请查阅 Kubernetes 发布说明 并使用考试所使用的 Kubernetes 版本进行实践,以避免不愉快的意外。考试环境将与 Kubernetes 发布日期后的四到八周内的最新 Kubernetes 次要版本对齐。
课程
以下概述列出了考试的高级部分或领域及其评分权重:
-
20%: 应用设计和构建
-
20%: 应用部署
-
15%: 应用可观察性和维护
-
25%: 应用环境、配置和安全
-
20%: 服务和网络
下面的章节详细介绍了每个领域。
应用设计和构建
课程的第一个领域涵盖了设计和构建一个容器化应用程序,并在 Kubernetes 中操作它。您需要熟悉基本的容器概念以及如何在 Pod 内定义一个容器。此外,该领域涵盖了更高级的用例和 Kubernetes 概念:在 Pod 中使用存储、定义多个容器以及定义和执行批处理和周期性工作负载的需求。
应用程序部署
该领域主要侧重于 Kubernetes 原语“Deployment”。部署(Deployment)有助于使用相同定义的 Pod 进行扩展,即所谓的副本,并管理其控制的所有副本的配置。您需要理解管理部署(Deployment)的方法,包括有助于控制向副本中滚动新版本应用程序的策略。最后,您需要熟悉 Helm,这是一个用于管理部署和配置应用程序堆栈所需清单集合的开源工具。
应用程序可观察性和维护
将应用程序部署到 Kubernetes 只是第一步。您需要能够监视、检查并可能调试 Kubernetes 对象。探针(Probes)是该领域涵盖的重要概念:它们为运行在 Pod 中的应用程序定义健康检查。此外,您需要能够确定工作负载的运行时问题并解决它们。
应用程序环境、配置和安全性
Kubernetes 提供了可配置用于 Pod 的安全性和资源管理功能。这包括本领域涵盖的安全上下文和资源需求/约束。此外,您需要能够演示使用 ConfigMaps 和 Secrets 将配置数据注入到 Pod 中,以控制其运行时行为。该领域还涉及基于角色的访问控制(RBAC)和自定义资源定义(CRDs)的基本概念和功能。
服务和网络
课程的最后一个领域涉及为应用程序提供来自集群内部和外部的网络访问。为此,您需要展示对服务(Services)和入口(Ingress)的了解。最后,您需要对网络策略有一个大致的了解,这些策略本质上是允许或拒绝 Pod 之间通信的规则。
涉及的 Kubernetes 原语
一些考试目标可以通过理解相关的核心 Kubernetes 原语来实现。请注意,考试将多个概念结合到一个问题中。请参考图 1-2,作为适用的 Kubernetes 资源及其关系的指南。

图 1-2. 考试相关的 Kubernetes 原语
文档
在考试期间,您被允许打开一系列明确定义的网页作为参考。您可以自由浏览这些页面,并将代码复制粘贴到考试终端。
官方 Kubernetes 文档包括参考手册、GitHub 网站和博客。此外,你也可以查阅 Helm 文档。
随身携带 Kubernetes 文档页面非常有价值,但务必知道在这些页面中如何找到相关信息。在考试准备期间,至少要从头到尾阅读一次所有文档页面。不要忘记官方文档页面的搜索功能。作为参考,附录 B 将考试目标与覆盖主题的书籍章节和相关 Kubernetes 文档页面进行了映射。
高效使用文档
使用搜索词可能比浏览菜单项更快地找到正确的文档页面。从文档中复制粘贴代码片段到考试环境的控制台中通常效果不错。在此过程中,可能需要手动调整 YAML 的缩进格式,以确保格式正确。
考试环境和建议
要参加考试,你必须购买一张注册凭证,可在CNCF 培训和认证网页上获取。偶尔,CNCF 会为凭证提供折扣(例如,在美国感恩节假期前后)。这些折扣信息通常在Linux 基金会 LinkedIn 页面和 Twitter 账号@LF_Training上公布。
购买凭证后,你可以在PSI(负责线上测试的公司)安排考试时间。目前没有在测试设施提供面对面的考试服务。在预定的考试日期,你将会收到一封包含测试平台登录链接的电子邮件。在你的计算机上启用音频和视频功能以防作弊。监考人员将通过音视频监控你的行动,如果发现你违反规则,将终止考试。
考试尝试次数
购买的凭证允许你有两次通过考试的机会。我建议在第一次尝试考试之前做好充分准备。这将为你通过考试提供公平的机会,并让你了解考试环境和问题的复杂性。如果第一次未能通过考试,不要担心,你还有一次免费的机会。
CKAD 考试时间限制为两小时。在这段时间内,你需要在一个真实预定义的 Kubernetes 集群上解决实际操作问题。每个问题都会指明你需要操作的集群。这种实际操作方式能更好地评估候选人的技能,优于多项选择题形式的测试,因为你可以直接在工作中执行任务时转化知识。
强烈建议阅读考试常见问题解答(FAQ)。你会在那里找到大多数紧迫问题的答案,包括机器的系统要求、评分、证书更新和重新考试的要求。
候选人技能
该认证假定你具有 Kubernetes 的基本理解。你应该熟悉 Kubernetes 的内部结构、核心概念以及命令行工具 kubectl。CNCF 提供免费的“Kubernetes 入门”课程,适合 Kubernetes 初学者。
你的背景很可能更多偏向应用开发者,尽管你最熟悉哪种编程语言并不重要。以下是你需要增加通过考试可能性的背景知识的简要概述:
Kubernetes 架构和概念
考试不会要求你从头安装 Kubernetes 集群。先了解 Kubernetes 的基础知识和其架构组件。参考第二章快速入门 Kubernetes 的架构和概念。
kubectl 命令行工具
在考试中,kubectl 命令行工具是你与 Kubernetes 集群交互的中心工具。即使你只有很少的准备时间,练习如何操作 kubectl 及其命令和相关选项也是至关重要的。考试期间,你将无法访问Web 仪表板 UI。第三章简要总结了与 Kubernetes 集群交互的最重要方式。
对容器运行时引擎的工作知识
Kubernetes 使用容器运行时引擎来管理镜像。广泛使用的容器运行时引擎是 Docker Engine。至少要了解容器文件、容器镜像、容器以及相关的命令行接口命令。第四章解释了你需要了解的有关考试的所有容器知识。
其他相关工具
Kubernetes 对象由 YAML 或 JSON 表示。本书的内容将使用 YAML 示例,因为在 Kubernetes 世界中,YAML 比 JSON 更常用。在考试中,你将需要编辑 YAML 以声明性地创建新对象或修改活动对象的配置。确保你对基本的 YAML 语法、数据类型和缩进符合规范有很好的掌握。你可能会问,如何编辑 YAML 定义?当然是从终端操作。考试终端环境预装有vi和vim工具。练习常见操作的键盘快捷键(尤其是如何退出编辑器)。我想要提到的最后一个工具是 GNU Bash。你必须理解这种脚本语言的基本语法和操作符。有可能你需要阅读、修改甚至扩展运行在容器中的多行 Bash 命令。
时间管理
考生有两个小时的时间完成考试,答对问题的比例需要达到 66%才能通过。许多问题由多个步骤组成。虽然 Linux Foundation 不提供详细的评分细则,但我认为部分正确的答案会得到相应的分数。
在考试时,你会注意到给定的时间限制会给你带来很大的压力。这是有意为之的。Linux Foundation 希望 Kubernetes 从业者能够在实际场景中应用他们的知识,及时找到解决问题的方法。
考试将提供一系列问题。有些问题简短且易解决;另一些需要更多的背景知识并且需要更多时间。个人建议首先解决易问题,以尽可能多地获得分数而不陷入难题。我会在考试环境集成的记事本功能中标记我无法立即解决的任何问题。在第二遍阅读时,重新审视你跳过的问题,并尝试解决它们。理想情况下,你将能够在规定的时间内解决所有问题。
命令行技巧和窍门
由于命令行是你与 Kubernetes 集群唯一的交互界面,所以你必须非常熟悉kubectl工具及其可用选项。本节提供了使其使用更高效和更有生产力的技巧和窍门。
设定上下文和命名空间
考试环境已为您设置了多个 Kubernetes 集群。请查看 说明 ,了解这些集群的高级技术概述。每个考试练习需要在指定的集群上解决问题,如其描述所述。此外,说明将要求您在除 default 之外的命名空间中工作。在开始问题之前,请确保首先设置上下文和命名空间,以下命令将上下文和命名空间设置为一次性操作:
$ kubectl config set-context *<context-of-question>* \
--namespace=*<namespace-of-question>*
$ kubectl config use-context *<context-of-question>*
您可以在 “使用 kubectl 进行身份验证” 中找到上下文概念及其相应 kubectl 命令的更详细讨论。
使用 kubectl 的别名
在考试过程中,您将不得不执行数十甚至数百次 kubectl 命令。您可能是一个非常快的打字员;然而,反复完整拼写可执行文件没有意义。考试环境已经为 kubectl 命令设置了别名 k。
在准备考试时,您可以在自己的机器上设置相同的行为。以下 alias 命令将字母 k 映射到完整的 kubectl 命令:
$ alias k=kubectl
$ k version
使用 kubectl 命令自动完成
熟记 kubectl 命令和命令行选项需要大量实践。考试环境默认启用了自动完成功能。您可以在 Kubernetes 文档 中找到设置命令行自动完成的说明。
内部化资源简称
许多 kubectl 命令可能非常冗长。例如,管理持久卷索赔的命令是 persistentvolumeclaims。逐字拼写完整命令可能会出错且耗时。幸运的是,一些较长的命令具有简短的用法。api-resources 命令列出所有可用命令及其简称:
$ kubectl api-resources
NAME SHORTNAMES APIGROUP NAMESPACED KIND
...
persistentvolumeclaims pvc true PersistentVolumeClaim
...
使用 pvc 替代 persistentvolumeclaims 可以使命令执行更加简洁和表达力强,如下所示:
$ kubectl describe pvc my-claim
练习和实践考试
在通过考试时进行实际操作练习非常重要。为此,您需要一个功能齐全的 Kubernetes 集群环境。以下选项显著:
-
我发现使用 Vagrant 和 VirtualBox 运行一个或多个虚拟机非常有用。这些工具有助于创建易于启动和根据需要处置的隔离 Kubernetes 环境。
-
在开发者机器上安装简单的 Kubernetes 集群相对容易。根据你的操作系统,Kubernetes 文档提供了各种安装选项。当涉及到像 Ingress 或存储类这样的更高级功能的实验时,Minikube 非常有用,因为它提供了作为单个命令可安装的附加功能。另外,你也可以尝试使用kind,这是另一个用于运行本地 Kubernetes 集群的工具。
-
如果你是O’Reilly 学习平台的订阅用户,你将无限制地访问到运行Kubernetes 沙盒环境的场景。此外,你可以通过CKAD 实践测试的交互式实验来测试你的知识。
你可能还想尝试以下一些商业学习和实践资源:
-
Killer Shell是一个为所有 Kubernetes 认证提供示例练习的模拟器。如果购买了考试凭证,你将获得两次免费的会话。
-
其他在线培训提供商为考试提供视频课程,其中一些包含集成的 Kubernetes 实践环境。我想提到KodeKloud和A Cloud Guru。你需要购买每门课程的订阅来访问内容。
概要
考试是完全的实际操作测试,要求你在多个 Kubernetes 集群中解决问题。你需要理解、使用和配置与应用开发相关的 Kubernetes 原语。考试大纲将这些重点区域细分,并对不同主题进行加权,以确定它们对总体得分的贡献。尽管重点区域被有意义地分组,但课程大纲不一定遵循自然的学习路径,因此在考试准备中跨章节参考书籍是很有帮助的。
在本章中,我们讨论了考试环境及其导航方式。要在考试中表现出色的关键是通过对kubectl的强化实践来解决真实场景。第一部分的接下来两章将为你提供 Kubernetes 的快速入门。
所有讨论领域细节的章节都为你提供了实践机会。每章结束时,你会找到示例练习。
第二章:Kubernetes 简介
如果你是新手,快速了解 Kubernetes 是什么以及它的工作原理会很有帮助。网上有许多教程和 101 课程,但我想在本章节总结最重要的背景信息和概念。在本书的学习过程中,我们将引用集群节点组件,随时可以回顾这些信息。
Kubernetes 是什么?
要理解 Kubernetes 是什么,首先让我们定义微服务和容器。
微服务架构要求将应用程序堆栈的各个部分作为单独的服务开发和执行,并且这些服务需要相互通信。如果决定在容器中操作这些服务,你需要管理大量的容器,同时考虑可扩展性、安全性、持久性和负载平衡等横切关注点。
像buildkit和Podman这样的工具将软件工件打包成容器镜像。像Docker Engine和containerd这样的容器运行时引擎使用这些镜像来运行容器。这在开发者机器上用于测试或作为持续集成管道的一部分的临时执行中非常有效。有关容器的更多信息,请参阅第四章。
Kubernetes 是一个容器编排工具,可以帮助在物理机器、虚拟机器或云中操作数百甚至数千个容器。Kubernetes 还可以实现之前提到的横切关注点。容器运行时引擎与 Kubernetes 集成。每当触发容器创建时,Kubernetes 将生命周期的方面委托给容器运行时引擎。
Kubernetes 中最基本的原语是 Pod。Pod 可以运行一个或多个容器,同时添加诸如安全需求和资源消耗预期等横切关注点。请查看第五章了解这些方面。
特性
上一节涉及了 Kubernetes 提供的一些功能。在这里,我们将通过更详细地解释这些功能来深入探讨:
声明式模型
你不需要使用编程语言编写命令式代码来告诉 Kubernetes 如何操作应用程序。作为最终用户,你只需要声明一个期望的状态。期望的状态可以使用符合 API 架构的 YAML 或 JSON 清单定义。Kubernetes 然后维护这个状态,并在故障发生时恢复它。
自动扩展
当应用程序负载增加时,您将希望扩展资源,并在流量减少时进行缩减。在 Kubernetes 中,可以通过手动或自动缩放来实现这一点。最实用、最优化的选项是让 Kubernetes 自动缩放容器化应用程序所需的资源。
应用程序管理
应用程序的更改,例如新功能和错误修复,通常会与新标签的容器映像一起打包。您可以使用 Kubernetes 的便捷复制功能轻松地在运行它们的所有容器上部署这些更改。如果需要,在出现阻塞性错误或检测到安全漏洞的情况下,Kubernetes 还允许回滚到先前的应用程序版本。
持久存储
容器仅提供临时文件系统。在容器重新启动时,写入文件系统的所有数据都会丢失。根据应用程序的性质,您可能需要长期保留数据,例如,如果您的应用程序与数据库交互。Kubernetes 提供了挂载应用程序工作负载所需的存储的能力。
网络
要支持微服务架构,容器编排器需要允许容器之间的通信,以及从集群外的最终用户到容器的通信。Kubernetes 使用内部和外部负载平衡来路由网络流量。
高级架构
在架构上,一个 Kubernetes 集群由控制平面节点和工作节点组成,如 图 2-1 所示。每个节点运行在物理机器、虚拟机器或云上提供的基础设施上。要添加到集群中的节点数及其拓扑结构取决于应用程序资源的需求。

图 2-1. Kubernetes 集群节点和组件
控制平面节点和工作节点具有特定的责任:
控制平面节点
此节点通过 API 服务器公开 Kubernetes API,并管理构成集群的节点。它还响应集群事件,例如,当最终用户请求扩展 Pod 数量以分配应用程序的负载时。生产集群采用 高可用 (HA) 架构,通常涉及三个或更多控制平面节点。
工作节点
工作节点执行由 Pod 管理的容器工作负载。每个工作节点需要在主机机器上安装容器运行时引擎,以便能够管理容器。
在接下来的两个部分中,我们将讨论嵌入在这些节点中以完成其任务的关键组件。像集群 DNS 这样的附加组件在此处没有明确讨论。请参阅 Kubernetes 文档 以获取更多详细信息。
控制平面节点组件
控制平面节点需要特定的组件来执行其工作。以下组件列表将为您提供一个概述:
API 服务器
API 服务器公开了 API 端点,客户端可以用来与 Kubernetes 集群通信。例如,如果您执行名为kubectl的命令行基础的 Kubernetes 客户端工具,您将向 API 服务器公开的端点发出 RESTful API 调用,这是其实现的一部分。 API 服务器内部的 API 处理过程将确保诸如身份验证、授权和准入控制等方面。有关更多信息,请参见第十七章。
调度器
调度器是一个后台进程,负责监视新的 Kubernetes Pod,如果没有分配节点,则将其分配给工作节点以执行。
控制器管理器
控制器管理器监视集群状态并在需要时实施更改。例如,如果您对现有对象进行配置更改,则控制器管理器将尝试将对象带入所需状态。
Etcd
集群状态数据需要随时间持久化,以便在节点或整个集群重新启动时重建。这是etcd的职责,它是 Kubernetes 集成的开源软件。在其核心,etcd 是一个键值存储,用于持久化与 Kubernetes 集群相关的所有数据。
常见节点组件
Kubernetes 使用所有节点都可以利用的组件,而不考虑它们的专门责任:
Kubelet
Kubelet 在集群中的每个节点上运行;然而,它最合理存在于工作节点上。原因在于控制平面节点通常不执行工作负载,而工作节点的主要责任是运行工作负载。 Kubelet 是一个代理程序,确保 Pod 中运行必要的容器。可以说,Kubelet 是 Kubernetes 与容器运行时引擎之间的粘合剂,并确保容器运行且健康。我们将在第十四章与 Kubelet 进行交互。
Kube 代理
Kube 代理是在集群中每个节点上运行的网络代理,用于维护网络规则并启用网络通信。部分责任是实现服务概念,这在第二十一章中有所涵盖。
容器运行时
如前所述,容器运行时是负责管理容器的软件。Kubernetes 可以配置以选择不同的容器运行时引擎。虽然可以在控制平面上安装容器运行时引擎,但通常并非必需,因为控制平面节点通常不处理工作负载。我们将在第四章使用容器运行时来创建容器镜像并运行使用该镜像创建的容器。
优势
本章指出了 Kubernetes 的几个优点,总结如下:
可移植性
容器运行时引擎可以独立于其运行时环境管理容器。容器镜像包含使其工作所需的一切,包括应用程序的二进制文件或代码、其依赖项和配置。Kubernetes 可以在本地和云环境中运行容器中的应用程序。作为管理员,您可以选择您认为最适合您需求的平台,而无需重写应用程序。许多云提供产品特定的、可选择的功能。虽然使用产品特定的功能有助于操作方面,但请注意,这将减弱您轻松切换平台的能力。
弹性
Kubernetes 设计为声明式状态机。控制器是协调循环,它们监视集群的状态,然后在需要时进行或请求更改,目标是将当前集群状态移向期望的状态。
可扩展性
企业以规模运行应用程序。想象一下像亚马逊、沃尔玛或 Target 这样的零售商需要运行业务所需的多少软件组件。Kubernetes 可以根据需求扩展 Pods 的数量,或根据资源消耗或历史趋势自动扩展。
基于 API
Kubernetes 通过 API 公开其功能。我们学到,每个客户端都需要与 API 服务器交互来管理对象。轻松实现一个新的客户端,可以对暴露的端点进行 RESTful API 调用。
可扩展性
API 方面的延伸更远。有时,Kubernetes 的核心功能不满足您的定制需求,但您可以实现自己的 Kubernetes 扩展。借助特定的扩展点,Kubernetes 社区可以根据其需求构建定制功能,例如监控或日志解决方案。
摘要
Kubernetes 是用于管理规模化容器化应用程序的软件。每个 Kubernetes 集群至少包含一个控制平面节点和一个工作节点。控制平面节点负责调度工作负载,并充当管理其功能的单一入口点。工作节点处理由控制平面节点分配给它们的工作负载。
Kubernetes 是一种成熟的运行时环境,适用于希望运行微服务架构并支持可扩展性、安全性、负载平衡和可扩展性等非功能性需求的公司。
下一章将解释如何使用命令行工具kubectl与 Kubernetes 集群进行交互。您将学习如何运行它来管理对象,这是通过考试的重要技能。
第三章:与 Kubernetes 交互
作为应用程序开发者,你会希望与 Kubernetes 集群交互,管理操作应用程序的对象。每次对集群的调用都由 API 服务器组件接受和处理。有多种方法可以调用 API 服务器。例如,你可以使用基于 Web 的仪表板(web-based dashboard)、像 kubectl 这样的命令行工具,或直接对 RESTful API 端点发出 HTTPS 请求。
考试不会测试使用可视用户界面与 Kubernetes 集群进行交互的能力。解答考试问题的唯一客户端是 kubectl。本章将涉及 Kubernetes API 原语和对象,以及使用 kubectl 管理对象的不同方法。
API 原语和对象
Kubernetes 原语是 Kubernetes 架构中创建和操作平台上应用程序的基本构建块。即使是 Kubernetes 的初学者,你可能也听说过 Pod、Deployment 和 Service 这些术语,它们都是 Kubernetes 的原语。在 Kubernetes 架构中,还有许多服务专用的原语。
拿面向对象编程的概念来类比,类定义了现实世界功能的蓝图:其属性和行为。Kubernetes 的原语相当于一个类。面向对象编程语言中,类的实例是一个对象,管理自己的状态并具有与系统其他部分通信的能力。每当你创建一个 Kubernetes 对象时,你就产生了这样一个实例。
例如,Kubernetes 中的 Pod 就是一个类,可以有许多具有自己身份的实例。每个 Kubernetes 对象都有一个系统生成的唯一标识符(也称为 UID),以清晰区分系统的实体。稍后,我们将查看 Kubernetes 对象的属性。图 3-1 展示了 Kubernetes 原语与对象之间的关系。

图 3-1. Kubernetes 对象身份
每个 Kubernetes 原语都遵循一个通用结构,如果你深入查看对象清单(如 图 3-2 所示),你可以观察到这一点。用于 Kubernetes 清单的主要标记语言是 YAML。

图 3-2. Kubernetes 对象结构
让我们看看 Kubernetes 系统中每个部分及其相关性:
API 版本
Kubernetes API 版本定义了原语的结构,并使用它来验证数据的正确性。API 版本的作用类似于 XML 文档的 XML 模式或 JSON 文档的 JSON 模式。该版本通常经历成熟过程,例如从 alpha 到 beta 到最终版本。有时,您会看到由斜杠分隔的不同前缀(apps)。您可以通过运行命令kubectl api-versions列出与您的集群版本兼容的 API 版本。
种类
种类定义了原语的类型,例如 Pod 或 Service。它最终回答了一个问题,“我们这里正在处理哪些资源类型?”
元数据
元数据描述了关于对象的更高级信息,例如其名称、所在的命名空间,或者是否定义了标签和注释。本节还定义了 UID。
规范
规范(简称“spec”)声明了期望的状态,例如,在创建后此对象应该如何?容器中应该运行哪个镜像,或者应该设置哪些环境变量?
状态
状态描述了对象的实际状态。Kubernetes 控制器及其协调循环不断尝试将一个 Kubernetes 对象从期望状态转换为实际状态。如果 YAML 状态显示值为{},则对象尚未实现。
有了这个基本结构的理解,让我们看看如何利用kubectl创建一个 Kubernetes 对象。
使用 kubectl
kubectl是从命令行与 Kubernetes 集群交互的主要工具。考试专门侧重于使用kubectl。因此,深入了解其内部工作原理并大量实践使用它非常重要。
这一部分为您提供了其典型使用模式的简要概述。让我们从查看运行命令的语法开始。一个kubectl执行包括一个命令、一个资源类型、一个资源名称和可选的命令行标志:
$ kubectl *[command] [TYPE] [NAME] [flags]*
命令指定您计划运行的操作。典型的命令是像create、get、describe或delete这样的动词。接下来,您需要提供您正在处理的资源类型,可以是完整的资源类型或其简写形式。例如,您可以在此处操作一个service或使用简写形式svc。
资源的名称标识用户可见的对象标识符,实际上是 YAML 表示中metadata.name的值。请注意,对象名称与 UID 不同。UID 是自动生成的,Kubernetes 内部对象引用,通常您无需与之交互。对象的名称必须在命名空间内同一资源类型的所有对象中是唯一的。
最后,您可以提供零到多个命令行标志来描述额外的配置行为。一个典型的命令行标志示例是--port标志,它公开一个 Pod 的容器端口。
图 3-3 展示了完整的kubectl命令的操作过程。

图 3-3. kubectl使用模式
在本书的过程中,我们将探讨能够在考试中提高效率的kubectl命令。还有许多其他命令,它们通常超出了应用程序开发人员日常使用的命令范围。接下来,我们将深入研究create命令,这是创建 Kubernetes 对象的命令式方式。我们还将比较命令式对象创建方法与声明式方法的区别。
管理对象
您可以通过两种方式在 Kubernetes 集群中创建对象:命令式或声明式。以下各节将描述每种方法,包括它们的优点、缺点和用例。
命令式对象管理
无需清单定义即可进行命令式对象管理。您可以使用kubectl通过单个命令和一个或多个命令行选项驱动对象的创建、修改和删除。有关命令式对象管理的更详细描述,请参阅Kubernetes 文档。
创建对象
使用run或create命令即可实时创建对象。运行时所需的任何配置都通过命令行选项提供。这种方法的好处是快速反馈时间,无需处理 YAML 结构。以下run命令创建了一个名为frontend的 Pod,该 Pod 在容器中执行nginx:1.24.0镜像,并暴露端口 80:
$ kubectl run frontend --image=nginx:1.24.0 --port=80
pod/frontend created
更新对象
仍然可以修改活动对象的配置。kubectl通过提供edit或patch命令支持此用例。
edit命令会打开一个编辑器,显示活动对象的原始配置。退出编辑器后,配置更改将应用于活动对象。该命令将打开由KUBE_EDITOR或EDITOR环境变量定义的编辑器,在 Linux 上会回退到vi,在 Windows 上会回退到notepad。此命令演示了对名为frontend的 Pod 活动对象使用edit命令进行编辑:
$ kubectl edit pod frontend
patch命令允许在属性级别对活动对象进行精细化修改,使用 JSON 合并补丁。以下示例演示了如何使用patch命令更新先前创建的 Pod 的容器镜像标签。-p标志定义了用于修改活动对象的 JSON 结构:
$ kubectl patch pod frontend -p '{"spec":{"containers":[{"name":"frontend",\
"image":"nginx:1.25.1"}]}}'
pod/frontend patched
删除对象
您可以随时删除 Kubernetes 对象。在考试期间,如果解决问题时出错并希望从头开始确保清洁状态,可能需要这样做。在生产环境中,您会希望删除不再需要的对象。以下delete命令通过其名称frontend删除了 Pod 对象:
$ kubectl delete pod frontend
pod "frontend" deleted
在执行delete命令时,Kubernetes 会尝试优雅地删除目标对象,以便最小化对最终用户的影响。如果对象在默认的优雅期限(30 秒)内无法删除,则 kubelet 尝试强制杀死对象。
在考试期间,最终用户影响并不是一个问题。最重要的目标是在分配给候选人的时间内完成所有任务。因此,等待对象优雅地被删除是浪费时间的。您可以使用--now选项强制立即删除对象。以下命令使用SIGKILL信号杀死名为nginx的 Pod:
$ kubectl delete pod nginx --now
声明性对象管理
声明性对象管理需要一个或多个清单,格式为 YAML 或 JSON,描述了对象的期望状态。您可以使用这种方法创建、更新和删除对象。
使用声明性方法的好处在于可重复性和改进的维护性,因为在大多数情况下文件被检入版本控制。声明性方法是在生产环境中创建对象的推荐方式。
更多关于声明性对象管理的信息可以在Kubernetes 文档中找到。
创建对象
声明性方法通过使用apply命令从一个清单(大多数情况下是 YAML 文件)创建对象。该命令通过指向一个文件、一个文件目录或一个 HTTP(S) URL 引用的文件来工作,使用-f选项。如果一个或多个对象已经存在,则该命令将同步对配置所做的更改与实际对象。
为了演示功能,我们假设以下目录和配置文件。以下命令从单个文件创建对象,从目录中的所有文件创建对象,并递归地从目录中的所有文件创建对象。如果您想尝试,请参考书籍的 GitHub 仓库中的文件。后续章节将解释这里使用的基元的目的:
.
├── app-stack
│ ├── mysql-pod.yaml
│ ├── mysql-service.yaml
│ ├── web-app-pod.yaml
│ └── web-app-service.yaml
├── nginx-deployment.yaml
└── web-app
├── config
│ ├── db-configmap.yaml
│ └── db-secret.yaml
└── web-app-pod.yaml
从单个文件创建对象:
$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
从目录中的多个文件创建对象:
$ kubectl apply -f app-stack/
pod/mysql-db created
service/mysql-service created
pod/web-app created
service/web-app-service created
从包含文件的递归目录树创建对象:
$ kubectl apply -f web-app/ -R
configmap/db-config configured
secret/db-creds created
pod/web-app created
从 HTTP(S) URL 引用的文件创建对象:
$ kubectl apply -f https://raw.githubusercontent.com/bmuschko/\
ckad-study-guide/master/ch03/object-management/nginx-deployment.yaml
deployment.apps/nginx-deployment created
apply命令通过添加或修改带有键kubectl.kubernetes.io/last-applied-configuration的注释来跟踪更改。以下是在get pod命令的输出中注释的示例:
$ kubectl get pod web-app -o yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{}, \
"labels":{"app":"web-app"},"name":"web-app","namespace":"default"}, \
"spec":{"containers":[{"envFrom":[{"configMapRef":{"name":"db-config"}}, \
{"secretRef":{"name":"db-creds"}}],"image":"bmuschko/web-app:1.0.1", \
"name":"web-app","ports":[{"containerPort":3000,"protocol":"TCP"}]}], \
"restartPolicy":"Always"}}
...
更新对象
更新现有对象与相同的apply命令完成。您只需修改配置文件然后对其运行该命令即可。示例 3-1 修改了文件nginx-deployment.yaml中部署配置的现有配置。我们添加了一个新的标签,键为 team,并将副本数从 3 改为 5。
示例 3-1. 修改后的部署配置文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
team: red
spec:
replicas: 5
...
下面的命令应用了更改后的配置文件。结果是,由底层 ReplicaSet 控制的 Pod 数量为 5:
$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment configured
部署的 kubectl.kubernetes.io/last-applied-configuration 注解反映了配置的最新更改:
$ kubectl get deployment nginx-deployment -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{}, \
"labels":{"app":"nginx","team":"red"},"name":"nginx-deployment", \
"namespace":"default"},"spec":{"replicas":5,"selector":{"matchLabels": \
{"app":"nginx"}},"template":{"metadata":{"labels":{"app":"nginx"}}, \
"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx", \
"ports":[{"containerPort":80}]}]}}}}
...
删除对象
虽然您可以使用 apply 命令通过提供选项 --prune -l <labels> 删除对象,但建议使用 delete 命令并将其指向配置文件来删除对象。下面的命令删除了一个部署及其控制的对象(ReplicaSet 和 Pods):
$ kubectl delete -f nginx-deployment.yaml
deployment.apps "nginx-deployment" deleted
您可以使用 --now 选项强制删除 Pods,如 “删除对象” 中所述。
混合方法
有时您可能希望采用混合方法。您可以通过使用即时方法生成一个清单文件,而无需实际创建对象。方法是使用命令行选项 -o yaml 和 --dry-run=client 执行 run 或 create 命令:
$ kubectl run frontend --image=nginx:1.25.1 --port=80 \
-o yaml --dry-run=client > pod.yaml
您现在可以使用生成的 YAML 清单作为进一步修改对象的起点。只需用编辑器打开文件,更改内容,然后执行声明性 apply 命令:
$ vim pod.yaml
$ kubectl apply -f pod.yaml
pod/frontend created
选择使用哪种方法?
在考试期间,使用即时命令是管理对象的最高效且快速的方式。并非所有的配置选项都通过命令行标志公开,这可能迫使您使用声明性方法。混合方法在这里可以帮助。
GitOps 和 Kubernetes
GitOps 是一种利用存储在 Git 仓库中的源代码来自动化基于 Kubernetes 的云原生环境中的基础设施管理的实践。诸如 Argo CD 和 Flux 的工具实施了 GitOps 原则,通过声明性方法将应用部署到 Kubernetes。负责监督真实 Kubernetes 集群及其内部应用的团队很可能会采用声明性方法。
在即时创建对象能够优化周转时间的同时,在真实的 Kubernetes 环境中,您几乎肯定会希望使用声明性方法。YAML 清单文件代表了 Kubernetes 对象的最终真实状态。版本控制的文件可以进行审计和共享,并且它们存储了变更的历史记录,以便在需要时回滚到先前的版本。
总结
Kubernetes 通过基元的帮助来表示部署和操作云原生应用程序的功能。每个基元都遵循一个通用的结构:API 版本、种类、元数据和资源的期望状态,也称为规范。在对象创建或修改时,Kubernetes 调度程序会自动尝试确保对象的实际状态遵循定义的规范。每个活动对象都可以被检查、编辑和删除。
Kubectl 作为 CLI 基础的客户端用于与 Kubernetes 集群进行交互。您可以使用它的命令和标志来管理 Kubernetes 对象。命令式方法通过单个命令提供快速的管理对象的回转时间,只要您记住可用的标志。更复杂的配置需要使用 YAML 清单来定义基元。使用声明式命令从该定义实例化对象。YAML 清单通常会被检入版本控制,并提供一种跟踪配置更改的方式。
第四章:容器
Kubernetes 是一个容器编排器,使用容器运行时在 Pod 内实例化容器。许多使用容器运行时 containerd 的 Kubernetes 集群版本为 1.24 或更高。
在 Kubernetes 节点上使用的容器运行时
您可以获取 Kubernetes 集群中任何节点上使用的容器运行时的信息。只需查看运行命令kubectl get nodes -o wide生成的CONTAINER-RUNTIME列的输出即可。查阅 Kubernetes 文档 了解如何为集群配置容器运行时更多信息。
在考试中,您需要理解定义、构建和发布容器镜像的实际操作,本章涵盖了这些内容。我们还将介绍在容器内运行容器镜像的操作。对于所有这些操作,我们将使用 Docker Engine 作为示例容器运行时,尽管其他实现提供了类似的功能。
本书关于容器的讨论只是皮毛。如果您想全面投入,这个主题还有很多信息。我可以推荐 Sean P. Kane 和 Karl Matthias 合著的书籍 Docker: Up & Running(O’Reilly)详细解释 Docker。
容器术语
容器将应用程序及其运行环境和配置打包成单个软件单元。此软件单元通常包括操作系统、应用程序的源代码或二进制文件、其依赖项以及其他所需的系统工具。容器声明的目标是将运行环境与应用程序分离,避免“但在我的机器上可以运行”的问题。
容器运行时引擎是能够在主机操作系统上运行容器的软件组件。例如 Docker Engine 或 containerd。容器编排器使用容器运行时引擎实例化容器,同时添加诸如扩展性和跨工作负载的网络功能。Kubernetes 是容器编排器的一个例子。其他工具如 Nomad 能够调度包括容器在内的各种工作负载。
将应用程序打包成容器的过程称为容器化。容器化基于容器文件中定义的指令工作。Docker 社区将其称为 Dockerfile。Dockerfile 明确指出了软件构建时需要发生的事情。操作的结果是容器镜像。
容器镜像通常发布到容器注册表以供其他利益相关者使用。Docker Hub是主要的公共容器镜像注册表。还有其他公共注册表,如 GCR 和 Quay。图 4-1 在容器化应用程序的背景下说明了这些概念。

图 4-1. 容器化过程
总结一下,Dockerfile 是软件打包的蓝图,镜像是该过程产生的工件,容器是运行镜像的实例,提供应用程序服务。接下来我们将看一个更具体的例子。
将基于 Java 的应用程序容器化
假设我们要将用 Java 编写的 Web 应用程序容器化。该应用程序不是从头开始编写核心功能,而是使用Spring Boot 框架作为外部库。此外,我们希望通过环境变量来控制运行时行为。例如,您可能希望提供 URL 和凭据以连接到其他服务,如数据库。我们将逐步讨论这个过程,并从终端执行相关的 Docker 命令。如果您想跟着做,请从项目生成器Spring Initializr下载示例应用程序。
编写 Dockerfile
在我们创建镜像之前,我们必须编写一个 Dockerfile。Dockerfile 可以放置在任何目录中,它是一个纯文本文件。接下来的指令使用 Azul JRE 分发的 Java 21 作为基础镜像。基础镜像包含操作系统和必要的工具,本例中为 Java。
此外,我们将二进制文件(一个可执行的 Java 存档文件 JAR)包含到镜像的/app目录中。最后,我们定义 Java 命令来执行程序,并将端口 8080 暴露出来,在容器中运行时使应用程序可访问。示例 4-1 概述了一个样例 Dockerfile。
示例 4-1. 用于构建 Java 应用程序的 Dockerfile
FROM azul/zulu-openjdk:21-jre 
WORKDIR /app 
COPY target/java-hello-world-0.0.1.jar java-hello-world.jar 
ENTRYPOINT ["java", "-jar", "/app/java-hello-world.jar"] 
EXPOSE 8080 
定义基础镜像。
设置容器的工作目录。任何RUN、CMD、ADD、COPY或ENTRYPOINT指令将在指定的工作目录中执行。
将包含编译应用程序代码的 JAR 文件复制到工作目录中。
设置从镜像启动容器时执行的默认命令。
记录容器应监听的网络端口。
对于初学者来说,编写 Dockerfile 看起来很简单,但为了实现小占地面积和安全性方面的优化容器镜像却并非易事。您可以在Docker 文档中找到更详细的 Dockerfile 最佳实践列表。
构建容器镜像
有了 Dockerfile,我们可以创建镜像。以下命令提供了镜像的名称和标签。最后一个参数指向上下文目录。上下文目录包含 Dockerfile 以及要包含在镜像中的任何目录和文件。这里,上下文目录是我们所在的当前目录,由“ . ”引用:
$ docker build -t java-hello-world:1.1.0 .
[+] Building 2.0s (9/9) FINISHED
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 284B
=> [internal] load metadata for docker.io/azul/zulu-openjdk:21-jre
=> [auth] azul/zulu-openjdk:pull token for registry-1.docker.io
=> [1/3] FROM docker.io/azul/zulu-openjdk:21-jre@sha256:d1e675cac0e5...
=> => resolve docker.io/azul/zulu-openjdk:21-jre@sha256:d1e675cac0e5...
=> => sha256:d1e675cac0e5ce9604283df2a6600d3b46328d32d83927320757ca7...
=> => sha256:67aa3090031eac26c946908c33959721730e42f9195f4f70409e4ce...
=> => sha256:ba408da684370e4d8448bec68b36fadf15c3819b282729df3bc8494...
=> [internal] load build context
=> => transferring context: 19.71MB
=> [2/3] WORKDIR /app
=> [3/3] COPY target/java-hello-world-0.0.1.jar java-hello-world.jar
=> exporting to image
=> => exporting layers
=> => writing image sha256:4b676060678b63de137536da24a889fc9d2d5fe0c...
=> => naming to docker.io/library/java-hello-world:1.1.0
What's Next?
View a summary of image vulnerabilities and recommendations → ...
列出容器镜像
如终端输出所示,镜像已创建。您可能已经注意到基础镜像已作为过程的一部分下载。通过运行以下命令,生成的镜像可以在本地 Docker 引擎缓存中找到:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
java-hello-world 1.1.0 4b676060678b 49 seconds ago 342MB
运行容器
现在是在容器中运行应用程序的时候了。run命令指向一个镜像并在容器中执行其逻辑:
$ docker run -d -p 8080:8080 java-hello-world:1.1.0
b0ee04accf078ea7c73cfe3be0f9d1ac6a099ac4e0e903773bc6bf6258acbb66
我们告诉命令使用-pCLI 选项将本地主机上的端口 8080 转发到容器端口 8080。-dCLI 选项在后台运行容器,这意味着它将与容器分离并返回到终端提示符。这意味着我们现在应该能够从本地机器解析应用程序的端点。如以下命令所示,对根上下文路径的简单curl将呈现消息“Hello World!”:
$ curl localhost:8080
Hello World!
列出容器
可以列出任何正在运行的容器以显示它们的运行时属性。以下命令呈现了先前启动的容器。输出包括容器的 ID,以供稍后参考。添加标志-a以同时呈现已终止的容器:
$ docker container ls
CONTAINER ID IMAGE COMMAND ...
b0ee04accf07 java-hello-world:1.1.0 "java -jar /app/java…" ...
与容器交互
一旦容器启动,您可以与其交互。您只需容器 ID。使用logs命令检查应用程序在容器启动时产生的日志消息。检查日志对故障排除很有帮助。以下命令呈现了 Spring Boot 在容器启动时产生的日志消息:
$ docker logs b0ee04accf07
...
2023-06-19 21:06:27.757 INFO 1 --- [nio-8080-exec-1] \
o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing \
Spring DispatcherServlet 'dispatcherServlet'
2023-06-19 21:06:27.757 INFO 1 --- [nio-8080-exec-1] \
o.s.web.servlet.DispatcherServlet : Initializing \
Servlet 'dispatcherServlet'
2023-06-19 21:06:27.764 INFO 1 --- [nio-8080-exec-1] \
o.s.web.servlet.DispatcherServlet : Completed \
initialization in 7 ms
如果容器镜像打包有命令行 shell,您可以深入了解运行中的容器的内部。例如,您可能想要检查应用程序消耗或产生的文件。使用exec命令在容器中运行命令。标志-it允许与容器进行迭代,直到您准备好退出。以下命令打开一个迭代的 bash shell 到正在运行的容器:
$ docker exec -it b0ee04accf07 bash
root@b0ee04accf07:/app# pwd
/app
root@b0ee04accf07:/app# exit
exit
要退出交互式 bash shell,请运行exit命令。您将返回到主机机器上的终端提示符。
发布容器镜像
要将图像发布到注册表,您需要进行一些预备工作。大多数注册表要求您提供一个前缀,该前缀表示用户名或主机名作为容器映像名称的一部分,您可以使用tag命令来实现这一点。
例如,Docker Hub 要求您提供用户名。我的用户名是bmuschko,因此在推送之前我必须重新标记我的映像:
$ docker tag java-hello-world:1.1.0 bmuschko/java-hello-world:1.1.0
tag命令不会创建容器映像的副本。它只是向现有容器映像添加另一个标识符,如下图所示。容器映像的 ID 和大小在两个条目中都是相同的:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
bmuschko/java-hello-world 1.1.0 4b676060678b 6 minutes ago 342MB
java-hello-world 1.1.0 4b676060678b 6 minutes ago 342MB
如果注册表受保护,您还需要提供凭据。对于 Docker Hub,我们使用用户名登录:
$ docker login --username=bmuschko
Password: *****
Login Succeeded
最后,您可以使用push命令将映像推送到注册表:
$ docker push bmuschko/java-hello-world:1.1.0
The push refers to repository [docker.io/bmuschko/java-hello-world]
a7b86a39983a: Pushed
df1b2befe5f0: Pushed
e4db97f0e9ef: Mounted from azul/zulu-openjdk
8e87ff28f1b5: Mounted from azul/zulu-openjdk
1.1.0: digest: sha256:6a5069bd9396a7eded10bf8e24ab251df434c121f8f4293c2d3ef...
您可以通过 Docker Hub 网页发现发布的容器映像,如图 4-2 所示。 “Tags”选项卡列出了映像的所有可用标记,包括其详细信息和使用docker命令快速引用的内容。

图 4-2. 在 Docker Hub 上发现容器映像
现在,任何有权访问注册表的人都可以使用pull命令来使用容器映像。
保存和加载容器映像
与其将容器映像发布到容器注册表中,您可能希望将其保存到文件中。文件可以轻松存储和备份在共享驱动器上,并且不需要容器注册表。save命令将一个或多个映像保存到 tar 存档中。生成的存档文件包含所有父层以及所有标记和版本。以下命令将容器映像保存到文件java-hello-world.tar中:
$ docker save -o java-hello-world.tar java-hello-world:1.1.0
要从 tar 存档中加载容器映像,请使用load命令。该命令还原映像和标记。以下命令从文件java-hello-world.tar中加载容器映像:
$ docker load --input java-hello-world.tar
Loaded image: java-hello-world:1.1.0
现在映像已经在缓存中可用,运行images命令显示如下:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
java-hello-world 1.1.0 4b676060678b 7 minutes ago 342MB
深入了解
到目前为止,您已经体验了最常见的开发者工作流程:将应用程序容器化并将映像推送到注册表。关于构建和运行容器还有很多内容需要学习,但这超出了本书的范围,我们在这里不会深入讨论。如果您想了解更多,请参阅Docker 文档。
总结
应用程序开发人员使用容器化将应用程序代码打包到容器映像中,以便作为可运行软件的单个单元部署到 Kubernetes 集群中。容器化过程使用容器运行时引擎定义、构建、运行和发布容器映像。在本章中,我们使用 Docker Engine 演示了基于 Java 的应用程序的过程;但是,对于使用不同编程语言编写的应用程序,涉及的步骤看起来可能类似。
考试要点
通过容器化过程获得实际经验
Pod 在容器内运行容器镜像。你需要了解如何定义、构建、运行和发布一个容器镜像,除了 Kubernetes 外的其他使用方式。练习使用容器运行时引擎的命令行工具来完成工作流程。
比较不同容器运行时引擎的功能
你应该熟悉 Docker Engine,特别是为了理解容器化过程。在撰写本文时,Docker Engine 仍然是最广泛使用的容器运行时引擎。通过尝试其他容器运行时引擎如 containerd 或 Podman 来扩展你的知识领域。
熟悉其他工作流程
作为应用开发者,你每天都会处理定义、构建和修改容器镜像。容器运行时引擎支持其他不太为人知的功能和工作流程。阅读容器运行时引擎的文档以获得更广泛的认识是一个不错的选择。
样例练习
这些练习的解决方案可在 附录 A 中找到。
-
导航至检出的 GitHub 仓库 bmuschko/ckad-study-guide 的目录 app-a/ch04/containerized-java-app。检查 Dockerfile。
从 Dockerfile 构建容器镜像,标签为
nodejs-hello-world:1.0.0。运行一个容器,使用容器镜像。使应用程序在端口 80 上可用。
对应用程序的端点执行
curl或wget命令。检索容器日志。
-
修改前一个练习中的 Dockerfile。将基础镜像更改为标签
20.4-alpine,工作目录更改为/node。从 Dockerfile 构建容器镜像,标签为
nodejs-hello-world:1.1.0。确保容器镜像已创建,通过列出来验证。
-
拉取容器镜像
alpine:3.18.2,可以在 Docker Hub 上找到。将容器镜像保存到文件 alpine-3.18.2.tar 中。
删除容器镜像。验证容器镜像已不再显示在列表中。
从文件 alpine-3.18.2.tar 恢复容器镜像。
验证容器镜像能够被列出。
第五章:Pods 和命名空间
Kubernetes API 中最重要的原语是 Pod。Pod 允许您运行一个容器化的应用程序。在实践中,您通常会发现 Pod 和容器之间是一对一的映射关系;然而,在第八章讨论的用例中,声明一个 Pod 中多个容器会更有利。
除了运行容器外,Pod 还可以使用其他服务,如存储、配置数据等等。因此,将 Pod 视为运行容器的包装器,同时能够混合跨切面和特定的 Kubernetes 功能。
使用 Pods
在本章中,我们将讨论仅运行单个容器的 Pod 的操作。我们将讨论创建、修改、交互和删除的所有重要的 kubectl 命令,使用命令式和声明式方法。
创建 Pods
Pod 定义需要为每个容器指定一个镜像。在创建 Pod 对象时,无论是命令式还是声明式,调度器都会将 Pod 分配给一个节点,并且容器运行时引擎将检查该节点上是否已存在容器镜像。如果镜像尚不存在,则引擎将从容器镜像注册表下载。默认情况下,注册表是 Docker Hub。一旦镜像存在于节点上,容器就会被实例化并运行。图 5-1 展示了执行流程。

图 5-1. 容器运行时接口与容器镜像的交互
run 命令是创建 Pods 的中心入口点。让我们讨论它的用法以及您应该记住和练习的最重要的命令行选项。假设您想在 Pod 中运行一个Hazelcast 实例。容器应该使用最新的Hazelcast 镜像,暴露端口 5701,并定义一个环境变量。此外,我们还希望为 Pod 分配两个标签。以下命令结合了这些信息,不需要进一步编辑实时对象:
$ kubectl run hazelcast --image=hazelcast/hazelcast:5.1.7 \
--port=5701 --env="DNS_DOMAIN=cluster" --labels="app=hazelcast,env=prod"
run 命令提供了丰富的命令行选项。执行 kubectl run --help 或参考 Kubernetes 文档获取广泛的概述。在考试中,您不需要理解每一个命令。表格 5-1 列出了最常用的选项。
表格 5-1. 重要的 kubectl run 命令行选项
| 选项 | 示例值 | 描述 |
|---|---|---|
--image |
nginx:1.25.1 | 要运行的容器的镜像。 |
--port |
8080 | 此容器暴露的端口。 |
--rm |
N/A | 在容器中的命令完成后删除 Pod。有关更多信息,请参见“创建临时 Pod”。 |
--env |
PROFILE=dev | 在容器中设置的环境变量。 |
--labels |
app=frontend | 要应用于 Pod 的标签的逗号分隔列表。第 9 章 更详细地解释了标签。 |
一些开发人员更习惯于根据 YAML 清单创建 Pods。也许您已经习惯于声明性方法,因为您在工作中使用它。您可以通过打开编辑器,从 Kubernetes 在线文档中复制 Pod YAML 代码片段,并根据需要进行修改,为 Hazelcast Pod 表达相同的配置。示例 5-1 展示了保存在文件 pod.yaml 中的 Pod 清单:
示例 5-1. Pod YAML 清单
apiVersion: v1
kind: Pod
metadata:
name: hazelcast 
labels: 
app: hazelcast
env: prod
spec:
containers:
- name: hazelcast
image: hazelcast/hazelcast:5.1.7 
env: 
- name: DNS_DOMAIN
value: cluster
ports:
- containerPort: 5701 
为 Pod 分配名称 hazelcast。
指定要应用于 Pod 的标签。
声明要在 Pod 的容器中执行的容器镜像。
向容器注入一个或多个环境变量。
要在 Pod 的 IP 地址上公开的端口数量。
从清单创建 Pod 很简单。只需使用 create 或 apply 命令,如此处所示,并在 “管理对象” 中进行了解:
$ kubectl apply -f pod.yaml
pod/hazelcast created
列出 Pods
现在您已经创建了一个 Pod,可以进一步检查其运行时信息。kubectl 命令提供了一个列出集群中所有正在运行的 Pods 的命令:get pods。以下命令渲染了名为 hazelcast 的 Pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
hazelcast 1/1 Running 0 17s
真实的 Kubernetes 集群可以同时运行数百个 Pods。如果您知道感兴趣的 Pod 的名称,通常更容易通过名称查询。您仍然只会看到一个单独的 Pod:
$ kubectl get pods hazelcast
NAME READY STATUS RESTARTS AGE
hazelcast 1/1 Running 0 17s
Pod 生命周期阶段
因为 Kubernetes 是一个带有异步控制循环的状态引擎,当列出 Pods 时,Pod 的状态可能不会立即显示为 Running 状态。通常需要几秒钟来获取镜像并启动容器。在 Pod 创建时,对象会经历几个 生命周期阶段,如图 5-2 所示。

图 5-2. Pod 生命周期阶段
理解每个阶段的影响很重要,因为它可以让您了解 Pod 的操作状态。例如,在考试期间,您可能会被要求识别具有问题的 Pod 并进一步调试该对象。表 5-2 描述了所有 Pod 生命周期阶段。
表 5-2. Pod 生命周期阶段
| 选项 | 描述 |
|---|---|
Pending |
Pod 已被 Kubernetes 系统接受,但一个或多个容器镜像尚未创建。 |
Running |
至少有一个容器仍在运行或正在启动或重新启动过程中。 |
Succeeded |
Pod 中的所有容器均已成功终止。 |
Failed |
Pod 中的容器已终止;至少一个以错误状态失败。 |
Unknown |
无法获取 Pod 的状态。 |
Pod 的生命周期阶段不应与 Pod 中的容器状态混淆。容器可以处于三种可能的状态之一:Waiting、Running和Terminated。您可以在Kubernetes 文档中详细了解容器的状态。
渲染 Pod 详情
get命令生成的表格提供了关于 Pod 的高级信息。但是,如果您需要更详细的信息,可以使用describe命令:
$ kubectl describe pods hazelcast
Name: hazelcast
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: docker-desktop/192.168.65.3
Start Time: Wed, 20 May 2020 19:35:47 -0600
Labels: app=hazelcast
env=prod
Annotations: <none>
Status: Running
IP: 10.1.0.41
Containers:
...
Events:
...
终端输出包含 Pod 的元数据信息、它运行的容器以及事件日志,例如在调度 Pod 时的失败。示例输出已经被压缩,仅显示元数据部分。您可以期望输出非常长。
您可以更具体地渲染所需的信息。如果您想要识别容器中正在运行的镜像,可以结合describe命令和 Unix 的grep命令:
$ kubectl describe pods hazelcast | grep Image:
Image: hazelcast/hazelcast:5.1.7
访问 Pod 的日志
作为应用程序开发者,我们非常了解我们实现的应用程序生成的日志文件中会出现什么。在容器中操作应用程序时可能会发生运行时故障。logs命令会下载容器的日志输出。以下输出表明 Hazelcast 服务器成功启动:
$ kubectl logs hazelcast
...
May 25, 2020 3:36:26 PM com.hazelcast.core.LifecycleService
INFO: [10.1.0.46]:5701 [dev] [4.0.1] [10.1.0.46]:5701 is STARTED
一旦容器接收到来自最终用户的流量,很可能会生成更多的日志条目。您可以使用命令行选项-f来实时流式传输日志。如果您希望实时查看日志,这个选项将非常有帮助。
Kubernetes 在某些条件下会尝试重新启动容器,例如如果在第一次尝试中无法解析镜像。在容器重新启动时,您将无法访问上一个容器的日志;logs命令仅会为当前容器渲染日志。然而,您仍然可以通过添加-p命令行选项返回到上一个容器的日志。您可能希望使用此选项来识别触发容器重新启动的根本原因。
在容器中执行命令
有些情况需要您进入正在运行的容器并探索文件系统。也许您想要检查应用程序的配置或调试其当前状态。您可以使用exec命令在容器中打开一个 shell,以交互方式进行探索,如下所示:
$ kubectl exec -it hazelcast -- /bin/sh
# ...
请注意,您无需提供资源类型。此命令仅适用于 Pod。两个破折号(--)将exec命令及其选项与要在容器内运行的命令分隔开来。
也可以在容器内执行单个命令。假设您希望渲染容器中可用的环境变量而无需登录。只需删除交互标志-it,并在两个破折号之后提供相关命令即可:
$ kubectl exec hazelcast -- env
...
DNS_DOMAIN=cluster
创建临时 Pod
在 Pod 内执行的命令(通常是实现业务逻辑的应用程序)通常意味着要无限运行。一旦创建了 Pod,它就会保持存在。在某些情况下,您需要仅为了故障排除而在 Pod 中执行命令。这种用例不要求 Pod 对象在命令执行后继续运行。这就是临时 Pod 发挥作用的地方。
run命令提供了--rm标志,这将在内部运行的命令结束后自动删除 Pod。假设您想使用env来查看容器内所有可用的环境变量。以下命令恰好可以做到这一点:
$ kubectl run busybox --image=busybox:1.36.1 --rm -it --restart=Never -- env
...
HOSTNAME=busybox
pod "busybox" deleted
输出中呈现的最后一条消息明确说明,在命令执行后,该 Pod 已被删除。
使用 Pod 的 IP 地址进行网络通信
每个 Pod 在创建时被分配一个 IP 地址。您可以使用get pod命令的-o wide命令行选项或描述 Pod 来查看 Pod 的 IP 地址。以下控制台输出中的 Pod 的 IP 地址是10.244.0.5:
$ kubectl run nginx --image=nginx:1.25.1 --port=80
pod/nginx created
$ kubectl get pod nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE \
NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 37s 10.244.0.5 minikube \
<none> <none>
$ kubectl get pod nginx -o yaml
...
status:
podIP: 10.244.0.5
...
分配给 Pod 的 IP 地址在所有节点和命名空间中是唯一的。这是通过在注册节点时为每个节点分配专用子网来实现的。在节点上创建新的 Pod 时,IP 地址是从分配的子网中租用的。这由网络生命周期管理器 kube-proxy 以及域名服务 (DNS) 和容器网络接口 (CNI) 处理。
你可以通过创建一个临时 Pod,在其中使用命令行工具curl或wget调用另一个 Pod 的 IP 地址来轻松验证其行为。
$ kubectl run busybox --image=busybox:1.36.1 --rm -it --restart=Never \
-- wget 172.17.0.4:80
Connecting to 172.17.0.4:80 (172.17.0.4:80)
saving to 'index.html'
index.html 100% |********************************| 615 0:00:00 ETA
'index.html' saved
pod "busybox" deleted
需要理解的重点是,IP 地址随时间的推移并不稳定。Pod 重新启动会获得新的 IP 地址。因此,这个 IP 地址通常被称为虚拟 IP 地址。构建微服务架构——其中每个应用程序在其自己的 Pod 中运行,并需要使用稳定的网络接口相互通信——需要不同的概念:服务。更多信息请参见第二十一章。
配置 Pods
课程希望您能够熟悉编辑 YAML 清单文件或作为实时对象表示。本节向您展示了考试期间可能遇到的一些典型配置场景。后续章节将通过触及其他配置方面来深化您的知识。
声明环境变量
应用程序需要暴露一种方法来使其运行时行为可配置。例如,您可能希望注入外部 Web 服务的 URL 或声明数据库连接的用户名。环境变量是提供此运行时配置的常见选项。
避免为每个环境创建容器镜像
或许会有诱惑说,“嘿,让我们为所需的每个目标部署环境创建一个容器镜像,包括其配置。”那是一个坏主意。持续交付和十二要素应用程序原则之一的实践是为每次提交构建一个可部署的构件。在这种情况下,构件是容器镜像。通过在实例化容器时注入运行时信息,可以控制偏离的配置运行时行为。根据需要使用环境变量来控制行为。
在 Pod 的 YAML 清单中定义环境变量相对比较简单。添加或增强容器的env部分。每个环境变量由键值对表示,通过name和value属性表示。Kubernetes 不强制执行或清理环境变量键的典型命名约定,但建议遵循使用大写字母和下划线(_)来分隔单词的标准。
要了解一组环境变量的示例,请参阅示例 5-2。此代码片段描述了一个使用 Spring Boot 框架运行 Java 应用程序的 Pod。
示例 5-2. 用于定义 Pod 环境变量的 YAML 清单
apiVersion: v1
kind: Pod
metadata:
name: spring-boot-app
spec:
containers:
- name: spring-boot-app
image: bmuschko/spring-boot-app:1.5.3
env:
- name: SPRING_PROFILES_ACTIVE
value: prod
- name: VERSION
value: '1.5.3'
第一个名为SPRING_PROFILES_ACTIVE的环境变量定义了指向所谓配置文件的指针。配置文件包含特定于环境的属性。在这里,我们指向配置生产环境的配置文件。环境变量VERSION指定了应用程序版本。其值对应于图像的标签,并且可以由运行中的应用程序公开以在用户界面中显示值。
定义带参数的命令
许多容器镜像已经定义了ENTRYPOINT或CMD指令。指令分配的命令将作为容器启动的一部分自动执行。例如,我们之前使用的 Hazelcast 镜像定义了指令CMD ["/opt/hazelcast/start-hazelcast.sh"]。
在 Pod 定义中,您可以重新定义镜像的ENTRYPOINT和CMD指令,或者为容器分配一个未被镜像指定的执行命令。您可以借助command和args属性为容器提供这些信息。command属性将覆盖镜像的ENTRYPOINT指令。args属性替换镜像的CMD指令。
假设你想要为尚未提供命令的镜像提供一个命令。通常有两种不同的方法:命令式和声明式。我们将利用 run 命令生成 YAML 清单。Pod 应该使用 busybox:1.36.1 镜像,并执行一个每 10 秒在无限循环中呈现当前日期的 shell 命令:
$ kubectl run mypod --image=busybox:1.36.1 -o yaml --dry-run=client \
> pod.yaml -- /bin/sh -c "while true; do date; sleep 10; done"
在生成但精简的 pod.yaml 文件中可以看到,示例 5-3 中的命令已被转换为 args 属性。Kubernetes 将每个参数指定为单独的行。
示例 5-3. 包含 args 属性的 YAML 清单
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: busybox:1.36.1
args:
- /bin/sh
- -c
- while true; do date; sleep 10; done
如果你要手动创建 YAML 清单,可以通过command和args属性的组合实现相同的效果。示例 5-4 展示了一种不同的方法。
示例 5-4. 包含 command 和 args 属性的 YAML 清单
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: busybox:1.36.1
command: ["/bin/sh"]
args: ["-c", "while true; do date; sleep 10; done"]
你可以快速验证声明的命令是否真的有效。首先创建 Pod 实例,然后尾随日志:
$ kubectl apply -f pod.yaml
pod/mypod created
$ kubectl logs mypod -f
Fri May 29 00:49:06 UTC 2020
Fri May 29 00:49:16 UTC 2020
Fri May 29 00:49:26 UTC 2020
...
删除一个 Pod
迟早你会想要删除一个 Pod。在考试期间,可能会要求你删除一个 Pod。或者,你可能出现了配置错误,希望从头开始:
$ kubectl delete pod hazelcast
pod "hazelcast" deleted
请记住,Kubernetes 尝试优雅地删除一个 Pod。这意味着 Pod 将尝试完成对其的活动请求,以避免给最终用户带来不必要的中断。一个优雅的删除操作可能需要 5 到 30 秒的时间,这是你在考试中不想浪费的时间。更多关于如何加速这一过程的信息,请参见 第一章。
删除一个 Pod 的另一种方法是将 delete 命令指向你用来创建它的 YAML 清单。行为是相同的:
$ kubectl delete -f pod.yaml
pod "hazelcast" deleted
为了在考试中节省时间,你可以通过将 delete 命令添加 --now 选项来绕过优雅期限。在生产 Kubernetes 环境中避免使用 --now 标志。
处理命名空间
命名空间是避免命名冲突的 API 构造,并且它们表示对象名称的作用域。命名空间的一个很好的用例是通过团队或责任来隔离对象。
对象的命名空间
本章内容演示了使用命名空间管理 Pod 对象。尽管命名空间不仅适用于 Pod 的概念。大多数对象类型都可以通过命名空间进行分组。
在考试中,大多数问题都会要求你在为你设置的特定命名空间中执行命令。以下部分简要介绍了处理命名空间所需的基本操作。
列出命名空间
Kubernetes 集群最初会启动几个初始命名空间。你可以使用以下命令列出它们:
$ kubectl get namespaces
NAME STATUS AGE
default Active 157d
kube-node-lease Active 157d
kube-public Active 157d
kube-system Active 157d
default命名空间托管尚未分配到显式命名空间的对象。以kube-前缀开头的命名空间不视为最终用户命名空间。它们是由 Kubernetes 系统创建的。作为应用程序开发人员,您不必与它们交互。
创建和使用命名空间
要创建新的命名空间,请使用create namespace命令。以下命令使用名称code-red:
$ kubectl create namespace code-red
namespace/code-red created
$ kubectl get namespace code-red
NAME STATUS AGE
code-red Active 16s
示例 5-5 显示了其作为 YAML 清单的对应表示。
示例 5-5. 命名空间 YAML 清单
apiVersion: v1
kind: Namespace
metadata:
name: code-red
命名空间就位后,您可以在其中创建对象。您可以使用命令行选项--namespace或其缩写形式-n来执行此操作。以下命令在命名空间code-red中创建一个新的 Pod,然后列出该命名空间中可用的 Pod:
$ kubectl run pod --image=nginx:1.25.1 -n code-red
pod/pod created
$ kubectl get pods -n code-red
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 13s
设置命名空间偏好
对每个命令提供--namespace或-n命令行选项非常繁琐且容易出错。如果您知道需要与负责的特定命名空间交互,可以设置永久命名空间偏好。第一个显示的命令设置了永久命名空间code-red。第二个命令显示当前设置的永久命名空间:
$ kubectl config set-context --current --namespace=code-red
Context "minikube" modified.
$ kubectl config view --minify | grep namespace:
namespace: hello
后续的kubectl执行不必再次明确指定命名空间code-red:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 13s
您可以始终使用config set-context命令切换回default命名空间或其他自定义命名空间:
$ kubectl config set-context --current --namespace=default
Context "minikube" modified.
删除命名空间
删除命名空间会对其中现有的对象产生级联影响。删除命名空间将自动删除其对象:
$ kubectl delete namespace code-red
namespace "code-red" deleted
$ kubectl get pods -n code-red
No resources found in code-red namespace.
总结
考试强调 Pod 的概念,这是 Kubernetes 的一个原语,负责在容器中运行应用程序。一个 Pod 可以定义使用容器映像的一个或多个容器。在创建时,容器映像将被解析并用于启动应用程序。可以使用相关的 YAML 配置进一步自定义每个 Pod。
考试要点
知道如何与 Pod 交互
一个 Pod 在容器内运行应用程序。您可以通过检查对象并使用kubectl get或kubectl describe命令来查看 Pod 的状态和配置。熟悉 Pod 的生命周期阶段可以快速诊断错误。命令kubectl logs可用于下载容器日志信息,而无需进入容器壳。使用命令kubectl exec进一步探索容器环境,例如检查进程或检查文件。
了解高级 Pod 配置选项
有时您必须从 Pod 的 YAML 清单开始,然后声明性地创建 Pod。如果您想向容器提供环境变量或声明自定义命令,这种情况可能会发生。通过从 Kubernetes 文档中复制相关代码片段来练习不同的配置选项。
练习使用自定义命名空间
考试中的大多数问题将要求您在特定的命名空间中工作。您需要了解如何使用 kubectl 的选项 --namespace 和 -n 与该命名空间进行交互。为了避免意外地在错误的命名空间上工作,知道如何永久设置命名空间。
示例练习
解决这些练习的方案可以在 附录 A 找到。
-
创建一个名为
nginx的新 Pod,运行镜像nginx:1.17.10。暴露容器端口 80。Pod 应该位于名为ckad的命名空间中。获取 Pod 的详细信息,包括其 IP 地址。
创建一个临时 Pod,使用
busybox:1.36.1镜像在容器内执行wget命令。wget命令应该访问nginx容器暴露的端点。您应该在终端看到渲染的 HTML 响应主体。获取
nginx容器的日志。向
nginxPod 的容器中添加环境变量DB_URL=postgresql://mydb:5432和DB_USERNAME=admin。打开
nginx容器的 shell 并检查当前目录的内容ls -l。退出容器。 -
为名为
loop的 Pod 创建一个 YAML 清单,该 Pod 在容器中运行busybox:1.36.1镜像。容器应运行以下命令:for i in {1..10}; do echo "Welcome $i times"; done。从 YAML 清单创建 Pod。Pod 的状态是什么?编辑名为
loop的 Pod。将运行命令更改为无限循环。每次迭代应该echo当前日期。检查
loopPod 的事件和状态。
第六章:Jobs 和 CronJobs
Job 模型化一次性过程——例如批处理操作。当工作完成后,Pod 及其包含的容器停止运行。CronJobs 根据其定义的调度周期定期运行。CronJob 的一个好应用场景是需要定期发生的任务(例如,导出数据的进程)。在本章中,您将学习如何配置、运行和检查 Job 和 CronJob。
与 Jobs 一起工作
Job 是 Kubernetes 的原语,运行功能,直到达到指定的完成数为止,非常适合一次性操作,如导入/导出数据过程或有限终端的 I/O 密集型过程。由 Job 管理的实际工作仍在 Pod 内运行。因此,您可以将 Job 视为执行工作负载的更高级协调实例。Figure 6-1 显示了 Job 与其管理的 Pod(s) 之间的父子关系。
当 Job 及其 Pod 完成时,Kubernetes 不会自动删除这些对象——它们会一直保留,直到明确删除为止。保留这些对象有助于调试 Pod 内部运行的命令,并且让你有机会检查日志。

图 6-1. Job 与其 Pods 之间的关系
Jobs 和 Pods 的自动清理
Kubernetes 支持一个 Job 的自动清理机制 及其受控 Pod,通过指定 YAML 属性 spec.ttlSecondsAfterFinished。
创建和检查 Jobs
让我们先创建一个 Job 并观察其在实践中的行为,然后再深入细节。要即时创建一个 Job,只需使用create job命令。如果提供的镜像不运行任何命令,则可能需要附加要在相应 Pod 中执行的命令。
下面的命令创建一个运行迭代过程的 Job。在循环的每次迭代中,一个名为 counter 的变量会递增。当计数器值达到 3 时,命令执行结束:
$ kubectl create job counter --image=nginx:1.25.1 \
-- /bin/sh -c 'counter=0; while [ $counter -lt 3 ]; do \
counter=$((counter+1)); echo "$counter"; sleep 3; done;'
job.batch/counter created
示例 6-1 显示了 Job 的 YAML 清单等效版本,如果您更喜欢声明性方法。
示例 6-1. 执行循环命令的 Job
apiVersion: batch/v1
kind: Job
metadata:
name: counter
spec:
template: 
spec:
containers:
- name: counter
image: nginx:1.25.1
command:
- /bin/sh
- -c
- counter=0; while [ $counter -lt 3 ]; do counter=$((counter+1)); \
echo "$counter"; sleep 3; done;
restartPolicy: Never
Pod 模板使用与 Pod 定义中相同的属性。
列出 Job 的输出显示当前完成数和预期完成数。默认完成数为 1. 这意味着如果执行命令的 Pod 成功,Job 就被视为完成。如下终端输出所示,默认情况下,一个 Job 使用单个 Pod 执行工作。相应的 Pod 可以通过名称标识——它使用 Job 名称作为其名称的前缀:
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
counter 0/1 13s 13s
$ kubectl get jobs
NAME COMPLETIONS DURATION AGE
counter 1/1 15s 19s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
counter-z6kdj 0/1 Completed 0 51s
若要验证 Job 的正确行为,可以下载其日志。预期地,输出会为每次迭代显示计数器:
$ kubectl logs counter-z6kdj
1
2
3
您可以进一步调整作业的运行时行为。接下来的两个部分将讨论配置作业操作类型和重启行为。
作业操作类型
作业的默认行为是在单个 Pod 中运行工作负载,并期望一次成功完成。这是 Kubernetes 称为非并行作业。这些参数在内部由属性 spec.completions 和 spec.parallelism 跟踪,每个属性的分配值均为 1. 以下命令呈现了我们之前创建的作业的参数:
$ kubectl get jobs counter -o yaml | grep -C 1 "completions"
...
completions: 1
parallelism: 1
...
您可以调整任何这些参数以适应您的用例需求。例如,如果您期望工作负载成功完成多次,则可以将 spec.completions 的值增加至至少 2. 有时,您可能希望通过多个 Pod 并行执行工作负载。在这些情况下,您会增加分配给 spec.parallelism 的值。这被称为并行作业。请记住,您可以使用这两个属性的分配值的任何组合。表 6-1 总结了不同的用例。
表 6-1. 不同作业操作类型的配置
| 类型 | spec.completions | spec.parallelism | 描述 |
|---|---|---|---|
| 单次完成非并行 | 1 | 1 | 一旦其 Pod 成功终止,即完成。 |
| 固定完成计数并行 | >= 1 | >= 1 | 当指定数量的任务成功完成时完成。 |
| 工作队列并行 | 未设置 | >= 1 | 当至少一个 Pod 成功终止且所有 Pod 都终止时完成。 |
重启行为
spec.backoffLimit 属性确定作业尝试成功完成工作负载的重试次数,直到执行的命令以退出代码 0 结束。默认值为 6,这意味着作业在被认为失败之前将执行工作负载 6 次。
作业清单需要通过 spec.template.spec.restartPolicy 明确声明重启策略。Pod 的默认重启策略是 Always,告诉 Kubernetes 调度器即使容器以 0 的退出代码退出,也会始终重新启动 Pod。作业的重启策略只能是 OnFailure 或 Never。
在容器失败时重新启动
图 6-2 展示了使用重启策略 OnFailure 的作业的行为。在容器失败时,该策略将简单地重新运行容器。

图 6-2. 重启策略 onFailure
失败时启动新的 Pod
图 6-3 展示了使用重启策略 Never 的作业的行为。该策略在容器失败时不会重新启动容器,而是启动一个新的 Pod。

图 6-3. 重启策略 Never
使用 CronJobs
作业表示有限操作。一旦操作成功执行,作业完成并且不再创建新的 Pod。CronJob 根据计划定期创建新的作业对象。由作业控制的 Pod 处理实际工作负载。图 6-4 展示了 CronJob、它管理的作业及执行工作负载的 Pod 之间的关系。

图 6-4. CronJob、作业及其 Pod 之间的关系
可以使用类似于 Unix cron 作业的 cron 表达式定义计划。图 6-5 显示了每小时执行一次的 CronJob。每次执行时,CronJob 创建一个新的 Pod 执行任务,并以 0 或非零退出代码完成。

图 6-5. 根据计划执行作业
创建和检查 CronJob
您可以使用命令式的create cronjob命令创建新的 CronJob。以下命令安排 CronJob 每分钟运行一次。每次执行时创建的 Pod 使用 Unix 的echo命令将当前日期渲染到标准输出:
$ kubectl create cronjob current-date --schedule="* * * * *" \
--image=nginx:1.25.1 -- /bin/sh -c 'echo "Current date: $(date)"'
cronjob.batch/current-date created
要从 YAML 文件创建 CronJob,请使用示例 6-2 中显示的定义。
示例 6-2. 打印当前日期的 CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: current-date
spec:
schedule: "* * * * *" 
jobTemplate: 
spec:
template:
spec:
containers:
- name: current-date
image: nginx:1.25.1
args:
- /bin/sh
- -c
- 'echo "Current date: $(date)"'
restartPolicy: OnFailure
定义确定何时需要创建新的作业对象的 cron 表达式。
描述作业模板的部分。
如果您使用get cronjobs命令列出现有的 CronJob,则会看到计划、上次计划执行以及 CronJob 是否当前活动:
$ kubectl get cronjobs
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
current-date * * * * * False 0 <none> 28s
$ kubectl get cronjobs
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
current-date * * * * * False 1 14s 53s
很容易匹配由 CronJob 创建的作业和 Pod。您可以通过名称前缀简单地识别它们。在这种情况下,前缀是current-date-:
$ kubectl get jobs,pods
NAME COMPLETIONS DURATION AGE
job.batch/current-date-28473049 1/1 3s 2m23s
job.batch/current-date-28473050 1/1 3s 83s
job.batch/current-date-28473051 1/1 3s 23s
NAME READY STATUS RESTARTS AGE
pod/current-date-28473049-l6hc7 0/1 Completed 0 2m23s
pod/current-date-28473050-csq7n 0/1 Completed 0 83s
pod/current-date-28473051-jg8st 0/1 Completed 0 23s
配置保留的作业历史记录
即使由 CronJob 控制的 Pod 中的任务完成后,它也不会自动删除。保留 Pod 的历史记录可以极大地帮助排查失败的工作负载或检查日志。默认情况下,CronJob 保留最后三个成功的 Pod 和最后一个失败的 Pod:
$ kubectl get cronjobs current-date -o yaml | grep successfulJobsHistoryLimit:
successfulJobsHistoryLimit: 3
$ kubectl get cronjobs current-date -o yaml | grep failedJobsHistoryLimit:
failedJobsHistoryLimit: 1
要重新配置作业保留历史记录限制,请为spec.successfulJobsHistoryLimit和spec.failedJobsHistoryLimit属性设置新值。示例 6-3 保留了最近五次成功执行和最后三次失败执行。
示例 6-3. 配置保留历史记录限制的 CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: current-date
spec:
successfulJobsHistoryLimit: 5 
failedJobsHistoryLimit: 3 
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: current-date
image: nginx:1.25.1
args:
- /bin/sh
- -c
- 'echo "Current date: $(date)"'
restartPolicy: OnFailure
定义保留的成功作业数。
定义保留的失败作业数。
总结
工作很适合实施作为有限操作在一个或多个 Pod 中运行的批处理过程。作业和 Pod 这两个对象在工作完成后不会被删除,以支持检查和故障排除活动。CronJob 与作业非常相似,但是按照 Unix cron 表达式的计划执行。
考试要点
了解作业和 CronJob 的实际用例
作业和 CronJob 管理应至少完成一次或定期完成的 Pod。您需要理解这些对象的创建方式以及如何在运行时检查它们。确保尝试不同的配置选项以及它们如何影响运行时行为。
练习不同的作业操作模式
作业可以在三种模式下运行:非并行模式,具有固定完成计数的并行模式以及具有工作队列的并行模式。作业的默认行为是在单个 Pod 中运行工作负载并期望一次成功完成(非并行作业)。属性 spec.completions 控制所需的成功完成数。属性 spec.parallelism 允许多个 Pod 并行执行工作负载。
示例练习
这些练习的解决方案可以在 附录 A 中找到。
-
使用容器镜像
alpine:3.17.3创建名为random-hash的作业,该作业执行 shell 命令echo $RANDOM | base64 | head -c 20。配置作业以两个 Pod 并行执行。完成次数应设置为 5。确定执行了 shell 命令的 Pod。您期望存在多少个 Pod?
从其中一个 Pod 中检索生成的哈希。
删除作业。相应的 Pod 是否会继续存在?
-
创建一个名为
google-ping的新 CronJob。执行时,作业应该运行一个curl命令访问google.com。选择合适的镜像。执行应每两分钟进行一次。观察由 CronJob 管理的基础作业的创建。检查相关命令的命令行选项或参考 Kubernetes 文档。
重新配置 CronJob 以保留七次执行的历史记录。
重新配置 CronJob,以防止当前执行仍在运行时禁止新执行。更多信息,请参考 Kubernetes 文档。
第七章:卷
容器的临时文件系统与任何其他容器或 Pod 隔离,并且在 Pod 重新启动后不会持久化。Pod 可以定义一个卷并将其挂载到容器上。
临时卷存在于 Pod 的生命周期内。如果您希望在运行在 Pod 中的多个容器之间共享数据,它们非常有用。持久卷则在 Pod 的生命周期外保留数据。对于需要数据长期存在的应用程序来说,它们是一个很好的选择,例如数据库驱动应用程序的存储形式。在本章中,我们将在 Pod 中练习不同类型的卷的使用。
使用存储
在容器中运行的应用程序可以使用临时文件系统读写文件。在容器崩溃或集群/节点重新启动时,kubelet 将重新启动容器。已写入临时文件系统的任何数据都会丢失,无法检索。容器实际上是以空白状态启动的。
有很多情况需要在容器中挂载卷。我们将在 第八章 中看到其中一个最突出的用例:使用临时卷在主应用程序容器和 sidecar 之间交换数据。图 7-1 说明了容器的临时文件系统与使用卷的差异。

图 7-1. 使用临时文件系统与卷的容器
卷类型
每个卷都需要定义一个类型。该类型确定支持卷的介质及其运行时行为。
临时卷
这些存在于 Pod 的生命周期内。临时卷在多个容器之间共享数据或者可以在 Pod 重新启动时轻松重建卷上的数据时非常有用。
持久卷
这些持久化数据超出了 Pod 的生命周期。持久卷是应用程序的好选择,需要数据存在更长时间,例如用于数据库驱动应用程序的存储形式。
Kubernetes 文档提供了一长串的卷类型。表 7-1 提供了我认为对考试最相关的一些卷类型的选择列表。
表 7-1. 最相关于考试的卷类型
| 类型 | 描述 |
|---|---|
emptyDir |
Pod 中具有读写访问权限的空目录。仅在 Pod 的生命周期内持久化。用于缓存实现或者在 Pod 的容器之间交换数据是一个不错的选择。 |
hostPath |
主机节点文件系统中的文件或目录。仅支持单节点集群,不适用于生产环境。 |
configMap, secret |
提供注入配置数据的方法。有关实际示例,请参见 第十九章。 |
nfs |
现有的 NFS(网络文件系统)共享。在 Pod 重新启动后保留数据。 |
persistentVolumeClaim |
声明持久卷。更多信息,请参见“创建持久卷声明”。 |
临时卷
为 Pod 定义临时卷需要两个步骤。首先,您需要使用属性 spec.volumes[] 声明卷本身。在定义中,您提供名称和类型。仅仅声明卷是不够的。其次,通过 spec.containers[].volumeMounts[] 将卷挂载到消耗容器的路径上。卷与卷挂载之间的映射通过匹配名称完成。
创建并挂载临时卷
在 示例 7-1 中,存储在文件 pod-with-volume.yaml 中的定义了类型为 emptyDir 的卷。卷已挂载到名为 nginx 的容器内的路径 /var/log/nginx。
示例 7-1. 定义并挂载临时卷的 Pod
apiVersion: v1
kind: Pod
metadata:
name: business-app
spec:
volumes:
- name: logs-volume
emptyDir: {}
containers:
- image: nginx:1.25.1
name: nginx
volumeMounts:
- mountPath: /var/log/nginx
name: logs-volume
与卷交互
让我们创建 Pod 并查看是否可以与挂载的卷交互。以下命令在 Pod 创建后打开一个交互式 shell,然后导航到挂载路径。您可以看到类型为 emptyDir 的卷将挂载路径初始化为一个空目录。可以根据需要创建新的文件和目录,没有限制:
$ kubectl create -f pod-with-volume.yaml
pod/business-app created
$ kubectl get pod business-app
NAME READY STATUS RESTARTS AGE
business-app 1/1 Running 0 43s
$ kubectl exec business-app -it -- /bin/sh
# cd /var/log/nginx
# pwd
/var/log/nginx
# ls
# touch app-logs.txt
# ls
app-logs.txt
关于多个容器挂载 emptyDir 卷类型的示例用例,请参见第八章。
持久卷
存储在卷上的数据可以在容器重新启动后持续存在。在许多应用程序中,数据的存活周期远远超出了应用程序、容器、Pod、节点甚至集群本身的生命周期。数据持久性确保数据的生命周期与集群资源的生命周期解耦。典型的例子是由数据库持久化的数据。这是持久卷的责任。Kubernetes 使用两个原语来持久化数据:持久卷和持久卷声明。
持久卷是 Kubernetes 集群中表示存储片段的原始对象。它与 Pod 完全解耦,因此具有自己的生命周期。该对象捕获存储的来源(例如,由云提供商提供的存储)。持久卷由 Kubernetes 管理员提供或通过映射到存储类动态分配。
持久卷声明请求持久卷的资源,例如存储的大小和访问类型。在 Pod 中,您将使用类型 persistentVolumeClaim 通过持久卷声明来挂载抽象的持久卷。
图 7-2 显示了 Pod、持久卷声明和持久卷之间的关系。

图 7-2. 从 Pod 中声明持久卷
静态与动态配置
持久卷可以静态或动态创建。如果选择静态方法,则首先需要创建存储设备,然后通过显式创建 PersistentVolume 对象来引用它。动态方法不需要您创建 PersistentVolume 对象。它将通过设置 spec.storageClassName 属性自动从 PersistentVolumeClaim 创建。
存储类是定义存储设备类别(例如,具有慢速或快速性能的存储)的抽象概念,用于不同的应用程序类型。设置存储类是 Kubernetes 管理员的工作。有关存储类的更深入讨论,请参见 “存储类”。现在,我们将重点放在持久卷的静态配置上。
创建持久卷
当您自行创建持久卷对象时,我们称这种方法为静态配置。只能使用先声明的方式创建持久卷。此时,kubectl 不允许使用 create 命令创建持久卷。每个持久卷都需要通过 spec.capacity 定义存储容量,并通过 spec.accessModes 设置访问模式。有关持久卷的配置选项的更多信息,请参见 “持久卷的配置选项”。
示例 7-2 创建一个名为 db-pv 的持久卷,其存储容量为 1Gi,并由单个节点具有读/写访问。属性 hostPath 挂载来自主机节点文件系统的目录 /data/db。我们将把 YAML 清单存储在文件 db-pv.yaml 中。
示例 7-2. 定义持久卷的 YAML 清单
apiVersion: v1
kind: PersistentVolume
metadata:
name: db-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/db
当您检查创建的持久卷时,您将发现大部分信息都来自于清单提供的信息。状态 Available 表示该对象已准备好被索取。回收策略决定在持久卷被释放后应该发生什么。默认情况下,对象将被保留。下面的示例使用短格式命令 pv 避免输入 persistentvolume:
$ kubectl create -f db-pv.yaml
persistentvolume/db-pv created
$ kubectl get pv db-pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS \
CLAIM STORAGECLASS REASON AGE
db-pv 1Gi RWO Retain Available \
10s
持久卷的配置选项
持久卷提供各种配置选项,确定其固有的运行时行为。对于考试而言,理解卷模式、访问模式和回收策略的配置选项非常重要。
卷模式
卷模式处理设备的类型。这是一个要么用于文件系统的设备,要么由块设备支持的设备。最常见的情况是文件系统设备。您可以使用 spec.volumeMode 属性设置卷模式。表 7-2 显示了所有可用的卷模式。
表 7-2. 持久卷卷模式
| 类型 | 描述 |
|---|---|
Filesystem |
默认。将卷挂载到消费 Pod 的目录中。如果卷由块设备支持且设备为空,则首先创建文件系统。 |
Block |
用于作为原始块设备的卷,无文件系统。 |
默认情况下,get pv 命令的控制台输出不会显示卷模式。您需要提供 -o wide 命令行选项来查看 VOLUMEMODE 列,如下所示:
$ kubectl get pv -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS \
CLAIM STORAGECLASS REASON AGE VOLUMEMODE
db-pv 1Gi RWO Retain Available \
19m Filesystem
访问模式
每个持久卷可以使用 spec.accessModes 属性表达其访问方式。例如,您可以定义卷仅可以由单个 Pod 以读写模式挂载,或者卷为只读但可同时从不同节点访问。表 7-3 提供了可用访问模式的概述。通常,在特定命令的输出中会渲染访问模式的短形式标注,例如 get pv 或 describe pv。
表 7-3. 持久卷访问模式
| 类型 | 短形式 | 描述 |
|---|---|---|
ReadWriteOnce |
RWO | 单个节点读写访问 |
ReadOnlyMany |
ROX | 多个节点只读访问 |
ReadWriteMany |
RWX | 多个节点读写访问 |
ReadWriteOncePod |
RWOP | 由单个 Pod 挂载的读写访问 |
下面的命令从名为 db-pv 的持久卷中解析访问模式。正如您所见,返回的值是一个数组,强调了您可以一次分配多个访问模式:
$ kubectl get pv db-pv -o jsonpath='{.spec.accessModes}'
["ReadWriteOnce"]
回收策略
另外,您还可以为持久卷定义回收策略。回收策略指定了当绑定的持久卷索赔被删除时持久卷对象应如何处理(参见 表 7-4)。对于动态创建的持久卷,可以通过存储类别的属性 .reclaimPolicy 设置回收策略。对于静态创建的持久卷,请使用持久卷定义中的属性 spec.persistentVolumeReclaimPolicy。
表 7-4. 持久卷回收策略
| 类型 | 描述 |
|---|---|
Retain |
默认。当持久卷索赔被删除时,持久卷“释放”,可以重新声明。 |
Delete |
删除会移除持久卷及其关联存储。 |
Recycle |
此值已被弃用。您应使用其他值之一。 |
此命令检索命名为 db-pv 的持久卷的分配回收策略:
$ kubectl get pv db-pv -o jsonpath='{.spec.persistentVolumeReclaimPolicy}'
Retain
创建持久卷索赔
接下来我们需要创建的对象是持久卷索赔。其目的是将持久卷绑定到 Pod。让我们看一下存储在文件 db-pvc.yaml 中的 YAML 清单,如 示例 7-3 所示。
示例 7-3. 持久卷索赔的定义
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: db-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
resources:
requests:
storage: 256Mi
我们所说的是:“给我一个能够满足 256Mi 资源请求并提供 ReadWriteOnce 访问模式的 PersistentVolume。” 如果不希望自动分配默认存储类,静态配置应该使用空字符串作为 spec.storageClassName 属性的值。基于这些条件,自动选择适当的 PersistentVolume 进行绑定。
创建 PersistentVolumeClaim 后,状态设置为 Bound,这意味着与 PersistentVolume 的绑定成功。一旦关联绑定发生,其他任何东西都不能绑定它。绑定关系是一对一的。基于这些条件,使用简短形式 pvc 而不是 persistentvolumeclaim 的以下 get 命令:
$ kubectl create -f db-pvc.yaml
persistentvolumeclaim/db-pvc created
$ kubectl get pvc db-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
db-pvc Bound db-pv 1Gi RWO 111s
PersistentVolume 尚未被 Pod 挂载。因此,检查对象的详细信息显示 <none>。使用 describe 命令是验证 PersistentVolumeClaim 是否已正确挂载的好方法:
$ kubectl describe pvc db-pvc
...
Used By: <none>
...
在 Pod 中挂载 PersistentVolumeClaim
剩下的工作就是在要使用它的 Pod 中挂载 PersistentVolumeClaim。您已经学会了如何在 Pod 中挂载卷。这里的主要区别,在 示例 7-4 中展示,是使用 spec.volumes[].persistentVolumeClaim 并提供 PersistentVolumeClaim 的名称。
示例 7-4. 引用 PersistentVolumeClaim 的 Pod
apiVersion: v1
kind: Pod
metadata:
name: app-consuming-pvc
spec:
volumes:
- name: app-storage
persistentVolumeClaim:
claimName: db-pvc
containers:
- image: alpine:3.18.2
name: app
command: ["/bin/sh"]
args: ["-c", "while true; do sleep 60; done;"]
volumeMounts:
- mountPath: "/mnt/data"
name: app-storage
假设我们将配置存储在名为 app-consuming-pvc.yaml 的文件中。从清单创建 Pod 后,您应该看到 Pod 进入 Ready 状态。 describe 命令将提供有关卷的附加信息:
$ kubectl create -f app-consuming-pvc.yaml
pod/app-consuming-pvc created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
app-consuming-pvc 1/1 Running 0 3s
$ kubectl describe pod app-consuming-pvc
...
Volumes:
app-storage:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim \
in the same namespace)
ClaimName: db-pvc
ReadOnly: false
...
PersistentVolumeClaim 现在还显示已经挂载的 Pod:
$ kubectl describe pvc db-pvc
...
Used By: app-consuming-pvc
...
您现在可以继续并打开一个交互式 Shell 到 Pod。导航到挂载路径 /mnt/data 可以让您访问底层的 PersistentVolume:
$ kubectl exec app-consuming-pvc -it -- /bin/sh
/ # cd /mnt/data
/mnt/data # ls -l
total 0
/mnt/data # touch test.db
/mnt/data # ls -l
total 0
-rw-r--r-- 1 root root 0 Sep 29 23:59 test.db
存储类
存储类是 Kubernetes 的一个基本概念,定义了特定类型或“类”的存储。典型的存储特性可以是类型(例如,快速 SSD 存储与远程云存储或存储的备份策略)。存储类用于根据其条件动态地提供 PersistentVolume。实际上,这意味着您不必自己创建 PersistentVolume 对象。分配给存储类的提供程序会处理这一切。大多数 Kubernetes 云提供商都配备了一组现有的提供程序。Minikube 已经创建了一个名为 standard 的默认存储类,您可以使用以下命令查询它:
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY \
VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) k8s.io/minikube-hostpath Delete \
Immediate false 108d
创建存储类
存储类只能在有 YAML 清单的帮助下声明。至少需要声明供应商。如果在创建时未提供其他属性,则使用默认值。大多数供应商允许您设置特定于存储类型的参数。示例 7-5 定义了一个由供应商 kubernetes.io/gce-pd 表示的 Google Compute Engine 上的存储类。
示例 7-5. 存储类的定义
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
replication-type: regional-pd
如果您将 YAML 内容保存在文件 fast-sc.yaml 中,则以下命令将创建该对象。可以使用 get storageclass 命令列出存储类:
$ kubectl create -f fast-sc.yaml
storageclass.storage.k8s.io/fast created
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY \
VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
fast kubernetes.io/gce-pd Delete \
Immediate false 4s
...
使用存储类
动态配置 PersistentVolume 需要在创建 PersistentVolumeClaim 时分配存储类。示例 7-6 展示了使用属性 spec.storageClassName 分配名为 standard 的存储类的用法。
示例 7-6. 在 PersistentVolumeClaim 中使用存储类
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: db-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 512Mi
storageClassName: standard
只有在存储类能够使用其供应商动态提供合适的 PersistentVolume 时,才会创建相应的 PersistentVolume 对象。重要的是要理解,如果情况不是这样,Kubernetes 不会显示错误或警告消息。
以下命令呈现了创建的 PersistentVolumeClaim 和 PersistentVolume。正如您所见,动态配置的 PersistentVolume 的名称使用哈希确保唯一命名:
$ kubectl get pv,pvc
NAME CAPACITY \
ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS \
REASON AGE
persistentvolume/pvc-b820b919-f7f7-4c74-9212-ef259d421734 512Mi \
RWO Delete Bound default/db-pvc standard \
2s
NAME STATUS VOLUME \
CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/db-pvc Bound pvc-b820b919-f7f7-4c74-9212-ef259d421734 \
512Mi RWO standard 2s
从 Pod 挂载 PersistentVolumeClaim 的步骤与静态和动态配置相同。有关更多信息,请参阅“在 Pod 中挂载 PersistentVolumeClaim”。
摘要
Kubernetes 提供了 Volume 的概念来实现使用场景。Pod 将 Volume 挂载到容器中的路径。Kubernetes 提供了广泛的 Volume 类型以满足不同的需求。
PersistentVolumes 可以在 Pod 或集群/节点重新启动后存储数据。这些对象与 Pod 的生命周期分离,因此由 Kubernetes 原语表示。PersistentVolumeClaim 抽象了 PersistentVolume 的底层实现细节,并充当 Pod 和 PersistentVolume 之间的中介。
考试要点
实践定义和使用临时 Volume
Volume 是考试中应用于不同领域的横向概念。了解在哪里找到定义 Volume 的相关文档以及从容器中消耗 Volume 的多种方式。一定要阅读第十九章,深入了解如何将 ConfigMaps 和 Secrets 挂载为 Volume,以及第八章,了解在两个容器之间共享 Volume 的方式。
深入理解定义和使用 PersistentVolume 的机制
创建 PersistentVolume 涉及几个关键步骤。了解 PersistentVolumes 和 PersistentVolumeClaims 的配置选项及其如何协同工作。尝试模拟导致 PersistentVolumeClaim 成功绑定的情况。然后通过采取反应措施来修复情况。掌握pv和pvc的简短命令,以在考试中节省宝贵的时间。
示例练习
这些练习的解决方案可以在附录 A 中找到。
-
创建一个 Pod 的 YAML 清单,其中包含两个使用镜像
alpine:3.12.0的容器。为两个容器提供一个命令,以使它们永远运行。为 Pod 定义一个类型为
emptyDir的 Volume。容器 1 应将 Volume 挂载到路径/etc/a,容器 2 应将 Volume 挂载到路径/etc/b。打开容器 1 的交互式 shell,并在挂载路径中创建名为data的目录。导航到该目录并创建包含内容“Hello World.”的文件hello.txt。退出容器。
打开容器 2 的交互式 shell,并导航到目录/etc/b/data。检查文件hello.txt的内容。退出容器。
-
创建名为
logs-pv的 PersistentVolume,映射到hostPath为/var/logs。访问模式应为ReadWriteOnce和ReadOnlyMany。预配存储容量为 5Gi。确保 PersistentVolume 的状态显示为Available。创建名为
logs-pvc的 PersistentVolumeClaim。它使用ReadWriteOnce访问。请求容量为 2Gi。确保 PersistentVolume 的状态显示为Bound。在运行
nginx镜像的 Pod 中挂载 PersistentVolumeClaim,挂载路径为/var/log/nginx。打开容器的交互式 shell,并在/var/log/nginx中创建名为my-nginx.log的新文件。退出 Pod。
删除 Pod,并使用相同的 YAML 清单重新创建 Pod。打开 Pod 的交互式 shell,导航到目录/var/log/nginx,并找到之前创建的文件。
第八章:多容器 Pod
第五章 解释了如何管理单容器 Pod。这是正常情况,因为你希望在一个单独的 Pod 中运行一个微服务,以加强关注点的分离并增加内聚性。从技术上讲,一个 Pod 允许你配置和运行多个容器。
在本章中,我们将讨论多容器 Pod 的需求、相关的用例以及 Kubernetes 社区中出现的设计模式。考试大纲特别提到了几种主要的设计模式:初始化容器、旁车容器等。我们将通过代表性示例深入了解它们的应用。
在 Pod 中使用多个容器
特别是对于 Kubernetes 初学者来说,如何适当地设计一个 Pod 并不一定显而易见。如果你阅读 Kubernetes 用户文档和互联网上的教程,你很快就会发现,你可以创建一个同时运行多个容器的 Pod。那么问题来了:“我应该将我的微服务堆栈部署到一个具有多个容器的单个 Pod 中,还是应该创建多个每个运行一个微服务的 Pod?”简短的答案是每个 Pod 运行一个微服务。这种操作模式促进了分散化、解耦和分布式架构。此外,它有助于在不必中断系统其他部分的情况下推出微服务的新版本。
那么,在一个 Pod 中运行多个容器有什么意义呢?有两种常见的用例。有时,你希望通过执行设置脚本、命令或任何其他类型的预配置过程来初始化你的 Pod,这些逻辑在初始化容器中运行。其他时候,你可能希望提供与应用程序容器并行运行的辅助功能,以避免将逻辑嵌入到应用程序代码中。例如,你可能希望处理应用程序产生的日志输出。运行辅助逻辑的容器被称为旁车容器。
初始化容器
初始化容器 提供了在主应用程序启动之前运行的初始化逻辑关注点。打个比方,我们来看一下编程语言中的类似概念。许多编程语言,尤其是像 Java 或 C++ 这样的面向对象语言,都带有构造函数或静态方法块。这些语言构造用于初始化字段、验证数据,并在创建类之前设置舞台。并非所有类都需要构造函数,但它们都具备这种能力。
在 Kubernetes 中,这个功能可以通过 init 容器来实现。Init 容器总是在主应用容器之前启动,这意味着它们有自己的生命周期。为了拆分初始化逻辑,甚至可以将工作分配到多个按照清单定义顺序运行的 init 容器中。当然,初始化逻辑可能会失败。如果一个 init 容器产生错误,整个 Pod 将会重新启动,导致所有 init 容器按顺序再次运行。因此,为了防止任何副作用,使 init 容器逻辑幂等是一个好的实践。图 8-1 展示了一个带有两个 init 容器和主应用程序的 Pod。

图 8-1. Pod 中 init 容器的顺序和原子生命周期
在过去的几章中,我们探讨了如何在 Pod 中定义一个容器:您只需在 spec.containers 下指定其配置即可。对于 init 容器,Kubernetes 提供了一个单独的部分:spec.initContainers。Init 容器始终在主应用程序容器之前执行,无论清单中的定义顺序如何。
示例 8-1 中显示的清单定义了一个 init 容器和一个主应用程序容器。大部分情况下,init 容器使用与常规容器相同的属性。但有一个很大的区别。它们不能定义探针,这在 第十四章 中讨论过。init 容器在目录 /usr/shared/app 中设置了一个配置文件。通过 Volume 共享此目录,以便 Node.js 应用程序在主容器中运行时可以引用它。
示例 8-1. 定义一个带有 init 容器和主应用容器的 Pod
apiVersion: v1
kind: Pod
metadata:
name: business-app
spec:
initContainers:
- name: configurer
image: busybox:1.36.1
command: ['sh', '-c', 'echo Configuring application... && \
mkdir -p /usr/shared/app && echo -e "{\"dbConfig\": \
{\"host\":\"localhost\",\"port\":5432,\"dbName\":\"customers\"}}" \
> /usr/shared/app/config.json']
volumeMounts:
- name: configdir
mountPath: "/usr/shared/app"
containers:
- image: bmuschko/nodejs-read-config:1.0.0
name: web
ports:
- containerPort: 8080
volumeMounts:
- name: configdir
mountPath: "/usr/shared/app"
volumes:
- name: configdir
emptyDir: {}
启动 Pod 时,您会看到 get 命令的状态列也提供有关 init 容器的信息。前缀 Init: 表示正在执行 init 容器的过程。冒号后面的状态部分显示了已完成的 init 容器数量与配置的总 init 容器数量:
$ kubectl create -f init.yaml
pod/business-app created
$ kubectl get pod business-app
NAME READY STATUS RESTARTS AGE
business-app 0/1 Init:0/1 0 2s
$ kubectl get pod business-app
NAME READY STATUS RESTARTS AGE
business-app 1/1 Running 0 8s
在执行 init 容器期间可能会出现错误。如果顺序初始化链中任何容器失败,则整个 Pod 将无法启动。您可以始终通过使用 --container 命令行选项(或其简短形式 -c)来检索 init 容器的日志,如 图 8-2 所示。

图 8-2. 针对特定容器
下面的命令显示了 configurer init 容器的日志,这相当于我们在 YAML 清单中配置的 echo 命令:
$ kubectl logs business-app -c configurer
Configuring application...
旁车模式
初始化容器的生命周期如下:启动、运行逻辑,然后一旦完成工作就终止。初始化容器不打算长时间运行。但有些场景需要不同的使用模式。例如,您可能希望创建一个 Pod,在其中持续运行多个容器。
Kubernetes 1.29 引入了边车容器。
未来使用 Kubernetes 1.29 或更高版本的考试可能会涵盖正式化的 边车容器。边车容器是辅助容器,它们将与 Pod 一起启动并在整个 Pod 的生命周期内保持运行。
通常,容器分为两种不同的类别:运行应用程序的容器和为主应用程序提供辅助功能的另一个容器。在 Kubernetes 中,提供辅助功能的容器称为边车。边车容器最常用的功能包括文件同步、日志记录和监视功能。边车通常不是主应用程序的主要流量或 API 的一部分。它们通常是异步操作的,不涉及公共 API。
为了说明边车的行为,考虑以下用例。主应用容器运行一个 Web 服务器——在本例中是 NGINX。一旦启动,Web 服务器会生成两个标准日志文件。文件 /var/log/nginx/access.log 记录对 Web 服务器端点的请求。另一个文件 /var/log/nginx/error.log 则记录处理传入请求时的失败。
作为 Pod 功能的一部分,我们希望实现一个监控服务。边车容器定期轮询文件的 error.log,检查是否发现任何故障。更具体地说,该服务试图查找日志文件中标有 [error] 的故障。如果发现错误,监控服务将对其作出反应。例如,它可以向系统管理员发送通知。我们希望保持功能尽可能简单。监控服务只会将错误消息输出到标准输出。主应用容器和边车容器之间的文件交换通过一个 Volume 实现,如 图 8-3 所示。

图 8-3. 边车模式的实际运行情况
在 示例 8-2 中显示的 YAML 文件设置了描述的场景。代码的最棘手部分是长长的 bash 命令。该命令运行一个无限循环。在每次迭代中,我们检查 error.log 文件的内容,使用 grep 查找错误并可能采取行动。循环每 10 秒执行一次。
示例 8-2. 典型的边车模式实现
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- name: nginx
image: nginx:1.25.1
volumeMounts:
- name: logs-vol
mountPath: /var/log/nginx
- name: sidecar
image: busybox:1.36.1
command: ["sh","-c","while true; do if [ \"$(cat /var/log/nginx/error.log \
| grep 'error')\" != \"\" ]; then echo 'Error discovered!'; fi; \
sleep 10; done"]
volumeMounts:
- name: logs-vol
mountPath: /var/log/nginx
volumes:
- name: logs-vol
emptyDir: {}
在启动 Pod 时,您会注意到总容器数将显示为 2. 在所有容器都可以启动之后,Pod 会发出 Running 状态的信号:
$ kubectl create -f sidecar.yaml
pod/webserver created
$ kubectl get pods webserver
NAME READY STATUS RESTARTS AGE
webserver 0/2 ContainerCreating 0 4s
$ kubectl get pods webserver
NAME READY STATUS RESTARTS AGE
webserver 2/2 Running 0 5s
您会发现 error.log 最初并不包含任何故障信息。它开始为空文件。通过以下命令,您可以故意引发错误。等待至少 10 秒后,您将在终端上找到预期的消息,您可以使用 logs 命令查询该消息:
$ kubectl logs webserver -c sidecar
$ kubectl exec webserver -it -c sidecar -- /bin/sh
/ # wget -O- localhost?unknown
Connecting to localhost (127.0.0.1:80)
wget: server returned error: HTTP/1.1 404 Not Found
/ # cat /var/log/nginx/error.log
2020/07/18 17:26:46 [error] 29#29: *2 open() "/usr/share/nginx/html/unknown" \
failed (2: No such file or directory), client: 127.0.0.1, server: localhost, \
request: "GET /unknown HTTP/1.1", host: "localhost"
/ # exit
$ kubectl logs webserver -c sidecar
Error discovered!
适配器模式
作为应用程序开发者,我们希望专注于实现业务逻辑。例如,在一个为期两周的迭代中,我们被要求添加购物车功能。除了功能需求外,我们还需要考虑暴露管理端点或者制定有意义且正确格式的日志输出等操作方面的内容。很容易陷入将所有方面都包含在应用程序代码中的习惯中,这使得代码更加复杂和难以维护。特别是横切关注点需要在多个应用程序之间复制,并且经常从一个代码库复制粘贴到另一个。
在 Kubernetes 中,我们可以通过在主应用容器之外的另一个容器中运行它们来避免将横切关注点捆绑到应用程序代码中。适配器模式将应用程序产生的输出转换为另一部分系统所需的格式,以使其可消费。图 8-4 展示了适配器模式的一个具体示例。

图 8-4. 适配器模式的实际应用
运行主容器的业务应用程序生成带时间戳的信息,例如可用磁盘空间,并将其写入文件 diskspace.txt。作为架构的一部分,我们希望从第三方监控应用程序消费该文件。问题在于外部应用程序要求的信息需要排除时间戳。当然,我们可以更改日志格式以避免写入时间戳,但如果我们确实想知道何时写入日志条目呢?这就是适配器模式可以帮助的地方。一个 sidecar 容器执行转换逻辑,将日志条目转换为外部系统所需的格式,而无需更改应用程序逻辑。
YAML 清单中的 示例 8-3 展示了适配器模式的实现可能看起来如何。app 容器每五秒生成一个新的日志条目。transformer 容器消耗文件内容,移除时间戳,并将其写入新文件。通过一个 Volume,这两个容器都可以访问相同的挂载路径。
示例 8-3. 一个示例适配器模式的实现
apiVersion: v1
kind: Pod
metadata:
name: adapter
spec:
containers:
- args:
- /bin/sh
- -c
- 'while true; do echo "$(date) | $(du -sh ~)" >> /var/logs/diskspace.txt; \
sleep 5; done;'
image: busybox:1.36.1
name: app
volumeMounts:
- name: config-volume
mountPath: /var/logs
- image: busybox:1.36.1
name: transformer
args:
- /bin/sh
- -c
- 'sleep 20; while true; do while read LINE; do echo "$LINE" | cut -f2 -d"|" \
>> $(date +%Y-%m-%d-%H-%M-%S)-transformed.txt; done < \
/var/logs/diskspace.txt; sleep 20; done;'
volumeMounts:
- name: config-volume
mountPath: /var/logs
volumes:
- name: config-volume
emptyDir: {}
在创建 Pod 后,我们将找到两个正在运行的容器。在进入 transformer 容器后,我们应该能够找到原始文件 /var/logs/diskspace.txt。转换后的数据存在用户主目录中的另一个文件中:
$ kubectl create -f adapter.yaml
pod/adapter created
$ kubectl get pods adapter
NAME READY STATUS RESTARTS AGE
adapter 2/2 Running 0 10s
$ kubectl exec adapter --container=transformer -it -- /bin/sh
/ # cat /var/logs/diskspace.txt
Sun Jul 19 20:28:07 UTC 2020 | 4.0K /root
Sun Jul 19 20:28:12 UTC 2020 | 4.0K /root
/ # ls -l
total 40
-rw-r--r-- 1 root root 60 Jul 19 20:28 2020-07-19-20-28-28-transformed.txt
...
/ # cat 2020-07-19-20-28-28-transformed.txt
4.0K /root
4.0K /root
大使模式
CKAD 还涵盖了另一个重要的设计模式,即大使模式。大使模式为与外部服务通信提供了一个代理。
许多用例可以证明引入大使模式的合理性。总体目标是隐藏和/或抽象与系统其他部分交互的复杂性。典型的责任包括在请求失败时的重试逻辑、安全问题(如提供认证或授权)以及监控延迟或资源使用情况。图 8-5 展示了这种模式。

图 8-5. 大使模式的实际应用
在这个例子中,我们希望为对外部服务的 HTTP(S) 调用实现速率限制功能。例如,速率限制器的要求可能是每 15 分钟最多允许应用程序进行 5 次调用。我们不会将速率限制逻辑紧密耦合到应用程序代码中,而是由大使容器提供。从业务应用程序发出的任何调用都必须通过大使容器进行。示例 8-4 展示了一个基于 Node.js 的速率限制器实现,它向外部服务 Postman 发起调用。
示例 8-4. Node.js HTTP 速率限制器实现
const express = require('express');
const app = express();
const rateLimit = require('express-rate-limit');
const https = require('https');
const rateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message:
'Too many requests have been made from this IP, please try again after an hour'
});
app.get('/test', rateLimiter, function (req, res) {
console.log('Received request...');
var id = req.query.id;
var url = 'https://postman-echo.com/get?test=' + id;
console.log("Calling URL %s", url);
https.get(url, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
res.send(data);
});
}).on("error", (err) => {
res.send(err.message);
});
})
var server = app.listen(8081, function () {
var port = server.address().port
console.log("Ambassador listening on port %s...", port)
})
在 示例 8-5 中展示的对应的 Pod 在不同端口上运行主应用容器和大使容器。对容器 business-app 的每次 HTTP 端点调用都会委派给容器 ambassador 的 HTTP 端点。需要指出的是,同一 Pod 内运行的容器可以通过 localhost 进行通信。不需要额外的网络配置。
示例 8-5. 一个优秀的大使模式实现
apiVersion: v1
kind: Pod
metadata:
name: rate-limiter
spec:
containers:
- name: business-app
image: bmuschko/nodejs-business-app:1.0.0
ports:
- containerPort: 8080
- name: ambassador
image: bmuschko/nodejs-ambassador:1.0.0
ports:
- containerPort: 8081
让我们来测试功能。首先,我们将创建 Pod,进入运行业务应用程序的容器,并执行一系列 curl 命令。前五次调用将被允许访问外部服务。在第六次调用时,由于在给定时间段内达到了速率限制,我们将收到一个错误消息:
$ kubectl create -f ambassador.yaml
pod/rate-limiter created
$ kubectl get pods rate-limiter
NAME READY STATUS RESTARTS AGE
rate-limiter 2/2 Running 0 5s
$ kubectl exec rate-limiter -it -c business-app -- /bin/sh
# curl localhost:8080/test
{"args":{"test":"123"},"headers":{"x-forwarded-proto":"https", \
"x-forwarded-port":"443","host":"postman-echo.com", \
"x-amzn-trace-id":"Root=1-5f177dba-e736991e882d12fcffd23f34"}, \
"url":"https://postman-echo.com/get?test=123"}
...
# curl localhost:8080/test
Too many requests have been made from this IP, please try again after an hour
总结
真实世界的场景要求在一个 Pod 内部运行多个容器。通过一个初始化容器执行初始化逻辑,可以为主应用容器设置舞台。一旦初始化逻辑完成,该容器将被终止。只有初始化容器顺利运行完其功能后,主应用容器才会启动。
涉及多个容器的其他设计模式包括适配器模式和大使模式。适配器模式有助于“转换”应用程序产生的数据,使其能够被第三方服务使用。大使模式在与外部服务通信时充当应用容器的代理,通过抽象“如何”来实现。
考试要点
理解在 Pod 中运行多个容器的必要性。
Pod 可以运行多个容器。你需要了解初始化容器和旁路容器之间的区别及其各自的生命周期。练习在多容器 Pod 中使用命令行选项--container或-c访问特定容器。
知道如何创建一个初始化容器。
初始化容器在企业 Kubernetes 集群环境中有很高的使用率。理解在各自场景中使用它们的必要性。练习定义一个具有一个或多个初始化容器的 Pod,并在创建 Pod 时观察它们的线性执行。体验在初始化容器发生故障时 Pod 的行为是很重要的。
理解多容器设计模式及其实现方法。
通过实现一个已建立模式的场景,最好理解多容器 Pod。根据你学到的知识,提出你自己的适用案例并创建一个多容器 Pod 来解决它。能够识别旁路模式,并理解它们在实践中为何重要以及如何自己搭建它们是很有帮助的。当你实现自己的旁路容器时,你可能会发现需要复习一下 bash 的知识。
示例练习
这些练习的解决方案可以在附录 A 中找到。
-
为名为
complex-pod的 Pod 创建一个 YAML 清单。主应用容器名为app,使用镜像nginx:1.25.1并暴露容器端口 80. 修改 YAML 清单,使得 Pod 定义一个名为setup的初始化容器,使用镜像busybox:1.36.1。初始化容器运行命令wget -O- google.com。从 YAML 清单创建 Pod。
下载初始化容器的日志。你应该看到
wget命令的输出。打开主应用容器的交互式 Shell,并运行
ls命令。退出容器。强制删除 Pod。
-
为名为
data-exchange的 Pod 创建一个 YAML 清单。主应用容器名为main-app,使用镜像busybox:1.36.1。容器运行的命令是每 30 秒在目录/var/app/data中无限循环写入一个新文件,文件名遵循{counter++}-data.txt的模式。变量 counter 在每个间隔中递增,初始值为 1。修改 YAML 配置文件,添加名为
sidecar的辅助容器。该辅助容器使用镜像busybox:1.36.1,并且在无限循环中每 60 秒统计main-app容器生成的文件数量。该命令将文件数量输出到标准输出。定义一个类型为
emptyDir的卷。为两个容器挂载路径 /var/app/data。创建 Pod。查看 sidecar 容器的日志。
删除 Pod。
第九章:标签和注释
考试课程大纲并未明确提到标签的概念;但是,它对于理解某些 Kubernetes 原语在内部如何运行至关重要。为了避免将标签与注释混淆,我们还将讨论这些概念之间的共同点和区别。
标签是查询、过滤和排序 Kubernetes 对象的重要工具。注释代表 Kubernetes 对象的描述性元数据,但不能用于查询。在本章中,您将学习如何分配和使用这两个概念。
处理标签
Kubernetes 允许您为对象分配键值对,以便稍后在搜索查询中使用它们。这些键值对称为标签。为了类比,您可以将标签视为博客文章的标签。
标签以不同的术语描述 Kubernetes 对象(例如“frontend”或“backend”类别),但并不意味着它们用于功能的详细、多词描述。作为规范的一部分,Kubernetes 将标签的长度限制为最多 63 个字符,并允许使用字母数字字符和分隔符字符的范围。
图 9-1 显示了名为frontend、backend和database的 Pod。每个 Pod 声明了一组独特的标签。

图 9-1. 带有标签的 Pod
在创建对象时,通常会分配一个或多个标签;但是,您可以根据需要修改活动对象的标签。当您第一次看到标签时,它们可能看似微不足道,但它们的重要性不可忽视。它们对于理解像部署和服务这样的更高级 Kubernetes 对象的运行时行为至关重要。在本章的后面,我们将详细了解标签在实践中的重要性,特别是在学习部署更多细节时。
声明标签
您可以使用run命令或在 YAML 清单的metadata.labels部分声明性地声明标签。在创建 Pod 时,命令行选项--labels(或其简写形式-l)在命令行上定义了一个逗号分隔的标签列表。以下命令在命令行上创建一个带有两个标签的新 Pod:
$ kubectl run labeled-pod --image=nginx:1.25.1 \
--labels=tier=backend,env=dev
pod/labeled-pod created
通过编辑清单将标签分配给 Kubernetes 对象需要更改metadata部分。如果我们从 YAML 清单开始,示例 9-1 显示了上一个命令的 Pod 定义。
示例 9-1. 定义了两个标签的 Pod
apiVersion: v1
kind: Pod
metadata:
name: labeled-pod
labels:
env: dev
tier: backend
spec:
containers:
- image: nginx:1.25.1
name: nginx
检查标签
您可以从不同角度检查分配给 Kubernetes 对象的标签。在这里,我们将讨论查看 Pod 标签的最常见方法。与任何其他运行时信息一样,您可以使用describe或get命令检索标签:
$ kubectl describe pod labeled-pod | grep -C 2 Labels:
...
Labels: env=dev
tier=backend
...
$ kubectl get pod labeled-pod -o yaml | grep -C 1 labels:
metadata:
labels:
env: dev
tier: backend
...
如果您想要列出所有对象类型或特定对象类型的标签,请使用--show-labels命令行选项。如果您需要筛选更长的对象列表,此选项非常方便。输出会自动添加一个名为LABELS的新列:
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
labeled-pod 1/1 Running 0 38m env=dev,tier=backend
对活动对象进行标签修改
您可以随时向现有的 Kubernetes 对象添加或删除标签,或者简单地修改现有的标签。实现这一目标的一种方法是编辑活动对象,并在metadata.labels部分更改标签定义。另一个选项,提供稍快的周转时间,是label命令。以下命令添加一个新标签,更改标签的值,然后用减号移除标签:
$ kubectl label pod labeled-pod region=eu
pod/labeled-pod labeled
$ kubectl get pod labeled-pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
labeled-pod 1/1 Running 0 22h env=dev,region=eu,tier=backend
$ kubectl label pod labeled-pod region=us --overwrite
pod/labeled-pod labeled
$ kubectl get pod labeled-pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
labeled-pod 1/1 Running 0 22h env=dev,region=us,tier=backend
$ kubectl label pod labeled-pod region-
pod/labeled-pod labeled
$ kubectl get pod labeled-pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
labeled-pod 1/1 Running 0 22h env=dev,tier=backend
使用标签选择器
仅当与选择功能结合使用时,标签才变得有意义。标签选择器使用一组条件查询 Kubernetes 对象。例如,您可以使用标签选择器表达“选择所有具有标签赋值env=dev、tier=frontend且具有具有键version的标签(其分配值独立于指定的值)的 Pod”,如图 9-2 所示。

图 9-2。按标签条件选择 Pod 的操作
Kubernetes 提供两种通过标签选择对象的方法:从命令行和在清单中。让我们讨论这两个选项。
命令行中的标签选择
在命令行上,您可以使用--selector选项(简写为-l)按标签选择对象。您可以通过提供基于相等性的要求或基于集合的要求来表达过滤。两种要求类型可以在单个查询中组合使用。
基于相等性的要求可以使用运算符=、==或!=。您可以用逗号分隔多个过滤条件,然后用布尔AND组合它们。此时,基于相等性的标签选择不能表示布尔OR操作。一个典型的表达式可以说:“选择所有具有标签赋值env=prod的 Pod。”
基于集合的要求可以根据一组值来过滤对象,使用运算符in、notin和exists。in和notin运算符基于布尔OR工作。一个典型的表达式可以说:“选择所有具有标签键env且值为prod或dev的 Pod。”
为了演示功能,我们将设置三个具有不同标签的 Pod。所有kubectl命令都使用--show-labels命令行选项,以便与我们的期望进行比较。不需要--show-labels选项来进行标签选择:
$ kubectl run frontend --image=nginx:1.25.1 --labels=env=prod,team=shiny
pod/frontend created
$ kubectl run backend --image=nginx:1.25.1 --labels=env=prod,team=legacy,\
app=v1.2.4
pod/backend created
$ kubectl run database --image=nginx:1.25.1 --labels=env=prod,team=storage
pod/database created
$ kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
backend 1/1 Running 0 37s app=v1.2.4,env=prod,team=legacy
database 1/1 Running 0 32s env=prod,team=storage
frontend 1/1 Running 0 42s env=prod,team=shiny
我们将从基于相等性的要求开始过滤 Pod。在这里,我们正在寻找所有具有标签赋值env=prod的 Pod。结果返回了所有三个 Pod:
$ kubectl get pods -l env=prod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
backend 1/1 Running 0 37s app=v1.2.4,env=prod,team=legacy
database 1/1 Running 0 32s env=prod,team=storage
frontend 1/1 Running 0 42s env=prod,team=shiny
下一个过滤操作使用基于集合的要求。我们正在寻找所有具有标签键team且值为storage或shiny的 Pod。结果仅返回名称为backend和frontend的 Pod:
$ kubectl get pods -l 'team in (shiny, legacy)' --show-labels
NAME READY STATUS RESTARTS AGE LABELS
backend 1/1 Running 0 19m app=v1.2.4,env=prod,team=legacy
frontend 1/1 Running 0 20m env=prod,team=shiny
最后,我们将结合基于相等性的需求与基于集合的需求。结果仅返回backend Pod:
$ kubectl get pods -l 'team in (shiny, legacy)',app=v1.2.4 --show-labels
NAME READY STATUS RESTARTS AGE LABELS
backend 1/1 Running 0 29m app=v1.2.4,env=prod,team=legacy
在清单中进行标签选择
一些高级 Kubernetes 对象(例如 Deployments、Services 或网络策略)充当 Pod 的配置代理。它们通常通过标签选择一组 Pod,并提供附加值。例如,网络策略控制与一组 Pod 之间和之间的网络流量。仅具有匹配标签的 Pod 将应用网络规则。示例 9-2 将网络策略应用于具有基于相等性的需求tier=frontend的 Pod。有关网络策略的更多详细信息,请参阅第二十三章。
示例 9-2. 作为网络策略 API 的一部分的标签选择
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-network-policy
spec:
podSelector:
matchLabels:
tier: frontend
...
在清单中定义标签选择的方式基于 Kubernetes 资源的 API 版本,不同类型可能有所不同。后续章节中的内容将大量使用标签选择。
推荐的标签
在继续使用标签时,您可能会发现希望为对象分配的常见键值对。其中许多标签围绕应用程序的元数据,例如,您正在部署的 Pod 的组件名称或其版本。
Kubernetes 提议一组推荐标签,所有这些标签都以键前缀app.kubernetes.io开头。示例 9-3 显示了在 Pod 定义中分配版本和组件标签。
示例 9-3. 使用推荐标签的 Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/version: "1.25.1"
app.kubernetes.io/component: server
spec:
containers:
- name: nginx
image: nginx:1.25.1
熟悉这些推荐标签,以便您可以在管理的所有对象中使用它们。这样做的好处是使得 Kubernetes 生态系统中的工具能够使用相同的术语,并使开发人员在引用应用程序元信息时使用相同的“语言”。
使用注解
注解的声明方式与标签类似,但它们有不同的用途。它们代表提供描述性元数据的键值对。最重要的区别是注解不能用于查询或选择对象。注解的典型示例可能包括 SCM 提交哈希 ID、发布信息或操作对象的团队的联系方式。如果注解的值包含特殊字符或空格,请确保将其放入单引号或双引号中。图 9-3 说明了一个具有三个注解的 Pod。

图 9-3. 带有注解的 Pod
Kubernetes 定义了一组保留的注解,在运行时将评估这些注解以控制对象的运行时行为。您可以在“保留注解”找到更多信息。
声明注解
kubectl run 命令没有像标签那样用于定义注释的命令行选项。您需要从头开始编写 YAML 清单,并在 metadata.annotations 下添加所需的注释,如示例 9-4 所示。
示例 9-4. 定义三个注释的 Pod
apiVersion: v1
kind: Pod
metadata:
name: annotated-pod
annotations:
commit: 866a8dc
author: 'Benjamin Muschko'
branch: 'bm/bugfix'
spec:
containers:
- image: nginx:1.25.1
name: nginx
检查注释
类似于标签,您可以使用 describe 或 get 命令来检索分配的注释:
$ kubectl describe pod annotated-pod | grep -C 2 Annotations:
...
Annotations: author: Benjamin Muschko
branch: bm/bugfix
commit: 866a8dc
...
$ kubectl get pod annotated-pod -o yaml | grep -C 3 annotations:
metadata:
annotations:
author: Benjamin Muschko
branch: bm/bugfix
commit: 866a8dc
...
修改活动对象的注释
annotate 命令是 labels 命令的对应物,但用于注释。正如下面的示例所示,使用模式相同:
$ kubectl annotate pod annotated-pod oncall='800-555-1212'
pod/annotated-pod annotated
$ kubectl annotate pod annotated-pod oncall='800-555-2000' --overwrite
pod/annotated-pod annotated
$ kubectl annotate pod annotated-pod oncall-
pod/annotated-pod annotated
保留注释
Kubernetes 本身及其 Kubernetes 的扩展使用注释的概念来配置对象的运行时行为。例如,您可以将保留注释 pod-security.kubernetes.io/enforce: "baseline" 分配给命名空间对象以强制执行安全标准,该命名空间中所有 Pod 均适用。示例 9-5 展示了分配该注释的命名空间定义。
示例 9-5. 使用保留注释的 Pod
apiVersion: v1
kind: Namespace
metadata:
name: secured
annotations:
pod-security.kubernetes.io/enforce: "baseline"
查看Kubernetes 文档,了解 Kubernetes 中保留的注释的完整列表。
摘要
标签是控制更高级 Kubernetes 对象运行时行为的核心概念。例如,在 Deployment 的上下文中,Kubernetes 要求您使用标签选择来选择Deployment 管理的 Pod。您可以使用标签来基于命令行查询或者在支持原语 API 的清单中选择对象。Kubernetes 建议为常用应用程序元数据使用标签键。
注释具有不同的目的;它们提供人类可读的信息性元数据。您不能使用注释来查询对象。Kubernetes 引入了保留注释作为一种标记对象以进行特殊运行时处理的方法。
考试要点
练习标签声明和选择
标签在 Kubernetes 中是一个极其重要的概念,因为许多其他原语都使用标签选择。练习如何为不同对象声明标签,并使用 -l 命令行选项基于基于相等性和集合的要求查询它们。在 YAML 清单中,基于 API 规范的标签选择可能会稍有不同。广泛练习使用对标签选择的原语。
理解标签和注释之间的区别。
关于注释,您只需了解如何从命令行和 YAML 清单中声明它们。请注意,注释仅用于为对象分配元数据,并且无法进行查询。
样例练习
这些练习的解决方案可以在附录 A 中找到。
-
创建三个使用镜像
nginx:1.25.1的 Pod。Pod 的名称应为pod-1、pod-2和pod-3。将标签
tier=frontend分配给pod-1,将标签tier=backend分配给pod-2和pod-3。所有 Pod 还应分配标签team=artemidis。将键为
deployer的注释分配给pod-1和pod-3。将你自己的名字用作值。从命令行使用标签选择来查找所有属于团队
artemidis或aircontrol的后端服务的 Pod。 -
创建一个使用镜像
nginx:1.25.1的 Pod,并分配两个推荐的标签:一个用于定义应用程序名称,其值为F5-nginx,另一个用于定义用于管理名为helm的应用程序的工具。渲染 Pod 对象的分配标签。
第十章:部署
Kubernetes 的一个重要卖点是其可伸缩性和复制功能。为支持这些功能,Kubernetes 提供了部署原语。在本章中,我们将展示如何创建部署以多个副本进行扩展,如何推出应用程序的修订版本,如何回滚到以前的修订版本,以及如何使用自动扩展器根据当前工作负载自动处理扩展问题。
处理部署
在容器中运行应用程序的基本单位是 Pod。使用单个 Pod 实例来运行应用程序存在缺陷——它代表了一个单点故障,因为所有针对应用程序的流量都会汇集到这个 Pod。当负载增加时,这种行为特别有问题(例如,在电子商务应用程序的高峰购物季节期间,或者当越来越多的微服务与集中的微服务功能(例如认证提供程序)进行通信时)。
在 Pod 中运行应用程序的另一个重要方面是容错性。调度器集群组件不会在节点故障的情况下重新调度 Pod,这可能会导致终端用户的系统中断。在本章中,我们将讨论支持应用程序可伸缩性和容错性的 Kubernetes 机制。
复本集 是 Kubernetes API 资源,控制运行应用程序的多个相同 Pod 实例,即所谓的副本。它具有根据需求增加或减少副本数量的能力。此外,它还知道如何在所有副本中推出应用程序的新版本。
部署 抽象了复本集的功能并在内部进行管理。在实践中,这意味着您不必自己创建、修改或删除复本集对象。部署保留应用程序版本的历史记录,并可以回滚到旧版本以应对生产问题的阻塞或潜在成本问题。此外,它还能够扩展副本的数量。
图 10-1 描述了部署、复本集及其控制副本之间的关系。

图 10-1. 部署和复本集之间的关系
接下来的几节将解释如何管理部署,包括扩展和推出功能。
创建部署
您可以使用命令 create deployment 创建部署。该命令提供一系列选项,其中一些是强制性的。至少,您需要提供部署的名称和容器镜像。部署将此信息传递给复本集,复本集使用它来管理副本。创建的默认副本数量为 1;但是,您可以使用选项 --replicas 定义更高数量的副本。
让我们观察该命令的执行。以下命令创建名为app-cache的 Deployment,在其中运行容器内的对象缓存Memcached的四个副本:
$ kubectl create deployment app-cache --image=memcached:1.6.8 --replicas=4
deployment.apps/app-cache created
Deployment 与其控制的副本之间的映射通过标签选择发生。当您运行命令时,kubectl会为您设置映射关系。示例 10-1 显示了 YAML 清单中的标签选择。该 YAML 清单可用于声明性地创建部署或通过检查由先前命令创建的实时对象来使用。
示例 10-1. Deployment 的 YAML 清单
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-cache
labels:
app: app-cache
spec:
replicas: 4
selector:
matchLabels:
app: app-cache
template:
metadata:
labels:
app: app-cache
spec:
containers:
- name: memcached
image: memcached:1.6.8
在由命令创建时,app是 Deployment 默认使用的标签键。您可以在 YAML 输出的三个不同位置找到此键:
-
metadata.labels -
spec.selector.matchLabels -
spec.template.metadata.labels
为了使标签选择正常工作,spec.selector.matchLabels和spec.template.metadata的分配需要匹配,如图 10-2 所示。
metadata.labels的值对于将 Deployment 映射到 Pod 模板是不相关的。如图所示,将metadata.labels的标签分配更改为deploy: app-cache是有意的,以突出显示对 Deployment 到 Pod 模板选择的重要性。

图 10-2. Deployment 标签选择
列出 Deployments 及其 Pods
您可以通过使用get deployments命令检查部署后的部署。命令的输出显示了其副本的重要细节,如下所示:
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
app-cache 4/4 4 4 125m
与由 Deployment 控制的副本相关的列标题显示在表格 10-1 中。
表 10-1. 列出部署时的运行时副本信息
| Column Title | Description |
|---|---|
| READY | 以spec.replicas的值。 |
| UP-TO-DATE | 列出已更新以实现期望状态的副本数。 |
| AVAILABLE | 列出最终用户可用的副本数。 |
您可以通过它们的命名前缀识别由 Deployment 控制的 Pods。在先前创建的 Deployment 中,Pods 的名称以app-cache-开头。在创建时,生成的哈希会自动附加到名称后面:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
app-cache-596bc5586d-84dkv 1/1 Running 0 6h5m
app-cache-596bc5586d-8bzfs 1/1 Running 0 6h5m
app-cache-596bc5586d-rc257 1/1 Running 0 6h5m
app-cache-596bc5586d-tvm4d 1/1 Running 0 6h5m
渲染 Deployment 详细信息
您可以渲染 Deployment 的详细信息。这些详细信息包括标签选择标准,在排除部署配置错误时可能非常有价值。以下输出提供了完整的要点:
$ kubectl describe deployment app-cache
Name: app-cache
Namespace: default
CreationTimestamp: Sat, 07 Aug 2021 09:44:18 -0600
Labels: app=app-cache
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=app-cache
Replicas: 4 desired | 4 updated | 4 total | 4 available | \
0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=app-cache
Containers:
memcached:
Image: memcached:1.6.10
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Progressing True NewReplicaSetAvailable
Available True MinimumReplicasAvailable
OldReplicaSets: <none>
NewReplicaSet: app-cache-596bc5586d (4/4 replicas created)
Events: <none>
您可能已经注意到输出中包含对 ReplicaSet 的引用。ReplicaSet 的目的是复制一组相同的 Pod。您无需深入了解 ReplicaSet 的核心功能即可参加考试。只需知道部署会自动创建 ReplicaSet,并使用部署的名称作为 ReplicaSet 的前缀,类似于它控制的 Pods。对于名为 app-cache 的前一个部署,ReplicaSet 的名称为 app-cache-596bc5586d。
删除部署
部署完全负责创建和删除它控制的对象:Pod 和 ReplicaSet。当您删除一个部署时,相应的对象也会被删除。假设您正在处理输出中显示的以下一组对象:
$ kubectl get deployments,pods,replicasets
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/app-cache 4/4 4 4 6h47m
NAME READY STATUS RESTARTS AGE
pod/app-cache-596bc5586d-84dkv 1/1 Running 0 6h47m
pod/app-cache-596bc5586d-8bzfs 1/1 Running 0 6h47m
pod/app-cache-596bc5586d-rc257 1/1 Running 0 6h47m
pod/app-cache-596bc5586d-tvm4d 1/1 Running 0 6h47m
NAME DESIRED CURRENT READY AGE
replicaset.apps/app-cache-596bc5586d 4 4 4 6h47m
运行 delete deployment 命令以级联删除其管理的对象:
$ kubectl delete deployment app-cache
deployment.apps "app-cache" deleted
$ kubectl get deployments,pods,replicasets
No resources found in default namespace.
执行滚动更新和回滚
部署通过委托给它管理的 ReplicaSet(s) 来完全抽象滚动更新和回滚功能。一旦用户更改了部署中 Pod 模板的定义,它将创建一个新的 ReplicaSet,将更改应用于它控制的副本,然后关闭之前的 ReplicaSet。在这一部分,我们将讨论两种情况:部署应用程序的新版本和回滚到旧版本的应用程序。
更新部署的 Pod 模板
您可以从多种选项中选择,以更新由部署控制的副本的定义。这些选项都是有效的,但在使用和操作环境上有所不同。
在实际项目中,您应该将清单文件检入版本控制。然后,可以通过直接编辑文件来进行定义更改。kubectl apply 可以通过指向更改后的清单更新 live 对象:
$ kubectl apply -f deployment.yaml
命令 kubectl edit 允许您通过在编辑器中修改 live 对象的清单来交互式地更改 Pod 模板。要编辑名为 web-server 的部署 live 对象,请使用以下命令:
$ kubectl edit deployment web-server
命令 kubectl set image 只更改分配给 Pod 模板的容器映像,选择容器的名称。例如,您可以使用此命令将映像 nginx:1.25.2 分配给名为 web-server 的部署中名为 nginx 的容器:
$ kubectl set image deployment web-server nginx=nginx:1.25.2
kubectl replace 命令允许您使用包含对清单更改的新定义替换现有的部署。可选的 --force 标志首先删除现有对象,然后从头开始创建。以下命令假定您在 deployment.yaml 中更改了容器映像分配:
$ kubectl replace -f deployment.yaml
命令 kubectl patch 要求您以补丁的形式提供合并,以更新部署。以下命令展示了操作的过程。在这里,您以 JSON 结构的形式发送要进行的更改:
$ kubectl patch deployment web-server -p '{"spec":{"template":{"spec":\
{"containers":[{"name":"nginx","image":"nginx:1.25.2"}]}}}}'
推出新版本
Deployment 使得向其控制的所有副本部署新版本的应用程序变得容易。假设您希望将 Memcached 的版本从 1.6.8 升级到 1.6.10 以获得最新功能和错误修复。您只需通过更新 Pod 模板更改对象的期望状态。Deployment 逐个将所有副本更新到新版本。这个过程称为 滚动更新 策略。
命令 set image 提供了一个快速、方便的方法来更改 Deployment 的图像,如下所示的命令:
$ kubectl set image deployment app-cache memcached=memcached:1.6.10
deployment.apps/app-cache image updated
您可以使用命令 rollout status 检查正在进行中的滚动更新的当前状态。输出指示自发出命令以来已更新的副本数量:
$ kubectl rollout status deployment app-cache
Waiting for rollout to finish: 2 out of 4 new replicas have been updated...
deployment "app-cache" successfully rolled out
Kubernetes 会在滚动历史中跟踪您对 Deployment 所做的更改。每个更改由一个 修订版 表示。当更改 Deployment 的 Pod 模板(例如通过更新图像)时,Deployment 会触发新的 ReplicaSet 的创建。Deployment 将逐步将 Pods 从旧 ReplicaSet 迁移到新 ReplicaSet。您可以通过运行以下命令来检查滚动历史记录。您将看到列出的两个修订版:
$ kubectl rollout history deployment app-cache
deployment.apps/app-cache
REVISION CHANGE-CAUSE
1 <none>
2 <none>
第一个修订版记录了创建对象时 Deployment 的原始状态。第二个修订版是为更改图像标签而添加的。
注意
默认情况下,Deployment 在其历史记录中持续保留最多 10 个修订版。您可以通过为 spec.revisionHistoryLimit 分配不同的值来更改此限制。
要获取修订版的更详细视图,请运行以下命令。您可以看到图像使用值 memcached:1.6.10:
$ kubectl rollout history deployments app-cache --revision=2
deployment.apps/app-cache with revision #2
Pod Template:
Labels: app=app-cache
pod-template-hash=596bc5586d
Containers:
memcached:
Image: memcached:1.6.10
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
滚动更新策略确保应用程序始终对最终用户可用。此方法意味着在更新过程中会有两个版本的同一应用程序可用。作为应用程序开发者,您必须意识到便利并非没有潜在的副作用。如果您碰巧对应用程序的公共 API 引入了破坏性更改,可能会暂时破坏消费者,因为他们可能会访问应用程序的修订版 1 或 2。
您可以通过为属性 spec.strategy.type 提供不同的值来更改 Deployment 的默认更新策略;然而,请考虑其中的权衡。例如,值 Recreate 首先终止所有 Pods,然后创建具有最新修订版的新 Pods,可能会导致消费者的潜在停机时间。请参阅 第十一章 以获取常见部署策略的更详细描述。
为修订版添加更改原因
滚动历史显示列 CHANGE-CAUSE。您可以填写修订版的信息来记录 为什么 您引入了新更改或者 使用了 哪个 kubectl 命令来进行更改。
默认情况下,更改 Pod 模板不会自动记录变更原因。要为当前版本添加变更原因,请向 Deployment 对象添加具有保留键 kubernetes.io/change-cause 的注释。以下命令使用 annotate 命令分配变更原因“Image updated to 1.6.10”:
$ kubectl annotate deployment app-cache kubernetes.io/change-cause=\
"Image updated to 1.6.10"
deployment.apps/app-cache annotated
现在回滚历史记录会显示当前版本的变更原因值:
$ kubectl rollout history deployment app-cache
deployment.apps/app-cache
REVISION CHANGE-CAUSE
1 <none>
2 Image updated to 1.6.10
回滚到以前的版本
在生产环境中可能会出现需要快速处理的问题。例如,您刚刚发布的容器镜像包含一个关键的 bug。Kubernetes 允许您在发布历史中回滚到以前的某个版本。您可以使用 rollout undo 命令来实现这一点。要选择特定版本,请提供命令行选项 --to-revision。如果不提供选项,该命令将回滚到上一个版本。在这里,我们正在回滚到版本 1:
$ kubectl rollout undo deployment app-cache --to-revision=1
deployment.apps/app-cache rolled back
因此,Kubernetes 对具有版本 1 的所有副本执行滚动更新。
回滚和持久数据
rollout undo 命令不会恢复与应用程序关联的任何持久数据。相反,它只是恢复到 ReplicaSet 的先前声明状态的新实例。
现在回滚历史记录显示版本 3。鉴于我们已经回滚到版本 1,没有必要保留该条目作为重复项。Kubernetes 简单地将版本 1 变为 3 并从列表中删除 1:
$ kubectl rollout history deployment app-cache
deployment.apps/app-cache
REVISION CHANGE-CAUSE
2 Image updated to 1.16.10
3 <none>
扩展工作负载
扩展性是 Kubernetes 内置的能力之一。我们将学习如何在应用程序负载增加时手动扩展副本数量。此外,我们将讨论 API 资源 Horizontal Pod Autoscaler,它允许您根据 CPU 和内存等资源阈值自动扩展管理的 Pod 集。
手动扩展部署
扩展(增加或减少)由 Deployment 控制的副本数量是一个简单的过程。您可以使用 edit deployment 命令手动编辑活动对象,并更改 spec.replicas 属性的值,也可以使用命令式的 scale deployment 命令。在现实世界的生产环境中,您希望编辑 Deployment YAML 清单,将其检入版本控制,并应用更改。以下命令将副本数量从四个增加到六个:
$ kubectl scale deployment app-cache --replicas=6
deployment.apps/app-cache scaled
您可以使用 -w 命令行标志实时观察副本的创建过程。您将看到新创建的 Pod 的状态从 ContainerCreating 变为 Running 的变化:
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
app-cache-5d6748d8b9-6cc4j 1/1 ContainerCreating 0 11s
app-cache-5d6748d8b9-6rmlj 1/1 Running 0 28m
app-cache-5d6748d8b9-6z7g5 1/1 ContainerCreating 0 11s
app-cache-5d6748d8b9-96dzf 1/1 Running 0 28m
app-cache-5d6748d8b9-jkjsv 1/1 Running 0 28m
app-cache-5d6748d8b9-svrxw 1/1 Running 0 28m
手动扩展副本数量需要一些猜测工作。您仍然需要监视系统的负载,以查看您的副本数量是否足以处理传入的流量。
自动扩展部署
另一种缩放部署的方法是借助水平 Pod 自动伸缩器(HPA)。HPA 是一个 API 原语,定义了在特定条件下自动调整副本数量的规则。常见的缩放条件包括目标值、平均值或特定指标(例如 CPU 和/或内存)的平均利用率。更多信息请参阅 MetricTarget API。
假设您想定义 CPU 利用率的平均 CPU 作为缩放条件。在运行时,HPA 检查由 metrics server 收集的指标,以确定部署的所有副本的平均最大 CPU 或内存使用是否低于或高于定义的阈值。确保在集群中安装了 metrics server。安装组件后,收集指标可能需要几分钟时间。有关更多信息,请参阅 “Inspecting Resource Metrics”。
图 10-3 展示了涉及 HPA 的概述架构图。

图 10-3. 自动缩放部署
创建水平 Pod 自动伸缩器
图 10-4 显示了一个 HPA 的使用情况,如果所有由部署控制的可用 Pod 的平均 CPU 利用率达到 80%,将扩展副本数量。

图 10-4. 自动缩放部署的水平扩展
您可以使用autoscale deployment命令为现有部署创建一个 HPA。选项--cpu-percent定义了平均最大 CPU 使用阈值。在撰写本文时,该命令并未提供用于定义平均最大内存利用率阈值的选项。选项--min和--max分别提供了要缩减到的最小副本数量以及 HPA 可以创建以处理增加负载的最大副本数量:
$ kubectl autoscale deployment app-cache --cpu-percent=80 --min=3 --max=5
horizontalpodautoscaler.autoscaling/app-cache autoscaled
这个命令是创建部署的 HPA 的一个很好的快捷方式。HPA 对象的 YAML 清单表示看起来像是 示例 10-2。
示例 10-2. HPA 的 YAML 清单
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-cache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app-cache
minReplicas: 3
maxReplicas: 5
metrics:
- resource:
name: cpu
target:
averageUtilization: 80
type: Utilization
type: Resource
列出水平 Pod 自动伸缩器
水平 Pod 自动伸缩器的简写命令是 hpa。透明列出所有 HPA 对象描述了它们当前的状态:此时的 CPU 利用率和副本数量:
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS \
AGE
app-cache Deployment/app-cache <unknown>/80% 3 5 4 \
58s
如果部署的 Pod 模板未定义 CPU 资源要求,或者无法从指标服务器检索 CPU 指标,则“目标”列的左侧值显示为
示例 10-3. 为 Pod 模板设置 CPU 资源要求
# ...
spec:
# ...
template:
# ...
spec:
containers:
- name: memcached
# ...
resources:
requests:
cpu: 250m
limits:
cpu: 500m
一旦流量到达副本,当前的 CPU 使用率将显示为百分比。这里的平均最大 CPU 利用率为 15%:
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
app-cache Deployment/app-cache 15%/80% 3 5 4 58s
渲染水平 Pod 自动扩展器详细信息
HPA 的事件日志可以提供有关重新调整活动的额外见解。渲染 HPA 详细信息可以是监控何时扩展副本数以及其扩展条件的强大工具:
$ kubectl describe hpa app-cache
Name: app-cache
Namespace: default
Labels: <none>
Annotations: <none>
CreationTimestamp: Sun, 15 Aug 2021 \
15:54:11 -0600
Reference: Deployment/app-cache
Metrics: ( current / target )
resource cpu on pods (as a percentage of request): 0% (1m) / 80%
Min replicas: 3
Max replicas: 5
Deployment pods: 3 current / 3 desired
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True ReadyForNewScale recommended size matches current size
ScalingActive True ValidMetricFound the HPA was able to successfully \
calculate a replica count from cpu resource utilization (percentage of request)
ScalingLimited True TooFewReplicas the desired replica count is less \
than the minimum replica count
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulRescale 13m horizontal-pod-autoscaler New size: 3; \
reason: All metrics below target
定义多个扩展度量
您可以将多个资源类型定义为扩展度量。如 示例 10-4 所示,我们正在检查 CPU 和内存利用率,以确定是否需要扩展或缩小部署的副本。
示例 10-4. 具有多个指标的 HPA 的 YAML 清单
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-cache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app-cache
minReplicas: 3
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 500Mi
为了确保 HPA 确定当前使用的资源,我们还将为 Pod 模板设置内存资源要求,如 示例 10-5 所示。
示例 10-5. 为 Pod 模板设置内存资源要求
...
spec:
...
template:
...
spec:
containers:
- name: memcached
...
resources:
requests:
cpu: 250m
memory: 100Mi
limits:
cpu: 500m
memory: 500Mi
列出 HPA 时会显示“目标”列中的两个指标,如此处显示的 get 命令的输出:
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS \
REPLICAS AGE
app-cache Deployment/app-cache 1994752/500Mi, 0%/80% 3 5 \
3 2m14s
摘要
部署是 Kubernetes 提供声明性更新和 Pod 生命周期管理的基本原语。ReplicaSet 执行管理这些 Pod(通常称为副本)的繁重工作。应用程序开发人员无需直接与 ReplicaSet 交互;部署在幕后管理 ReplicaSet。
部署可以轻松地推出和回滚由容器中运行的镜像表示的应用程序的修订版。在本章中,您学习了控制修订历史及其操作的命令。手动扩展部署需要深入了解应用程序的需求和负载。水平 Pod 自动扩展器可以根据运行时观察到的 CPU 和内存阈值自动扩展副本的数量。
考试要点
详细了解部署的各个方面
考虑到部署在 Kubernetes 中是一个如此核心的原语,您可以期待考试将在此方面对您进行测试。了解如何创建部署并学习如何扩展到多个副本。部署的一个卓越特性是其新修订的推出功能。练习如何推出新的修订版,检查推出历史记录,并回滚到先前的修订版。
理解使用水平 Pod Autoscaler 的影响
由 Deployment 控制的副本数量可以使用 Horizontal Pod Autoscaler(HPA)进行缩放。HPA 定义资源(如 CPU 和内存)的阈值,告诉对象需要进行缩放事件。重要的是要了解,只有在安装度量服务器组件并为容器定义资源请求和限制时,HPA 才能正常工作。
示例练习
这些练习的解决方案在附录 A 中提供。
-
创建名为
nginx的 Deployment,有 3 个副本。Pod 应使用nginx:1.23.0镜像,名称为nginx。Deployment 使用标签tier=backend。Pod 模板应使用标签app=v1。列出 Deployment 并确保正在运行正确数量的副本。
将镜像更新为
nginx:1.23.4。验证变更已部署到所有副本。
将变更原因“拾取补丁版本”分配给修订版本。
将 Deployment 缩放到 5 个副本。
查看 Deployment 的部署历史。将 Deployment 回滚到修订版本 1。
确保 Pod 使用镜像
nginx:1.23.0。 -
创建名为
nginx的 Deployment,有 1 个副本。Deployment 的 Pod 模板应使用容器镜像nginx:1.23.4;将 CPU 资源请求设置为 0.5,内存资源请求/限制设置为 500Mi。为名为
nginx-hpa的 Deployment 创建 HorizontalPodAutoscaler,其最小缩放到 3 个副本,最大缩放到 8 个副本。缩放应基于平均 CPU 利用率 75%和平均内存利用率 60%。检查 HorizontalPodAutoscaler 对象并确定当前使用的资源。您期望存在多少副本?
第十一章:部署策略
将一个应用程序(打包到容器镜像中)部署到一个或多个 Pods 中只是其在 Kubernetes 集群中生命周期的开始。开发人员会定期生成和发布新的容器镜像标签,以发布 bug 修复和新功能。逐一手动更新 Pods 到新的容器镜像标签将是极其繁琐的。Kubernetes 提供了部署原语来简化这一过程。
第十章 解释了如何使用部署原语自动推出新版本。在本章中,我们将讨论原语支持的内置部署策略。我们还将讨论其他需要刻意人为决策的部署策略。每种部署策略都配有一个示例,展示其优点和可能的权衡。还存在更多部署策略,但本书不涉及这些内容。
注意
某些部署策略需要使用尚未讨论的概念。跳转至 第十四章 了解容器探测的覆盖范围。参考 第二十一章 以获取有关服务目的的更多信息。
滚动部署策略
部署原语默认采用滚动部署策略,也称为逐步部署。称之为“逐步”,是因为部署逐渐将副本从旧版本过渡到新版本的批次中。用户更新 Pod 模板后,部署会自动为所需更改创建新的 ReplicaSet。
图 11-1 显示了在部署过程中时间点的快照。

图 11-1 滚动部署策略
在这种情况下,用户启动了从版本 1.0.0 到 2.0.0 的应用程序版本更新。因此,部署创建了一个新的 ReplicaSet,并启动运行新应用程序版本的 Pods,同时缩减旧版本。服务将网络流量路由到应用程序的旧版或新版。
实施
默认情况下,部署使用滚动更新策略。属性 spec.strategy.type 的运行时值为 RollingUpdate。用户可以微调此策略。您可以使用属性 spec.strategy.rollingUpdate.maxUnavailable 和 spec.strategy.rollingUpdate.maxSurge 来改变发布速率。这两个属性可以使用固定整数(例如,3)或分配所需 Pods 总数的百分比(例如,33%)。maxUnavailable 和 maxSurge 的默认值为 25%。
属性 maxUnavailable 指定在更新过程中可以不可用的最大 Pods 数量。例如,如果将值设置为 40%,则旧 ReplicaSet 在滚动更新开始时可以立即缩减到 60%。
属性 maxSurge 指定了可以在所需 Pods 的最大数量之上创建的 Pods 的最大数量。例如,如果设置为 10%,则新旧 Pods 的总数在新 ReplicaSet 创建后不能超过 110%。
无论为属性 maxUnavailable 和 maxSurge 分配了什么值,由旧 ReplicaSet 控制的所有副本都将逐渐关闭到 0,直到由新 ReplicaSet 控制的所有副本等于 spec.replicas 的值。
建议为 Pod 模板定义就绪探测器,以确保副本能够处理传入请求。属性 spec.minReadySeconds 指定了副本在可以处理传入请求之前需要处于可用状态的秒数。
示例 11-1 展示了在 deployment-rolling-update.yaml 文件中存储的完整 Deployment YAML 清单的使用情况。
示例 11-1. 使用滚动更新策略配置的 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 40% 
maxSurge: 10% 
minReadySeconds: 60 
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:2.4.23-alpine
ports:
- containerPort: 80
protocol: TCP
readinessProbe: 
httpGet:
path: /
port: 80
在更新期间可以不可用的 Pod 的百分比。
可以暂时超出总副本数的 Pods 的百分比。
Pod 中的就绪探测器必须在健康状态下运行多少秒,以便继续执行部署过程。
所有副本的就绪探测与 spec.minReadySeconds 相关。
分配给 maxUnavailable 和 maxSurge 的值的组合决定了部署的运行时行为和速度。您可以调整这些参数,以找到最适合您的应用程序的组合。
用例和权衡
滚动部署是一个适合零停机时间推出新应用版本的部署策略。根据 Deployment 管理的副本数量不同,这个过程可能相对较慢,因为旧版本的应用逐步关闭,而新版本的应用则分批逐步启动。
需要注意的是,这种部署策略存在潜在的风险。旧版本和新版本的应用并行运行。如果新版本引入了破坏性变更,消费者如果未能将客户端软件适应最新更改,可能会遇到意外且难以调试的错误。推荐以向后兼容的方式推出新应用版本,例如通过使用有版本的 API,以避免出现这种情况。
固定部署策略
固定部署策略会立即终止旧应用版本的副本,然后创建另一个控制新应用版本副本的 ReplicaSet。
Figure 11-2 描述了从应用程序版本 1.0.0 更新 Pod 模板到 2.0.0 的发布过程。所有旧 ReplicaSet 的副本将同时关闭。然后,由新 ReplicaSet 控制的副本将启动。在此过程中,服务可能无法访问任何副本,这可能会导致消费者的不必要停机时间。

图 11-2. 固定的部署策略
实现
要为 Deployment 配置固定的部署策略,将属性 spec.strategy.type 设置为 Recreate。在内部,这种策略类型会自动将总副本数分配给属性 maxUnavailable。不需要提供其他配置选项。
Example 11-2 展示了在 deployment-fixed.yaml 文件中存储的完整 Deployment YAML 清单上下文中的 Recreate 策略类型。
示例 11-2. 使用固定部署策略配置的 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 4
strategy:
type: Recreate 
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:2.4.23-alpine
ports:
- containerPort: 80
protocol: TCP
配置固定部署的策略类型。
为 Pod 模板定义的容器分配一个就绪探针并不是绝对必要的,因为所有旧应用版本的副本将同时关闭。尽管如此,在传入流量到达容器之前验证应用程序是否正常运行仍然是有意义的。
使用案例和权衡
固定的部署策略适用于可以接受应用程序停机时间的情况。例如,如果要将新应用程序部署到开发环境进行测试,这种策略非常适合。对于生产环境,如果向客户宣布了停机时间窗口,这种部署策略可能会起作用。
蓝绿部署策略
蓝绿部署策略(有时称为红黑部署策略)在象征上使用蓝色来表示旧应用程序版本,绿色来表示新应用程序版本。两个应用程序版本将同时运行,具有相同数量的副本。
Kubernetes 将流量路由到蓝部署,而开发或测试团队则部署和测试绿部署。一旦绿部署被认为达到了生产就绪状态,流量就会切换到绿部署。在这一点上,管理应用程序的团队可以停用蓝部署。
Figure 11-3 展示了管理具有不同应用程序版本副本的两个 Deployment。服务可以通过更改标签选择来将网络流量从旧应用程序版本切换到新应用程序版本。

图 11-3. 蓝绿部署策略
实现
蓝绿部署不是您可以在部署资源中配置的内置策略。您将需要为两个应用程序版本创建一个部署对象。该服务将流量路由到由蓝色或绿色部署管理的副本。
示例 11-3 展示了存储在文件 deployment-blue.yaml 中的蓝色部署 YAML 清单,指定 Pod 模板中的容器镜像 httpd:2.4.23-alpine。
示例 11-3. 蓝色部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server-blue
spec:
replicas: 4
selector:
matchLabels:
type: blue
template:
metadata:
labels:
type: blue 
spec:
containers:
- name: httpd
image: httpd:2.4.23-alpine 
ports:
- containerPort: 80
protocol: TCP
使用标签分配 type: blue 到由相应 ReplicaSet 管理的任何副本。
旧的应用程序版本 2.4.23-alpine。
要设置一个运行较新容器镜像 httpd:2.4.57-alpine 的绿色部署,只需创建另一个部署对象。请注意,用于 Pod 模板的标签与蓝色部署不同。示例 11-4 展示了文件 deployment-green.yaml 中绿色部署的定义。
示例 11-4. 绿色部署
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server-green
spec:
replicas: 4
selector:
matchLabels:
type: green
template:
metadata:
labels:
type: green 
spec:
containers:
- name: httpd
image: httpd:2.4.57-alpine 
ports:
- containerPort: 80
protocol: TCP
使用标签分配 type: green 到由相应 ReplicaSet 管理的任何副本。
新的应用程序版本 2.4.57-alpine。
如前所述,服务是负责将网络流量路由到旧或新应用程序版本的 Kubernetes 对象。示例 11-5 展示了一个服务对象。
示例 11-5. 将流量路由到蓝色部署的服务
apiVersion: v1
kind: Service
metadata:
name: web-server
spec:
selector:
type: blue 
ports:
- protocol: TCP
port: 80
targetPort: 80
指向由蓝色部署管理的副本的标签选择器。
当前的资源声明指向蓝色部署;要切换到绿色,只需将标签选择从 type: blue 更改为 type: green。在那时,您可以删除蓝色部署。
使用情况和权衡
蓝绿部署策略适用于需要在不影响消费者的情况下执行复杂升级的部署场景。如果升级需要数据迁移或者需要同时更改多个依赖软件组件,则可能出现这种情况。如果需要回滚到旧应用程序版本,则只需在服务中更改标签选择即可。
在这方面,值得一提的是,与其他部署策略相比,您将需要更多的硬件资源。如果您需要五个副本来运行旧的应用程序版本,那么对于新的应用程序版本,假设资源需求不会有所不同,您将需要相同数量的资源。
金丝雀部署策略
金丝雀部署策略类似于蓝绿部署;但是,您只会将新的应用程序版本提供给一部分消费者。通过这种方法,您可以实施新功能的 A/B 测试,或者需要收集消费者行为指标。根据定义的成功标准集,可以逐渐增加对新应用程序版本的流量。目标是完全关闭旧的应用程序版本。
图 11-4 显示服务如何将流量发送到两个应用程序版本。

图 11-4. 金丝雀部署策略
部署 1 控制应用程序版本 v1.0.0. 部署 2 控制应用程序版本 v2.0.0. 部署 1 和 2 在其 Pod 模板中使用相同的标签分配。服务选择由两个部署定义的标签键值对。
实施
在 Kubernetes 集群中,您可以使用部署对象来表示每个应用程序版本。通过将 spec.replicas 属性的值分配为比当前应用程序版本少的副本数,您可以推出新的应用程序版本。
下面的代码片段显示了控制旧应用程序版本的部署的截断定义:
kind: Deployment
spec:
replicas: 4
selector:
matchLabels:
app: httpd
对于新的应用程序版本,分配较少数量的副本:
kind: Deployment
spec:
replicas: 1
selector:
matchLabels:
app: httpd
确保旧版和新版应用程序版本的副本接收来自消费者的请求,需要在两个部署对象的分配的 Pod 模板标签上保持相同。确保服务选择这些标签,如此截断的服务定义中所示:
kind: Service
spec:
selector:
app: httpd 
选择分配给两个部署的标签。
用例和权衡
组织通常使用金丝雀部署策略来推出可能对系统性能产生影响的实验性功能或更改。您可以在仅向部分消费者提供新功能的同时评估成功标准。实施金丝雀部署通常需要的硬件资源比蓝绿部署少得多,因为具有新应用程序版本的副本数量要低得多。
摘要
部署是将软件更改提供给最终用户或程序的过程。您需要考虑两个方面:如何部署更改的过程和将网络流量路由到应用程序。根据用例、应用程序类型和权衡选择合适的部署策略。
使用部署原语,Kubernetes 本地支持两种部署策略:滚动部署和固定部署。由 RollingUpdate 策略指定的滚动部署逐步批量推出更改。通过 Recreate 策略配置的固定部署首先关闭旧的应用程序版本,然后启动新的应用程序版本。
可以通过创建第二个部署对象来设置蓝绿和金丝雀部署策略,该对象与旧版本并行管理新应用程序版本。然后,服务将网络流量路由到两个应用程序版本的副本(蓝绿),或者随着时间推移将消费者过渡到新应用程序版本(金丝雀)。
考试要点
了解如何配置部署原语本地的策略
考试可能会用不同的部署策略面对您。您需要了解如何实施最常见的策略以及如何修改现有的部署场景。学习如何配置部署原语中的内置策略及其用于调整运行时行为的选项。
练习多阶段部署策略
您可以使用部署和服务原语实施更复杂的部署场景。例如,蓝绿和金丝雀部署策略需要多阶段的部署过程。暴露您自己到实施技术和部署过程中的技巧。由 Kubernetes 社区提供的运营商,例如 Argo Rollouts,提供更复杂的部署策略的高级抽象。考试不要求您了解外部工具以实施部署策略。
样本练习
这些练习的解决方案可在 附录 A 中找到。
-
您的一个队友创建了一个部署 YAML 文件清单,用于操作容器镜像
grafana/grafana:9.5.9。从 YAML 文件deployment-grafana.yaml创建部署对象:apiVersion: apps/v1 kind: Deployment metadata: name: grafana spec: replicas: 6 selector: matchLabels: app: grafana template: metadata: labels: app: grafana spec: containers: - image: grafana/grafana:9.5.9 name: grafana ports: - containerPort: 3000您需要更新所有副本,使用容器镜像
grafana/grafana:10.1.2。确保批量每次两个副本进行滚动更新。确保定义了就绪探针。 -
在这个练习中,您将设置蓝绿部署场景。首先创建初始(蓝色)部署,并通过服务公开它。稍后,您将创建第二个(绿色)部署,并切换流量。
创建名为
nginx-blue的部署,有 3 个副本。部署的 Pod 模板应使用容器镜像nginx:1.23.0并分配标签version=blue。使用类型为 ClusterIP 的服务将部署暴露出来,命名为
nginx。将入口和出口端口映射到 80。选择标签为version=blue的 Pod。运行一个临时的 Pod,使用容器镜像
alpine/curl:3.14,使用curl调用服务。创建名为
nginx-green的第二个部署,有 3 个副本。部署的 Pod 模板应使用容器镜像nginx:1.23.4并分配标签version=green。更改服务的标签选择,以便将流量路由到由部署
nginx-green控制的 Pod。删除名为
nginx-blue的部署。运行一个临时 Pod,使用容器镜像
alpine/curl:3.14对该服务进行调用。
第十二章:Helm
Helm 是一种用于一组 Kubernetes 清单的模板引擎和包管理器。运行时,它将 YAML 模板文件中的占位符替换为实际的、最终用户定义的值。Helm 可执行文件生成的工件称为 chart 文件,打包了组成应用程序 API 资源的清单。您可以将 chart 文件上传到 chart 仓库,以便其他团队可以使用它来部署捆绑的清单。Helm 生态系统提供了广泛的可重用图表,用于常见用例,可以在 Artifact Hub 上搜索(例如用于运行 Grafana 或 PostgreSQL)。
由于 Helm 提供了丰富的功能,我们仅讨论基础知识。考试不要求您成为 Helm 专家;相反,它希望您熟悉使用 Helm 安装现有包的工作流程。构建和发布自己的图表超出了考试范围。有关 Helm 的更详细信息,请参阅 用户文档。本文档描述的 Helm 功能版本为 3.13.0。
管理现有的图表
作为开发人员,您希望重用现有的功能,而不是花时间定义和配置它。例如,您可能希望在集群上安装开源监控服务 Prometheus。
Prometheus 需要安装多个 Kubernetes 原语。幸运的是,Kubernetes 社区提供了一个 Helm 图表,使得安装和配置所有组件变得非常简单,形成了一个 Kubernetes operator。
以下列表显示了消费和管理 Helm 图表的典型工作流程。大多数步骤需要使用 helm 可执行文件:
-
确定您想要安装的图表
-
添加包含图表的仓库
-
从仓库安装图表
-
验证已安装的 Kubernetes 对象。
-
渲染已安装图表的列表
-
升级已安装的图表
-
如果不再需要其功能,可以卸载图表。
以下各节将解释每个步骤。
确定图表
多年来,Kubernetes 社区实施并发布了成千上万个 Helm 图表。Artifact Hub 提供了基于关键字搜索的网络搜索功能,用于发现图表。
假设您想要查找一个安装持续集成解决方案 Jenkins 的图表。您只需在搜索框中输入术语“jenkins”并按回车键即可。Figure 12-1 显示了 Artifact Hub 中的结果列表。

图 12-1。在 Artifact Hub 上搜索 Jenkins 图表
在撰写本文时,搜索条件匹配了 141 项结果。你将能够通过点击搜索结果中的任一项来查看有关图表的详细信息,包括高层描述和包含图表文件的仓库。此外,你还可以检查与图表文件捆绑在一起的模板,显示安装后将创建的对象及其配置选项。图 12-2 显示了官方 Jenkins 图表的页面。

图 12-2. Jenkins 图表详情
你不能直接从 Artifact Hub 安装图表。你必须从托管图表文件的仓库安装它。
添加图表仓库
图表描述可能会提及托管图表文件的仓库。或者,你也可以点击“安装”按钮以渲染仓库详情及添加它的命令。图 12-3 显示了点击“安装”按钮后出现的上下文弹出窗口。
默认情况下,Helm 安装不定义任何外部仓库。以下命令显示如何列出所有注册的仓库。当前尚未注册任何仓库:
$ helm repo list
Error: no repositories to show

图 12-3. Jenkins 图表安装说明
正如你从截图中所见,图表文件存储在带有网址 https://charts.jenkins.io 的仓库中。我们需要添加这个仓库。这是一次性的操作。你可以从该仓库安装其他图表,或者使用我们将在后面讨论的命令更新源自该仓库的图表。
当注册一个仓库时,需要为其提供一个名称。尽可能使仓库名称描述性强。以下命令使用名称 jenkinsci 注册了该仓库:
$ helm repo add jenkinsci https://charts.jenkins.io/
"jenkinsci" has been added to your repositories
现在列出的仓库显示了名称和 URL 之间的映射关系:
$ helm repo list
NAME URL
jenkinsci https://charts.jenkins.io/
你已经将仓库永久性地添加到了 Helm 安装中。
在仓库中搜索图表
“安装”弹出窗口已经提供了安装图表的命令。如果你不知道图表的名称或最新版本,也可以在该仓库中搜索可用的图表。添加 --versions 标志以列出所有可用版本:
$ helm search repo jenkinsci
NAME CHART VERSION APP VERSION DESCRIPTION
jenkinsci/jenkins 4.6.5 2.414.2 ...
当前可用的最新版本是 4.6.5. 如果你在自己的机器上运行该命令,可能会得到不同的版本,因为 Jenkins 项目可能已发布了更新版本。
安装图表
假设最新版本的 Helm 图表存在安全漏洞。因此,我们决定安装 Jenkins 图表的前一个版本,即 4.6.4. 你需要分配一个名称以便识别已安装的图表。这里我们将使用 my-jenkins 作为名称:
$ helm install my-jenkins jenkinsci/jenkins --version 4.6.4
NAME: my-jenkins
LAST DEPLOYED: Thu Sep 28 09:47:21 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
...
该图表会自动在 default 命名空间中创建 Kubernetes 对象。你可以使用以下命令发现最重要的资源类型:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/my-jenkins-0 2/2 Running 0 12m
NAME TYPE CLUSTER-IP EXTERNAL-IP ...
service/my-jenkins ClusterIP 10.99.166.189 <none> ...
service/my-jenkins-agent ClusterIP 10.110.246.141 <none> ...
NAME READY AGE
statefulset.apps/my-jenkins 1/1 12m
该图表已使用默认配置选项安装。您可以通过单击图表页面上的“默认值”按钮查看这些默认值,如图 12-4 所示。

图 12-4. Jenkins 图表默认值
您还可以使用以下命令发现这些配置选项。显示的输出仅渲染了一部分值,即controller.adminUser和controller.adminPassword分别表示管理员用户名和其密码:
$ helm show values jenkinsci/jenkins
...
controller:
# When enabling LDAP or another non-Jenkins identity source, the built-in \
# admin account will no longer exist.
# If you disable the non-Jenkins identity store and instead use the Jenkins \
# internal one,
# you should revert controller.adminUser to your preferred admin user:
adminUser: "admin"
# adminPassword: <defaults to random>
...
在安装图表时,您可以自定义任何配置值。要在安装过程中传递配置数据,请使用以下标志之一:
-
--values:以指向 YAML 清单文件的指针形式指定覆盖项。 -
--set:直接从命令行指定覆盖。
有关更多信息,请参见Helm 文档中的“在安装之前自定义图表”。
您可以决定将图表安装到自定义命名空间中。使用-n标志提供现有命名空间的名称。如果命名空间尚不存在,则添加--create-namespace标志将自动创建命名空间。
以下命令显示如何在安装过程中自定义一些值和使用的命名空间:
$ helm install my-jenkins jenkinsci/jenkins --version 4.6.4 \
--set controller.adminUser=boss --set controller.adminPassword=password \
-n jenkins --create-namespace
我们特别为管理员用户设置了用户名和密码。Helm 将由图表控制的对象创建到jenkins命名空间中。
列出已安装的图表
图表可以存在于default命名空间或自定义命名空间中。您可以使用helm list命令检查已安装图表的列表。如果不知道使用哪个命名空间,只需将--all-namespaces标志添加到命令中即可:
$ helm list --all-namespaces
NAME NAMESPACE REVISION UPDATED STATUS CHART
my-jenkins default 1 2023-09-28... deployed jenkins-4.6.4
命令的输出包括列NAMESPACE,显示特定图表使用的命名空间。类似于使用kubectl,helm list命令提供了选项-n来指定命名空间。如果命令不附带任何标志,则返回default命名空间的结果。
升级已安装的图表
升级已安装的图表通常意味着转移到新的图表版本。您可以通过运行以下命令来轮询存储库中提供的新版本:
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "jenkinsci" chart repository
Update Complete. ⎈Happy Helming!⎈
如果要将现有的图表安装升级到新的图表版本,该如何操作?运行以下命令将图表升级到该特定版本并使用默认配置:
$ helm upgrade my-jenkins jenkinsci/jenkins --version 4.6.5
Release "my-jenkins" has been upgraded. Happy Helming!
...
与install命令一样,如果要在升级图表时调整图表的运行时行为,您将需要提供自定义配置值。
卸载图表
有时您不再需要运行图表。卸载图表的命令很简单,如下所示。它将删除由图表控制的所有对象。如果之前将图表安装到除default以外的命名空间中,请不要忘记提供-n标志:
$ helm uninstall my-jenkins
release "my-jenkins" uninstalled
执行该命令可能需要最多 30 秒,因为 Kubernetes 需要等待工作负载的优雅结束期。
概要
Helm 已发展成为将应用程序堆栈部署到 Kubernetes 的事实标准工具。包含清单文件、默认配置值和元数据的工件称为图表。团队或个人可以将图表发布到图表存储库。用户可以通过 Artifact Hub 用户界面发现已发布的图表,并将其安装到 Kubernetes 集群中。
使用 Helm 时,主要的开发者工作流程之一包括查找、安装和升级特定版本的图表。您可以通过注册包含要消费的图表文件的存储库来开始。helm install 命令会下载图表文件并将其存储在本地缓存中。它还会创建图表描述的 Kubernetes 对象。
安装过程是可配置的。开发者可以提供自定义配置值的覆盖。helm upgrade 命令允许您升级已安装图表的版本。要卸载图表并删除图表管理的所有 Kubernetes 对象,请运行 helm uninstall 命令。
考试要点
假设 Helm 可执行文件已预安装
不幸的是,考试 FAQ 没有提及 Helm 可执行文件或预期的 Helm 版本的任何细节。可以合理地假设它将为您预安装,因此您无需记忆安装说明。您将能够浏览 Helm 文档页面。
熟悉 Artifact Hub
Artifact Hub 提供了 Helm 图表的基于 Web 的用户界面。探索搜索能力和各个图表提供的详细信息非常值得,尤其是图表文件所在的存储库及其可配置值。在考试中,您可能不会被要求导航到 Artifact Hub,因为其 URL 未列为允许的文档页面之一。您可以假设考试题目会为您提供存储库的 URL。
练习需要消费现有 Helm 图表的命令
考试不要求您构建和发布自己的图表文件。您只需要理解如何消费现有的图表。您需要熟悉 helm repo add 命令以注册存储库,helm search repo 命令以查找可用的图表版本,以及 helm install 命令以安装图表。您应该对使用 helm upgrade 命令升级已安装的 Helm 图表的过程有基本的理解。
示例练习
这些练习的解决方案可以在 附录 A 中找到。
-
在这个练习中,您将使用 Helm 安装所需的 Kubernetes 对象,用于开源监控解决方案Prometheus。在 Kubernetes 上安装 Prometheus 的最简单方式是使用prometheus-operator Helm 图表。
您可以在 Artifact Hub 上搜索kube-prometheus-stack。将该仓库添加到 Helm 可访问的已知仓库列表中,名称为
prometheus-community。更新来自各自图表仓库的最新图表信息。
运行 Helm 命令以列出可用的 Helm 图表及其版本。确定
kube-prometheus-stack的最新图表版本。安装图表
kube-prometheus-stack。列出已安装的 Helm 图表。列出由 Helm 图表创建的名为
prometheus-operated的 Service。该对象位于default命名空间。使用 kubectl 的
port-forward命令将本地端口 8080 转发到 Service 的端口 9090。打开浏览器并打开 Prometheus 仪表盘。停止端口转发并卸载 Helm 图表。
第十三章:API 弃用
Kubernetes 项目定期发布新版本。每个发布版本都会增加新功能和错误修复,但也可能引入现有 API 的弃用。API 是应用程序开发人员在定义 Kubernetes 对象时与之交互的接口。
如果 Kubernetes 团队计划更改、替换或完全移除支持的 API,弃用可能会生效。您需要了解如何处理 API 弃用,以避免在将节点更新到较新的 Kubernetes 版本之前出现问题。
理解弃用策略
Kubernetes 项目每年会发布 三个版本。理想情况下,Kubernetes 集群的管理员应尽早升级到最新版本,以便获得增强功能和安全修复。然而,升级集群并非没有潜在的成本和风险。您需要确保在升级到新版本时,现有的运行中对象仍然与之兼容。
Kubernetes 发布版本可能会弃用一个 API,这意味着该 API 计划要被移除或替换。弃用引入的规则遵循 Kubernetes 文档中解释的 弃用策略。
在清单中为 version 属性分配的值指定了 API 版本。使用已弃用的 API 在创建或更新对象时会显示警告消息。虽然您仍然可以创建或修改对象,但警告消息会告知用户采取措施,以确保其与较新的 Kubernetes 版本未来兼容。已弃用 API 迁移指南 显示了一些已弃用的 API 和计划中将移除其支持的版本列表。
列出可用的 API 版本
Kubectl 提供了一个命令用于发现可用的 API 版本。api-versions 命令以 group/version 的格式列出所有 API 版本:
$ kubectl api-versions
admissionregistration.k8s.io/v1
apiextensions.k8s.io/v1
apiregistration.k8s.io/v1
apps/v1
authentication.k8s.io/v1
authorization.k8s.io/v1
autoscaling/v1
autoscaling/v2
batch/v1
certificates.k8s.io/v1
coordination.k8s.io/v1
discovery.k8s.io/v1
events.k8s.io/v1
flowcontrol.apiserver.k8s.io/v1beta2
flowcontrol.apiserver.k8s.io/v1beta3
networking.k8s.io/v1
node.k8s.io/v1
policy/v1
rbac.authorization.k8s.io/v1
scheduling.k8s.io/v1
storage.k8s.io/v1
v1
某些 API,如 autoscaling 组的 v1 和 v2 版本,存在不同的版本。一般来说,通过选择更高的主版本可以使您的清单更加未来兼容。在撰写本文时,api-versions 命令的输出中不包含这些 API 的弃用状态。您需要在 Kubernetes 文档中查找其状态。
处理弃用警告
让我们演示使用已弃用 API 的效果。以下示例假设您正在运行版本在 1.23 到 1.25 之间的 Kubernetes 集群。示例 13-1 展示了一个使用 autoscaling 组的 beta API 版本的 Horizontal Pod Autoscaler 的清单。
示例 13-1. 使用已弃用 API 定义的 Horizontal Pod Autoscaler
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
从 YAML 清单创建新对象不会导致错误。Kubernetes 将愉快地创建对象,但命令会告知您在将来的 Kubernetes 版本中该 API 的用法。如下输出所示,建议您替换使用 autoscaling/v2 的 API:
$ kubectl create -f hpa.yaml
Warning: autoscaling/v2beta2 HorizontalPodAutoscaler is deprecated in v1.23+, \
unavailable in v1.26+; use autoscaling/v2 HorizontalPodAutoscaler
除了警告消息外,您可能还希望查看弃用 API 迁移指南。查找关于弃用 API 的信息的简便方法是在弃用指南中搜索它。例如,您将找到关于 API autoscaling/v2beta2 的 以下段落:
HorizontalPodAutoscaler 的
autoscaling/v2beta2API 版本已在 v1.26 中停止服务。
- 迁移清单和 API 客户端以使用
autoscaling/v2API 版本,自 v1.23 起可用。- 所有现有的持久化对象可通过新的 API 访问
弃用 API 迁移指南
为使清单未来兼容,您只需分配新的 API 版本。
处理已移除或替换的 API
Kubernetes 集群的管理员可能会决定一次跳过多个小版本进行升级。可能您并未注意到当前正在使用的 API 已经被移除。在升级生产集群之前,验证现有 Kubernetes 对象的兼容性非常重要,以避免任何中断。
假设您一直使用 示例 13-2 中展示的 ClusterRole 的定义。在 Kubernetes 1.8 中管理该对象运作良好;然而,管理员将集群节点升级到了 1.22。
示例 13-2. 使用 API 版本 rbac.authorization.k8s.io/v1beta1 的 ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
尝试从 ClusterRole 清单文件中创建对象将不会显示弃用消息。相反,kubectl 将返回一个错误消息。因此,命令未创建对象:
$ kubectl apply -f clusterrole.yaml
error: resource mapping not found for name: "pod-reader" namespace: "" from \
"clusterrole.yaml": no matches for kind "ClusterRole" in version \
"rbac.authorization.k8s.io/v1beta1"
仅仅通过查看错误消息,你无法清楚地了解命令失败的原因。建议查阅弃用 API 迁移指南。在页面上搜索 API 版本 rbac.authorization.k8s.io/v1beta1 将给出 以下信息。这里的解决方案是改用替代 API rbac.authorization.k8s.io/v1:
rbac.authorization.k8s.io/v1beta1API 版本的 ClusterRole、ClusterRoleBinding、Role 和 RoleBinding 已在 v1.22 中停止服务。
- 迁移清单和 API 客户端以使用
rbac.authorization.k8s.io/v1API 版本,自 v1.8 起可用。- 所有现有的持久化对象可通过新的 API 访问
- 无显著变化
弃用 API 迁移指南
在某些条件下,Kubernetes 原语可能无法提供替代 API。作为一个典型的用例,我想提到 PodSecurityPolicy 原语。该功能已被一个新的 Kubernetes 内部概念所取代,即 Pod Security Admission。您应该查阅即将发布的 Kubernetes 版本的发布说明,以了解更加重大的变更。
总结
长期使用 Kubernetes 时,您将不可避免地遇到 API 弃用问题。作为开发者,您需要知道如何解释弃用警告消息。Kubernetes 文档提供了识别替代 API 或功能所需的所有信息。建议在将节点升级到新版本 Kubernetes 之前,测试所有当前生产集群中使用的 YAML 清单对象。
考试要点
随时保留 API 弃用文档
考试可能会使您遇到使用弃用 API 的对象。您需要随时保留 弃用 API 迁移指南 文档页面,以应对此类情况。该页面按 Kubernetes 版本分类描述了已弃用、已移除和已替换的 API。使用浏览器的搜索功能快速查找有关 API 的相关信息。页面右侧的快速参考链接让您可以快速导航到特定的 Kubernetes 版本。
样例练习
这些练习的解决方案可以在 附录 A 中找到。
-
负责集群的 Kubernetes 管理员计划将所有节点从 Kubernetes 1.8 升级到 1.28。您定义了多个 Kubernetes YAML 清单,用于操作一个应用程序堆栈。管理员为您提供了一个 Kubernetes 1.28 测试环境。确保所有 YAML 清单与 Kubernetes 版本 1.28 兼容。
转到已检出 GitHub 仓库 bmuschko/ckad-study-guide 的目录 app-a/ch13/deprecated。检查当前目录中的文件 deployment.yaml 和 configmap.yaml。
根据 YAML 清单创建对象。根据需要修改定义。
确保对象能在 Kubernetes 1.28 中实例化。
第十四章:容器探针
在容器中运行的应用程序并不遵循“发射即忘”的前提。一旦 Kubernetes 启动容器,您希望知道应用程序是否准备好使用,并且在一个小时、一周或一个月后仍然按预期工作。健康探针是一个定期运行的迷你进程,询问应用程序的状态并在特定条件下采取行动。
在本章中,我们将讨论容器健康探针——更具体地说,就绪、存活和启动探针。您将了解不同的健康验证方法以及如何为适当的用例定义它们。
使用探针
即使采用最佳的测试策略,也几乎不可能在部署软件到生产环境之前找到所有的错误。特别是对于仅在操作软件一段时间后发生的故障情况而言。看到内存泄漏、死锁、无限循环及类似情况在最终用户对应用程序进行负载时出现并不罕见。
适当的监控有助于识别这些问题;然而,您仍然需要采取措施来减轻情况。首先,您可能希望重新启动应用程序以防止进一步的中断。其次,开发团队需要确定潜在的根本原因并修复应用程序的代码。
探针类型
Kubernetes 提供了称为健康探测的概念,用于自动检测和修正此类问题。您可以配置一个容器执行定期的迷你进程,检查特定条件。这些进程定义如下:
就绪探针
即使应用程序启动后,它可能仍然需要执行配置过程,例如连接到数据库并准备数据。此探针检查应用程序是否准备好接受传入请求。图 14-1 展示了就绪探针。

图 14-1. 就绪探针检查应用程序是否准备好接受流量
存活探针
一旦应用程序运行起来,您希望确保它仍然如预期般工作且没有问题。此探针定期检查应用程序的响应性。如果探测器认为应用程序处于不健康状态,Kubernetes 将自动重新启动容器,如图 14-2 所示。

图 14-2. 存活探针检查应用程序是否健康
启动探针
特别是旧应用程序可能需要很长时间才能启动 —— 可能会有几分钟。可以实例化启动探针来等待预定义的时间量,然后才允许启动存活探针进行探测。通过设置启动探针,可以防止应用程序进程被探测请求压倒。如果应用程序无法在设定的时间内启动,则启动探针会终止容器。图 14-3 展示了启动探针的行为。

图 14-3. 启动探针延迟启动存活探针的行为
从运维的角度来看,最重要的探针是准备探针。如果不定义存活性和启动探针,Kubernetes 控制平面组件将处理大部分默认行为。
每个探针提供了一些独特的方法来验证容器的健康状态,详见下一节。
健康验证方法
您可以为容器定义一个或多个健康验证方法。表 14-1 描述了可用的健康验证方法、它们对应的 YAML 属性以及它们的运行时行为。
表 14-1. 可用的健康验证方法
| 方法 | 选项 | 描述 |
|---|---|---|
| 自定义命令 | exec.command |
在容器内执行命令(例如 cat 命令),并检查其退出代码。Kubernetes 认为零退出代码表示成功,非零退出代码表示错误。 |
| HTTP GET 请求 | httpGet |
发送 HTTP GET 请求到应用程序公开的端点。在 200 到 399 范围内的 HTTP 响应代码表示成功。任何其他响应代码被视为错误。 |
| TCP socket 连接 | tcpSocket |
尝试打开到端口的 TCP socket 连接。如果连接成功建立,则探测尝试成功。无法连接被视为错误。 |
| gRPC | grpc |
应用程序实现了 GRPC 健康检查协议,验证服务器是否能处理远程过程调用(RPC)。 |
请记住,您可以将任何探针与任何健康检查方法结合使用。您选择的健康验证方法高度依赖于容器中运行的应用程序类型。例如,Web 应用程序的明显选择是使用 HTTP GET 请求验证方法。
健康检查属性
每个探针都提供一组属性,可以进一步配置运行时行为,如 表 14-2 所示。更多信息,请参阅 探针 v1 核心 对象的 API。
表 14-2. 用于微调健康检查运行时行为的属性
| 属性 | 默认值 | 描述 |
|---|---|---|
initialDelaySeconds |
0 | 直到执行第一次检查之前的延迟秒数。 |
periodSeconds |
10 | 执行检查的间隔(例如,每 20 秒一次)。 |
timeoutSeconds |
1 | 检查操作超时的最大秒数。 |
successThreshold |
1 | 在失败后多少次成功检查尝试后,探测被视为成功。 |
failureThreshold |
3 | 在检查尝试失败之前的失败次数,探测将被标记为失败并采取行动。 |
terminationGracePeriodSeconds |
30 | 在失败后强制停止容器之前的优雅期限。 |
以下各节将演示不同探针类型的大多数验证方法的使用。
就绪探针
在这种情况下,我们希望为 Node.js 应用程序定义一个就绪探针。Node.js 应用程序在根上下文路径上公开了一个 HTTP 端点,并运行在 3000 端口上。处理基于 Web 的应用程序,使用 HTTP GET 请求非常适合探测其就绪状态。您可以在本书的 GitHub 代码库中找到该应用程序的源代码。
在 示例 14-1 中显示的 YAML 清单中,就绪探针在两秒后执行其第一次检查,并在此后每八秒重复检查。所有其他属性使用默认值。即使应用程序成功启动后,就绪探针仍将定期检查。
示例 14-1. 使用 HTTP GET 请求的就绪探针
apiVersion: v1
kind: Pod
metadata:
name: readiness-pod
spec:
containers:
- image: bmuschko/nodejs-hello-world:1.0.0
name: hello-world
ports:
- name: nodejs-port 
containerPort: 3000
readinessProbe:
httpGet:
path: /
port: nodejs-port 
initialDelaySeconds: 2
periodSeconds: 8
您可以为端口分配一个名称,以便在探测中引用。
而不是再次分配 3000 端口,我们简单地使用端口名称。
通过将 apply 命令指向 YAML 清单来创建一个 Pod。在 Pod 的启动过程中,可能会显示状态为 Running,但容器尚未准备好接受传入请求,如 READY 列中的 0/1 所示:
$ kubectl apply -f readiness-probe.yaml
pod/readiness-pod created
$ kubectl get pod readiness-pod
NAME READY STATUS RESTARTS AGE
pod/readiness-pod 0/1 Running 0 6s
$ kubectl get pod readiness-pod
NAME READY STATUS RESTARTS AGE
pod/readiness-pod 1/1 Running 0 68s
$ kubectl describe pod readiness-pod
...
Containers:
hello-world:
...
Readiness: http-get http://:nodejs-port/ delay=2s timeout=1s \
period=8s #success=1 #failure=3
...
存活探针
活跃性探针检查应用程序是否按预期工作。为了演示活跃性探针,我们将使用一个自定义命令。自定义命令是验证容器健康状态最灵活的方式,因为它允许调用容器可用的任何命令。这可以是基础镜像中提供的命令行工具,也可以是您作为容器化过程的一部分安装的工具。
在 示例 14-2 中,我们将使应用程序创建并更新一个文件,/tmp/heartbeat.txt,以显示其仍在运行。我们通过每五秒运行 Unix 的 touch 命令来实现这一点。探针将定期检查文件的修改时间戳是否早于一分钟。如果是,则 Kubernetes 可以假定该应用程序未按预期运行,并将重新启动容器。
示例 14-2. 使用自定义命令的活跃性探针
apiVersion: v1
kind: Pod
metadata:
name: liveness-pod
spec:
containers:
- image: busybox:1.36.1
name: app
args:
- /bin/sh
- -c
- 'while true; do touch /tmp/heartbeat.txt; sleep 5; done;'
livenessProbe:
exec:
command:
- test `find /tmp/heartbeat.txt -mmin -1`
initialDelaySeconds: 5
periodSeconds: 30
下一个命令使用存储在文件liveness-probe.yaml中的 YAML 示例 14-2 清单来创建 Pod。描述 Pod 会呈现有关存活探测的信息。我们不仅可以检查自定义命令及其配置,还可以看到在探测失败时容器已重启的次数:
$ kubectl apply -f liveness-probe.yaml
pod/liveness-pod created
$ kubectl get pod liveness-pod
NAME READY STATUS RESTARTS AGE
pod/liveness-pod 1/1 Running 0 22s
$ kubectl describe pod liveness-pod
...
Containers:
app:
...
Restart Count: 0
Liveness: exec [test `find /tmp/heartbeat.txt -mmin -1`] delay=5s \
timeout=1s period=30s #success=1 #failure=3
...
启动探测
启动探测的目的是找出应用程序何时完全启动。定义探测对于启动时间较长的应用程序很有用。kubelet 在运行启动探测时将暂停就绪和存活探测。启动探测在以下情况下完成其操作:
-
如果可以验证应用程序已启动
-
如果应用程序在超时期内没有响应
为了演示启动探测的功能,示例 14-3 定义了一个 Pod,在容器中运行Apache HTTP 服务器。默认情况下,该镜像公开容器端口 80,我们使用 TCP 套接字连接来探测该端口。
示例 14-3. 使用 TCP 套接字连接的启动探测
apiVersion: v1
kind: Pod
metadata:
name: startup-pod
spec:
containers:
- image: httpd:2.4.46
name: http-server
startupProbe:
tcpSocket:
port: 80
initialDelaySeconds: 3
periodSeconds: 15
livenessProbe:
...
正如您在以下终端输出中所看到的那样,describe命令可以检索启动探测的配置:
$ kubectl apply -f startup-probe.yaml
pod/startup-pod created
$ kubectl get pod startup-pod
NAME READY STATUS RESTARTS AGE
pod/startup-pod 1/1 Running 0 31s
$ kubectl describe pod startup-pod
...
Containers:
http-server:
...
Startup: tcp-socket :80 delay=3s timeout=1s period=15s \
#success=1 #failure=3
...
总结
在本章中,我们查看了您可以为 Pod 定义的所有可用健康探测类型。健康探测是定期运行的迷你进程,询问运行在容器中的应用程序的状态。可以将其视为系统的脉搏检测。
就绪探测确保容器只接受传入流量,前提是应用程序正常运行。存活探测确保应用程序按预期运行,并在必要时重新启动容器。启动探测暂停存活探测,直到应用程序启动完成。实际上,您通常会发现一个容器定义了这三个探测。
考试要点
理解所有探测的目的
为了准备本节的考试,专注于理解和使用健康探测。您应理解启动、就绪和存活探测的目的,并练习如何配置它们。在您的 Kubernetes 集群中,尝试模拟成功和失败的条件,以查看探测的效果和它们采取的操作。
练习使用不同的验证方法
您可以选择多种适用于探测的验证方法。高层次了解何时应用哪种验证方法,以及如何配置每种方法。
示例练习
这些练习的解决方案可在附录 A 中找到。
-
在 YAML 清单中定义名为
web-server的新 Pod,使用镜像nginx:1.23.0。公开容器端口 80。暂时不要创建该 Pod。对于容器,声明一个类型为
httpGet的启动探针。验证 kubelet 能够请求到根上下文端点。使用探针的默认配置。对于容器,声明一个类型为
httpGet的就绪探针。验证 kubelet 能够请求到根上下文端点。在首次检查之前等待五秒钟。对于容器,声明一个类型为
httpGet的存活探针。验证 kubelet 能够请求到根上下文端点。在首次检查之前等待十秒钟。探针应每 30 秒运行一次检查。创建 Pod 并在过程中跟踪 Pod 的生命周期阶段。
检查 Pod 的探针的运行时详细信息。
第十五章:故障排除 Pod 和容器
在生产 Kubernetes 集群中操作应用程序时,故障几乎是不可避免的。您不能完全将此工作交给 Kubernetes 管理员——作为应用程序开发者,您有责任为您设计和部署的 Kubernetes 对象解决问题。
在本章中,我们将探讨一些故障排除策略,这些策略有助于识别问题的根本原因,以便您可以采取措施并适当地纠正故障。
故障排除 Pods
在大多数情况下,创建 Pod 没有问题。您只需发出run,create或apply命令以实例化 Pod。如果 YAML 清单形式正确,Kubernetes 将接受您的请求,因此可以假定一切正常工作。为了验证正确的行为,您首先需要做的是检查 Pod 的高级运行时信息。该操作可能涉及其他 Kubernetes 对象,如负责部署多个 Pod 副本的部署。
检索高级信息
为了检索信息,运行kubectl get pods命令以仅获取在命名空间中运行的 Pod,或者运行kubectl get all命令以检索命名空间中最突出的对象类型(包括部署)。您需要查看READY,STATUS和RESTARTS列。在最佳情况下,准备就绪的容器数量与您在spec中定义的容器数量相匹配。对于单容器 Pod,READY列将显示 1/1。
状态应显示为Running,以指示 Pod 已进入正确的生命周期状态。请注意,可能会发生 Pod 呈现Running状态,但实际上应用程序并未正常工作。如果重新启动次数大于 0,则可能需要检查活跃探测器(如果已定义)的逻辑,并确定重新启动所需的原因。
以下 Pod 观察到状态为ErrImagePull,并且使得 0/1 个容器对传入流量可用。简言之,此 Pod 存在问题:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
misbehaving-pod 0/1 ErrImagePull 0 2s
在使用 Kubernetes 一段时间后,您将自动识别常见的错误条件。表 15-1 列出了其中一些错误状态,并说明如何修复它们。
表 15-1。常见的 Pod 错误状态
| 状态 | 根本原因 | 潜在修复方案 |
|---|---|---|
ImagePullBackOff或ErrImagePull |
无法从注册表中拉取镜像。 | 检查正确的镜像名称,检查该镜像名称是否存在于注册表中,验证节点到注册表的网络访问,确保正确的身份验证。 |
CrashLoopBackOff |
容器中运行的应用程序或命令崩溃。 | 检查在容器中执行的命令,确保镜像可以正确执行(例如通过使用 Docker 创建容器)。 |
CreateContainerConfigError |
容器引用的 ConfigMap 或 Secret 未找到。 | 检查配置对象的正确名称,验证命名空间中配置对象的存在性。 |
检查事件
您可能不会遇到这些错误状态中的任何一个。但 Pod 可能存在配置问题的可能性。您可以使用kubectl describe pod命令检查其事件,以获取有关 Pod 的详细信息。
以下输出属于尝试挂载不存在的 Secret 的 Pod。该 Pod 并未呈现特定的错误消息,而是以ContainerCreating状态挂起:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
secret-pod 0/1 ContainerCreating 0 4m57s
$ kubectl describe pod secret-pod
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully \
assigned
default/secret-pod \
to minikube
Warning FailedMount 3m15s kubelet, minikube Unable to attach or \
mount volumes: \
unmounted \
volumes=[mysecret], \
unattached volumes= \
[default-token-bf8rh \
mysecret]: timed out \
waiting for the \
condition
Warning FailedMount 68s (x10 over 5m18s) kubelet, minikube MountVolume.SetUp \
failed for volume \
"mysecret" : secret \
"mysecret" not found
Warning FailedMount 61s kubelet, minikube Unable to attach or \
mount volumes: \
unmounted volumes= \
[mysecret], \
unattached \
volumes=[mysecret \
default-token-bf8rh \
]: timed out \
waiting for the \
condition
另一个有用的命令是kubectl get events。该命令的输出列出了给定命名空间内所有 Pod 的事件。您可以使用额外的命令行选项进一步过滤和排序事件:
$ kubectl get events
LAST SEEN TYPE REASON OBJECT MESSAGE
3m14s Warning BackOff pod/custom-cmd Back-off \
restarting \
failed container
2s Warning FailedNeedsStart cronjob/google-ping Cannot determine \
if job needs to \
be started: too \
many missed start \
time (> 100). Set \
or decrease \
.spec. \
startingDeadline \
Seconds or check \
clock skew
有时仅仅进行故障排除是不够的。您可能需要深入了解容器中的应用程序运行时行为和配置。
使用端口转发
在生产环境中,您将操作由 ReplicaSet 控制的多个 Pod 上的应用程序。其中一个副本遇到运行时问题并不罕见。您可以通过隧道化的 HTTP 连接将流量转发到 Pod,而不是从集群内的临时 Pod 中排查问题的 Pod。这就是port-forward命令的作用所在。
让我们演示这种行为。以下命令将创建一个新的 Deployment,在三个副本中运行 nginx:
$ kubectl create deployment nginx --image=nginx:1.24.0 --replicas=3 --port=80
deployment.apps/nginx created
结果的 Pod 将具有根据 Deployment 名称衍生的唯一名称。假设 Pod nginx-595dff4799-ph4js存在您想要排查的问题:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-595dff4799-pfgdg 1/1 Running 0 6m25s
nginx-595dff4799-ph4js 1/1 Running 0 6m25s
nginx-595dff4799-s76s8 1/1 Running 0 6m25s
port-forward命令将 HTTP 连接从本地端口转发到 Pod 中公开的端口。此示例命令将本地机器上的端口 2500 转发到运行在 Pod nginx-595dff4799-ph4js中的容器端口 80:
$ kubectl port-forward nginx-595dff4799-ph4js 2500:80
Forwarding from 127.0.0.1:2500 -> 80
Forwarding from [::1]:2500 -> 80
port-forward命令不会返回。您需要打开另一个终端,通过端口转发调用 Pod。以下命令仅仅检查 Pod 是否可从您的本地机器访问,使用curl:
curl -Is localhost:2500 | head -n 1
HTTP/1.1 200 OK
HTTP 响应码 200 明确显示我们可以从集群外访问 Pod。port-forward命令并非长时间运行的命令。它的主要目的是用于测试或排查 Pod,而无需通过 Service 公开它。
容器故障排除
您可以与容器交互,深入了解应用程序的运行时环境。接下来的章节将讨论如何检查日志,打开与容器的交互式 shell,并调试不提供 shell 的容器。
注意
下面描述的命令也适用于 init 和 sidecar 容器。如果运行的容器多于一个,可以使用 -c 或 --container 命令行标志来指定目标容器。有关多容器 Pod 的更多信息,请参见第 8 章。
检查日志
在故障排除 Pod 时,可以通过下载并检查其日志获取下一级别的详细信息。您可能会找到指向行为不端的 Pod 根本原因的额外信息,也可能不会,但肯定值得一试。示例 15-1 中显示的 YAML 清单定义了一个运行 Shell 命令的 Pod。
示例 15-1. 运行失败 Shell 命令的 Pod
apiVersion: v1
kind: Pod
metadata:
name: incorrect-cmd-pod
spec:
containers:
- name: test-container
image: busybox:1.36.1
command: ["/bin/sh", "-c", "unknown"]
创建对象后,Pod 失败,状态显示为 CrashLoopBackOff。运行 logs 命令显示容器中运行的命令存在问题:
$ kubectl create -f crash-loop-backoff.yaml
pod/incorrect-cmd-pod created
$ kubectl get pods incorrect-cmd-pod
NAME READY STATUS RESTARTS AGE
incorrect-cmd-pod 0/1 CrashLoopBackOff 5 3m20s
$ kubectl logs incorrect-cmd-pod
/bin/sh: unknown: not found
logs 命令提供了两个有用的选项。选项 -f 可以实时流式传输日志,意味着您将看到新的日志条目随着其实时生成。选项 --previous 获取容器先前实例的日志,如果容器已重新启动,则非常有帮助。
打开交互式 Shell
如果之前的任何命令都没有指向导致 Pod 失败的根本原因,那么现在是时候向容器打开交互式 Shell 了。作为应用程序开发者,您最了解在运行时从应用程序中期望什么行为。使用 Unix 或 Windows 实用工具来检查运行中的进程,具体取决于容器中运行的镜像。
假设您遇到一个情况,其中 Pod 表面上似乎正常工作,如示例 15-2 所示。
示例 15-2. 定期向文件写入当前日期的 Pod
apiVersion: v1
kind: Pod
metadata:
name: failing-pod
spec:
containers:
- args:
- /bin/sh
- -c
- while true; do echo $(date) >> ~/tmp/curr-date.txt; sleep \
5; done;
image: busybox:1.36.1
name: failing-pod
创建 Pod 后,检查其状态。状态显示为 Running;然而,当请求应用程序时,端点报告错误。接下来,检查日志。日志输出显示一个指向不存在目录的错误消息。显然,应用程序所需的目录尚未正确设置:
$ kubectl create -f failing-pod.yaml
pod/failing-pod created
$ kubectl get pods failing-pod
NAME READY STATUS RESTARTS AGE
failing-pod 1/1 Running 0 5s
$ kubectl logs failing-pod
/bin/sh: can't create /root/tmp/curr-date.txt: nonexistent directory
exec 命令打开一个交互式 Shell 以进一步调查问题。在下面的代码中,我们在运行的容器内使用 Unix 工具 mkdir、cd 和 ls 来解决问题。显然,更好的缓解策略是在应用程序中创建目录或在 Dockerfile 中提供指令:
$ kubectl exec failing-pod -it -- /bin/sh
# mkdir -p ~/tmp
# cd ~/tmp
# ls -l
total 4
-rw-r--r-- 1 root root 112 May 9 23:52 curr-date.txt
与 Distroless 容器交互
一些运行在容器中的镜像出于安全原因设计得非常精简。例如,Google distroless 镜像没有预安装任何 Unix 实用工具。甚至无法打开容器的 Shell,因为它不带 Shell。
为容器镜像集成安全最佳实践
以可访问的 shell 和使用 root 用户运行的方式来运输容器显然是不鼓励的,因为这些方面可能被用作潜在的攻击向量。查看 CKS 认证 以了解更多有关 Kubernetes 安全问题的信息。
Google 的一个 Distroless 镜像是 k8s.gcr.io/pause:3.1,如 示例 15-3 所示。
示例 15-3. 运行一个 Distroless 镜像
apiVersion: v1
kind: Pod
metadata:
name: minimal-pod
spec:
containers:
- image: k8s.gcr.io/pause:3.1
name: pause
正如您在以下 exec 命令中所见,该镜像不提供 shell:
$ kubectl create -f minimal-pod.yaml
pod/minimal-pod created
$ kubectl get pods minimal-pod
NAME READY STATUS RESTARTS AGE
minimal-pod 1/1 Running 0 8s
$ kubectl exec minimal-pod -it -- /bin/sh
OCI runtime exec failed: exec failed: container_linux.go:349: starting \
container process caused "exec: \"/bin/sh\": stat /bin/sh: no such file \
or directory": unknown
command terminated with exit code 126
Kubernetes 提供了 短暂容器 的概念。这些容器是可丢弃的,没有像探针这样的弹性功能。您可以为通常不允许使用 exec 命令的最小容器部署一个临时容器进行故障排除。
Kubernetes 1.18 引入了一个新的 debug 命令,可以为调试目的向运行中的 Pod 注入临时容器。以下命令将运行镜像 busybox 的临时容器添加到名为 minimal-pod 的 Pod,并为其打开交互式 shell:
$ kubectl alpha debug -it minimal-pod --image=busybox
Defaulting debug container name to debugger-jf98g.
If you don't see a command prompt, try pressing enter.
/ # pwd
/
/ # exit
Session ended, resume using 'kubectl alpha attach minimal-pod -c \
debugger-jf98g -i -t' command when the pod is running
检查资源指标
将软件部署到 Kubernetes 集群只是长期运行应用程序的开始。开发人员需要了解其应用程序的资源消耗模式和行为,目标是提供可扩展和可靠的服务。
在 Kubernetes 世界中,像 Prometheus 和 Datadog 这样的监控工具帮助收集、处理和可视化信息随着时间的推移。考试不要求您熟悉第三方监控、日志记录、跟踪和聚合工具;但了解负责收集使用度量的底层 Kubernetes 基础设施的基本原理是有帮助的。以下是典型指标的示例:
-
集群中的节点数量
-
节点的健康状态
-
如 CPU、内存、磁盘空间、网络等节点性能指标
-
如 CPU 和内存消耗等 Pod 级性能指标
这项责任落到 指标服务器,这是一个集群范围内的资源使用数据聚合器。如 图 15-1 所示,运行在节点上的 kubelet 收集指标并将其发送到指标服务器。

图 15-1. 指标服务器的数据收集
指标服务器将数据存储在内存中,并且不会随时间持久化数据。如果您正在寻找保留历史数据的解决方案,那么您需要查看商业选项。有关其安装过程的更多信息,请参阅文档。
如果您的练习环境是 Minikube,使用以下命令简单地启用 metrics-server 插件是直接的:enabling the metrics-server add-on。
$ minikube addons enable metrics-server
The 'metrics-server' addon is enabled
现在您可以使用top命令查询集群节点和 Pod 的指标:
$ kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
minikube 283m 14% 1262Mi 32%
$ kubectl top pod frontend
NAME CPU(cores) MEMORY(bytes)
frontend 0m 2Mi
安装指标服务器后,需要几分钟时间收集资源消耗信息。如果收到错误消息,请重新运行kubectl top命令。
摘要
我们讨论了解决失败或行为异常 Pod 的策略。主要目标是诊断失败的根本原因,然后采取正确的措施进行修复。故障排除 Pod 并不一定困难。通过正确的kubectl命令,您可以逐个排除根本原因,从而更清晰地了解情况。
Kubernetes 生态系统提供了许多选项来收集和处理集群的指标。其中包括像 Prometheus 和 Datadog 这样的商业监控工具。许多工具使用指标服务器作为这些指标的真实来源。我们还简要介绍了安装过程和从命令行检索指标的kubectl top命令。
考试要点
了解如何调试 Pod 对象
在本章中,我们主要专注于故障排除问题 Pod 和容器。练习所有相关的kubectl命令,这些命令可以帮助诊断问题。请参阅Kubernetes 文档以了解更多关于调试其他 Kubernetes 资源类型的信息。
学习如何检索和解释资源指标
监控 Kubernetes 集群是成功运行在真实环境中的重要方面。您应该阅读关于商业监控产品的信息,以及指标服务器可以收集哪些数据。您可以假设考试环境已安装指标服务器。学习如何使用kubectl top命令查看 Pod 和节点的资源指标并进行解释。
样例练习
这些练习的解决方案在附录 A 中可用。
-
在这个练习中,您将通过检查配置错误的 Pod 来练习故障排除技能。导航到检出的 GitHub 存储库bmuschko/ckad-study-guide的目录app-a/ch15/troubleshooting。
根据文件pod.yaml中的 YAML 清单创建一个新的 Pod。检查 Pod 的状态。是否有任何问题?
渲染正在运行的容器的日志并识别问题。进入容器的 Shell。您能根据渲染的日志消息验证问题吗?
建议解决方案,可以修复问题的根本原因。
-
要检查度量服务器收集的度量指标。导航到已检出的 GitHub 仓库 bmuschko/ckad-study-guide 的目录 app-a/ch15/stress-test。当前目录包含三个 Pod 的 YAML 清单,stress-1-pod.yaml、stress-2-pod.yaml 和 stress-3-pod.yaml。检查这些清单文件。
创建命名空间
stress-test和该命名空间内的 Pods。利用度量服务器提供的数据,确定哪个 Pod 消耗的内存最多。
先决条件: 如果要能够检查实际资源指标,您需要安装度量服务器。您可以在该项目的 GitHub 页面上找到安装说明。
第十六章:自定义资源定义(CRD)
Kubernetes 提供的原语支持应用程序堆栈操作员所需的最常见用例。对于定制用例,Kubernetes 允许实现和安装平台扩展。
CustomResourceDefinition(CRD)是 Kubernetes 的扩展机制,用于引入自定义 API 原语,以满足内置原语无法覆盖的需求。本章将主要关注 CRD 的实现和与之交互。
使用 CRD
CRD 可以被理解为定义自定义对象蓝图的模式,然后使用新引入的类型实例化这些对象。对于 CRD 的实用性,必须由控制器支持。控制器与 Kubernetes API 交互,并实现与 CRD 对象交互的协调逻辑。CRD 和控制器的组合通常称为操作者模式。考试不要求您理解控制器,因此不会涵盖它们的实现。图 16-1 显示了带有其所有组成部分的操作者模式。

图 16-1. Kubernetes 操作者模式
Kubernetes 社区在OperatorHub.io上实施了许多有用的操作者。您可以使用单个kubectl安装其中许多操作者。一个重要的操作者是External Secrets Operator,它帮助集成外部的秘密管理器(如 AWS Secrets Manager 和 HashiCorp Vault)与 Kubernetes。在考试中,您需要理解如何发现外部操作者提供的 CRD 模式以及如何与遵循 CRD 模式的对象进行交互。本章节我们将进一步讨论如何创建自己的 CRD 模式。
示例 CRD
最佳实践是通过示例来解释 CRD 的实现和交互。我们将实现和实例化一个 CRD,该 CRD 代表在将应用程序堆栈部署到 Kubernetes 后执行的服务对象的烟测试。
技术基线
假设您负责管理一个基于 Web 的应用程序。管理该应用程序所需的 Kubernetes 对象包括用于在 Pod 中运行应用程序的部署(Deployment)对象和用于将网络流量路由到副本的服务(Service)对象。
期望的功能
应在部署负责操作应用程序的 Kubernetes 对象后自动触发烟测试。在运行时,烟测试通过目标服务的 DNS 名称执行 HTTPS 调用到应用程序的端点。烟测试的结果(成功或失败)将被发送到外部服务,以便在仪表盘上呈现为图表和图形。
目标
要实现此功能,您可以决定编写一个 CRD 和控制器。在本章中,我们仅讨论了用于 smoke test 的 CRD,而不是执行 smoke test 的控制器。
实现 CRD 架构
要使 CRD 运行,您必须创建两个对象:自定义资源架构和自定义资源对象。自定义资源架构根据 OpenAPI v3 规范指定 CRD 蓝图。自定义资源对象遵循 CRD 架构的规范,并为架构可用的属性分配值。
使用 Kubebuilder 生成 CRD
CRD 架构规范可以变得非常广泛。为了避免手工编写这些规范,请查看 Kubebuilder 项目。Kubebuilder 的一个功能是能够使用用户提供的最小输入生成 CRD 架构。使用这个工具可以帮助节省启动 Kubernetes 操作者项目的时间。
首先,让我们看一下 smoke test 的自定义资源架构。如 示例 16-1 所示,架构使用 kind CustomResourceDefinition。
虽然我不能在此处解释每个 CustomResourceDefinition 属性的含义,但我想指出一些重要的方面。CustomResourceDefinition 指定了自定义基元的组、版本和名称。它还详细说明了所有可配置的属性,包括它们的数据类型。有关 CustomResourceDefinition 的更详细描述,请参阅 Kubernetes 文档。
示例 16-1. 代表 smoke test 的自定义资源架构
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: smoketests.stable.bmuschko.com 
spec:
group: stable.bmuschko.com 
versions:
- name: v1 
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties: 
spec:
type: object
properties:
service:
type: string
path:
type: string
timeout:
type: integer
retries:
type: integer
scope: Namespaced
names: 
plural: smoketests
singular: smoketest
kind: SmokeTest
shortNames:
- st
结合标识符 <plural>.<group> 的组合。
用于 CRD 的 API 组。
CRD 支持的版本。一个版本可以定义 0..n 个属性。
要设置的自定义类型属性。
自定义类型的标识符,例如 kind 和单数/复数/短名称。
文件 smoketest-resource.yaml 包含了前面示例中显示的 YAML 内容。您现在可以使用典型的 kubectl 命令为架构创建对象:
$ kubectl apply -f smoketest-resource.yaml
customresourcedefinition.apiextensions.k8s.io/smoketests.stable.bmuschko.com \
created
为 CRD 实例化一个对象
一旦架构对象创建完成,您可以为新的自定义类型创建对象。在 示例 16-2 中的 YAML 文件定义了一个名为 backend-smoke-test 的类型为 SmokeTest 的基元,用于名为 backend 的服务。正如您在示例中所见,可以指定额外的属性以微调其运行时行为。
示例 16-2. 实例化 CRD 种类 SmokeTest
apiVersion: stable.bmuschko.com/v1 
kind: SmokeTest 
metadata:
name: backend-smoke-test
spec:
service: backend 
path: /health 
timeout: 600 
retries: 3 
自定义类型的组和版本。
由 CRD 定义的种类。
使自定义类型可配置的属性及其值。
继续并从文件 smoketest.yaml 创建 SmokeTest 对象:
$ kubectl apply -f smoketest.yaml
smoketest.stable.bmuschko.com/backend-smoke-test created
你可以像操作 Kubernetes 中的任何其他对象一样与backend-smoke-test交互。例如,要列出对象,请使用get命令。要删除对象,请使用delete命令。以下命令展示了这些操作的执行过程:
$ kubectl get smoketest backend-smoke-test
NAME AGE
backend-smoke-test 12s
$ kubectl delete smoketest backend-smoke-test
smoketest.stable.bmuschko.com "backend-smoke-test" deleted
您可以根据需要创建更多此类型的对象以执行针对其他服务的冒烟测试,尽管每个对象都需要一个唯一的名称。我们现在已经放置了 CRD 模式对象。在下一节中,我们将与其交互。
发现 CRD
CRD 模式注册了一个新的 API 资源。每个 API 资源都可以被发现。您可以按照与内置 API 资源相同的方式列出自定义 API 资源。以下命令按 API 组 stable.bmuschko.com 列出 API 资源:
$ kubectl api-resources --api-group=stable.bmuschko.com
NAME SHORTNAMES APIVERSION NAMESPACED KIND
smoketests st stable.bmuschko.com/v1 true SmokeTest
可以像使用 kubectl 操作任何其他对象一样与 CRD 对象进行交互。您可以创建、读取、更新和删除它们。以下命令列出了集群中安装的所有 CRD:
$ kubectl get crds
NAME CREATED AT
smoketests.stable.bmuschko.com 2023-05-04T14:49:40Z
在输出中看到的 CRD 是代表冒烟测试的一个。如果在输出中看到其他 CRD,则可能是由您集群中安装的外部运算符提供的。
实现控制器
冒烟测试对象只表示数据,并且本身不会有用。您需要添加一个控制器实现,该实现对这些对象进行操作。简而言之,控制器通过调用 Kubernetes API 检查 CRD 对象的状态来作为协调过程。
在运行时,控制器实现需要轮询新的 SmokeTest 对象,并执行 HTTPS 请求到配置的服务端点。最后,控制器检查请求的响应,评估结果,并将其发送到外部服务以记录在数据库中。然后,仪表板可以查看历史结果,并根据需要渲染,例如作为图表和图形。
控制器可以使用 Kubernetes 客户端库之一(使用 Go 或 Python 编写)来访问自定义资源。访问相关文档以获取有关如何实现控制器的更多信息和示例。
摘要
CRD 模式定义了自定义资源的结构。模式包括组、名称、版本及其可配置的属性。在注册模式后,可以创建此类新对象。您可以使用 kubectl 与任何其他原语使用的相同 CRUD 命令来与自定义对象交互。
当与控制器实现结合使用时,CRDs 能够发挥其全部潜力。控制器实现检查特定自定义对象的状态,并根据其发现的状态做出反应。Kubernetes 将 CRD 及其对应的控制器称为操作员模式。Kubernetes 社区已经实现了许多操作员以满足定制需求。您可以将它们安装到您的集群中以重复使用这些功能。
考试要点
获取 CRD 模式的可配置选项的高级理解
您不需要实现 CRD 模式。您只需要知道如何使用kubectl发现和使用它们。控制器实现明显超出了考试范围。
练习安装和发现 CRD 的命令
学习如何使用kubectl get crds命令来发现已安装的 CRDs,并学习如何根据 CRD 模式创建对象。如果您想进一步探索,请安装一个开源的 CRD,比如Prometheus operator或者Jaeger operator,并检查它们的模式。
示例练习
这些练习的解决方案可以在附录 A 中找到。
-
您决定通过官方社区操作员的帮助,在 Kubernetes 中管理MongoDB安装。此操作员提供了一个 CRD。安装操作员后,您将与 CRD 进行交互。
导航到已签出的 GitHub 仓库bmuschko/ckad-study-guide的app-a/ch16/mongodb-operator目录。使用以下命令安装操作员:
kubectl apply -fmongodbcommunity.mongodb.com_mongodbcommunity.yaml。使用适当的
kubectl命令列出所有 CRDs。您能识别出安装过程中安装的 CRD 吗?检查 CRD 的模式。此 CRD 的类型和属性名称是什么?
-
作为应用程序开发者,您可能希望通过 Kubernetes 操作员模式安装扩展平台的 Kubernetes 功能。本练习的目标是熟悉创建和管理 CRDs。您不需要编写控制器。
使用以下规格创建名为
backup.example.com的 CRD 资源:-
分组:
example.com -
版本:
v1 -
类型:
Backup -
单数形式:
backup -
复数形式:
backups -
类型为
string的属性:cronExpression、podName、path
检索在前一步创建的
Backup自定义资源的详细信息。为 CRD 创建名为
nginx-backup的自定义对象。提供以下属性值:-
cronExpression:0 0 * * * -
podName:nginx -
path:/usr/local/nginx
检索在前一步创建的
nginx-backup对象的详细信息。 -
第十七章:认证、授权和准入控制
API 服务器是访问 Kubernetes 集群的入口。任何人类用户、客户端(例如kubectl)、集群组件或服务账号都将通过 HTTPS 进行 RESTful API 调用访问 API 服务器。这是执行操作(如创建 Pod 或删除 Service)的中心点。
在本章中,我们将重点关注与 API 服务器相关的安全特定方面。有关 API 服务器的内部工作原理及使用 Kubernetes API 的详细讨论,请参阅 Brendan Burns 和 Craig Tracey(O’Reilly)的管理 Kubernetes。
处理请求
图 17-1 说明了调用 API 服务器时请求经历的阶段。有关更多信息,请参阅Kubernetes 文档。

图 17-1. API 服务器请求处理
请求处理的第一阶段是认证。认证通过检查客户端证书或持有者令牌来验证调用者的身份。如果持有者令牌与服务账号关联,则在此处进行验证。
第二阶段确定第一阶段提供的身份是否可以访问动词和 HTTP 路径请求。因此,第二阶段处理请求的授权,使用标准的 Kubernetes RBAC 模型实现。在这里,我们确保服务账号被允许列出 Pod 或者在请求时创建新的 Service 对象。
请求处理的第三阶段涉及准入控制。准入控制验证请求是否格式良好或可能需要在处理请求之前进行修改。例如,准入控制策略可以确保创建 Pod 的请求包含特定标签的定义。如果请求未定义标签,则请求将被拒绝。
使用 kubectl 进行认证
开发人员通过运行kubectl命令行工具与 Kubernetes API 进行交互。每当使用kubectl执行命令时,需要通过 HTTPS 调用 API 服务器进行身份验证。
Kubeconfig
用于kubectl的凭证存储在文件$HOME/.kube/config中,也称为kubeconfig 文件。kubeconfig 文件定义了我们希望与之交互的集群的 API 服务器端点,以及注册到集群的用户列表,包括其客户端证书形式的凭证。给定命名空间的集群与用户之间的映射称为上下文。kubectl使用当前选择的上下文来确定要与之通信的集群以及要使用的凭证。
注意
你可以将环境变量 KUBECONFIG 指向一组 kubeconfig 文件。在运行时,kubectl 将合并定义的 kubeconfig 文件集的内容并使用它们。默认情况下,KUBECONFIG 未设置并且会回退到 $HOME/.kube/config。
示例 17-1 展示了一个 kubeconfig 文件。请注意,示例中分配的文件路径是特定于用户的,在您自己的环境中可能会有所不同。你可以在 Config resource type API 文档中找到所有可配置属性的详细描述。
示例 17-1. 一个 kubeconfig 文件
apiVersion: v1
kind: Config
clusters: 
- cluster:
certificate-authority: /Users/bmuschko/.minikube/ca.crt
extensions:
- extension:
last-update: Mon, 09 Oct 2023 07:33:01 MDT
provider: minikube.sigs.k8s.io
version: v1.30.1
name: cluster_info
server: https://127.0.0.1:63709
name: minikube
contexts: 
- context:
cluster: minikube
user: bmuschko
name: bmuschko
- context:
cluster: minikube
extensions:
- extension:
last-update: Mon, 09 Oct 2023 07:33:01 MDT
provider: minikube.sigs.k8s.io
version: v1.30.1
name: context_info
namespace: default
user: minikube
name: minikube
current-context: minikube 
preferences: {}
users: 
- name: bmuschko
user:
client-key-data: <REDACTED>
- name: minikube
user:
client-certificate: /Users/bmuschko/.minikube/profiles/minikube/client.crt
client-key: /Users/bmuschko/.minikube/profiles/minikube/client.key
集群及其 API 服务器端点的参考名称列表。
上下文的参考名称列表(集群和用户的组合)。
当前选择的上下文。
用户及其凭据的参考名称列表。
用户管理由集群管理员处理。管理员创建一个代表开发者的用户,并将相关信息(用户名和凭据)交给希望通过 kubectl 与集群交互的人类。另外,也可以通过外部身份提供者(例如 OpenID Connect)集成以进行身份验证。
手动创建新用户包括多个步骤,如 Kubernetes documentation 所述。然后,开发者将用户添加到旨在与集群交互的机器上的 kubeconfig 文件中。
使用 kubectl 管理 Kubeconfig
不需要手动编辑 kubeconfig 文件以更改或添加配置。Kubectl 提供了用于读取和修改其内容的命令。以下命令提供了概述。你可以在 kubectl cheatsheet 中找到更多命令示例。
要查看 kubeconfig 文件(们)的合并内容,请运行以下命令:
$ kubectl config view
apiVersion: v1
kind: Config
clusters:
...
要渲染当前选择的上下文,请使用 current-context 子命令。上下文名称为 minikube 是当前活动的。
$ kubectl config current-context
minikube
要更改上下文,请使用 use-context 子命令并提供名称。在这里,我们正在切换到上下文 bmuschko:
$ kubectl config use-context bmuschko
Switched to context "bmuschko".
要使用set-credentials子命令向 kubeconfig 文件注册用户,请选择分配用户名myuser并通过提供相应的 CLI 标志指向客户端证书:
$ kubectl config set-credentials myuser \
--client-key=myuser.key --client-certificate=myuser.crt \
--embed-certs=true
对于考试,请熟悉kubectl config命令。考试中的每个任务都要求您使用特定的上下文和/或命名空间。
基于角色的访问控制授权
我们了解到,API 服务器将尝试通过验证提供的凭据来认证使用kubectl发送的任何请求。经过认证的请求将需要根据分配给请求者的权限进行检查。API 处理工作流程的授权阶段检查操作是否允许针对请求的 API 资源进行。
在 Kubernetes 中,可以使用基于角色的访问控制(RBAC)来控制这些权限。简而言之,RBAC 通过允许或禁止对管理 API 资源的访问来定义用户、组和服务账户的策略。对于强调安全性的任何组织来说,启用和配置 RBAC 是强制性的。
设置权限是集群管理员的责任。在接下来的章节中,我们将简要讨论 RBAC 对来自用户和服务账户请求的影响。
RBAC 概述
RBAC 有助于实现各种用例:
-
建立一个系统,使具有不同角色的用户能够访问一组 Kubernetes 资源
-
控制在 Pod 中运行的进程(与服务账户相关联)并对 Kubernetes API 执行操作
-
限制每个命名空间内某些资源的可见性
RBAC 由三个关键构建块组成,如图 17-2 所示。它们将 API 基元及其允许的操作连接到所谓的主体,这可以是用户、组或服务账户。

图 17-2. RBAC 关键构建块
每个块的责任如下:
主体
想要访问资源的用户或服务账户
资源
Kubernetes API 资源类型(例如 Deployment 或节点)
动词
可对资源执行的操作(例如创建 Pod 或删除 Service)
理解 RBAC API 基元
在掌握了这些关键概念后,让我们来看看实现 RBAC 功能的 Kubernetes API 基元:
角色
角色 API 基元声明了规则应在特定命名空间内操作的 API 资源及其操作。例如,您可能希望说“允许列出和删除 Pod”,或者可以表达“允许监视 Pod 的日志”,或者两者兼而有之。任何未明确说明的操作在绑定到主体后即被禁止。
RoleBinding
RoleBinding API 原语将 Role 对象绑定到特定命名空间中的主体。这是使规则生效的关键。例如,您可能想说“将允许更新服务的角色绑定到用户约翰·多伊。”
图 17-3 显示了涉及的 API 原语之间的关系。请注意,该图像仅呈现了选定的 API 资源类型和操作列表。

图 17-3. RBAC 原语
以下各节演示了角色和 RoleBindings 的命名空间范围使用,但相同的操作和属性适用于讨论的“命名空间范围和集群范围的 RBAC” 中的集群范围角色和 RoleBindings。
默认用户可见角色
Kubernetes 定义了一组默认角色。您可以通过 RoleBinding 分配它们给主体,或根据需要定义自己的自定义角色。表 17-1 描述了默认的用户可见角色。
表 17-1. 默认的用户可见角色
| 默认 ClusterRole | 描述 |
|---|---|
| cluster-admin | 允许跨所有命名空间读取和写入资源。 |
| admin | 允许在命名空间内读取和写入资源,包括角色和角色绑定。 |
| edit | 允许在命名空间内读取和写入资源,除了角色和角色绑定。提供对 Secrets 的访问权限。 |
| view | 允许在命名空间内只读访问资源,除了角色、角色绑定和 Secrets。 |
要定义新的角色和 RoleBindings,您必须使用允许创建或修改它们的上下文,即 cluster-admin 或 admin。
创建角色
可以使用create role命令声明性地创建角色。该命令的最重要选项是--verb用于定义动词(即操作),以及--resource用于声明 API 资源列表(核心原语以及自定义资源定义)。以下命令创建了一个新的角色,用于资源 Pod、Deployment 和 Service,并分别使用动词list、get 和 watch:
$ kubectl create role read-only --verb=list,get,watch \
--resource=pods,deployments,services
role.rbac.authorization.k8s.io/read-only created
对于单个声明性create role命令声明多个动词和资源,可以作为相应命令行选项的逗号分隔列表或多个参数进行声明。例如,--verb=list,get,watch 和 --verb=list --verb=get --verb=watch 执行相同的指令。您还可以使用通配符“*”来指代所有动词或资源。
命令行选项--resource-name指定了一个或多个策略规则应适用的对象名称。Pod 的名称可以是nginx,可以在此处列出其名称。提供资源名称列表是可选的。如果未提供任何名称,则提供的规则将适用于资源类型的所有对象。
声明式方法可能会变得有些冗长。如在 示例 17-2 中所示,rules 部分列出了资源和动词。具有 API 组的资源(例如使用 API 版本 apps/v1 的 Deployments)需要在 apiGroups 属性下显式声明它。所有其他资源(例如 Pods 和 Services)只需使用空字符串,因为它们的 API 版本不包含组。请注意,创建 Role 的命令是命令式的,会自动确定 API 组。
示例 17-2. 定义 Role 的 YAML 清单
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: read-only
rules:
- apiGroups:
- ""
resources:
- pods
- services
verbs:
- list
- get
- watch
- apiGroups: 
- apps
resources:
- deployments
verbs:
- list
- get
- watch
任何属于 API 组的资源都需要作为显式规则列出,除了不属于 API 组的 API 资源之外。
列出角色
创建 Role 后,可以列出其对象。角色列表仅显示名称和创建时间戳。列出的每个角色都不会透露任何详细信息:
$ kubectl get roles
NAME CREATED AT
read-only 2021-06-23T19:46:48Z
渲染角色详情
您可以使用 describe 命令检查 Role 的详细信息。输出显示将资源映射到其允许的动词的表格:
$ kubectl describe role read-only
Name: read-only
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [] [list get watch]
services [] [] [list get watch]
deployments.apps [] [] [list get watch]
由于此集群没有创建资源,因此以下控制台输出中的资源名称列表当前为空。
创建 RoleBindings
创建 RoleBinding 对象的命令是 create rolebinding。要将角色绑定到 RoleBinding,使用 --role 命令行选项。可以通过声明选项 --user、--group 或 --serviceaccount 来分配主体类型。以下命令创建了名为 read-only-binding 的 RoleBinding,并绑定到名为 bmuschko 的用户:
$ kubectl create rolebinding read-only-binding --role=read-only --user=bmuschko
rolebinding.rbac.authorization.k8s.io/read-only-binding created
示例 17-3 展示了表示 RoleBinding 的 YAML 清单。从结构中可以看出,一个角色可以映射到一个或多个主体。数组的数据类型由 subjects 属性下的破折号字符表示。目前只有用户 bmuschko 被分配。
示例 17-3. 定义 RoleBinding 的 YAML 清单
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-only-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: read-only
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: bmuschko
列出 RoleBindings
RoleBindings 列表显示的最重要信息是关联的 Role。以下命令显示 RoleBinding read-only-binding 已映射到 Role read-only:
$ kubectl get rolebindings
NAME ROLE AGE
read-only-binding Role/read-only 24h
输出不提供主体的指示。您需要渲染对象的详细信息以获取更多信息,如下一节所述。
渲染 RoleBinding 详情
RoleBindings 可以使用 describe 命令进行检查。输出显示一个主体和分配的角色的表格。以下示例显示了名为 read-only-binding 的 RoleBinding 的描述表示:
$ kubectl describe rolebinding read-only-binding
Name: read-only-binding
Labels: <none>
Annotations: <none>
Role:
Kind: Role
Name: read-only
Subjects:
Kind Name Namespace
---- ---- ---------
User bmuschko
查看 RBAC 规则生效情况
现在,让我们看看 Kubernetes 如何为我们迄今设置的场景执行 RBAC 规则。首先,我们将使用 cluster-admin 权限创建一个新的部署。在 Minikube 中,默认情况下上下文 minikube 拥有这些权限:
$ kubectl config current-context
minikube
$ kubectl create deployment myapp --image=:1.25.2 --port=80 --replicas=2
deployment.apps/myapp created
现在,我们将切换到用户 bmuschko 的上下文:
$ kubectl config use-context bmuschko-context
Switched to context "bmuschko-context".
请记住,用户 bmuschko 被允许列出部署。我们将使用 get deployments 命令来验证:
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
myapp 2/2 2 2 8s
RBAC 规则仅允许列出部署、Pod 和服务。以下命令尝试列出 ReplicaSets,结果将导致错误:
$ kubectl get replicasets
Error from server (Forbidden): replicasets.apps is forbidden: User "bmuschko" \
cannot list resource "replicasets" in API group "apps" in the namespace "default"
当尝试使用除 list、get 或 watch 之外的动词时,可以观察到类似的行为。以下命令尝试删除一个部署:
$ kubectl delete deployment myapp
Error from server (Forbidden): deployments.apps "myapp" is forbidden: User \
"bmuschko" cannot delete resource "deployments" in API group "apps" in the \
namespace "default"
在任何时候,您都可以使用 auth can-i 命令检查用户的权限。该命令可以让您列出所有权限或检查特定权限:
$ kubectl auth can-i --list --as bmuschko
Resources Non-Resource URLs Resource Names Verbs
...
pods [] [] [list get watch]
services [] [] [list get watch]
deployments.apps [] [] [list get watch]
$ kubectl auth can-i list pods --as bmuschko
yes
命名空间范围和集群范围的 RBAC
Roles 和 RoleBindings 适用于特定的命名空间。在创建这两个对象时,您将不得不指定命名空间。有时,一组 Roles 和 RoleBindings 需要应用到多个命名空间,甚至整个集群。对于集群范围的定义,Kubernetes 提供了 API 资源类型 ClusterRole 和 ClusterRoleBinding。配置元素实际上是相同的。唯一的区别是 kind 属性的值:
-
要定义集群范围的角色,请使用命令
clusterrole或在 YAML 清单中使用类型ClusterRole。 -
要定义集群范围的 RoleBinding,请使用命令
clusterrolebinding或在 YAML 清单中使用类型ClusterRoleBinding。
ClusterRoles 和 ClusterRoleBindings 不仅为命名空间资源设置了集群范围的权限,还可以用于设置非命名空间资源(如 CRD 和节点)的权限。
使用服务账户工作
我们一直在使用 kubectl 可执行文件来对 Kubernetes 集群执行操作。在幕后,其实现通过对暴露的端点进行 HTTP 调用来调用 API 服务器。一些运行在 Pod 内部的应用程序可能也需要与 API 服务器通信。例如,该应用程序可能请求特定的集群节点信息或可用的命名空间。
Pod 可以通过认证令牌使用服务账户与 API 服务器进行身份验证。Kubernetes 管理员通过 RBAC 为服务账户分配规则,以授权访问特定资源和操作,如 图 17-4 所示。

图 17-4. 使用服务账户与 API 服务器通信
Pod 不一定需要参与此过程。其他用例需要在 Kubernetes 集群外部利用服务账户。例如,您可能希望在 CI/CD 管道自动化步骤中与 API 服务器通信。服务账户可以提供凭据来与 API 服务器进行身份验证。
默认服务账户
到目前为止,我们还没有为 Pod 定义服务账户。如果没有显式分配,Pod 将使用与未经身份验证用户相同权限的 default service account。这意味着 Pod 无法查看或修改集群状态,也不能列出或修改其任何资源。但 default 服务账户可以通过分配的 system:discovery 角色请求基本集群信息。
您可以使用子命令 serviceaccounts 查询可用的服务账户。您应该只看到输出中列出的 default 服务账户。
$ kubectl get serviceaccounts
NAME SECRETS AGE
default 0 4d
尽管可以执行 kubectl 操作删除 default 服务账户,但 Kubernetes 会立即重新实例化该服务账户。
创建服务账户
您可以使用命令式和声明式方法创建自定义服务账户对象。此命令创建一个名为 cicd-bot 的服务账户对象。这里的假设是使用此服务账户进行由 CI/CD 管道发起的对 API 服务器的调用:
$ kubectl create serviceaccount cicd-bot
serviceaccount/cicd-bot created
您还可以以清单的形式表示服务账户。在其最简单形式中,定义指定了 ServiceAccount 种类和名称,如 示例 17-4 所示。
示例 17-4. 用于服务账户的 YAML 清单
apiVersion: v1
kind: ServiceAccount
metadata:
name: cicd-bot
您可以为服务账户设置一些配置选项。例如,当将服务账户分配给 Pod 时,您可能希望禁用自动挂载身份验证令牌。尽管您在考试中不需要理解这些配置选项,但通过阅读 Kubernetes 文档深入了解安全最佳实践是明智的选择。
为服务账户设置权限
重要的是仅限制必要应用程序功能的服务账户权限。接下来的章节将解释如何实现此目标,以尽量减少潜在的攻击面。
要使此场景正常工作,您需要创建一个 ServiceAccount 对象并将其分配给 Pod。可以将服务账户与 RBAC 配置并通过 Role 和 RoleBinding 分配角色,以定义它们应执行的操作。
将服务账户绑定到 Pod
作为起点,我们将设置一个 Pod,通过调用 Kubernetes API 列出命名空间 k97 中的所有 Pods 和 Deployments。该调用作为每十秒无限循环的一部分执行。来自 API 调用的响应将写入标准输出,可通过 Pod 的日志访问。
访问 API 服务器端点
从 Pod 访问 Kubernetes API 很简单。不必使用 API 服务器 Pod 的 IP 地址和端口,只需直接引用名为 kubernetes.default.svc 的 Service 即可。这个特殊的 Service 存在于 default 命名空间,并且由集群自动创建。
要对 API 服务器进行身份验证,我们将发送与 Pod 使用的服务账号关联的令牌。服务账号的默认行为是在路径 /var/run/secrets/kubernetes.io/serviceaccount/token 上自动挂载 API 凭据。我们只需使用 cat 命令行工具获取文件内容,并将其作为 HTTP 请求的标头发送。示例 17-5 在单个 YAML 清单文件 setup.yaml 中定义了命名空间、服务账号和 Pod。
示例 17-5. 分配服务账号给 Pod 的 YAML 清单
apiVersion: v1
kind: Namespace
metadata:
name: k97
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sa-api
namespace: k97
---
apiVersion: v1
kind: Pod
metadata:
name: list-objects
namespace: k97
spec:
serviceAccountName: sa-api 
containers:
- name: pods
image: alpine/curl:3.14
command: ['sh', '-c', 'while true; do curl -s -k -m 5 -H \
"Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/ \
serviceaccount/token)" https://kubernetes.default.svc.cluster. \
local/api/v1/namespaces/k97/pods; sleep 10; done'] 
- name: deployments
image: alpine/curl:3.14
command: ['sh', '-c', 'while true; do curl -s -k -m 5 -H \
"Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/ \
serviceaccount/token)" https://kubernetes.default.svc.cluster. \
local/apis/apps/v1/namespaces/k97/deployments;
sleep 10; done'] 
使用名称引用的服务账号,用于与 Kubernetes API 通信。
执行 API 调用以获取命名空间 k97 中 Pod 的列表。
执行 API 调用以获取命名空间 k97 中部署的列表。
使用以下命令从 YAML 清单创建对象:
$ kubectl apply -f setup.yaml
namespace/k97 created
serviceaccount/sa-api created
pod/list-objects created
验证默认权限
名为 list-objects 的 Pod 在专用容器中调用 API 服务器以获取 Pod 和 Deployments 的列表。容器 pods 执行调用以列出 Pods。容器 deployments 向 API 服务器发送请求以列出 Deployments。
如 Kubernetes 文档 中所述,默认的 RBAC 策略不授予 kube-system 命名空间外的服务账号任何权限。容器 pods 和 deployments 的日志返回一个错误消息,指示服务账号 sa-api 未经授权列出资源:
$ kubectl logs list-objects -c pods -n k97
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:k97:sa-api\" \
cannot list resource \"pods\" in API group \"\" in the \
namespace \"k97\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}
$ kubectl logs list-objects -c deployments -n k97
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "deployments.apps is forbidden: User \
\"system:serviceaccount:k97:sa-api\" cannot list resource \
\"deployments\" in API group \"apps\" in the namespace \
\"k97\"",
"reason": "Forbidden",
"details": {
"group": "apps",
"kind": "deployments"
},
"code": 403
}
接下来,我们将创建一个带有必要 API 权限的 Role 和 RoleBinding 对象。
创建角色
首先在 role.yaml 文件中定义名为 list-pods-role 的角色,如 示例 17-6 所示。该规则集仅添加 Pod 资源和动词 list。
示例 17-6. 允许列出 Pods 的 Role 的 YAML 清单
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: list-pods-role
namespace: k97
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
通过指向相应的 YAML 清单文件创建对象:
$ kubectl apply -f role.yaml
role.rbac.authorization.k8s.io/list-pods-role created
创建 RoleBinding
示例 17-7 定义了 rolebinding.yaml 文件中 RoleBinding 的 YAML 清单。RoleBinding 将角色 list-pods-role 映射到名为 sa-pod-api 的服务账号,并且仅适用于命名空间 k97。
示例 17-7. 附加到服务账户的 RoleBinding 的 YAML 清单
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: serviceaccount-pod-rolebinding
namespace: k97
subjects:
- kind: ServiceAccount
name: sa-api
roleRef:
kind: Role
name: list-pods-role
apiGroup: rbac.authorization.k8s.io
使用apply命令创建两个 RoleBinding 对象:
$ kubectl apply -f rolebinding.yaml
rolebinding.rbac.authorization.k8s.io/serviceaccount-pod-rolebinding created
验证授予的权限
给予list权限后,服务账户现在能够正确地检索k97命名空间中的所有 Pods。pods容器中的curl命令成功执行,如下输出所示:
$ kubectl logs list-objects -c pods -n k97
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "628"
},
"items": [
{
"metadata": {
"name": "list-objects",
"namespace": "k97",
...
}
]
}
我们未授予服务账户其他资源的任何权限。在k97命名空间中列出 Deployments 仍然失败。以下输出显示了在deployments命名空间中curl命令的响应:
$ kubectl logs list-objects -c deployments -n k97
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "deployments.apps is forbidden: User \
\"system:serviceaccount:k97:sa-api\" cannot list resource \
\"deployments\" in API group \"apps\" in the namespace \
\"k97\"",
"reason": "Forbidden",
"details": {
"group": "apps",
"kind": "deployments"
},
"code": 403
}
随意修改 Role 对象,以允许列出 Deployment 对象。
准入控制
处理向 Kubernetes API 服务器发出请求的最后阶段是准入控制。准入控制由准入控制器实现。准入控制器提供了在请求生效之前批准、拒绝或修改请求的方式。
可以通过 API 服务器的配置注册准入控制器。默认情况下,配置文件位于/etc/kubernetes/manifests/kube-apiserver.yaml。集群管理员负责管理 API 服务器配置。以下 API 服务器的命令行调用启用了名为NamespaceLifecycle、PodSecurity和LimitRanger的准入控制插件:
$ kube-apiserver --enable-admission-plugins=NamespaceLifecycle,PodSecurity,\
LimitRanger
作为开发者,您无意间使用已为您配置的准入控制插件。一个例子是LimitRanger和ResourceQuota,我们将在“使用限制范围”和“使用资源配额”中讨论它们。
概述
API 服务器处理对 Kubernetes API 的请求。每个请求都必须经过三个阶段:认证、授权和准入控制。每个阶段都可以直接终止处理过程。例如,如果请求中发送的凭据无法进行认证,则该请求将立即被丢弃。
我们查看了所有阶段的示例。认证阶段涵盖了kubectl作为调用 Kubernetes API 的客户端。kubeconfig 文件作为命名集群、用户及其凭据的配置源。在 Kubernetes 中,授权由 RBAC 处理。我们了解了让您为与一个或多个主体关联的 API 资源配置权限的 Kubernetes 原语。最后,我们简要介绍了准入控制的目的,并列出了一些作为验证或变异 Kubernetes API 请求控制器的插件。
考试要点
练习与 Kubernetes API 交互
本章展示了一些与 Kubernetes API 通信的方法。我们通过切换到用户上下文并借助 curl 进行了 RESTful API 调用来执行 API 请求。请自行探索 Kubernetes API 及其端点,以获得更广泛的了解。
理解为用户和服务账户定义 RBAC 规则的影响
匿名用户对 Kubernetes API 的请求不会允许任何实质性操作。对来自用户或服务账户的请求,您需要仔细分析授予主体的权限。通过创建相关对象来学习定义 RBAC 规则的细节。在 Pod 中使用时,服务账户会自动挂载一个令牌。仅在打算从 Pod 中进行 API 调用时,将令牌公开为一个卷。
了解准入控制的基本需求
在考试中,您无需了解如何在 API 服务器中配置准入控制插件。开发人员与其交互,但配置任务由集群管理员负责。阅读不同的插件以更好地理解准入控制的环境。
样本练习
这些练习的解决方案可在 附录 A 中找到。
-
本练习的前提是创建一个新用户并将其添加到 kubeconfig 文件中。然后,您将定义一个使用该用户的上下文,切换到该上下文,并执行一个
kubectl命令。为名为
mary的用户创建证书。不为用户提供任何权限。将用户添加到 kubeconfig 文件。定义名为
mary-context的上下文,将用户分配到 kubeconfig 文件中已有的集群中。将当前选定的上下文设置为
mary-context。使用kubectl创建一个 Pod。您预期会看到什么结果? -
使用 RBAC 为服务账户授予权限。权限应仅适用于特定的 API 资源和操作。
创建一个名为
t23的新命名空间。在命名空间t23中创建一个名为 service-list 的 Pod。容器使用镜像alpine/curl:3.14,并在无限循环中对 Kubernetes API 进行curl调用。创建并附加服务账户
api-call到 Pod。启动 Pod 后,检查容器日志。您预期从
curl命令获得什么响应?为允许 Pod 所需操作的服务账户分配一个 ClusterRole 和 RoleBinding。注意
curl命令的响应。 -
确定已配置的 API 服务器准入控制器插件。
定位 API 服务器的配置文件。
检查定义 API 服务器准入控制器插件的命令行标志。捕获其值。
第十八章:资源需求、限制和配额
在 Pod 中执行的工作负载将消耗一定量的资源(例如 CPU 和内存)。您应该为这些应用程序定义资源需求。在容器级别,可以定义运行应用程序所需的最小资源量,以及应用程序允许消耗的最大资源量。应用程序开发人员应通过负载测试或在运行时通过监控资源消耗来确定合适的资源大小。
注释
Kubernetes 使用毫核 CPU 和字节内存来衡量资源。这就是为什么您可能会看到资源定义为 600m 或 100Mi。深入了解这些资源单位,值得参考官方文档中关于 “Kubernetes 中的资源单位” 的部分。
Kubernetes 管理员可以采取措施来强制使用可用资源容量。我们将讨论两个 Kubernetes 原语,ResourceQuota 和 LimitRange。ResourceQuota 在命名空间级别定义聚合资源约束。LimitRange 是一种策略,用于约束或为特定类型的单个对象(如 Pod 或 PersistentVolumeClaim)的资源分配设置默认值。
处理资源需求
推荐的最佳实践是为每个容器指定资源请求和限制。确定这些资源期望并不总是容易,特别是对于尚未在生产环境中运行的应用程序。在开发周期早期对应用程序进行负载测试可以帮助分析资源需求。将应用程序部署到集群后,可以通过监控应用程序的资源消耗进一步进行调整。
定义容器资源请求
用于工作负载调度的一个指标是 Pod 中容器定义的 资源请求。常用的可指定资源包括 CPU 和内存。调度器确保节点的资源容量能够满足 Pod 的资源需求。具体而言,调度器确定 Pod 中所有容器定义的每种资源类型的资源请求总和,并将其与节点的可用资源进行比较。
每个 Pod 中的每个容器都可以定义自己的资源请求。表 18-1 描述了可用的选项,包括一个示例值。
表 18-1. 资源请求选项
| YAML 属性 | 描述 | 示例值 |
|---|---|---|
spec.containers[].resources.requests.cpu |
CPU 资源类型 | 500m(五百毫 CPU) |
spec.containers[].resources.requests.memory |
内存资源类型 | 64Mi(2²⁶ 字节) |
spec.containers[].resources.requests.hugepages-<size> |
大页资源类型 | hugepages-2Mi: 60Mi |
spec.containers[].resources.requests.ephemeral-storage |
临时存储资源类型 | 4Gi |
为了澄清这些资源请求的用途,我们将看一个示例定义。在 示例 18-1 中显示的 Pod YAML 清单定义了两个容器,每个容器都有自己的资源请求。允许运行该 Pod 的任何节点需要能够支持至少 320Mi 的内存容量和 1250m 的 CPU,这是两个容器资源的总和。
示例 18-1. 设置容器资源请求
apiVersion: v1
kind: Pod
metadata:
name: rate-limiter
spec:
containers:
- name: business-app
image: bmuschko/nodejs-business-app:1.0.0
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "1"
- name: ambassador
image: bmuschko/nodejs-ambassador:1.0.0
ports:
- containerPort: 8081
resources:
requests:
memory: "64Mi"
cpu: "250m"
当节点上的资源不足以安排 Pod 时,Pod 的事件日志将使用 PodExceedsFreeCPU 或 PodExceedsFreeMemory 的原因指示此情况。有关如何排查和解决此情况的详细信息,请参阅相关 文档部分。
定义容器资源限制
另一个您可以为容器设置的度量是 资源限制。资源限制确保容器不能消耗超过分配的资源量。例如,您可以表达在容器中运行的应用程序应受到 1000m CPU 和 512Mi 内存的限制。
根据集群使用的容器运行时,超过允许的任何资源限制都会导致终止在容器中运行的应用程序进程或导致系统阻止超出限制的资源分配。有关容器运行时 Docker Engine 如何处理资源限制的深入讨论,请参阅 文档。
表 18-2 描述了可用选项,包括示例值。
表 18-2. 资源限制选项
| YAML 属性 | 描述 | 示例值 |
|---|---|---|
spec.containers[].resources.limits.cpu |
CPU 资源类型 | 500m (500 毫核) |
spec.containers[].resources.limits.memory |
内存资源类型 | 64Mi (2²⁶ 字节) |
spec.containers[].resources.limits.hugepages-<size> |
大页资源类型 | hugepages-2Mi: 60Mi |
spec.containers[].resources.limits.ephemeral-storage |
临时存储资源类型 | 4Gi |
示例 18-2 展示了限制定义的实际操作。在这里,名为 business-app 的容器不能使用超过 512Mi 的内存。名为 ambassador 的容器定义了 128Mi 内存的限制。
示例 18-2. 设置容器资源限制
apiVersion: v1
kind: Pod
metadata:
name: rate-limiter
spec:
containers:
- name: business-app
image: bmuschko/nodejs-business-app:1.0.0
ports:
- containerPort: 8080
resources:
limits:
memory: "256Mi"
- name: ambassador
image: bmuschko/nodejs-ambassador:1.0.0
ports:
- containerPort: 8081
resources:
limits:
memory: "64Mi"
定义容器资源请求和限制
要向 Kubernetes 提供你的应用程序资源期望的全貌,你必须为每个容器指定资源请求和限制。示例 18-3 将资源请求和限制结合在单个 YAML 清单中。
示例 18-3. 设置容器资源请求和限制
apiVersion: v1
kind: Pod
metadata:
name: rate-limiter
spec:
containers:
- name: business-app
image: bmuschko/nodejs-business-app:1.0.0
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "1"
limits:
memory: "256Mi"
- name: ambassador
image: bmuschko/nodejs-ambassador:1.0.0
ports:
- containerPort: 8081
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "64Mi"
分配静态容器资源需求是一个近似的过程。你希望在你的 Kubernetes 集群中最大化资源的有效利用。不幸的是,Kubernetes 文档并没有提供很多关于最佳实践的指导。博客文章 “停止在 Kubernetes 上使用 CPU 限制” 提供了以下建议:
-
总是定义内存请求。
-
总是定义内存限制。
-
总是将内存请求设置为与限制相等。
-
总是定义 CPU 请求。
-
不要使用 CPU 限制。
在将你的应用程序投入生产后,仍然需要监控应用程序的资源消耗模式。在运行时审查资源消耗,并跟踪实际的调度行为以及应用程序接收负载后可能出现的不良行为。找到一个合适的平衡可能会令人沮丧。类似 Goldilocks 和 KRR 的项目出现,提供了关于适当确定资源请求的建议和指导。其他选择,如 Kubernetes 1.27 中引入的 容器调整策略,允许更精细地控制容器的 CPU 和内存资源在运行时如何自动调整大小。
使用 Resource Quotas
Kubernetes 的原始 ResourceQuota 定义了每个命名空间可用的资源的最大数量。一旦生效,Kubernetes 调度器将负责强制执行这些规则。下面的列表应该让你对可以定义的规则有一个了解:
-
设置特定类型可以创建的对象数量的上限(例如,最多三个 Pod)。
-
限制计算资源的总和(例如,3Gi RAM)。
-
期望 Pod 的服务质量(QoS)类别(例如,
BestEffort表示 Pod 不应设置任何内存或 CPU 的限制或请求)。
创建 ResourceQuotas
创建 ResourceQuota 对象通常是 Kubernetes 管理员的任务,但定义和创建这样的对象相对简单。首先,创建应用此配额的命名空间:
$ kubectl create namespace team-awesome
namespace/team-awesome created
接下来,在 YAML 中定义 ResourceQuota。为了展示 ResourceQuota 的功能性,向命名空间添加约束,如 示例 18-4 所示。
示例 18-4. 使用 ResourceQuota 定义硬资源限制
apiVersion: v1
kind: ResourceQuota
metadata:
name: awesome-quota
namespace: team-awesome
spec:
hard:
pods: 2 
requests.cpu: "1" 
requests.memory: 1024Mi 
limits.cpu: "4" 
limits.memory: 4096Mi 
将 Pod 的数量限制为 2 个。
定义处于非终端状态的所有 Pod 的最小资源请求为 1 个 CPU 和 1024Mi RAM。
定义处于非终端状态的所有 Pod 使用的最大资源为 4 个 CPU 和 4096Mi RAM。
您已准备好为命名空间创建 ResourceQuota:
$ kubectl create -f awesome-quota.yaml
resourcequota/awesome-quota created
渲染 ResourceQuota 详情
你可以使用kubectl describe命令渲染已使用资源与硬限制的表格概述:
$ kubectl describe resourcequota awesome-quota -n team-awesome
Name: awesome-quota
Namespace: team-awesome
Resource Used Hard
-------- ---- ----
limits.cpu 0 4
limits.memory 0 4Gi
pods 0 2
requests.cpu 0 1
requests.memory 0 1Gi
在硬限制列出了与 ResourceQuota 定义提供的相同值。只要您不修改对象的规范,这些值不会改变。在已使用列下,您可以找到命名空间内的实际聚合资源消耗。目前,所有值均为 0,因为尚未创建任何 Pod。
探索 ResourceQuota 的运行时行为
在命名空间team-awesome中实施了配额规则后,我们希望看到其执行情况。我们将从创建超过最大 Pod 数目的实验开始,这个数目是两个。为了测试这一点,我们可以创建任何我们喜欢的定义 Pod。例如,我们可以使用一个最基本的定义,该定义在容器中运行图像nginx:1.25.3,如示例 18-5 所示。
示例 18-5. 没有资源需求的 Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: team-awesome
spec:
containers:
- image: nginx:1.25.3
name: nginx
根据存储在 nginx-pod.yaml 中的 YAML 定义,让我们创建一个 Pod 并看看会发生什么。实际上,Kubernetes 将拒绝创建对象,并显示以下错误信息:
$ kubectl apply -f nginx-pod.yaml
Error from server (Forbidden): error when creating "nginx-pod.yaml": \
pods "nginx" is forbidden: failed quota: awesome-quota: must specify \
limits.cpu for: nginx; limits.memory for: nginx; requests.cpu for: \
nginx; requests.memory for: nginx
因为我们在命名空间中为对象定义了最小和最大资源配额,所以我们必须确保 Pod 对象实际上定义了资源请求和限制。通过更新resources指令来修改初始定义,如示例 18-6 所示。
示例 18-6. 具有资源需求的 Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: team-awesome
spec:
containers:
- image: nginx:1.25.3
name: nginx
resources:
requests:
cpu: "0.5"
memory: "512Mi"
limits:
cpu: "1"
memory: "1024Mi"
我们应该能够创建两个具有唯一名称的 Pod——nginx1和nginx2——使用该清单;合并后的资源需求仍符合 ResourceQuota 中定义的边界:
$ kubectl apply -f nginx-pod1.yaml
pod/nginx1 created
$ kubectl apply -f nginx-pod2.yaml
pod/nginx2 created
$ kubectl describe resourcequota awesome-quota -n team-awesome
Name: awesome-quota
Namespace: team-awesome
Resource Used Hard
-------- ---- ----
limits.cpu 2 4
limits.memory 2Gi 4Gi
pods 2 2
requests.cpu 1 1
requests.memory 1Gi 1Gi
假设我们尝试使用nginx1和nginx2的定义创建另一个 Pod,你或许能够想象出会发生什么。它会因为两个原因而失败。第一个原因是我们不被允许在命名空间中创建第三个 Pod,因为最大数目被设置为两个。第二个原因是我们将超出requests.cpu和requests.memory的最大分配限制。以下错误信息提供了这些信息:
$ kubectl apply -f nginx-pod3.yaml
Error from server (Forbidden): error when creating "nginx-pod3.yaml": \
pods "nginx3" is forbidden: exceeded quota: awesome-quota, requested: \
pods=1,requests.cpu=500m,requests.memory=512Mi, used: pods=2,requests.cpu=1,\
requests.memory=1Gi, limited: pods=2,requests.cpu=1,requests.memory=1Gi
使用限制范围
在前一节中,您了解了资源配额如何限制特定命名空间中资源的总体消耗。对于单个 Pod 对象,资源配额无法设置任何约束。这就是 LimitRange 的作用。在处理 API 请求时,LimitRange 规则的执行发生在准入控制阶段。
LimitRange 是 Kubernetes 的一个原语,用于约束或默认特定对象类型的资源分配:
-
在命名空间中强制 Pod 或容器的最小和最大计算资源使用
-
在命名空间中强制每个 PersistentVolumeClaim 的最小和最大存储请求
-
强制在命名空间中资源请求和限制之间设置比率
-
在命名空间中为计算资源设置默认请求/限制,并在运行时自动注入到容器中
在命名空间中定义多个 LimitRange
最好每个命名空间只创建一个 LimitRange 对象。在同一命名空间中由多个 LimitRange 对象指定的默认资源请求和限制会导致这些规则的非确定性选择。只有一个默认定义会生效,但无法预测哪一个会生效。
创建 LimitRange
LimitRange 提供了一系列可配置的约束属性列表。所有这些属性在 Kubernetes API 文档中的LimitRangeSpec中都有详细描述。示例 18-7 展示了使用某些约束属性的 LimitRange 的 YAML 清单。
示例 18-7。定义多个约束条件的 LimitRange
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-resource-constraint
spec:
limits:
- type: Container 
defaultRequest: 
cpu: 200m
default: 
cpu: 200m
min: 
cpu: 100m
max: 
cpu: "2"
应用约束的上下文。在这种情况下,应用到运行在 Pod 中的容器。
如果未提供,分配给容器的默认 CPU 资源请求值。
如果未提供,分配给容器的默认 CPU 资源限制值。
可分配给容器的最小和最大 CPU 资源请求和限制值。
通常情况下,我们可以使用kubectl create或kubectl apply命令从清单中创建对象。LimitRange 的定义已存储在文件cpu-resource-constraint-limitrange.yaml中:
$ kubectl apply -f cpu-resource-constraint.yaml
limitrange/cpu-resource-constraint created
当创建新对象时,约束将自动应用。更改现有 LimitRange 对象的约束对已运行的 Pods 没有影响。
渲染 LimitRange 详细信息
使用kubectl describe命令可以查看实时的 LimitRange 对象。以下命令呈现了名为cpu-resource-constraint的 LimitRange 对象的详细信息:
$ kubectl describe limitrange cpu-resource-constraint
Name: cpu-resource-constraint
Namespace: default
Type Resource Min Max Default Request Default Limit ...
---- -------- --- --- --------------- -------------
Container cpu 100m 2 200m 200m ...
命令的输出将每个限制约束渲染为单独的行。任何对象未显式设置的约束属性将显示破折号 (-) 作为分配的值。
探索 LimitRange 的运行行为
让我们演示 LimitRange 对 Pod 创建的影响。我们将讨论两种不同的使用情况:
-
如果 Pod 定义没有提供资源要求,则自动设置资源要求。
-
如果声明的资源要求被 LimitRange 禁止,则阻止创建 Pod。
设置默认资源要求
LimitRange 定义了默认的 CPU 资源请求为 200m,CPU 资源限制为 200m。这意味着如果要创建一个 Pod,并且它没有定义 CPU 资源请求和限制,LimitRange 将自动分配默认值。
示例 18-8 展示了一个没有资源要求的 Pod 定义。
示例 18-8. 定义无资源要求的 Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx-without-resource-requirements
spec:
containers:
- image: nginx:1.25.3
name: nginx
根据存储在文件 nginx-without-resource-requirements.yaml 中的内容创建对象将按预期工作:
$ kubectl apply -f nginx-without-resource-requirements.yaml
pod/nginx-without-resource-requirements created
Pod 对象将以两种方式被修改。首先,LimitRange 设置的默认资源要求将被应用。其次,将添加一个键为 kubernetes.io/limit-ranger 的注释,提供关于已更改内容的元信息。您可以在 describe 命令的输出中找到这两个信息:
$ kubectl describe pod nginx-without-resource-requirements
...
Annotations: kubernetes.io/limit-ranger: LimitRanger plugin set: cpu \
request for container nginx; cpu limit for container nginx
...
Containers:
nginx:
...
Limits:
cpu: 200m
Requests:
cpu: 200m
...
强制资源要求
LimitRange 也可以强制资源限制。对于我们之前创建的 LimitRange 对象,CPU 的最小值设置为 100m,最大值设置为 2. 为了查看执行行为,我们将创建一个新的 Pod,如 示例 18-9 中所示。
示例 18-9. 定义 CPU 资源请求和限制的 Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-resource-requirements
spec:
containers:
- image: nginx:1.25.3
name: nginx
resources:
requests:
cpu: "50m"
limits:
cpu: "3"
这个 Pod 的资源要求不符合 LimitRange 对象期望的约束。CPU 资源请求少于 100m,CPU 资源限制高于 2. 因此,对象将不会被创建,并将显示适当的错误消息:
$ kubectl apply -f nginx-with-resource-requirements.yaml
Error from server (Forbidden): error when creating "nginx-with-resource-\
requirements.yaml": pods "nginx-with-resource-requirements" is forbidden: \
[minimum cpu usage per Container is 100 m, but request is 50 m, maximum cpu \
usage per Container is 2, but limit is 3]
错误消息提供了关于预期资源定义的一些指导。不幸的是,消息没有指出实施这些期望的 LimitRange 对象的名称。请主动检查命名空间中是否已创建 LimitRange 对象,并使用 kubectl get limitranges 命令查看设置的参数。
概要
资源请求是 kube-scheduler 算法在决定 Pod 可以被调度到哪个节点时考虑的众多因素之一。一个容器可以使用 spec.containers[].resources.requests 来指定请求。调度程序根据节点的可用硬件容量选择节点。资源限制确保容器不能消耗超过分配的资源量。可以使用属性 spec.containers[].resources.limits 为容器定义限制。如果应用程序因为实现中的内存泄漏等原因消耗超过允许的资源量,容器运行时很可能会终止应用程序进程。
资源配额定义了一个命名空间中可用的计算资源(例如 CPU、RAM 和临时存储),以防止运行其中的 Pod 消耗无限资源。因此,Pod 必须在这些资源边界内工作,声明它们的最小和最大资源期望。您还可以限制允许创建的资源类型的数量(例如 Pods、Secrets 或 ConfigMaps)。Kubernetes 调度程序将在请求对象创建时强制执行这些边界。
限制范围不同于 ResourceQuota,它为特定类型的单个对象定义资源约束。它还可以通过指定资源默认值来帮助对象的治理,这些值应在 API 创建请求未提供信息时自动应用。
考试要点
体验资源需求对调度和自动扩展的影响
由 Pod 定义的容器可以指定资源请求和限制。通过定义单个和多个容器 Pod 的这些要求,解决场景。在创建 Pod 时,您应该能够看到将对象调度到节点的影响。此外,练习如何识别节点的可用资源容量。
理解资源配额的目的和运行时效果
ResourceQuota 定义了命名空间中对象的资源边界。最常用的边界适用于计算资源。练习定义它们并了解它们对 Pod 创建的影响。了解列出 ResourceQuota 的硬性要求和当前正在使用的资源的命令非常重要。您会发现 ResourceQuota 提供了其他选项。详细了解这些选项,以更广泛地了解这个主题。
理解限制范围的目的和运行时效果
LimitRange 可以指定特定基元的资源约束和默认值。如果在创建对象时遇到错误消息,请检查是否有限制范围对象强制执行这些约束。不幸的是,错误消息没有指出强制执行它的对象,因此您可能需要主动列出 LimitRange 对象以识别约束。
示例练习
这些练习的解决方案在附录 A 中可用。
-
你被要求创建一个 Pod 以在容器中运行应用程序。在应用程序开发期间,你运行了负载测试,以确定所需的最小资源量和应用程序允许增长到的最大资源量。定义该 Pod 的资源请求和限制。
定义一个名为
hello-world的 Pod,运行容器镜像bmuschko/nodejs-hello-world:1.0.0。容器公开端口 3000。添加一个类型为
emptyDir的卷,并将其挂载到容器路径/var/log。对于容器,指定以下最小资源数量:
-
CPU:100m
-
内存:500Mi
-
临时存储:1Gi
对于容器,指定以下最大资源数量:
-
内存:500Mi
-
临时存储:2Gi
从 YAML 清单创建 Pod。检查 Pod 的详细信息。Pod 运行在哪个节点上?
-
-
在此练习中,你将为新名称空间创建一个具有特定 CPU 和内存限制的资源配额。在名称空间中创建的 Pod 将必须遵守这些限制。
使用以下 YAML 定义在文件 resourcequota.yaml 中,在名称空间
rq-demo下创建一个名为app的 ResourceQuota:apiVersion: v1 kind: ResourceQuota metadata: name: app spec: hard: pods: "2" requests.cpu: "2" requests.memory: 500Mi创建一个超出资源配额要求限制的新 Pod,例如定义了 1Gi 的内存但保持低于 CPU,例如 0.5。记录下错误消息。
更改请求限制以满足要求,以确保成功创建 Pod。记录下命令的输出,显示名称空间中使用的资源量。
-
LimitRange 可以限制名称空间中 Pod 的资源消耗,并在未定义资源需求时分配默认的计算资源。你将练习 LimitRange 在不同场景下对 Pod 创建的影响。
转到 GitHub 仓库 bmuschko/ckad-study-guide 中的目录 app-a/ch18/limitrange。检查文件 setup.yaml 中 YAML 清单的定义。从 YAML 清单文件创建对象。
在名称空间
d92中创建一个名为pod-without-resource-requirements的新 Pod,使用容器镜像nginx:1.23.4-alpine,没有任何资源要求。检查 Pod 的详细信息。你期望分配哪些资源定义?在名称空间
d92中创建一个名为pod-with-more-cpu-resource-requirements的新 Pod,使用容器镜像nginx:1.23.4-alpine,CPU 资源请求为 400m,限制为 1.5。你期望看到什么运行时行为?在名称空间
d92中创建一个名为pod-with-less-cpu-resource-requirements的新 Pod,使用容器镜像nginx:1.23.4-alpine,CPU 资源请求为 350m,限制为 400m。你期望看到什么运行时行为?
第十九章:配置映射和机密
Kubernetes 专门为定义配置数据而设计了两个基本概念:ConfigMap 和 Secret。这两个基本概念与 Pod 的生命周期完全解耦,这使得您可以更改它们的配置数据值,而无需重新部署 Pod。
本质上,配置映射和机密存储一组键值对。这些键值对可以作为环境变量注入到容器中,也可以作为卷挂载。图 19-1 说明了选项。

图 19-1。消耗配置数据
表面上看,配置映射和机密在目的和结构上几乎相同;然而,它们之间存在一个轻微但重要的区别。配置映射存储纯文本数据,例如连接 URL、运行时标志,甚至结构化数据,如 JSON 或 YAML 内容。机密更适合表示像密码、API 密钥或 SSL 证书等敏感数据,并以 base64 编码形式存储数据。
配置映射和机密数据的加密
存储配置映射和机密对象数据的集群组件是 etcd。Etcd 默认以未加密形式管理此数据。您可以按照Kubernetes 文档中的描述配置 etcd 中的数据加密。Etcd 加密不在考试范围内。
本章大量涉及卷的概念。请参考第七章,以重新了解在 Pod 中使用卷的机制。
使用配置映射
应用程序通常实现使用配置数据来控制运行时行为的逻辑。配置数据的示例包括连接 URL 和网络通信选项(如第三方服务之间的重试次数或超时)在不同目标部署环境中的差异。
同一配置数据需要在多个 Pod 中提供并不罕见。您可以选择将信息集中在一个配置映射对象中,而不是在多个 Pod 定义中复制粘贴相同的键值对。配置映射对象保存配置数据,并可以由尽可能多的 Pod 消耗。因此,如果需要更改数据,则只需在一个位置进行修改即可。
创建配置映射
您可以通过使用命令行create configmap来创建配置映射。此命令要求您以选项的形式提供数据的来源。Kubernetes 区分如表 19-1 所示的四种不同选项。
表 19-1。配置映射解析的数据源选项
| 选项 | 示例 | 描述 |
|---|---|---|
--from-literal |
--from-literal=locale=en_US |
字面值,即键-值对的纯文本形式 |
--from-env-file |
--from-env-file=config.env |
包含键值对并期望它们作为环境变量的文件 |
--from-file |
--from-file=app-config.json |
一个包含任意内容的文件 |
--from-file |
--from-file=config-dir |
一个包含一个或多个文件的目录 |
很容易混淆选项--from-env-file和--from-file。选项--from-env-file期望一个文件,该文件包含以新行分隔的KEY=value格式的环境变量。键值对遵循环境变量的典型命名约定(例如,键是大写的,并且单词之间用下划线分隔)。从历史上看,此选项已用于处理Docker Compose .env文件,尽管您可以将其用于包含环境变量的任何其他文件。
--from-env-file选项不强制或规范环境变量的典型命名约定。--from-file选项指向一个包含任何任意内容的文件或目录。这是用于包含应用程序读取的结构化配置数据文件的合适选项(例如,属性文件、JSON 文件或 XML 文件)。
以下命令展示了创建 ConfigMap 的过程。我们只需提供键值对作为字面量:
$ kubectl create configmap db-config --from-literal=DB_HOST=mysql-service \
--from-literal=DB_USER=backend
configmap/db-config created
生成的 YAML 对象看起来像示例 19-1 中显示的对象。正如您所见,该对象在名为data的部分中定义了键值对。ConfigMap 没有spec部分。
示例 19-1。ConfigMap YAML 清单
apiVersion: v1
kind: ConfigMap
metadata:
name: db-config
data:
DB_HOST: mysql-service
DB_USER: backend
您可能已经注意到,分配给 ConfigMap 数据的键遵循环境变量使用的典型命名约定。这样做的目的是在容器中以这种方式使用它们。
将 ConfigMap 作为环境变量消耗
创建 ConfigMap 后,您现在可以将其键值对作为环境变量注入到容器中。示例 19-2 展示了使用spec.containers[].envFrom[].configMapRef来按名称引用 ConfigMap。
示例 19-2。将 ConfigMap 键值对注入到容器中
apiVersion: v1
kind: Pod
metadata:
name: backend
spec:
containers:
- image: bmuschko/web-app:1.0.1
name: backend
envFrom:
- configMapRef:
name: db-config
从 YAML 清单创建 Pod 后,您可以通过运行envUnix 命令来检查容器中可用的环境变量。
$ kubectl exec backend -- env
...
DB_HOST=mysql-service
DB_USER=backend
...
注入的配置数据将列在容器可用的环境变量中。
作为卷挂载 ConfigMap
另一种在运行时配置应用程序的方法是处理机器可读的配置文件。假设我们决定将数据库配置存储在名为db.json的 JSON 文件中,其结构如示例 19-3 所示。
示例 19-3。用于配置数据库信息的 JSON 文件
{
"db": {
"host": "mysql-service",
"user": "backend"
}
}
给定我们不处理字面上的键值对,创建 ConfigMap 对象时需要提供选项--from-file:
$ kubectl create configmap db-config --from-file=db.json
configmap/db-config created
示例 19-4 显示了 ConfigMap 的相应 YAML 清单。可以看到文件名成为键;文件内容使用了多行值。
示例 19-4。定义结构化数据的 ConfigMap YAML 清单
apiVersion: v1
kind: ConfigMap
metadata:
name: db-config
data:
db.json: |- 
{
"db": {
"host": "mysql-service",
"user": "backend"
}
}
此 YAML 结构中使用的多行字符串语法(|-)删除了换行符并去除了尾随空白行。有关更多信息,请参见YAML 多行字符串的语法。
Pod 将 ConfigMap 作为卷挂载到容器内的特定路径,并具有只读权限。假设应用程序将在启动时读取配置文件。示例 19-5 演示了 YAML 定义。
示例 19-5。将 ConfigMap 挂载为卷
apiVersion: v1
kind: Pod
metadata:
name: backend
spec:
containers:
- image: bmuschko/web-app:1.0.1
name: backend
volumeMounts:
- name: db-config-volume
mountPath: /etc/config
volumes:
- name: db-config-volume
configMap: 
name: db-config
分配卷类型以通过名称引用 ConfigMap 对象。
要验证正确的行为,请打开一个交互式 shell 到容器。如下命令所示,目录/etc/config包含一个文件,其中包含我们在 ConfigMap 中使用的键。内容表示 JSON 配置:
$ kubectl exec -it backend -- /bin/sh
# ls -1 /etc/config
db.json
# cat /etc/config/db.json
{
"db": {
"host": "mysql-service",
"user": "backend"
}
}
应用程序代码现在可以从挂载路径读取文件,并根据需要配置运行时行为。
处理 Secrets
存储在 ConfigMaps 中的数据表示任意的明文键值对。与 ConfigMap 相比,Secret 原语旨在表示敏感的配置数据。Secret 数据的典型示例是用于认证的密码或 API 密钥。
存储在 Secret 中的值仅进行编码,而不是加密。
Secrets 期望每个条目的值都是 Base64 编码的。Base64 仅对值进行编码,但不进行加密。因此,任何具有其值访问权限的人都可以毫无问题地解码它。因此,应避免将 Secret 清单存储在源代码仓库中,与其他资源文件一起。
Kubernetes 项目决定选择“Secret”这个术语来表示敏感数据,有些令人遗憾。命名暗示数据实际上是秘密的,因此应进行加密。您可以从多种选项中选择,以确保在现实世界的项目中保持敏感数据的安全性。
Bitnami 密封的 Secrets 是一个成熟且经过验证的 Kubernetes 操作器,使用非对称加密技术进行数据加密。数据的清单表示,即 CRD 密封的 Secret,可以安全地存储在公共源代码库中。您无法自行解密此数据。安装了操作器的控制器是唯一可以解密数据的实体。另一个选项是将敏感数据存储在外部 Secrets 管理器中,例如 HashiCorp Vault 或 AWS Secrets Manager,并将其与 Kubernetes 集成。External Secrets Operator 将外部 API 中的 Secrets 同步到 Kubernetes 中。考试只要求您理解内置的 Secret 原语,在下面的章节中进行了详细介绍。
创建一个 Secret
您可以使用命令create secret来创建一个 Secret。此外,需要提供一个必需的子命令,以确定 Secret 的类型。Table 19-2 列出了不同类型。Kubernetes 将 Internal Type 列中的值分配给 live 对象中的 type 属性。“专用 Secret 类型” 讨论了其他 Secret 类型及其用途。
Table 19-2. 创建 Secret 的选项
| CLI 选项 | 描述 | 内部类型 |
|---|---|---|
generic |
从文件、目录或明文值创建 Secret | Opaque |
docker-registry |
创建用于 Docker 注册表的 Secret,例如,在 Pod 请求时从私有注册表拉取镜像 | kubernetes.io/dockercfg |
tls |
创建一个 TLS Secret | kubernetes.io/tls |
最常用的 Secret 类型是 generic。泛型 Secret 的选项与 ConfigMap 完全相同,如 Table 19-3 所示。
Table 19-3. 解析为 Secret 的数据源选项
| 选项 | 示例 | 描述 |
|---|---|---|
--from-literal |
--from-literal=password=secret |
明文键值对的文字值 |
--from-env-file |
--from-env-file=config.env |
包含键值对并期望它们成为环境变量的文件 |
--from-file |
--from-file=id_rsa=~/.ssh/id_rsa |
包含任意内容的文件 |
--from-file |
--from-file=config-dir |
包含一个或多个文件的目录 |
要演示功能,让我们创建一个类型为generic的秘密。该命令从作为命令行选项提供的文字中获取键值对:
$ kubectl create secret generic db-creds --from-literal=pwd=s3cre!
secret/db-creds created
使用命令创建时,Secret 将自动对提供的值进行 Base64 编码。这可以通过查看生成的 YAML 清单来观察到。您可以在 Example 19-6 中看到值 s3cre! 已被转换为 czNjcmUh,即其 Base64 编码的等效值。
Example 19-6. 含有 Base64 编码值的 Secret
apiVersion: v1
kind: Secret
metadata:
name: db-creds
type: Opaque 
data:
pwd: czNjcmUh 
类型为Opaque的值被分配用于表示通用敏感数据。
如果对象是通过命令式方式创建的,那么纯文本值会自动进行 Base64 编码。
如果您使用 YAML 清单开始创建秘密对象,则需要创建 Base64 编码的值以便将其分配给data属性。Unix 工具base64可以完成此工作。以下命令正好实现了这一点:
$ echo -n 's3cre!' | base64
czNjcmUh
请记住,如果您可以访问秘密对象或其 YAML 清单,那么您可以随时使用 Unix 工具base64解码 Base64 编码的值。因此,在定义清单时,您也可以指定纯文本值,我们将在下一节讨论。
使用纯文本值定义秘密数据
必须生成并分配 Base64 编码的值到秘密清单可能变得繁琐。秘密原语提供了stringData属性来替代data属性。使用stringData,您可以在清单文件中分配纯文本值,正如在示例 19-7 中展示的那样。
示例 19-7. 一个包含纯文本值的秘密
apiVersion: v1
kind: Secret
metadata:
name: db-creds
type: Opaque
stringData: 
pwd: s3cre! 
stringData属性允许分配纯文本键值对。
pwd键引用的值以纯文本格式提供。
Kubernetes 在从清单创建对象时会自动对s3cre!值进行 Base64 编码。结果是在示例 19-8 中展示的实时对象表示,您可以使用kubectl get secret db-creds -o yaml命令检索它。
示例 19-8. 一个实时秘密对象
apiVersion: v1
kind: Secret
metadata:
name: db-creds
type: Opaque
data: 
pwd: czNjcmUh! 
一个秘密的实时对象总是使用data属性,即使在清单中可能使用了stringData。
在创建时值已经被 Base64 编码。
您可以使用Opaque类型表示任意秘密数据。Kubernetes 提供了专门的秘密类型供您选择,以适应特定的用例。我们将在下一节讨论这些专门的秘密类型。
专门的秘密类型
您还可以使用其中一个专门类型来表示特定用例的配置数据,而不是使用Opaque秘密类型。类型kubernetes.io/basic-auth适用于基本认证,并期望键username和password。在撰写时,Kubernetes 不验证分配的键的正确性。
从这个定义创建的对象会自动为两个键的值进行 Base64 编码。示例 19-9 展示了一个具有类型kubernetes.io/basic-auth的秘密的 YAML 清单。
示例 19-9. 使用 kubernetes.io/basic-auth 秘密类型
apiVersion: v1
kind: Secret
metadata:
name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData: 
username: bmuschko 
password: secret 
使用stringData属性允许分配明文值。
指定了kubernetes.io/basic-auth秘密类型所需的强制键。
作为环境变量消费秘密
与 ConfigMaps 的环境变量类似,将秘密作为环境变量消费的工作方式。在这里,您将使用 YAML 表达式spec.containers[].envFrom[].secretRef来引用秘密的名称。示例 19-10 将名为secret-basic-auth的秘密作为环境变量注入到名为backend的容器中。
示例 19-10. 将秘密键值对注入容器
apiVersion: v1
kind: Pod
metadata:
name: backend
spec:
containers:
- image: bmuschko/web-app:1.0.1
name: backend
envFrom:
- secretRef:
name: secret-basic-auth
检查容器中的环境变量会显示,秘密值无需解码。这是 Kubernetes 自动完成的操作。因此,运行的应用程序无需实现自定义逻辑来解码该值。请注意,Kubernetes 不验证或规范化环境变量的典型命名约定,正如您在以下输出中看到的:
$ kubectl exec backend -- env
...
username=bmuschko
password=secret
...
重映射环境变量键
有时,存储在秘密中的键值对不符合环境变量的典型命名约定,或者在不影响正在运行的服务的情况下无法更改。您可以使用spec.containers[].env[].valueFrom属性重新定义用于将环境变量注入 Pod 中的键。示例 19-11 将键username转换为USER,将键password转换为PWD。
示例 19-11. 重映射秘密条目的环境变量键
apiVersion: v1
kind: Pod
metadata:
name: backend
spec:
containers:
- image: bmuschko/web-app:1.0.1
name: backend
env:
- name: USER
valueFrom:
secretKeyRef:
name: secret-basic-auth
key: username
- name: PWD
valueFrom:
secretKeyRef:
name: secret-basic-auth
key: password
现在,容器可用的环境变量遵循环境变量的典型约定,并且我们改变了它们在应用程序代码中的消费方式:
$ kubectl exec backend -- env
...
USER=bmuschko
PWD=secret
...
重新分配环境变量键的相同机制适用于 ConfigMaps。您将使用属性spec.containers[].env[].valueFrom.configMapRef。
将秘密作为卷挂载
要演示将秘密作为卷挂载,我们将创建一个新的类型为kubernetes.io/ssh-auth的秘密。这种秘密类型捕获了 SSH 私钥的值,您可以使用命令cat ~/.ssh/id_rsa查看。要使用create secret命令处理 SSH 私钥文件,需要将其作为名为ssh-privatekey的文件可用:
$ cp ~/.ssh/id_rsa ssh-privatekey
$ kubectl create secret generic secret-ssh-auth --from-file=ssh-privatekey \
--type=kubernetes.io/ssh-auth
secret/secret-ssh-auth created
将秘密作为卷挂载遵循两步骤的方法:首先定义卷,然后将其引用为一个或多个容器的挂载路径。卷类型称为secret,如示例 19-12 中使用的那样。
示例 19-12. 将秘密作为卷挂载
apiVersion: v1
kind: Pod
metadata:
name: backend
spec:
containers:
- image: bmuschko/web-app:1.0.1
name: backend
volumeMounts:
- name: ssh-volume
mountPath: /var/app
readOnly: true 
volumes:
- name: ssh-volume
secret:
secretName: secret-ssh-auth 
由作为卷挂载的秘密提供的文件无法修改。
请注意指向秘密名称的属性secretName与 ConfigMap 的属性name不同。
您将在挂载路径/var/app中找到名为ssh-privatekey的文件。要验证,请打开交互式 shell 并呈现文件内容。文件内容不是 Base64 编码的:
$ kubectl exec -it backend -- /bin/sh
# ls -1 /var/app
ssh-privatekey
# cat /var/app/ssh-privatekey
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,8734C9153079F2E8497C8075289EBBF1
...
-----END RSA PRIVATE KEY-----
总结
应用程序运行时行为可以通过将配置数据作为环境变量注入或通过挂载到路径来控制。在 Kubernetes 中,此配置数据以 API 资源 ConfigMap 和 Secret 的形式表示为键值对。ConfigMap 适用于明文数据,而 Secret 对值进行 Base64 编码以混淆值。对于像凭据和 SSH 私钥这样的敏感信息,Secret 更合适。
考试要点
练习使用命令式和声明式方法创建 ConfigMap 对象
最快创建这些对象的方法是使用命令式kubectl create configmap命令。了解如何使用不同的命令行标志提供数据的方法。ConfigMap 在 YAML 清单的data部分指定了明文键值对。
练习使用命令式和声明式方法创建 Secret 对象
使用命令式命令kubectl create secret创建 Secret 时无需对提供的值进行 Base64 编码。kubectl会自动执行编码操作。声明式方法要求 Secret 的 YAML 清单在data部分指定 Base64 编码的值。如果您更喜欢提供明文值,可以使用stringData方便属性代替data属性。实时对象将使用 Base64 编码值。在运行时,使用data和stringData之间没有功能上的区别。
了解专门的 Secret 类型的目的
Secrets 提供专门的类型,例如kubernetes.io/basic-auth或kubernetes.io/service-account-token,以表示特定用例的数据。请阅读 Kubernetes 文档中关于不同类型的内容,并了解它们的用途。
知道如何检查 ConfigMap 和 Secret 数据
考试可能涉及现有的 ConfigMap 和 Secret 对象。您需要了解如何使用kubectl get或kubectl describe命令检查这些对象的数据。Secret 的实时对象始终以 Base64 编码格式表示值。
在 Pod 中练习使用 ConfigMaps 和 Secrets
ConfigMaps 和 Secrets 的主要用例是从 Pod 消耗数据。Pod 可以将配置数据作为环境变量注入到容器中,或者将配置数据作为卷挂载。在考试中,您需要熟悉这两种消耗方法。
示例练习
这些练习的解决方案可在附录 A 中找到。
-
在这个练习中,你将首先从一个 YAML 配置文件创建一个 ConfigMap。随后,你将创建一个 Pod,将 ConfigMap 作为 Volume 消费,并作为文件检查键-值对。
导航至已检出的 GitHub 仓库bmuschko/ckad-study-guide的目录app-a/ch19/configmap。检查名为application.yaml的 YAML 配置文件。
从该文件创建名为
app-config的新 ConfigMap。创建名为
backend的 Pod,将 ConfigMap 作为 Volume 挂载到路径/etc/config。容器运行nginx:1.23.4-alpine镜像。进入 Pod 的 Shell,并检查挂载点处的文件。
-
在这个练习中,首先从字面值创建一个 Secret。接下来,你将创建一个 Pod,并将 Secret 作为环境变量消费。最后,你将在容器内部打印出其值。
创建名为
db-credentials的新 Secret,包含键/值对db-password=passwd。创建名为
backend的 Pod,使用 Secret 作为名为DB_PASSWORD的环境变量,并以nginx:1.23.4-alpine镜像运行容器。进入 Pod 的 Shell,并打印出创建的环境变量。你应该能找到
DB_PASSWORD变量。
第二十章:安全上下文
在 Kubernetes 中运行 Pod 如果没有实施更严格的安全措施可能会造成安全风险。没有这些措施,攻击者可能会访问主机系统或执行恶意活动,例如访问包含敏感数据的文件。安全上下文定义了容器作为 Pod 规范的一部分的特权和访问控制设置。以下列表提供了一些与安全相关的参数示例:
-
应该用于运行 Pod 和/或容器的用户 ID
-
用于文件系统访问的组 ID
-
在容器内运行的进程授予根用户的一些特权,但并非全部
本章将为您提供定义安全上下文并在实践中查看其运行时效果的概述。考虑到安全设置的广泛范围,我们无法讨论所有设置。您可以在 Kubernetes 文档 中找到更多用例和配置选项。
使用安全上下文
安全上下文不是 Kubernetes 的基本结构。它被建模为 Pod 规范中 securityContext 指令下的一组属性。在 Pod 级别定义的安全设置适用于运行在 Pod 中的所有容器。当应用于单个容器时,它对其他在同一 Pod 中运行的容器没有影响。
-
您可以使用
spec.securityContext属性将安全设置应用于 Pod 的所有容器。 -
对于单个容器,您可以使用
spec.containers[].securityContext属性应用安全设置。
图 20-1 展示了在 Pod 和容器级别应用的 runAsUser 和 fsGroup 安全设置的使用。稍后的部分将通过示例描述这些设置的运行时效果。

图 20-1. 在 Pod 和容器级别应用安全设置
一些安全上下文属性可用于 Pod 和容器级别。如果在两个级别上定义相同的安全上下文,则容器级别的值将优先。图 20-2 展示了对名为 nginx 的容器覆盖容器级安全上下文值的情况。对于该容器,将应用值 2000。

图 20-2. 在容器级别覆盖安全上下文属性
有关 Pod 级安全属性的具体信息,请参阅 PodSecurityContext API。容器级安全属性可以在 SecurityContext API 中找到。
将安全上下文应用于 Deployment 的 Pod 模板
Deployment 的定义以与普通 Pod 定义相同的方式应用安全上下文属性。您会在 Deployment 的 Pod 模板部分使用相同的 Pod 和容器级别属性。
使用安全上下文定义与容器相关的安全参数始终限于容器。Kubernetes 生态系统提供其他改进或管理应用程序安全性的方式,其中一些直接与安全上下文概念相关。例如,您可以使用 Pod 安全准入 来强制执行命名空间内所有 Pod 的所需安全设置。
在 Pod 级别定义安全上下文
容器镜像可以定义安全相关的指令,以减少运行容器的攻击向量。默认情况下,容器以 root 特权运行,这为所有进程和容器文件系统提供了最高权限访问。作为最佳实践,应以某种方式编写相应的 Dockerfile,使容器将以用户 ID 0 以外的用户 ID 运行,通过 USER 指令的帮助。有许多其他方法可以在容器级别上保护容器,但我们在此不再详述。有关更多信息,请参考 Container Security(由 Liz Rice 撰写,O’Reilly 出版)。
为了使安全上下文的功能更加透明,让我们看一个使用案例。某些镜像(例如用于开源反向代理服务器 NGINX 的镜像)必须以 root 用户运行。假设您希望作为合理的安全策略强制要求容器不能以 root 用户运行。在 例子 20-1 中显示的 YAML 清单将安全配置定义为 spec 属性的直接子级。如果要在 Pod 内运行其他容器,则 runAsNonRoot 设置也会适用于它们。
例子 20-1. 为 NGINX 镜像在容器级别设置安全上下文
apiVersion: v1
kind: Pod
metadata:
name: nginx-non-root
spec:
securityContext:
runAsNonRoot: true 
containers:
- image: nginx:1.25.3
name: secured-container
强制使用非 root 用户来运行容器。
创建 Pod 的清单将按预期工作:
$ kubectl apply -f container-nginx-root-user.yaml
pod/nginx-non-root created
不幸的是,该镜像不兼容。在启动过程中,容器失败并显示状态为 CreateContainerConfigError:
$ kubectl get pod nginx-non-root
NAME READY STATUS RESTARTS AGE
nginx-non-root 0/1 CreateContainerConfigError 0 7s
您会在事件日志中找到此问题的根本原因:
$ kubectl describe pod nginx-non-root
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned \
default/non-root to \
minikube
Normal Pulling 18s kubelet, minikube Pulling image \
"nginx:1.25.3"
Normal Pulled 14s kubelet, minikube Successfully pulled \
image "nginx:1.25.3"
Warning Failed 0s (x3 over 14s) kubelet, minikube Error: container has \
runAsNonRoot and image \
will run as root
有可用的替代 NGINX 镜像,不需要以 root 用户运行。一个例子是 bitnami/nginx。在生成镜像的 Dockerfile 中仔细查看后,您会发现容器以用户 ID 1001 运行。例子 20-2 展示了使用 Bitnami 镜像的情况。
例子 20-2. 为 Bitnami NGIX 镜像在容器级别设置安全上下文
apiVersion: v1
kind: Pod
metadata:
name: bitnami-ngnix-non-root
spec:
securityContext:
runAsNonRoot: true
containers:
- image: bitnami/nginx:1.25.3
name: secured-container
使用 runAsNonRoot 指令启动容器将正常工作:
$ kubectl apply -f container-bitnami-nginx-root-user.yaml
pod/bitnami-ngnix-non-root created
容器将显示“运行”状态:
$ kubectl get pod nginx-non-root
NAME READY STATUS RESTARTS AGE
bitnami-ngnix-non-root 1/1 Running 0 7s
容器可以以容器镜像设置的用户 ID 执行,你可以通过容器内运行以下命令轻松查看:
$ kubectl exec -it bitnami-ngnix-non-root -- id -u
1001
命令的输出呈现了用户 ID 1001,即非 root 用户 ID。
在容器级别定义安全上下文
在 Kubernetes 中,你可以对运行的容器施加许多其他安全限制。例如,你可能希望设置文件和目录的访问控制。假设在文件系统上创建文件时,文件的所有者应该是任意组 ID 3500。示例 20-3 中的 YAML 清单在容器级别分配了安全上下文设置。
示例 20-3. 在容器级别设置安全上下文
apiVersion: v1
kind: Pod
metadata:
name: fs-secured
spec:
containers:
- image: nginx:1.25.3
name: secured-container
securityContext:
fsGroup: 3500
volumeMounts:
- name: data-volume
mountPath: /data/app
volumes:
- name: data-volume
emptyDir: {}
从清单文件创建 Pod 对象并检查状态。Pod 应该转换为“运行”状态:
$ kubectl apply -f pod-file-system-group.yaml
pod/fs-secured created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
fs-secured 1/1 Running 0 24s
你可以轻松验证设置文件系统组 ID 的效果。打开容器的交互式 shell,进入挂载的卷,并创建一个新文件:
$ kubectl exec -it fs-secured -- /bin/sh
# cd /data/app
# touch logs.txt
# ls -l
-rw-r--r-- 1 root 3500 0 Jul 9 01:41 logs.txt
检查文件所有权将显示自动分配的组 ID 3500。
在 Pod 和容器级别定义安全上下文
最后,让我们演示在容器级别覆盖行为,如果已在 Pod 级别定义相同的属性。示例 20-4 展示了在两个级别上都定义 runAsNonRoot 的定义。
示例 20-4. 在 Pod 和容器级别设置安全上下文
apiVersion: v1
kind: Pod
metadata:
name: non-root-user-override
spec:
securityContext:
runAsNonRoot: true 
containers:
- image: nginx:1.25.3
name: root
securityContext:
runAsNonRoot: false 
- image: bitnami/nginx:1.25.3
name: non-root
将默认值 true 分配给 Pod 的所有容器。
即使在 Pod 级别分配了 true,值为 false 仍然优先。
从清单文件创建 Pod 对象:
$ kubectl apply -f pod-non-root-user-override.yaml
pod/non-root-user-override created
打开容器的交互式 shell 并执行命令来查看运行容器的用户 ID:
$ kubectl exec -it -c root non-root-user-override -- id -u
0
$ kubectl exec -it -c non-root non-root-user-override -- id -u
1001
容器 root 返回值 0,即 root 用户的用户 ID。容器 non-root 返回用户 ID 1001,这是容器镜像本身设置的 ID。
总结
对 Pod 实施安全最佳实践非常重要。本章介绍了安全上下文概念。借助安全上下文,你可以控制容器访问对象(如文件)、在特权和非特权模式下运行容器、指定 Linux 权限等。
可以在 Pod 和容器级别声明安全上下文。Pod 级别将提供的安全设置应用于 Pod 中的所有容器。容器级别仅适用于各个容器。如果在两个级别上都指定了相同属性值,则容器级别的安全设置将覆盖 Pod 级别的安全设置。
考试要点
尝试安全上下文可用的选项
Kubernetes 用户文档和 API 文档是探索安全上下文选项的良好起点。您会发现通过 PodSecurityContext 和 SecurityContext API 可用的选项存在重叠。在解决由安全上下文选项解决的不同用例时,请通过运行应允许或不允许的操作来验证其结果。
理解在 Pod 和容器级别定义安全上下文的影响。
您可以在 Pod 级别使用spec.securityContext定义安全上下文,在容器级别使用spec.containers[].securityContext定义。如果在 Pod 级别定义,设置可以通过在容器级别指定不同值来覆盖。考试可能会遇到在两个级别上都设置安全上下文的现有 Pods。理解哪个值将生效。
样例练习
这些练习的解决方案可以在附录 A 中找到。
-
定义一个名为
busybox-security-context的 Pod,使用镜像busybox:1.36.1,其中单个容器运行命令sh -c sleep 1h。添加一个类型为
emptyDir的临时卷。将该卷挂载到容器的/data/test目录下。定义一个安全上下文,以 1000 用户 ID、3000 组 ID 运行容器,并且文件系统组 ID 为 2000。确保容器不允许特权升级。
创建 Pod 对象,并确保其转换为“运行”状态。
打开运行中容器的 shell,并在目录/data/test中创建一个名为logs.txt的新文件。文件的用户 ID 和组 ID 是多少?
-
在命名空间
h20中创建一个名为nginx的部署,包括 3 个副本。Pod 模板应使用镜像nginx:1.25.3-alpine。使用安全上下文,为该 Pod 模板分配
dropLinux 能力。drop能力的属性应使用值all。创建部署对象并检查其副本。NGINX 是否按预期工作?
第二十一章:服务
在“使用 Pod 的 IP 地址进行网络通信”中,我们了解到可以通过其 IP 地址与 Pod 进行通信。重新启动 Pod 将自动分配新的虚拟集群 IP 地址。因此,如果需要彼此通信,则系统的其他部分不能依赖于 Pod 的 IP 地址。
构建微服务架构,其中每个组件在其自己的 Pod 中运行,并需要通过稳定的网络接口相互通信,需要一个不同的原语,即服务。
服务在 Pod 上实现了一个抽象层,为所有具有匹配标签的 Pod 分配一个固定的虚拟 IP,并且该虚拟 IP 称为集群 IP。本章将重点介绍服务的各个方面,尤其是基于声明类型在集群内外部暴露 Pods 的方法。
在 minikube 中访问服务
在 minikube 中访问类型为NodePort和LoadBalancer的服务需要特殊处理。有关详细说明,请参阅文档。
使用服务
简而言之,服务为一组 Pod 提供可发现的名称和负载平衡。服务利用 Kubernetes DNS 控制平面组件帮助保持对 IP 地址的不可知性,这是我们将在“通过 DNS 查找发现服务”中讨论的一个方面。类似于部署,服务通过标签选择确定其工作的 Pods。
图 21-1 说明了其功能。Pod 1 接收流量,因为其分配的标签与服务中定义的标签选择匹配。Pod 2 由于定义了不匹配的标签,因此不接收流量。

图 21-1。基于标签选择的服务流量路由
请注意,可以创建一个没有标签选择器的服务来支持其他场景。有关更多信息,请参阅相关的Kubernetes 文档。
服务与部署
服务是部署的补充概念。服务将网络流量路由到一组 Pods,而部署则管理一组 Pods,即副本。虽然可以单独使用这两个概念,但建议同时使用部署和服务。主要原因是能够扩展副本数量,并同时能够暴露端点以将网络流量引导到这些 Pods。
服务类型
每个服务定义了一种类型。这种类型负责在集群内部和/或外部公开服务。表 21-1 列出了与考试相关的服务类型。
表 21-1。服务类型
| 类型 | 描述 |
|---|---|
ClusterIP |
在集群内部 IP 上公开服务。仅从集群内部可访问。Kubernetes 使用循环算法在目标 Pod 之间均匀分布流量。 |
NodePort |
在每个节点的 IP 地址上以静态端口公开服务。从集群外部可访问。该服务类型不提供跨多个节点的负载均衡。 |
负载均衡器 |
使用云提供商的负载均衡器将服务外部化。 |
其他服务类型,例如 ExternalName 或无头服务,可以定义;但由于它们不在考试范围内,本书不会对它们进行详细讨论。有关更多信息,请参阅 Kubernetes 文档。
服务类型继承
刚才提到的服务类型,ClusterIP、NodePort 和 LoadBalancer,使得服务在不同的暴露级别下可访问。理解这些服务类型也是至关重要的,因为它们是相互构建的。图 21-2 展示了不同服务类型之间的关系。

图 21-2. 服务类型的网络可访问性特征
例如,创建一个 NodePort 类型的服务意味着该服务将具有 ClusterIP 服务类型的网络可访问性特征。反过来,NodePort 服务可从集群内部和外部访问。本章通过示例演示每种服务类型。在接下来的章节中,您将找到对继承的暴露行为的引用。
何时使用哪种服务类型?
在构建微服务架构时,一个问题是选择哪种服务类型来实现特定的使用案例。我们在这里简要讨论这个问题。
ClusterIP 服务类型适用于需要将微服务暴露给集群内其他 Pod 的用例。假设您有一个前端微服务需要连接一个或多个后端微服务。为了正确实现该场景,您将建立一个 ClusterIP 服务来路由流量到后端 Pod。然后,前端 Pod 将与该服务通信。
NodePort服务类型经常被提及作为一种向外部消费者暴露应用程序的方式。消费者必须知道节点的 IP 地址和静态分配的端口才能连接到服务。这对多种原因都是有问题的。首先,节点端口通常是动态分配的。因此,您通常无法预先知道它。其次,提供节点的 IP 地址将使网络流量只通过单个节点进行,因此您将无法使用负载均衡。最后,通过开放一个公开可用的节点端口,您有增加集群攻击面的风险。因为所有这些原因,NodePort服务主要用于开发或测试目的,而在生产环境中使用较少。
LoadBalancer服务类型通过外部负载均衡器提供的外部 IP 地址,使应用程序对外可用。网络流量将分布在集群中的多个节点上。这种解决方案非常适合生产环境,但请记住,每个配置的负载均衡器都会产生费用,并且可能会导致昂贵的基础设施账单。更经济有效的解决方案是使用 Ingress,在第二十二章中讨论。
端口映射
服务使用标签选择来确定转发流量的 Pod 集。成功路由网络流量取决于端口映射。
图 21-3 显示一个服务在端口 80 上接受传入流量。这是清单中spec.ports[].port属性定义的端口。任何传入的流量都将路由到目标端口,表示为spec.ports[].targetPort。

图 21-3. 服务端口映射
目标端口与在标签选择的 Pod 内运行的容器定义的spec.containers[].ports[].containerPort端口相同。在这个示例中,这是端口 8080。只有当服务的目标端口和容器端口匹配时,所选的 Pod(s) 才会接收到流量。
创建服务
您可以以多种方式创建服务,其中一些适合考试,因为它们提供快速的结果。首先让我们讨论命令式方法。
服务需要通过匹配标签来选择一个 Pod。以下run命令创建的 Pod 称为echoserver,它将应用暴露在容器端口 8080 上。在内部,它会自动为对象分配标签键值对run=echoserver:
$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
--port=8080
pod/echoserver created
您可以使用create service命令创建一个服务对象。确保提供服务类型作为强制参数。这里我们使用类型clusterip。命令行选项--tcp指定端口映射。端口 80 将服务暴露给传入的网络流量。端口 8080 指向 Pod 暴露的容器端口:
$ kubectl create service clusterip echoserver --tcp=80:8080
service/echoserver created
使用 run 命令和 --expose 选项可以更快地创建 Pod 和 Service。以下命令一次性创建两个对象,并建立适当的标签选择。在考试中,如果要求同时创建 Pod 和 Service,此命令行选项是节省时间的好选择:
$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
--port=8080 --expose
service/echoserver created
pod/echoserver created
实际上更常见的是使用一起工作的 Deployment 和 Service。以下一系列命令创建了一个具有五个副本的 Deployment,然后使用 expose deployment 命令实例化 Service 对象。端口映射可以通过 --port 和 --target-port 选项提供:
$ kubectl create deployment echoserver --image=k8s.gcr.io/echoserver:1.10 \
--replicas=5
deployment.apps/echoserver created
$ kubectl expose deployment echoserver --port=80 --target-port=8080
service/echoserver exposed
示例 21-1 展示了一个以 YAML 清单形式表示的 Service。该 Service 声明了用于标签选择的键值对 app=echoserver,并定义了从端口 80 到 8080 的端口映射。
示例 21-1. YAML 清单定义的 Service
apiVersion: v1
kind: Service
metadata:
name: echoserver
spec:
selector:
run: echoserver 
ports: 
- port: 80
targetPort: 8080
选择所有带有指定标签分配的 Pods。
定义 Service 的传入和传出端口。传出端口需与所选 Pods 的容器端口匹配。
所示的 Service YAML 清单未分配显式类型。未为属性 spec.type 指定值的 Service 对象在创建时将默认为 ClusterIP。
服务清单
显示所有服务会呈现一个包含服务类型、集群 IP 地址、可选的外部 IP 地址以及传入端口的表视图。在这里,你可以看到我们之前创建的 echoserver Pod 的输出:
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
echoserver ClusterIP 10.109.241.68 <none> 80/TCP 6s
当 Service 类型为 ClusterIP 时,Kubernetes 会分配一个集群 IP 地址。此类 Service 没有外部 IP 地址可用。Service 可通过端口 80 访问。
渲染服务详细信息
如果向一个 Service 的传入流量没有正确路由到你预期处理请求的一组 Pods,可能需要深入了解该 Service 的详细信息以进行故障排除。
describe service 命令呈现有关 Service 配置的宝贵信息。用于故障排除的配置是字段 Selector、IP、Port、TargetPort 和 Endpoints 的值。常见的配置错误来源是选择不正确的标签和端口分配。确保所选标签确实存在于意图将流量路由到的 Pods 中,并且 Service 的目标端口与 Pods 暴露的容器端口匹配。
查看以下 describe 命令的输出。这是为一个由 Deployment 控制的五个 Pods 创建的 Service 的详细信息。Endpoints 属性列出了一系列端点,每个端点对应一个 Pod:
$ kubectl describe service echoserver
Name: echoserver
Namespace: default
Labels: app=echoserver
Annotations: <none>
Selector: app=echoserver
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.109.241.68
IPs: 10.109.241.68
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 172.17.0.4:8080,172.17.0.5:8080,172.17.0.7:8080 + 2 more...
Session Affinity: None
Events: <none>
端点是一个可解析的网络端点,充当 Pod 的虚拟 IP 地址和容器端口。如果一个 Service 没有显示任何端点,则可能存在配置错误。
Kubernetes 通过一个专用的原始数据来表示端点,您可以对其进行查询。端点对象与实例化服务对象同时创建。以下命令列出了名为 echoserver 的服务的端点:
$ kubectl get endpoints echoserver
NAME ENDPOINTS AGE
echoserver 172.17.0.4:8080,172.17.0.5:8080,172.17.0.7:8080 + 2 more... 8m5s
端点的详细信息显示了完整的 IP 地址和端口组合列表:
$ kubectl describe endpoints echoserver
Name: echoserver
Namespace: default
Labels: app=echoserver
Annotations: endpoints.kubernetes.io/last-change-trigger-time: \
2021-11-15T19:09:04Z
Subsets:
Addresses: 172.17.0.4,172.17.0.5,172.17.0.7,172.17.0.8,172.17.0.9
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 8080 TCP
Events: <none>
ClusterIP 服务类型
ClusterIP 是默认的服务类型。它将服务暴露在集群内部 IP 地址上。这意味着只能从运行在集群内部的 Pod 中访问服务,而不能从集群外部(例如,从您的本地机器)进行调用。图 21-4 说明了类型为 ClusterIP 的服务的可访问性。

图 21-4. 类型为 ClusterIP 的服务的可访问性
创建和检查服务
我们将创建一个 Pod 和相应的服务,以演示 ClusterIP 服务类型的运行时行为。名为 echoserver 的 Pod 公开容器端口 8080,并指定标签 app=echoserver。服务为传入流量定义端口 5005,将其转发到出口端口 8080. 标签选择与我们设置的 Pod 匹配:
$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
--port=8080 -l app=echoserver
pod/echoserver created
$ kubectl create service clusterip echoserver --tcp=5005:8080
service/echoserver created
使用命令 kubectl get service echoserver -o yaml 检查实时对象,将呈现分配的集群 IP 地址。示例 21-2 显示了服务运行时表示的简化版本。
示例 21-2. 运行时的 ClusterIP 服务对象
apiVersion: v1
kind: Service
metadata:
name: echoserver
spec:
type: ClusterIP 
clusterIP: 10.96.254.0 
selector:
app: echoserver
ports:
- port: 5005
targetPort: 8080
protocol: TCP
设置为 ClusterIP 类型的服务。
运行时分配给服务的集群 IP 地址。
此示例中使服务可用的集群 IP 地址是 10.96.254.0。列出服务对象是呈现所需信息的另一种方式,以便调用服务:
$ kubectl get service echoserver
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
echoserver ClusterIP 10.96.254.0 <none> 5005/TCP 8s
接下来,我们将尝试对服务进行调用。
访问服务
您可以使用集群 IP 地址和传入端口组合来访问服务:10.96.254.0:5005. 从集群外的任何其他机器上进行请求将失败,如以下 wget 命令所示:
$ wget 10.96.254.0:5005 --timeout=5 --tries=1
--2021-11-15 15:45:36-- http://10.96.254.0:5005/
Connecting to 10.96.254.0:5005... ]failed: Operation timed out.
Giving up.
从 Pod 内部访问服务会正确地将请求路由到与标签选择匹配的 Pod:
$ kubectl run tmp --image=busybox:1.36.1 --restart=Never -it --rm \
-- wget 10.96.254.0:5005
Connecting to 10.96.254.0:5005 (10.96.254.0:5005)
saving to 'index.html'
index.html 100% |********************************| 408 0:00:00 ETA
'index.html' saved
pod "tmp" deleted
除了使用集群 IP 地址和端口外,您还可以通过 DNS 名称和容器可用的环境变量来发现服务。
通过 DNS 查找发现服务
Kubernetes 通过其名字将每个服务注册到其 DNS 服务 CoreDNS 中。在内部,CoreDNS 将服务名称存储为主机名,并将其映射到集群 IP 地址。在构建微服务架构时,通过其 DNS 名称而不是 IP 地址访问服务更加方便和表达性。
可以通过在同一命名空间中运行一个 Pod 来验证正确的服务发现,该 Pod 使用其主机名和传入端口调用 Service:
$ kubectl run tmp --image=busybox:1.36.1 --restart=Never -it --rm \
-- wget echoserver:5005
Connecting to echoserver:5005 (10.96.254.0:5005)
saving to 'index.html'
index.html 100% |********************************| 408 0:00:00 ETA
'index.html' saved
pod "tmp" deleted
在 Pod 与位于不同命名空间中的 Service 进行调用并不罕见。仅仅引用 Service 的主机名在跨命名空间中是不起作用的。你需要追加命名空间。以下是在 other 命名空间中的 Pod 调用 default 命名空间中的 Service 的示例:
$ kubectl run tmp --image=busybox:1.36.1 --restart=Never -it --rm \
-n other -- wget echoserver.default:5005
Connecting to echoserver.default:5005 (10.96.254.0:5005)
saving to 'index.html'
index.html 100% |********************************| 408 0:00:00 ETA
'index.html' saved
pod "tmp" deleted
Service 的完整主机名为 echoserver.default.svc.cluster.local。字符串 svc 描述了我们要通信的资源类型。CoreDNS 使用默认值 cluster.local 作为域名(如果需要更改,此域名是可配置的)。在与 Service 通信时,不必拼写完整的主机名。
通过环境变量发现 Service
对于在 Pod 中运行的应用程序,直接使用环境变量中的 Service 连接信息可能更为简单。kubelet 使每个活动 Service 的集群 IP 地址和端口作为环境变量可用。与 Service 相关的环境变量的命名约定为 <SERVICE_NAME>_SERVICE_HOST 和 <SERVICE_NAME>_SERVICE_PORT。
Service 环境变量的可用性
确保在实例化 Pod 之前创建 Service。否则,这些环境变量将不会被填充。
通过列出容器的环境变量可以验证实际的键值对:
$ kubectl exec -it echoserver -- env
ECHOSERVER_SERVICE_HOST=10.96.254.0
ECHOSERVER_SERVICE_PORT=8080
...
Service 的名称 echoserver 不包含任何特殊字符。这就是为什么将其转换为环境变量键名是简单的原因;Service 名称只是简单地大写以符合环境变量命名约定。任何 Service 名称中的特殊字符(例如连字符)都将被下划线字符替换。如果要填充这些环境变量,确保在启动 Pod 前已创建了 Service。
NodePort Service 类型
使用 NodePort 类型声明 Service,通过节点 IP 地址公开访问,可以从 Kubernetes 集群外部解析。节点 IP 地址可以与端口号(范围在 30000 到 32767,也称为节点端口)结合使用,该端口在创建 Service 时会自动分配。图 21-5 说明了通过 NodePort 类型 Service 转发流量至 Pods 的路由。

图 21-5. 使用 NodePort 类型的 Service 的可访问性
节点端口在集群中的每个节点上都是开放的,并且其值在集群范围内是全局且唯一的。为避免端口冲突,最好不定义确切的节点端口,而是让 Kubernetes 找到一个可用端口。
创建和检查 Service
下面两个命令创建了一个 Pod 和一个 NodePort 类型的 Service。这里唯一的区别是命令行选项中提供了 nodeport 而不是 clusterip:
$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
--port=8080 -l app=echoserver
pod/echoserver created
$ kubectl create service nodeport echoserver --tcp=5005:8080
service/echoserver created
服务对象的运行时表示如示例 21-3 所示。需要指出的是,节点端口将自动分配。请记住,NodePort(大写N)是服务类型,而nodePort(小写n)是其键对应的值。
示例 21-3. 运行时的 NodePort 服务对象
apiVersion: v1
kind: Service
metadata:
name: echoserver
spec:
type: NodePort 
clusterIP: 10.96.254.0
selector:
app: echoserver
ports:
- port: 5005
nodePort: 30158 
targetPort: 8080
protocol: TCP
将服务类型设置为NodePort。
使服务从集群外部可访问的静态分配节点端口。
创建服务后,可以列出它。您会发现端口表示包含使服务可访问的静态分配端口:
$ kubectl get service echoserver
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
echoserver NodePort 10.101.184.152 <none> 5005:30158/TCP 5s
在此输出中,节点端口为 30158(通过冒号分隔可识别)。入站端口 5005 仍然可用于解析来自集群内部的服务目的。
访问服务
在集群内部,您仍然可以使用集群 IP 地址和端口号访问服务。此服务的行为与ClusterIP类型完全相同:
$ kubectl run tmp --image=busybox:1.36.1 --restart=Never -it --rm \
-- wget 10.101.184.152:5005
Connecting to 10.101.184.152:5005 (10.101.184.152:5005)
saving to 'index.html'
index.html 100% |********************************| 414 0:00:00 ETA
'index.html' saved
pod "tmp" deleted
从集群外部,您需要使用集群中任何工作节点的 IP 地址和静态分配的端口。确定工作节点 IP 地址的一种方法是渲染节点详细信息。另一种选择是使用 Pod 的status.hostIP属性值,即 Pod 运行所在的工作节点的 IP 地址。
此处的节点 IP 地址为192.168.64.15。可用于从集群外部调用服务:
$ kubectl get nodes -o \
jsonpath='{ $.items[*].status.addresses[?(@.type=="InternalIP")].address }'
192.168.64.15
$ wget 192.168.64.15:30158
--2021-11-16 14:10:16-- http://192.168.64.15:30158/
Connecting to 192.168.64.15:30158... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/plain]
Saving to: ‘index.html’
...
LoadBalancer 服务类型
本书讨论的最后一种服务类型是LoadBalancer。这种服务类型为 Kubernetes 云提供商提供外部负载均衡器,主要用于向集群节点分发入站请求的单一 IP 地址。负载平衡策略的实现(例如轮询)由云提供商决定。
本地 Kubernetes 集群的负载均衡器
Kubernetes 未为本地集群提供原生负载均衡器解决方案。云提供商负责提供适当的实现。MetalLB 项目旨在填补这一空白。
图 21-6 显示了LoadBalancer服务类型的架构概述。

图 21-6. 类型为LoadBalancer的服务的可访问性
如图所示,负载均衡器在不同节点之间路由流量,只要目标 Pod 满足请求的标签选择。
创建和检查服务
要将服务创建为负载均衡器,请在清单中将类型设置为LoadBalancer,或使用create service loadbalancer命令:
$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
--port=8080 -l app=echoserver
pod/echoserver created
$ kubectl create service loadbalancer echoserver --tcp=5005:8080
service/echoserver created
LoadBalancer Service 类型的运行时特性看起来与 NodePort Service 类型提供的类似。主要区别在于外部 IP 地址列具有值,如 Example 21-4 所示。
示例 21-4. 运行时的 LoadBalancer Service 对象
apiVersion: v1
kind: Service
metadata:
name: echoserver
spec:
type: LoadBalancer 
clusterIP: 10.96.254.0
loadBalancer: 10.109.76.157 
selector:
app: echoserver
ports:
- port: 5005
targetPort: 8080
nodePort: 30158
protocol: TCP
Service 类型设置为 LoadBalancer。
Service 在运行时分配的外部 IP 地址。
列出 Service 时会显示外部 IP 地址,示例中为 10.109.76.157,如以下命令所示:
$ kubectl get service echoserver
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
echoserver LoadBalancer 10.109.76.157 10.109.76.157 5005:30642/TCP 5s
由于外部负载均衡器需要由云提供商进行配置,因此直到外部 IP 地址变为可用可能需要一些时间。
访问 Service
要从集群外调用 Service,请使用外部 IP 地址及其入站端口:
$ wget 10.109.76.157:5005
--2021-11-17 11:30:44-- http://10.109.76.157:5005/
Connecting to 10.109.76.157:5005... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/plain]
Saving to: ‘index.html’
...
如讨论的,LoadBalancer Service 也可以像访问 ClusterIP 或 NodePort Service 一样访问。
概述
Kubernetes 为集群中的每个 Pod 分配唯一的 IP 地址。Pod 可以使用该 IP 地址彼此通信;但是,不能依赖 IP 地址随时间的稳定性。这就是 Kubernetes 提供 Service 资源类型的原因。
Service 根据标签选择和端口映射将网络流量转发到一组 Pod。每个 Service 都需要分配一个类型,该类型决定了如何从集群内部或外部访问 Service。考试相关的 Service 类型有 ClusterIP、NodePort 和 LoadBalancer。CoreDNS,即 Kubernetes 的 DNS 服务器,允许 Pod 从同一命名空间及其他命名空间通过主机名访问 Service。
考试要点
理解 Service 的目的
通过它们的 IP 地址进行 Pod 到 Pod 的通信不能保证随时间的稳定网络接口。Pod 的重新启动将会分配一个新的虚拟 IP 地址。Service 的目的是提供稳定的网络接口,以便您可以操作在 Kubernetes 集群中运行的复杂微服务架构。在大多数情况下,Pod 通过主机名调用 Service。主机名由运行在 kube-system 命名空间中的名为 CoreDNS 的 DNS 服务器提供。
练习如何访问每种类型的 Service
考试期望您理解 ClusterIP、NodePort 和 LoadBalancer 之间的差异。根据分配的类型,Service 可以从集群内部或外部访问。
解决 Service 故障排除场景
很容易配置 Service 的配置错误。任何配置错误都将导致无法将网络流量传输到预期的 Pod 集合。常见的配置错误包括不正确的标签选择和端口分配。kubectl get endpoints 命令将帮助您了解 Service 可以路由流量到哪些 Pod。
示例练习
这些练习的解决方案位于附录 A 中。
-
创建名为
myapp的 Service,类型为ClusterIP,暴露端口 80 并映射到目标端口 80。创建名为
myapp的 Deployment,创建 1 个副本,运行镜像nginx:1.23.4-alpine。暴露容器端口 80。将 Deployment 扩展到 2 个副本。使用镜像
busybox:1.36.1创建临时 Pod,并执行针对服务 IP 的wget命令。将服务类型更改为
NodePort,以便从集群外部访问 Pod。从集群外部执行wget命令来访问该服务。 -
Kate 是负责实现基于 Web 的应用程序堆栈的开发人员。她对 Kubernetes 不熟悉,并请求您的帮助。相关对象已创建;然而,无法从集群内部连接到应用程序。帮助 Kate 修复她的 YAML 清单的配置。
导航至 GitHub 仓库bmuschko/ckad-study-guide的已检出分支中的目录app-a/ch21/troubleshooting。从 YAML 清单setup.yaml创建对象。检查命名空间
y72中的对象。在命名空间
y72中使用镜像busybox:1.36.1创建临时 Pod。容器命令应调用 Serviceweb-app进行wget调用。wget调用无法成功连接到该 Service。确定连接问题的根本原因并加以修复。通过重复前一步骤验证正确行为。
wget调用应返回成功响应。
第二十二章:Ingress
第二十一章 探讨了服务原语的目的和创建。一旦需要将应用程序暴露给外部消费者,选择适当的服务类型变得至关重要。最实际的选择通常涉及创建类型为 LoadBalancer 的服务。这种服务通过分配给消费者外部可访问的外部 IP 地址来提供负载平衡能力,超出 Kubernetes 集群的范围。
然而,为每个外部可达的应用程序选择 LoadBalancer 服务存在缺点。在云提供商环境中,每个服务触发外部负载均衡器的配置,导致成本增加。此外,管理一组 LoadBalancer 服务对象可能会带来管理挑战,因为每个外部可访问的微服务都必须建立一个新的对象。
为了缓解这些问题,Ingress 原语发挥作用,提供一个统一的、负载均衡的应用程序堆栈入口点。Ingress 具有根据可选的、可通过 DNS 解析的主机名和 URL 上下文路径路由外部 HTTP(S) 请求到集群内一个或多个服务的能力。本章将指导您创建和访问 Ingress。
在 minikube 中访问 Ingress
在 minikube 中访问 Ingress 需要特别处理。请参考 Kubernetes 教程 “在 Minikube 上使用 NGINX Ingress 控制器设置 Ingress” 获取详细指南。
使用 Ingress
Ingress 通过外部可访问的 URL 公开 HTTP(和可选的 HTTPS)路由给客户端。与 Ingress 配置的路由规则决定了 流量应该如何 被路由。云提供商的 Kubernetes 环境通常会部署一个外部负载均衡器。Ingress 从负载均衡器获取公共 IP 地址。您可以配置规则,根据特定的 URL 上下文路径将流量路由到多个服务,如 图 22-1 所示。

图 22-1. 通过 HTTP(S) 管理对服务的外部访问
在 图 22-1 中描述的场景中,将 Ingress 实例化为 HTTP(S) 调用到域名“next.example.com”的唯一入口点。根据提供的 URL 上下文,Ingress 将流量定向到两个虚构服务之一:一个设计用于业务应用程序,另一个用于获取与应用程序相关的指标。
具体而言,URL 上下文路径/app被路由到负责管理业务应用程序的应用服务。相反,向 URL 上下文/metrics发送请求将导致调用被转发到能够返回相关指标的指标服务。
安装 Ingress 控制器
为了使 Ingress 正常运行,必须有一个 Ingress 控制器。此控制器评估由 Ingress 定义的一组规则,指导流量的路由。选择 Ingress 控制器通常取决于 Kubernetes 集群管理员的特定用例、要求和偏好。生产级别的 Ingress 控制器的显著示例包括 F5 NGINX Ingress Controller 或 AKS Application Gateway Ingress Controller。还可以在 Kubernetes 文档 中探索其他选项。
在安装后,您应该找到至少一个运行 Ingress 控制器的 Pod。此输出显示由 NGINX Ingress 控制器创建的 Pod 位于命名空间 ingress-nginx 中:
$ kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-qqhrp 0/1 Completed 0 60s
ingress-nginx-admission-patch-56z26 0/1 Completed 1 60s
ingress-nginx-controller-7c6974c4d8-2gg8c 1/1 Running 0 60s
一旦 Ingress 控制器 Pod 进入“Running”状态,可以假定由 Ingress 对象定义的规则将被评估。
部署多个 Ingress 控制器
当然,在单个集群内部部署多个 Ingress 控制器是一个可行的选择,特别是如果云服务提供商已经在 Kubernetes 集群中预配置了一个 Ingress 控制器。Ingress API 引入了属性 spec.ingressClassName,以便按名称选择特定的控制器实现。要识别所有已安装的 Ingress 类别,可以使用以下命令:
$ kubectl get ingressclasses
NAME CONTROLLER PARAMETERS AGE
nginx k8s.io/ingress-nginx <none> 14m
Kubernetes 通过扫描所有 Ingress 类对象中的注释 ingressclass.kubernetes.io/is-default-class: "true" 来确定默认的 Ingress 类。在 Ingress 对象没有使用属性 spec.ingressClassName 明确指定 Ingress 类的情况下,它们会自动默认为通过此注释标记为默认的 Ingress 类。该机制提供了管理 Ingress 类的灵活性,并在单个 Ingress 对象中未指定特定类时提供默认行为。
配置 Ingress 规则
创建 Ingress 时,您可以灵活定义一个或多个规则。每个规则包括可选主机的规范、一组 URL 上下文路径以及负责路由入站流量的后端。该结构允许对 Kubernetes 集群内的外部 HTTP(S) 请求进行细粒度控制,根据指定条件为不同的服务提供服务。表 22-1 描述了三个规则。
表 22-1. Ingress 规则
| 类型 | 示例 | 描述 |
|---|---|---|
| 可选主机 | next.example.com |
如果提供,规则适用于该主机。如果未定义主机,则处理所有入站 HTTP(S) 流量(例如,如果通过 Ingress 的 IP 地址进行)。 |
| 路径列表 | /app |
进入流量必须匹配主机和路径,以正确转发流量到服务。 |
| 后端 | app-service:8080 |
服务名称和端口的组合。 |
Ingress 控制器可以选择定义一个默认后端,作为如果没有匹配任何配置的 Ingress 规则时的备用路由。您可以在Ingress 原语文档中了解更多信息。
创建 Ingresses
你可以使用命令create ingress来创建一个 Ingress。你需要提供的主要命令行选项是--rule,它以逗号分隔的方式定义规则。每个键值对的表示法为<host>/<path>=<service>:<port>。让我们创建一个具有两个规则的 Ingress 对象:
$ kubectl create ingress next-app \
--rule="next.example.com/app=app-service:8080" \
--rule="next.example.com/metrics=metrics-service:9090"
ingress.networking.k8s.io/next-app created
当查看create ingress --help命令的输出时,可以指定更精细的规则。
支持 TLS 终止
对于 HTTP 流量的端口 80 是隐含的,因为我们没有指定对 TLS Secret 对象的引用。如果在规则定义中指定了tls=mysecret,那么端口 443 也会在此列出。有关启用 HTTPS 流量的更多信息,请参见Kubernetes 文档。考试不涵盖为 Ingress 配置 TLS 终止。
使用 YAML 清单来定义 Ingress 通常更直观,也是许多人首选的方式。它提供了一种更清晰和更结构化的方式来表达所需的配置。作为 YAML 清单定义的 Ingress 显示在示例 22-1 中。
示例 22-1. 由 YAML 清单定义的 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: next-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1 
spec:
rules:
- host: next.example.com 
http:
paths:
- backend:
service:
name: app-service
port:
number: 8080
path: /app
pathType: Exact
- host: next.example.com 
http:
paths:
- backend:
service:
name: metrics-service
port:
number: 9090
path: /metrics
pathType: Exact
分配了一个 NGINX Ingress 特定的注解,用于重写 URL。
定义将app-service后端映射到 URL next.example.com/app 的规则。
定义将metrics-service后端映射到 URL next.example.com/metrics 的规则。
Ingress YAML 清单与由命令创建的实时对象表示之间存在一个主要区别:Ingress 控制器注解的分配。某些 Ingress 控制器实现提供了注解来自定义它们的行为。您可以在对应文档中找到 NGINX Ingress 控制器提供的所有注解的完整列表。
定义路径类型
前面的 YAML 清单演示了通过属性spec.rules[].http.paths[].pathType指定路径类型的选项之一。路径类型定义了如何根据声明的路径评估传入请求。表 22-2 指示了传入请求及其路径的评估。有关更全面列表,请参阅Kubernetes 文档。
表 22-2. Ingress 路径类型
| 路径类型 | 规则 | 进入请求 |
|---|---|---|
精确 |
/app |
仅匹配 /app,不匹配 /app/test 或 /app/ |
前缀 |
/app |
匹配 /app 和 /app/,但不匹配 /app/test |
精确 和 前缀 路径类型之间的关键区别在于它们对尾部斜杠的处理。前缀 路径类型仅关注 URL 上下文路径的提供前缀,允许处理包含尾部斜杠的 URL 请求。相反,精确 路径类型更为严格,要求指定的 URL 上下文路径完全匹配,不考虑尾部斜杠。
列出入口
使用 get ingress 命令可以列出 Ingress。您将看到在创建 Ingress 时指定的部分信息(例如主机):
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
next-app nginx next.example.com 192.168.66.4 80 5m38s
Ingress 自动选择了由 Ingress 控制器配置的默认 Ingress 类 nginx。您可以在 CLASS 列下找到该信息。在 ADDRESS 列下列出的值是外部负载均衡器提供的 IP 地址。
渲染 Ingress 详细信息
describe ingress 命令是获取有关 Ingress 资源详细信息的宝贵工具。它以清晰的表格格式呈现规则,有助于理解路由配置。此外,在故障排除时,注意任何额外消息或事件至关重要。
在提供的输出中,显然可能存在与 Ingress 规则中映射的名为 app-service 和 metrics-service 的服务存在问题。指定服务与其存在之间的差异可能导致路由错误:
$ kubectl describe ingress next-app
Name: next-app
Labels: <none>
Namespace: default
Address: 192.168.66.4
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
next.example.com
/app app-service:8080 (<error: endpoints \
"app-service" not found>)
/metrics metrics-service:9090 (<error: endpoints \
"metrics-service" not found>)
Annotations: <none>
Events:
Type Reason Age From ...
---- ------ ---- ---- ...
Normal Sync 6m45s (x2 over 7m3s) nginx-ingress-controller ...
此外,观察显示 Ingress 控制器同步活动的事件日志至关重要。此日志中的任何警告或错误都可以提供关于同步过程中潜在问题的见解。
为了解决问题,请确保 Ingress 规则中指定的服务实际存在并且在 Kubernetes 集群内可访问。此外,请检查事件日志以查看可能指示不一致原因的相关消息。
让我们解决无法路由到 Ingress 对象配置的后端的问题。以下命令创建 Pod 和服务:
$ kubectl run app --image=k8s.gcr.io/echoserver:1.10 --port=8080 \
-l app=app-service
pod/app created
$ kubectl run metrics --image=k8s.gcr.io/echoserver:1.10 --port=8080 \
-l app=metrics-service
pod/metrics created
$ kubectl create service clusterip app-service --tcp=8080:8080
service/app-service created
$ kubectl create service clusterip metrics-service --tcp=9090:8080
service/metrics-service created
检查 Ingress 对象并未显示配置规则的任何错误。如果您现在能够看到可解析后端列表以及相应的 Pod 虚拟 IP 地址和端口,则 Ingress 对象已正确配置,并且后端已被识别并且可访问:
$ kubectl describe ingress next-app
Name: next-app
Labels: <none>
Namespace: default
Address: 192.168.66.4
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
next.example.com
/app app-service:8080 (10.244.0.6:8080)
/metrics metrics-service:9090 (10.244.0.7:8080)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 13m (x2 over 13m) nginx-ingress-controller Scheduled for sync
如果您在通过 Ingress 端点路由流量时遇到任何问题,回到 Ingress 细节是值得的。
访问入口
要使传入的 HTTP(S) 流量通过 入口 并进一步传递到配置的 服务,设置与外部地址映射的 DNS 记录非常重要。通常涉及配置 A 记录或 CNAME 记录。 ExternalDNS 项目 是一个有价值的工具,可以帮助自动管理这些 DNS 记录。
对于在你的机器上的 Kubernetes 集群上的本地测试,请按照以下步骤进行:
-
找出入口使用的负载均衡器的 IP 地址。
-
将 IP 地址添加到主机名映射到 /etc/hosts 文件中。
通过将 IP 地址添加到本地 /etc/hosts 文件中,可以在不依赖实际 DNS 记录的情况下模拟本地 DNS 解析,从而测试入口的行为:
$ kubectl get ingress next-app \
--output=jsonpath="{.status.loadBalancer.ingress[0]['ip']}"
192.168.66.4
$ sudo vim /etc/hosts
...
192.168.66.4 next-app
现在你可以向后端发送 HTTP 请求了。此调用匹配 精确 路径规则,因此从应用程序返回 HTTP 200 响应代码:
$ wget next.example.com/app --timeout=5 --tries=1
--2021-11-30 19:34:57-- http://next.example.com/app
Resolving next.example.com (next.example.com)... 192.168.66.4
Connecting to next.example.com (next.example.com)|192.168.66.4|:80... \
connected.
HTTP request sent, awaiting response... 200 OK
此次调用使用了带有尾部斜杠的 URL。入口路径规则不支持此情况,因此调用无法通过。你会收到 HTTP 404 响应代码。要使第二次调用起作用,必须将路径规则更改为 前缀:
$ wget next.example.com/app/ --timeout=5 --tries=1
--2021-11-30 15:36:26-- http://next.example.com/app/
Resolving next.example.com (next.example.com)... 192.168.66.4
Connecting to next.example.com (next.example.com)|192.168.66.4|:80... \
connected.
HTTP request sent, awaiting response... 404 Not Found
2021-11-30 15:36:26 ERROR 404: Not Found.
你可以观察到配置了 URL 上下文路径 metrics 的指标服务的相同行为。也可以随时尝试一下。
概要
资源类型入口定义了将群集外部 HTTP(S) 流量路由到一个或多个服务的规则。每个规则定义了一个 URL 上下文路径以定位一个服务。为了使入口工作,您首先需要安装一个入口控制器。入口控制器定期评估这些规则,并确保它们适用于群集。为了暴露入口,云提供商通常会启动一个外部负载均衡器,为入口提供外部 IP 地址。
考试要点
理解 服务 和 入口 之间的区别。
入口不应与服务混淆。入口旨在基于可选的主机名和强制路径将群集外部 HTTP(S) 流量路由到一个或多个服务。服务将流量路由到一组 Pod。
理解入口控制器的作用
在 入口控制器 安装之前,入口才能正常运行。如果没有安装入口控制器,入口规则将不会生效。你可以在 Kubernetes 文档页面上找到一系列入口控制器的实现方式。假设在考试环境中会预先安装入口控制器。
练习定义入口规则
你可以在入口中定义一个或多个规则。每个规则包含一个可选的主机名、URL 上下文路径和服务 DNS 名称和端口。尝试定义多个规则以及如何访问端点。你不必理解配置入口的 TLS 终止过程 —— 这一方面已经在 CKS 考试中涵盖。
示例练习
这些练习的解决方案可以在 附录 A 中找到。
-
创建一个名为
web的新部署,控制一个运行在端口 3000 上的映像bmuschko/nodejs-hello-world:1.0.0的副本。使用类型为ClusterIP的名为web的 Service 公开部署。该 Service 将流量路由到由部署web控制的 Pod。向应用程序的端点路径 / 发出请求。您应该看到消息 “Hello World.”。创建一个暴露路径 / 的 Ingress,用于主机 hello-world.exposed。流量应路由到先前创建的 Service。列出 Ingress 对象。
在 /etc/hosts 中添加一个条目,将负载均衡器 IP 地址映射到主机 hello-world.exposed。向 http://hello-world.exposed 发出请求。您应该看到消息 “Hello World.”。
-
任何应用程序都已经通过 Ingress 暴露。一些最终用户报告从集群外部连接到应用程序时出现问题。检查现有设置并为最终用户解决问题。
切换到检出的 GitHub 仓库bmuschko/ckad-study-guide的目录 app-a/ch22/troubleshooting。从 YAML 清单 setup.yaml 中创建对象。在命名空间
s96中检查这些对象。为主机名 faulty.ingress.com 在 /etc/hosts 中创建一个条目。使用
wget或curl对 faulty.ingress.com/ 执行 HTTP 调用。检查连接错误。更改配置以确保最终用户可以连接到 Ingress。通过执行另一个 HTTP 调用来验证连接的正确性。
第二十三章:网络策略
分配给 Pod 的 IP 地址的唯一性在所有节点和命名空间中保持不变。这是通过在其创建过程中为每个注册节点分配专用子网来实现的。当在节点上创建新的 Pod 时,容器网络接口(CNI)插件会从分配的子网中分配 IP 地址。因此,节点上的 Pod 可以与集群中任何节点上运行的所有其他 Pod 无缝通信。
Kubernetes 中的网络策略与防火墙规则类似,专门设计用于管理 Pod 到 Pod 的通信。这些策略包括指定网络流量方向(入站和/或出站)的规则,适用于命名空间内或跨不同命名空间中一个或多个 Pod。此外,这些规则定义了通信的目标端口。这种精细化的控制增强了 Kubernetes 集群内部流量的安全性和管理能力。
使用网络策略
在 Kubernetes 集群中,任何 Pod 都可以使用其IP 地址或 DNS 名称与任何其他 Pod 自由通信,甚至跨命名空间。不仅不受限制的 Pod 间通信可能存在安全风险,还使得架构的心智通信模型难以理解。网络策略定义了控制从 Pod 到 Pod 的流量的规则,如图 23-1 所示。

图 23-1. 网络策略定义了从 Pod 到 Pod 的流量
例如,没有充分的理由允许运行在一个 Pod 中的后端应用直接与运行在另一个 Pod 中的前端应用进行通信。通信应该从前端 Pod 指向后端 Pod。
安装网络策略控制器
没有网络策略控制器,网络策略是无法工作的。网络策略控制器评估由网络策略定义的规则集合。在Kubernetes 文档中可以找到各种网络策略控制器的说明。
Cilium是一个实现网络策略控制器的 CNI。您可以在云提供商和本地 Kubernetes 集群上安装 Cilium。详细信息请参阅安装说明。安装完成后,您应该能够在 kube-system 命名空间中找到至少两个运行 Cilium 和 Cilium Operator 的 Pod:
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
cilium-k5td6 1/1 Running 0 110s
cilium-operator-f5dcdcc8d-njfbk 1/1 Running 0 110s
现在可以假设网络策略对象定义的规则将被评估。此外,您可以使用 Cilium 命令行工具验证正确的安装。
创建网络策略
标签选择在定义网络策略适用于哪些 Pod 方面起着至关重要的作用。我们已经在其他上下文中看到过这个概念(例如,部署和服务)。此外,网络策略定义了流量的方向,允许或禁止。在网络策略的上下文中,入站流量称为入口,出站流量称为出口。对于入口和出口,您可以像 Pod、IP 地址或端口这样设置白名单。
网络策略不适用于服务。
在大多数情况下,您将设置服务对象来基于标签和端口选择将网络流量引导到 Pod。网络策略完全不涉及服务。所有规则都是命名空间和 Pod 特定的。
通过示例最好解释了网络策略的创建。假设您正在处理以下场景:您正在运行一个 Pod,该 Pod 向其他消费者公开 API。例如,一个处理其他应用程序付款的 Pod。您所在的公司正在将应用程序从旧的付款处理器迁移到新的付款处理器。因此,您只希望允许能够正确与其通信的应用程序访问。目前,您有两个消费者——一个杂货店和一个咖啡店——每个都在单独的 Pod 中运行其应用程序。咖啡店已准备好消费付款处理器的 API,但杂货店尚未准备好。 Figure 23-2 显示了 Pod 及其分配的标签。

图 23-2. 限制与 Pod 之间的流量
在创建网络策略之前,我们将启动用于表示场景的 Pod:
$ kubectl run grocery-store --image=nginx:1.25.3-alpine \
-l app=grocery-store,role=backend --port 80
pod/grocery-store created
$ kubectl run payment-processor --image=nginx:1.25.3-alpine \
-l app=payment-processor,role=api --port 80
pod/payment-processor created
$ kubectl run coffee-shop --image=nginx:1.25.3-alpine \
-l app=coffee-shop,role=backend --port 80
鉴于 Kubernetes 默认允许无限制的 Pod 对 Pod 通信,这三个 Pod 将能够相互通信。以下命令验证了这种行为。杂货店和咖啡店的 Pod 对付款处理器 Pod 的 IP 地址执行 wget 调用:
$ kubectl get pod payment-processor --template '{{.status.podIP}}'
10.244.0.136
$ kubectl exec grocery-store -it -- wget --spider --timeout=1 10.244.0.136
Connecting to 10.244.0.136 (10.244.0.136:80)
remote file exists
$ kubectl exec coffee-shop -it -- wget --spider --timeout=1 10.244.0.136
Connecting to 10.244.0.136 (10.244.0.136:80)
remote file exists
您不能使用命令式的 create 命令创建新的网络策略。而是必须使用声明式方法。存储在文件 networkpolicy-api-allow.yaml 中的 Example 23-1 中的 YAML 清单显示了前面描述场景的网络策略。
Example 23-1. 使用 YAML 声明 NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow
spec:
podSelector: 
matchLabels:
app: payment-processor
role: api
ingress: 
- from:
- podSelector:
matchLabels:
app: coffee-shop
通过标签选择来选择应用策略的 Pod。
允许来自同一命名空间中具有匹配标签的 Pod 的入站流量。
网络策略定义了几个重要属性,这些属性一起形成其规则集。 Table 23-1 显示了 spec 级别的属性。
表 23-1. 网络策略的规范属性
| 属性 | 描述 |
|---|---|
podSelector |
选择在命名空间中应用网络策略的 Pod。 |
policyTypes |
定义网络策略适用的流量类型(即入口和/或出口)。 |
ingress |
列出了入站流量的规则。每个规则可以定义 from 和 ports 部分。 |
egress |
列出了出站流量的规则。每个规则可以定义 to 和 ports 部分。 |
您可以使用 spec.ingress.from[] 和 spec.egress.to[] 来独立指定入口和出口规则。每个规则由 Pod 选择器、可选的命名空间选择器或两者的组合组成。表 23-2 列出了 to 和 from 选择器的相关属性。
表 23-2. 网络策略 to 和 from 选择器的属性
| 属性 | 描述 |
|---|---|
podSelector |
通过标签在与网络策略相同的命名空间中选择 Pod,这些 Pod 应被允许作为入口源或出口目的地。 |
namespaceSelector |
通过标签选择命名空间,所有 Pod 应被允许作为入口源或出口目的地。 |
namespaceSelector 和 podSelector |
通过标签在命名空间中选择 Pod。 |
让我们看看网络策略在实际中的效果。从清单创建网络策略对象:
$ kubectl apply -f networkpolicy-api-allow.yaml
networkpolicy.networking.k8s.io/api-allow created
网络策略阻止了从杂货店 Pod 调用支付处理器。从咖啡店 Pod 访问支付处理器完全正常,因为网络策略的 Pod 选择器与分配给 Pod 的标签 app=coffee-shop 匹配:
kubectl exec grocery-store -it -- wget --spider --timeout=1 10.244.0.136
Connecting to 10.244.0.136 (10.244.0.136:80)
wget: download timed out
command terminated with exit code 1
$ kubectl exec coffee-shop -it -- wget --spider --timeout=1 10.244.0.136
Connecting to 10.244.0.136 (10.244.0.136:80)
remote file exists
作为开发人员,您可能需要处理由其他团队成员或管理员为您设置的网络策略。您需要了解 kubectl 命令来列出和检查网络策略对象,以了解它们对微服务之间方向性网络流量的影响。
列出网络策略
列出网络策略的操作与任何其他 Kubernetes 原语相同。使用 get 命令结合资源类型 networkpolicy 或其简写形式 netpol。对于先前的网络策略,您会看到一个表格,显示名称和 Pod 选择器:
$ kubectl get networkpolicy api-allow
NAME POD-SELECTOR AGE
api-allow app=payment-processor,role=api 83m
遗憾的是,该命令的输出并未提供关于入口和出口规则的大量信息。要获取更多信息,您需要深入了解详情。
渲染网络策略详细信息
您可以使用 describe 命令检查网络策略的详细信息。输出会渲染所有重要信息:Pod 选择器,以及入口和出口规则:
$ kubectl describe networkpolicy api-allow
Name: api-allow
Namespace: default
Created on: 2024-01-10 09:06:59 -0700 MST
Labels: <none>
Annotations: <none>
Spec:
PodSelector: app=payment-processor,role=api
Allowing ingress traffic:
To Port: <any> (traffic allowed to all ports)
From:
PodSelector: app=coffee-shop
Not affecting egress traffic
Policy Types: Ingress
网络策略的详细信息没有清晰地描述基于其规则选择的 Pod。您可以创建符合规则和不符合规则的 Pod,以验证网络策略的预期行为。
可视化网络策略
正确定义网络策略的规则可能具有挑战性。页面 networkpolicy.io 提供了一个网络策略的可视化编辑器,在浏览器中呈现图形表示。
如前所述,每个 Pod 可以与集群的任何节点上运行的其他 Pod 进行通信,这暴露了潜在的安全风险。攻击者如果能够访问一个 Pod,理论上可以通过其虚拟 IP 地址与另一个 Pod 进行通信试图进行破坏。
应用默认网络策略
最小权限原则是一个基本的安全概念,在 Kubernetes 中限制 Pod 与 Pod 之间的网络流量时非常推荐。其思想是最初禁止所有流量,然后根据应用程序的架构和通信需求选择性地打开必要的连接。
通过默认网络策略,您可以锁定 Pod 与 Pod 之间的通信。默认网络策略是管理员设置的自定义策略,用于默认强制执行限制性通信模式。
为了演示这种默认网络策略的功能,我们将在命名空间 internal-tools 中设置两个 Pod。在命名空间内,所有 Pod 将能够彼此通信:
$ kubectl create namespace internal-tools
namespace/internal-tools created
$ kubectl run metrics-api --image=nginx:1.25.3-alpine --port=80 \
-l app=api -n internal-tools
pod/metrics-api created
$ kubectl run metrics-consumer --image=nginx:1.25.3-alpine --port=80 \
-l app=consumer -n internal-tools
pod/metrics-consumer created
让我们创建一个默认网络策略,在命名空间中拒绝所有入站和出站网络流量。我们将网络策略存储在名为 networkpolicy-deny-all.yaml 的文件中。
示例 23-2. 使用默认策略禁止所有流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: internal-tools
spec:
podSelector: {} 
policyTypes: 
- Ingress 
- Egress 
对于 spec.podSelector 的大括号意味着“适用于命名空间中的所有 Pod”。
定义规则应适用的流量类型,在本例中为入站和出站流量。
从清单创建网络策略:
$ kubectl apply -f networkpolicy-deny-all.yaml
networkpolicy.networking.k8s.io/default-deny-all created
网络策略防止 internal-tools 命名空间中 Pod 之间的任何网络通信,如下所示:
$ kubectl get pod metrics-api --template '{{.status.podIP}}' -n internal-tools
10.244.0.182
$ kubectl exec metrics-consumer -it -n internal-tools \
-- wget --spider --timeout=1 10.244.0.182
Connecting to 10.244.0.182 (10.244.0.182:80)
wget: download timed out
command terminated with exit code 1
$ kubectl get pod metrics-consumer --template '{{.status.podIP}}' \
-n internal-tools
10.244.0.70
$ kubectl exec metrics-api -it -n internal-tools \
-- wget --spider --timeout=1 10.244.0.70
Connecting to 10.244.0.70 (10.244.0.70:80)
wget: download timed out
command terminated with exit code 1
有了这些默认的拒绝限制,您可以逐步定义更详细的规则并逐渐放宽限制。网络策略是累加的。现在设置额外的网络策略来打开方向性流量是常见做法,但仅限于真正需要的那些。
限制对特定端口的访问
在 Kubernetes 中,控制端口级别的访问是网络安全的关键方面。如果没有网络策略明确定义,所有端口都是可访问的,这可能存在安全风险。例如,如果您在 Pod 中运行一个将端口 80 暴露给外部世界的应用程序,则保持其他所有端口开放会不必要地扩大攻击面。端口规则可以作为网络策略的一部分指定为入站和出站。在示例 23-3 中定义的网络策略允许访问端口 80。
示例 23-3. 定义允许端口 8080 入站访问的网络策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: port-allow
namespace: internal-tools
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- podSelector:
matchLabels:
app: consumer
ports: 
- protocol: TCP 
port: 80 
仅允许端口 80 的入站流量。
在定义网络策略时,只允许实施架构需求所需的端口。所有其他端口都应该被锁定。
总结
Kubernetes 中的容器内通信或同一 Pod 中两个容器之间的通信是完全不受限制的。网络策略规定了控制从 Pod 到 Pod 或从 Pod 到外部网络的流量的规则。你可以将网络策略视为 Pod 的防火墙规则。最佳实践是从“拒绝所有流量”规则开始,以最小化攻击向量。
从那里,您可以根据需要开放访问。学习网络策略的复杂性需要一些实践经验,因为不直接明显规则是否按预期工作。
考试要点
理解网络策略的目的和影响
默认情况下,Pod 与 Pod 之间的通信是不受限制的。使用最小特权原则实例化默认拒绝规则,以限制 Pod 与 Pod 之间的网络流量。网络策略的 spec.podSelector 属性根据标签选择来选择目标 Pod,规则适用于该 Pod。入口和出口规则定义了允许的入站和出站流量的 Pod、命名空间、IP 地址和端口。
知道如何实施最小特权原则
网络策略可以进行聚合。默认拒绝规则可以禁止入口和/或出口流量。附加的网络策略可以通过更精细的定义打开这些规则。
探索常见的网络策略场景
要探索常见场景,请查看名为 “Kubernetes Network Policy Recipes” 的 GitHub 仓库。该仓库提供每个场景的可视化表示,并引导您设置网络策略及相关 Pod 的步骤。这是一个很好的实践资源。
示例练习
这些练习的解决方案可在 附录 A 中找到。
-
您已被委托为现有应用程序堆栈设置网络策略。该堆栈包含
end-user命名空间中的frontendPod 和internal命名空间中的backendPod。转到已检出的 GitHub 仓库 bmuschko/ckad-study-guide 中的 app-a/ch23/app-stack 目录。从 YAML 清单 setup.yaml 创建对象。检查两个命名空间中的对象。
在
end-user命名空间中创建名为app-stack的网络策略。只允许frontendPod 到backendPod 的出口流量。backendPod 只能通过端口 80 访问。 -
转到已检出的 GitHub 仓库 bmuschko/ckad-study-guide 中的 app-a/ch23/troubleshooting 目录。从 YAML 清单 setup.yaml 创建对象。检查命名空间
k1和k2中的对象。确定在命名空间
k2中nginxPod 的虚拟 IP 地址。尝试从命名空间k1中的busyboxPod 到命名空间k2中的nginxPod 在端口 80 上进行 wget 调用。当前的设置下,此调用将失败。创建一个网络策略,允许命名空间
k1中所有 Pod 向命名空间k2中的nginxPod 执行入口调用。所有其他命名空间中的 Pod 应被拒绝向命名空间k2中的 Pod 执行入口调用。验证是否可以建立网络连接。
第一部分:介绍
本书的介绍部分涉及考试的最重要方面,并将 Kubernetes 的初学者引导到一个不引入过多复杂性的领域。
后续章节涵盖以下概念:
-
第一章讨论了考试的目标、课程内容以及通过考试的技巧和窍门。
-
第二章简要介绍了 Kubernetes。本章总结了 Kubernetes 的目的和好处,并概述了其架构和组件。
-
第三章讨论如何使用命令行工具
kubectl与 Kubernetes 集群进行交互。在考试中,这个工具将是你唯一的用户界面。我们将比较命令式和声明式命令,它们各自的优缺点,以及考试中的节省时间的技巧。
第二部分:应用程序设计与构建
应用程序设计与构建 领域总结了设计用于在 Kubernetes Pod 中运行的流程或应用程序的所有基础主题。
接下来的章节涵盖了这些概念:
-
第四章解释了基本的容器术语。内容使用 Docker 引擎介绍了定义、构建、运行和修改容器镜像的命令。
-
第五章将现有的容器镜像运行在 Pod 中,这是在 Kubernetes 中运行应用程序的基本原语,位于一个命名空间中。
-
第六章涉及在 Kubernetes 中建模批量工作负载和定期执行的工作负载。
-
第七章解释了如何使用临时卷在单个 Pod 中运行的容器之间共享数据。本章还将解释如何利用持久卷来存储长期数据。
-
第八章说明了希望在一个 Pod 中运行多个容器的用例,以及如何利用成熟的设计模式来实现它们。
-
第九章介绍了如何为对象分配标签和注释。阅读完本章后,您将了解标签在查询、排序和过滤对象中的重要性。相比于标签,注释添加了仅供人类阅读的元信息到对象中。
第三部分:应用部署
应用部署 涵盖了 Kubernetes 在企业设置中应用部署的概念、技术和原语。
后续章节涵盖以下概念:
-
第十章 通过示例解释了部署原语的特性。本章演示了如何手动和自动扩展运行应用程序的 Pod,并展示了新应用程序版本的默认部署过程。
-
第十一章 扩展了上一章节所学的经验。您将学习不同的部署策略,它们的适用场景以及如何在 Kubernetes 中实施它们。
-
第十二章 讨论了使用开源工具 Helm 部署更复杂的应用程序堆栈。本章重点介绍了通过 Artifact Hub 使用现有 Helm 图表的工作流程。
第四部分:应用程序的可观察性与维护
应用程序的可观察性与维护 包括在 Kubernetes 集群中运行、监视和故障排除应用程序的概念和技术。
下面的章节涵盖了以下概念:
-
第十三章为您详细介绍了在使用已弃用 API 定义对象时可能出现的场景。本章解释了您需要采取的措施,以确保这些对象在未来 Kubernetes 版本中的可操作性。
-
第十四章讨论了您可以为容器定义的探测器,以自动监视应用程序,以便检测潜在的运行时问题。
-
第十五章讲解了 Pod 和容器的故障排除技术。您将能够使用这些技术来识别应用运行时问题的根本原因,并学习如何修复它们。
第五部分:应用环境、配置和安全
本领域 应用环境、配置和安全 的主要焦点是配置带有安全设置的应用程序,定义和注入配置数据以及指定资源要求。与本领域相关的另外两个方面是通过自定义资源扩展 Kubernetes API,以及处理对 Kubernetes API 请求的内部工作方式。
接下来的章节涵盖了这些概念:
-
第十六章介绍如何通过引入自定义基元来扩展 Kubernetes API。您将学习如何定义、检查和创建 CustomResourceDefinition(CRD),作为实例化对象以实现 Kubernetes 本身未覆盖的定制需求的模式。
-
第十七章描述了每当像
kubectl这样的客户端调用 API 服务器时会启动的三个阶段。具体来说,我们将讨论认证阶段和授权阶段(包括使用 RBAC 控制权限)。本章还将简要介绍准入阶段。 -
第十八章涉及与应用程序开发相关的资源管理。您将了解到容器资源要求及其对 Pod 调度和运行时行为的影响。本章还将介绍如何通过资源配额来强制执行特定命名空间中对象的累积资源消耗。最后,我们将深入探讨如何针对特定资源类型通过限制范围来管理资源消耗。
-
第十九章展示了如何使用 ConfigMaps 和 Secrets 集中定义配置数据,以及从 Pod 中消费配置数据的不同方法。
-
第二十章讨论了安全上下文的概念,即为容器定义安全设置的一种方式。
第六部分:服务和网络
考试中的最后一个领域被命名为服务和网络。它涵盖了在集群中运行的微服务之间或外部消费者之间建立和限制通信的重要 Kubernetes 基元。更具体地说,这个领域涵盖了服务(Services)、入口(Ingresses)以及网络策略。
下列章节涵盖了这些概念:
-
第二十一章 介绍了服务资源类型。您将学习如何将集群内的微服务暴露给系统的其他部分。服务还允许将应用程序暴露给集群外的最终用户。这一章并不止于此,它还提供了调试配置错误的服务对象的技术。
-
第二十二章 从解释为什么服务通常不足以将应用程序暴露给外部消费者开始。入口基元可以将负载均衡的端点暴露给通过 HTTP(S)访问的消费者。
-
第二十三章 从安全角度解释了网络策略的必要性。默认情况下,Kubernetes 的 Pod 间通信是无限制的;然而,您希望实施最小特权原则,以确保只有那些符合您架构需求的 Pod 可以互相通信。限制 Pod 间的网络通信将减少潜在的攻击面。


浙公网安备 33010602011771号