Kubernetes-认证管理员-CKA-学习指南-全-

Kubernetes 认证管理员(CKA)学习指南(全)

原文:zh.annas-archive.org/md5/1c2bfd1175a06198ac9830e485118e2e

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

作为微服务的运行时和编排环境,Kubernetes 在初创企业和大型企业中广泛使用。随着组织应用数量的增加,管理 Kubernetes 集群成为一项全职工作。这是 Kubernetes 管理员的角色。负责这项工作的人确保每个集群处于运行状态,通过加入节点扩展集群,升级节点的 Kubernetes 版本以包含补丁和新功能,并负责关键集群数据的备份策略。为了帮助求职者和雇主有一种标准手段来展示和评估在 Kubernetes 环境中开发的熟练程度,云原生计算基金会(CNCF)开发了 认证的 Kubernetes 管理员(CKA) 计划。要获得这一认证,你需要通过一项考试。

在 CNCF 网页上可以找到另外两种 Kubernetes 认证。认证的 Kubernetes 应用开发者(CKAD) 专注于开发者中心化的 Kubernetes 应用。认证的 Kubernetes 安全专家(CKS) 是为验证在安全相关主题上的能力而创建的,需要在注册之前成功通过 CKA 考试。CKAD 和 CKS 的通过对于参加 CKA 考试并不是强制的。

在这本学习指南中,我将探讨覆盖 CKA 考试内容的主题,以充分准备您通过认证考试。我们将研究何时以及如何应用 Kubernetes 核心概念来管理应用程序。我们还将审视 Kubernetes 工程师的主要工具 kubectl 命令行工具。我还将提供一些提示,帮助您更好地准备考试,并分享我准备考试各个方面的个人经验。

CKA 与其他认证的典型多选题格式不同。它完全基于表现,并要求你在极大的时间压力下展示对手头任务的深刻理解。你准备好一次通过考试了吗?

本书适合对象

本书的主要目标群体是想为 CKA 考试做准备的管理员。内容“考试详情和资源”涵盖了考试课程的所有方面,但预期你具备 Kubernetes 架构及其概念的基本知识。如果你完全是 Kubernetes 新手,我建议先阅读 《Kubernetes 上手指南》(Brendan Burns、Joe Beda、Kelsey Hightower 和 Lachlan Evenson(O’Reilly))或者 Kubernetes 实战(Marko Lukša(Manning Publications))。

你将学到什么

本书内容总结了与 CKA 考试相关的最重要方面。像 AKS 或 GKE 这样的云提供商特定的 Kubernetes 实现不需要考虑在内。鉴于 Kubernetes 中提供的大量配置选项,几乎不可能覆盖所有用例和场景而不重复官方文档。鼓励考生参考Kubernetes 文档,作为广泛学习的权威手册。

本书的大纲严格遵循 CKA 课程。虽然一般来说,可能有更自然、更教学的学习 Kubernetes 的结构,但课程大纲将帮助考生通过专注于特定主题来准备考试。因此,根据您的现有知识水平,您将发现自己在书的其他章节之间进行交叉参考。

请注意,本书仅涵盖与 CKA 考试相关的概念。某些您可能期望在认证课程中涵盖的基元(例如 API 基元 Ingress)未涉及。如果您希望深入了解,请参考 Kubernetes 文档或其他书籍。

在通过 Kubernetes 进行实际体验对通过考试至关重要。每章都包含名为“示例练习”的部分,提供实践问题。这些问题的解决方案可在附录中找到。

本书使用的约定

本书使用以下排印约定:

斜体

表示新术语、URL 和电子邮件地址。

常量宽度

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

常量宽度粗体

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

常量宽度斜体

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

提示

此元素表示提示或建议。

注意

此元素表示一般说明。

警告

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

使用代码示例

本书中所有示例和练习的源代码都可以在GitHub上找到。该存储库使用 Apache License 2.0 进行分发,代码可在商业和开源项目中免费使用。如果您在源代码中遇到问题或有疑问,请在GitHub 问题跟踪器中提出问题。我很乐意与您交流并解决可能出现的任何问题。

本书旨在帮助您完成工作。一般情况下,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了大量代码,否则无需联系我们获取许可。例如,编写一个使用本书多个代码块的程序不需要许可。出售或分发 O’Reilly 图书中的示例代码需要许可。引用本书并引用示例代码来回答问题不需要许可。将本书的大量示例代码整合到您产品的文档中需要许可。我们感谢但通常不要求署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Certified Kubernetes Administrator (CKA) Study Guide by Benjamin Muschko (O’Reilly)。版权 2022 年 Some Benjamin Muschko,978-1-098-10722-2。”

如果您觉得您使用的代码示例超出了合理使用范围或上述许可,请随时通过permissions@oreilly.com与我们联系。

O’Reilly 互动 Katacoda 实验室

交互式 Katacoda 场景模仿真实的生产环境,让您在浏览器中学习时编写和运行代码。作者开发了一系列 Katacoda 场景,让您通过这本书中概述的工具和实践进行实践。有关我们的交互式内容的更多信息,请访问http://oreilly.com,查看本标题的电子书格式,还可以了解我们的学习平台的所有内容。

O’Reilly 在线学习

注意

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

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

如何联系我们

请向出版商发送有关本书的评论和问题:

  • O’Reilly Media, Inc.

  • 1005 Gravenstein Highway North

  • CA 95472 Sebastopol

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

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

  • 707-829-0104(传真)

我们为本书创建了一个网页,列出了勘误、示例和任何额外信息。您可以访问https://oreil.ly/cka-study-guide查看。

通过bookquestions@oreilly.com来评论或询问有关本书的技术问题。

有关我们的图书和课程的新闻和信息,请访问http://oreilly.com

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

关注我们的 Twitter:http://twitter.com/oreillymedia

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

关注作者的 Twitter:https://twitter.com/bmuschko

关注作者的 GitHub:https://github.com/bmuschko

关注作者的博客:https://bmuschko.com

致谢

每本书籍项目都是漫长的旅程,没有编辑团队和技术审阅者的帮助是不可能完成的。特别感谢 Jonathon Johnson、Kaslin Fields 和 Werner Dijkerman 提供的详细技术指导和反馈。我还要感谢 O’Reilly Media 的编辑 John Devins 和 Michele Cronin,他们的持续支持和鼓励。

第一章:考试详细信息和资源

本介绍章节解答了考生在准备 Certified Kubernetes Administrator (CKA) 考试时常问的最紧迫的问题。我们将讨论认证的目标受众、课程内容、考试环境以及技巧和额外的学习资源。如果您已熟悉认证项目,可以直接跳到任何涵盖技术概念的章节。

考试目标

Kubernetes 集群需要由熟练的专业人士安装、配置和维护。这就是 Kubernetes 管理员的工作。CKA 认证项目验证了对工作中遇到的典型管理任务的深入理解,特别是 Kubernetes 集群的维护、网络、存储解决方案以及故障排除应用程序和集群节点。

考试中使用的 Kubernetes 版本

在撰写本文时,考试基于 Kubernetes 1.23 版本。本书中的所有内容都将遵循该特定版本的特性、API 和命令行支持。未来版本可能会破坏向后兼容性。在准备认证时,请查阅 Kubernetes 发行说明,并使用考试中使用的 Kubernetes 版本进行实践,以避免不愉快的意外。

课程

以下概述列出了 CKA 的高级部分及其分数权重:

  • 25%: 集群架构、安装和配置

  • 15%: 工作负载和调度

  • 20%: 服务和网络

  • 10%: 存储

  • 30%: 故障排除

CKA 课程在 2020 年 9 月经历了重大的 改革。重新组织和优化考试领域的原因之一是新的 Certified Kubernetes Security Specialist (CKS) 认证。大部分与安全相关的主题已经转移到 CKS,而 CKA 则继续关注典型的管理活动和特性。

注意

本书的大纲完全按照 CKA 课程的要求进行编写。尽管学习 Kubernetes 通常会有更自然、更教学化的组织结构,但课程大纲将帮助考生通过专注于特定主题来准备考试。因此,根据您的现有知识水平,您可能会发现自己需要交叉参考本书的其他章节。

让我们在接下来的章节中详细分解每个领域。

集群架构、安装和配置

课程的这一部分涉及与 Kubernetes 集群相关的各种内容。这包括理解 Kubernetes 集群的基本架构,如控制平面与工作节点、高可用设置以及安装、升级和维护集群的工具。您需要演示从头开始安装集群、升级集群版本以及备份/恢复 etcd 数据库的过程。Cloud Native Computing Foundation(CNCF)还决定在本节中添加一个不太相关的主题:管理基于角色的访问控制(RBAC)。RBAC 是每个管理员都应该理解如何设置和应用的重要概念。

工作负载和调度

管理员需要深入了解用于操作云原生应用的 Kubernetes 概念。章节“工作负载和调度”满足了这一需求。您需要熟悉部署(Deployments)、副本集(ReplicaSets)以及由 ConfigMaps 和 Secrets 指定的配置数据。创建新的 Pod 时,Kubernetes 调度器将对象放置在可用节点上。节点亲和性和污点/容忍度等调度规则控制并优化行为。在考试中,您只需要理解 Pod 资源限制对调度的影响。此外,您需要熟悉命令式和声明式清单管理,以及像 Kustomize、yq和 Helm 等常见模板工具。

服务和网络

云原生微服务很少独立运行。在大多数情况下,它需要与其他微服务或外部系统进行交互。理解 Pod 间通信、将应用程序暴露到集群外部以及配置集群网络对管理员来说至关重要,以确保系统正常运行。在考试的这一部分,您需要展示对 Kubernetes 基元服务(Service)和入口(Ingress)的知识。

存储

本节涵盖了用于读写数据的不同类型卷。作为管理员,您需要知道如何创建和配置它们。持久卷确保即使在集群节点重新启动后数据也能永久保留。您需要了解其运作机制,并演示如何将持久卷挂载到容器中的路径上。确保您理解静态绑定和动态绑定之间的区别。

故障排除

当然,在生产 Kubernetes 集群中可能会出现问题。有时,应用程序可能会运行不正常,变得无响应,甚至无法访问。其他时候,集群节点可能会崩溃或遇到配置问题。开发有效的故障排除策略非常重要,以便尽快解决这些情况。考试的这一部分分数权重最高。您将面对典型场景,需要通过采取适当措施来解决问题。

涉及的 Kubernetes 基元

考试的主要目的是测试你对 Kubernetes 原语的实际知识。考试预计会将多个概念结合到一个问题中。参考图 1-1 作为适用的 Kubernetes 资源及其关系的大致指南。

ckas 0101

图 1-1. 与考试相关的 Kubernetes 原语

考试环境和提示

要参加 CKA 考试,你必须购买一张凭证作为注册。凭证可以在CNCF 培训和认证网页上获取。偶尔,CNCF 会在美国感恩节左右提供凭证折扣。这些折扣信息通常会在 Twitter 账号@LF_Training上公布。

拿着凭证,你可以安排考试时间。在预定考试的当天,你会收到一封电子邮件,里面有一个提供给你的 URL,要求你登录测试平台。你需要打开你的电脑音视频设备以防作弊。监考员将通过音视频监控你的行为,如果认为你没有遵守规则,将会终止考试。

考试尝试

你购买的凭证允许你有两次尝试通过 CKA 考试的机会。我建议你在第一次尝试考试之前做出合理的准备。这将给你通过考试的公平机会,同时也会给你一个对考试环境和问题复杂性的良好印象。如果第一次未能通过考试,不要担心,你还有第二次机会。

CKA 考试限时两小时。在这段时间内,你需要在一个真实的、预定义的 Kubernetes 集群上解决实际问题。每个问题都会说明你需要在哪个集群上工作。通过实际方法来评估候选人的技能集要比多项选择题的测试更为优越,因为你可以直接将知识转化为工作中执行的任务。

你可以打开额外的浏览器标签来浏览官方 Kubernetes 文档资料。这些页面包括https://oreil.ly/w0vibhttps://oreil.ly/XLYLj,和https://oreil.ly/1sr3B及其子域。你可以创建书签并在考试期间打开它们,只要它们属于上述 URL 范围内。

尽管随手拿到 Kubernetes 文档页面非常有价值,确保你知道哪里找到这些页面中的相关信息。在考试准备过程中,至少要彻底阅读一次所有文档页面。不要错过官方文档页面的搜索功能。

使用文档的高效方法

使用搜索术语可能会比浏览菜单项更快地找到正确的文档页面。从文档复制粘贴代码片段到考试环境的控制台中通常效果不错。有时,您可能需要手动调整 YAML 缩进,因为在这个过程中可能会丢失正确的格式。

我强烈建议阅读CKA 考试常见问题解答。您将在那里找到大多数紧迫问题的答案,包括计算机系统要求、评分、认证更新和重考要求。

候选人技能

该认证假定您已经具备 Kubernetes 的基本理解。您应熟悉 Kubernetes 的内部机制、核心概念以及命令行工具 kubectl。CNCF 为 Kubernetes 初学者提供免费的“介绍 Kubernetes”课程,以及更高级主题的培训课程。

CKA 考试假定您在管理员角色中工作,并且每天都面临典型的维护任务。除了命令行工具 kubectl,您还需要熟悉其他与操作 Kubernetes 集群相关的工具。以下内容概述了工具的使用情况。

Kubernetes 架构和概念

考试可能要求您从零开始安装 Kubernetes 集群。了解 Kubernetes 的基础知识及其架构组件是必要的。在考试中不要期望遇到任何多项选择题。

kubectl 命令行工具

考试中,您将主要使用 kubectl 命令行工具与 Kubernetes 集群进行交互。即使您只有很少的时间来准备考试,练习如何操作 kubectl 以及其命令及相关选项也是必不可少的。在考试期间将无法访问Web 仪表板 UI

Kubernetes 集群维护工具

从零开始安装 Kubernetes 集群和升级现有集群的 Kubernetes 版本,使用工具 kubeadm 完成。了解其使用方法和相关流程对于顺利进行操作非常重要。此外,还需深入了解工具 etcdctl,包括其用于备份和恢复 etcd 数据库的命令行选项。

其他相关工具

Kubernetes 对象由 YAML 或 JSON 表示。本书的内容只会使用 YAML 示例,因为在 Kubernetes 的世界中,它比 JSON 更常用。在考试中,您将需要编辑 YAML 以声明性地创建新对象或在修改现有对象的配置时。确保您对基本的 YAML 语法、数据类型和符合规范的缩进有很好的掌握。您可能会问如何编辑 YAML 定义?当然是从终端。考试终端环境预装了 vivim 工具。练习常见操作的键盘快捷键(特别是如何退出编辑器)。我想提到的最后一个工具是 GNU Bash。您必须理解脚本语言的基本语法和运算符。当与集群节点交互时,具有良好的 Linux 和 shell 命令行工作知识将非常有帮助。

时间管理

考生有两个小时的时间完成考试。至少需要有 66%的问题答案是正确的。许多问题由多个步骤组成。虽然 Linux Foundation 没有提供分数细分,但我认为部分正确的答案仍然可以得到部分分数。

在参加考试时,您会注意到规定的时间限制会给您带来很大压力。这是有意的。Linux Foundation 希望 Kubernetes 从业者能够通过及时找到解决问题的方法来将其知识应用于实际场景。

CKA 考试将向您呈现各种问题。有些问题简短且易解决;另一些则需要更多背景信息和时间。就个人而言,我尝试首先解决简单的问题,以尽可能多地获得分数,而不会陷入更难的问题中。我在考试环境中集成的记事本功能中记下了任何我无法立即解决的问题。在第二轮中,重新访问您跳过的问题,并尝试解决它们。在最理想的情况下,您将能够在规定的时间内解决所有问题。

命令行技巧与窍门

考虑到命令行将成为您与 Kubernetes 集群的唯一接口,因此您必须非常熟悉 kubectlkubeadmetcdctl 及其可用选项。本节涉及一些使它们的使用更高效和生产力更强的窍门和技巧。

设置上下文和命名空间

CKA 考试环境为您预先设置了六个 Kubernetes 集群。请查看说明,以了解这些集群的高级技术概述。每个考试题目都需要在指定的集群上解决,详细描述如下。此外,说明还会要求您在除了default之外的命名空间中工作。在处理问题之前,确保首先设置上下文和命名空间是必要的操作。以下命令一次性设置上下文和命名空间:

$ kubectl config set-context *<context-of-question>* \
  --namespace=*<namespace-of-question>*
$ kubectl config use-context *<context-of-question>*

使用 kubectl 的别名

在考试过程中,您将不得不执行kubectl命令数十甚至数百次。您可能是一个极快的键盘打字员,但是反复完整拼写可执行文件并无意义。设置kubectl命令的别名要高效得多。以下alias命令将字母k映射到完整的kubectl命令:

$ alias k=kubectl
$ k version

您可以重复相同的过程,为其他命令行工具如kubeadmetcdctl设置别名,以进一步减少输入量。考试环境已为kubectl命令设置了别名。

使用 kubectl 命令自动补全

熟记kubectl命令和命令行选项需要大量实践。在考试期间,您可以配置 bash 自动补全。具体指南在 Kubernetes 文档的“Linux 上的 bash 自动补全”部分中有详细说明。确保理解在设置自动补全所需时间与手动输入命令和选项之间的权衡。

内化资源简称

许多kubectl命令可能非常冗长。例如,管理持久卷索赔的命令是persistentvolumeclaims。必须完整拼写命令可能会出错且耗时。幸运的是,某些较长的命令具有简略形式的用法。命令api-resources列出所有可用命令及其简称:

$ kubectl api-resources
NAME                    SHORTNAMES  APIGROUP  NAMESPACED  KIND
...
persistentvolumeclaims  pvc                   true        PersistentVolumeClaim
...

在这里使用pvc而不是persistentvolumeclaims会导致更简洁和更具表现力的命令执行:

$ kubectl describe pvc my-claim

删除 Kubernetes 对象

某些情况需要您删除现有的 Kubernetes 对象。例如,在考试期间,您可能希望从头开始进行任务,因为您可能出现配置错误,或者您可能需要更改需要重新创建而不是修改现有对象的运行时配置。执行delete命令时,Kubernetes 尝试优雅地删除目标对象,以尽量减少对最终用户的影响。如果对象无法在默认的优雅期限(30 秒)内删除,则 kubelet 会尝试强制终止该对象。

在 CKA 考试期间,不必考虑最终用户的影响。最重要的目标是在候选人授予的时间内完成所有任务。因此,等待对象被优雅删除是浪费时间的。您可以使用 --force 选项立即强制删除对象。以下命令使用 SIGKILL 信号杀死名为 nginx 的 Pod:

$ kubectl delete pod nginx --force

查找对象信息

作为管理员,您经常会面临需要调查 Kubernetes 集群中失败情况的情况。该集群可能已经运行由一组不同对象类型组成的工作负载。CKA 考试将模拟故障场景,以测试您的故障排除技能。

列出特定类型的对象有助于识别问题的根本原因;但是,您需要确保搜索相关信息。您可以结合 describeget 命令与 Unix 命令 grep,通过搜索词过滤对象。grep 命令的 -C 命令行选项在搜索词之前和之后呈现上下文配置。

以下命令展示了它们的用法。第一个命令查找所有具有注解键值对 author=John Doe 的 Pod,并显示周围 10 行。第二个命令搜索所有 Pod 的 YAML 表示形式,以查找其标签,并显示输出的前后五行:

$ kubectl describe pods | grep -C 10 "author=John Doe"
$ kubectl get pods -o yaml | grep -C 5 labels:

发现命令选项

Kubernetes 文档内容广泛,涵盖了生态系统的最重要方面,包括 Kubernetes 资源的 API 参考。虽然搜索功能大大缩短了通过搜索词查找相关信息的时间,但您可能需要进一步浏览结果页面。

另一条路线是使用 kubectl 内置的帮助功能,使用命令行选项 --help。该选项呈现命令和子命令的详细信息,包括选项和示例。以下命令演示了 --help 选项用于 create 命令的用法:

$ kubectl create --help
Create a resource from a file or from stdin.

JSON and YAML formats are accepted.

Examples:
  ...

Available Commands:
  ...

Options:
  ...

此外,您可以使用 explain 命令探索每个 Kubernetes 资源的可用字段。作为参数,提供您想要呈现详细信息的对象的 JSONPath。以下示例列出了 Pod 的 spec 的所有字段:

$ kubectl explain pods.spec
KIND:     Pod
VERSION:  v1

RESOURCE: spec <Object>

DESCRIPTION:
  ...

FIELDS:
  ...

练习和练习考试

在通过考试时,动手实践是非常重要的。为此,您需要一个正常运行的 Kubernetes 集群环境。以下选项显著突出:

  • 确定您的雇主是否已设置了 Kubernetes 集群,并且是否允许您使用它进行练习。

  • 对于练习 Kubernetes 集群节点的安装或升级过程,我发现使用 VagrantVirtualBox 运行一个或多个虚拟机非常有用。这些工具帮助创建一个隔离的 Kubernetes 环境,可以根据需要快速启动和丢弃。本书中的一些练习就是从这种设置开始的。

  • 在你的开发者机器上安装 Kubernetes 是一个快速简便的设置方法。Kubernetes 文档根据你的操作系统提供了各种 安装选项。当涉及到像 Ingress 或存储类这样的更高级功能时,Minikube 尤其有用。

  • 如果你是 O’Reilly 学习平台 的订阅用户,你可以无限制地访问在 Katacoda 中运行 Kubernetes 环境的场景

你可能还想尝试以下付费学习和实践资源:

概要

CKA 考试验证你在安装、维护、升级和故障排除 Kubernetes 集群方面的实际操作知识。此外,你还需要理解在 Kubernetes 环境中典型用于运行、暴露和扩展云原生应用的 Kubernetes 资源类型。考试课程将这些主题分组,分配不同的权重。你将面对一项具有挑战性的实际操作测试,需要在真实的 Kubernetes 环境中解决实际问题。Linux Foundation 没有公布特定问题解决方案如何评分的信息。可以肯定的是,部分解决方案会被计入评分。

在本章中,我们讨论了你需要了解的关于考试的一切,以便开始准备。我们涉及了考试环境、时间管理的技巧和窍门、考生需要熟悉的工具,以及额外的学习和实践资源。

以下章节与考试大纲对齐,因此您可以将内容映射到学习目标。在每章末尾,您将找到样例练习,以提升您的知识水平。

第二章:集群架构、安装和配置

根据章节名称,课程的第一部分涉及您对 Kubernetes 管理员预期的典型任务。这些任务包括理解 Kubernetes 集群的架构组件、从头开始设置集群以及在之后维护集群。

有趣的是,本节还涵盖了集群的安全方面,特别是基于角色的访问控制(RBAC)。您需要了解如何将操作的权限映射到一组用户或进程的 API 资源。

在本章末尾,您将了解安装和维护 Kubernetes 集群的工具和流程。此外,您还将了解如何针对典型的实际用例配置 RBAC。

高级别来看,本章涵盖以下概念:

  • 理解 RBAC

  • 使用kubeadm安装集群

  • 使用kubeadm升级 Kubernetes 集群版本

  • 使用etcdctl备份和恢复 etcd

  • 理解高可用性的 Kubernetes 集群

基于角色的访问控制

在 Kubernetes 中,在允许对 API 资源进行请求之前,您需要经过身份验证。集群管理员通常可以访问所有资源和操作。操作集群的最简单方式是提供每个人一个管理员帐户。尽管“每个人都有管理员访问权限”听起来很棒,但随着业务的发展,这带来了相当大的风险。用户可能会意外删除一个 Secret Kubernetes 对象,这可能会破坏一个或多个应用程序,因此对最终用户造成巨大影响。可以想象,这种方法对运行关键应用程序的生产环境并不是一个好主意。

与其他生产系统一样,只有特定用户应该拥有完全访问权限,而大多数用户根据角色仅具有只读访问权限(并可能访问以改变系统)。例如,应用程序开发人员不需要管理集群节点。他们只需要管理运行和配置其应用程序所需的对象。

RBAC 通过允许或禁止访问管理 API 资源来定义用户、组和进程的策略。对于安全重视的任何组织而言,启用和配置 RBAC 都是必需的。考试中,您需要理解涉及的 RBAC API 资源类型以及如何在不同场景下创建和配置它们。

RBAC 高级概述

RBAC 有助于实现多种用例:

  • 建立一个允许具有不同角色的用户访问一组 Kubernetes 资源的系统

  • 通过 Kubernetes API 控制运行在 Pod 中的进程及其操作

  • 限制每个命名空间中某些资源的可见性

RBAC 由三个关键构建块组成,如图 2-1 所示。它们将 API 原语及其允许的操作与所谓的主体(用户、组或 ServiceAccount)连接起来。

以下列表按术语分解了职责:

主体

想要访问资源的用户或进程

资源

Kubernetes API 资源类型(例如 Deployment 或节点)

动词

可对资源执行的操作(例如创建 Pod 或删除 Service)

ckas 0201

图 2-1. RBAC 的关键构建块

创建主体

在 RBAC 的上下文中,您可以使用用户帐户、服务帐户或组作为主体。用户和组不存储在 etcd、Kubernetes 数据库中,而是用于运行在集群外的进程。服务帐户作为 Kubernetes 中的对象存在,并由运行在集群内的进程使用。本节将教您如何创建它们。

用户帐户和组

Kubernetes 不像 API 资源那样表示用户。用户由 Kubernetes 集群的管理员管理,然后将帐户的凭据分发给真实人员或供外部进程使用。

需要对使用用户的 API 请求进行身份验证。Kubernetes 为这些 API 请求提供了多种身份验证方法。表 2-1(#authentication_strategies_for_managing_rbac_subjects)显示了管理 RBAC 主体的不同身份验证方式。

表 2-1. 用于管理 RBAC 主体的认证策略

认证策略 描述
X.509 客户端证书 使用 OpenSSL 客户端证书进行身份验证
基本身份验证 使用用户名和密码进行身份验证
Bearer 令牌 使用 OpenID(OAuth2 的一种变体)或 Webhook 进行身份验证

为简化操作,以下步骤演示了使用 OpenSSL 客户端证书创建用户的过程。这些操作必须使用 cluster-admin 角色对象执行。在考试期间,您不需要自己创建用户。可以假设相关设置已为您执行。因此,您无需记忆以下步骤:

  1. 登录到 Kubernetes 控制平面节点,并创建一个临时目录,用于保存生成的密钥。进入该目录:

    $ mkdir cert && cd cert
    
  2. 使用 openssl 可执行文件创建私钥。提供一个具有表达性的文件名,例如 <username>.key

    $ openssl genrsa -out johndoe.key 2048
    Generating RSA private key, 2048 bit long modulus
    ..............................+
    ..+
    e is 65537 (0x10001)
    $ ls
    johndoe.key
    
  3. 在文件中创建证书签名请求(CSR),其扩展名为.csr。您需要提供上一步骤中的私钥。-subj 选项提供了用户名(CN)和组(O)。以下命令使用用户名 johndoe 和名为 cka-study-guide 的组。如果不想将用户分配到组中,请省略 /O 组件的赋值:

    $ openssl req -new -key johndoe.key -out johndoe.csr -subj \
      "/CN=johndoe/O=cka-study-guide"
    $ ls
    johndoe.csr johndoe.key
    
  4. 最后,使用 Kubernetes 集群证书颁发机构(CA)对 CSR 进行签名。CA 通常位于目录/etc/kubernetes/pki中,并且需要包含文件ca.crtca.key。我们在这里将使用 minikube,它将这些文件存储在目录 pass:[~/.minikube] 中。以下命令对 CSR 进行签名,并使其在 364 天内有效:

    $ openssl x509 -req -in johndoe.csr -CA [/.minikube/ca.crt -CAkey] \
      /.minikube/ca.key -CAcreateserial -out johndoe.crt -days 364
    Signature ok
    subject=/CN=johndoe/O=cka-study-guide
    Getting CA Private Key
    
    
  5. 通过在 kubeconfig 中为johndoe设置用户条目来在 Kubernetes 中创建用户。指向 CRT 和 key 文件。在 kubeconfig 中为johndoe设置上下文条目:

    $ kubectl config set-credentials johndoe \
      --client-certificate=johndoe.crt --client-key=johndoe.key
    User "johndoe" set.
    $ kubectl config set-context johndoe-context --cluster=minikube \
      --user=johndoe
    Context "johndoe-context" modified.
    
  6. 要切换到名为johndoe-context的上下文,请使用命令config current-context检查当前上下文:

    $ kubectl config use-context johndoe-context
    Switched to context "johndoe-context".
    $ kubectl config current-context
    johndoe-context
    

ServiceAccount

用户代表一个真实的人,通常使用kubectl可执行文件或 UI 仪表板与 Kubernetes 集群进行交互。某些服务应用程序(如 Helm 在 Pod 内运行时)需要通过 RESTful HTTP 调用向 API 服务器发出请求与 Kubernetes 集群进行交互。例如,Helm chart 将定义业务应用程序所需的多个 Kubernetes 对象。Kubernetes 使用 ServiceAccount 通过认证令牌验证 Helm 服务进程与 API 服务器的身份。此 ServiceAccount 可以分配给 Pod 并映射到 RBAC 规则。

Kubernetes 集群已经配备了一个 ServiceAccount,即default ServiceAccount,位于default命名空间中。任何未明确分配 ServiceAccount 的 Pod 都会使用default ServiceAccount。

要想命令式地创建自定义 ServiceAccount,请运行create serviceaccount命令:

$ kubectl create serviceaccount build-bot
serviceaccount/build-bot created

创建 ServiceAccount 的声明方式非常直接。只需提供适当的kind和名称,如示例 2-1 所示。

示例 2-1. 定义一个 ServiceAccount 的 YAML 清单
apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-bot

列出 ServiceAccounts

可以使用get serviceaccounts命令列出 ServiceAccounts。正如您在以下输出中所看到的,default命名空间列出了default ServiceAccount 和我们刚刚创建的自定义 ServiceAccount:

$ kubectl get serviceaccounts
NAME        SECRETS   AGE
build-bot   1         78s
default     1         93d

渲染 ServiceAccount 详细信息

在对象创建时,API 服务器会创建一个包含 API 令牌的 Secret,并将其分配给 ServiceAccount。Secret 和令牌名称使用 ServiceAccount 名称作为前缀。您可以使用describe serviceaccount命令查看 ServiceAccount 的详细信息,如下所示:

$ kubectl describe serviceaccount build-bot
Name:                build-bot
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   build-bot-token-rvjnz
Tokens:              build-bot-token-rvjnz
Events:              <none>

因此,您应该能够找到用于defaultbuild-bot ServiceAccount 的 Secret 对象:

$ kubectl get secrets
NAME                    TYPE                                  DATA   AGE
build-bot-token-rvjnz   kubernetes.io/service-account-token   3      20m
default-token-qgh5n     kubernetes.io/service-account-token   3      93d

将 ServiceAccount 分配给 Pod

要使 ServiceAccount 生效,它需要分配给运行预期进行 API 调用的应用程序的 Pod。在 Pod 创建时,您可以与run命令一起使用命令行选项--serviceaccount

$ kubectl run build-observer --image=alpine --restart=Never \
  --serviceaccount=build-bot
pod/build-observer created

或者,您可以直接在 Pod、Deployment、Job 或 CronJob 的 YAML 清单中分配 ServiceAccount,使用字段 serviceAccountName。示例 2-2 展示了将 ServiceAccount 定义给 Pod 的示例。

示例 2-2. 将 ServiceAccount 分配给 Pod 的 YAML 清单
apiVersion: v1
kind: Pod
metadata:
  name: build-observer
spec:
  serviceAccountName: build-bot
...

理解 RBAC API 基元

有了这些关键概念,让我们来看看实现 RBAC 功能的 Kubernetes API 基元:

Role

Role API 基元声明了此规则应操作的 API 资源及其操作。例如,您可能想说“允许列出和删除 Pods”,或者您可以表达“允许监视 Pods 的日志”,甚至两者同时用相同的 Role。一旦绑定到主体,任何未显式列出的操作将被禁止。

RoleBinding

RoleBinding API 基元 绑定 了 Role 对象到主体(s)。这是使规则生效的粘合剂。例如,您可能想说“将允许更新服务的 Role 绑定给用户 John Doe”。

图 2-2 展示了涉及的 API 基元之间的关系。请记住,该图像仅呈现了一组选定的 API 资源类型和操作。

ckas 0202

图 2-2. RBAC 基元

下面的章节展示了 Roles 和 RoleBindings 的命名空间范围的使用方法,但是相同的操作和属性也适用于集群范围的 Roles 和 RoleBindings,详见“命名空间范围和集群范围的 RBAC”。

默认面向用户的 Roles

Kubernetes 定义了一组默认的 Roles。您可以通过 RoleBinding 分配它们给主体,或根据需要定义自己的自定义 Roles。表 2-2 描述了默认的面向用户的 Roles。

表 2-2. 默认面向用户的 Roles

默认 ClusterRole 描述
cluster-admin 允许跨所有命名空间对资源进行读写访问。
admin 允许在命名空间中对资源进行读写访问,包括 Roles 和 RoleBindings。
edit 允许在命名空间中对资源进行读写访问,但不包括 Roles 和 RoleBindings。提供对 Secrets 的访问权限。
view 允许在命名空间中对资源进行只读访问,但不包括 Roles、RoleBindings 和 Secrets。

要定义新的 Roles 和 RoleBindings,您需要使用允许创建或修改它们的上下文,即 cluster-admin 或 admin。

创建 Roles

可以使用 create role 命令来命令式地创建 Roles。命令的最重要选项是 --verb 用于定义动词(即操作),以及 --resource 用于声明一组 API 资源。以下命令创建了一个新的 Role,允许对 Pod、Deployment 和 Service 资源进行 listgetwatch 操作:

$ kubectl create role read-only --verb=list,get,watch \
  --resource=pods,deployments,services
role.rbac.authorization.k8s.io/read-only created

对于单个命令行选项的多个动词和资源,可以声明为相应命令行选项的逗号分隔列表或作为多个参数。例如,--verb=list,get,watch--verb=list --verb=get --verb=watch 执行相同的指令。您还可以使用通配符“*”引用所有动词或资源。

命令行选项--resource-name会列出一个或多个策略规则应用的对象名称。Pod 的名称可以是nginx,在这里列出其名称。提供资源名称列表是可选的。如果没有提供名称,则提供的规则适用于该资源类型的所有对象。

声明式方法可能会变得有些冗长。如您在示例 2-3 中所见,rules部分列出了资源和动词。具有 API 组的资源,例如使用 API 版本apps/v1的 Deployments,需要在属性apiGroups下显式声明。所有其他资源(例如 Pods 和 Services)只需使用空字符串,因为它们的 API 版本不包含组。请注意,创建角色的命令会自动确定 API 组。

示例 2-3. 一个定义 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

列出 Roles

一旦 Role 创建完成,可以列出其对象。Roles 列表只呈现名称和创建时间戳。列出的每个角色都不透露任何详细信息:

$ kubectl get roles
NAME        CREATED AT
read-only   2021-06-23T19:46:48Z

渲染 Role 详细信息

您可以使用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。要将 Role 绑定到 RoleBinding,请使用--role命令行选项。可以通过声明选项--user--group--serviceaccount来分配主体类型。以下命令将 RoleBinding 命名为read-only-binding并绑定到名为johndoe的用户:

$ kubectl create rolebinding read-only-binding --role=read-only --user=johndoe
rolebinding.rbac.authorization.k8s.io/read-only-binding created

示例 2-4 展示了代表 RoleBinding 的 YAML 清单。您可以从结构中看到,一个角色可以映射到一个或多个主体。数据类型是由属性subjects下的破折号字符指示的数组。此时,只有用户johndoe已分配。

示例 2-4. 一个定义 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: johndoe

列出 RoleBindings

RoleBindings 列表中最重要的信息是关联的 Role。以下命令显示了 RoleBinding read-only-binding已映射到 Role read-only

$ kubectl get rolebindings
NAME                ROLE             AGE
read-only-binding   Role/read-only   24h

输出不提供主体的指示。您需要渲染对象的详细信息以获取更多信息,如下一节所述。

渲染 RoleBinding 详细信息

可以使用describe命令检查 RoleBindings。输出呈现主体和分配角色的表格。以下示例呈现了名为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  johndoe

查看正在生效的 RBAC 规则

让我们看看 Kubernetes 如何对我们迄今设置的场景执行 RBAC 规则。首先,我们将使用cluster-admin凭据创建一个新的 Deployment。在 Minikube 中,此用户分配给上下文minikube

$ kubectl config current-context
minikube
$ kubectl create deployment myapp --image=nginx --port=80 --replicas=2
deployment.apps/myapp created

现在,我们将为用户johndoe切换上下文:

$ kubectl config use-context johndoe-context
Switched to context "johndoe-context".

请记住,用户johndoe被允许列出 deployments。我们将使用get deployments命令验证:

$ kubectl get deployments
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
myapp   2/2     2            2           8s

RBAC 规则只允许列出 Deployments、Pods 和 Services。以下命令尝试列出 ReplicaSets,结果出现错误:

$ kubectl get replicasets
Error from server (Forbidden): replicasets.apps is forbidden: User "johndoe" \
cannot list resource "replicasets" in API group "apps" in the namespace "default"

尝试使用除listgetwatch以外的其他动词时,会观察到类似的行为。以下命令尝试删除一个 Deployment:

$ kubectl delete deployment myapp
Error from server (Forbidden): deployments.apps "myapp" is forbidden: User \
"johndoe" cannot delete resource "deployments" in API group "apps" in the \
namespace "default"

在任何时候,您可以使用auth can-i命令检查用户的权限。该命令为您提供列出所有权限或检查特定权限的选项:

$ kubectl auth can-i --list --as johndoe
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 johndoe
yes

命名空间范围和集群范围的 RBAC

角色和 RoleBindings 适用于特定命名空间。在创建这两个对象时,您必须指定命名空间。有时,一组角色和 Rolebindings 需要应用于多个命名空间甚至整个集群。对于集群范围的定义,Kubernetes 提供了 API 资源类型 ClusterRole 和 ClusterRoleBinding。配置元素实际上是相同的。唯一的区别是kind属性的值:

  • 要定义集群范围的 Role,请使用命令clusterrole或在 YAML 清单中使用ClusterRole种类。

  • 要定义集群范围的 RoleBinding,请使用命令clusterrolebinding或在 YAML 清单中使用ClusterRoleBinding种类。

聚合 RBAC 规则

可以聚合现有的 ClusterRoles 以避免不必要地重新定义新的组合规则集,这可能导致指令的重复。例如,假设您想将一个面向用户的角色与自定义角色合并。聚合的 ClusterRole 可以通过标签选择合并规则,而无需将现有规则复制粘贴到一个文件中。

假设我们定义了两个示例中显示的集群角色,2-5 和 2-6。集群角色list-pods允许列出 Pods,而集群角色delete-services允许删除 Services。

示例 2-5. 定义用于列出 Pods 的 ClusterRole 的 YAML 清单
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: list-pods
  namespace: rbac-example
  labels:
    rbac-pod-list: "true"
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - list
示例 2-6. 定义用于删除 Services 的 ClusterRole 的 YAML 清单
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: delete-services
  namespace: rbac-example
  labels:
    rbac-service-delete: "true"
rules:
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - delete

要聚合这些规则,ClusterRole 可以指定一个 aggregationRule。这个属性描述了标签选择规则。示例 2-7 展示了一个通过 matchLabels 条件数组定义的聚合 ClusterRole。ClusterRole 不会添加自己的规则,如 rules: [] 所示;但是,没有限制因素会阻止它。

示例 2-7. 使用聚合规则定义 ClusterRole 的 YAML 清单
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pods-services-aggregation-rules
  namespace: rbac-example
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac-pod-list: "true"
  - matchLabels:
      rbac-service-delete: "true"
rules: []

通过描述对象可以验证 ClusterRole 的正确聚合行为。您可以在以下输出中看到,list-podsdelete-services 这两个 ClusterRole 都已经被考虑进去了:

$ kubectl describe clusterroles pods-services-aggregation-rules -n rbac-example
Name:         pods-services-aggregation-rules
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  services   []                 []              [delete]
  pods       []                 []              [list]

有关 ClusterRole 标签选择规则的更多信息,请参阅 官方文档。页面还解释了如何聚合默认用户面向的 ClusterRole。

创建和管理 Kubernetes 集群

当考虑 Kubernetes 管理员的典型任务时,我确信以下至少一种基本活动会浮现在脑海中:

  • 引导控制平面节点

  • 引导工作节点并将它们加入集群

  • 升级集群到较新版本

执行集群引导操作的低级命令行工具称为 kubeadm。它不适用于提供基础设施。这是像 Ansible 和 Terraform 这样的基础设施自动化工具的用途。要安装 kubeadm,请按照官方 Kubernetes 文档中的 安装说明 进行操作。

虽然在 CKA 常见问题(FAQ)页面中没有明确说明,但可以假定 kubeadm 可执行文件已经预先安装好了。接下来的章节将高层次地描述创建和管理 Kubernetes 集群的流程,并且将大量使用 kubeadm。有关每个任务的详细信息,请参阅逐步 Kubernetes 参考文档。

安装集群

Kubernetes 集群的最基本拓扑结构包括一个既作为控制平面又作为工作节点的单节点。默认情况下,许多面向开发者的 Kubernetes 安装,如 minikube 或 Docker Desktop,从这种配置开始。虽然单节点集群可能是 Kubernetes 实验场的一个不错选择,但出于可扩展性和高可用性的考虑,它并不是一个良好的基础。至少,您希望创建一个带有单个控制平面和一个或多个节点处理工作负载的集群。

本节说明了如何使用单个控制平面和一个工作节点安装集群。您可以重复工作节点安装过程,以向集群添加更多工作节点。您可以在官方 Kubernetes 文档的 安装步骤 中找到完整描述。图 2-3 说明了安装过程。

ckas 0203

图 2-3. 集群安装流程图

初始化控制平面节点后

首先在控制平面节点上初始化控制平面。控制平面是负责托管 API 服务器、etcd 和其他重要组件以管理 Kubernetes 集群的机器。

使用 ssh 命令打开交互式 shell 到名为 kube-control-plane 的运行 Ubuntu 18.04.5 LTS 的控制平面节点:

$ ssh kube-control-plane
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-132-generic x86_64)
...

使用 kubeadm init 命令初始化控制平面。您需要添加以下两个命令行选项:使用 --pod-network-cidr 选项为 Pod 网络提供 IP 地址范围。使用 --apiserver-advertise-address 选项可以声明 API 服务器将要监听的 IP 地址。

控制台输出显示了一个 kubeadm join 命令。请将该命令保留以备后用。这是后续步骤中将工作节点加入集群的重要步骤。

获取工作节点的加入命令

如果您丢失了 join 命令,可以在控制平面节点上运行 kubeadm token create --print-join-command 来获取它。

以下命令使用 172.18.0.0/16 作为无类域间路由(CIDR)的地址范围,API 服务器使用 IP 地址 10.8.8.10

$ sudo kubeadm init --pod-network-cidr 172.18.0.0/16 \
  --apiserver-advertise-address 10.8.8.10
...
To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on \
each as root:

kubeadm join 10.8.8.10:6443 --token fi8io0.dtkzsy9kws56dmsp \
    --discovery-token-ca-cert-hash \
    sha256:cc89ea1f82d5ec460e21b69476e0c052d691d0c52cce83fbd7e403559c1ebdac

init 命令完成后,请从控制台输出中运行必要的命令以非 root 用户身份启动集群:

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

您必须部署容器网络接口(CNI)插件,以便 Pod 之间可以通信。您可以从 Kubernetes 文档 中列出的多种网络插件中进行选择。流行的插件包括 Flannel、Calico 和 Weave Net。有时文档中会出现与插件同义的术语“add-ons”。

CKA 考试很可能要求您安装特定的 add-on。大多数安装说明存储在外部网页上,在考试期间不允许使用。请确保在官方 Kubernetes 文档中搜索相关指南。例如,您可以在这里找到 Weave Net 的安装说明。以下命令安装 Weave Net 对象:

$ kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version= \
  $(kubectl version | base64 | tr -d '\n')"
serviceaccount/weave-net created
clusterrole.rbac.authorization.k8s.io/weave-net created
clusterrolebinding.rbac.authorization.k8s.io/weave-net created
role.rbac.authorization.k8s.io/weave-net created
rolebinding.rbac.authorization.k8s.io/weave-net created
daemonset.apps/weave-net created

使用命令 kubectl get nodes 验证控制平面节点显示为“Ready”状态。节点从“NotReady”状态转换到“Ready”状态可能需要几秒钟时间。如果状态未发生转换,则表示节点安装存在问题。参考第七章进行故障排除策略:

$ kubectl get nodes
NAME                 STATUS   ROLES                  AGE   VERSION
kube-control-plane   Ready    control-plane,master   24m   v1.21.2

使用 exit 命令退出控制平面节点:

$ exit
logout
...

加入工作节点

工作节点负责处理由控制平面调度的工作负载。工作负载的示例包括 Pods、Deployments、Jobs 和 CronJobs。要将工作节点添加到集群中以便使用,您将需要运行一些命令,如下所述。

使用 ssh 命令打开到运行 Ubuntu 18.04.5 LTS 的名为 kube-worker-1 的工作节点的交互式 shell:

$ ssh kube-worker-1
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-132-generic x86_64)
...

在控制平面节点的 kubeadm init 控制台输出提供的 kubeadm join 命令。以下命令显示了一个示例。请记住,对于您来说,令牌和 SHA256 哈希将不同:

$ sudo kubeadm join 10.8.8.10:6443 --token fi8io0.dtkzsy9kws56dmsp \
  --discovery-token-ca-cert-hash \
  sha256:cc89ea1f82d5ec460e21b69476e0c052d691d0c52cce83fbd7e403559c1ebdac
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with \
'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file \
"/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with \
flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control plane to see this node join the cluster.

如果不从控制平面节点复制管理员 kubeconfig 文件,您将无法从工作节点运行 kubectl get nodes 命令。请按照 Kubernetes 文档中的 说明 或重新登录到控制平面节点。在这里,我们只是要重新登录到控制平面节点。您应该看到工作节点已加入集群并处于“Ready”状态:

$ ssh kube-control-plane
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-132-generic x86_64)
...
$ kubectl get nodes
NAME                 STATUS   ROLES                  AGE     VERSION
kube-control-plane   Ready    control-plane,master   5h49m   v1.21.2
kube-worker-1        Ready    <none>                 15m     v1.21.2

您可以为要添加到集群的任何其他工作节点重复此过程。

管理高可用性集群

单控制平面集群易于安装;然而,当节点丢失时会出现问题。一旦控制平面节点不可用,运行在工作节点上的任何 ReplicaSet 由于无法与运行在控制平面节点上的调度器通信,就无法重新创建 Pod。此外,集群不再可以从外部访问(例如,通过 kubectl),因为无法连接到 API 服务器。

高可用性(HA)集群有助于扩展性和冗余性。在考试中,您需要基本了解如何配置它们及其影响。考虑到建立 HA 集群的复杂性,不太可能在考试中要求您执行这些步骤。要了解如何设置 HA 集群,请参阅 Kubernetes 文档中的 相关页面

堆叠的 etcd 拓扑 涉及创建两个或多个 etcd 与节点共存的控制平面节点。图 2-4 显示了具有三个控制平面节点的拓扑表示。

ckas 0204

图 2-4. 具有三个控制平面节点的堆叠 etcd 拓扑

每个控制平面节点托管 API 服务器、调度器和控制器管理器。工作节点通过负载均衡器与 API 服务器通信。建议出于冗余原因操作此集群拓扑,至少需要三个控制平面节点,因为 etcd 与控制平面节点的紧密耦合。默认情况下,kubeadm 在将控制平面节点加入集群时会创建一个 etcd 实例。

外部 etcd 节点拓扑结构通过在专用机器上运行 etcd 将其与控制平面节点分离。图 2-5 展示了一个设置,其中三个控制平面节点在不同的机器上运行 etcd。

ckas 0205

图 2-5. 外部 etcd 节点拓扑结构

类似于堆叠的 etcd 拓扑结构,每个控制平面节点托管 API 服务器、调度器和控制器管理器。工作节点通过负载均衡器与它们通信。这里的主要区别是,etcd 实例在单独的主机上运行。这种拓扑将 etcd 与其他控制平面功能解耦,因此在控制平面节点丢失时对冗余性影响较小。正如图示所示,这种拓扑结构需要的主机数是堆叠 etcd 拓扑结构的两倍。

升级集群版本

随着时间的推移,您会希望将现有集群的 Kubernetes 版本升级,以获取 bug 修复和新功能。必须以受控的方式执行升级过程,以避免当前执行的工作负载中断,并防止集群节点的损坏。

建议从次要版本升级到下一个更高版本(例如,从 1.18.0 到 1.19.0),或从补丁版本升级到更高版本(例如,从 1.18.0 到 1.18.3)。避免跨越多个次要版本跳跃,以避免意外副作用。您可以在官方 Kubernetes 文档中找到关于升级步骤的完整描述。图 2-6 展示了升级过程。

ckas 0206

图 2-6. 集群版本升级过程

升级控制平面节点

如前所述,Kubernetes 集群可能使用一个或多个控制平面节点来更好地支持高可用性和可扩展性问题。在升级集群版本时,需要逐个处理控制平面节点。

选择一个包含 kubeconfig 文件(位于 /etc/kubernetes/admin.conf)的控制平面节点,并使用 ssh 命令打开控制平面节点的交互式 shell。以下命令针对名为 kube-control-plane 的控制平面节点,运行 Ubuntu 18.04.5 LTS:

$ ssh kube-control-plane
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-132-generic x86_64)
...

首先,检查节点及其 Kubernetes 版本。在此设置中,所有节点都运行在版本 1.18.0 上。我们只处理一个控制平面节点和一个工作节点:

$ kubectl get nodes
NAME                 STATUS   ROLES    AGE     VERSION
kube-control-plane   Ready    master   4m54s   v1.18.0
kube-worker-1        Ready    <none>   3m18s   v1.18.0

开始升级 kubeadm 版本。确定您希望升级到的版本。在 Ubuntu 机器上,您可以使用以下 apt-get 命令。版本格式通常包括一个补丁版本(例如 1.20.7-00)。如果您的机器运行不同的操作系统,请查阅 Kubernetes 文档:

$ sudo apt update
...
$ sudo apt-cache madison kubeadm
   kubeadm |  1.21.2-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.21.1-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.21.0-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.8-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.7-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.6-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.5-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.4-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.2-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.1-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
   kubeadm |  1.20.0-00 | http://apt.kubernetes.io kubernetes-xenial/main \
   amd64 Packages
...

kubeadm升级到目标版本。比如你想升级到版本1.19.0-00。以下一系列命令安装具有该特定版本的kubeadm并检查当前安装的版本以进行验证:

$ sudo apt-mark unhold kubeadm && sudo apt-get update && sudo apt-get install \
  -y kubeadm=1.19.0-00 && sudo apt-mark hold kubeadm
Canceled hold on kubeadm.
...
Unpacking kubeadm (1.19.0-00) over (1.18.0-00) ...
Setting up kubeadm (1.19.0-00) ...
kubeadm set on hold.
$ sudo apt-get update && sudo apt-get install -y --allow-change-held-packages \
  kubeadm=1.19.0-00
...
kubeadm is already the newest version (1.19.0-00).
0 upgraded, 0 newly installed, 0 to remove and 7 not upgraded.
$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.0", \
GitCommit:"e19964183377d0ec2052d1f1fa930c4d7575bd50", GitTreeState:"clean", \
BuildDate:"2020-08-26T14:28:32Z", GoVersion:"go1.15", Compiler:"gc", \
Platform:"linux/amd64"}

检查可升级到哪些版本,并验证当前集群是否可升级。你可以在以下命令的输出中看到我们可以升级到版本1.19.12。现在,我们将继续使用1.19.0

$ sudo kubeadm upgrade plan
...
[upgrade] Fetching available versions to upgrade to
[upgrade/versions] Cluster version: v1.18.20
[upgrade/versions] kubeadm version: v1.19.0
I0708 17:32:53.037895   17430 version.go:252] remote version is much newer: \
v1.21.2; falling back to: stable-1.19
[upgrade/versions] Latest stable version: v1.19.12
[upgrade/versions] Latest version in the v1.18 series: v1.18.20
...
You can now apply the upgrade by executing the following command:

	kubeadm upgrade apply v1.19.12

Note: Before you can perform this upgrade, you have to update kubeadm to v1.19.12.
...

正如控制台输出中所述,我们将开始控制平面的升级。此过程可能需要几分钟。您可能还需要升级 CNI 插件。请按照提供者的说明获取更多信息:

$ sudo kubeadm upgrade apply v1.19.0
...
[upgrade/version] You have chosen to change the cluster version to "v1.19.0"
[upgrade/versions] Cluster version: v1.18.20
[upgrade/versions] kubeadm version: v1.19.0
...
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.19.0". Enjoy!

[upgrade/kubelet] Now that your control plane is upgraded, please proceed \
with upgrading your kubelets if you haven't already done so.

通过驱逐工作负载来排空控制平面节点。在取消针对该节点的调度之前,任何新的工作负载都无法被调度:

$ kubectl drain kube-control-plane --ignore-daemonsets
node/kube-control-plane cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-qndb9, \
kube-system/kube-proxy-vpvms
evicting pod kube-system/calico-kube-controllers-65f8bc95db-krp72
evicting pod kube-system/coredns-f9fd979d6-2brkq
pod/calico-kube-controllers-65f8bc95db-krp72 evicted
pod/coredns-f9fd979d6-2brkq evicted
node/kube-control-plane evicted

将 kubelet 和kubectl工具升级到相同版本:

$ sudo apt-mark unhold kubelet kubectl && sudo apt-get update && sudo \
  apt-get install -y kubelet=1.19.0-00 kubectl=1.19.0-00 && sudo apt-mark \
  hold kubelet kubectl
...
Setting up kubelet (1.19.0-00) ...
Setting up kubectl (1.19.0-00) ...
kubelet set on hold.
kubectl set on hold.

重新启动 kubelet 进程:

$ sudo systemctl daemon-reload
$ sudo systemctl restart kubelet

重新启用控制平面节点,以便新的工作负载可以被调度:

$ kubectl uncordon kube-control-plane
node/kube-control-plane uncordoned

控制平面节点现在应显示 Kubernetes 1.19.0 的使用情况:

$ kubectl get nodes
NAME                 STATUS   ROLES    AGE   VERSION
kube-control-plane   Ready    master   21h   v1.19.0
kube-worker-1        Ready    <none>   21h   v1.18.0

使用exit命令退出控制平面节点:

$ exit
logout
...

升级工作节点

选择一个工作节点,并使用ssh命令打开到该节点的交互式 shell。以下命令针对名为kube-worker-1的运行 Ubuntu 18.04.5 LTS 的工作节点:

$ ssh kube-worker-1
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-132-generic x86_64)
...

kubeadm升级到目标版本。这与前面用于控制平面节点的相同命令一样:

$ sudo apt-mark unhold kubeadm && sudo apt-get update && sudo apt-get install \
  -y kubeadm=1.19.0-00 && sudo apt-mark hold kubeadm
Canceled hold on kubeadm.
...
Unpacking kubeadm (1.19.0-00) over (1.18.0-00) ...
Setting up kubeadm (1.19.0-00) ...
kubeadm set on hold.
$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.0", \
GitCommit:"e19964183377d0ec2052d1f1fa930c4d7575bd50", GitTreeState:"clean", \
BuildDate:"2020-08-26T14:28:32Z", GoVersion:"go1.15", Compiler:"gc", \
Platform:"linux/amd64"}

升级 kubelet 配置:

$ sudo kubeadm upgrade node
[upgrade] Reading configuration from the cluster...
[upgrade] FYI: You can look at this config file with 'kubectl -n kube-system \
get cm kubeadm-config -o yaml'
[preflight] Running pre-flight checks
[preflight] Skipping prepull. Not a control plane node.
[upgrade] Skipping phase. Not a control plane node.
[kubelet-start] Writing kubelet configuration to file \
"/var/lib/kubelet/config.yaml"
[upgrade] The configuration for this node was successfully updated!
[upgrade] Now you should go ahead and upgrade the kubelet package using your \
package manager.

通过驱逐工作负载来排空工作节点。在取消针对该节点的调度之前,任何新的工作负载都无法被调度:

$ kubectl drain kube-worker-1 --ignore-daemonsets
node/kube-worker-1 cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-2hrxg, \
kube-system/kube-proxy-qf6nl
evicting pod kube-system/calico-kube-controllers-65f8bc95db-kggbr
evicting pod kube-system/coredns-f9fd979d6-7zm4q
evicting pod kube-system/coredns-f9fd979d6-tlmhq
pod/calico-kube-controllers-65f8bc95db-kggbr evicted
pod/coredns-f9fd979d6-7zm4q evicted
pod/coredns-f9fd979d6-tlmhq evicted
node/kube-worker-1 evicted

使用与控制平面节点相同的命令升级 kubelet 和kubectl工具的版本:

$ sudo apt-mark unhold kubelet kubectl && sudo apt-get update && sudo apt-get \
install -y kubelet=1.19.0-00 kubectl=1.19.0-00 && sudo apt-mark hold kubelet \
kubectl
...
Setting up kubelet (1.19.0-00) ...
Setting up kubectl (1.19.0-00) ...
kubelet set on hold.
kubectl set on hold.

重新启动 kubelet 进程:

$ sudo systemctl daemon-reload
$ sudo systemctl restart kubelet

重新启用工作节点,以便新的工作负载可以被调度:

$ kubectl uncordon kube-worker-1
node/kube-worker-1 uncordoned

现在列出的节点应显示工作节点版本 1.19.0。你无法在工作节点上运行kubectl get nodes,除非从控制平面节点复制管理员 kubeconfig 文件。请按照 Kubernetes 文档中的说明进行操作,或重新登录到控制平面节点:

$ kubectl get nodes
NAME                 STATUS   ROLES    AGE   VERSION
kube-control-plane   Ready    master   24h   v1.19.0
kube-worker-1        Ready    <none>   24h   v1.19.0

使用exit命令退出工作节点:

$ exit
logout
...

备份和恢复 etcd

Kubernetes 将集群的声明和观察状态存储在分布式 etcd 键值存储中。重要的是要制定一个备份计划,以帮助在数据损坏时恢复数据。定期进行数据备份是很重要的,以尽可能减少历史数据的丢失。

备份过程将 etcd 数据存储在所谓的快照文件中。可以随时使用此快照文件恢复 etcd 数据。可以加密快照文件以保护敏感信息。工具etcdctl在备份和恢复过程中是至关重要的。

作为管理员,你需要了解如何使用该工具执行这两个操作。如果控制平面节点上尚未安装etcdctl,则可能需要安装它。你可以在 etcd GitHub 存储库中找到安装说明。图 2-7 展示了 etcd 备份和恢复过程。

ckas 0207

图 2-7. etcd 备份和恢复过程

根据你的集群拓扑结构,你的集群可能包含一个或多个 etcd 实例。参考“高可用集群设置”部分,了解如何设置它。以下部分解释了单节点 etcd 集群设置。你可以在官方 Kubernetes 文档中找到有关多节点 etcd 集群备份和恢复过程的附加说明

etcd 的备份

使用ssh命令打开托管 etcd 的机器上的交互式 shell。以下命令针对名为kube-control-plane的控制平面节点,运行的是 Ubuntu 18.04.5 LTS:

$ ssh kube-control-plane
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-132-generic x86_64)
...

检查etcdctl的安装版本,以验证该工具是否已安装。在此节点上,版本为 3.4.14:

$ etcdctl version
etcdctl version: 3.4.14
API version: 3.4

Etcd 作为kube-system命名空间中的一个 Pod 部署。通过描述 Pod 来检查其版本。在下面的输出中,你会发现版本是 3.4.13-0:

$ kubectl get pods -n kube-system
NAME                                       READY   STATUS    RESTARTS   AGE
...
etcd-kube-control-plane                    1/1     Running   0          33m
...
$ kubectl describe pod etcd-kube-control-plane -n kube-system
...
Containers:
  etcd:
    Container ID:  docker://28325c63233edaa94e16691e8082e8d86f5e7da58c0fb54 \
    d95d68dec6e80cf54
    Image:         k8s.gcr.io/etcd:3.4.3-0
    Image ID:      docker-pullable://k8s.gcr.io/etcd@sha256:4afb99b4690b418 \
    ffc2ceb67e1a17376457e441c1f09ab55447f0aaf992fa646
...

相同的describe命令显示了 etcd 服务的配置。查找选项--listen-client-urls的值,以获取端点 URL。在下面的输出中,主机是localhost,端口是2379。服务器证书位于/etc/kubernetes/pki/etcd/server.crt,由选项--cert-file定义。CA 证书位于/etc/kubernetes/pki/etcd/ca.crt,由选项--trusted-ca-file指定:

$ kubectl describe pod etcd-kube-control-plane -n kube-system
...
Containers:
  etcd:
    ...
    Command:
      etcd
      ...
      --cert-file=/etc/kubernetes/pki/etcd/server.crt
      --key-file=/etc/kubernetes/pki/etcd/server.key
      --listen-client-urls=/etc/kubernetes/pki/etcd/server.key
      --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
...

使用etcdctl命令创建版本为 3 的工具备份。作为一个良好的起点,从官方 Kubernetes 文档复制命令。提供必需的命令行选项--cacert--cert--key。因为我们在与 etcd 相同的服务器上运行命令,所以不需要--endpoints选项。运行命令后,文件/tmp/etcd-backup.db已创建:

$ sudo ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save /opt/etcd-backup.db
{"level":"info","ts":1625860312.3468597, \
"caller":"snapshot/v3_snapshot.go:119", \
"msg":"created temporary db file","path":"/opt/etcd-backup.db.part"}
{"level":"info","ts":"2021-07-09T19:51:52.356Z", \
"caller":"clientv3/maintenance.go:200", \
"msg":"opened snapshot stream; downloading"}
{"level":"info","ts":1625860312.358686, \
"caller":"snapshot/v3_snapshot.go:127", \
"msg":"fetching snapshot","endpoint":"127.0.0.1:2379"}
{"level":"info","ts":"2021-07-09T19:51:52.389Z", \
"caller":"clientv3/maintenance.go:208", \
"msg":"completed snapshot read; closing"}
{"level":"info","ts":1625860312.392891, \
"caller":"snapshot/v3_snapshot.go:142", \
"msg":"fetched snapshot","endpoint":"127.0.0.1:2379", \
"size":"2.3 MB","took":0.045987318}
{"level":"info","ts":1625860312.3930364, \
"caller":"snapshot/v3_snapshot.go:152", \
"msg":"saved","path":"/opt/etcd-backup.db"}
Snapshot saved at /opt/etcd-backup.db

使用exit命令退出节点:

$ exit
logout
...

恢复 etcd

你创建了 etcd 的备份并将其存储在安全的位置。此时无需执行其他操作。实际上,这是你的保险政策,当遭遇灾难时会变得重要。在灾难场景下,etcd 中的数据被损坏或管理 etcd 的机器遇到物理存储故障时,你就需要提取 etcd 备份进行恢复。

要从备份中恢复 etcd,请使用 etcdctl snapshot restore 命令。至少提供 --data-dir 命令行选项。在这里,我们使用数据目录 /tmp/from-backup。运行命令后,您应该能够在目录 /var/lib/from-backup 中找到恢复的备份:

$ sudo ETCDCTL_API=3 etcdctl --data-dir=/var/lib/from-backup snapshot restore \
  /opt/etcd-backup.db
{"level":"info","ts":1625861500.5752304, \
"caller":"snapshot/v3_snapshot.go:296", \
"msg":"restoring snapshot","path":"/opt/etcd-backup.db", \
"wal-dir":"/var/lib/from-backup/member/wal", \
"data-dir":"/var/lib/from-backup", \
"snap-dir":"/var/lib/from-backup/member/snap"}
{"level":"info","ts":1625861500.6146874, \
"caller":"membership/cluster.go:392", \
"msg":"added member","cluster-id":"cdf818194e3a8c32", \
"local-member-id":"0", \
"added-peer-id":"8e9e05c52164694d", \
"added-peer-peer-urls":["http://localhost:2380"]}
{"level":"info","ts":1625861500.6350253, \
"caller":"snapshot/v3_snapshot.go:309", \
"msg":"restored snapshot","path":"/opt/etcd-backup.db", \
"wal-dir":"/var/lib/from-backup/member/wal", \
"data-dir":"/var/lib/from-backup", \
"snap-dir":"/var/lib/from-backup/member/snap"}
$ sudo ls /var/lib/from-backup
member

编辑 etcd Pod 的 YAML 清单,该清单位于 /etc/kubernetes/manifests/etcd.yaml。将属性 spec.volumes.hostPath 的值从原始值 /var/lib/etcd 更改为 /var/lib/from-backup

$ cd /etc/kubernetes/manifests/
$ sudo vim etcd.yaml
...
spec:
  volumes:
  ...
  - hostPath:
      path: /var/lib/from-backup
      type: DirectoryOrCreate
    name: etcd-data
...

etcd-kube-control-plane Pod 将重新创建,并指向恢复的备份目录:

$ kubectl get pod etcd-kube-control-plane -n kube-system
NAME                      READY   STATUS    RESTARTS   AGE
etcd-kube-control-plane   1/1     Running   0          5m1s

如果 Pod 不能转换为“运行”状态,请尝试使用命令 kubectl delete pod etcd-kube-control-plane -n kube-system 手动删除它。

使用 exit 命令退出节点:

$ exit
logout
...

摘要

生产就绪的 Kubernetes 集群应该使用安全策略来控制哪些用户和进程可以管理对象。基于角色的访问控制(RBAC)定义了这些规则。RBAC 引入了特定的 API 资源,将主体映射到特定对象允许的操作。规则可以在命名空间或集群级别使用 API 资源类型 Role、ClusterRole、RoleBinding 和 ClusterRoleBinding 来定义。为了避免规则重复,可以使用标签选择来聚合 ClusterRoles。

作为 Kubernetes 管理员,您需要熟悉涉及管理集群节点的典型任务。安装新节点和升级节点版本的主要工具是 kubeadm。此类集群的集群拓扑可以有所不同。为了实现冗余和可扩展性的最佳结果,请考虑配置具有三个或更多控制平面节点和专用 etcd 主机的高可用设置。

定期备份 etcd 数据库以防止节点或存储损坏时关键数据的丢失是一个必要的过程。您可以使用工具 etcdctl 从控制平面节点或通过 API 端点备份和还原 etcd。

考试要点

知道如何定义 RBAC 规则。

定义 RBAC 规则涉及几个关键部分:由用户、组和 ServiceAccounts 定义的主体;命名空间和集群级别上的 RBAC 特定 API 资源;最后是允许在 Kubernetes 对象上执行相应操作的动词。练习创建主体,并学会如何将它们结合起来形成所需的访问规则。确保使用不同的组合验证正确的行为。

知道如何创建和管理 Kubernetes 集群。

安装新的集群节点和升级现有集群节点的版本是 Kubernetes 管理员执行的典型任务。您不需要记住涉及的所有步骤。文档提供了这些操作的逐步易于遵循的手册。对于升级集群版本,建议先跳过一个单个次要版本或多个补丁版本,然后再处理下一个更高版本。高可用性集群有助于实现冗余和可扩展性。在考试中,您需要理解不同的 HA 拓扑,尽管您不太可能配置其中一个,因为这个过程涉及一套不同的主机。

练习备份和恢复 etcd。

etcd 灾难恢复过程的文档并不像你预期的那样完善。动手练习几次备份和恢复过程,以掌握操作技巧。记得将控制平面节点指向恢复的快照文件以恢复数据。

示例练习

这些练习的解答可在附录中找到。

  1. 在名为apps的新命名空间中创建名为api-access的 ServiceAccount。

  2. 创建名为api-clusterrole的 ClusterRole,并创建名为api-clusterrolebinding的 ClusterRoleBinding。将上一步骤中的 ServiceAccount 映射到 API 资源pods,操作为watchlistget

  3. 在名为apps的命名空间中创建一个名为operator的 Pod,使用镜像nginx:1.21.1。暴露容器端口 80。将 ServiceAccountapi-access分配给 Pod。在命名空间rm中创建另一个名为disposable的 Pod,使用镜像nginx:1.21.1。不要为 Pod 分配 ServiceAccount。

  4. 打开名为operator的 Pod 的交互式 shell。使用命令行工具curl调用 API 列出命名空间rm中的 Pods。你期望得到什么响应?使用命令行工具curl调用 API 删除命名空间rm中的 Poddisposable。与第一次调用相比,响应有何不同?你可以在参考指南中找到有关通过 HTTP 与 Pod 交互的信息。

  5. 转到检出的 GitHub 仓库bmuschko/cka-study-guide的目录app-a/ch02/upgrade-version。使用命令vagrant up启动运行集群的虚拟机。将集群的所有节点从 Kubernetes 1.20.4 升级到 1.21.2。集群包括一个名为k8s-control-plane的单个控制平面节点,以及三个名为worker-1worker-2worker-3的工作节点。完成后,使用vagrant destroy -f关闭集群。

    先决条件: 此练习需要安装工具VagrantVirtualBox

  6. 进入已检出的 GitHub 仓库 bmuschko/cka-study-guide 的目录 app-a/ch02/backup-restore-etcd。使用命令 vagrant up 启动运行集群的虚拟机(VMs)。该集群包括一个名为 k8s-control-plane 的控制平面节点和两个名为 worker-1worker-2 的工作节点。节点 k8s-control-plane 上预安装了 etcdctl 工具。将 etcd 备份到快照文件 /opt/etcd.bak。从快照文件恢复 etcd。使用数据目录 /var/bak。完成后,使用 vagrant destroy -f 停止集群。

    先决条件: 此练习需要安装工具 VagrantVirtualBox

第三章:工作负载

当我们谈论Kubernetes 中的工作负载时,我们指的是运行应用程序的 API 资源类型。这些 API 资源类型包括 Deployment、ReplicaSet、StatefulSet、DaemonSet、Job、CronJob 和当然 Pod。CKA 的课程对你需要熟悉的工作负载类型非常具体。考试只包括 Deployment、ReplicaSet 和 Pod。您需要理解 Deployment 管理的复制和升级功能,并理解将配置数据注入 Pod 的 API 原语。

注意

本章将使用体积的概念。如果您对 Kubernetes 的持久存储选项不熟悉,请查看第六章以获取更多信息。

在高层次上,本章涵盖以下概念:

  • 对 Deployments 的基本理解

  • Deployment 的部署和回滚功能

  • 由 ReplicaSet 控制的副本的手动和自动扩展

  • ConfigMap 和 Secret

使用 Deployments 管理工作负载

在 Kubernetes 中,工作负载在 Pod 中执行。有多种 API 资源来管理一个或多个 Pod。在本节中,我们将集中讨论 Deployment 和 ReplicaSet 这两个最与考试相关的 API 资源。此外,我们将简要介绍管理保持状态的工作负载的 StatefulSet。

理解 Deployments

在容器中运行应用程序的中心 API 资源是 Pod。使用单个 Pod 实例来运行应用程序存在缺陷。它代表一个单点故障,因为所有针对应用程序的流量都被引导到此 Pod。当负载因需求增加而增加时(例如,在电子商务应用程序的高峰购物季节期间,或者当中心微服务(如认证提供者)被系统内其他微服务越来越多地使用时),这种行为特别有问题。在 Pod 中运行应用程序的另一个重要方面是故障容忍性。在节点故障的情况下,Pod 将不会重新调度,因此可能导致终端用户的系统中断。在本节中,我们将讨论支持应用程序可伸缩性和故障容忍等方面的 Kubernetes 机制。

ReplicaSet是控制运行应用程序的多个相同 Pod 实例的 Kubernetes API 资源,称为副本。它具有根据需求扩展或缩减副本数量的能力。此外,它知道如何在所有副本中部署新版本的应用程序。

Deployment将 ReplicaSet 的功能抽象化,并在内部进行管理。在实际应用中,这意味着您无需自行创建、修改或删除 ReplicaSet 对象。Deployment 会保留应用程序版本的历史记录,并能够回滚到旧版本,以应对可能导致生产问题的阻塞或成本高昂的情况。此外,它还提供了扩展副本数量的能力。

图 3-1 说明了 Deployment、ReplicaSet 及其受控副本之间的关系。

ckas 0301

图 3-1. Deployment 与 ReplicaSet 之间的关系

接下来的几节将解释如何管理 Deployments,包括扩展和回滚功能。

创建 Deployments

您可以使用命令create deployment创建一个 Deployment。该命令提供了多种选项,其中一些是必填的。至少,您需要提供 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已为您设置好映射。示例 3-1 展示了 YAML 清单中的标签选择。此 YAML 清单可用于声明性地创建 Deployment,或通过检查前述命令式命令创建的现有对象来使用。

示例 3-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

当通过命令式命令创建时,默认情况下,Deployment 使用的标签键是app。您可以在 YAML 输出的三个不同位置找到此键:

  1. metadata.labels

  2. spec.selector.matchLabels

  3. spec.template.metadata.labels

要使标签选择正常工作,spec.selector.matchLabelsspec.template.metadata的分配需要匹配,如图 3-2 所示。

ckas 0302

图 3-2. Deployment 标签选择

metadata.labels的值对于将 Deployment 映射到 Pod 模板是无关紧要的。正如您在图 3-2 中所看到的,将metadata.labels的标签分配故意更改为deploy: app-cache,以强调对于 Deployment 到 Pod 模板选择而言并不重要。

列出 Deployments 及其 Pods

您可以使用命令get deployments在创建后检查 Deployment。该命令的输出呈现了其副本的重要细节,如下所示:

$ kubectl get deployments
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
app-cache   4/4     4            4           125m

你可以观察到与 Deployment 控制的副本相关的以下列标题,在表 3-1 中。

表 3-1. 运行时副本信息在列出部署时

列标题 描述
READY 列出格式为/的副本数,用于向最终用户显示。所需副本数对应于spec.replicas的值。
UP-TO-DATE 列出已更新以达到期望状态的副本数。
AVAILABLE 列出对最终用户可用的副本数。

通过它们的名称中的前缀可以识别由部署控制的 Pods。在之前创建的部署的情况下,Pod 的名称以 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

渲染部署详情

你可以渲染部署的详细信息。这些细节包括标签选择标准,当排除故障配置错误的部署时,这些细节可能非常有价值。以下输出提供了整体要点:

$ 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 的目的是复制一组相同的 Pods。你不需要深入理解 ReplicaSet 的核心功能来通过考试。只需知道 ReplicaSet 由部署自动创建,并使用与其控制的 Pods 类似的部署名称作为 ReplicaSet 的前缀。在前面命名为 app-cache 的部署的情况下,ReplicaSet 的名称为 app-cache-596bc5586d

删除部署

部署全权负责创建和删除它所控制的对象,即 Pods 和 ReplicaSets。当你删除一个部署时,相应的对象也会被删除。假设你正在处理以下输出中显示的对象集:

$ 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.

执行滚动更新和回滚

部分 API 资源内置了部署的发布和回滚功能。一旦更改了部署中的 Pod 模板的定义,Kubernetes 就知道如何将更改应用于对象管理的所有 Pods。在本节中,我们将讨论两种情况:部署应用程序的新版本和恢复应用程序的旧版本。

滚动部署新版本

部署使得将应用程序的新版本滚动到它所控制的所有副本变得非常容易。假设你想将 Memcached 的版本从 1.6.8 升级到 1.6.10,以便获得最新的功能和错误修复。你只需要通过更新 Pod 模板来更改对象的期望状态。部署会逐一更新所有副本到新版本。这个过程称为滚动更新

随时可以使用命令edit deployment修改活动对象。或者,命令set image提供了一种快速便捷的方式来更改 Deployment 的镜像,如下所示:

$ kubectl set image deployment app-cache memcached=memcached:1.6.10 --record
deployment.apps/app-cache image updated

标志--record是可选的,默认值为false。如果提供而没有值或设置为true,则将记录用于更改的命令。在内部,set image命令通过分配具有键kubernetes.io/change-cause的注释来保留更改原因到 Deployment。

您可以在进行中检查滚动发布的当前状态。要使用的命令是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 所做的更改。每个更改都由所谓的修订版本表示。您可以通过运行以下命令检查滚动历史。您将看到列出了两个修订版本:

$ kubectl rollout history deployment app-cache
deployment.apps/app-cache
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl set image deployment app-cache memcached=memcached:1.6.10 \
          --record=true

第一个修订版本是记录了创建对象时 Deployment 的原始状态。第二个修订版本是为更改镜像标签而添加的。请注意,“CHANGE-CAUSE”列显示了用于更改的命令。

要获取修订版本的更详细视图,请运行以下命令。您可以看到镜像使用的值为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
  Annotations:	kubernetes.io/change-cause: kubectl set image deployment \
                app-cache memcached=memcached:1.6.10 --record=true
  Containers:
   memcached:
    Image:	memcached:1.6.10
    Port:	<none>
    Host Port:	<none>
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>

回滚到之前的修订版本

在生产环境中可能会出现需要快速处理的问题。例如,假设刚刚发布的容器镜像包含一个关键的 bug。Kubernetes 允许您回滚到滚动历史中的先前修订版本之一。您可以使用rollout undo命令来实现这一点。要选择特定的修订版本,请提供命令行选项--to-revision。如果不提供选项,该命令将回滚到先前的修订版本。在这里,我们回滚到修订版本 1:

$ kubectl rollout undo deployment app-cache --to-revision=1
deployment.apps/app-cache rolled back

因此,Kubernetes 对所有副本执行了带有修订版本 1 的滚动更新。现在检查滚动历史,会列出修订版本 3。考虑到我们回滚到了修订版本 1,就没有必要再保留该条目作为重复的了。Kubernetes 简单地将修订版本 1 转换为 3,并从列表中删除 1:

$ kubectl rollout history deployment app-cache
deployment.apps/app-cache
REVISION  CHANGE-CAUSE
2         kubectl set image deployment app-cache memcached=memcached:1.6.10 \
          --record=true
3         <none>

扩展工作负载

扩展性是 Kubernetes 内置的能力之一。我们将学习如何根据应用程序负载的增加手动扩展副本数。此外,我们还将讨论 API 资源 Horizontal Pod Autoscaler,它允许根据 CPU 和内存等资源阈值自动扩展管理的 Pod 集合。

手动扩展 Deployment

增加或减少由 Deployment 控制的副本数量是一个简单的过程。您可以通过手动编辑活动对象使用edit deployment并更改spec.replicas属性的值,或者使用命令式scale deployment命令。以下命令将副本数量从四个增加到六个:

$ kubectl scale deployment app-cache --replicas=6
deployment.apps/app-cache scaled

您可以实时观察副本的创建过程。如果您速度足够快,您可能仍然可以看到新创建的 Pod 的状态从 ContainerCreating 变为 Running

$ kubectl get pods
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
$ kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
app-cache-5d6748d8b9-6cc4j   1/1     Running   0          3m17s
app-cache-5d6748d8b9-6rmlj   1/1     Running   0          32m
app-cache-5d6748d8b9-6z7g5   1/1     Running   0          3m17s
app-cache-5d6748d8b9-96dzf   1/1     Running   0          32m
app-cache-5d6748d8b9-jkjsv   1/1     Running   0          31m
app-cache-5d6748d8b9-svrxw   1/1     Running   0          32m

手动扩展副本数需要一些猜测。您仍需监视系统负载,以确定副本数量是否足以处理传入的流量。

手动扩展 StatefulSet

另一个可以手动扩展的 API 资源是 StatefulSet。StatefulSet 用于通过一组 Pod(例如数据库)管理有状态应用程序。与部署类似,StatefulSet 定义了一个 Pod 模板;然而,每个副本保证具有独特和持久的标识。与部署类似,StatefulSet 使用 ReplicaSet 来管理副本。

我们不会详细讨论 StatefulSet,但您可以在 文档 中进一步了解它们。我们在这里讨论 StatefulSet API 资源的原因是它可以像部署一样进行手动扩展。

假设我们要处理 StatefulSet 和 Service 的 YAML 定义,运行并公开 Redis 数据库,如 示例 3-2 所示。

示例 3-2. 用于 StatefulSet 和 Service 的 YAML 清单
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: default
  labels:
    app: redis
spec:
  ports:
  - port: 6379
    protocol: TCP
  selector:
    app: redis
  type: ClusterIP
  clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  selector:
    matchLabels:
      app: redis
  replicas: 1
  serviceName: "redis"
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:6.2.5
        command: ["redis-server", "--appendonly", "yes"]
        ports:
        - containerPort: 6379
          name: web
        volumeMounts:
        - name: redis-vol
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redis-vol
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

创建完成后,列出 StatefulSet 在 “READY” 列中的副本数。正如下面的输出所示,我们将副本数设置为 1:

$ kubectl create -f redis.yaml
service/redis created
statefulset.apps/redis created
$ kubectl get statefulset redis
NAME    READY   AGE
redis   1/1     2m10s
$ kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
redis-0   1/1     Running   0          2m

我们在部署环境中探索的 scale 命令在这里同样有效。在以下命令中,我们将副本数从一个扩展到三个:

$ kubectl scale statefulset redis --replicas=3
statefulset.apps/redis scaled
$ kubectl get statefulset redis
NAME    READY   AGE
redis   3/3     3m43s
$ kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
redis-0   1/1     Running   0          101m
redis-1   1/1     Running   0          97m
redis-2   1/1     Running   0          97m

需要提到的是,缩减 StatefulSet 的过程需要所有副本处于健康状态。由 StatefulSet 控制的 Pod 中长期未解决的问题可能会导致应用不可用于最终用户。

自动扩展部署

另一种扩展部署的方式是借助水平 Pod 自动扩展器(HPA)。HPA 是一个 API 原语,用于在特定条件下自动扩展副本数。当前稳定 API 版本的 HPA 仅支持 CPU 利用率作为扩展条件。运行时,HPA 会检查由 metrics server 收集的指标,以确定部署的所有副本的平均最大 CPU 使用率是否低于或高于定义的阈值。图 3-3 展示了涉及 HPA 的高级架构图。

ckas 0303

图 3-3. 自动扩展部署

创建水平 Pod 自动扩展器

您可以使用autoscale deployment命令为现有的 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

上述命令是为 Deployment 创建 HPA 的绝佳快捷方式。HPA 对象的 YAML 清单表示如示例 3-3 所示。

示例 3-3. 用于 HPA 的 YAML 清单
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: app-cache
spec:
  maxReplicas: 5
  minReplicas: 3
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app-cache
  targetCPUUtilizationPercentage: 80

列出水平 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

如果 Deployment 的 Pod 模板未定义 CPU 资源需求,或者无法从指标服务器检索 CPU 指标,则“TARGETS”列的左值显示为。示例 3-4 设置 Pod 模板的资源需求,以便 HPA 正常工作。

示例 3-4. 设置 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

使用水平 Pod 自动缩放器的 Beta API 版本

Kubernetes 1.12 引入了 HPA 的 Beta API 版本autoscaling/v2beta2,该版本在 Kubernetes 1.23 中成为名为autoscaling/v2的最终 API。API 资源模型的 YAML 清单以更通用的方式观察指标。正如在示例 3-5 中所示,我们正在检查 CPU 和内存利用率,以确定是否需要扩展或缩减 Deployment 的副本。

示例 3-5. 使用 v2 的 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 模板的内存资源需求,如示例 3-6 所示。

示例 3-6. 设置 Pod 模板的内存资源需求
...
spec:
  ...
  template:
    ...
    spec:
      containers:
      - name: memcached
        ...
        resources:
          requests:
            cpu: 250m
            memory: 100Mi
          limits:
            cpu: 500m
            memory: 500Mi

列出 HPA 会在“TARGETS”列中呈现两个指标,如此处显示的get命令输出:

$ kubectl get hpa
NAME        REFERENCE              TARGETS                 MINPODS   MAXPODS \
  REPLICAS   AGE
app-cache   Deployment/app-cache   1994752/500Mi, 0%/80%   3         5       \
  3          2m14s

定义和使用配置数据

遇到评估环境变量以控制其运行行为的应用程序是很常见的。例如,应用程序可以定义一个环境变量,指向外部服务的 URL,或者注入用于与另一个微服务进行身份验证的 API 密钥。

为容器声明环境变量很简单。你只需将它们作为键值对列在属性spec.containers[].env[]下即可。示例 3-7 定义了容器memcached的环境变量MEMCACHED_CONNECTIONSMEMCACHED_THREADS

示例 3-7. 为容器设置的环境变量
apiVersion: v1
kind: Pod
metadata:
  name: memcached
spec:
  containers:
  - name: memcached
    image: memcached:1.6.8
    env:
    - name: MEMCACHED_CONNECTIONS
      value: "2048"
    - name: MEMCACHED_THREADS
      value: "150"

如果这些环境变量成为同一命名空间内多个 Pod 清单中的常见商品,则无法避免复制粘贴定义。针对这种特定用例,Kubernetes 引入了由专用 API 资源表示的配置数据概念。

这些 API 资源称为 ConfigMap 和 Secret。它们都定义了一组键值对,并可以作为环境变量注入到容器中或者作为卷挂载。图 3-4 说明了这些选项。

Secret 的值只能被编码

Secrets 期望每个条目的值都是 Base64 编码的。Base64 仅编码值,但不加密它。因此,任何拥有访问权限的人都可以无问题地解码它。Secret 仅分发给实际需要访问它的 Pod 所在的节点。此外,Secrets 存储在内存中,永远不会写入物理存储。

ckas 0304

图 3-4. Kubernetes 中的配置数据

创建一个 ConfigMap

您可以通过发出命令create configmap来创建一个 ConfigMap。此命令要求您以选项的形式提供数据的来源。Kubernetes 区分了表 3-2 中显示的四种不同选项。

表 3-2. 由 ConfigMap 解析数据的源选项

选项 示例 描述
--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-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 对象看起来像示例 3-8 中显示的对象。正如你所见,该对象在一个名为data的部分定义了键值对。ConfigMap 没有spec部分。

示例 3-8. ConfigMap YAML 文件
apiVersion: v1
kind: ConfigMap
metadata:
  name: db-config
data:
  DB_HOST: mysql-service
  DB_USER: backend

将 ConfigMap 作为环境变量使用

创建 ConfigMap 后,现在可以将其键值对作为环境变量注入到容器中。示例 3-9 展示了使用spec.containers[].envFrom[].configMapRef引用 ConfigMap 的名称。

示例 3-9. 将 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 文件中,其结构如示例 3-10 所示。

示例 3-10. 用于配置数据库信息的 JSON 文件
{
    "db": {
      "host": "mysql-service",
      "user": "backend"
    }
}

鉴于我们不处理字面键值对,创建 ConfigMap 对象时需要提供选项--from-file

$ kubectl create configmap db-config --from-file=db.json
configmap/db-config created

Pod 将 ConfigMap 作为卷挂载到容器内的特定路径。假设应用程序在启动时将读取配置文件。示例 3-11 演示了 YAML 定义。

示例 3-11. 将 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

若要验证正确的行为,请打开容器的交互式 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"
    }
}

创建一个 Secret

您可以使用命令 create secret 创建一个秘密。此外,必须提供一个强制性子命令,确定秘密的类型。 表 3-3 列出了不同的类型。

表 3-3. 创建秘密的选项

选项 描述
generic 从文件、目录或字面值创建秘密。
docker-registry 为 Docker 注册表创建秘密。
tls 创建一个 TLS 秘密。

最常用的秘密类型是 generic。通用秘密的选项与 ConfigMap 完全相同,如 表 3-4 所示。

表 3-4. 由秘密解析的数据的源选项

选项 示例 描述
--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

在使用命令创建时,秘密将自动对提供的值进行 Base64 编码。这可以通过查看生成的 YAML 清单来观察到。您可以在 示例 3-12 中看到,值 s3cre! 已经转换为 czNjcmUh,即其 Base64 编码等效形式。

示例 3-12. 带有 Base64 编码值的秘密
apiVersion: v1
kind: Secret
metadata:
  name: db-creds
type: Opaque
data:
  pwd: czNjcmUh

如果您从 YAML 清单开始创建秘密对象,则需要自行创建 Base64 编码的值。一个完成此任务的 Unix 工具是 base64。以下命令正是如此:

$ echo -n 's3cre!' | base64
czNjcmUh

或者,您还可以使用其中一个专门的 秘密类型 来避免提供 Base64 编码的值。类型 kubernetes.io/basic-auth 适用于基本身份验证,期望键 usernamepassword。从此定义创建的对象将自动为这两个键的值进行 Base64 编码。 示例 3-13 展示了一个类型为 kubernetes.io/basic-auth 的秘密的 YAML 清单。请注意,定义键值对的属性称为 stringData,而不是 Opaque 秘密类型使用的 data

示例 3-13. 使用类型为 kubernetes.io/basic-auth 的秘密
apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: bmuschko
  password: secret

作为环境变量消耗秘密

作为环境变量消耗秘密与为 ConfigMaps 做法类似。在这里,您将使用 YAML 表达式spec.containers[].envFrom[].secretRef引用秘密的名称。示例 3-14 将名为secret-basic-auth的秘密注入为环境变量到名为backend的容器中。

示例 3-14. 将秘密键值对注入到容器中
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
...

将一个秘密作为卷挂载

要演示将一个秘密作为卷挂载,我们将创建一个新的类型为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,如在示例 3-15 中使用的。

示例 3-15. 将一个秘密作为卷挂载
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

您将在挂载路径/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-----

总结

CKA 涵盖的工作负载部分包括 API 资源部署、副本集和 Pod。部署控制着负责管理多个相同 Pod 的副本集。这些副本的数量可以通过手动或使用水平 Pod 自动缩放器自动缩放。对部署定义的副本模板进行的任何更改都将应用于副本。作为最终用户,您可以检查卷出历史记录、当前卷出状态及其进度。

应用程序的运行时行为可以通过将配置数据作为环境变量注入或将卷挂载到路径来控制。在 Kubernetes 中,这些配置数据以键值对的形式表示为 API 资源 ConfigMap 和 Secret。ConfigMap 用于纯文本数据,而 Secret 将值编码为 Base64 以混淆值。Secret 通常更适合包含敏感信息如凭证和 SSH 私钥的情况。

考试要点

知道如何扩展一个部署并滚动更新

部署具有一组 Pod 的卓越管理功能。与创建、更新和删除单个 Pod 相比,应优先使用它们。您需要熟悉部署的所有方面,包括手动扩展副本数量或借助水平 Pod 自动伸缩器进行自动扩展。回滚历史记录跟踪对 Pod 模板所做的修订。您可以部署新的修订版或回滚到先前的修订版。练习这些技术以及它对副本的影响。

练习创建和使用 ConfigMaps 和 Secrets。

可以使用 ConfigMap 或 Secret 将配置数据注入到 Pod 中。通过提供不同的数据源(例如文字值、文件和目录),实践使用命令式和声明式方法创建这些对象。Secrets 提供专门的类型。尝试不同的设置方式。您需要精通将 ConfigMaps 和 Secrets 定义的数据注入到容器中的不同方式。

示例练习

这些练习的解决方案位于附录中。

  1. 创建名为nginx的部署(Deployment),使用镜像nginx:1.17.0。初始设置两个副本。

  2. 使用scale命令将部署扩展到七个副本。确保存在正确数量的 Pod。

  3. 为部署创建水平 Pod 自动伸缩器(Horizontal Pod Autoscaler),将 CPU 的平均利用率设置为 65%,内存的平均利用率设置为 1Gi。将最小副本数设置为 3,最大副本数设置为 20。

  4. 更新部署的 Pod 模板,使用镜像nginx:1.21.1。确保记录更改。检查修订历史记录。应该呈现多少个修订版本?回滚到第一个修订版本。

  5. 创建名为basic-auth的新 Secret,类型为kubernetes.io/basic-auth。分配键值对username=superpassword=my-s8cr3t。将 Secret 作为卷挂载到路径/etc/secret,并为部署控制的 Pod 设置只读权限。

第四章:调度和工具

CKA 考试的调度部分关注的是在由 Kubernetes 调度程序评估时定义资源边界的影响。调度程序的默认运行行为也可以通过定义节点亲和规则以及污点和容忍度来修改。在这些概念中,你只需理解资源边界的细微差别及其在不同场景中对调度程序的影响。最后,该课程领域提到了清单管理和模板工具的高级知识。

从高层次来看,本章涵盖以下概念:

  • Pod 的资源边界

  • 命令式和声明式清单管理

  • 像 Kustomize、yq和 Helm 这样的常见模板工具

理解资源限制如何影响 Pod 调度

Kubernetes 集群可以由多个节点组成。根据各种规则(例如,节点选择器节点亲和性污点和容忍度),Kubernetes 调度程序决定选择哪个节点来运行工作负载。CKA 考试并不要求你理解前述调度概念,但对它们的工作原理有个大致了解会有所帮助。

在工作负载调度中起作用的一个度量标准是由 Pod 中的容器定义的资源请求。常用的可以指定的资源包括 CPU 和内存。调度程序确保节点的资源容量可以满足 Pod 的资源需求。具体来说,调度程序计算 Pod 中所有容器定义的每种资源类型的资源请求总和,并将其与节点的可用资源进行比较。图 4-1 说明了基于资源请求的调度过程。

ckas 0401

图 4-1. 基于资源请求的 Pod 调度

定义容器资源请求

每个 Pod 中的每个容器都可以定义自己的资源请求。表 4-1 描述了包括示例值在内的可用选项。

表格 4-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

Kubernetes 使用资源单元来管理那些不同于标准资源单位如兆字节和千兆字节的资源类型。解释所有这些单位的复杂性超出了本书的范围,但你可以在文档中详细阅读相关细节。

为了使这些资源请求的使用透明化,我们将查看一个定义示例。在示例 4-1 中显示的 Pod YAML 文件定义了两个容器,每个容器都有自己的资源请求。任何可以运行 Pod 的节点都需要支持至少 320Mi 内存容量和 1250m CPU,这是两个容器中所有资源的总和。

示例 4-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"

在这种场景中,我们正在处理一个 Minikube Kubernetes 集群,包括三个节点,一个控制平面节点和两个工作节点。以下命令列出所有节点:

$ kubectl get nodes
NAME           STATUS   ROLES                  AGE   VERSION
minikube       Ready    control-plane,master   12d   v1.21.2
minikube-m02   Ready    <none>                 42m   v1.21.2
minikube-m03   Ready    <none>                 41m   v1.21.2

在下一步中,我们将根据 YAML 文件创建 Pod。调度器会将 Pod 放置在名为minikube-m03的节点上:

$ kubectl create -f rate-limiter-pod.yaml
pod/rate-limiter created
$ kubectl get pod rate-limiter -o yaml | grep nodeName:
  nodeName: minikube-m03

进一步检查节点时,您可以查看其最大容量、可分配容量以及节点上调度的 Pod 的内存请求。以下命令列出了相关信息,并将输出压缩为相关的片段:

$ kubectl describe node minikube-m03
...
Capacity:
  cpu:                2
  ephemeral-storage:  17784752Ki
  hugepages-2Mi:      0
  memory:             2186612Ki
  pods:               110
Allocatable:
  cpu:                2
  ephemeral-storage:  17784752Ki
  hugepages-2Mi:      0
  memory:             2186612Ki
  pods:               110
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits  \
  Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------  \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   0 (0%)      \
  320Mi (14%)      0 (0%)         9m
...

可能由于节点上可用资源不足而无法调度 Pod。在这些情况下,Pod 的事件日志将以PodExceedsFreeCPUPodExceedsFreeMemory的原因指示此情况。有关如何排查和解决此情况的更多信息,请参阅相关文档部分

定义容器资源限制

另一个可以为容器设置的度量标准是其资源限制。资源限制确保容器不会消耗超过分配的资源量。例如,您可以表达运行在容器中的应用程序应被限制在 1000m 的 CPU 和 512Mi 的内存。

根据集群使用的容器运行时,超出任何允许的资源限制会导致终止容器中运行的应用程序进程,或者系统会完全阻止分配超出限制的资源。关于 Docker 容器运行时如何处理资源限制的深入讨论,请参阅文档

表 4-2 描述了包括示例值在内的可用选项。

表 4-2. 资源限制选项

YAML 属性 描述 示例值
spec.containers[].resources.limits.cpu CPU 资源类型 500m (500 毫 CPU)
spec.containers[].resources.limits.memory 内存资源类型 64Mi (2²⁶ 字节)
spec.containers[].resources.limits.hugepages-<size> 大页面资源类型 hugepages-2Mi: 60Mi
spec.containers[].resources.limits.ephemeral-storage 临时存储资源类型 4Gi

示例 4-2 展示了限制定义的实际应用。在这里,名为 business-app 的容器不能使用超过 512Mi 的内存和 2000m 的 CPU。名为 ambassador 的容器定义了 128Mi 的内存和 500m 的 CPU 的限制。

示例 4-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: "512Mi"
        cpu: "2"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"

假设 Pod 被调度到节点 minikube-m03 上。描述节点的详细信息显示了 CPU 和内存限制已生效。但还有更多。如果只定义了限制,Kubernetes 会自动为请求分配相同数量的资源:

$ kubectl describe node minikube-m03
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits  \
   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------  \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   1250m (62%) \
  320Mi (14%)      320Mi (14%)    11s
...

容器资源请求和限制的定义

建议为每个容器指定资源请求和限制。确定这些资源期望并不总是容易,特别是对于尚未在生产环境中使用过的应用程序。在开发周期的早期阶段进行负载测试可以帮助分析资源需求。将应用程序部署到集群后,通过监控应用程序的资源消耗可以进行进一步调整。示例 4-3 在单个 YAML 清单中结合了资源请求和限制。

示例 4-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: "512Mi"
        cpu: "2"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

因此,您可以看到不同的资源请求和限制的设置:

$ kubectl describe node minikube-m03
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits   \
   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------   \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   2500m (125%) \
  320Mi (14%)      640Mi (29%)    3s
...

管理对象

使用命令式的 kubectl 命令或者针对声明了对象期望状态的配置文件(称为清单)运行 kubectl 命令可以创建、修改和删除 Kubernetes 对象。清单的主要定义语言是 YAML,尽管您也可以选择 JSON,后者在 Kubernetes 社区中采用较少。建议开发团队将这些配置文件提交并推送到版本控制存储库,以便随时间跟踪和审计变更。

在 Kubernetes 中建模应用程序通常需要一组支持对象,每个对象可以有自己的清单。例如,您可能希望创建一个部署,将应用程序运行在五个 Pod 上,一个 ConfigMap 以将配置数据注入为环境变量,以及一个 Service 用于公开网络访问。

本节主要关注声明式对象管理的支持,借助清单来实现。要深入讨论命令式支持,请参阅文档中相关部分。此外,我们将涉及类似 Kustomize 和 Helm 的工具,以让您了解它们的优势、功能和工作流程。

使用配置文件进行声明式对象管理

声明式对象管理需要一个或多个配置文件,格式可以是 YAML 或 JSON,描述对象的期望状态。您可以通过这种方法创建、更新和删除对象。

创建对象

要创建新对象,请使用apply命令,指向一个文件、文件目录或通过 HTTP(S) URL 引用的文件,并使用-f选项。如果一个或多个对象已经存在,则命令将会同步配置所做的更改与实时对象。

为了演示功能,我们假设以下目录和配置文件。以下命令从单个文件创建对象,从目录中的所有文件创建对象,以及递归目录中的所有文件创建对象:

.
├── 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/cka-study-guide/ \
  master/ch04/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命令完成。您只需更改配置文件,然后运行该命令。示例 4-4 修改了文件nginx-deployment.yaml中部署的现有配置。我们添加了一个新标签,键为team,并将副本数从 3 修改为 5。

示例 4-4. 修改后的部署配置文件
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
    team: red
spec:
  replicas: 5
...

以下命令应用了更改后的配置文件。结果,由底层副本集控制的 Pod 数量为 5。部署的注释kubectl.kubernetes.io/last-applied-configuration反映了配置的最新更改:

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment configured
$ kubectl get deployments,pods
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   5/5     5            5           10m

NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-66b6c48dd5-79j6t   1/1     Running   0          35s
pod/nginx-deployment-66b6c48dd5-bkkgb   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-d26c8   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-fcqrs   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-whfnn   1/1     Running   0          35s
$ 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命令删除对象,并指向配置文件。以下命令删除一个部署及其控制的对象(副本集和 Pod):

$ kubectl delete -f nginx-deployment.yaml
deployment.apps "nginx-deployment" deleted
$ kubectl get deployments,replicasets,pods
No resources found in default namespace.

使用 Kustomize 进行声明式对象管理

Kustomize 是一个引入于 Kubernetes 1.14 的工具,旨在使清单管理更加方便。它支持三种不同的使用场景:

  • 从其他来源生成清单。例如,创建一个 ConfigMap,并从属性文件填充其键值对。

  • 添加多个清单中的通用配置。例如,为部署和服务添加命名空间和一组标签。

  • 组合和自定义一组清单。例如,为多个部署设置资源边界。

Kustomize 正常工作所需的中心文件是kustomization 文件。该文件的标准名称为kustomization.yaml,不可更改。一个 kustomization 文件定义了 Kustomize 处理的处理规则。

Kustomize 与kubectl完全集成,可以以两种模式执行:在控制台上呈现处理输出或创建对象。只要它们包含 kustomization 文件和引用的资源文件,这两种模式都可以操作目录、tarball、Git 存档或 URL:

呈现生成的输出

第一种模式使用kustomize子命令在控制台上呈现生成的结果,但不创建对象。此命令类似于您可能从run命令中了解的干运行选项:

$ kubectl kustomize <target>

创建对象

第二种模式使用apply命令与-k命令行选项结合使用,以应用 Kustomize 处理的资源,如前一节所述:

$ kubectl apply -k <target>

以下部分通过单个示例演示了每个用例。要全面覆盖所有可能的场景,请参阅文档Kustomize GitHub 存储库

组合清单

Kustomize 的核心功能之一是从其他清单创建组合清单。将多个清单合并为一个可能看起来并不那么有用,但稍后描述的许多其他功能将基于这种能力构建。假设您想要从一个部署和一个服务资源文件中组合清单。您所需做的就是将资源文件放入与 kustomization 文件相同的文件夹中:

.
├── kustomization.yaml
├── web-app-deployment.yaml
└── web-app-service.yaml

kustomization 文件在resources部分列出了资源,如示例 4-5 所示。

示例 4-5. 结合两个清单的 kustomization 文件
resources:
- web-app-deployment.yaml
- web-app-service.yaml

因此,kustomize子命令呈现了包含所有资源的组合清单,这些资源由三个连字符(---)分隔以表示不同的对象定义:

$ kubectl kustomize ./
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web-app-service
  name: web-app-service
spec:
  ports:
  - name: web-app-port
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: web-app
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web-app-deployment
  name: web-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - env:
        - name: DB_HOST
          value: mysql-service
        - name: DB_USER
          value: root
        - name: DB_PASSWORD
          value: password
        image: bmuschko/web-app:1.0.1
        name: web-app
        ports:
        - containerPort: 3000

从其他来源生成清单

在本章早些时候,我们了解到 ConfigMaps 和 Secrets 可以通过将它们指向包含实际配置数据的文件来创建。Kustomize 可以通过映射这些配置对象的 YAML 清单与它们的数据之间的关系来帮助处理这个过程。此外,我们还希望将创建的 ConfigMap 和 Secret 作为环境变量注入到 Pod 中。在本节中,您将学习如何借助 Kustomize 实现此目标。

下面的文件和目录结构包含了 Pod 的清单文件以及我们需要用于 ConfigMap 和 Secret 的配置数据文件。强制的 kustomization 文件位于目录树的根级别:

.
├── config
│   ├── db-config.properties
│   └── db-secret.properties
├── kustomization.yaml
└── web-app-pod.yaml

kustomization.yaml中,您可以定义应以给定名称生成 ConfigMap 和 Secret 对象。ConfigMap 的名称应为db-config,Secret 的名称将为db-credsconfigMapGeneratorsecretGenerator的两个生成器属性引用用于输入配置数据的输入文件。可以使用resources属性明确指定任何其他资源。示例 4-6 显示了 kustomization 文件的内容。

示例 4-6. 使用 ConfigMap 和 Secret 生成器的 kustomization 文件
configMapGenerator:
- name: db-config
  files:
  - config/db-config.properties
secretGenerator:
- name: db-creds
  files:
  - config/db-secret.properties
resources:
- web-app-pod.yaml

Kustomize 通过向名称添加后缀来生成 ConfigMaps 和 Secrets。使用apply命令创建对象时可以看到此行为。Pod 清单中可以按名称引用 ConfigMap 和 Secret:

$ kubectl apply -k ./
configmap/db-config-t4c79h4mtt unchanged
secret/db-creds-4t9dmgtf9h unchanged
pod/web-app created

可以在 kustomization 文件中使用属性generatorOptions配置此命名策略。有关更多信息,请参阅文档

我们还可以尝试kustomize子命令。该命令不会创建对象,而是在控制台上呈现处理后的输出:

$ kubectl kustomize ./
apiVersion: v1
data:
  db-config.properties: |-
    DB_HOST: mysql-service
    DB_USER: root
kind: ConfigMap
metadata:
  name: db-config-t4c79h4mtt
---
apiVersion: v1
data:
  db-secret.properties: REJfUEFTU1dPUkQ6IGNHRnpjM2R2Y21RPQ==
kind: Secret
metadata:
  name: db-creds-4t9dmgtf9h
type: Opaque
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - envFrom:
    - configMapRef:
        name: db-config-t4c79h4mtt
    - secretRef:
        name: db-creds-4t9dmgtf9h
    image: bmuschko/web-app:1.0.1
    name: web-app
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

在多个清单文件中添加常见配置

应用程序开发人员通常在由多个清单组成的应用程序堆栈集上工作。例如,应用程序堆栈可以由前端微服务、后端微服务和数据库组成。通常的做法是为每个清单使用相同的交叉配置(如命名空间、标签或注释)。Kustomize 提供了一系列支持的字段(例如命名空间、标签或注释)。请参阅文档以了解所有支持的字段。

对于下一个示例,我们假设一个 Deployment 和一个 Service 位于同一个命名空间,并使用共同的一组标签。命名空间称为persistence,标签是键值对team: helix。示例 4-7 展示了如何在 kustomization 文件中设置这些共同字段。

示例 4-7. 使用共同字段的 kustomization 文件
namespace: persistence
commonLabels:
  team: helix
resources:
- web-app-deployment.yaml
- web-app-service.yaml

要在 kustomization 文件中创建引用的对象,请运行apply命令。确保预先创建persistence命名空间:

$ kubectl create namespace persistence
namespace/persistence created
$ kubectl apply -k ./
service/web-app-service created
deployment.apps/web-app-deployment created

处理后的文件的 YAML 表示如下:

$ kubectl kustomize ./
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web-app-service
    team: helix
  name: web-app-service
  namespace: persistence
spec:
  ports:
  - name: web-app-port
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: web-app
    team: helix
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web-app-deployment
    team: helix
  name: web-app-deployment
  namespace: persistence
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
      team: helix
  template:
    metadata:
      labels:
        app: web-app
        team: helix
    spec:
      containers:
      - env:
        - name: DB_HOST
          value: mysql-service
        - name: DB_USER
          value: root
        - name: DB_PASSWORD
          value: password
        image: bmuschko/web-app:1.0.1
        name: web-app
        ports:
        - containerPort: 3000

定制一组清单文件

Kustomize 可以将一个 YAML 清单文件的内容与另一个 YAML 清单文件中的代码片段合并。典型用例包括向 Pod 定义中添加安全上下文配置或为 Deployment 设置资源边界。kustomization 文件允许指定不同的补丁策略,如patchesStrategicMergepatchesJson6902。有关补丁策略之间差异的更深入讨论,请参阅文档。

示例 4-8 显示了一个 kustomization 文件的内容,该文件用文件nginx-deployment.yaml中的 Deployment 定义修补文件security-context.yaml的内容。

示例 4-8. 定义补丁的 Kustomization 文件
resources:
- nginx-deployment.yaml
patchesStrategicMerge:
- security-context.yaml

在示例 4-9 中显示的补丁文件为 Deployment 的 Pod 模板定义了安全上下文。在运行时,补丁策略尝试查找名为nginx的容器并增强额外的配置。

示例 4-9. 补丁 YAML 清单
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  template:
    spec:
      containers:
      - name: nginx
        securityContext:
          runAsUser: 1000
          runAsGroup: 3000
          fsGroup: 2000

结果是一个已打补丁的部署定义,如下一个展示的kustomize子命令的输出所示。补丁机制可以应用于其他需要统一安全上下文定义的文件:

$ kubectl kustomize ./
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        name: nginx
        ports:
        - containerPort: 80
        securityContext:
          fsGroup: 2000
          runAsGroup: 3000
          runAsUser: 1000

常见模板工具

如前一节所示,Kustomize 提供了模板功能。Kubernetes 生态系统提供了其他解决此问题的解决方案,我们将在这里讨论。我们将涉及 YAML 处理器yq和模板引擎 Helm。

使用 YAML 处理器 yq

工具yq用于读取、修改和增强 YAML 文件的内容。本节将演示这三种用法。有关详细使用示例列表,请参阅GitHub 仓库。在 CKA 考试期间,您可能会被要求应用这些技术,尽管不必深入理解所用工具的所有复杂性。下文描述的yq版本是 4.2.1。

读取值

从现有 YAML 文件中读取值需要使用 YAML 路径表达式。路径表达式允许您深入导航 YAML 结构并提取您正在查找的属性的值。示例 4-10 显示了定义两个环境变量的 Pod 的 YAML 清单。

示例 4-10. Pod 的 YAML 清单
apiVersion: v1
kind: Pod
metadata:
  name: spring-boot-app
spec:
  containers:
  - image: bmuschko/spring-boot-app:1.5.3
    name: spring-boot-app
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: '1.5.3'

要读取一个值,使用命令eval或简写e,提供 YAML 路径表达式,并将其指向源文件。以下两个命令读取 Pod 的名称和单个容器定义的第二个环境变量的值。注意,路径表达式需要以必需的点字符(.)开头,表示 YAML 结构的根节点:

$ yq e *.metadata.name* pod.yaml
spring-boot-app
$ yq e *.spec.containers[0].env[1].value* pod.yaml
1.5.3

修改值

修改现有值与使用相同的命令并添加-i标志一样简单。通过将新值分配给路径表达式来将新值分配给属性。以下命令将 Pod YAML 文件的第二个环境变量更改为值 1.6.0:

$ yq e -i *.spec.containers[0].env[1].value = "1.6.0"* pod.yaml
$ cat pod.yaml
...
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: *1.6.0*

合并 YAML 文件

与 Kustomize 类似,yq可以合并多个 YAML 文件。Kustomize 显然更强大且更方便使用;但是,对于较小的项目,yq可能会派上用场。比如,您希望将示例 4-11 中显示的 sidecar 容器定义合并到 Pod 的 YAML 文件中。

示例 4-11. 容器定义的 YAML 清单
spec:
  containers:
  - image: envoyproxy/envoy:v1.19.1
    name: proxy-container
    ports:
    - containerPort: 80

要实现此操作的命令是 eval-all。鉴于此命令的多种配置选项,我们不会深入讨论。如需详细了解,请查阅 “Multiply (Merge)” operationyq 用户手册中的介绍。以下命令将在现有容器数组的 Pod 清单中附加 sidecar 容器:

$ yq eval-all 'select(fileIndex == 0) *+ select(fileIndex == 1)' pod.yaml \
  sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
  name: spring-boot-app
spec:
  containers:
  - image: bmuschko/spring-boot-app:1.5.3
    name: spring-boot-app
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: '1.5.3'
  - image: envoyproxy/envoy:v1.19.1
    name: proxy-container
    ports:
    - containerPort: 80

使用 Helm

Helm 是一个用于 Kubernetes 清单的模板引擎和包管理器。在运行时,它将 YAML 模板文件中的占位符替换为实际的、用户定义的值。由 Helm 可执行文件生成的产物称为 chart 文件,打包了组成应用程序的 API 资源的清单。这个 chart 文件可以上传到包管理器,在部署过程中使用。Helm 生态系统在中央图表库中提供了大量可重用的图表,例如运行 Grafana 或 PostgreSQL。

由于 Helm 提供了丰富的功能,我们仅讨论最基础的部分。CKA 考试不要求您成为 Helm 的专家,而是希望您熟悉其优势和概念。有关 Helm 的更详细信息,请参阅用户文档。本文描述的 Helm 版本为 3.7.0。

标准图表结构

图表需要遵循预定义的目录结构。您可以为根目录选择任何名称。在该目录中,必须存在两个文件:Chart.yamlvalues.yaml。文件 Chart.yaml 描述了图表的元信息(例如名称和版本)。文件 values.yaml 包含了运行时用于替换 YAML 清单中占位符的键值对。任何打包到图表归档文件中的模板文件都需要放在 templates 目录中。位于 templates 目录中的文件不需要遵循任何命名约定。

以下目录结构展示了一个图表示例。templates 目录包含一个 Pod 和一个 Service 的文件:

$ tree
.
├── Chart.yaml
├── templates
│   ├── web-app-pod-template.yaml
│   └── web-app-service-template.yaml
└── values.yaml

图表文件

文件 Chart.yaml 在高层次描述了图表。必填属性包括图表的 API 版本、名称和版本。此外,还可以提供可选属性。有关属性的完整列表,请参阅相关的文档。示例 4-12 显示了图表文件的最基本内容。

示例 4-12. 一个基本的 Helm 图表文件
apiVersion: 1.0.0
name: web-app
version: 2.5.4

值文件

文件 values.yaml 定义了用于替换 YAML 模板文件中占位符的键值对。示例 4-13 指定了四组键值对。请注意,如果您不希望在运行时替换值,该文件可以为空。

示例 4-13. 一个 Helm values 文件
db_host: mysql-service
db_user: root
db_password: password
service_port: 3000

模板文件

模板文件需要存放在 templates 目录中。模板文件是一个常规的 YAML 清单,可以(可选地)使用双大括号({{ }})定义占位符。要从 values.yaml 文件中引用值,请使用表达式 {{ .Values.<key> }}。例如,要在运行时替换 db_host 键的值,请使用表达式 {{ .Values.db_host }}。 示例 4-14 定义了一个 Pod 作为模板,同时定义了三个引用 values.yaml 中值的占位符。

示例 4-14. Pod 的 YAML 模板清单
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - image: bmuschko/web-app:1.0.1
    name: web-app
    env:
    - name: DB_HOST
      value: {{ .Values.db_host }}
    - name: DB_USER
      value: {{ .Values.db_user }}
    - name: DB_PASSWORD
      value: {{ .Values.db_password }}
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

执行 Helm 命令

Helm 可执行文件配备广泛的命令。我们来演示其中一些。 template 命令在本地渲染图表模板并在控制台显示结果。您可以在以下输出中看到操作的实际效果。所有占位符都已由 values.yaml 文件中的实际值替换:

$ helm template .
---
# Source: Web Application/templates/web-app-service-template.yaml
...
---
# Source: Web Application/templates/web-app-pod-template.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - image: bmuschko/web-app:1.0.1
    name: web-app
    env:
    - name: DB_HOST
      value: mysql-service
    - name: DB_USER
      value: root
    - name: DB_PASSWORD
      value: password
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

一旦您满意结果,您将希望将模板文件打包成图表存档文件。图表存档文件是一个以 .tgz 结尾的压缩 TAR 文件。 package 命令评估 Chart.yaml 中的元数据信息以导出图表存档文件名:

$ helm package .
Successfully packaged chart and saved it to: /Users/bmuschko/dev/projects/ \
cka-study-guide/ch04/templating-tools/helm/web-app-2.5.4.tgz

欲了解所有命令和典型工作流程的完整列表,请参阅 Helm 文档页面

总结

资源边界是 kube-scheduler 算法在决定可以将 Pod 调度到哪个节点时考虑的众多因素之一。容器可以指定资源请求和限制。调度程序根据其可用的硬件容量选择节点。

声明式清单管理是在实际的云原生项目中创建、修改和删除对象的首选方法。底层的 YAML 清单应存入版本控制,并自动跟踪对对象所做的更改,包括其相应提交哈希的时间戳。 kubectl applydelete 命令可以对一个或多个 YAML 清单执行这些操作。

出现了更多方便的清单管理工具。Kustomize 与 kubectl 工具链完全集成。它帮助生成、组合和定制清单。具有模板功能的工具如 yq 和 Helm 可进一步简化管理应用程序堆栈的各种工作流程所需的清单。

考试要点

理解资源边界对调度的影响

由 Pod 定义的容器可以指定资源请求和限制。通过定义单容器和多容器 Pod 的边界,分析能看到在节点上调度对象的效果。此外,练习如何识别节点的可用资源容量。

使用命令式和声明式方法管理对象

YAML 清单对于表达对象的期望状态至关重要。您需要了解如何使用kubectl apply命令创建、更新和删除对象。该命令可以指向单个清单文件或包含多个清单文件的目录。

对常见模板化工具有高级别的理解

Kustomize、yg和 Helm 是管理 YAML 清单的成熟工具。它们的模板功能支持像合成和合并多个清单这样的复杂场景。在考试中,深入了解这些工具、它们的功能以及它们解决的问题。

示例练习

这些练习的解决方案可以在附录中找到。

  1. 为名为ingress-controller的新 Pod 编写清单,其中包含一个使用镜像bitnami/nginx-ingress-controller:1.0.0的容器。对于容器,将内存请求设置为 256Mi,CPU 设置为 1。将内存限制设置为 1024Mi,CPU 设置为 2.5。

  2. 使用清单,在三个节点的集群上安排 Pod 的调度。创建后,确定运行 Pod 的节点。将节点名称写入文件node.txt

  3. 创建名为manifests的目录。在目录中创建两个文件:pod.yamlconfigmap.yamlpod.yaml文件应定义一个名为nginx的 Pod,使用的镜像是nginx:1.21.1configmap.yaml文件定义了一个名为logs-config的 ConfigMap,具有键值对dir=/etc/logs/traffic.log。使用单个声明性命令创建这两个对象。

  4. 修改 ConfigMap 清单,将键dir的值更改为/etc/logs/traffic-log.txt。应用更改。使用单个声明性命令删除两个对象。

  5. 使用 Kustomize 为资源文件pod.yaml设置一个常见命名空间t012。文件pod.yaml定义了名为nginx的 Pod,使用的镜像是nginx:1.21.1,没有命名空间。运行 Kustomize 命令,在控制台上呈现转换后的清单。

第五章:服务和网络

在 Kubernetes 集群中运行的应用很少是孤立的。在微服务架构的星座中,运行在相应 Pod 中的一组应用需要共同工作。Kubernetes 实现了集群内部 Pod 到 Service 的通信,并借助各种网络概念与集群外的选定服务进行通信。CKA 的这个领域专注于服务和网络方面。您需要理解服务和入口的概念,以及启用网络通信的集群配置。

在高层次上,本章涵盖以下概念:

  • Kubernetes 网络基础知识

  • Pod 之间的连通性

  • 服务、服务类型及其端点

  • Ingress 控制器和 Ingress

  • 使用和配置 CoreDNS

  • 选择容器网络接口(CNI)插件

Kubernetes 网络基础知识

Kubernetes 设计为管理分布式数据和计算复杂性的操作系统。工作负载可以被调度到一组节点上以分发负载。Kubernetes 网络模型实现了网络通信,并需满足以下要求:

  1. 容器到容器的通信:运行在同一 Pod 中的容器通常需要彼此通信。同一 Pod 中的容器可以通过 IPC 消息发送、共享文件,并且通常直接通过环回接口使用 localhost 主机名进行通信。由于每个 Pod 分配了一个唯一的虚拟 IP 地址,因此同一 Pod 中的每个容器都共享该上下文和相同的端口空间。

  2. Pod 到 Pod 的通信:一个 Pod 需要能够在同一节点或不同节点上的另一个 Pod 上进行通信,而不需要进行网络地址转换(NAT)。Kubernetes 在每个 Pod 创建时从其节点的 Pod CIDR 范围内分配一个唯一的 IP 地址。该 IP 地址是临时的,因此不能长期稳定。每次 Pod 重新启动时,都会分配一个新的 IP 地址。建议使用 Pod 到 Service 的通信,而不是 Pod 到 Pod 的通信。

  3. Pod 到 Service 的通信:服务为一组 Pod 公开单一且稳定的 DNS 名称,并具有跨 Pod 的请求负载均衡能力。可以从集群内部或外部接收到对服务的流量。

  4. 节点之间的通信:注册到集群的节点可以相互通信。每个节点分配一个节点 IP 地址。

Kubernetes 网络模型的规范称为容器网络接口 (CNI)。实现 CNI 规范的网络插件广泛可用,并可由管理员在 Kubernetes 集群中配置。

容器之间的连通性

单个 Pod 创建的容器共享相同的 IP 地址和端口空间。容器可以使用 localhost 相互通信。在创建需要实现多容器模式的云原生应用程序时尤其有用,如 图 5-1 所示。有关常见多容器设计模式的更多信息,请参阅《Kubernetes Patterns》(O’Reilly)的书籍链接 Kubernetes Patterns

ckas 0501

图 5-1. Pod 中的容器间通信

YAML 清单中的 示例 5-1 创建一个 sidecar 容器,通过 localhost 的 80 端口调用主应用容器:

示例 5-1. 多容器 Pod
apiVersion: v1
kind: Pod
metadata:
  name: multi-container
spec:
  containers:
  - image: nginx
    name: app
    ports:
    - containerPort: 80
  - image: curlimages/curl:7.79.1
    name: sidecar
    args:
    - /bin/sh
    - -c
    - 'while true; do curl localhost:80; sleep 5; done;'

检查 sidecar 容器的日志显示,与主应用程序容器的通信成功:

$ kubectl create -f pod.yaml
pod/multi-container created
$ kubectl logs multi-container -c sidecar
...
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Pod 间的连接

每个 Pod 在创建时分配一个 IP 地址。您可以使用 get pods 命令的 -o wide 选项或描述 Pod 来检查 Pod 的 IP 地址。以下控制台输出中的 Pod 的 IP 地址为 172.17.0.4

$ kubectl run nginx --image=nginx --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   172.17.0.4   minikube   \
<none>           <none>
$ kubectl get pod nginx -o yaml
...
status:
  podIP: 172.17.0.4
...

分配给 Pod 的 IP 地址在所有节点和命名空间中是唯一的。这通过在注册节点时为每个节点分配专用子网来实现。在节点上创建新的 Pod 时,IP 地址是从分配的子网中租借的。这由网络生命周期管理器 kube-proxy 与 DNS 服务和 CNI 共同处理。以下命令通过节点名为 minikube 的属性 spec.podCIDR 查询分配的子网:

$ kubectl get nodes
NAME       STATUS   ROLES                  AGE   VERSION
minikube   Ready    control-plane,master   42d   v1.21.2
$ kubectl get nodes minikube -o json | jq .spec.podCIDR
"172.17.0.0/24"

节点上的 Pod 可以与集群中任何其他节点上运行的所有其他 Pod 通信。图 5-2 展示了使用案例。

ckas 0502

图 5-2. Pod 间通信

您可以通过创建一个临时 Pod,使用命令行工具 curlwget 调用另一个 Pod 的 IP 地址来轻松验证此行为:

$ kubectl run busybox --image=busybox --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 地址。构建微服务架构时,每个应用程序在自己的 Pod 中运行,需要使用稳定的网络接口进行通信,这就需要另一个概念,即 Service。

理解 Services

简而言之,Services 为一组 Pods 提供可发现的名称和负载均衡。借助 Kubernetes DNS 控制平面组件,Services 和 Pods 无需关注 IP 地址。与 Deployment 类似,Service 使用标签选择确定其操作的 Pods。

图 5-3 说明了该功能。Pod 1 接收流量,因为其分配的标签与服务中定义的标签选择匹配。Pod 2 由于定义了不匹配的标签而不接收流量。请注意,可以在不常见的场景中创建没有标签选择器的服务。有关更多信息,请参阅相关的Kubernetes 文档

ckas 0503

图 5-3。基于标签选择的服务流量路由

服务是部署的补充概念。服务将网络流量路由到一组 Pod,而部署管理一组 Pod 副本。尽管您可以单独使用这两个概念,但建议同时使用部署和服务。主要原因是能够扩展副本数,并同时暴露端点以控制网络流量。

服务类型

每个服务都需要定义一个类型。表 5-1 列出了与 CKA 考试相关的服务类型。

表 5-1。服务类型

类型 描述
ClusterIP 在集群内部 IP 上暴露服务。只能从集群内部访问。
NodePort 在每个节点的 IP 地址上以静态端口暴露服务。可从集群外部访问。
LoadBalancer 使用云提供商的负载均衡器外部暴露服务。

其他服务类型可以定义;然而,在本书中我们不会涉及它们,因为它们超出了考试范围。有关更多信息,请参阅Kubernetes 文档

创建服务

您可以通过多种方式创建服务,其中一些方式更有利于 CKA 考试,因为它们提供了快速的反馈。让我们首先讨论命令式方法。

服务需要通过匹配标签选择一个 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 和服务。以下命令在创建正确的标签选择同时创建两个对象。如果要求您创建 Pod 和服务,这个命令行选项在 CKA 考试中节省时间是一个不错的选择:

$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
  --port=8080 --expose
service/echoserver created
pod/echoserver created

实际上更常见的是使用一起工作的部署(Deployment)和服务(Service)。以下一组命令创建了一个带有五个副本的部署,并使用expose deployment命令创建了服务。端口映射可以通过--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

示例 5-2 展示了一个以 YAML 清单形式表示的服务的表示形式。该服务声明了键值app: echoserver以进行标签选择,并定义了将端口 80 映射到 8080 的设置。

示例 5-2. 由 YAML 清单定义的服务
apiVersion: v1
kind: Service
metadata:
  name: echoserver
spec:
  type: ClusterIP
  selector:
    app: echoserver
  ports:
  - port: 80
    targetPort: 8080

列出服务

列出所有服务呈现了一个包含服务类型、集群 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

渲染服务详情

您可能希望钻取服务的详细信息以进行故障排除。如果服务的入站流量未正确路由到您期望处理工作负载的一组 Pod,则可能是这种情况。

describe命令提供有关服务配置的宝贵信息。与排除服务故障相关的配置包括选择器(Selector)、IP、端口(Port)、目标端口(TargetPort)和端点(Endpoints)的值。

查看以下describe命令的输出。这是为由部署(Deployment)控制的五个 Pod 创建的服务的详细信息。端点(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>

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 endpoint 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>

端口映射

服务通过分配的标签选择一组 Pod 来转发流量。成功的网络流量路由还取决于正确的端口映射。在前面的部分中,我们创建了不同的服务并为它们分配了端口。在这里,我们将希望通过使其各个部分更加透明来重新访问端口映射。

图 5-4 显示了一个接受端口 80 入站流量的服务。这是清单中spec.ports[].port属性定义的端口。任何入站流量都将路由到目标端口,该端口由spec.ports[].targetPort表示。目标端口与标签选择的 Pod 内运行的容器定义的端口相同。在这种情况下,即端口 8080。

ckas 0504

图 5-4. 服务端口映射

使用类型为 ClusterIP 的服务访问

ClusterIP 是 Service 的默认类型。它在集群内部 IP 地址上公开 Service。这意味着只能从集群内部运行的 Pod 访问 Service,而不能从集群外部访问(例如,如果您从本地计算机调用 Service)。图 5-5 说明了具有类型 ClusterIP 的 Service 的可访问性。

ckas 0505

图 5-5. 具有类型 ClusterIP 的 Service 的可访问性

我们将创建一个 Pod 和一个相应的 Service 来演示 ClusterIP 的运行时行为。名为 echoserver 的 Pod 公开容器端口 8080,并指定标签 app: echoserver。Service 为传入流量定义了端口 5005,该流量被转发到所选 Pod 的端口 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

使 Service 可用的集群 IP 是 10.96.254.0。列出 Service 还会呈现用于传入流量到 Service 的端口:

$ kubectl get pod,service
NAME             READY   STATUS    RESTARTS   AGE
pod/echoserver   1/1     Running   0          23s

NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/echoserver   ClusterIP   10.96.254.0   <none>        5005/TCP   8s

您无法使用集群 IP 和端口从本地计算机访问 Service,如下所示的 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 访问 Service 可正确将请求路由到匹配标签选择的 Pod:

$ kubectl run tmp --image=busybox --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

使用类型为 NodePort 的 Service 访问 Service

使用类型为 NodePort 的 Service 声明可通过节点 IP 地址访问,并且可以从 Kubernetes 集群外部解析。节点 IP 地址可以与范围在 30000 到 32767 之间的端口号结合使用,该端口在创建 Service 时自动分配。该端口在集群中的每个节点上打开,并且其值在集群范围内是全局且唯一的。为避免端口冲突,最好不定义确切的节点端口,而是让 Kubernetes 找到一个可用端口。请记住,NodePort(大写 N)是 Service 类型,而 nodePort(小写 n)是值的键。图 5-6 说明了通过类型为 NodePort 的 Service 将流量路由到 Pod 的情况。

ckas 0506

图 5-6. 具有类型 NodePort 的 Service 的可访问性

接下来的两个命令创建一个类型为 NodePort 的 Pod 和一个 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

创建后,您可以列出 Pod 和 Service。您会发现端口表示包含使 Service 可访问的静态分配端口。在我们的情况下,那就是端口 30158:

$ kubectl get pod,service
NAME             READY   STATUS    RESTARTS   AGE
pod/echoserver   1/1     Running   0          17s

NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        \
AGE
service/echoserver   NodePort    10.101.184.152   <none>        5005:30158/TCP \
5s

从集群内部,您仍然可以使用集群 IP 地址和端口号访问 Service。这些 Service 公开的行为与其类型为 ClusterIP 时完全相同:

$ kubectl run tmp --image=busybox --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

从集群外部,您需要使用运行 Pod 的节点的 IP 地址和静态分配的端口。确定节点 IP 地址的一种方法是通过 kubectl cluster-info 或查询 Pod。

在 Minikube 中确定 Service URL

Minikube 提供了一个快捷方式,可以通过命令 minikube service --url <service-name> 确定服务的端点。更多信息,请参阅 Minikube 文档

这里的节点 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 类型访问服务

Kubernetes 云提供商支持通过类型 LoadBalancer 从预先存在的外部负载均衡器配置到服务的配置。此服务类型暴露一个单一 IP 地址,将传入请求分发到集群节点。负载均衡策略的实现(例如轮询)由云提供商决定。图 5-7 展示了架构概览。

ckas 0507

图 5-7. 使用 LoadBalancer 类型访问服务的可访问性

在 Minikube 中设置网络路由

Minikube 不是 Kubernetes 的云提供商实现;然而,你可以配置网络路由到服务以尝试负载均衡器功能。你只需要在单独的 shell 中运行命令 minikube tunnel。更多信息,请参阅 Minikube 文档

要创建一个作为负载均衡器的服务,请在清单中将类型设置为 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 的服务暴露一个外部 IP 地址。列出服务以显示外部 IP 地址,在下面的输出中是 10.109.76.157

$ kubectl get pod,service
NAME             READY   STATUS    RESTARTS   AGE
pod/echoserver   1/1     Running   0          9s

NAME                 TYPE           CLUSTER-IP      EXTERNAL-IP   \
PORT(S)          AGE
service/echoserver   LoadBalancer   10.109.76.157   10.109.76.157 \
5005:30642/TCP   5s

要从集群外部调用服务,请使用外部 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’
...

理解 Ingress

标准的 Kubernetes Ingress 解决方案仅在第 7 层(HTTP 或 HTTPS 流量)提供负载均衡,并将事务从集群外部路由到集群内的服务,如 图 5-8 所示。这不是一个特定的服务类型,也不应与服务类型 LoadBalancer 混淆。

ckas 0508

图 5-8. 通过 HTTP(S) 管理对服务的外部访问

没有 Ingress 控制器,Ingress 无法工作。Ingress 控制器评估由 Ingress 定义的规则集,确定流量路由。一个生产级别的 Ingress 控制器示例是 F5 NGINX Ingress ControllerAKS Application Gateway Ingress Controller。你可以在 Kubernetes 文档 中找到其他选项。

如果你正在使用 Minikube,必须显式启用 Ingress 功能。Ingress 控制器作为 ingress-nginx 命名空间中的一个 Pod 运行:

$ minikube addons enable ingress
$ kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-controller-59b45fb494-xpfzn   0/1     Running     0          14s

网络规则

Ingress 对象定义的规则遵循 表 5-2 中列出的三个标准。

表 5-2. Ingress 规则

类型 示例 描述
可选主机 mycompany.abc.com 如果提供了主机,则规则适用于该主机。如果未定义主机,则处理所有入站 HTTP(S)流量。
路径列表 /corellian/api 入站流量必须匹配主机和路径,以正确转发流量到 Service。
后端 corellian:8080 Service 名称和端口的组合。

创建 Ingress

您可以使用create ingress命令创建 Ingress。您需要提供的主要命令行选项是--rule,它以逗号分隔的方式定义规则。每个键值对的表示法是<host>/<path>=<service>:<port>。如果查看create ingress --help命令的输出,可以指定更详细的规则:

$ kubectl create ingress corellian \
  --rule="star-alliance.com/corellian/api=corellian:8080"
ingress.networking.k8s.io/corellian created
提示

对于 HTTP 流量,端口 80 是隐含的,因为我们没有指定 TLS Secret 对象的引用。如果您在规则定义中指定了tls=mysecret,那么端口 443 也将在此处列出。有关启用 HTTPS 流量的更多信息,请参阅Kubernetes 文档

作为 YAML 清单定义的相同 Ingress 在示例 5-3 中显示。

示例 5-3. 由 YAML 清单定义的 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: corellian
spec:
  rules:
  - host: star-alliance.com
    http:
      paths:
      - backend:
          service:
            name: corellian
            port:
              number: 8080
        path: /corellian/api
        pathType: Exact

定义路径类型

前面的 YAML 清单演示了通过属性spec.rules[].host[].http.paths[].pathType指定路径类型的选项之一。路径类型定义了如何评估传入请求与声明的路径的匹配。表 5-3 应该为您提供了关于传入请求及其路径评估的指示。有关更全面的列表,请参阅Kubernetes 文档

表 5-3. Ingress 路径类型

路径类型 规则 入站请求
Exact /corellian/api 匹配/corellian/api但不匹配/corellian/test/corellian/api/
Prefix /corellian/api 匹配/corellian/api/corellian/api/但不匹配/corellian/test

列出 Ingress

通过执行get ingress命令可以列出 Ingress。您将看到创建 Ingress 时指定的一些信息(例如,主机):

$ kubectl get ingress
NAME        CLASS    HOSTS               ADDRESS         PORTS   AGE
corellian   <none>   star-alliance.com   192.168.64.15   80      10m

呈现 Ingress 详细信息

使用describe ingress命令可以呈现 Ingress 的详细信息。每个规则都在表中列出。为了故障排除,请注意额外的消息。在以下输出中,您可以看到我们在这里映射的名为corellian的 Service 不存在。此外,事件日志显示 Ingress 控制器通过同步活动调整规则:

$ kubectl describe ingress corellian
Name:             corellian
Namespace:        default
Address:
Default backend:  default-http-backend:80 (<error: endpoints \
                  "default-http-backend" not found>)
Rules:
  Host               Path  Backends
  ----               ----  --------
  star-alliance.com
                     /corellian/api   corellian:8080 (<error: \
                     endpoints "corellian" not found>)
Annotations:         <none>
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  Sync    13s   nginx-ingress-controller  Scheduled for sync

使用服务和 Pod 提供请求时,这里是 Ingress 的详细信息:

$ kubectl run corellian --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
  --port=8080 -l app=corellian
pod/corellian created
$ kubectl create service clusterip corellian --tcp=8080:8080
service/corellian created
$ kubectl describe ingress corellian
Name:             corellian
Namespace:        default
Address:          192.168.64.15
Default backend:  default-http-backend:80 (<error: \
                  endpoints "default-http-backend" not found>)
Rules:
  Host               Path  Backends
  ----               ----  --------
  star-alliance.com
                     /corellian/api   corellian:8080 (172.17.0.5:8080)
Annotations:         <none>
Events:              <none>

访问 Ingress

后端与路径路由组合,通过 Ingress 将传入的 HTTP(S) 流量传播到配置的服务。要在本地 Kubernetes 集群上测试行为,首先需要找出 Ingress 使用的负载均衡器的 IP 地址。接下来,您需要将 IP 地址添加到您的 /etc/hosts 文件中:

$ kubectl get ingress corellian \
  --output=jsonpath="{.status.loadBalancer.ingress[0][*ip*]}"
192.168.64.15
$ sudo vim /etc/hosts
...
192.168.64.15   star-alliance.com

您现在可以向后端发送 HTTP 请求。第一个调用匹配 Exact 路径规则。第二个调用因路径规则不匹配而未通过:

$ wget star-alliance.com/corellian/api --timeout=5 --tries=1
--2021-11-30 19:34:57--  http://star-alliance.com/corellian/api
Resolving star-alliance.com (star-alliance.com)... 192.168.64.15
Connecting to star-alliance.com (star-alliance.com)|192.168.64.15|:80... \
connected.
HTTP request sent, awaiting response... 200 OK
...
$ wget star-alliance.com/corellian/api/ --timeout=5 --tries=1
--2021-11-30 15:36:26--  http://star-alliance.com/corellian/api/
Resolving star-alliance.com (star-alliance.com)... 192.168.64.15
Connecting to star-alliance.com (star-alliance.com)|192.168.64.15|:80... \
connected.
HTTP request sent, awaiting response... 404 Not Found
2021-11-30 15:36:26 ERROR 404: Not Found.

使用和配置 CoreDNS

Kubernetes 专注于操作微服务架构。各个微服务提供独特的、自包含的功能,并相互通信以相互补充。本章前面讨论了使用服务提供稳定网络接口的用法。许多示例使用 IP 地址和端口与服务进行通信。

Kubernetes 运行名为 CoreDNS 的 DNS 服务器实现,将服务的名称映射到其 IP 地址。反过来,微服务可以轻松地通过服务名称相互引用。要深入了解 CoreDNS,请查阅优秀的书籍 Learning CoreDNS(O’Reilly)。

检查 CoreDNS Pod

CoreDNS 服务器在命名空间 kube-system 中的 Pod 中运行。以下命令为 Minikube 集群安装渲染 CoreDNS Pod:

$ kubectl get pods -n kube-system
NAME                               READY   STATUS    RESTARTS   AGE
coredns-558bd4d5db-s89vn           1/1     Running   2          64d

CoreDNS 使用所谓的 Corefile 配置 DNS 服务器的运行时行为。在同一命名空间设置的名为 coredns 的 ConfigMap 定义配置文件的内容。CoreDNS Pod 挂载 ConfigMap,如 示例 5-4 所示。

示例 5-4. CoreDNS Pod 的 YAML 清单
apiVersion: v1
kind: Pod
metadata:
  name: coredns-558bd4d5db-s89vn
  namespace: kube-system
...
spec:
  containers:
  - name: coredns
    image: k8s.gcr.io/coredns/coredns:v1.8.0
    volumeMounts:
    - mountPath: /etc/coredns
      name: config-volume
      readOnly: true
  volumes:
  - configMap:
      defaultMode: 420
      items:
      - key: Corefile
        path: Corefile
      name: coredns
    name: config-volume
...

检查 CoreDNS 配置

您可以使用命令 kubectl get configmaps coredns -n kube-system -o yaml 检查 ConfigMap。示例 5-5 显示了 Corefile 的内容。请查看 CoreDNS 手册 获取有关 Corefile 语法和指令的更多信息。

示例 5-5. 由 ConfigMap 定义的默认 Corefile
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        hosts {
           192.168.64.1 host.minikube.internal
           fallthrough
        }
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
...

定制 CoreDNS 配置

Corefile 的默认配置可以进一步定制。为此,在命名空间 kube-system 中创建一个新的 ConfigMap,格式如 示例 5-6 所示。

示例 5-6. 由 ConfigMap 定义的自定义 Corefile
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns-custom
  namespace: kube-system
data:
  ...

创建由文件 coredsns-custom.yaml 指定的新 ConfigMap,并通过删除它强制重新加载 CoreDNS Pod 配置。CoreDNS 将会重新启动,因为 CoreDNS Pod 是通过 Deployment 的状态部署和管理的。Pod 的重启策略默认为 Always;删除 Pod 是强制其重新启动的快速方法,新的 Pod 实例将映射到 ConfigMap 中更新的信息:

$ kubectl apply -f coredsns-custom.yaml
$ kubectl delete pod coredns -n kube-system

DNS 服务

服务与 CoreDNS 提供的 DNS 服务紧密相关。在本节中,我们将讨论几种场景,说明如何从同一或不同命名空间中的另一个 Pod 访问服务的情况。

从同一命名空间通过主机名解析服务的示例

如果两个对象位于同一个命名空间,一个 Pod 可以通过主机名解析服务。图 5-9 展示了一个实现 UI 前端功能的 Pod 如何通过服务调用后端微服务的示例。

ckas 0509

图 5-9. 从同一命名空间解析服务

使用以下命令可以轻松验证其行为。在此,我们将在名为dns的命名空间中创建一个服务和一个名为echoserver的 Pod:

$ kubectl create namespace dns
namespace/dns created
$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
  --port=8080 --expose -n dns
service/echoserver created
pod/echoserver created
$ kubectl get services,pods -n dns
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/echoserver   ClusterIP   10.99.124.240   <none>        8080/TCP   17m

NAME             READY   STATUS    RESTARTS   AGE
pod/echoserver   1/1     Running   0          17m

你可以通过在同一命名空间中运行一个 Pod,并通过其主机名和入口端口对服务发起调用来验证正确的服务发现:

$ kubectl run busybox --image=busybox --rm -it --restart=Never -n dns \
  -- wget echoserver:8080
Connecting to echoserver:8080 (10.99.124.240:8080)
saving to 'index.html'
index.html           100% |********************************|   406  0:00:00 ETA
'index.html' saved
pod "busybox" deleted

从不同命名空间通过主机名解析服务

在 Pod 向不同命名空间中的服务发起调用并不罕见。仅引用服务的主机名无法跨命名空间工作。你需要同时附加命名空间。

图 5-10 展示了在名为business的命名空间中的后端 Pod 调用了在命名空间other中的服务的示例。为了从business命名空间访问weather-api服务,你需要通过weather-api.other进行引用。位于default命名空间的服务需要相应地进行引用(例如,lottery.default)。

ckas 0510

图 5-10. 从不同命名空间解析服务
$ kubectl create namespace other
namespace/other created
$ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
  --port=8080 --expose -n other
service/echoserver created
pod/echoserver created
$ kubectl get services,pods -n other
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/echoserver   ClusterIP   10.99.124.240   <none>        8080/TCP   17m

NAME             READY   STATUS    RESTARTS   AGE
pod/echoserver   1/1     Running   0          17m

在不同命名空间,例如称为business的情况下,从临时 Pod 向命名空间other中的服务发起调用。如果不指定命名空间,尝试调用服务将失败连接。在下面的代码中可以看到,服务的命名空间需要明确指定为echoserver.other

$ kubectl create namespace business
namespace/business created
$ kubectl run busybox --image=busybox --rm -it --restart=Never -n business \
  -- wget echoserver:8080
wget: bad address 'echoserver:8080'
pod "busybox" deleted
pod other/busybox terminated (Error)
$ kubectl run busybox --image=busybox --rm -it --restart=Never -n business \
  -- wget echoserver.other:8080
Connecting to echoserver.other:8080 (10.99.32.59:8080)
saving to 'index.html'
index.html           100% |********************************|   418  0:00:00 ETA
'index.html' saved
pod "busybox" deleted

Corefile定义了一个集群域。默认情况下,集群域的值是cluster.local。在引用服务时,你可以在主机名后附加集群域。此外,你需要使用你要通信的对象类型。字符串svc描述了服务类型。一个服务的完整主机名为echoserver.other.svc.cluster.local。你可以在以下命令中看到这些调用:

$ kubectl run busybox --image=busybox --rm -it --restart=Never -n business \
  -- wget echoserver.other.svc:8080
Connecting to echoserver.other.svc:8080 (10.99.32.59:8080)
saving to 'index.html'
index.html           100% |********************************|   426  0:00:00 ETA
'index.html' saved
pod "busybox" deleted
$ kubectl run busybox --image=busybox --rm -it --restart=Never -n business \
  -- wget echoserver.other.svc.cluster.local:8080
Connecting to echoserver.other.svc.cluster.local:8080 (10.99.32.59:8080)
saving to 'index.html'
index.html           100% |********************************|   454  0:00:00 ETA
'index.html' saved
pod "busybox" deleted

Pods 的 DNS

Pods 可以通过 IP 地址跨命名空间进行通信。CoreDNS 在Corefile文件中提供了pods insecure的配置选项,用于为 Pods 创建 DNS 记录。要引用一个 Pod,请使用 IP 地址,但将点替换为破折号。例如,具有 IP 地址10.0.0.85的 Pod 具有相应的 DNS 记录10-0-0-85。图 5-11](#dns_pod)展示了通过它们的 DNS 记录相互引用的 Pods。

ckas 0511

图 5-11. 通过 DNS 记录解析 Pod

通过主机名解析 Pod

我们将创建两个 Pod 来演示 CoreDNS 在 Pod 中的运行时行为。Pod 1 名为echoserver1,运行在命名空间ns1中,IP 地址为172.17.0.8。Pod 2 名为echoserver2,运行在命名空间ns2中,IP 地址为172.17.0.9

$ kubectl create namespace ns1
namespace/ns1 created
$ kubectl create namespace ns2
namespace/ns2 created
$ kubectl run echoserver1 --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
  --port=8080 -n ns1
pod/echoserver1 created
$ kubectl run echoserver2 --image=k8s.gcr.io/echoserver:1.10 --restart=Never \
  --port=8080 -n ns2
pod/echoserver2 created
$ kubectl get pod echoserver1 -n ns1 --template={{.status.podIP}}
172.17.0.8
$ kubectl get pod echoserver2 -n ns2 --template={{.status.podIP}}
172.17.0.9

要通过 DNS 解析 Pod,您需要在主机名中明确指定命名空间和对象类型,无论 Pod 位于何处。以下两个命令使用临时 Pod 来调用同一命名空间内和不同命名空间内的另一个 Pod。添加cluster.local是可选的:

$ kubectl run busybox --image=busybox --rm -it --restart=Never -n ns1 \
  -- wget 172-17-0-8.ns1.pod:8080
Connecting to 172-17-0-8.ns1.pod:8080 (172.17.0.8:8080)
saving to 'index.html'
index.html           100% |********************************|   424  0:00:00 ETA
'index.html' saved
pod "busybox" deleted
$ kubectl run busybox --image=busybox --rm -it --restart=Never -n ns1 \
  -- wget 172-17-0-9.ns2.pod:8080
Connecting to 172-17-0-9.ns2.pod:8080 (172.17.0.9:8080)
saving to 'index.html'
index.html           100% |********************************|   424  0:00:00 ETA
'index.html' saved
pod "busybox" deleted

这个选项pods insecure仅出于与kube-dns的向后兼容性原因而存在,kube-dns 是 Kubernetes DNS 服务器的原始实现。您可以通过使用pods disabled配置 CoreDNS 来禁用为 Pod 创建 DNS 记录。不建议依赖 Pod 的 DNS 记录。

选择合适的容器网络接口插件

在第二章中,我们讨论了安装容器网络接口插件的机制。CNI 是一个 Cloud Native Computing Foundation 项目,包括一个用于在 Linux 容器中配置网络接口的规范和库,以及多个插件。CNI 只关注容器的网络连接性,并在删除容器时删除分配的资源。Kubernetes 使用 CNI 作为网络提供程序与 Kubernetes Pod 网络之间的接口。

CNI 规范定义了 Pod 网络接口和能力。插件实现规范,并允许 Kubernetes 管理员选择和使用产品特定的功能集。图 5-12 展示了您可以选择的一些示例插件。有关插件列表,请参见Kubernetes 文档

ckas 0512

图 5-12. CNI 规范和实现

选择适合的 CNI 插件取决于您的需求。参考这篇博客文章的决策矩阵,可以帮助您为您的 Kubernetes 集群做出正确的选择。在 CKA 考试期间,您可能会被要求安装 CNI 插件。Kubernetes 文档中提到的许多插件链接到 Kubernetes 域外的网页。请记住,在考试期间不允许访问官方 Kubernetes 文档之外的页面。在打开链接之前,请将鼠标指针悬停在链接上查看浏览器中的链接。例如,这个Kubernetes 文档部分描述了 Weave Net 的安装说明。

总结

Kubernetes 为集群中的每个 Pod 分配唯一的 IP 地址。Pod 可以使用该 IP 地址相互通信;但是,您不能依赖 IP 地址随时间稳定不变。这就是为什么 Kubernetes 提供了 Service 资源类型的原因。

服务基于标签选择和端口映射将网络流量转发到一组 Pod。每个服务都需要分配一个类型,以确定服务如何在集群内部或外部可访问。在 CKA 考试中相关的服务类型有ClusterIPNodePortLoadBalancer。CoreDNS 是 Kubernetes 的 DNS 服务器,允许 Pod 通过主机名从同一及其他命名空间访问服务。

资源类型入口(Ingress)定义了将传入的、集群外的 HTTP(S)流量路由到服务的规则。入口控制器定期评估这些规则,并确保它们适用于集群。

考试要点

理解服务的目的

通过它们的 IP 地址进行 Pod 对 Pod 通信不能保证稳定的网络接口。服务的目的是提供稳定的网络接口,以便您可以操作在 Kubernetes 集群中运行的复杂微服务架构。在大多数情况下,Pod 通过 CoreDNS 提供的主机名调用服务。

练习如何访问每种类型的服务

CKA 考试要求您理解服务类型ClusterIPNodePortLoadBalancer之间的区别。根据分配的类型,服务可以从集群内部或外部访问。

理解服务和入口之间的区别

入口(Ingress)不应与服务混淆。入口用于基于可选主机名和必需路径将集群外 HTTP(S)流量路由到一个或多个服务。服务则将流量路由到一组 Pod。

示例练习

这些练习的解决方案可以在附录中找到(Appendix)。

  1. 在命名空间external中,创建一个名为nginx的部署,使用nginx镜像,并配置三个副本。容器应该暴露端口 80。在同一命名空间内,创建一个类型为LoadBalancer的服务。该服务应将流量路由到部署管理的 Pod。

  2. 从本地机器(集群外)使用wgetcurl调用负载均衡器。通过查看日志,确认哪些 Pod 接收到了流量。

  3. 将服务类型更改为ClusterIP。使用wgetcurl调用服务,以便 Pod 接收流量。

  4. 在命名空间external中创建一个名为incoming的入口(Ingress)。定义路径类型Prefix,将路径/映射到前面步骤的服务。该入口应能够处理任何传入的 HTTP 流量。

  5. 从本地机器使用wgetcurl调用该入口。验证 Pod 是否收到流量。

  6. 在命名空间external中创建一个名为echoserver的新服务,类型为ClusterIP。选定和将要创建的 Pod 应使用k8s.gcr.io/echoserver:1.10镜像,端口 8080。向现有的入口添加一个新规则,将流量路由到echoserver服务,路径为/echo,类型为Exact

  7. 使用本地机器上的wgetcurl来调用服务,以便可以访问echoserver

  8. 为 CoreDNS 配置创建一个重写规则,允许使用集群域名cka.example.com引用一个服务。确保自定义的 CoreDNS 配置生效。

  9. 在名为hello的新命名空间中,从临时 Pod 使用wgetcurl来调用nginx服务,并确保使用适当的主机名。

第六章:存储

当容器镜像实例化为容器时,容器需要上下文——CPU、内存和 I/O 资源的上下文。Pod 为其内部的容器提供网络和文件系统上下文。网络以 Pod 的虚拟 IP 地址提供,并且文件系统挂载到主机节点的文件系统上。在容器内运行的应用程序可以作为 Pod 上下文的一部分与文件系统交互。容器的临时文件系统与任何其他容器或 Pod 隔离,并且在 Pod 重新启动后不会持久化。CKA 课程的“存储”部分处理了 Kubernetes 中负责在容器或 Pod 重新启动后持久化数据的技术抽象。

卷是 Kubernetes 能力,可在 Pod 重新启动后持久化数据。本质上,卷是可以在 Pod 的多个容器之间共享的目录。您将学习不同卷类型的知识以及定义和挂载容器中卷的过程。

持久卷是卷更广泛概念中的一种特定类别。持久卷的机制稍微复杂一些。持久卷是将数据持久保存到底层物理存储的资源。持久卷声明代表着 Pod 与负责请求存储的持久卷之间的连接资源。最后,Pod 需要声明持久卷并将其挂载到容器内运行的目录路径上。

在高层次上,本章涵盖以下概念:

  • 持久卷

  • 静态与动态提供持久卷

  • 存储类

  • 持久卷的配置选项

  • 持久卷声明

  • 在 Pod 中挂载持久卷

理解卷

在容器中运行的应用程序可以使用临时文件系统读写文件。如果容器崩溃或集群/节点重新启动,则 kubelet 将重新启动容器。写入临时文件系统的任何数据都将丢失,无法再检索。容器实际上会再次以干净的状态启动。

有许多情况需要在容器中挂载卷。其中最突出的用例之一是多容器 Pods,它们使用卷在主应用容器和 sidecar 之间交换数据。图 6-1 展示了容器的临时文件系统与使用卷的差异。

ckas 0601

图 6-1. 使用临时文件系统与使用卷的容器

卷类型

每个卷都需要定义一个类型。类型确定支持卷的介质及其运行时行为。Kubernetes 文档提供了一个长列表的卷类型。例如,azureDiskawsElasticBlockStoregcePersistentDisk 等类型仅在特定云提供商的 Kubernetes 集群中可用。表 6-1 显示了我认为在考试中最相关的卷类型的简化列表。

表 6-1. 考试相关的卷类型

类型 描述
emptyDir Pod 中具有读写访问权限的空目录。仅在 Pod 的生命周期内保留。用于缓存实现或容器间数据交换的良好选择。
hostPath 主机节点文件系统中的文件或目录。
configMap, secret 提供注入配置数据的方式。有关实际示例,请参见 “定义和使用配置数据”。
nfs 现有的网络文件系统(NFS)共享。在 Pod 重新启动后保留数据。
persistentVolumeClaim 请求持久卷。有关更多信息,请参见 “创建持久卷声明”。

创建和访问卷

为 Pod 定义卷需要两个步骤。首先,您需要使用属性 spec.volumes[] 声明卷本身。作为定义的一部分,您提供名称和类型。然而,仅仅声明卷是不够的。其次,需要通过 spec.containers.volumeMounts[] 将卷挂载到消费容器的路径上。卷和卷挂载之间的映射是通过匹配的名称完成的。

从存储在文件 pod-with-volume.yaml 中的 YAML 清单,并显示在 示例 6-1 中,您可以看到类型为 emptyDir 的卷的定义。该卷已经挂载到名为 nginx 的容器内部的路径 /var/logs

示例 6-1. 定义和挂载卷的 Pod
apiVersion: v1
kind: Pod
metadata:
  name: business-app
spec:
  volumes:
  - name: logs-volume
    emptyDir: {}
  containers:
  - image: nginx
    name: nginx
    volumeMounts:
    - mountPath: /var/logs
      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/logs
# pwd
/var/logs
# ls
# touch app-logs.txt
# ls
app-logs.txt

理解持久卷

存储在卷上的数据会在容器重新启动后继续存在。在许多应用程序中,数据的生命周期远远超过了应用程序、容器、Pod、节点甚至集群本身的生命周期。数据持久性确保了数据的生命周期与集群资源的生命周期分离。典型的例子是由数据库持久化的数据。这是持久卷的责任。Kubernetes 使用两个基本构建块来持久化数据:持久卷(PersistentVolume)和持久卷声明(PersistentVolumeClaim)。

持久卷是 Kubernetes 集群中的存储设备。它与 Pod 完全解耦,因此具有自己的生命周期。该对象捕获存储的来源(例如,由云提供商提供的存储)。持久卷可以由 Kubernetes 管理员提供,也可以通过映射到存储类动态分配。

持久卷声明请求持久卷的资源,例如存储大小和访问类型。在 Pod 中,您将使用类型persistentVolumeClaim通过持久卷声明来挂载抽象的持久卷。

图 6-2 显示了 Pod、持久卷声明和持久卷之间的关系。

ckas 0602

图 6-2. 从 Pod 中索取持久卷

静态 vs. 动态配置

持久卷可以静态或动态创建。如果选择静态方法,则需要首先创建存储设备,并通过显式创建PersistentVolume对象来引用它。动态方法不需要您创建PersistentVolume对象。它将通过设置存储类名称使用属性spec.storageClassNamePersistentVolumeClaim自动创建。

存储类是一个抽象概念,定义了用于不同应用程序类型的存储设备类别(例如,性能慢或快的存储)。Kubernetes 管理员的工作是设置存储类。有关存储类的更深入讨论,请参见“理解存储类”。现在,我们将专注于持久卷的静态配置。

创建持久卷

当您自己创建一个持久卷对象时,我们将这种方法称为静态配置。持久卷只能通过先创建清单的方法来创建。此时,kubectl不允许使用create命令创建持久卷。每个持久卷都需要使用spec.capacity定义存储容量,并通过spec.accessModes设置访问模式。有关持久卷可用配置选项的更多信息,请参见“持久卷的配置选项”。

示例 6-2 创建了一个名为db-pv的持久卷,存储容量为 1Gi,由单个节点具有读/写访问权限。属性hostPath将主机节点文件系统中的目录/data/db挂载到持久卷上。我们将将 YAML 清单存储在文件db-pv.yaml中。

示例 6-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设置卷模式。表 6-2 显示了所有可用的卷模式。

表 6-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 挂载且为读写模式,或者卷为只读但可以同时从不同节点访问。表 6-3 提供了可用访问模式的高级概述。访问模式的简写通常在特定命令的输出中显示,例如get pvdescribe pv

表 6-3. 持久卷访问模式

类型 简写形式 描述
ReadWriteOnce RWO 单节点读写访问
ReadOnlyMany ROX 多节点只读访问
ReadWriteMany RWX 多节点读写访问
ReadWriteOncePod RWOP 单 Pod 挂载的读写访问

以下命令解析名为db-pv的持久卷的访问模式。如您所见,返回的值是一个数组,强调您可以一次分配多个访问模式:

$ kubectl get pv db-pv -o jsonpath='{.spec.accessModes}'
["ReadWriteOnce"]

回收策略

可选地,您还可以为持久卷定义回收策略。回收策略指定在绑定的持久卷索取声明被删除时持久卷对象应该发生什么变化(参见表 6-4)。对于动态创建的持久卷,可以通过存储类中的属性.reclaimPolicy来设置回收策略。对于静态创建的持久卷,请使用持久卷定义中的属性spec.persistentVolumeReclaimPolicy

表 6-4. 持久卷回收策略

类型 描述
Retain 默认选项。当持久卷索取声明被删除时,持久卷会被“释放”,可以被重新获取。
Delete 删除会移除持久卷及其关联存储。
Recycle 此值已弃用。您应该使用上述值之一。

以下命令检索名为db-pv的持久卷的分配回收策略:

$ kubectl get pv db-pv -o jsonpath='{.spec.persistentVolumeReclaimPolicy}'
Retain

创建持久卷索取声明

我们接下来需要创建的对象是持久卷索取声明。它的目的是将持久卷绑定到 Pod 上。让我们看一下存储在文件db-pvc.yaml中的 YAML 清单,如示例 6-3 所示。

Example 6-3. 持久卷索取声明的定义
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: db-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ""
  resources:
    requests:
      storage: 256Mi

我们在这里要表达的是,“给我一个可以满足资源请求为 256Mi 并提供访问模式ReadWriteOnce的持久卷。”如果不希望静态配置自动分配默认存储类,请将属性spec.storageClassName设置为空字符串。根据这些条件,适当的持久卷的绑定会自动发生。

创建持久卷索取声明后,状态被设置为Bound,这意味着成功绑定到了持久卷。一旦关联绑定发生,其他内容就不能再绑定到它。绑定关系是一对一的。一旦索取,其他内容就不能再绑定到持久卷。以下get命令使用了短格式pvc而不是persistentvolumeclaim

$ 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

持久卷尚未被 Pod 挂载。因此,检查对象的详细信息显示为<none>。使用describe命令是验证持久卷索取声明是否被正确挂载的好方法:

$ kubectl describe pvc db-pvc
...
Used By:       <none>
...

在 Pod 中挂载持久卷索取声明

唯一剩下的是在希望使用它的 Pod 中挂载持久卷索取声明。您已经学会了如何在 Pod 中挂载卷。这里在示例 6-4 中显示的主要区别是使用了spec.volumes[].persistentVolumeClaim并提供了持久卷索取声明的名称。

示例 6-4. 引用持久卷索取声明的 Pod
apiVersion: v1
kind: Pod
metadata:
  name: app-consuming-pvc
spec:
  volumes:
    - name: app-storage
      persistentVolumeClaim:
        claimName: db-pvc
  containers:
  - image: alpine
    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
...

现在可以打开 Pod 的交互式 shell。导航到 /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 清单声明式地创建存储类。最低限度,您需要声明提供者。如果未在创建时提供,所有其他属性均使用默认值。大多数提供者允许您设置特定于存储类型的参数。示例 6-5 定义了一个由提供者 kubernetes.io/gce-pd 标识的 Google Compute Engine 存储类。

示例 6-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
...

使用存储类

在创建 PersistentVolumeClaim 时,动态配置持久卷需要分配存储类。示例 6-6 展示了使用属性 spec.storageClassName 来分配名为 standard 的存储类。

示例 6-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

将 PersistentVolumeClaim 从 Pod 挂载的步骤与静态和动态提供相同。有关更多信息,请参阅 “在 Pod 中挂载 PersistentVolumeClaim”。

总结

容器将数据存储在临时文件系统中,每次启动新的 Pod 时都会清空。应用程序开发人员需要将数据持久保存超出容器、Pod、节点和集群的生命周期。典型示例包括持久日志文件或数据库中的数据。

Kubernetes 提供了卷的概念以实现用例。一个 Pod 将一个卷挂载到容器中的路径上。写入挂载存储的任何数据将在容器重新启动后持久保存。Kubernetes 提供了广泛的卷类型以满足不同的需求。

PersistentVolumes 甚至在 Pod 或集群/节点重新启动后仍然存储数据。这些对象与 Pod 的生命周期解耦,因此由 Kubernetes 原语表示。PersistentVolumeClaim 抽象了 PersistentVolume 的底层实现细节,并充当 Pod 和 PersistentVolume 之间的中介。可以通过创建对象静态或通过分配给存储类的提供程序动态地提供 PersistentVolume。

考试要点

了解卷的需求和用例

许多在云原生环境中运行的生产就绪应用程序堆栈需要持久保存数据。阅读常见用例并探索描述典型场景的配方。您可以在 O’Reilly 书籍Kubernetes PatternsKubernetes Best PracticesCloud Native DevOps with Kubernetes中找到一些示例。

练习定义和使用卷

卷是考试中不同领域中应用的一个横切概念。了解在哪里找到定义卷的相关文档以及从容器消耗卷的多种方式。一定要深入阅读“定义和消耗配置数据”,详细了解如何将 ConfigMaps 和 Secrets 挂载为卷。

内化定义和消耗 PersistentVolume 的机制

创建 PersistentVolume 涉及几个移动部件。了解 PersistentVolumes 和 PersistentVolumeClaims 的配置选项及其如何协同工作。尝试模拟阻止成功绑定 PersistentVolumeClaim 的情况。然后通过采取对策来解决问题。在考试期间掌握pvpvc的简短命令,以节省宝贵的时间。

了解静态和动态提供 PersistentVolume 的差异

可以通过使用create命令从 YAML 清单静态创建 PersistentVolume 对象来创建 PersistentVolume。或者,您可以让 Kubernetes 动态地为您创建 PersistentVolume,而无需直接参与。为此,将存储类分配给 PersistentVolumeClaim。存储类的提供程序负责为您创建 PersistentVolume 对象。

示例练习

这些练习的解决方案可以在附录中找到。

  1. 创建一个名为logs-pv的 PersistentVolume,映射到hostPath/tmp/logs。访问模式应为ReadWriteOnceReadOnlyMany。配置存储容量为 2Gi。分配回收策略为Delete,存储类为空字符串。确保 PersistentVolume 的状态显示为Available

  2. 创建一个名为logs-pvc的 PersistentVolumeClaim。使用的访问模式是ReadWriteOnce。请求容量为 1Gi。确保 PersistentVolume 的状态显示为Bound

  3. 将 PersistentVolumeClaim 挂载到运行nginx镜像的 Pod 中,挂载路径为/var/log/nginx

  4. 打开容器的交互式 shell,并在/var/log/nginx中创建一个名为my-nginx.log的新文件。退出 Pod。

  5. 删除 Pod 和 PersistentVolumeClaim。PersistentVolume 会发生什么?

  6. 列出可用的存储类,并确定默认存储类。注意提供程序。

  7. 创建一个名为custom的新存储类,使用默认存储类的提供程序。

  8. 创建一个名为custom-pvc的 PersistentVolumeClaim。请求容量为 500Mi,并声明访问模式为ReadWriteOnce。分配存储类名称为custom

  9. PersistentVolume 应该是动态配置的。找出名称并将其写入名为pv-name.txt的文件中。

  10. 删除 PersistentVolumeClaim。PersistentVolume 会发生什么?

第七章:故障排除

建立 Kubernetes 集群是一回事。确保集群保持运行是另一回事。作为 Kubernetes 管理员,您不断面临确保集群保持功能性的挑战。因此,您的故障排除技能必须精湛,以便能够提出识别问题根本原因和修复问题的策略。

在考试涵盖的所有领域中,“故障排除”部分对总体分数的影响最大,因此理解故障场景并学会如何解决它们至关重要。本章将讨论如何在不同情况下监视和故障排除应用程序。此外,我们将讨论可能由于错误配置或错误条件而导致集群组件失败的情况。

从高层次来看,本章涵盖了以下概念:

  • 评估日志记录选项

  • 监控应用程序

  • 访问容器日志

  • 故障排除应用程序故障

  • 故障排除集群故障

评估集群和节点日志记录

实际的 Kubernetes 集群管理着数百甚至数千个 Pod。对于每个 Pod,至少有一个运行进程的容器。每个进程可以将日志输出到标准输出或标准错误流。捕获日志输出以有效地确定应用程序错误的根本原因至关重要。此外,集群组件生成用于诊断目的的日志。

如您所见,Kubernetes 的日志记录机制对于追踪错误和监视集群组件和应用程序至关重要。可以配置 Kubernetes 在集群或节点级别上记录日志。实施方法及其潜在的权衡可能各不相同。

集群日志记录

Kubernetes 并没有为集群级别的日志记录提供原生解决方案,但您可以从以下三个选项中选择以满足需求:

  • 实例化运行在每个集群节点上的节点级别日志代理

  • 配置负责处理应用程序日志的边车容器

  • 直接从应用程序逻辑推送日志到日志后端

下面的讨论解释了每种方法的优缺点。要详细讨论,请参阅Kubernetes 文档

使用节点日志代理

日志代理是一个专用工具,将日志发布到后端。后端可以是集群外的外部日志服务。图 7-1 展示了日志架构。

ckas 0701

图 7-1. 使用代理进行集群级别日志记录

这种方法的好处在于,应用程序不需要更改代码或 Pod 配置来支持收集日志。代理应作为 DaemonSet 运行。

使用边车容器

可以配置 Pod 来运行另一个侧车容器,与主应用程序容器并行运行。侧车容器会流式传输应用程序生成的标准输出和错误,并将流重定向到不同的位置(例如日志后端或挂载到容器的卷)。图 7-2 展示了集成流式侧车的 Pod 的日志设置。

ckas 0702

图 7-2. 使用侧车容器进行集群级别的日志记录

这种方法的好处在于可以轻松地分离不同的流(例如,将错误日志与信息日志分开)。

直接推送到日志后端

这种方法将责任推给应用程序而没有添加中间人。图 7-3 展示了直接推送到后端的日志设置。

ckas 0703

图 7-3. 通过直接推送到后端的集群级别的日志记录

虽然在架构上较少复杂,但任何对日志后端的更改都将要求修改应用程序代码,并因此需要新的部署。

节点日志

节点日志意味着日志文件将存储在集群节点上。容器运行时(例如 Docker Engine)通过配置的日志驱动程序将标准输出和错误流重定向到节点的存储中。

为避免填满节点存储的日志内容,应实施日志轮换。日志轮换是一种自动化过程,用于压缩、移动、删除和/或归档超过某一阈值的日志数据。Linux 工具 logrotate 是配置 Kubernetes 集群日志轮换的一种方式。图 7-4 可视化了节点级别的架构。

ckas 0704

图 7-4. 节点级别的日志记录

当你运行 kubectl logs 命令时,kubelet 收到请求后直接从节点上的日志文件读取,并将内容返回给客户端。kubectl logs 命令仅返回最新的日志内容,而不是已经归档的日志条目。

集群组件 kube-scheduler 和 kube-proxy 运行在容器中。因此,日志处理与任何其他应用程序容器相同。对于不在容器中运行的系统组件(例如 kubelet 和容器运行时),如果存在 systemd,则将日志写入 journald。如果没有 systemd,则系统组件将其日志文件写入目录 /var/log,文件扩展名为 .log

监控集群组件和应用程序

将软件部署到 Kubernetes 集群只是长期运行应用程序的开始。开发人员和管理员都需要了解其应用程序的资源消耗模式和行为,以提供可扩展和可靠的服务。

在 Kubernetes 的世界中,像 Prometheus 和 Datadog 这样的监控工具帮助收集、处理和可视化信息。考试不要求您熟悉商业监控、日志记录、跟踪和聚合工具;但是,了解负责收集使用情况指标的底层 Kubernetes 基础设施的基本情况会有所帮助。以下列表显示了典型指标的示例:

  • 集群中的节点数

  • 节点的健康状态

  • 节点性能指标如 CPU、内存、磁盘空间、网络

  • Pod 级别的性能指标如 CPU 和内存消耗

这一责任落在度量服务器手中,它是整个集群中资源使用数据的聚合器。如图 7-5 所示,运行在节点上的 kubelet 收集指标并将其发送到度量服务器。

ckas 0705

图 7-5. 用于度量服务器的数据收集

度量服务器将数据存储在内存中,不会持久化数据。如果您正在寻找保留历史数据的解决方案,那么您需要考虑商业选项。有关其安装过程的更多信息,请参阅文档。如果您在使用 Minikube 作为练习环境,则可以使用以下命令简单地启用度量服务器插件

$ 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

故障排除应用程序故障

在生产 Kubernetes 集群中运行应用时,几乎不可避免地会遇到故障情况。作为管理员,您有责任(可能与应用程序开发人员密切合作)调试已部署的 Kubernetes 对象的问题。

在本节中,我们将探讨可以帮助识别问题根本原因的调试策略,以便您可以采取行动并适当地修正故障。有关更多信息,请参考Kubernetes 文档

故障排除 Pods

在大多数情况下,创建 Pod 不会出现问题。您只需使用 runcreateapply 命令来实例化 Pod。如果 YAML 文件清单格式正确,Kubernetes 将接受您的请求,因此假设一切正常运行。为了验证正确行为,您首先需要检查 Pod 的高级运行时信息。此操作可能涉及其他 Kubernetes 对象,如负责部署多个 Pod 副本的 Deployment。

检索高级信息

若要检索信息,请运行 kubectl get pods 命令来获取命名空间中正在运行的 Pods 的信息,或者运行 kubectl get all 命令来检索命名空间中最重要的对象类型(包括 Deployments)。你需要查看 READYSTATUSRESTARTS 列。在最佳情况下,准备好的容器数量应该与 Pod 预期创建的容器数量匹配。对于单容器 Pod,READY 列应为 1/1。状态应为 Running,表示 Pod 进入了正确的生命周期状态。请注意,可能存在一个 Pod 处于 Running 状态,但实际上应用程序未能正常工作。如果重启次数大于 0,则可能需要检查存活探测器(如果定义了)的逻辑,并确定为何需要重启。

以下 Pod 观察到 ErrImagePull 状态,并且没有可用于入站流量的容器(0/1)。简言之,这个 Pod 存在问题:

$ kubectl get pods
NAME                  READY   STATUS         RESTARTS   AGE
pod/misbehaving-pod   0/1     ErrImagePull   0          2s

在使用 Kubernetes 一段时间后,您将自动识别常见的错误条件。表 7-1 列出了一些常见的错误状态,并解释了如何修复它们。

表 7-1. 常见 Pod 错误状态

状态 根本原因 潜在修复方法
ImagePullBackOffErrImagePull 无法从注册表拉取图像。 检查正确的图像名称,检查图像名称在注册表中是否存在,验证节点到注册表的网络访问,确保适当的身份验证。
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。该命令的输出列出了给定命名空间中所有 Pods 的事件。您可以使用额外的命令行选项进一步过滤和排序事件:

$ 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.startingDeadlineSeconds or check clock skew

检查日志

在调试 Pod 时,可以通过下载和检查其日志来获取下一级别的详细信息。您可能会找到指向行为不端 Pod 根本原因的额外信息,也可能找不到。但值得一试。在 示例 7-1 中显示的 YAML 清单定义了运行 shell 命令的 Pod。

示例 7-1. 运行失败的 shell 命令的 Pod
apiVersion: v1
kind: Pod
metadata:
  name: incorrect-cmd-pod
spec:
  containers:
  - name: test-container
    image: busybox
    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 表面上似乎正常工作,如 示例 7-2 所示。

示例 7-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
    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 工具 mkdircdls 来解决问题。显然,更好的缓解策略是从应用程序中创建目录或在 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

故障排除服务

服务为 Pod 提供统一的网络接口。有关 Kubernetes 网络方面的全面覆盖,请参阅 第 5 章。在此,我想指出这个原始的故障排除技术。

如果无法访问应该映射到服务的 Pod,请先确保标签选择器与 Pod 的分配标签匹配。您可以通过描述服务查询信息,然后使用选项 --show-labels 渲染可用 Pod 的标签。以下示例没有匹配的标签,因此不适用于命名空间中运行的任何 Pod:

$ kubectl describe service myservice
...
Selector:          app=myapp
...
$ kubectl get pods --show-labels
NAME                     READY   STATUS    RESTARTS   AGE     LABELS
myapp-68bf896d89-qfhlv   1/1     Running   0          7m39s   app=hello
myapp-68bf896d89-tzt55   1/1     Running   0          7m37s   app=world

或者,您还可以查询 Service 实例的端点。假设您期望有三个 Pod 被匹配的标签选择,但只暴露了两个。您将需要查看标签选择条件:

$ kubectl get endpoints myservice
NAME        ENDPOINTS                     AGE
myservice   172.17.0.5:80,172.17.0.6:80   9m31s

一个常见的困惑来源是 Service 的类型。默认情况下,Service 类型是ClusterIP,这意味着只有从集群内的同一节点查询时,才能通过 Service 访问 Pod。首先检查 Service 的类型。如果您认为ClusterIP是您想要分配的正确类型,请从集群内的临时 Pod 打开交互式 shell,并运行curlwget命令:

$ kubectl get services
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
myservice    ClusterIP   10.99.155.165   <none>        80/TCP    15m
$ kubectl run tmp --image=busybox -it --rm -- wget -O- 10.99.155.165:80
...

最后,检查从 Service 的目标端口到 Pod 的容器端口的端口映射是否正确配置。这两个端口需要匹配,否则网络流量将无法正确路由:

$ kubectl get service myapp -o yaml | grep targetPort:
    targetPort: 80
$ kubectl get pods myapp-68bf896d89-qfhlv -o yaml | grep containerPort:
    - containerPort: 80

故障排除集群故障

许多影响因素可以在组件级别上导致 Kubernetes 集群出现故障。列出集群中可用的节点是一个好主意,以识别潜在问题:

$ kubectl get nodes
NAME           STATUS   ROLES                  AGE     VERSION
minikube       Ready    control-plane,master   3m18s   v1.22.3
minikube-m02   Ready    <none>                 2m51s   v1.22.3
minikube-m03   Ready    <none>                 2m24s   v1.22.3

输出将为您提供一览。您可以从ROLES列、使用的 Kubernetes 版本以及当前的健康状态轻松地识别每个节点的责任。

在高级别识别问题时需要注意几点:

  • 节点的健康状态是否除“Ready”之外的其他状态?

  • 节点的版本是否与其他节点的版本不一致?

在接下来的章节中,您可以找到关于故障排除控制平面节点与工作节点的各自部分。

故障排除控制平面节点

控制平面节点是保持集群运行的关键组件。如“管理高可用集群”中所述,集群可以由多个控制平面节点组成,以确保高度的可用性。检测到其中一个控制平面节点故障应被视为极端紧急,以避免损害高可用特性。有关故障排除技术和根本原因分析的更多信息,请参考Kubernetes 文档

渲染集群信息

要进一步诊断控制平面节点上的问题,请运行命令kubectl cluster-info。如下输出所示,该命令呈现了控制平面和其他集群服务的地址:

$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.64.21:8443
CoreDNS is running at https://192.168.64.21:8443/api/v1/namespaces/ \
kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use *kubectl cluster-info dump*.

要详细查看集群日志,请添加dump子命令。由于页面和页面的日志消息,我们不会在本书中渲染输出。解析消息以查找是否存在任何错误:

$ kubectl cluster-info dump

检查控制平面组件

在控制平面节点上可用的组件包括以下内容:

  • kube-apiserver:为像kubectl这样的客户端公开 Kubernetes API,用于管理对象。

  • etcd:用于存储集群数据的键值存储。

  • kube-scheduler:为已计划但未创建的 Pod 选择节点。

  • kube-controller-manager:运行控制器进程(例如,负责 Job 对象执行的作业控制器)。

  • cloud-controller-manager:将特定于云提供商的 API 链接到 Kubernetes 集群。在 Kubernetes 的本地集群安装中,此控制器不可用。

要查找这些组件及其状态,请列出命名空间kube-system中可用的 Pods。在这里,你可以找到 Minikube 上控制平面组件的列表:

$ kubectl get pods -n kube-system
NAME                               READY   STATUS    RESTARTS      AGE
etcd-minikube                      1/1     Running   1 (11d ago)   29d
kube-apiserver-minikube            1/1     Running   1 (11d ago)   29d
kube-controller-manager-minikube   1/1     Running   1 (11d ago)   29d
kube-scheduler-minikube            1/1     Running   1 (11d ago)   29d
...

任何状态不显示“Running”的情况都应进一步检查。你可以像检索其他 Pod 的日志一样,使用logs命令检索控制平面组件 Pod 的日志。以下命令下载 kube-apiserver 组件的日志:

$ kubectl logs kube-apiserver-minikube -n kube-system

故障排除工作节点

工作节点负责管理工作负载。确保有足够数量的工作节点可用以分配负载。有关如何加入工作节点到集群的更深入讨论,请参阅第二章。

集群中的任何节点都可以转入错误状态。作为 Kubernetes 管理员,你的工作是及时识别这些情况并加以修复。当列出集群节点时,你可能会看到一个工作节点不处于“Ready”状态,这表明它无法处理工作负载。在get nodes命令的输出中,你可以看到名为worker-1的节点处于“NotReady”状态:

$ kubectl get nodes
NAME                STATUS     ROLES                  AGE     VERSION
k8s-control-plane   Ready      control-plane,master   4d20h   v1.22.3
worker-1            NotReady   <none>                 4d20h   v1.22.3
worker-2            Ready      <none>                 4d20h   v1.22.3

“NotReady”状态意味着节点未使用,并将积累操作成本而未实际安排工作负载。节点进入此状态可能有各种原因。以下列表显示了最常见的原因:

  • 资源不足:节点可能内存或磁盘空间不足。

  • kubelet 进程存在问题:该进程可能已崩溃或在节点上停止运行。因此,它无法再与任何控制平面节点上运行的 API 服务器进行通信。

  • kube-proxy 存在问题:运行 kube-proxy 的 Pod 负责集群内外的网络通信。该 Pod 已转入非功能状态。

SSH 登录相关工作节点并开始调查。

检查可用资源

确定不可用工作节点根本原因的一种好方法是查看其详细信息。describe node命令呈现标记为“Conditions”的部分:

$ kubectl describe node worker-1
....
Conditions:
  Type                 Status  LastHeartbeatTime               \
    LastTransitionTime                Reason                       Message
  ----                 ------  -----------------               \
    ------------------                ------                       -------
  NetworkUnavailable   False   Thu, 20 Jan 2022 18:12:13 +0000 \
    Thu, 20 Jan 2022 18:12:13 +0000   CalicoIsUp               \
        Calico is running on this node
  MemoryPressure       False   Tue, 25 Jan 2022 15:59:18 +0000 \
    Thu, 20 Jan 2022 18:11:47 +0000   KubeletHasSufficientMemory \
      kubelet has sufficient memory available
  DiskPressure         False   Tue, 25 Jan 2022 15:59:18 +0000 \
    Thu, 20 Jan 2022 18:11:47 +0000   KubeletHasNoDiskPressure \
        kubelet has no disk pressure
  PIDPressure          False   Tue, 25 Jan 2022 15:59:18 +0000 \
    Thu, 20 Jan 2022 18:11:47 +0000   KubeletHasSufficientPID  \
        kubelet has sufficient PID available
  Ready                True    Tue, 25 Jan 2022 15:59:18 +0000 \
    Thu, 20 Jan 2022 18:12:07 +0000   KubeletReady             \
        kubelet is posting ready status. AppArmor enabled
...

表格包含有关节点可用资源的信息,以及类似网络的其他服务的指示。查看是否有任何资源类型显示状态TrueUnknown,这意味着特定资源存在问题。你可以进一步使用系统级命令排查不可用资源。

要检查内存使用情况和运行进程的数量,请使用top命令:

$ top
top - 18:45:09 up 1 day,  2:21,  1 user,  load average: 0.13, 0.13, 0.15
Tasks: 116 total,   3 running,  70 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.5 us,  0.8 sy,  0.0 ni, 97.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1008552 total,   134660 free,   264604 used,   609288 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   611248 avail Mem
...

要检查可用磁盘空间,请使用 df 命令:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            480M     0  480M   0% /dev
tmpfs            99M  1.0M   98M   2% /run
/dev/sda1        39G  2.7G   37G   7% /
tmpfs           493M     0  493M   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           493M     0  493M   0% /sys/fs/cgroup
vagrant         1.9T  252G  1.6T  14% /vagrant
tmpfs            99M     0   99M   0% /run/user/1000

检查 kubelet 进程

describe node 命令生成的某些条件提到了 kubelet 进程。如果查看 Message 列,您可能会对 kubelet 进程是否正常运行有所了解。若要排除 kubelet 进程的问题,请运行以下 systemctl 命令:

$ systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; \
   vendor preset: enabled)
  Drop-In: /etc/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: active (running) since Thu 2022-01-20 18:11:41 UTC; 5 days ago
     Docs: https://kubernetes.io/docs/home/
 Main PID: 6537 (kubelet)
    Tasks: 15 (limit: 1151)
   CGroup: /system.slice/kubelet.service
           └─6537 /usr/bin/kubelet \
           --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
           --kubeconfig=/etc/kubernetes/kubelet.conf \
           --config=/var/lib/kubelet/config.yaml --network-lines 1-10/10

输出中最重要的信息是 Active 属性的值。如果除了“active (running)”之外还有其他内容,则需要深入挖掘。使用 journalctl 查看进程的日志文件:

$ journalctl -u kubelet.service
-- Logs begin at Thu 2022-01-20 18:10:41 UTC, end at
Tue 2022-01-25 18:44:05 UTC. --
Jan 20 18:11:31 worker-1 systemd[1]: Started kubelet: The Kubernetes Node Agent.
Jan 20 18:11:31 worker-1 systemd[1]: kubelet.service: Current command vanished \
from the unit file, execution of the command list won't be resumed.
Jan 20 18:11:31 worker-1 systemd[1]: Stopping kubelet: The Kubernetes
Node Agent...
Jan 20 18:11:31 worker-1 systemd[1]: Stopped kubelet: The Kubernetes Node Agent.
Jan 20 18:11:31 worker-1 systemd[1]: Started kubelet: The Kubernetes Node Agent.
....

一旦在日志中确定了问题并进行了修复,您将需要重新启动进程:

$ systemctl restart kubelet

检查证书

有时,kubelet 使用的证书可能会过期。确保 IssuerNot After 属性的值是正确的:

$ openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 2 (0x2)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = worker-1-ca@1642702301
        Validity
            Not Before: Jan 20 17:11:41 2022 GMT
            Not After : Jan 20 17:11:41 2023 GMT
        Subject: CN = worker-1@1642702301
        ...

检查 kube-proxy Pod

kube-proxy 组件在 kube-system 命名空间中的一组专用 Pods 中运行。您可以通过它们的命名前缀 kube-proxy 和附加的哈希来清楚地识别这些 Pods。验证任何一个 kube-proxy Pod 的状态是否与“Running”不同。每个 kube-proxy Pod 都运行在专用的工作节点上。您可以添加 -o wide 选项,在新列中渲染 Pod 所在的节点:

$ kubectl get pods -n kube-system
NAME               READY   STATUS    RESTARTS   AGE
...
kube-proxy-csrww   1/1     Running   0          4d22h
kube-proxy-fjd48   1/1     Running   0          4d22h
kube-proxy-tvf52   1/1     Running   0          4d22h

查看似乎存在问题的 kube-proxy Pods 的事件日志。以下命令描述了名为 kube-proxy-csrww 的 Pod。此外,您可能会在相应 DaemonSet 的事件日志中找到更多信息:

$ kubectl describe pod kube-proxy-csrww -n kube-system
$ kubectl describe daemonset kube-proxy -n kube-system

日志也可能很有用。您只能检查在特定工作节点上运行的 kube-proxy Pod 的日志:

$ kubectl describe pod kube-proxy-csrww -n kube-system | grep Node:
Node:                 worker-1/10.0.2.15
$ kubectl logs kube-proxy-csrww -n kube-system

总结

作为 Kubernetes 管理员,您需要能够识别和修复应用程序和集群组件的问题。

日志记录对于追踪应用流程和捕获潜在错误消息至关重要。您可以在集群级别和节点级别配置日志记录,每种方法都有其各自的优势和潜在缺陷。根据日志捕获的方法,可能会涉及诸如日志轮换和日志后端服务等方面。在 Pod 级别,您可以直接使用 kubectl logs 命令请求应用程序日志。使用命令行选项 -c 来针对多容器设置中的特定容器。

Kubernetes 的原生监控服务,指标服务器,可以安装在集群上,用于收集和聚合 Pod 和节点资源利用率数据。节点通过 kubelet 向集中的指标服务器发送指标。最终用户可以使用 kubectl top 命令,将这些指标呈现出来,以识别资源使用过度的情况。

工作负载和网络对象的错误配置可能导致应用程序问题。您需要熟悉诊断根本原因的相关策略以及如何修复它们。集群也可能出现错误状态,导致各种运营问题。您需要了解控制平面节点和工作节点上运行的集群组件和进程。我们讨论了应对不同故障情况的策略。

考试要点

理解日志配置的理论层面。

Kubernetes 日志记录不是内置功能。您需要在集群或节点级别积极配置日志记录。比较不同的方法及其潜在的权衡。

使访问容器日志成为您的日常工作重点。

访问容器日志非常直接。只需使用logs命令即可。练习所有相关的命令行选项的使用。选项-c用于指定特定容器。对于单容器 Pod,不必显式使用该选项。选项-f可用于跟踪应用程序中的日志条目。如果需要查看先前容器的日志,即使容器已重启,也可以使用选项-p来访问日志。

安装和使用度量服务器

Kubernetes 集群默认未安装度量服务器。按照安装服务的步骤操作。在考试中,可以假设度量服务器已经可用。使用top命令查看 Pods 和节点的资源消耗情况。

知道如何排查应用程序问题。

运行在 Pod 中的应用程序可能因配置错误而容易出现故障。考虑可能发生的情景,并试图积极地对其进行建模,以表示故障情况。然后使用命令getlogsexec,深入了解问题并修复它。尝试构想不常见的场景,以便更轻松地找到和修复不同资源类型的应用程序问题。

知道如何排查集群问题。

控制平面和工作节点可能因多种原因而变得无响应或无功能。管理员需要关注其集群的健康状况,以保持其可操作性和可扩展性。尝试模拟可能发生的错误情景,并应用讨论的故障排除技术来识别和修复潜在的根本原因。

样例练习

这些练习的解决方案可在附录中找到。

  1. 您应该使用一个带有边车容器的集群级别日志记录。创建一个名为multi的多容器 Pod。主应用程序容器名为nginx,使用镜像nginx:1.21.6。边车容器名为streaming,使用镜像busybox:1.35.0和参数/bin/sh, -c, 'tail -n+1 -f /var/log/nginx/access.log'

  2. 为 Pod 定义一个emptyDir类型的卷,并将其挂载到路径/var/log/nginx上,供两个容器使用。

  3. 使用 wgetcurl 命令多次访问 nginx 服务的端点。检查 sidecar 容器的日志。

  4. 创建两个名为 stress-1stress-2 的 Pod。定义一个使用镜像 polinux/stress:1.0.4,命令为 stress,参数为 /bin/sh, -c'stress --vm 1 --vm-bytes $(shuf -i 20-200 -n 1)M --vm-hang 1' 的容器。设置容器的内存资源限制和请求为 250Mi。

  5. 利用指标服务器提供的数据,确定 stress-1stress-2 中哪个 Pod 消耗了最多的内存。将 Pod 的名称写入文件 max-memory.txt

  6. 进入检出的 GitHub 仓库 bmuschko/cka-study-guide 的目录 app-a/ch07/troubleshooting-pod。按照文件 instructions.md 中的说明来排除 Pod 安装的故障。

  7. 进入检出的 GitHub 仓库 bmuschko/cka-study-guide 的目录 app-a/ch07/troubleshooting-deployment。按照文件 instructions.md 中的说明来排除部署安装的故障。

  8. 进入检出的 GitHub 仓库 bmuschko/cka-study-guide 的目录 app-a/ch07/troubleshooting-service。按照文件 instructions.md 中的说明来排除服务安装的故障。

  9. 进入检出的 GitHub 仓库 bmuschko/cka-study-guide 的目录 app-a/ch07/troubleshooting-control-plane-node。按照文件 instructions.md 中的说明来排除控制平面节点安装的故障。

    先决条件: 此练习需要安装工具 VagrantVirtualBox

  10. 进入检出的 GitHub 仓库 bmuschko/cka-study-guide 的目录 app-a/ch07/troubleshooting-worker-node。按照文件 instructions.md 中的说明来排除工作节点安装的故障。

    先决条件: 此练习需要安装工具 VagrantVirtualBox

第八章:结语

感谢您参加这场关于认证 Kubernetes 管理员相关内容的旋风之旅。我在这本书中为您总结了最相关的信息,希望能帮助您成功。您的旅程并未结束。事实上,这只是开始,因为在 Kubernetes 领域还有更多要学习的内容。我鼓励您深入探索本书讨论的主题,甚至进一步拓展到 Kubernetes 工具生态系统的更多领域。

在我们结束之前,让我总结一下考试中获得高分的最重要的几点。要尽可能多地得分的关键是实践、实践和再实践。在这本书中运行示例练习,利用免费和商业实践考试,甚至设计自己的实践场景,以了解 Kubernetes 和kubectl的方方面面。为了巩固你对 Kubernetes 的接触,至少要从头到尾阅读 Kubernetes 文档一次。在这本书的过程中,我们看到考试不仅仅是在测试你对kubectl的知识。你还需要熟悉其他工具,如 vim、bash 和 YAML。

祝您在获得 CKA 认证时一切顺利。通过这本书,我希望能为您提供相关的信息和学习策略,帮助您成功。

对于我们必须在做之前学会的事情,我们通过做来学习它们。

亚里士多德

第九章:复习问题的答案

第二章,“集群架构、安装和配置”

  1. 首先创建名为apps的命名空间。然后,我们将创建 ServiceAccount:

    $ kubectl create namespace apps
    $ kubectl create serviceaccount api-access -n apps
    

    或者,您可以使用声明性方法。从文件apps-namespace.yaml中的定义创建命名空间:

    apiVersion: v1
    kind: Namespace
    metadata:
      name: apps
    

    从 YAML 文件创建命名空间:

    $ kubectl create -f apps-namespace.yaml
    

    使用以下内容创建名为api-serviceaccount.yaml的新 YAML 文件:

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: api-access
      namespace: apps
    

    运行create命令从 YAML 文件实例化 ServiceAccount:

    $ kubectl create -f api-serviceaccount.yaml
    
  2. 使用create clusterrole命令以命令方式创建 ClusterRole:

    $ kubectl create clusterrole api-clusterrole --verb=watch,list,get \
      --resource=pods
    

    如果您更喜欢从 YAML 文件开始,请使用文件api-clusterrole.yaml中显示的内容:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: api-clusterrole
    rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["watch","list","get"]
    

    从 YAML 文件创建 ClusterRole:

    $ kubectl create -f api-clusterrole.yaml
    

    使用create clusterrolebinding命令以命令方式创建 ClusterRoleBinding。

    $ kubectl create clusterrolebinding api-clusterrolebinding \
      --serviceaccount=apps:api-access --clusterrole=api-clusterrole
    

    ClusterRoleBinding 的声明性方法可能类似于文件api-clusterrolebinding.yaml中的方法:

    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: api-clusterrolebinding
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: api-clusterrole
    subjects:
    - apiGroup: ""
      kind: ServiceAccount
      name: api-access
      namespace: apps
    

    从 YAML 文件创建 ClusterRoleBinding:

    $ kubectl create -f api-clusterrolebinding.yaml
    
  3. 执行run命令在不同的命名空间中创建 Pods。您需要在实例化 Poddisposable之前创建rm命名空间:

    $ kubectl run operator --image=nginx:1.21.1 --restart=Never \
      --port=80 --serviceaccount=api-access -n apps
    $ kubectl create namespace rm
    $ kubectl run disposable --image=nginx:1.21.1 --restart=Never \
      -n rm
    

    以下 YAML 清单显示了存储在文件rm-namespace.yaml中的rm命名空间定义:

    apiVersion: v1
    kind: Namespace
    metadata:
      name: rm
    

    存储在文件api-pods.yaml中的这些 Pod 的 YAML 表示如下所示:

    apiVersion: v1
    kind: Pod
    metadata:
      name: operator
      namespace: apps
    spec:
      serviceAccountName: api-access
      containers:
      - name: operator
        image: nginx:1.21.1
        ports:
        - containerPort: 80
    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: disposable
      namespace: rm
    spec:
      containers:
      - name: disposable
        image: nginx:1.21.1
    

    从 YAML 文件创建命名空间和 Pods:

    $ kubectl create -f rm-namespace.yaml
    $ kubectl create -f api-pods.yaml
    
  4. 确定 ServiceAccount 的 API 服务器端点和 Secret 访问令牌。您将需要这些信息来进行 API 调用:

    $ kubectl config view --minify -o \
      jsonpath='{.clusters[0].cluster.server}'
    https://192.168.64.4:8443
    $ kubectl get secret $(kubectl get serviceaccount api-access -n apps \
      -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' -n apps \
      | base64 --decode
    eyJhbGciOiJSUzI1NiIsImtpZCI6Ii1hOUhI...
    
    

    打开名为operator的 Pod 的交互式 Shell:

    $ kubectl exec operator -it -n apps -- /bin/sh
    

    发出 API 调用以列出所有在rm命名空间中生存的 Pods 并删除 Poddisposable。您会发现虽然list操作被允许,但delete操作不被允许:

    # curl https://192.168.64.4:8443/api/v1/namespaces/rm/pods --header \
    "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ii1hOUhI..." \
    --insecure
    {
        "kind": "PodList",
        "apiVersion": "v1",
        ...
    }
    # curl -X DELETE https://192.168.64.4:8443/api/v1/namespaces \
    /rm/pods/disposable --header \
    "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ii1hOUhI..." \
    --insecure
    {
      "kind": "Status",
      "apiVersion": "v1",
      "metadata": {
    
      },
      "status": "Failure",
      "message": "pods \"disposable\" is forbidden: User \
      \"system:serviceaccount:apps:api-access\" cannot delete \
      resource \"pods\" in
      API group \"\" in the namespace \"rm\"",
      "reason": "Forbidden",
      "details": {
        "name": "disposable",
        "kind": "pods"
      },
      "code": 403
    }
    
  5. 解决此示例练习需要许多手动步骤。以下命令不会输出它们的结果。

    使用 Vagrant 打开控制平面节点的交互式 Shell:

    $ vagrant ssh k8s-control-plane
    

    升级kubeadm到版本 1.21.2 并应用它:

    $ sudo apt-mark unhold kubeadm && sudo apt-get update && sudo apt-get \
      install -y kubeadm=1.21.2-00 && sudo apt-mark hold kubeadm
    $ sudo kubeadm upgrade apply v1.21.2
    

    驱逐节点,升级 kubelet 和kubectl,重新启动 kubelet,并取消节点的禁用状态:

    $ kubectl drain k8s-control-plane --ignore-daemonsets
    $ sudo apt-get update && sudo apt-get install -y \
      --allow-change-held-packages kubelet=1.21.2-00 kubectl=1.21.2-00
    $ sudo systemctl daemon-reload
    $ sudo systemctl restart kubelet
    $ kubectl uncordon k8s-control-plane
    

    节点的版本现在应该显示为 v1.21.2。退出节点:

    $ kubectl get nodes
    $ exit
    

    使用 Vagrant 打开第一个工作节点的交互式 Shell。对其他工作节点重复所有以下步骤:

    $ vagrant ssh worker-1
    

    升级kubeadm到版本 1.21.2,并将其应用到节点:

    $ sudo apt-get update && sudo apt-get install -y \
      --allow-change-held-packages kubeadm=1.21.2-00
    $ sudo kubeadm upgrade node
    

    驱逐节点,升级 kubelet 和kubectl,重新启动 kubelet,并取消节点的禁用状态:

    $ kubectl drain worker-1 --ignore-daemonsets
    $ sudo apt-get update && sudo apt-get install -y \
      --allow-change-held-packages kubelet=1.21.2-00 kubectl=1.21.2-00
    $ sudo systemctl daemon-reload
    $ sudo systemctl restart kubelet
    $ kubectl uncordon worker-1
    

    节点的版本现在应该显示为 v1.21.2。退出节点:

    $ kubectl get nodes
    $ exit
    
  6. 解决此示例练习需要许多手动步骤。以下命令不会输出它们的结果。

    使用 Vagrant 打开控制平面节点的交互式 Shell。这并非使用etcdctl命令行工具安装:

    $ vagrant ssh k8s-control-plane
    

    通过描述它来确定 Podetcd-k8s-control-plane的参数。使用正确的参数值创建快照文件:

    $ kubectl describe pod etcd-k8s-control-plane -n kube-system
    $ sudo ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt \
      --cert=/etc/kubernetes/pki/etcd/server.crt \
      --key=/etc/kubernetes/pki/etcd/server.key snapshot save /opt/etcd.bak
    

    从快照文件恢复备份。编辑 etcd YAML 清单,并为名为etcd-data的卷的spec.volumes.hostPath.path值进行更改:

    $ sudo ETCDCTL_API=3 etcdctl --data-dir=/var/bak snapshot restore \
      /opt/etcd.bak
    $ sudo vim /etc/kubernetes/manifests/etcd.yaml
    

    过一段时间后,Pod etcd-k8s-control-plane应该会恢复到“Running”状态。退出节点:

    $ kubectl get pod etcd-k8s-control-plane -n kube-system
    $ exit
    

第三章,“工作负载”

  1. 首先创建名为nginx的部署。使用最直接的方法来实现最快的反应时间:

    $ kubectl create deployment nginx --image=nginx:1.17.0 --replicas=2
    
  2. scale命令将副本数量增加到 7。get命令应该呈现出具有部署名称作为其名称前缀的七个 Pod:

    $ kubectl scale deployment nginx --replicas=7
    $ kubectl get deployments,pods
    NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/nginx   7/7     7            7           93s
    
    NAME                         READY   STATUS    RESTARTS   AGE
    pod/nginx-844f997cc9-6tbzw   1/1     Running   0          93s
    pod/nginx-844f997cc9-8mzz2   1/1     Running   0          93s
    pod/nginx-844f997cc9-n7g8x   1/1     Running   0          10s
    pod/nginx-844f997cc9-sbrmf   1/1     Running   0          10s
    pod/nginx-844f997cc9-wtbk6   1/1     Running   0          10s
    pod/nginx-844f997cc9-xghl9   1/1     Running   0          10s
    pod/nginx-844f997cc9-zsggj   1/1     Running   0          10s
    
  3. 目前支持定义 CPU 和内存利用率阈值的 Horizontal Pod Autoscaler 的 API 版本是autoscaling/v2beta2。以下的 YAML 清单在文件hpa.yaml中指定了自动扩展的参数:

    apiVersion: autoscaling/v2beta2
    kind: HorizontalPodAutoscaler
    metadata:
      name: nginx-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: nginx
      minReplicas: 3
      maxReplicas: 20
      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: 65
      - type: Resource
        resource:
          name: memory
          target:
            type: AverageValue
            averageValue: 1Gi
    

    使用create命令创建 Horizontal Pod Autoscaler:

    $ kubectl create -f hpa.yaml
    
  4. 您可以通过使用edit命令手动更改镜像名称,或者使用set image命令作为快捷方式。使用--record选项记录命令作为更改原因:

    $ kubectl set image deployment nginx nginx=nginx:1.21.1 --record
    

    rollout 历史记录将显示两个修订版本,起始修订版本为 1,最近的更改为 2。请注意,更改原因显示了记录的命令:

    $ kubectl rollout history deployment nginx
    deployment.apps/nginx
    REVISION  CHANGE-CAUSE
    1         <none>
    2         kubectl set image deployment nginx nginx=nginx:1.21.1 \
              --record=true
    

    使用rollout undo命令回滚到先前的修订版本。修订版本 1 变为修订版本 3:

    $ kubectl rollout undo deployment nginx --to-revision=1
    $ kubectl rollout history deployment nginx
    deployment.apps/nginx
    REVISION  CHANGE-CAUSE
    2         kubectl set image deployment nginx nginx=nginx:1.21.1 \
              --record=true
    3         <none>
    
  5. 以下的 YAML 清单显示了类型为kubernetes.io/basic-auth的 Secret,存储在文件basic-auth-secret.yaml中:

    apiVersion: v1
    kind: Secret
    metadata:
      name: basic-auth
    type: kubernetes.io/basic-auth
    stringData:
      username: super
      password: my-s8cr3t
    

    使用create命令创建 Secret:

    $ kubectl create -f basic-auth-secret.yaml
    

    若要将 Secret 作为卷挂载到 Pod 模板中,请使用edit命令编辑 Deployment 的活动对象。Deployment 的重要部分将类似于以下的 YAML 清单:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      replicas: 7
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:1.17.0
            volumeMounts:
            - mountPath: /etc/secret
              name: auth-vol
              readOnly: true
          volumes:
          - name: auth-vol
            secret:
              secretName: basic-auth
    

第四章,“调度和工具”

  1. 定义资源边界的 Pod 清单存储在文件ingress-controller-pod.yaml中,可能如下所示:

    apiVersion: v1
    kind: Pod
    metadata:
      name: ingress-controller
    spec:
      containers:
      - name: ingress-controller
        image: bitnami/nginx-ingress-controller:1.0.0
        resources:
          requests:
            memory: "256Mi"
            cpu: "1"
          limits:
            memory: "1024Mi"
            cpu: "2.5"
    
  2. 假设一个由三个节点组成的集群:一个控制平面节点和两个工作节点。以下的多节点集群已经通过 Minikube 设置好。更多信息请参见设置指南

    $ kubectl get nodes
    NAME           STATUS   ROLES                  AGE   VERSION
    minikube       Ready    control-plane,master   41d   v1.21.2
    minikube-m02   Ready    <none>                 21h   v1.21.2
    minikube-m03   Ready    <none>                 21h   v1.21.2
    

    在创建对象后,您可以确定哪个节点运行了 Pod。将节点名称写入文件node.txt

    $ kubectl create -f ingress-controller-pod.yaml
    $ kubectl get pod ingress-controller -o yaml | grep nodeName:
      nodeName: minikube-m02
    $ echo "minikube-m02" >> node.txt
    
  3. 导航到包含manifests目录的文件夹。使用递归的apply命令创建manifests目录中包含的所有对象:

    $ kubectl apply -f manifests/ -R
    configmap/logs-config created
    pod/nginx created
    
  4. 使用编辑器修改文件configmap.yamldir键的值。然后使用以下命令更新 ConfigMap 的活动对象:

    $ vim manifests/configmap.yaml
    $ kubectl apply -f manifests/configmap.yaml
    configmap/logs-config configured
    

    使用递归的delete命令删除从manifests目录创建的所有对象:

    $ kubectl delete -f manifests/ -R
    configmap "logs-config" deleted
    pod "nginx" deleted
    
  5. 创建文件kustomization.yaml。它应该定义命名空间的公共属性,并引用文件pod.yaml中的资源。以下的 YAML 文件显示了它的内容:

    namespace: t012
    resources:
    - pod.yaml
    

    运行以下kustomize命令以将转换后的清单渲染为控制台输出:

    $ kubectl kustomize ./
    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx
      namespace: t012
    spec:
      containers:
      - image: nginx:1.21.1
        name: nginx
    

第五章,“服务和网络”

  1. 首先创建命名空间external。在命名空间内,使用命令创建部署和 Service:

    $ kubectl create namespace external
    $ kubectl create deployment nginx --image=nginx --port=80 --replicas=3 \
      -n external
    $ kubectl create service loadbalancer nginx --tcp=80:80 -n external
    

    如果您更愿意使用声明性方法,请参见以下 YAML 清单:

    external-namespace.yaml

    apiVersion: v1
    kind: Namespace
    metadata:
      name: external
    

    external-deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
      namespace: external
    spec:
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - image: nginx
            name: nginx
            ports:
            - containerPort: 80
    

    external-service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
      namespace: external
    spec:
      type: LoadBalancer
      selector:
        app: nginx
      ports:
      - port: 80
        targetPort: 80
    

    要创建所有对象,请运行createapply命令:

    $ kubectl create -f external-namespace.yaml
    $ kubectl create -f external-deployment.yaml
    $ kubectl create -f external-service.yaml
    
  2. 确定类型为LoadBalancer的 Service 的外部 IP 地址。在下面的输出中,外部 IP 地址为10.108.34.2。如果使用 Minikube,请记得在另一个 Shell 中运行minikube tunnel,以便EXTERNAL-IP的值得到填充:

    $ kubectl get service -n external
    NAME    TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
    nginx   LoadBalancer   10.108.34.2   10.108.34.2   80:31898/TCP   36m
    

    以下curl命令通过使用外部 IP 地址和端口调用 Service:

    $ wget 10.108.34.2:80
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    ...
    
  3. 通过将属性spec.type的值从LoadBalancer更改为ClusterIP来编辑实时对象:

    $ kubectl edit service nginx -n external
    ...
    spec:
      type: ClusterIP
    ...
    

    现在 Service 应该指示类型为ClusterIP。请注意,外部 IP 地址的值已经消失。此外,静态分配的端口也消失了:

    $ kubectl get service -n external
    NAME    TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
    nginx   ClusterIP   10.108.34.2   <none>        80/TCP    52m
    

    类型为ClusterIP的 Service 仅从集群内部可访问。您可以从同一命名空间内的临时 Pod 中对 Service 进行调用。您可以使用集群 IP 地址(在本例中为10.108.34.2)或使用 Service nginx的 DNS 名称:

    $ kubectl run tmp --image=busybox --restart=Never -n external -it --rm \
      -- wget 10.108.34.2:80
    Connecting to 10.108.34.2:80 (10.108.34.2:80)
    saving to 'index.html'
    index.html           100% |********************************|   615  \
    0:00:00 ETA
    'index.html' saved
    pod "tmp" deleted
    $ kubectl run tmp --image=busybox --restart=Never -n external -it --rm \
      -- wget nginx:80
    Connecting to 10.108.34.2:80 (10.108.34.2:80)
    saving to 'index.html'
    index.html           100% |********************************|   615  \
    0:00:00 ETA
    'index.html' saved
    pod "tmp" deleted
    
    
  4. 要以声明性方式创建入口,运行以下命令:

    $ kubectl create ingress incoming --rule="/*=nginx:80" -n external
    
    

    如果您更愿意使用声明性方法,请参见此处显示的文件incoming-ingress.yaml中的 YAML 清单:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: incoming
      namespace: external
    spec:
      rules:
      - http:
          paths:
          - backend:
              service:
                name: nginx
                port:
                  number: 80
            path: /
            pathType: Prefix
    

    要创建对象,请运行createapply命令:

    $ kubectl create -f incoming-ingress.yaml
    
  5. 要验证 Ingress 的正确行为,请获取集群中任何节点的 IP 地址。在这里,我们只处理具有 IP 地址192.168.64.19的单个节点:

    $ kubectl get nodes -o wide
    NAME      STATUS   ROLES                 AGE   VERSION   INTERNAL-IP   \
    EXTERNAL-IP   OS-IMAGE               KERNEL-VERSION   CONTAINER-RUNTIME
    minikube  Ready    control-plane,master  13d   v1.21.2   192.168.64.19 \
    <none>        Buildroot 2020.02.12   4.19.182         docker://20.10.6
    

    Ingress 已配置为访问任何主机名的调用。从本地机器对节点的 IP 地址进行调用。流量将通过名为nginx的 Service 路由到 Pods:

    $ curl 192.168.64.19
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    ...
    
  6. 为了快速实现,您可以使用run命令加上--expose CLI 选项一起创建echoserver Pod 和 Service:

    $ kubectl run echoserver --image=k8s.gcr.io/echoserver:1.10 \
      --restart=Never --port=8080 --expose -n external
    

    声明性方法需要创建以下 YAML 清单:

    external-echoserver-pod.yaml:

    apiVersion: v1
    kind: Pod
    metadata:
      name: echoserver
      namespace: external
      labels:
        run: nginx
    spec:
      containers:
      - name: echoserver
        image: k8s.gcr.io/echoserver:1.10
        ports:
        - containerPort: 8080
    

    external-echoserver-service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: echoserver
      namespace: external
    spec:
      type: ClusterIP
      selector:
        run: nginx
      ports:
      - port: 8080
        targetPort: 8080
    

    要创建对象,请运行createapply命令:

    $ kubectl create -f external-echoserver-pod.yaml
    $ kubectl create -f external-echoserver-service.yaml
    

    修改现有的 Ingress 并添加一个新的规则,将流量路由到echoservice Service。生成的 YAML 定义如下所示:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: incoming
      namespace: external
    spec:
      rules:
      - http:
          paths:
          - backend:
              service:
                name: nginx
                port:
                  number: 80
            path: /
            pathType: Prefix
          - backend:
              service:
                name: echoserver
                port:
                  number: 8080
            path: /echo
            pathType: Exact
    
  7. 从本地机器对节点的 IP 地址进行调用,路径为/echo。流量将通过名为echoservice的 Service 路由到 Pods:

    $ curl 192.168.64.19/echo
    
    Hostname: echoserver
    ...
    
  8. 您可以通过使用命令kubectl edit configmap coredns -n kube-system编辑命名空间kube-system中的 ConfigMap coredns来自定义 CoreDNS 设置。以下 YAML 清单显示了rewrite规则:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coredns-custom
      namespace: kube-system
    data:
      Corefile: |
        .:53 {
            ...
            rewrite name substring svc.cka.example.com svc.cluster.local
            kubernetes cluster.local in-addr.arpa ip6.arpa {
            ...
        }
    

    在命名空间kube-system中查找运行 CoreDNS 的 Pod,并删除它以强制重新创建 Pod。您应该看到 Kubernetes 为 CoreDNS Pod 创建了一个新对象:

    $ kubectl get pods -n kube-system
    NAME                               READY   STATUS    RESTARTS   AGE
    coredns-558bd4d5db-kjdtx           1/1     Running   0          9m35s
    ...
    $ kubectl delete pod coredns-558bd4d5db-kjdtx -n kube-system
    $ kubectl get pods -n kube-system
    NAME                               READY   STATUS    RESTARTS   AGE
    coredns-558bd4d5db-mc98t           1/1     Running   0          54s
    ...
    
  9. 如果尚不存在,请创建命名空间hello。从hello命名空间的临时 Pod 针对echoserver.external.svc.cka.example.com运行wget命令。调用应成功:

    $ kubectl create namespace hello
    $ kubectl run tmp --image=busybox --restart=Never -n hello -it --rm \
      -- wget echoserver.external.svc.cka.example.com:8080
    Connecting to echoserver.external.svc.cka.example.com:8080 \
    (10.104.248.24:8080)
    saving to 'index.html'
    index.html           100% |********************************|   \
    460  0:00:00 ETA
    'index.html' saved
    pod "tmp" deleted
    
    

第六章,“存储”

  1. 首先创建名为logs-pv.yaml的新文件。内容如下:

    kind: PersistentVolume
    apiVersion: v1
    metadata:
      name: logs-pv
    spec:
      capacity:
        storage: 2Gi
      accessModes:
        - ReadWriteOnce
        - ReadOnlyMany
      persistentVolumeReclaimPolicy: Delete
      storageClassName: ""
      hostPath:
        path: /tmp/logs
    

    创建持久卷对象并检查其状态:

    $ kubectl create -f logs-pv.yaml
    persistentvolume/logs-pv created
    $ kubectl get pv
    NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS \
      CLAIM              STORAGECLASS   REASON   AGE
    logs-pv   2Gi        RWO,ROX        Delete           Bound  \
      default/logs-pvc                           16m
    
  2. 创建文件logs-pvc.yaml以定义持久卷声明。以下 YAML 清单显示了其内容:

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: logs-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: ""
      resources:
        requests:
          storage: 1Gi
    

    创建持久卷声明对象并检查其状态:

    $ kubectl create -f logs-pvc.yaml
    persistentvolumeclaim/logs-pvc created
    $ kubectl get pvc
    NAME       STATUS   VOLUME    CAPACITY   ACCESS MODES \
      STORAGECLASS   AGE
    logs-pvc   Bound    logs-pv   2Gi        RWO,ROX      \
                     17m
    
  3. 使用--dry-run命令行选项创建基本的 YAML 清单:

    $ kubectl run nginx --image=nginx --dry-run=client --restart=Never \
      -o yaml > nginx-pod.yaml
    

    现在,编辑文件nginx-pod.yaml并将持久卷声明绑定到它:

    apiVersion: v1
    kind: Pod
    metadata:
      creationTimestamp: null
      labels:
        run: nginx
      name: nginx
    spec:
      volumes:
        - name: logs-volume
          persistentVolumeClaim:
            claimName: logs-pvc
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
          - mountPath: "/var/log/nginx"
            name: logs-volume
        resources: {}
      dnsPolicy: ClusterFirst
      restartPolicy: Never
    status: {}
    

    使用以下命令创建 Pod 并检查其状态:

    $ kubectl create -f nginx-pod.yaml
    pod/nginx created
    $ kubectl get pods
    NAME    READY   STATUS    RESTARTS   AGE
    nginx   1/1     Running   0          8s
    
  4. 使用exec命令打开与 Pod 交互的 shell,并在挂载的目录中创建文件:

    $ kubectl exec nginx -it -- /bin/sh
    # cd /var/log/nginx
    # touch my-nginx.log
    # ls
    access.log  error.log  my-nginx.log
    # exit
    
  5. 删除 Pod 和持久卷声明。由于其回收策略,持久卷将被自动删除:

    $ kubectl delete pod nginx
    $ kubectl delete pvc logs-pvc
    $ kubectl get pv,pvc
    No resources found
    
  6. 您可以使用以下命令列出存储类。如果使用 Minikube,可能只会找到一个存储类,默认的存储类名为standard,使用提供程序k8s.io/minikube-hostpath

    $ kubectl get sc
    NAME                 PROVISIONER                RECLAIMPOLICY \
      VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    standard (default)   k8s.io/minikube-hostpath   Delete        \
      Immediate           false                  22d
    
  7. 为存储类创建文件custom-sc.yaml。YAML 清单可能如下所示:

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: custom
    provisioner: k8s.io/minikube-hostpath
    

    使用以下命令创建存储类。列出所有存储类会显示默认存储类和新的存储类:

    $ kubectl create -f custom-sc.yaml
    storageclass.storage.k8s.io/custom created
    $ kubectl get sc
    NAME                 PROVISIONER                RECLAIMPOLICY \
      VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    custom               k8s.io/minikube-hostpath   Delete        \
      Immediate           false                  11s
    standard (default)   k8s.io/minikube-hostpath   Delete        \
      Immediate           false                  22d
    
  8. 创建文件custom-pvc.yaml以定义持久卷声明。以下 YAML 清单显示了其内容:

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: custom-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      storageClassName: custom
      resources:
        requests:
          storage: 500Mi
    

    创建持久卷声明对象并检查其状态:

    $ kubectl create -f custom-pvc.yaml
    persistentvolumeclaim/custom-pvc created
    $ kubectl get pv,pvc
    NAME                                                        CAPACITY \
      ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM   STORAGECLASS \
        REASON   AGE
    persistentvolume/pvc-6fe081b5-e425-45cd-a94a-3488ce24cb87   500Mi    \
      RWO            Delete           Bound    default/custom-pvc   custom \
                       13s
    
    NAME                               STATUS \
      VOLUME                                     CAPACITY   ACCESS MODES \
       STORAGECLASS   AGE
    persistentvolumeclaim/custom-pvc   Bound  \
      pvc-6fe081b5-e425-45cd-a94a-3488ce24cb87   500Mi      RWO \
                custom           13s
    
  9. 将持久卷的名称写入文件pv-name.txt

    $ echo "pvc-6fe081b5-e425-45cd-a94a-3488ce24cb87" > pv-name.txt
    
  10. 删除持久卷声明将同时删除绑定的持久卷:

    $ kubectl delete pvc custom-pvc
    $ kubectl get pv,pvc
    No resources found
    

第七章,“故障排除”

  1. 首先创建名为multi-container.yaml的 Pod 的 YAML 起点文件。以下命令创建文件:

    $ kubectl run multi --image=nginx:1.21.6 -o yaml --dry-run=client \
      --restart=Never > multi-container.yaml
    

    编辑 YAML 清单。添加 sidecar 容器。以下 YAML 文件的内容可能如下所示:

    apiVersion: v1
    kind: Pod
    metadata:
      name: multi
    spec:
      containers:
      - image: nginx:1.21.6
        name: nginx
      - image: busybox:1.35.0
        name: streaming
        args: [/bin/sh, -c, 'tail -n+1 -f /var/log/nginx/access.log']
    
  2. 添加卷定义并将其挂载到两个容器中。最终的 YAML 清单如下所示:

    apiVersion: v1
    kind: Pod
    metadata:
      name: multi
    spec:
      containers:
      - image: nginx:1.21.6
        name: nginx
        volumeMounts:
        - name: accesslog
          mountPath: /var/log/nginx
      - image: busybox:1.35.0
        name: streaming
        args: [/bin/sh, -c, 'tail -n+1 -f /var/log/nginx/access.log']
        volumeMounts:
        - name: accesslog
          mountPath: /var/log/nginx
      volumes:
      - name: accesslog
        emptyDir: {}
    

    通过指向 YAML 文件的create命令创建 Pod:

    $ kubectl create -f multi-container.yaml
    
  3. 确定 Pod 的 IP 地址。在以下示例中,IP 地址为10.244.2.3。使用临时 Pod 对 nginx 进行三次调用。streaming容器的日志有三个条目:

    $ kubectl get pod multi -o wide
    NAME    READY   STATUS    RESTARTS   AGE     IP           NODE         \
      NOMINATED NODE   READINESS GATES
    multi   2/2     Running   0          3m23s   10.244.2.3   minikube-m03 \
      <none>           <none>
    $ kubectl run tmp --image=busybox --restart=Never -it --rm \
      -- wget 10.244.2.3
    $ kubectl run tmp --image=busybox --restart=Never -it --rm \
      -- wget 10.244.2.3
    $ kubectl run tmp --image=busybox --restart=Never -it --rm \
      -- wget 10.244.2.3
    $ kubectl logs multi -c streaming
    10.244.1.2 - - [27/Jan/2022:16:44:25 +0000] "GET / HTTP/1.1" 200 \
    615 "-" "Wget" "-"
    10.244.1.3 - - [27/Jan/2022:16:44:29 +0000] "GET / HTTP/1.1" 200 \
    615 "-" "Wget" "-"
    10.244.1.4 - - [27/Jan/2022:16:44:32 +0000] "GET / HTTP/1.1" 200 \
    615 "-" "Wget" "-"
    
  4. 创建 Pods stress-1stress-2。以下 YAML 清单显示了名为stress-1的 Pod 在文件stress-1-pod.yaml中的定义。创建第二个 YAML 文件,并相应更改 Pod 名称:

    apiVersion: v1
    kind: Pod
    metadata:
      name: stress-1
    spec:
      containers:
      - image: polinux/stress:1.0.4
        name: consumer
        resources:
          limits:
            memory: "250Mi"
          requests:
            memory: "250Mi"
        args: [/bin/sh, -c, 'stress --vm 1 --vm-bytes \
               $(shuf -i 20-200 -n 1)M --vm-hang 1']
    

    创建 Pod 并检查其状态:

    $ kubectl create -f stress-1-pod.yaml
    $ kubectl create -f stress-2-pod.yaml
    $ kubectl get pods
    NAME       READY   STATUS    RESTARTS   AGE
    stress-1   1/1     Running   0          15m
    stress-2   1/1     Running   0          6m28s
    
  5. 如果集群尚未安装metrics server,请安装它。从 metrics 服务器检索 Pod 的指标。在下面的示例中,名为stress-2的 Pod 消耗了更多内存。将 Pod 名称写入文件max-memory.txt

    $ kubectl top pods
    NAME       CPU(cores)   MEMORY(bytes)
    stress-1   32m          93Mi
    stress-2   47m          117Mi
    $ echo "stress-2" > max-memory.txt
    
  6. 你可以在检出的 GitHub 仓库 bmuschko/cka-study-guide 的文件 app-a/ch07/troubleshooting-pod/solution/solution.md 中找到解决方案。

  7. 你可以在检出的 GitHub 仓库 bmuschko/cka-study-guide 的文件 app-a/ch07/troubleshooting-deployment/solution/solution.md 中找到解决方案。

  8. 你可以在检出的 GitHub 仓库 bmuschko/cka-study-guide 的文件 app-a/ch07/troubleshooting-service/solution/solution.md 中找到解决方案。

  9. 你可以在检出的 GitHub 仓库 bmuschko/cka-study-guide 的文件 app-a/ch07/troubleshooting-control-plane-node/solution/solution.md 中找到解决方案。

  10. 你可以在检出的 GitHub 仓库 bmuschko/cka-study-guide 的文件 app-a/ch07/troubleshooting-worker-node/solution/solution.md 中找到解决方案。

posted @ 2025-11-16 08:58  绝不原创的飞龙  阅读(7)  评论(0)    收藏  举报