Kubernetes-认证管理员考试指南-全-
Kubernetes 认证管理员考试指南(全)
原文:Acing the Certified Kubernetes Administrator Exam
译者:飞龙
前言
前言
我是在 2017 年开始我的 Kubernetes 之旅的,当时我在德克萨斯州奥斯汀的一家小型金融科技公司工作。我被要求将服务器从 Amazon EC2(亚马逊弹性云计算)迁移到 AKS(Azure Kubernetes 服务)。我之前从未听说过 Kubernetes,但管理层的决定是坚定的,所以我被推到了火里。我疯狂地浏览文档网站、博客、书籍以及我能找到的任何东西,试图吸收这项新技术。我记得我害怕参加站立会议,因为我只是让自己更加困惑,并没有取得任何实质性的进展。我陷入了困境,需要一些帮助。我需要的是比我稍微领先一点的人——一个对 Kubernetes 足够了解,能帮我摆脱困境的人。
在这次经历不久之后,我将帮助他人成功学习 Kubernetes 作为我的目标。恰好这和第一次认证 Kubernetes 管理员(CKA)考试的时间相吻合,这次考试为正确管理 Kubernetes 必须具备的技能提供了一个蓝图。我在 2018 年创建了第一个 CKA 准备课程,并从那时起一直在分享我的 Kubernetes 知识。今天,我很高兴地说,我已经通过我的课程和内容帮助了成千上万的人。这本书是我兴奋地分享我的知识和经验的一种新媒介。
这本书是关于通过 CKA 考试的完整指南,包含了场景、练习和课程,帮助你练习并彻底吸收内容。CKA 考试与其他很多考试不同,你将在第一章中发现这一点。通过持续的练习和努力,你将为 CKA 考试做好充分的准备。
我自 2018 年以来一直持有 CKA 认证,并在过去一年内再次参加考试以获得再认证,并将最新的信息传递给你,这样你就有最好的机会获得你的证书。我祝愿你在考试中一切顺利,但你可以确信,在阅读这本书后,你已经做好了充分的准备。你一定能做到!
致谢
与 Manning 合作撰写书籍是一次非常广泛且令人大开眼界的经历。我对 Manning 对每本书所采取的勤奋过程深感钦佩和尊重,这无疑为这本书增添了巨大的价值。没有我的开发编辑 Connor O’Brien,我无法完成这项工作。通过他的仔细检查和深思熟虑的审查,我学到了很多。
我要感谢 Curtis Bates,他是这本书的技术编辑,拥有 25 年在分布式计算、云和 HPC 领域担任软件架构师、系统工程师和软件开发者的经验。
致所有审稿人——Alessandro Campeis、Amit Lamba、Bradford Hysmith、Dale Francis、Dan Sheikh、David Moravec、Dylan Scott、Emanuele Piccinelli、Ernesto Cárdenas Cangahuala、Frankie Thomas-Hockey、Ganesh Swaminathan、Giampiero Granatella、Giang Châu、Ioannis Polyzos、John Harbin、Joseph Perenia、Kamesh Ganesan、Michael Bright、Michele Adduci、Morteza Kiadi、Roman Levchenko、Shawn Bolan、Simeon Leyzerzon、Simon Tschöke、Stanley Anozie、Swapneelkumar Deshpande 和 Tim Sina——谢谢你们,你们的建议帮助使这本书变得更好。
我还想感谢我的妻子,Georgianne,因为她鼓励我写作,并在许多夜晚和周末照顾孩子,而我则在最近的咖啡馆里敲击键盘。
关于这本书
适合阅读这本书的人
这本书是为那些寻求获得认证的人准备的,当然,但它也适合那些希望遵循经过验证和认可的 Kubernetes 学习方法的人。通过遵循考试蓝图,你会很高兴地发现这本书包含了成为 Kubernetes 管理员所需的所有必要组件。话虽如此,这不是一本 Kubernetes 入门书籍,它将要求你了解如何导航 Linux 操作系统,并具备对 Kubernetes 及其旨在解决的问题的基本理解。
Kubernetes 经验将帮助你进一步发展你的职业生涯,因为它越来越多地被许多财富 500 强公司采用。这个高级技能集需求很高,在你的简历上拥有这个认证将为你的简历增添巨大的价值,这大大增加了增加你薪资的可能性。
这本书是如何组织的:路线图
这本书非常实用,就像考试一样。你应该计划完成实际操作练习,对于这些练习,你不需要任何特殊硬件——一个 Mac、Windows 或 Linux 桌面就足够了。我们将使用 kind Kubernetes 设置本地集群;这些设置说明可以在附录 A 中找到。当你完成练习时,你将在附录 D 中找到如何解决它们的额外信息。
本书非常紧密地遵循云原生计算基金会(CNCF)的考试标准,因为你会希望确保所有领域都被涵盖,并且你为考试日做好了充分的准备。本书从对考试的介绍开始,介绍考试是什么,以及如何准备考试,然后第二章从集群架构、安装和配置的 CKA 考试能力开始。接着,本书继续介绍工作负载和调度,然后是服务和网络、存储,最后是故障排除。
第二章和第三章直接进入部署 Kubernetes 集群的基础设施配置,使用 kubeadm 在 Kubernetes 集群上进行版本升级,实施 etcd 备份和恢复,管理基于角色的访问控制,以及管理高可用性 Kubernetes 集群,以全面覆盖集群架构、安装和配置考试标准。
第四章和第五章在前面章节的基础上,通过配置运行在 Kubernetes 之上的应用程序,使用 ConfigMaps 和 Secrets,并继续解释资源限制如何影响 Pod 调度。你将了解如何使用清单管理和常见的模板工具,以及用于在 Kubernetes 中创建自愈应用程序的原语。然后,你将了解如何扩展在 Kubernetes 中运行的应用程序,以及更新 Deployments 如何与滚动更新和回滚协同工作。为了完善你的知识,你将了解如何创建、更新和管理容器化应用程序,这将完成考试的工作负载和调度领域。
第六章帮助你理解 Kubernetes 中的网络工作方式,包括集群节点上的网络配置和 Pod 之间的连接性。你将了解 Kubernetes 中服务类型,包括 ClusterIP、NodePort 和 LoadBalancer。你将了解 Ingress 控制器和 Ingress 资源在 Kubernetes 中的工作方式,以及如何配置和使用 CoreDNS。为了完善本章,你将能够选择合适的容器网络接口插件,这将完成服务和网络考试标准。
第七章探讨了卷和存储在 Kubernetes 中的工作方式,包括存储类、持久卷和持久卷声明。你将了解卷的模式、访问模式和回收策略。你还将学习如何配置具有持久存储的应用程序,这将完善考试中的存储领域。
第八章转向解决 Kubernetes 集群中的问题。你将学习如何评估集群和节点日志,以及如何监控在 Kubernetes 中运行的应用程序。你还将学习如何管理容器标准输出(标准输出)和标准错误(标准错误)日志,以及如何解决应用程序故障、集群组件故障和网络问题。这将完全满足考试标准的故障排除部分。
第九章是逐章复习,旨在为你提供考试前一晚的复习材料,以防你需要最后时刻快速回顾书中的任何内容。这种复习还可以帮助你建立信心,因为你可以在检查完第九章的每一节后感觉准备得更好。不要忘记每个章节中的练习!到本书结束时,你应该对自己的 Kubernetes 集群管理充满信心,并准备好在不久之后参加考试。
关于练习
在每一章中,你将看到各种练习,这些练习将测试你对上一节所读内容的理解。我鼓励你独立完成这些练习,在你的本地集群上,或者如果你想通过浏览器访问免费的集群,请访问killercoda.com。如果你需要帮助,请参阅附录 D,了解如何解决所有练习。在终端中练习这些练习,除了主要示例场景外,对于准备考试日也将至关重要。
关于代码
在某些情况下,我可能会引用 YAML 文件或配置文件,所有这些文件都在以下 GitHub 仓库中:github.com/chadmcrowell/acing-the-cka-exam。这本书包含许多源代码示例,这些代码以固定宽度字体的形式呈现,以区别于普通文本。在许多情况下,原始源代码已被重新格式化;我们添加了换行符并重新调整了缩进,以适应书籍中的可用页面空间,并且一些列表中包含行续接标记(➥)。
你可以从这本书的 liveBook(在线)版本中获取可执行的代码片段,网址为livebook.manning.com/book/acing-the-certified-kubernetes-administrator-exam。书中示例的完整代码可以从 Manning 网站www.manning.com/books/acing-the-certified-kubernetes-administrator-exam和 GitHubgithub.com/chadmcrowell/acing-the-cka-exam下载。
liveBook 讨论论坛
购买 通过认证的 Kubernetes 管理员考试 包括对 liveBook(在线阅读平台)的免费访问。使用 liveBook 的独家讨论功能,你可以在全球范围内或针对特定章节或段落附加评论。为自己做笔记、提问和回答技术问题,以及从作者和其他用户那里获得帮助都非常简单。要访问论坛,请访问livebook.manning.com/book/acing-the-certified-kubernetes-administrator-exam/discussion。你还可以在livebook.manning.com/discussion了解更多关于 Manning 论坛和行为准则的信息。
Manning 对读者的承诺是提供一个平台,让读者之间以及读者与作者之间可以进行有意义的对话。这并不是对作者参与特定数量活动的承诺,作者对论坛的贡献仍然是自愿的(且未付费)。我们建议你尝试向作者提出一些挑战性的问题,以免他们的兴趣转移!只要这本书还在印刷中,论坛和以前讨论的存档将可通过出版商的网站访问。
其他在线资源
在第一章中包含了你可以在考试期间打开的资源,这些资源可以补充考试准备。你将能够在虚拟机内的浏览器中访问以下文档及其子域名:kubernetes.io/docs/ 和 kubernetes.io/blog/。这包括这些页面的所有可用语言翻译(例如,kubernetes.io/zh/docs/)。所有这些资源都将帮助你准备考试,并且熟悉这些有用的工具对于考试当天将大有裨益。例如,Kubernetes 文档网站有一个搜索功能,你可以使用某些关键词快速导航到资源,这在第九章中有更深入的介绍。
关于作者

Chad M. Crowell 是 Raft 公司的 DevSecOps 工程师,同时也是微软认证培训师。Chad 已经与 A Cloud Guru 和 INE 等公司合作发布了八门关于 DevOps 和 Kubernetes 的课程。目前,他领导着一个名为 KubeSkills 的社区,通过辅导和小组学习的方式帮助人们学习 Kubernetes 和容器。你可以在 community.kubeskills.com 的 KubeSkills 社区中找到 Chad 的帖子。他还在 YouTube 上有账号 YouTube.com/@kubeskills,并在 Twitter 上以 @chadmcrowell 为名。
关于封面插图
封面上的图片是来自 《通过认证 Kubernetes 管理员考试》 的“Tehinguise ou Danseuse Turcque”,或称为“土耳其舞者”,这幅画取自雅克·格拉塞·德·圣索沃尔(Jacques Grasset de Saint-Sauveur)的作品集,该作品集于 1788 年出版。每一幅插图都是手工精心绘制和着色的。
在那些日子里,人们通过他们的服饰很容易就能识别出他们住在哪里,以及他们的职业或社会地位。Manning 通过基于几个世纪前丰富多样的地区文化,并由像这样的作品集中的图片重新呈现的封面,来庆祝计算机行业的创新精神和主动性。
1 第一步
本章涵盖
-
介绍认证 Kubernetes 管理员考试
-
定义 Kubernetes 管理员
-
认识 Kubernetes 及其解决的问题
-
介绍 Kubernetes API
-
Kubernetes 组件和服务,以及 Linux 后端服务
-
声明性和命令性命令
欢迎加入 通过认证 Kubernetes 管理员考试。如果你已经购买了这本书,那么你很可能已经研究了考试,知道它是什么,甚至可能已经安排了考试。如果没有,不要担心;我们将讨论考试是什么以及如何尽快报名。对于那些想直接进入正题的人,请继续阅读本节,因为它对你来说很可能是复习。你可以跳到第二部分,那里我们将深入探讨考试课程的核心内容。
对于那些刚开始接触 CKA 考试的人来说,让我们先了解一下考试是什么以及它包含的内容。首先,让我先说,我很高兴你决定加入我们,一起踏上获得 Kubernetes 认证的旅程。获得认证 Kubernetes 管理员(CKA)认证是一项相当大的成就,并将极大地推动你的职业生涯。此外,你将成为一个大型群体中的一员,这个群体包括全球超过 32,000 人。你可能想知道这是否值得,对此,我会说值得,原因如下:
-
Kubernetes 和分布式系统将会长期存在。
-
Kubernetes 技能需求量大。
-
通过认证将有助于巩固你对 Kubernetes 的理解,并表明你在 Kubernetes 方面知识全面、熟练。
1.1 介绍 CKA 考试
现在,让我们深入了解考试的内容。CKA 考试是一种独特的胜任力测试。与其他考试不同,它不是选择题或填空题,而是在 PSI 服务(考试提供者)提供的远程桌面环境中,完全在 Linux 终端(Ubuntu XFCE)内执行。是的,没错;他们会给你一组任务来完成,你将通过在提供的远程桌面环境中的终端内输入命令来执行解决方案。这就是整个考试体验,你将有两个小时的时间完成 15-20 个这样的任务。一旦完成,你将根据任务的成果进行评分,无论你采取了哪种途径来实现成果。这意味着可能存在多种解决给定任务的方法。在这本书的整个过程中,你将学习不同的方法来获得相同的结果,这将为你提供更多的工具,帮助你在这场考试中取得及格的分数,及格分数是 66%或更高。你也可以在任何一个任务中获得部分分数,这将有助于提高你的成功率。
在本书中,我们将针对每一章中每个主题可用的技巧和窍门进行讨论。这将很好地与运用你的课程内容相结合,并为你提供必要的技能,以自信地应对考试。这些考试技巧,结合你的决心和反复练习,将有助于你在通过 CKA 考试中取得成功。我无法强调肌肉记忆和投入练习对于在考试时间来临时帮助你的大脑保留和访问适当的 Kubernetes 命令有多么重要。真正掌握 CKA 确实是一项锻炼,所以如果你决心通过考试,你不应该在学习期间长时间休息,尤其是如果你不是每天与 Kubernetes 一起工作。尽管如此,不要因此气馁;我们将一起完成这个 Kubernetes 锻炼!
CKA 考试由 Linux 基金会提供,Kubernetes 由云原生计算基金会(CNCF)维护。截至本书编写时,考试费用为 375 美元,但请访问 Linux 基金会网站 training.linuxfoundation.org 查看最新的价格和不同货币的价格。这个价格可能比同类认证略高,但它们确实允许免费重考一次,以及在考试期间打开一个额外的浏览器标签页,访问以下网站及其子域名:
为了参加考试,你需要一台运行 Windows 8.1、Windows 10、Windows 11、macOS 10.15、macOS 11、macOS 12、Ubuntu 18.04 或 Ubuntu 20.04 的电脑,并且已经安装了 Chrome 网络浏览器(所有浏览器都受支持,但 PSI 强烈推荐使用 Chrome)。当你开始考试时,你会被告知下载并安装一个新的 PSI 安全浏览器,这将自动为你提供访问 PSI 监考平台,称为PSI Bridge,这是一个远程桌面环境。远程桌面环境将包括打开终端以及提供的 Firefox 浏览器的链接(你必须使用 Firefox)以便你可以浏览到之前列出的授权网站。你还需要一个至少具有 640 × 480 像素分辨率的摄像头,因为它们将需要房间的一个 360 度视图(台式机需要外部摄像头)并在整个考试过程中监视你。你的电脑屏幕分辨率必须为 1368 x 769 像素或更高(不支持双显示器),你必须有一个功能正常的麦克风,并且你的互联网带宽速度必须至少为 300 Kbps 以进行下载和上传。你参加考试的地方必须安静且光线充足。不允许在公共空间,如咖啡馆或商店进行考试。你的桌子必须清理干净,不得放置任何纸张和其他电子产品,并且你必须清晰地出现在你的摄像头的中心框架中。
在考试当天,你将坐在电脑前,确保电脑已连接电源,然后访问 Linux 基金会门户开始考试。在点击“开始考试”按钮之前,请确保所有浏览器标签页都已关闭,且后台没有运行其他应用程序(监考官也会检查这一点)。一旦你点击开始考试的按钮,你将立即遇到一位考试监考官。这位监考官会检查你的环境,确保你的桌子是清理过的,周围没有纸张或未经授权的电子产品。因此,使用你的摄像头,你将环顾四周以获得完整的 360 度视角,并等待他们的批准。他们还会定期检查你的双手和手腕。在考试开始之前,监考官首先会检查你的双手和手腕,然后在考试过程中,他们还会在频繁的间隔中让你停下来,要求你展示双手和手腕的两侧。在他们将考试释放给你之前,他们还会要求你出示政府颁发的身份证件,你必须将其举到摄像头前。一旦监考官完成身份验证和检查工作空间,他们将会释放考试,这意味着他们允许你进入考试并查看第一题。你会发现每道题都有类似的格式,包括你必须使用的上下文以及你必须通过命令行执行的任务来解决该问题。如果你遇到无法回答的问题,我的建议是跳过它;你可以在考试期间随时标记它并返回。在考试开始时,他们会通过简短的自动化演示向你展示如何标记问题以供审查。对于每个任务,你也会看到该任务占的百分比。如果你对某事感到困惑,看看它值多少分。如果它值,比如说,5%,那么就继续跳过它。
考试技巧:如果你需要在 Firefox 浏览器和终端之间复制粘贴文本,请使用快捷键 CTRL-SHIFT-C 来复制,CTRL-SHIFT-V 来粘贴。
你将被测试的核心能力包括集群架构、安装和配置;工作负载和调度;服务和网络;存储;以及故障排除。本书将涵盖所有这些领域。在集群架构能力方面,这将占考试问题的 25%,你将接受基于角色的访问控制、使用 kubeadm 添加功能和更新 Kubernetes 集群、以及备份和恢复 etcd 数据存储的测试。在工作负载和调度能力方面,这将占考试问题的 15%,你将接受执行滚动更新和回滚、以及扩展应用程序和使用 ConfigMaps 和 Secrets 的测试。在服务和网络能力方面,这将占考试问题的 20%,你将接受在 Kubernetes 中创建和更新各种服务、使用 Ingress、DNS 和 Kubernetes 的容器网络接口的测试。在存储能力方面,这将占考试问题的 10%,你需要了解 Kubernetes 中的存储类、持久卷和卷模式。然后,在故障排除能力方面,这是考试问题的 30%,你将需要知道如何从 Kubernetes 集群中获取日志,以及监控和修复核心集群组件。表 1.1 概述了这些领域及其能力百分比。
表 1.1 考试能力和其占考试百分比
| 集群架构、安装和配置—25% |
|---|
|
-
管理基于角色的访问控制(RBAC)。
-
使用 kubeadm 安装基本集群。
-
管理高可用性 Kubernetes 集群。
-
使用 kubeadm 在 Kubernetes 集群上执行版本升级。
-
实施 etcd 备份和恢复。
|
| 工作负载和调度—15% |
|---|
|
-
理解 Deployments 以及如何执行滚动更新和回滚。
-
使用 ConfigMaps 和 Secrets 配置应用程序。
-
了解如何扩展应用程序。
-
理解用于创建健壮、自我修复的应用程序部署的原语。
-
理解资源限制如何影响 Pod 调度。
-
了解清单管理和常用模板工具。
|
| 服务和网络—20% |
|---|
|
-
在集群节点上理解主机网络配置。
-
理解 Pod 之间的连接性。
-
理解 ClusterIP、NodePort 和 LoadBalancer 服务类型和端点。
-
了解如何使用 Ingress 控制器和 Ingress 资源。
-
了解如何配置和使用 CoreDNS。
-
选择合适的容器网络接口插件。
|
| 存储—10% |
|---|
|
-
理解存储类和持久卷。
-
理解卷和访问模式以及卷的回收策略。
-
理解持久卷声明原语。
-
了解如何配置具有持久存储的应用程序。
|
| 故障排除—30% |
|---|
|
-
评估集群和节点日志。
-
理解如何监控应用程序。
-
管理容器标准输出和标准错误日志。
-
故障排除应用程序故障。
-
故障排除集群组件故障。
-
故障排除网络问题。
|
在考试期间,你将拥有六个集群可供使用,你将需要根据问题在这些集群之间进行切换。通常,每个问题都会要求你在与上一个问题不同的集群上执行任务。他们将提供如何在集群之间切换的说明,所以不必过于担心记住集群名称和切换集群的命令。
考试还将为 kubectl 设置别名 k。别名是你运行的一个与另一个命令相关的命令。例如,大多数 Linux 操作系统都存在一个常见的别名 l='ls -lah',这意味着当你输入命令 l 时,它与输入 ls -lah 是相同的。同样,对于 CKA 考试,当你输入命令 k 时,它与命令 kubectl 相同。从你电脑的命令行中,你可以输入 alias 来列出你电脑上所有现有的别名。所有六个集群都只有一个控制平面节点,其中两个集群只有一个工作节点,这两个节点中有一个缺少工作节点。它们都将安装容器网络接口(CNI),并命名为 k8s、hk8s、bk8s、wk8s、ek8s 和 ik8s,如图 1.1 所示。

图 1.1 考试环境中所有六个集群的集群配置
有这么多人注册了 CKA 考试,以至于它已经成为迄今为止最受欢迎的 Linux 基金会认证之一。这部分反映了认证的需求,但也反映了获得认证后的可信度。证书有效期为三年,重新认证的过程与你第一次参加考试时相同。
我建议你现在就安排考试,这样你就有了一个完成考试的目标和截止日期。这将让你保持动力完成这本书,并提供必要的时间表,以保持你的知识在心中,并适当准备参加考试。如果你目前在工作中使用 Kubernetes,并且你每天都在输入 kubectl 命令,那么请安排你在今天起一个月后参加考试。
如果你刚刚接触 Kubernetes 主题,之前从未听说过它,那么请安排你在考试调度器允许的最晚日期参加考试(通常这是三个月,但可能更短。)你总是可以重新安排,但关键是要给自己两样东西:(1)一个截止日期,这样你就可以认真对待这次考试并实现你的预期结果;(2)日常练习,将其融入你的前额叶皮层(记忆存储的地方)。这正是你保持新鲜和为考试做好准备所需要的东西。
1.2 有哪些内容
在整本书中,我将插入练习和类似考试的情景,让您练习kubectl命令,并为考试做准备。为了使练习尽可能容易,我包括了创建您本地集群的说明。在附录 A 中,我将向您介绍使用 kind 创建 Kubernetes 集群的步骤。Kind Kubernetes (kind.sigs.k8s.io/) 是一个免费、轻量级且易于使用的工具,可以在您现有的笔记本电脑或台式机上创建集群。唯一的要求是 Docker,只需一条命令,您就能在几秒钟内拥有一个集群并运行起来。这消除了练习 CKA 的入门障碍。我建议您使用这种方法,因为您可能会浪费大量时间手动构建集群;而且由于考试将为您准备好的集群,我认为这是学习和跟随这本书的最佳方式。还有其他创建本地集群的方法,您也可以使用,例如 minikube 或 MicroK8s。想法是相似的,但那些工具可能不会直接与本书中的场景相匹配。例如,在第五章中,我们将准备一个缺少节点的集群。设置环境的步骤是 kind 特有的,因此在 minikube 中重现环境将有所不同。
在本书的每一章中,将会有一个类似于真实考试的情景,我们将一起解决,然后是您可以独立完成的额外练习。在附录 D 中,您可以回顾解决这些练习的提示。您可以使用这些提示来测试您对本书内容的了解,并评估您对 CKA 考试的准备情况。
在我们开始之前,我想澄清一些要点,并让您了解这本书是什么以及它不是什么。这本书不是 Kubernetes 的入门介绍,所以我期望您对容器有背景知识,并理解 Kubernetes 解决的问题,因为这些内容本书不会涉及。CKA 考试不是入门级考试;因此,它将需要相当多的使用和导航 Linux 操作系统的经验。那些理解 cgroups 和 namespaces(在 Linux 中提供容器)的人,将更容易跟随这本书,并随后通过考试。此外,大多数参加这次考试的人已经在他们的工作中使用这项技术,无论是通过一个新的项目接触它,还是已经将集群管理作为他们角色中的主要功能。我并不是说没有与 Kubernetes 合作的人不能通过考试,但直接接触现实场景,以日常、实际的方式运用您的知识,更有可能带来成功。
1.2.1 Kubernetes 管理员是什么?
Kubernetes 管理员的职责有两方面。Kubernetes 管理员了解 Kubernetes 的内部工作原理以及如何将其转化为对最终用户的价值。以下是一份 Kubernetes 管理员职位发布的内容:“Kubernetes 管理员的职责是通过开发持续改进、工具和自动化,确保公司的服务在期望的可靠性、性能和可用性水平上满足客户需求。你必须知道如何安装、配置、部署、更新和修补 Kubernetes 基础设施中的每个组件,以确保服务和底层系统得到适当的监控并具有足够的可观察性。这包括识别和监控适当的 KPI(关键绩效指标)以确保服务健康,最小化 MTTA(平均确认时间)和 MTTR(平均修复时间)。”
我们不妨实话实说;考试的难度仍然很高,因此需要这本书。但你在阅读这本书后,将开始意识到 Kubernetes 的复杂性——不仅在于如何构想这些复杂性以及它们如何与你已知的工程和技术知识相关联,还在于如何深入复杂性以进行故障排除并确定 Kubernetes 集群内操作的根本原因。
1.3 了解 Kubernetes
这引出了下一个要点——Kubernetes 集群。它是什么?Kubernetes 集群被称为“集群”,因为它是由协同工作的机器组成的 RESTful API,就像图 1.2 所示。就像一个服务器农场一样,机器相互连接并位于同一设施或网络内。但在 Kubernetes 中的关键区别是,连接的服务器不仅能够适当地分配负载,而且可以轻松交换数据以消除单点故障。如果一个节点失败,它不会使整个集群崩溃。在这本书的整个过程中,我们将把服务器称为“节点”。“节点”是一个特定于集群的术语,通常表示服务器是更大系统的一部分。我们将在第二章中更多地讨论集群的解剖结构。

图 1.2 集群中协同工作的服务器(称为节点)。当其中一个失败时,它会将自己从集群中移除以进行修复。
注意:Kubernetes 通常简称为 K8s(发音为 kates),其中 K 和 S 之间的 8 代表这两个字母之间的字符数(“ubernete” = 8 个字符)。
Kubernetes 不过是一块软件,您通过 REST API 与之交互。RESTful API 是一个定义良好、高度可扩展、松耦合的应用架构,它优先考虑通过网络进行通信——更重要的是,通过网络传输资源的状态。我无法强调记住这一点的重要性,即 Kubernetes 背后有一个 API(一个 RESTful API,它是一组可以通过该 API 进行操作的资源)。资源是这里的关键词。资源是我们如何在 Kubernetes 中定位对象的方式。有时在 Kubernetes 社区中,我们使用资源和对象这两个词可以互换使用,但它们之间有一个根本的区别。对象可以有多个资源地址;例如,Deployments 可以根据 API 版本或根据 Deployment 名称拥有多个 URI(统一资源标识符),如图 1.3 所示。Deployments在 Kubernetes 中是对象,它们为您在 Kubernetes 上运行的应用程序中的 Pods 和 ReplicaSets 提供更自动化的控制。ReplicaSets 是 Kubernetes 中的另一种对象类型,它提供了一个运行指定数量 Pods(即 Pod 副本)的控制循环。您可以使用kubectl api-resources命令从任何 Kubernetes 集群列出所有可用的 API 资源。

图 1.3 Kubernetes 通过资源 URI 来定位对象,其中多个 URI 可以指向单个对象。
我们通常通过一个名为kubectl的命令行工具与 Kubernetes API 及其中的对象进行交互。在这本书中,我们将仅使用kubectl,因为这是在考试中用于与 Kubernetes API 接口的命令行工具。除了证书外,还需要kubectl命令行工具来创建、读取、更新和删除 Kubernetes 资源。图 1.4 展示了为我们提供执行 Kubernetes 集群中某些操作所需基于角色的访问权限的证书。

图 1.4 kubectl是用于访问 Kubernetes 对象的工具,在 kubeconfig 内部提供有效的证书。
1.3.1 集群架构、安装和配置
如前所述,集群的架构由节点组成,在这些节点上运行着 Pod。Pod 是 Kubernetes 中最小的可部署单元,包含一个或多个容器。在 API 的抽象中,创建了由ReplicaSets组成的资源,这些 ReplicaSets 又运行在多个节点上的一个或多个 Pod。在 Kubernetes 中,有两种类型的节点——控制平面节点和工作节点。控制平面节点运行 API 服务器、DNS、控制器管理器、调度器、kube-proxy 和 etcd 数据存储。所有这些部分及其关系都在图 1.5 中展示。

图 1.5 显示了 Kubernetes 架构,其中包括节点、Pod、部署和副本集。
与控制平面通信通过 API 在控制平面节点上进行。工作节点通过 kubelet 获取来自控制平面的指令,以履行其职责,不仅运行容器,还要报告健康状况,向控制平面提供持续的状态。工作节点承担着大部分负载,这使得它们在 Kubernetes 中运行应用程序时扮演着非常重要的角色。
1.3.2 工作负载和调度
像副本集(rs)和部署(deploy)这样的资源对于在 Kubernetes 上运行无状态应用程序工作负载至关重要。没有它们,我们就无法自动扩展 Pod,这提供了负载均衡,使得我们的应用程序在 Kubernetes 上运行时更加易于访问。我们将将这些应用程序部署到 Kubernetes 的过程称为调度。术语调度来自调度器,它是控制平面的一部分。我们将在本章后面更详细地讨论控制平面组件。历史上,如果你必须创建一个在硬件上运行的应用程序,它必须定期维护和更新。这可能会导致中断和/或限制该硬件的功能(它会过时)。Kubernetes(应用程序)抽象化了这种硬件,并在它和所有其他东西(包括硬件)之间创建了一个通用接口(一个 API)。这允许你在运行时更换硬件,以及整合来自不同供应商的硬件并进行混合匹配。
我最喜欢的阐述这个观点的方法是查看一个例子,以获得对 API 内部资源的深入了解。让我们看看一个通过浏览器在 swapi.dev 渲染的 API。如果你访问 SWAPI(星球大战 API),你会看到如何请求有关 星球大战 电影的不同事实。你通过向 API 发送 GET 请求来执行此请求。因为 API 是建立在 HTTP 协议之上的,我们可以执行 HTTP 方法来对 API 端点采取行动。这些操作可以是创建、读取、更新或删除,通常用缩写 CRUD 表示。然而,SWAPI 的目的是向 API 发送请求并GET回一些数据。因此,在请求字段中,输入 people/1/,下面你应该能看到结果,如图 1.6 所示。

图 1.6 SWAPI 网站允许你以与 Kubernetes 相同的方式访问对象。
结果是 "Luke Skywalker",但结果本身并不重要;重要的是你刚刚从 API 接收到了数据(以 JSON 格式)。你接收到的数据是我们一直在谈论的资源 URI。位于该 URI 的对象是 "Luke Skywalker",该对象包含诸如身高、发色、眼色等数据。将此与 Kubernetes 相关联,SWAPI 中的 people 对象与 Kubernetes 中的 Deployment 对象非常相似。你可以类似地访问 Kubernetes API。Kubernetes Dashboard 甚至通过浏览器提供 API 的渲染,就像 SWAPI 一样。图 1.7 显示了一些 Kubernetes 资源 URI。

图 1.7 根据资源 URI 访问 Kubernetes 中的对象。例如,节点位于 /apis/apps/v1/nodes。
有不同的 API 调用(称为 HTTP 方法)对 API 执行某些操作(假设你已经对 API 进行了认证)。它们是 GET、POST、PATCH、PUT 和 DELETE。GET HTTP 方法正是我们一直在 SWAPI 中所做的那样,即检索或查看我们 API 中资源的有关信息。POST HTTP 方法用于在 API 中创建新的资源。PATCH HTTP 方法用于更新现有资源,而 PUT HTTP 方法用于替换 API 中的现有资源。最后,DELETE HTTP 方法用于从 API 中删除资源。
1.3.3 服务和网络
要让最终用户与运行在 Kubernetes 上的应用程序交互,我们创建了一个名为 Service 的对象。这些服务为它们所服务的 Pods 提供负载均衡功能。它们还提供了一个单一的 IP 地址和 DNS 名称,我们将在后面的章节中回顾。说到 DNS,Kubernetes 中有一个名为 CoreDNS 的组件,它提供 IP 地址的名称。这意味着 Kubernetes 中的资源可以使用通用名称而不是 IP 地址相互通信。三种类型的服务是 ClusterIP、NodePort 和 LoadBalancer。
Ingress 资源是 Kubernetes 中的另一个对象,它提供了基于路径的路由到 Kubernetes 中的服务。例如,你会创建一个 Ingress 资源,根据 URL 路径提供一个基于 7 层(应用层)的路由到服务。
1.3.4 存储
在管理 Kubernetes 中的短暂对象,如 Pods 时,你不能依赖于存储与单个 Pod 相关联。存储是应用程序用于在文件系统(如 NTFS、XFS 或 ext4)中存储和组织文件的数据。文件系统是在操作系统(如 Linux)中发生的存储组织行为。在 Kubernetes 中,有一个持久卷的概念,它不与单个 Pod 相关联,而是与 NFS、EFS 或 iSCSI 卷相关联。将存储与短暂的应用程序解耦,可以创建数据的持久性。
此外,从开发者的角度来看,你不再需要管理底层存储。对 Kubernetes 来说,它似乎只是一个大型的存储层。开发者可以使用一个名为persistent volume claim的对象来预留持久卷,这样它就不能被 Kubernetes 中运行的其他应用程序使用。但这仍然需要持久卷存在,所以如果它不存在,开发者可以使用名为storage class的对象动态访问存储。存储类与卷的不同类别有关——例如,慢速与快速。
卷也可以由多个 Pod 同时使用,并且可以重用,分别指定某些访问模式和某些回收策略。访问模式允许一个或多个 Pod 读取和写入卷的能力。回收策略将允许或拒绝来自 Kubernetes 集群中其他 Pod 的访问。
1.3.5 故障排除
Kubernetes 集群并不完美;随着在集群中创建的资源和对象越来越多,它们可能会变得相当复杂。当出现问题时进行故障排除对于限制运行应用程序的停机时间以及检测问题以优化和使 Kubernetes 表现最佳至关重要。能够解析运行容器的日志、分析故障并提出解决方案是成为一名优秀的 Kubernetes 管理员的关键。日志是基于时间的基于文本的行为数据,发送到文件系统上的一个目录,以提供有关正在发生的问题的详细信息。世界上所有的冗余都无法修复一个构建不良且维护不善的集群。
在 Kubernetes 上运行的应用程序也增加了维护责任。它们包含来自 stdout 和 stderr 的日志,这对于检测通信问题何时出现或应用程序即将失败至关重要。
1.4 控制平面节点
使其成为控制平面的服务和组件如下:
-
控制器管理器
-
API 服务器
-
调度器
-
etcd 数据存储
控制器管理器是一个控制循环,负责将当前状态与期望状态相匹配。例如,如果 Pods 的数量不匹配期望的副本数,控制器管理器将自动扩展 Deployment。控制器管理器还会为新命名空间创建默认账户和 API 访问令牌。这就是为什么在下一章中创建 Kubernetes 集群时,你会看到已经为你创建了一个默认命名空间、服务账户和密钥,以便你可以开始部署你的资源(应用程序)。这使得立即在 Kubernetes 上运行容器化应用程序变得容易。当提到命名空间时,可以将其视为一个专门为你的 Kubernetes 资源设置的虚拟环境。默认命名空间将在这个命名空间的范围内进行隔离,并且可以有自己的 Kubernetes 资源分组。
API 服务器正如其名:它是暴露 Kubernetes API(我们之前提到的 RESTful API)的组件。它是集群的入口点,因此所有通往集群的通信都通过它来访问集群组件。想象一下,它就像一个只有用正确的密钥通过认证才能打开的大门,就像图 1.8 中所示。我们将在第三章中更多地讨论认证。

图 1.8 要访问 Kubernetes API,你需要使用密钥进行认证。
调度器是选择 Pod 运行节点的组件。你可能会在这本书中多次听到“调度”这个词,所以从现在开始,当我提到“调度”时,它仅仅意味着 Pod 正在被放置(调度)到节点上以运行。如果多个节点已经运行了 Pod,那么调度器将会将 Pod 放置到不同的节点上,同时考虑可用的资源以及已经放置在该节点上的其他规则/规范。
有一些服务(称为守护进程)运行在控制平面,对于 Kubernetes 集群的运行至关重要。实际上,它们是如此关键,以至于在生产场景中,它们会被复制以实现高可用性。复制控制平面组件超出了本书和考试的范畴,因为考试中的 Kubernetes 集群不会超过一个控制平面节点。一般来说,复制控制平面是通过执行两个功能来实现的。首先,复制确保控制平面组件一次只有一个实例接收请求。这是通过选举一个领导者来实现的。领导者将充当主控制平面,并将其状态传递给其他追随者。其次,因为控制平面依赖于 etcd 来存储所有的 Kubernetes 配置数据(资源的状态以及它们在 Kubernetes 中的运行方式),所以你会创建多个冗余的 etcd 副本。如果你想了解更多关于在 Kubernetes 中实现高可用性的控制平面复制,你可以在这里阅读更多:mng.bz/5wz7。
etcd 数据存储是我们之前提到的那些关键控制平面组件之一。Etcd 存储了所有的 Kubernetes 配置数据(Kubernetes 的状态)。Etcd 是一个一致性的、分布式的键值存储,旨在存储可以完全放入内存中的少量数据。这很重要,因为数据可以比其他传统数据库更快地检索(见图 1.9)。关于 etcd 数据存储,最重要的是记住,丢失它(etcd 故障)是灾难性的,因为它包含了运行在 Kubernetes 集群内部的所有资源的配置信息,所以确保它被备份是极其重要的。你可能会在考试中遇到有关备份 etcd 的问题,所以我们将在下一章中介绍如何进行备份。

图 1.9 etcd 数据存储位于控制平面节点上的 Kubernetes Pod 中。
1.5 工作节点
既然我们已经详细讨论了控制平面组件,现在让我们回顾一下位于工作节点上的组件,如图 1.10 所示。运行在工作节点上的组件与控制平面上的组件不同,因为工作节点在 Kubernetes 环境中扮演着不同的角色。您在 Kubernetes 集群中肯定会有不止一个节点,以下每个组件都会安装在每个节点上:
-
kubelet
-
kube-proxy
-
容器运行时

图 1.10 工作节点的角色是运行应用程序工作负载。它还包含 kubelet、kube-proxy 和容器运行时(例如,containerd)。
运行在每个工作节点上的 kubelet 服务确保容器在 Pod 中运行。然而,尽管它不知道由调度器未管理的容器,kubelet 可以检测到容器(在 Pod 内)失败的情况,并采取纠正措施以确保容器以 YAML 清单中指定的方式重启,YAML 清单是一组用于配置 Pod 的 YAML(YAML 不是标记语言)格式的指令。它就像是一组声明性指令(考虑到最终状态),供 kubelet 遵循以保持运行在 Kubernetes 上的应用程序的高可用性。如果 kubelet 无法使容器运行,它将向 Kubernetes API 服务器报告 Pod 和容器的状态,您可以通过执行命令kubectl describe po nginx(nginx 是 Pod 的名称)在 Pod 事件中看到它。这里显示了类似的输出:
$ kubectl describe po nginx
Name: nginx
Namespace: default
Priority: 0
Node: host01/172.17.0.33
Start Time: Tue, 01 Feb 2022 16:49:36 +0000
Labels: run=nginx
Annotations: <none>
Status: Running
IP: 192.168.0.4
IPs:
IP: 192.168.0.4
Containers:
nginx:
Container ID: containerd:/ /4de3efffd3a6f1ec49c968d7fde95e8eae4ae0c25574e8055cca33a1974998
➥ 79
Image: nginx
Image ID:
➥ docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d
➥ 0f4837f16e78d00deb7e7767
Port: <none>
Host Port: <none>
State: Running
Started: Tue, 01 Feb 2022 16:49:44 +0000
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-
➥ 5ffvj (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-5ffvj:
Type: Projected (a volume that contains injected
➥ data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute
➥ op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute
➥ op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 17s default-scheduler Successfully assigned
➥ default/nginx to host01
Normal Pulling 16s kubelet Pulling image "nginx"
Normal Pulled 9s kubelet Successfully pulled image
➥ "nginx" in 6.993614669s
Normal Created 9s kubelet Created container nginx
Normal Started 9s kubelet Started container nginx
当您描述 Pod 后查看事件时,您会看到容器(显示为状态)的不同状态,包括等待、运行和终止。您还可以通过运行命令kubectl get po来查看容器的状态。
运行在每个工作节点上的 kube-proxy 服务是 Kubernetes 服务的通信机制。服务是 Kubernetes 中的另一种资源类型,负责将流量分配给该服务内的各个 Pod。我们将在第六章中深入探讨服务。这些是节点在需要将流量发送到 Kubernetes 中的 Pod 时需要遵循的基本网络规则。有一个非常有趣的视频详细描述了这一点,名为“数据包的一生”。我强烈建议您在这里观看:youtu.be/0Omvgd7Hg1I。
最后,在每个 Kubernetes 节点上运行的是容器运行时。这是 Kubernetes 运作所必需的组件,因为它是运行容器的引擎。有趣的是,它是一个外部依赖项,这意味着它是 Kubernetes 集群中唯一需要 Kubernetes 本身不应用或安装的组件之一。这意味着在安装容器引擎时,你有选择权。Kubernetes 支持任何符合 CRI 要求的容器运行时 (mng.bz/6D0R)。常见的选项包括 Docker (docs.docker.com/engine/)、containerd (containerd.io/docs/) 或 CRI-O (cri-o.io/#what-is-cri-o)。你可能听说过 Docker,但你是否知道 Docker 在底层使用 containerd 作为容器运行时,同时使用名为 dockershim 的功能来访问 containerd?Docker 也不符合 CRI 标准 (mng.bz/o1nD)。因此,最后一句中隐含的 containerd 是一个精简版(因此它轻量级且便携)的容器运行时,同时也符合 CRI 标准。CRI-O 是另一个轻量级容器运行时,支持开放容器倡议 (OCI)。因此,它是一个由社区驱动的开源项目,支持多种镜像格式,包括 Docker 镜像。
1.6 API 模型和 PKI
我们将在第三章中更详细地讨论 Kubernetes 中的 RBAC 和身份管理,但到目前为止,让我们先谈谈构成 Kubernetes 中客户端到服务器关系的公共密钥基础设施。正如我们所见,要操作 Kubernetes 中的对象,我们需要两样东西。第一样是命令行工具 kubectl。第二样是客户端证书,如图 1.11 所示。

图 1.11 要操作 Kubernetes 对象,我们必须使用客户端证书进行身份验证。
公共密钥基础设施 (PKI) 是一种非常常见的客户端-服务器通信模式,并且是我们计算机通过 Web 服务器安全地与网站通信的通常方式。从高层次来看,PKI 通过确保你访问的网站是你打算访问的网站(通过加密签名)来启用 Web 上的安全通信。这确保了你访问的是正确的网站(而不是冒名顶替者的网站),同时也确保没有人正在窃听或拦截往返的流量。如果你考虑通过 Web 访问你的银行,这个 PKI 基础设施对于确保银行账户安全、访问它们的人确实是账户的所有者以及你访问的是正确的银行至关重要。
PKI 的基本组成部分包括三个元素,如图 1.12 所示。首先是证书颁发机构(CA)。CA 是真相来源,并签署服务器证书(在用于 API 的情况下),随后客户端可以确定服务器是否有效。例如,在互联网上,常见的证书颁发机构有 DigiCert、Symantec 和 Thawte,并且浏览器(客户端)已经信任它们作为浏览器快速验证网站身份的方式(例如,是否 google.com 确实是谷歌)。PKI 拼图的第二和第三部分是服务器和客户端,它们都依赖于 CA——客户端试图验证服务器的身份,服务器则试图向 CA 进行身份验证并证明它们就是他们所说的那样。就像互联网的 PKI 一样,相同的 PKI 模型也应用于 Kubernetes。

图 1.12 控制平面节点作为证书颁发机构(CA),它签署证书并为客户端和服务器之间的通信提供身份验证。
将 PKI 拼图的三块相同的拼图应用到 Kubernetes 中,Kubernetes 是其自己的 CA,并将成为 Kubernetes 集群内其他组件的真相来源。Kubernetes 中的客户端(检查服务器是否为它们所声称的身份的客户端)包括 kubelet、调度器、控制器管理器和 etcd 数据存储。同一个 Kubernetes 组件可以同时作为客户端和服务器。Kubernetes 中的服务器(试图向 CA 证明其身份的客户端)包括 kubelet、Kubernetes API 和 etcd 数据存储(见图 1.13)。

图 1.13 Kubernetes API 在 PKI 基础设施中既作为客户端又作为服务器承担多个职责。CA 生成证书,服务器则试图证明其身份(mng.bz/mV68)。
幸运的是,你不必担心创建 CA、客户端或服务器证书。Kubeadm 会为你完成所有这些工作。Kubeadm 是一个类似于kubectl的命令行工具,但其目的是创建构成我们 Kubernetes 集群所需的所有组件(包括 CA 和其他证书);有时我们称之为引导集群。我们将在第三章中更多地讨论 kubeadm 以及所有这些证书的位置。
1.7 Linux 系统服务
在所有关于服务和控制平面以及工作节点组件的讨论中,我认为提及 Linux 系统服务是恰当的。Linux 系统服务是在 Linux 操作系统上的一组文件,它提供了一个在后台持续运行的软件程序。如果你还记得,我提到过在控制平面运行的一些服务被称为守护进程。在 Linux 世界中,守护进程已经使用了很长时间,不要与 Kubernetes 中的 DaemonSet 混淆,它确保守护进程作为 Kubernetes 集群中的 Pod 在所有时间运行,如图 1.14 所示。如果你更熟悉 Windows 计算机,这个相同的概念被称为服务。

图 1.14. DaemonSets 确保集群中的每个节点上运行一个 Pod。kube-proxy 和 CNI 都作为 DaemonSets 运行。
关于这一点,我们之前提到的控制平面组件是一个很好的例子。你可以在每个节点上运行一个 DaemonSet,提供 kube-proxy 组件,例如。通过运行命令kubectl get ds -A来亲自看看。
$ kubectl get ds -A
NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE
➥ AVAILABLE NODE SELECTOR AGE
kube-system kube-flannel-ds 1 1 1 1
➥ 1 <none> 18m
kube-system kube-proxy 1 1 1 1
➥ 1 kubernetes.io/os=linux 19m
然而,有一个服务你不想让 Kubernetes 管理(作为一个 DaemonSet),那就是 kubelet。kubelet 是在 Kubernetes 环境中唯一位于 Linux 系统本身(作为一个 Linux 系统服务)的服务之一。你必须知道这一点,因为考试可能会让你修复它。了解这个服务的位置以及如何修复它将非常有用。要查看 Linux 系统上所有服务的列表,请输入命令sudo systemctl list-unit-files --type service --all | grep kubelet -a6。
$ sudo systemctl list-unit-files --type service --all | grep kubelet -a6
ip6tables.service enabled enabled
iptables.service enabled enabled
irqbalance.service enabled enabled
keyboard-setup.service enabled enabled
kmod-static-nodes.service static enabled
kmod.service static enabled
kubelet.service enabled enabled
logrotate.service static enabled
lvm2-lvmpolld.service static enabled
lvm2-monitor.service enabled enabled
lvm2-pvscan@.service static enabled
lvm2.service masked enabled
man-db.service static enabled
你将看到一个显示单元文件(服务名称)和服务状态(启用/禁用/掩码/静态)的服务列表。在这个列表中,你会看到kubelet.service作为其中之一。一旦你确定 kubelet 服务在你的系统上正在运行,你可以做三件事情之一。如果它已经停止,你可以通过输入systemctl start kubelet来启动它。如果你注意到当节点重启时服务没有启动,那么你可以使用命令systemctl enable kubelet来启用它,这样当节点启动(或重启)时它会自动启动。最后,如果你只是想检查服务状态以查看它是活动状态还是非活动状态,你可以运行命令systemctl status kubelet。
在服务列表中,你可能已经注意到了systemd.journald服务。如果没有注意到,可以运行以下命令来显示它:sudo systemctl list-unit-files --type service --all | grep journald.service。
$ systemctl list-unit-files --type service --all | grep journald.service
systemd-journald.service static enabled
Journald(另一个 Linux 系统服务)用于收集每个节点上 kubelet 服务的日志。Journalctl 是一个命令行工具,用于查看由 systemd 收集的 Linux 系统日志,systemd 是 Linux 中控制所有进程的主要守护进程。这种日志机制在考试期间可能非常有用。例如,你可能会发现自己正在挖掘日志以找出 kubelet 服务失败的原因。可以使用命令sudo journalctl -u kubelet和sudo journalctl -u containerd作为参考。
你可以在/var/log/pods目录中检查 Pod 日志;然而,你也可以使用kubectl logs命令以完全相同的方式检索这些日志(例如,kubectl logs kube-controller-manager-server1 -n kube-system)。关于故障排除和收集这些日志的更多内容,请参阅第八章。现在,只需知道这些 Linux 系统服务对于收集有关 Kubernetes 的有价值信息非常重要,并且一些服务存在于 Linux 系统(节点)上,而一些服务存在于 Kubernetes(Pod)的范围内。
1.8 声明式语法
现在你已经很好地了解了 Kubernetes 集群及其底层服务的组成,我不得不提一下 Kubernetes 的主要目的,那就是运行应用程序。是的,一旦你有了控制平面组件(我们在此章中已经讨论过)、工作节点组件和 Linux 系统服务的基石,并且集群作为一个松散耦合的单元运行(记得 API 模型吗?),那么就是时候运行你的应用程序了。毕竟,这是主要功能,也是我们最初使用 Kubernetes 的原因。至于在 Kubernetes 上运行这些应用程序,你可以在 Kubernetes 上运行 Java 应用程序,就像.NET 应用程序一样。

图 1.15 在真正的微服务架构中,每个微服务都与其他服务解耦;因此,它可以以最适合该服务的语言构建。
Kubernetes 根本不在乎语言或应用程序的运行方式,如图 1.15 所示。它将以相同的方式在每次实例中运行你的容器,并且通过保持相同的容器基础优势来实现这一点,因为应用程序的二进制文件和库与容器一起打包,并且因为应用程序在每台机器上以相同的方式运行。
因此,如果 Kubernetes 不关心应用程序的语言或框架,那么应用程序是如何在 Kubernetes 上部署的呢?这引出了 DevOps 和云原生中的一个非常重要的词——声明式。你会发现,以声明式方式在 Kubernetes 上构建应用程序要比命令式方式好得多。为什么?命令式方式不过是按照特定顺序运行一系列命令。这使得跟踪这些命令和在执行这些命令的顺序中检测命令失败变得很困难。声明式方式更加描述性和简洁,具有以下主要优势:
-
通过 YAML 文件描述你的配置,该文件可以提交到版本控制
-
以最终状态为目标构建,不考虑顺序或特定的运行命令
-
通过并行操作获得高效和快速启动的能力
YAML 是一种人类可读的语言,同时也适用于机器,因此创建这种配置文件相对简单。大多数资源都遵循 YAML 的相同模式和结构。以下是一个名为 my-pod-manifest.yaml 的文件示例:
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
这些 YAML 文件(称为 manifests)可以在多年后引用,这是记录应用程序构建过程的一种良好形式。将其提交到版本控制将允许你跟踪更改,并在团队中共同开发并随着时间的推移改进配置。你可以自由地构建以 Kubernetes 运行的应用程序,并着眼于最终目标。这被称为 目标寻求。你不必担心达到最终状态的道路;你只需将 YAML 文件提交给 Kubernetes API,API 就会选择最佳路径来完成构建。
有趣的是,将你的文件提交给 API 与执行一个 POST 请求到 API 是相同的。因为你的集群中运行的所有内容都对 API 可见,动作可以并行处理,并且会更快地达到最终状态。以下是一个使用 kubectl 通过 YAML 文件声明式创建 Pod 的命令式命令示例:
$ kubectl create -f my-pod-manifest.yaml
pod/nginx created
你可以使用三种不同的方式将此文件提交给 Kubernetes API 使用 kubectl——通过使用命令 kubectl create -f my-pod-manifest.yaml,命令 kubectl apply -f my-pod-manifest.yaml,或者命令 kubectl replace -f manifest.yaml。create 命令期望还没有创建任何资源,如果否则,它将抛出一个错误。另一方面,apply 命令可以用来创建或更新现有资源。replace 命令如果存在将删除资源,并创建一个全新的资源,就像你手动删除资源然后创建一个新的一样。例如,在 Deployment 上使用 replace 命令将触发一个新的部署,其中该 Deployment 内的 Pod 将被优雅地终止,并启动新的 Pod 副本。我们将在第四章中更详细地讨论 Deployment,在第五章中讨论滚动更新。
幂等 是另一个需要记住的重要词汇。与 Kubernetes API 一起工作的好处是,无论对 API 进行多少次相同的调用,资源的状态都将保持不变。这为您的工作环境带来了一致性,并且随着时间的推移,您的基础设施和应用变得越来越复杂,您也知道可以期待什么。
既然我们已经对 Kubernetes 讨论得淋漓尽致,并讨论了我们可能遇到或可能没有遇到的复杂性和复杂性,那么让我们深入下一章,并更深入地了解我们学习 Kubernetes 的方法。毕竟,您将在考试中动手操作键盘,为什么不深入实践和模拟考试体验呢?卷起袖子吧!
1.9 考试练习
-
执行列出 Kubernetes 集群中所有 API 资源的命令。将输出保存到名为
resources.csv的文件中。 -
列出与 Kubernetes 相关的 Linux 操作系统上的服务。将输出保存到名为
services.csv的文件中。 -
列出在 Kubernetes 节点上运行的 kubelet 服务的状态,将结果输出到名为
kubelet-status.txt的文件中,并将文件保存在/tmp目录中。 -
使用声明性语法从 YAML 文件创建 Kubernetes 中的 Pod。将 YAML 文件保存为
chap1-pod.yaml。使用kubectl create命令创建 Pod。 -
使用
kubectlCLI 工具列出您在 Kubernetes 集群中所有命名空间中创建的所有服务。将命令的输出保存到名为all-k8s-services.txt的文件中。
摘要
在本章中,您已经认识了 Kubernetes。现在您不仅知道 Kubernetes 的内部和外部结构,还知道它通常被用于什么。您还应该了解以下内容:
-
Kubernetes 是一个运行其他应用程序的应用程序,其构建方式与其它 RESTful 网络应用程序类似。API 是 Kubernetes 集群中身份验证、授权和通信的中心枢纽。
-
集群有许多不同的组件,包括两个主要部分:控制平面,由控制平面节点处理,以及工作节点,它们负责运行工作负载(即运行在 Kubernetes 之上的容器化应用程序)。
-
在 Kubernetes 中,Linux 系统服务很重要,因为它们负责在主机本身上保持 Kubernetes 的运行。
-
您可以通过两种方式访问集群,编程方式或使用名为
kubectl的工具,这两种方式都需要证书进行身份验证。这种证书在 PKI(公钥基础设施)系统中很常见,该系统会与 CA(证书颁发机构)进行交互,以确保证书有效,并且可以在 Kubernetes 集群中的组件之间进行通信。 -
Kubernetes 是考虑到微服务而构建的,这意味着大型微服务应用程序可以在 Kubernetes 中运行得更有效率,因为每个服务都与整体应用程序解耦。
-
在 Kubernetes 上运行服务通过声明式方法更为高效。这种方式,我们可以描述我们希望最终状态是什么,而不是运行一系列命令式指令来达到相同的结果。
2 Kubernetes 集群
本章涵盖
-
多节点集群中的控制平面和工作节点组件
-
使用 kubeadm 升级控制平面组件
-
调查 Pod 和节点网络
-
备份和恢复 etcd
-
污点和容忍
没有进入运行中的集群并亲自发现组件,几乎不可能理解 Kubernetes 集群架构。无论你怎么看,作为 Kubernetes 管理员,我们必须了解底层的运作情况。当你完成本章后,你将能够舒适地访问 Kubernetes 集群中的所有组件,在需要时升级组件,并备份集群配置。
集群架构、安装和配置
本章回顾了 CKA 课程中集群架构、安装和配置领域的部分内容。该领域涵盖了 Kubernetes 集群的元素,包括关键组件以及如何维护一个健康的 Kubernetes 集群。本章包括以下能力。
| 能力 | 章节部分 |
|---|---|
| 提供底层基础设施以部署 Kubernetes 集群。 | 2.1 |
| 使用 kubeadm 在 Kubernetes 集群上执行版本升级。 | 2.1 |
| 实施 etcd 备份和恢复。 | 2.2 |
| 管理一个高可用性的 Kubernetes 集群。 | 2.2 |
2.1 Kubernetes 集群组件
控制平面 Pods 协同工作以形成控制平面。这些组件包括 API 服务器、控制器管理器、调度器和 etcd。在你在考试当天可能会遇到的 Kubernetes 集群中,这些组件以在控制平面节点上运行的 Pods 的形式存在。你可以通过查看运行在 kube-system 命名空间中的 Pods 来查看集群组件。你可以使用命令 kubectl get po -n kube-system 来查看这些 Pods,你将看到类似于图 2.1 的输出。我们首先运行命令 docker exec -it kind-control-plane bash 来获取控制平面节点的 shell。

图 2.1 kube-system 命名空间中的 Pods
在本章的后续部分,你将学习如何管理这些组件,包括如何访问它们、修改它们的配置、备份它们以及升级它们。现在,让我们在终端中动手操作。
2.1.1 Kubernetes 版本升级
CKA 考试将测试你维护 Kubernetes 集群的知识。这包括将控制平面组件升级到特定版本。例如,考试问题可能会说如下内容。
| 考试任务 公司 X 需要升级 Kubernetes 控制器到 1.24 或更高版本,因为一个影响 Pod 调度的错误。以最少的停机时间和服务损失执行更新。 |
|---|
知道你应该使用 kubeadm 来这样做,将使你能够轻松地完成这项任务,并对你正在朝着及格分数前进感到自信。
如果你还没有访问现有的 Kubernetes 集群,附录 A 中解释了如何使用 kind 创建 Kubernetes 集群。一旦你的 kind 集群构建完成,就可以使用预安装在控制平面节点上的kubectl工具。你可以通过输入命令docker exec -it kind-control-plane bash并跟随操作来获取控制平面节点的 Bash shell。使用 kubeadm 构建的集群意味着我们也可以使用 kubeadm 查看我们的控制平面组件的版本,如图 2.2 所示,并升级集群。从控制平面运行kubeadm upgrade plan命令会显示控制平面组件列表,并在CURRENT列中显示当前版本,以及在TARGET列中显示你可以升级到的版本。

图 2.2 查看控制平面组件以及如何升级它们(如果需要的话)。
注意:TARGET列不显示 Kubernetes 的最新版本。这是因为我们只能更新到当前版本的 kubeadm。如果 kubeadm 是 1.26.0 版本,TARGET列将显示 v1.26.3。要将 kubeadm 从 1.26.0 升级到 1.26.3,运行命令apt update; apt install -y kubeadm=1.26.3-00。
如果你必须将 kubeadm 升级到最新版本,首先下载 GPG 密钥并将 kubeadm 添加到你的本地apt软件包(仍然在控制平面节点上的 shell 中)。要下载 GPG 密钥,运行命令curl -fsSLo /etc/apt/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg。要将 kubeadm 添加到你的本地apt软件包,运行命令echo "deb [signed-by=/ etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list。完成这些操作后,重新运行命令apt update; apt install -y kubeadm=1.26.3-00以将 kubeadm 升级到版本 1.26.3。当你再次运行kubeadm upgrade plan命令时,你会注意到你的TARGET列显示了一个比 1.24.3 更高的版本,你可以升级到这个版本。请参见图 2.3 中此命令的缩略输出。

图 2.3 当你升级 kubeadm 时,你也可以升级你的控制平面组件。
恭喜!你已经成功升级了 Kubernetes 集群的控制平面组件,也就是“升级 Kubernetes”。
2.1.2 控制平面
控制平面和工作节点有不同的职责。工作节点承载应用程序的工作负载并运行包含这些应用程序容器的 Pods,而控制平面运行一组初始的 Pods,包括我们在升级集群时看到的控制平面组件。我们称它们为系统 Pods,因为它们包含了围绕整个 Kubernetes 系统运行的必要结构。《kube-apiserver》、《kube-controller-manager》、《kube-scheduler》和 etcd 都在控制平面节点上作为系统 Pod 运行。这些系统 Pod 将存在于你的 Kubernetes 集群所在的位置或其构建方式如何,因为它们对于 Kubernetes 的核心至关重要。你可以通过运行命令k get po -o wide -A --field-selector spec.nodeName=kind-control-plane来查看系统 Pods,该命令将显示类似于图 2.4 的输出。

图 2.4 控制平面节点上的系统 Pods
CoreDNS 也是一个运行在控制平面节点上的 Pod,但它被视为一个插件,而不是核心系统 Pod。此外,kube-proxy Pod 将在所有节点上运行,无论它是一个控制平面节点还是工作节点。让我们专注于控制平面节点的核心组件,如图 2.5 所示。

图 2.5 Kubernetes 控制平面节点上的系统 Pods 和插件
每个 Pod 在 Kubernetes 的操作中都扮演着特定的角色。控制器管理器 Pod 维护集群操作,确保部署正在运行正确的副本数。调度器 Pod 负责检测节点上的可用资源,以便你可以在该节点上放置 Pod。将 Pod 分配给节点的行为称为调度;从现在起,当我说“一个 Pod 被调度到节点上”时,我的意思是该节点上已启动了一个新的 Pod。API 服务器 Pod 将公开 API 接口,并且是所有其他组件的通信中心,包括工作节点和控制平面节点上的组件。最后,etcd Pod 是用于存储集群配置数据的 etcd 数据存储,正如我们在本章以及上一章所讨论的。
2.1.3 污点(Taints)和容忍度(Tolerations)
默认情况下,应用程序 Pod 不会在控制平面节点上运行。为什么是这样呢?因为控制平面有一个特殊的属性,称为污点。污点会排斥工作,这意味着除非 YAML 规范中存在一个称为容忍度的特定规范,否则将禁用对该节点的调度。让我们通过在控制平面节点上(docker exec -it kind-control-plane bash)输入命令kubectl describe no | grep Taints来查看我们的控制平面节点上的污点是什么样的:
root@kind-control-plane:/# kubectl describe no | grep Taints
Taints: node-role.kubernetes.io/master:NoSchedule
Taints: <none>
Taints: <none>
如我们所见,在集群创建过程中,已经将污点应用于我们的控制平面节点,污点为node-role.kubernetes.io/master:NoSchedule。污点由三部分组成:一个键、一个效果和一个值。污点不需要有值,但必须有键和效果。因此,在这种情况下,键是node-role.kubernetes.io,效果是NoSchedule。这意味着除非有对该污点的容忍,否则 Pod 不会调度到具有该污点的节点。在图 2.6 中,您将看到这一演示,因为 Pod 没有对该污点的容忍,所以它没有被调度到该节点。

图 2.6 将污点应用于控制平面节点,但没有容忍;因此,Pod 没有被调度。
让我们在我们的一个工作节点上应用一个污点,并看看具有键、效果和值的污点是什么样的,使用命令kubectl taint no kind-worker decdicated=special-user:NoSchedule。这个命令在图 2.7 中被分解,以显示哪个项目是键,哪个项目是值和效果。

图 2.7 污点各部分的分解:键、值和效果
当您想更具体地说明 Pod 必须具备的资格才能“绕过”污点时,使用完整的污点(键、值和效果)是合适的。是的,有一种方法可以将 Pod 调度到节点,即使该节点可能已应用污点。这被称为容忍。这一点很重要,因为容忍并不意味着您正在选择那个特定的节点;它们意味着调度器可能会在所有其他节点条件合适的情况下选择将 Pod 调度到那里。这就是像 DNS Pod 这样的 Pod 如何在控制平面运行的原因。这些 Pod 对污点有容忍。要查看 Pod YAML中的容忍,请运行命令kubectl get po coredns-558bd4d5db-7dmrp -o yaml -n kube-system | grep tolerations -A14。此命令将获取名为core-dns-64897985d-4th9h的 Pod 在kube-system命名空间中的 YAML 输出,然后使用 grep 工具过滤结果,并给出结果后的 14 行。输出将类似于图 2.8,您可以将其与放置在控制平面节点上的污点进行比较。

图 2.8 CoreDNS Pod 中污点设置与容忍的比较
正如我们在该 CoreDNS Pod 的 YAML 规范中看到的那样,在容忍列表中,键node-role.kubernetes.io/master和效果NoSchedule与我们的污点相匹配。这就是 CoreDNS Pod 能够调度到控制平面节点的原因。请记住,您的 CoreDNS Pod 将有一个不同的名称,因此您需要将core-dns-64897985d-4th9h替换为您的 CoreDNS Pod 的名称。如果您不知道 Pod 的名称,可以通过输入kubectl get po -A来获取名称。
正如我们所看到的,容忍度必须与键和效果完全匹配。如果稍有偏差,则 Pod 将不会被调度到目标节点。因此,正如你所想象的,为容忍度定义更宽松的规则使得在编写 YAML 时定义容忍度更容易。在图 2.8 中,你还可以注意到其他字段,如 operator 和 tolerationSeconds。如果未明确定义 operator,例如在控制平面污点的 node-role 容忍度中,它将默认为两个可能值 Exists 或 Equal 中的 Equal。因为 node-role 污点容忍度没有定义运算符,它默认为 Equal。
如果键和效果相同,并且运算符是相等的,则容忍度与污点匹配。如果有值,则容忍度与污点匹配,如果键、效果和值都完全匹配,并且运算符是 Equal。如果运算符是 Exists,则不应指定值,但键和效果必须匹配。参考图 2.8 的命令输出,效果 NoExecute 和键 node.kubernetes.io/not-ready 必须完全匹配,因为运算符是 Exists。tolerationSeconds 是一个可选参数,允许你指定 Pod 在添加污点后绑定到节点的持续时间,在这种情况下,300 秒。图 2.9 中有一个这样的例子,其中容忍度与节点的污点匹配;因此,它成功调度到该节点。

图 2.9 应用到 Pod 上的容忍度确保它可以调度到污点节点。
现在,让我们创建一个具有针对我们应用于 kind-worker 的污点的容忍度的 Pod。这个污点有 NoSchedule 的效果,special-user 的值,以及名为 dedicated 的键。这意味着我们必须使用默认运算符 Equal 匹配所有这些。让我们从使用命令 k run pod-tolerate --image=nginx --dry-run=client -o yaml > pod-tolerate.yaml 构建一个 Pod 模板开始:
root@kind-control-plane:/# k run pod-tolerate --image=nginx --dry-
➥ run=client -o yaml > pod-tolerate.yaml
root@kind-control-plane:/# cat pod-tolerate.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod-tolerate
name: pod-tolerate
spec:
containers:
- image: nginx
name: pod-tolerate
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
注意:我们使用 dry-run 标志构建这个 Pod 模板,并将命令的输出发送到文件,这比从头开始键入文件要容易得多。
让我们使用 Vim 文本编辑器通过命令 vim pod-tolerate.yaml 打开文件 pod-tolerate.yaml。我们可以添加所需的容忍度以匹配应用在我们工作节点上的污点。在命令 kubectl describe no kind-worker | grep Taints 的输出中,我们看到键是 dedicated,值是 special-user,效果是 NoSchedule。在文件仍然打开的情况下,与单词 containers 同行插入 tolerations:。紧接着,插入行 - key: "dedicated",然后是 value: "special-user",下一行是 effect: "NoSchedule"。你可以在图 2.10 中看到它应该是什么样子。

图 2.10 如何将容忍度应用于污点以有效地调度 Pod
一旦您将容忍度(toleration)添加到 Pod 的 YAML 清单中,您就可以保存并关闭文件。然后,您可以使用命令 kubectl create -f pod-tolerate.yaml 将 YAML 提交到 API 服务器,Pod 将在默认命名空间中创建。您可以使用命令 kubectl get po -o wide 查看 Pod 正在运行的节点:
root@kind-control-plane:/# kubectl create -f pod-tolerate.yaml
pod/pod-tolerate created
root@kind-control-plane:/# k get po -o wide
“[CA]”
NAME READY STATUS RESTARTS AGE IP
➥ NODE NOMINATED NODE READINESS GATES
pod-tolerate 1/1 Running 0 6s 10.244.2.6
➥ kind-worker <none> <none>
从宽输出中,您会看到 Pod 正在 kind-worker 节点上运行,但它同样可以被调度到 kind-worker2,因为容忍度并没有明确选择调度器应该选择哪个节点。如果您在创建 Pod 时仍然遇到困难,您可以运行这个命令,该命令已经包含了完整的 YAML,然后您可以比较并查看您出错的地方。只需输入命令 k apply -f https://raw.githubusercontent.com/chadmcrowell/k8s/main/manifests/pod-tolerate.yaml. 即可。
接下来,我们将讨论您可以添加到 Pod YAML 中的节点选择器和节点名,这些选择器将对调度器做出明确的声明。节点选择器,正如其名所示,允许您根据节点标签选择一个节点,以便将 Pod 调度到该节点。默认情况下,许多标签都会应用到节点上。您可以通过执行命令 k get no --show-labels 来查看标签列表。标签不会改变节点的操作,但可以用来查询具有特定标签的节点,或者使用其标签调度 Pod 到该节点(我们将在本章的后续部分讨论)。标签也可以应用到 Pod 上。您可以使用命令 k get po -n kube-system --show-labels 在 kube-system 命名空间中查看 Pod 标签。输出将类似于图 2.11。

图 2.11 创建 Kubernetes 集群时默认应用到节点和 Pod 上的标签
节点名允许您通过主机名选择单个节点。让我们保留对节点的污点(taint)应用,这样我们可以一起工作,更好地可视化这些节点选择器在 Kubernetes 中的应用。
2.1.4 节点
kind 代表 Kubernetes in Docker。kind 在 Docker 容器内创建集群的 Kubernetes 节点。如图 2.12 所示,容器存在于 Pod 内,Pod 又存在于节点内,在这种情况下(使用 kind),节点本身也是一个容器,由 Docker 中的 Kubernetes 集群组成。

图 2.12 Kind 集群架构,节点作为容器在我们的本地系统上运行
让我们先查看 kind 为我们创建的 Docker 容器。您可以使用命令 docker container ls 查看 Docker 容器,您的输出将与此完全相同,但容器 ID 不同:
$ docker container ls
➥ CONTAINER ID IMAGE COMMAND CREATED
➥ STATUS PORTS NAMES
c23f3ab00ba3 kindest/node:v1.21.1 "/usr/lo..." 4 days ago Up 25 hours
➥ 127.0.0.1:59546->6443/tcp kind-control-plane
容器名称是 kind-control-plane,您可能已经猜到它由控制平面组件组成。那么工作节点呢?嗯,因为我们使用这个集群进行测试(为了简单起见),我们将控制平面和工作组件组合在同一个节点上,但在所有其他生产场景中,这些将是两个或更多单独的节点。为了防止走题并保持简单,让我们继续。我们将在本章的后面部分构建一些多节点集群,因为它们将包含在 CKA 考试中。您还可以使用 kubectl 工具通过 kubectl get nodes 命令查看 Kubernetes 集群中的节点,如下所示:
$ kubectl get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 4d23h v1.21.1
如您在表 2.1 中所见,我们可以在 Kubernetes 中使用缩写来指定某些资源,这对于考试来说很有用,因为它可以节省很多按键。此外,在表 2.1 中,您还可以看到用于指定不同 Kubernetes 资源的其他缩写或缩写。
表 2.1 Kubernetes 资源缩写及其与 kubectl 的使用示例
| 资源 | 缩写 | 示例 |
|---|---|---|
| 命名空间 | ns |
k get ns |
| Pod | po |
k get po |
| 部署 | deploy |
k get deploy |
| ReplicaSet | rs |
k get rs |
| 服务 | svc |
k get svc |
| 服务账户 | sa |
k get sa |
| 配置映射 | cm |
k get cm |
| DaemonSet | ds |
k get ds |
| 持久卷 | pv |
k get pvc |
| 持久卷声明 | pvc |
k get pvc |
| 存储类 | sc |
k get sc |
| 网络策略 | netpol |
k get netpol |
| 入口 | ing |
k get ing |
| 端点 | ep |
k get ep |
您也可以通过输入 kubectl get no -o wide 来看到,在我们的 kind Kubernetes 集群中实际上使用的是 containerd 而不是 dockershim。-o wide 是从 get 命令检索详细输出的方式。我们将在本书的其余部分进一步探讨这一点。
$ kubectl get no -o wide
NAME STATUS ROLES AGE VERSION
➥ INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION
➥ CONTAINER-RUNTIME
kind-control-plane Ready control-plane,master 3d23h v1.21.1
➥ 172.18.0.2 <none> Ubuntu 21.04 5.10.76-linuxkit
➥ containerd://1.5.2
回到我们的单节点集群,现在让我们在容器内部获取一个 shell,这样我们就可以查看其内部情况。我们可以通过在终端中输入 docker exec -it kind-control-plane bash 来做到这一点,我们会看到这个确切输出,这意味着您正在 kind-control-plane 容器内部以 root 身份操作:
$ docker exec -it kind-control-plane bash
root@kind-control-plane:/#
从现在开始,我们将在容器内部执行命令(在 root@kind-control-plane:/# 之后),因为 kubectl 已经预安装,我们可以查看有关我们集群的更多信息。例如,我们可以使用 crictl 工具列出控制平面组件作为容器。crictl 工具是另一种用于检查和调试与 CRI(容器运行时接口)兼容的容器的命令行实用程序。CRI 是容器运行时构建的标准,并为 kubelet 提供了一个与容器运行时交互的一致接口,无论是 Docker、containerd 还是任何其他符合 CRI 标准的容器运行时。使用 crictl ps 命令,您可以列出在 kind-control-plane 容器内部运行的容器:
root@kind-control-plane:/# crictl ps
CONTAINER IMAGE CREATED STATE
➥ NAME ATTEMPT POD ID
1fec85d809fc4 e422121c9c5f9 26 hours ago Running
➥ local-path-provisioner 2 6be3b31e9b9eb
1b8372af65cf2 296a6d5035e2d 26 hours ago Running
➥ coredns 1 0821c481585d6
85c7a1f4305f0 6de166512aa22 26 hours ago Running
➥ kindnet-cni 1 f7a5090363d9b
fe14d75b82341 296a6d5035e2d 26 hours ago Running
➥ coredns 1 4ee287bd9f248
76ab2ca16da9d 0e124fb3c695b 26 hours ago Running
➥ kube-proxy 1 e40195d6d1bed
4198ed634fc67 1248d2d503d37 26 hours ago Running
➥ kube-scheduler 1 c2aaffa11479c
5aac336ec4338 0369cf4303ffd 26 hours ago Running
➥ etcd 1 09c5891be5560
80a672211961d 96a295389d472 26 hours ago Running
➥ kube-controller-manager 1 764b1292d728e
a67abd31ab1ae 94ffe308aeff9 26 hours ago Running
➥ kube-apiserver 1 1865042cc7955
local-path-provisioner 用于我们集群的持久存储,coredns 用于我们集群中名称解析到 IP 地址(DNS),而 kindnet-cni 用于我们集群中的 Pod 到 Pod 通信。Pod 是 Kubernetes 中最小的可部署单元,可以包含一个或多个容器。我们将在未来的章节中看到 Pods 可以独立运行,也可以作为 Deployment、ReplicaSet 和 StatefulSet 的一部分运行。
现在,让我们谈谈存在于工作节点上的组件。这些组件对于集群中的每个节点都是相同的,无论您只有一个工作节点还是有成千上万个工作节点。它们都包含 kubelet、kube-proxy 和容器运行时。让我们使用命令 kubectl get po -o wide -A | grep worker 查看我们集群中工作节点上运行的 kube-proxy Pods:
root@kind-control-plane:/# kubectl get po -o wide -A | grep worker
kube-system kindnet-fs6jt 1/1
➥ Running 0 22h 172.18.0.2 kind-worker
kube-system kube-proxy-szr7n 1/1
➥ Running 0 22h 172.18.0.2 kind-worker
kube-proxy 创建我们的 iptables 规则,并确保当创建服务时,我们可以访问与该服务关联的 Pods,正如之前在控制平面节点上关于 kube-proxy 的讨论中提到的。我们看到的与 kube-proxy Pods 并列的 kindnet Pods 是用于 CNI 的,但同样,它被视为插件,而不是核心 Kubernetes 的一部分。
接下来,让我们看一下每个节点上安装的容器运行时。为此,我们将运行命令 kubectl get no -o wide | grep containerd:
root@kind-control-plane:/# kubectl get no -o wide | grep containerd
kind-control-plane Ready 22h v1.26.3 172.18.0.3 Ubuntu 22.04.1 LTS
➥ 5.15.49-linuxkit containerd://1.6.7
kind-worker Ready 22h v1.25.0 172.18.0.2 Ubuntu 22.04.1 LTS 5.15.49-
➥ linuxkit containerd://1.6.7
如我们在输出结果的最后一列中看到的,containerd 是用于我们节点的容器运行时。Containerd 是一个轻量级的 Linux 守护进程,它利用 cgroups 和命名空间来运行 Kubernetes 中运行的 Pods 内部的容器。
最后,我们将查看 kubelet。因为 kubelet 是在节点上运行的 systemd 服务,而且因为我们的节点实际上是容器,所以我们需要进入我们的工作节点容器。首先输入 exit 退出控制平面 shell。现在,您可以使用命令 docker exec -it kind-worker bash 获取到工作节点的 shell。现在,通过运行命令 systemctl status kubelet 如此查看服务的状态:
$ docker exec -it kind-worker bash
root@kind-worker:/# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/etc/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-03-03 15:34:52 UTC; 5h 17min
➥ ago
Docs: http://kubernetes.io/docs/
Process: 170 ExecStartPre=/bin/sh -euc if [ -f
➥ /sys/fs/cgroup/cgroup.controllers ]; then create-kubelet-cgroup-v2; fi
➥ (code=exited, status=0/SUCCESS)
Main PID: 176 (kubelet)
Tasks: 19 (limit: 2246)
Memory: 38.0M
CPU: 5min 39.923s
CGroup: /system.slice/kubelet.service
└─176 /usr/bin/kubelet --bootstrap-
➥ kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf -
➥ kubeconfig=/etc/kubernetes/kubelet.conf
➥ config=/var/lib/kubelet/config.yaml --container-runtime=remote -
➥ container-runtime-endpoint=unix:///run/containerd/containerd.sock -
➥ fail-swap-on=false --node-ip=172.18.0.2 --node-labels= --pod-infra-
➥ container-image=k8s.gcr.io/pause:3.4.1 --provider-
➥ id=kind://docker/kind/kind-worker --fail-swap-on=false --cgroup-
➥ root=/kubelet
Mar 03 20:04:49 kind-worker kubelet[176]: W0303 20:04:49.309978 176
➥ sysinfo.go:203] Nodes topology is not available, providing CPU topology
Mar 03 20:09:49 kind-worker kubelet[176]: W0303 20:09:49.100474 176
➥ sysinfo.go:203] Nodes topology is not available, providing CPU topology
如您所见,systemd 服务处于活动状态并正在运行,这是好事。考试中可能会有一个问题需要您记住如何检查 kubelet 服务是否正在运行。记住,如果 kubelet 没有运行,您的节点状态将显示为 NotReady。您甚至可以通过运行命令 systemctl stop kubelet,退出容器 shell,然后运行命令 kubectl get no 来测试这一点。试试看吧!
root@kind-worker:~# systemctl stop kubelet
root@kind-worker:~# exit
$ kubectl get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 5h27m v1.21.1
kind-worker NotReady <none> 5h27m v1.21.1
kind-worker2 Ready <none> 5h27m v1.21.1
注意:kubelet 服务未运行的消息返回到 API 服务器可能需要几秒钟,所以如果节点没有立即显示 NotReady,请不要担心。
要重新启动 kubelet 服务,只需再次在工作节点内部获取一个 shell,然后运行命令 systemctl start kubelet:
$ docker exec -it kind-worker bash
root@kind-worker:/# systemctl start kubelet
root@kind-worker:/#
我们将在第八章中更详细地讨论服务故障排除,但我实在忍不住想向您展示这个确定 Kubernetes 集群中 kubelet 服务是否未启动的宝贵工具。在接下来的这一节中,我们将使用exit命令退出kind-worker shell。
2.2 数据存储 etcd
数据存储 etcd 包含我们集群的所有配置数据,这意味着有关我们集群中运行了多少 Pod 或 Deployment 的信息(包括历史信息),以及哪些端口在哪些服务上暴露。如果您没有这些数据,您在从头开始重新创建 Kubernetes 资源时会感到迷茫。为了明确,我们不是在谈论与您的应用程序相关的数据。我们谈论的是 Kubernetes 集群本身的配置。这如图 2.13 所示,其中 etcd 数据存储在 Kubernetes 集群中的一个 Pod 内部,包含集群配置数据。

图 2.13 Etcd 作为 Pod 存在于kube-system命名空间中。
2.2.1 使用 etcdctl
CKA 考试将测试您备份和恢复 etcd 数据库的知识。幸运的是,考试终端中预装了一个名为etcdctl的工具,您可以使用它执行这两个操作。在考试当天了解这一点将非常有价值,因为您将因正确回答与 etcd 相关的问题而获得奖励,这将帮助您达到及格分数。一个考试问题可能看起来像以下这样。
实考任务 集群 k8s 配置错误,需要从位于/tmp/c02dkjs0-001.db的备份中恢复。执行恢复并验证 DaemonSet kube-proxy 是否已恢复到集群中。 |
|---|
首先,我们将使用命令docker exec -it kind-control-plane bash获取到控制平面节点的 shell。为了完成这个任务,我们首先必须使用名为etcdctl的 etcd 命令行工具备份 etcd。我们可以使用命令apt update; apt install -y etcd-client安装etcdctl。
注意:考试中etcdctl客户端将为您可用,因此您不需要在考试期间知道如何安装它。
一旦我们安装了etcdctl,我们需要将其设置为版本 3。我们可以通过命令export ETCDCTL_API=3来完成,这创建了一个名为ETCDCTL_API的环境变量,并将其设置为值3。现在etcdctl工具已设置为正确的版本,因此该命令将可用于执行备份。运行命令etcdctl -v以检索etcdctl的版本,它现在应设置为版本 3:
root@kind-control-plane:/# etcdctl -v
etcdctl version: 3.3.25
API version: 2
现在我们已经设置了版本,运行以下命令以对 etcd 数据存储执行快照备份:etcdctl snapshot save snapshotdb --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key。运行ls | grep snapshotdb以列出当前目录中的快照:
root@kind-control-plane:/# etcdctl snapshot save snapshotdb --cacert
➥ /etc/kubernetes/pki/etcd/ca.crt --cert
➥ /etc/kubernetes/pki/etcd/server.crt --key
➥ /etc/kubernetes/pki/etcd/server.key
2022-03-02 03:49:00.578988 I | clientv3: opened snapshot stream; downloading
2022-03-02 03:49:00.616108 I | clientv3: completed snapshot read; closing
Snapshot saved at snapshotdb
root@kind-control-plane:/# ls | grep snapshotdb
snapshotdb
现在,我们可以使用命令 etcdctl snapshot status snapshotdb --write-out=table 来检查那个快照文件中是否有任何数据:
root@kind-control-plane:/# etcdctl snapshot status snapshotdb --write-
➥ out=table+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| dac82c4e | 893315 | 1217 | 2.1 MB |
+----------+----------+------------+------------+
让我们继续进行 etcd 的恢复操作。为了模拟恢复过程,我们需要删除为我们的 kube-proxy 服务创建的 DaemonSet,这样我们就可以在恢复后验证我们的恢复操作是否成功。要删除 DaemonSet,请执行以下命令
kubectl delete ds kube-proxy -n kube-system
$ kubectl delete ds kube-proxy -n kube-system
daemonset.apps "kube-proxy" deleted
如果我们在 kube-system 命名空间中查看 DaemonSet,我们会注意到只有一个 kindnet DaemonSet,而 kube-proxy DaemonSet 已经不再存在:
$ kubectl get ds -A
NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE
➥ AVAILABLE NODE SELECTOR AGE
kube-system kindnet 1 1 1 1
➥ 1 <none> 6d20h
考试技巧:命令末尾的 -A 是 --all-namespaces 的缩写。这对于 CKA 考试来说是一个节省时间的真实技巧。
现在我们已经修改了集群状态,并且我们 etcd 数据存储中的键与我们备份的不同,我们可以执行恢复操作。为此,我们将使用命令 etcdctl snapshot restore snapshotdb --data-dir /var/lib/etcd-restore:
root@kind-control-plane:/# etcdctl snapshot restore snapshotdb --data-dir
➥ /var/lib/etcd-restore
2022-03-02 16:01:03.198967 I | mvcc: restore compact to 914490
2022-03-02 16:01:03.207022 I | etcdserver/membership: added member
➥ 8e9e05c52164694d [http://localhost:2380] to cluster cdf818194e3a8c32
我们使用 --data-dir 参数指定了一个集群可以读取的格式,并将我们的备份移动到 Kubernetes 已经使用的目录。现在我们已经为 Kubernetes 准备了快照并将其存储在 /var/lib/etcd-restore 目录中,我们将继续更改 Kubernetes 查找 etcd 数据的位置。这可以在 API 服务器 YAML 规范中更改,它始终位于 /etc/kubernetes/manifests 目录中的控制平面节点上。您放置在该目录中的任何 YAML 文件都将自动调度 Kubernetes 资源。
kind-control-plane 上没有安装 Vim,所以我们将运行命令 apt install update; apt install vim 来安装它并编辑我们的文件。使用命令 vim /etc/kubernetes/manifests/etcd.yaml 打开文件。滚动到文件底部,并将卷的路径从 /var/lib/etcd 更改为 /var/lib/etcd-restore(确保按 I 键进入插入模式)。以下是该文件的片段,以便您可以检查您的操作:
volumes:
- hostPath:
path: /etc/kubernetes/pki/etcd
type: DirectoryOrCreate
name: etcd-certs
- hostPath:
path: /var/lib/etcd-restore
type: DirectoryOrCreate
name: etcd-data
status: {}
注意:要退出 Vim,您需要在键盘上按 Esc 键,然后按冒号(:)和字母 w 以写入,按字母 q 以退出(:wq)。
完成恢复所需的所有操作就这些了!从 API 服务器获取您新集群数据的响应可能需要几秒钟,但不久之后,您将能够运行 kubectl get ds -A 并看到 kube-proxy 和 kindnet DaemonSet 都回到了它们应该的位置。恭喜,您已成功备份并恢复了 etcd!
2.2.2 客户端和服务器证书
在上一节中,我们讨论了 etcd 需要通过认证到集群。这种客户端和服务器之间的认证方法称为 PKI(公钥基础设施)。PKI 是 Kubernetes 强加的客户端-服务器模型,使用证书认证所有进入 API 的请求。kubectl也使用证书来认证到 Kubernetes API,我们将其称为 kubeconfig。然而,kubectl并不是唯一试图访问 API 的工具或对象。除了kubectl之外,控制器管理器、调度器和 kubelet 都需要使用证书来认证到 API。这些证书都是由引导过程(通过 kubeadm)生成的。您不需要管理这些证书,但了解它们的位置对于 CKA 考试是有好处的。在图 2.14 中,您将看到 Kubernetes 集群中每个组件使用的每个客户端或服务器证书的视觉表示。让我们运行命令ls /etc/kubernetes来列出该目录的内容,并查看 kubelet、调度器和控制器管理器的客户端证书:
root@kind-control-plane:/# ls /etc/kubernetes
admin.conf controller-manager.conf kubelet.conf manifests pki scheduler.conf

图 2.14 Kubernetes 中客户端和服务器证书的放置
注意:您仍然需要一个到kind-control-plane的 shell。如果您忘记了,这里有一个命令:docker exec -it kind-control-plane bash。
您将看到controller-manager.conf, kubelet.conf和scheduler.conf,这些是用于认证到 Kubernetes API 的 kubeconfig 的代表性配置。例如,如果您查看kubelet.conf的内容,您将看到一个类似于您的kubectl kubeconfig(位于~/.kube/config)的配置。输入命令cat /etc/kubernetes/kubelet.conf:
root@kind-control-plane:/# cat /etc/kubernetes/kubelet.conf
apiVersion: v1
clusters:
- cluster:
certificate-authority-data:
➥ LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ00FURS0tLS
➥ 0tCg==
server: https://kind-control-plane:6443
name: kind
contexts:
- context:
cluster: kind
user: system:node:kind-control-plane
name: system:node:kind-control-plane@kind
current-context: system:node:kind-control-plane@kind
kind: Config
preferences: {}
users:
- name: system:node:kind-control-plane
user:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
注意用户是系统用户,客户端证书位于节点上的/var/lib/kubelet/pki/kubelet-client-current.pem。在 Kubernetes 中,所有节点都会运行 kubelet 服务,所以情况对所有节点都是如此。
您可能已经注意到了/etc/kubernetes中的pki目录,如果您列出该目录的内容,您将找到更多的客户端和服务器证书。让我们使用命令ls /etc/kubernetes/pki来查看:
root@kind-control-plane:/# ls /etc/kubernetes/pki
apiserver-etcd-client.crt apiserver-kubelet-client.crt apiserver.crt
➥ ca.crt etcd front-proxy-ca.key front-proxy-
➥ client.key sa.pub
apiserver-etcd-client.key apiserver-kubelet-client.key apiserver.key
➥ ca.key front-proxy-ca.crt front-proxy-client.crt sa.key
在这个pki目录中,您将看到另一个 kubelet 证书(kubelet.crt),这是一个服务器证书。是的,在 Kubernetes 中,您可以为同一个服务拥有客户端和服务器证书。这一点在名为apiserver.crt和apiserver-etcd-client.crt的两个文件中也很明显,分别代表服务器和客户端证书。由于 kubelet 有一个服务器证书,它必须有一个客户端证书,这个证书就是名为apiserver-kubelet-client.crt的文件。apiserver.crt是客户端证书,正如我们刚才在/etc/kubernetes目录中看到的,它是服务器证书。
最后,还有 etcd。在/etc/kubernetes/pki/etcd目录中,你还会看到一个server.crt文件,它属于 etcd 的服务器证书。API 服务器不断尝试验证到 etcd 数据存储并获取集群配置数据。这样,不是任何人都可以查看或修改集群状态。
2.3 练习
-
通过缩短
kubectl命令并创建到k的 shell 别名来提高使用kubectl命令的效率。 -
使用
kubectlCLI 工具,获取在kube-system命名空间中运行的 Pod 的输出并显示 Pod IP 地址。将命令的输出保存到名为pod-ip-output.txt的文件中。 -
使用 CLI 工具,该工具允许你查看 kubelet 用于向 Kubernetes API 进行身份验证的客户端证书。将结果输出到名为
kubelet-config.txt的文件中。 -
使用
etcdctlCLI 工具,将 etcd 数据存储备份到名为etcdbackup1的快照文件中。一旦备份完成,将命令etcdctl snapshot status etcdbackup1的输出发送到名为snapshot-status.txt的文件中。 -
使用
etcdctlCLI 工具,使用之前练习中相同的etcdbackup1文件恢复 etcd 数据存储。当你完成恢复操作后,使用cat命令查看 etcd YAML 并将其保存到名为etcd-restore.yaml的文件中。 -
使用 kubeadm 升级控制平面组件。完成后,检查包括 kubelet 和
kubectl在内的一切是否已升级到版本 1.24.0。
摘要
-
引导过程隐藏了创建 Kubernetes 集群的复杂性,如 PKI 和 etcd 创建、生成 kubeconfig 等。
-
kubeconfig 是在引导过程中由 kubeadm 创建的,是我们使用
kubectl与 API 进行身份验证的方式。此文件位于~/.kube/config目录中。 -
服务是 Kubernetes 对象,根据 Pod 标签提供对一个或多个 Pod 的负载均衡。服务拥有自己的唯一 IP 地址和 DNS 名称,通过这些名称可以访问 Pod。
-
目录
/etc/kubernetes/pki中的客户端和服务器证书允许控制平面和工作节点对 API 进行身份验证。多个 Kubernetes 组件都可以有客户端和服务器证书。 -
Etcd 是一个键值数据存储,包含集群配置,包括存在多少对象。要备份 etcd,使用命令
etcdctl snapshot save,要从快照恢复,使用命令etcdctl snapshot restore。 -
目录
/etc/kubernetes/manifests包含 API 服务器、控制器管理器、调度器和 etcd 的 YAML 文件。这些文件不会被调度器检测;因此,kubelet 将自动获取并创建此目录中的内容。 -
在多节点集群中,工作节点运行 kubelet、kube-proxy 和容器运行时,而控制平面也运行 kubelet、控制器管理器、调度器和 etcd。为了防止应用 Pod 在控制平面运行,在引导过程中应用了一个污点(taint)。
3 身份和访问管理
本章涵盖了
-
RBAC 基础
-
创建角色和角色绑定
-
使用证书创建用户和组
-
使用角色创建服务账户
-
将服务账户挂载到 Pod 上
在本章中,我们将重点关注基于角色的访问控制,这是考试课程中集群架构、安装和配置部分的内容。既然你已经了解了 Kubernetes API 的工作原理,那么了解如何对用户和/或服务账户进行身份验证和授权对于考试至关重要。
集群架构、安装和配置领域
本章涵盖了 CKA 课程中集群架构、安装和配置领域的一部分。该领域涵盖了 Kubernetes 集群由什么组成以及我们如何配置集群的不同方面。它包括以下能力。
| 能力 | 章节部分 |
|---|---|
| 管理基于角色的访问控制 (RBAC)。 | 3.1, 3.2 |
| 管理高可用性 Kubernetes 集群。 | 3.3 |
3.1 基于角色的访问控制
要访问 Kubernetes 集群内的资源,你必须首先进行身份验证。如果你还记得第二章,我们使用了 kubectl 工具与 Kubernetes API 进行交互。我们之所以能够这样做,是因为我们有一个携带令牌的客户证书,如图 3.1 所示。这被称为基于角色的访问控制(RBAC),因为根据我们在 Kubernetes 中的角色,我们能够列出 kube-system 命名空间中运行的 Pod。我们可以以不同的方式控制这些操作,如果需要,可以阻止它们。Kubernetes 内置的身份验证和授权系统使我们能够控制特定用户或机器对 Kubernetes 集群的访问。我们将在本章的后续部分进一步讨论这个问题。

图 3.1 使用证书和令牌访问 Kubernetes API
就此而言,你可以通过使用 curl 并传递证书来查看 Kubernetes 中它们 API 组内的资源。在接下来的段落中,我们的 Pod 和 Deployment 在 API 中的位置分别在 /core 和 /apps 组中。从使用 kubeadm 构建的 Kubernetes 集群中,你可以使用以下命令以相同的方式访问这些资源,假设控制平面的地址是 10.0.0.4,可以使用命令 kubectl config view 和/或 kubectl cluster-info 获取:
kubectl create role pod-deploy-reader --verb=get --verb=list --verb=watch -
➥ -resource=pods,deployments
kubectl create rolebinding pod-deploy-reader-binding --role=pod-deploy-
➥ reader --serviceaccount=default:default
TOKEN=$(kubectl get secret -n default -o json | jq -r
➥ '(.items[].data.token)' | base64 --decode | tr -d "\n")
curl -X GET https://10.0.0.4:6443/api/v1/namespaces/default/pods --header
➥ "Authorization: Bearer $TOKEN" --cacert /etc/kubernetes/pki/ca.crt
curl -X GET
➥ https://10.0.0.4:6443/apis/apps/v1/namespaces/default/deployments --
➥ header "Authorization: Bearer $TOKEN" --cacert /etc/kubernetes/pki/ca.crt
你可以通过不同的方式通过身份验证插件进行身份验证。其中一些插件是 Kubernetes 自带的,这就是为什么我们不需要单独安装它们。我们与 kubectl 一起使用的内置插件称为证书插件。还有许多其他身份验证插件是身份验证的来源。以下是一些常见的插件,如图 3.2 所示:
-
静态令牌文件(例如,CSV 文件)
-
第三方身份服务(AWS IAM)
-
基本 HTTP 身份验证(令牌通过 HTTP 头部传递)
在启动 API 服务器时可以启用更多插件。


当你向 Kubernetes API 发起请求时,所有插件都会分析这个请求并尝试确定它们是否可以按顺序读取它。第一个能够翻译请求含义的插件处理身份验证。这涉及到确定请求是否来自人类或机器。如果请求来自人类——例如当我们使用 kubectl 时——则用户将以特定的用户名进行身份验证,并且该名称将用于后续步骤。如果请求来自机器,它必须有一个服务账户,并且其凭证必须以秘密的形式存储在 Kubernetes 中。
服务账户和秘密都是 Kubernetes 资源,而用户不是。服务账户是运行在 Pod 内部的进程的身份,提供了一种在集群中代表 Pod 进行身份验证和执行操作的方法。这允许其他访问管理系统通过 LDAP(轻量级目录访问协议)或 AD(活动目录)通过单点登录(SSO)轻松地与 Kubernetes 集成进行身份验证。
服务账户是 Kubernetes 资源;因此,Kubernetes 会管理它以及其他资源,如 Pods、Deployments 和持久卷。服务账户通常与在集群中运行的 Pods 关联,允许 Pod 内部运行的应用程序(作为容器)访问 Kubernetes API 并发起请求。如图 3.3 所示,通过服务账户将令牌挂载到每个 Pod 上。你可以禁用此功能,因为在大多数情况下,你不需要你的 Pods 与 API 通信。我们将在本章后面通过一个在 Pod 中挂载服务账户并防止服务账户挂载到 Pod 的示例进行说明。


在身份验证阶段之后,请求会被传递到授权插件。RBAC 被视为身份验证插件之一,但还有其他插件,如节点授权插件和 WebHook 授权插件。为了 CKA 考试的目的,我们将专注于 RBAC,如图 3.4 所示。RBAC 通过最小权限原则阻止未经授权的用户通过查看或修改集群状态。该原则指出,管理员可以允许对 Kubernetes 中单个主题进行明确的访问,但不会向任何其他主题提供额外的权限(例如,查看 Pods 的权限,但不允许删除它们)。


在我们的 kind 集群中,授权模式在引导过程中被设置为 RBAC 以及 Kubernetes API 服务器上的节点授权。我们可以在kind-control-plane容器中的/etc/kubernetes/manifests目录中查看此设置。您将在spec下看到它,在容器命令列表中,作为--authorization-mode=Node,RBAC。让我们使用命令cat /etc/kubernetes/manifests/kube-apiserver.yaml | more获取该文件的正文并将其通过more命令查看:
root@kind-control-plane:/# cat /etc/kubernetes/manifests/kube-
➥ apiserver.yaml | more
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint:
➥ 172.18.0.4:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=172.18.0.4
- --allow-privileged=true
- --authorization-mode=Node,RBAC
- --client-ca-file=/etc/kubernetes/pki/ca.crt
- --enable-admission-plugins=NodeRestriction
- --enable-bootstrap-token-auth=true
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
- --insecure-port=0
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-
➥ client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
- --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
- --requestheader-allowed-names=front-proxy-client
- --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- --requestheader-extra-headers-prefix=X-Remote-Extra-
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --runtime-config=
--More--
我们本可以用文本编辑器打开这个文件,但我们还是使用了cat命令。这个 YAML 清单非常重要,因为它包含了 Kubernetes 集群中许多组件的集群配置,例如广告地址、特权模式,以及 etcd 和 kubelet 的所有证书和密钥。
考试技巧:记住控制平面组件的清单位于/etc/Kubernetes/manifests目录中。此目录将位于控制平面服务器上,您需要通过 SSH 连接到该服务器以查看或修改。在考试中,SSH 密钥是共享的;因此,您只需键入ssh control-plane-node即可获得控制平面节点的 shell。
当 RBAC 授权插件收到请求时,它会确定用户或组可以执行哪些操作。是的,组也被纳入了 RBAC,这意味着在 Kubernetes 中,一组用户被分配了相同的权限级别。我们将在本章后面讨论创建用户和组。
3.1.1 角色和角色绑定
那么,RBAC 是如何知道允许执行哪些操作的?这由 Kubernetes 中的四种不同类型的对象确定:角色、集群角色、角色绑定和集群角色绑定,如图 3.5 所示。之前我们讨论了 Kubernetes 中命名空间范围资源。命名空间内资源的权限也需要限定在那个命名空间内。对于不是命名空间范围资源的情况,例如持久卷、节点或命名空间本身,也是如此。这些资源需要正确定义的集群角色(集群范围内)或角色(命名空间内)。

图 3.5 角色和角色绑定定义了命名空间中的权限,而集群角色和集群角色绑定定义了集群范围内的权限。
注意:记住哪种类型的用户将访问命名空间资源与集群资源是有帮助的。应用程序开发者通常会被分配角色,因为他们开发的应用程序存在于命名空间内。集群管理员通常会被分配集群角色,因为他们更新节点、创建卷以及其他在命名空间之外(集群范围内)的操作。
角色不仅限于命名空间,还限于 Kubernetes 中的特定资源(例如 Pod),并列出该角色中允许的具体操作。这些操作被称为 请求动词。它们类似于 API 请求,但由于资源可以有多个端点,请求动词针对特定的端点,并使用请求的 HTTP 方法的小写作为动词。因此,get、list、create、update、patch、watch、delete 和 deletecollection 都是 API 请求动词,允许你在资源上执行操作,如图 3.6 所示。

图 3.6 请求动词大致对应于 HTTP 动词,如图所示。
让我们通过输入命令 kubectl create role pod-reader --verb=get,list,watch --resource=pods 来查看一个示例,你会看到类似于图 3.7 的输出。我们刚刚使用命令 kubectl get role pod-reader -o yaml 创建的角色的 YAML 输出类似于图 3.8。

图 3.7 使用这个命令式命令创建一个角色并为资源分配请求动词。

图 3.8 kubectl get role 命令显示了该角色适用于哪些资源以及该角色允许哪些操作。
我们不必立即创建角色,我们可以先进行“dry run”(模拟运行),并将内容保存到 YAML 文件中。如果你喜欢采用更声明性的方法,并将 YAML 文件保存在版本控制系统中的话,这会很有用。让我们运行相同的命令来创建一个角色,但这次我们将执行模拟运行,并将命令的输出保存到名为 role.yaml 的文件中。运行命令 kubectl create role pod-reader -verb=get,list,watch -resource=pods -dry-run=client -o yaml > role.yaml,你会看到文件已经创建,如图 3.9 所示。

图 3.9 我们不是创建角色,而是执行模拟运行,然后将 YAML 内容保存到文件中。
在第一章中,我们探讨了所有的不同 Kubernetes API 资源。每个资源都有一个路径,路径的尽头指向对象。对于 Pod 来说,路径是 /api/v1/pods,因此 API 组位于 /api/v1/ 内。Deployment 位于 API 组 /apis/apps/v1,在本角色的定义中单独列出,如图 3.10 所示。

图 3.10 API 组用于在角色和集群角色中指定资源。
注意,这个角色根本不表示任何用户权限。这就是角色绑定发挥作用的地方。角色绑定会将一个角色绑定到用户、组或服务账户上,并为该用户、组或服务账户提供角色。现在让我们使用命令 kubectl create rolebinding pod-reader-binding --role=pod-reader --user=carol. 创建一个角色绑定。
我们的 pod-reader-binding 的 YAML 输出显示了角色引用(roleRef)和用户(subjects)。这个角色绑定就是用户 carol 将如何被授权访问我们的 Kubernetes 集群中的 Pod。
$ kubectl create rolebinding pod-reader-binding --role=pod-reader -
➥ user=carol
rolebinding.rbac.authorization.k8s.io/admin-binding created
$ kubectl get rolebinding pod-reader-binding -oyaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: "2022-03-29T04:22:35Z"
name: pod-reader-binding
namespace: default
resourceVersion: "566785"
uid: 9f7d1036-9123-4288-94cf-ef527aa090d0
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: carol
kubectl 中内置了一个非常有用的工具,用于验证用户是否可以在不明确假定该用户身份的情况下在 Kubernetes 集群中执行某些操作。此命令是 kubectl auth can-i。如果您查看此命令的帮助页面,您将看到大量示例,您可以使用这些示例来测试作为用户、组或服务账户的授权:
$ kubectl auth can-i -h
Check whether an action is allowed.
VERB is a logical Kubernetes API verb like 'get', 'list', 'watch',
➥ 'delete', etc. TYPE is a Kubernetes resource.
Shortcuts and groups will be resolved. NONRESOURCEURL is a partial URL that
➥ starts with "/". NAME is the name of a
particular Kubernetes resource.
Examples:
# Check to see if I can create pods in any namespace
kubectl auth can-i create pods --all-namespaces
# Check to see if I can list deployments in my current namespace
kubectl auth can-i list deployments.apps
# Check to see if I can do everything in my current namespace ("*" means
➥ all)
kubectl auth can-i '*' '*'
# Check to see if I can get the job named "bar" in namespace "foo"
kubectl auth can-i list jobs.batch/bar -n foo
# Check to see if I can read pod logs
kubectl auth can-i get pods --subresource=log
# Check to see if I can access the URL /logs/
kubectl auth can-i get /logs/
# List all allowed actions in namespace "foo"
kubectl auth can-i --list --namespace=foo
让我们使用命令 kubectl auth can-i 检查用户 carol 是否可以在我们的 Kubernetes 集群的默认命名空间中列出或删除 Pod。注意输出只说“是”或“否”。这是在 Kubernetes 中验证授权多么简单的方法!
$ kubectl auth can-i get pods -n default --as carol
yes
$ kubectl auth can-i delete pods -n default --as carol
no
考试技巧:我鼓励您在考试中使用 kubectl auth can-i,因为它将允许您验证您是否已成功完成任务,并节省宝贵的时间。
创建集群角色和集群角色绑定的方式相同,所以我们不会详细说明这一点。创建集群角色的 kubectl 命令是 kubectl create clusterrole,创建集群角色绑定的命令是 kubectl create clusterrolebinding。
考试技巧:使用 -h 标志获取运行您的命令的示例列表(例如,kubectl create clusterrole -h)。这将在考试中节省您的时间,因为您可以在不输入或记住整个命令的情况下复制示例。
当您需要访问不在命名空间范围内(例如,节点)的资源时,您也可以使用集群角色与角色绑定。您不能将集群角色绑定附加到角色上,因为命名空间内的权限(角色权限)不能应用于非命名空间范围的主体,正如我们在图 3.11 中看到的那样。您必须记住的一点是,通过集群角色授予的权限将影响所有命名空间,包括当前和未来的。您必须牢记最小权限原则,谨慎地不要分配一个或集群角色,这会给您的 Kubernetes 集群带来无限制的访问权限。

图 3.11 除了将集群角色绑定到角色之外,所有其他组合都受到支持。
3.1.2 系统角色和组
现在我们知道了用户、服务账户、角色和绑定用于什么,让我们看看内置的角色和组,以及到目前为止我们为我们的集群使用了哪些用户和组。API 服务器创建了一组默认的集群角色和集群角色绑定。它们由控制平面直接管理。当您运行命令 kubectl get clusterrole | grep -v system 时,您将看到它们是 cluster-admin、admin、edit 和 view:
root@kind-control-plane:/# kubectl get clusterrole | grep -v system
NAME CREATED AT
admin 2022-03-25T17:44:03Z
cluster-admin 2022-03-25T17:44:03Z
edit 2022-03-25T17:44:03Z
kindnet 2022-03-25T17:44:08Z
kubeadm:get-nodes 2022-03-25T17:44:05Z
local-path-provisioner-role 2022-03-25T17:44:09Z
view 2022-03-25T17:44:09Z
cluster-admin 集群角色旨在集群范围内使用,并允许对大多数对象进行读写访问,包括 Roles 和 Role bindings,但资源配额、Endpoints 或命名空间本身除外。编辑集群角色将不允许你修改 Roles 或 Role bindings;然而,它将允许你访问 Secrets 并在命名空间内使用任何 Service Accounts 运行 Pods。视图集群角色正如其名,允许你查看大多数命名空间对象,但 Secrets 除外,因为 Secrets 的内容包含 API 访问的凭证。
考试技巧 Kubernetes 允许通过 Role 或集群 Role 绑定将集群角色分配给多个用户/组/Service Accounts,因此,在考试中,创建一个 Role 绑定并将其分配给现有的 Role(即 view)可能会节省你的时间。
现在我们知道了所有默认创建的 Roles,那么我们假设哪个是 kubectl 的用户?为此,我们得更详细地谈谈组——更具体地说,是由 Kubernetes API 服务器管理的内置组。像用户一样,组在 Kubernetes 中不是一个受管理的资源,因此组名只是一个任意名称。然而,这些内置组是不同的。它们由 API 服务器管理,不能被修改。当 API 服务器启动时,它会与任何缺失的权限进行协调,并使用任何缺失的主题(即组)更新默认的集群 Role 绑定。这允许集群自动修复任何配置错误,并确保 Roles 和绑定与 Kubernetes 的新版本保持最新。这些内置的默认集群 Role 绑定是 system:authenticated、system:unauthenticated 和 system:masters:
-
system:authenticated组将权限分配给成功认证的用户。 -
当没有任何认证插件可以验证请求时,使用
system:unauthenticated组。 -
system:masters组用于超级用户,并提供对 Kubernetes 中所有内容的无限制访问。
system:masters 组是我们一直假设的组。哎呀!我们最好小心行事,以免配置错误,并认为我们的集群无法运行。开个玩笑,你永远不应该为普通用户使用这个组,因为如果它落入错误的手中,可能会很危险。为了证明我们对集群有不受限制的访问权限,我们可以使用命令 kubectl auth can-i --list 来列出我们可以在集群中的每个对象上执行的操作:
root@kind-control-plane:/# kubectl auth can-i --list
Resources Non-Resource URLs
➥ Resource Names Verbs
*.* [] []
➥ [*]
[*] []
➥ [*]
selfsubjectaccessreviews.authorization.k8s.io [] []
➥ [create]
selfsubjectrulesreviews.authorization.k8s.io [] []
➥ [create]
[/api/*] []
➥ [get]
[/api] []
➥ [get]
[/apis/*] []
➥ [get]
[/apis] []
➥ [get]
[/healthz] []
➥ [get]
[/healthz] []
➥ [get]
[/livez] []
➥ [get]
[/livez] []
➥ [get]
[/openapi/*] []
➥ [get]
[/openapi] []
➥ [get]
[/readyz] []
➥ [get]
[/readyz] []
➥ [get]
[/version/] []
➥ [get]
[/version/] []
➥ [get]
[/version] []
➥ [get]
[/version] []
➥ [get]
此外,我们可以运行命令 kubectl get clusterrolebinding cluster-admin -o yaml 来查看将 Role cluster-admin 绑定到我们用户的 clusterrolebinding,该用户是 system:masters 组的成员:
kubectl get clusterrolebinding cluster-admin -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
creationTimestamp: "2022-03-25T17:44:03Z"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: cluster-admin
resourceVersion: "141"
uid: d91f5e22-e179-47b3-b90e-2591f4617b3b
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:masters
最后,我们可以看到角色,它还显示了我们可以对集群资源执行的操作。你会注意到星号 (*) 是代表所有内容的通配符。因此,我们有权访问所有 API 组中的所有资源,并且允许执行任何操作(获取、列出、创建、删除、更新、修补、监视)。
考试技巧:如果你在如何格式化集群角色上遇到困难,可以将 kubectl 的输出发送到文件并直接编辑它。为此,使用命令 kubectl create clusterrole pod-reader --verb get,list,watch --resource pods --dry-run=client -o yaml > pod-reader.yaml。
考试练习
创建一个名为 sa-creator 的新角色,允许创建服务账户。
创建一个与之前 sa-creator 角色关联的角色绑定,命名为 sa-creator-binding,它将绑定到用户 Sandra。
3.2 用户和组
我们揭露了全世界每个安全工程师的最大恐惧:一个通向我们的 Kubernetes 集群的后门,允许完全无限制地访问做任何事情!我们甚至可以将我们的集群公开给匿名用户,邀请任何人和每个人进入我们的集群并随心所欲。不,不要这么做。让我们让那些安全人员晚上能安心休息,并创建一个新的用户,遵循最小权限原则。还记得卡罗尔吗?我们创建了一个将 pod-reader 角色与 carol 用户关联的 Role binding。让我们创建 carol 可以用来访问集群的 kubeconfig,如图 3.12 所示。

图 3.12 为新用户创建 kubeconfig 并使用证书签名请求生成证书
因为用户和组是在证书上的任意标记,并且不由 Kubernetes 管理,所以让我们先创建一个新的证书签名请求。还记得我们学到 Kubernetes API 是它自己的证书颁发机构吗?这意味着它可以为我们签名证书,使它们成为 Kubernetes API 的有效认证机制。多么方便啊!
首先,让我们使用命令 openssl genrsa -out carol.key 2048 生成一个使用 2048 位加密的私钥:
root@kind-control-plane:/# openssl genrsa -out carol.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
......................................................+++++
.......................+++++
e is 65537 (0x010001)
现在,让我们创建一个证书签名请求文件,使用我们刚刚创建的私钥,我们最终将把它交给 Kubernetes API。在这里,我们指定用户和组在证书签名请求的通用名称中非常重要,使用命令 openssl req -new -key carol.key -subj "/CN=carol/O=developers" -out carol.csr:
root@kind-control-plane:/# openssl req -new -key carol.key -subj "/CN=carol/O=developers" -out carol.csr
root@kind-control-plane:/# ls | grep carol
carol.csr
carol.key
让我们将 CSR 文件存储在一个环境变量中,因为我们稍后会需要它。为此,使用命令 export REQUEST=$(cat carol.csr | base64 -w 0) 将 CSR 文件的 Base64 编码版本存储在名为 REQUEST 的环境变量中。
现在我们已经有了创建 Kubernetes 中证书签名请求资源所需的一切。我们创建这个资源的方式就像我们创建许多其他 Kubernetes 资源和对象(例如,部署、Pod、服务账户等)一样,通过一个 YAML 文件。
考试技巧:因为您可以在考试期间打开额外的标签页,请利用 Kubernetes 文档中的搜索功能(kubernetes.io/docs)。这是允许的,也是鼓励的。在您点击搜索结果之前,将鼠标悬停在链接上,确保它保持在kubernetes.io/docs子域内。
我们可以使用 Kubernetes 文档来找到证书签名请求资源的正确 YAML 结构的副本。您需要熟悉 Kubernetes 文档以备考试,因为它是您的生命线,也是复制示例(即 YAML)并将其粘贴到终端的巨大节省时间。让我们去mng.bz/vnaa并练习。复制证书签名请求的 YAML——位于request一词右侧的长字符串——并将其粘贴到您的终端中,更改三个元素:将其替换为我们的REQUEST环境变量,并将名称和组更改为与 CSR 文件中的通用名称匹配。它看起来像这样:
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: carol
spec:
groups:
- developers
request: $REQUEST
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
EOF
如果您不想麻烦复制粘贴,或者您现在可能无法访问浏览器,那也行。您可以运行以下命令,它将实现相同的结果:kubectl apply -f https://raw.githubusercontent.com/chadmcrowell/k8s/main/manifests/csr-carol.yaml.。
恭喜,您已成功向 Kubernetes API 提交了签名请求!因为这个对象是由 Kubernetes 管理的,我们可以运行命令kubectl get csr来查看请求列表。您会注意到我们刚刚提交的请求名为carol,其状态为Pending:
root@kind-control-plane:/# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR
➥ CONDITION
carol 4s kubernetes.io/kube-apiserver-client kubernetes-admin
➥ Pending
我们可以使用命令kubectl certificate approve carol简单地批准请求,并且我们会看到状态从pending变为Approved,Issued:
root@kind-control-plane:/# kubectl certificate approve carol
certificatesigningrequest.certificates.k8s.io/carol approved
root@kind-control-plane:/# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR
➥ CONDITION
carol 2m12s kubernetes.io/kube-apiserver-client kubernetes-admin
➥ Approved,Issued
现在它已经被批准,我们可以提取已签名证书的内容——即客户端证书——Base64 解码,并将其存储在名为carol.crt的文件中,使用命令kubectl get csr carol -o jsonpath='{.status.certificate}' | base64 -d > carol.crt:
root@kind-control-plane:/# kubectl get csr carol -o jsonpath='{.status.certificate}' | base64 -d > carol.crt
root@kind-control-plane:/# ls | grep carol
carol.crt
carol.csr
carol.key
我们有密钥(carol.key)和客户端证书(carol.crt),可以将这些凭证添加到我们的 kubeconfig 中。我们可以使用命令kubectl config set-credentials carol --client-key=carol.key --client-certificate=carol.crt --embed-certs同时将证书和密钥值嵌入到文件中(类似于我们当前的 kubeconfig)。
注意:如果您不想嵌入证书和密钥,可以省略--embed-certs选项。这将列出两个文件的路径而不是原始证书值。
您现在可以通过使用命令kubectl config view在 kubeconfig 中看到carol的凭证:
root@kind-control-plane:/# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://kind-control-plane:6443
name: kind
contexts:
- context:
cluster: kind
user: kubernetes-admin
name: kubernetes-admin@kind
current-context: kubernetes-admin@kind
kind: Config
preferences: {}
users:
- name: carol
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
要将用户 carol 添加到我们的上下文,我们将运行命令kubectl config set-context carol --user=carol --cluster=kind。一旦运行此命令,您会注意到通过运行命令kubectl config get-contexts已经添加了上下文:
root@kind-control-plane:/# kubectl config set-context carol --user=carol -
➥ cluster=kind
Context "carol" created.
root@kind-control-plane:/# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
carol kind carol
* kubernetes-admin@kind kind kubernetes-admin
左侧 current 列中的星号表示我们当前正在使用哪个上下文。在继续之前,让我们运行命令 kubectl run nginx --image nginx 创建一个 Pod,这样我们就可以正确地测试用户 carol 是否根据 pod-reader 角色具有适当的权限。然后,要切换上下文并使用 carol 上下文,请运行命令 kubectl config use-context carol。您会注意到星号已将当前上下文更改为 carol。最后,就像使用 kubectl auth can-i 命令一样,我们可以验证 carol 可以列出 Pod 但不能删除它们:
root@kind-control-plane:/# kubectl config use-context carol
Switched to context "carol".
root@kind-control-plane:/# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 13s
root@kind-control-plane:/# kubectl delete po nginx
Error from server (Forbidden): pods "nginx" is forbidden: User "carol"
➥ cannot delete resource "pods" in API group "" in the namespace "default"
让我们更进一步,将角色(使用角色绑定)绑定到一个组而不是用户(这是我们目前的情况)。要切换回管理员用户,请输入命令 kubectl config use-context kubernetes-admin@kind。然后,使用命令 kubectl delete rolebinding admin-binding 删除当前的角色绑定,并使用命令 kubectl create rolebinding pod-reader-bind --role=pod-reader --group=developers 创建一个新的角色绑定。让我们使用命令 kubectl config use-context carol 切换回用户 carol,并使用命令 kubectl get po 测试我们的授权(这次是通过组):
root@kind-control-plane:/# kubectl config use-context kubernetes-admin@kind
Switched to context "kubernetes-admin@kind".
root@kind-control-plane:/# kubectl delete rolebinding admin-binding
rolebinding.rbac.authorization.k8s.io "admin-binding" deleted
root@kind-control-plane:/# kubectl create rolebinding pod-reader-bind -
➥ role=pod-reader --group=developers
rolebinding.rbac.authorization.k8s.io/pod-reader-bind created
root@kind-control-plane:/# kubectl config use-context carol
Switched to context "carol".
root@kind-control-plane:/# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 8m9s
我们已经用用户 carol 完成了测试,所以让我们再次切换回管理员用户,继续本章的下一个主题:服务账户。
练习考试
通过首先创建私钥,然后创建证书签名请求,最后使用 Kubernetes 中的 CSR 资源生成客户端证书,创建一个名为 Sandra 的新用户。使用 kubectl config 命令将 Sandra 添加到您的本地 kubeconfig 中。
3.3 服务账户
用户和组很棒,但机器怎么办?它们也需要访问 Kubernetes API,有时我们会使用像 Helm 或 Jenkins 或负载均衡器控制器这样的工具——作为我们集群中的 Pod 运行,这些工具需要与 Kubernetes API 通信以执行其主要功能的一部分(读取、写入、删除等)。我们为它们提供类似的身份验证机制——即令牌——以执行这些操作,如图 3.13 所示。然后我们可以使用角色和角色绑定来限制它们的权限,再次确保我们遵循最小权限原则。我们不希望我们的集群中的 Pod 有像我们的 system:masters 组那样的无限制访问。好消息是,这个默认组不会像对我们的管理员用户那样应用到服务账户上。

图 3.13 服务账户挂载到 Pod 可以像用户一样访问 API。
服务账户是命名空间范围内的资源。您在每个命名空间中至少会找到一个,那个被称为默认服务账户的,每次您生成一个新的命名空间时都会自动创建。让我们试试!
root@kind-control-plane:/# kubectl create ns web
namespace/web created
root@kind-control-plane:/# kubectl get sa -n web
NAME SECRETS AGE
default 1 10s
这个默认服务账户是自动创建的;我们不需要做任何特殊的事情来使这种默认行为发生。如果我们运行命令 kubectl describe secret -n web,我们可以看到与服务账户一起自动生成的令牌:
root@kind-control-plane:/# kubectl describe sa -n web
Name: default
Namespace: web
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: default-token-mv8xd
Tokens: default-token-mv8xd
Events: <none>
此外,您可以使用命令 kubectl describe secret default-token-mv8xd -n web 来 cat 出令牌并揭示其值:
root@kind-control-plane:/# kubectl describe secret default-token-mv8xd -n
➥ web
Name: default-token-mv8xd
Namespace: web
Labels: <none>
Annotations: kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: b0f4ff77-80be-44ab-85b3-
➥ b6f662678fff
Type: kubernetes.io/service-account-token
Data
====
namespace: 3 bytes
token:
➥ eyJhbGciOiJSUzI1NiIsImtpZCI6IllyN3NCd1JPOHZMbkhFZ3BLQkVBbUloZGx0eU5JSEF
➥ kS3JuUFRxS3dwWGsifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3V
➥ iZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJ3ZWIiLCJrdWJlcm5ldG
➥ VzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZGVmYXVsdC10b2tlbi1tdjh4Z
➥ CIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUi
➥ OiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY29
➥ 1bnQudWlkIjoiYjBmNGZmNzctODBiZS00NGFiLTg1YjMtYjZmNjYyNjc4ZmZmIiwic3ViIj
➥ oic3lzdGVtOnNlcnZpY2VhY2NvdW50OndlYjpkZWZhdWx0In0.DoBREFRm9pOUnSfqaI4qe
➥ mDGFKA5nkXBfXePPQscAm8C-S4an0X6JtcQShRp04WrDQzqGYCQ2nnCldxdxCPd8BPwBV-
➥ xBzsidL1Cwg-
➥ iXQTElvJLIx0N6CB9FGpiFBBHFY5GRTtX3LFBcWqoUoqNmyDUqJJnlNEKqGzy5-
➥ 4bjjMNOQ5JYywtjm50cxiGE2flORBjU7FBzZVWmvzo2XhtVR18LWUdomaZ1IwESXFU5HZNe
➥ sK-1MqcvD4C5wWc9igVBBxFJBoMJ06Z_Afi9vitnCtCYarQHcg66RvOVK-
➥ xyrc00RtjQbFLIlrjy68F3zG4BrvU9ChQOuMPAbCkG1ay4g
ca.crt: 1066 bytes
技术上,我们可以使用这个令牌(作为用户)来通过 Kubernetes API 进行身份验证。这就是为什么默认的 view 角色不允许查看服务账户,以及为什么我们犹豫是否给任何用户读取我们的机密数据的权限。这些令牌也可以在集群外部使用。
当我们创建一个 Pod 时,这个默认服务账户会自动挂载到 Pod 上,这样 Pod 就可以认证到 Kubernetes API。让我们运行命令 kubectl get po nginx -o yaml | grep volumeMounts -A14 来列出 Pod YAML 中的服务账户。你会看到默认服务账户实际上是在路径 /var/run/secrets/kubernetes.io/serviceaccount 上挂载到 Pod 上的:
root@kind-control-plane:/# kubectl get po nginx -o yaml | grep volumeMounts
➥ -A14
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-nhxf2
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: kind-worker2
preemptionPolicy: PreemptLowerPriority
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
如果我们 exec 进入 Pod,我们可以像之前描述 Secret 那样查看令牌。要进入 Pod 的容器并 cat 出令牌,我们可以运行命令 kubectl exec -it nginx --sh,然后 cat /var/run/secrets/kubernetes.io/serviceaccount/token(# 表示你已经成功进入容器内部的 shell):
root@kind-control-plane:/# kubectl exec -it nginx --sh
# cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6IllyN3NCd1JPOHZMbkhFZ3BLQkVBbUloZGx0eU5JSEFkS3J
➥ uUFRxS3dwWGsifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmN
➥ sdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjgwMTQ0NDMxLCJpYXQiOjE2NDg2MDg0MzEsImlzcy
➥ I6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZ
➥ XJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0IiwicG9kIjp7Im5hbWUiOiJuZ2lu
➥ eCIsInVpZCI6ImRmMzE3OTA3LTgwNTAtNDJmMS05ZWRmLTNhNDY2NzU4M2E5NiJ9LCJzZXJ
➥ 2aWNlYWNjb3VudCI6eyJuYW1lIjoiZGVmYXVsdCIsInVpZCI6ImRkMjlkZWEzLTkyMjMtNG
➥ QyOC1iOGE1LWUwODIxNTU1NGRjZCJ9LCJ3YXJuYWZ0ZXIiOjE2NDg2MTIwMzh9LCJuYmYiO
➥ jE2NDg2MDg0MzEsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1
➥ bHQifQ.ECeBokD1IbvCbBOIi6yiWtV1VIPABtxwnXmtudiQcRdmglQX06MVBEdCVJ4PT_rb
➥ nS7oB5fNAYFU2-8xU0EcDofeJH6IFEkALaI09TLCcha5YnQ65C6J1aKV-
➥ 58BR2I9aIZDb9uCJ247nyrDc075e2nf5zuBcfX82X5Yl0qAUtuiWDG-
➥ HPIwLfVnWYj9AasiFOMAMVaM_Ydsgru1U2vHwGFi-
➥ 7vo2CITU33X3NoTyDCSuNaNlAcB08_aWJuOYpQFtlAzpHQJR0XqGveIqu7cCMBd4XuD-
➥ m9AQDpkUZY59GOsJINCzkKo0cH1_nWx_PU42-drEwHU8m1HEdGwsAhNRg#
如果我们不希望自动将令牌挂载到我们创建的每个 Pod 上,那么我们可以从 serviceAccount 本身关闭此功能,或者我们可以在 Pod YAML 规范中卸载某些 Pod 的令牌,如图 3.14 所示。即使我们的团队成员不小心忘记为创建的 Pod 处理这个问题,我们也可以同时做这两件事。

图 3.14 您可以选择禁用从服务账户或 Pod 或两者挂载机密。
让我们创建一个新的服务账户,并告诉服务账户不要自动将令牌挂载到 Pod 上。最快的方法是创建服务账户 YAML 的框架,然后使用命令 kubectl create sa nomount-sa --dry-run=client -o yaml > nomount-sa.yaml 在原地修改它。使用命令 echo "automountServiceAccountToken: false" >> nomount-sa.yaml 将 automountServiceAccountToken: false 添加到文件 nomount-sa.yaml 的末尾:
root@kind-control-plane:/# echo "automountServiceAccountToken: false" >>
➥ nomount-sa.yaml
root@kind-control-plane:/# cat nomount-sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: null
name: nomount-sa
automountServiceAccountToken: false
现在我们已经设置了 YAML,我们可以使用命令 kubectl create -f nomount-sa.yaml 创建服务账户对象。如果我们创建一个新的 Pod 并使用这个服务账户,我们根本看不到它被挂载。让我们创建一个名为 no-mount 的新 Pod,并在 YAML 中指定使用这个新的服务账户,就像图 3.15 所示。

图 3.15 角色主体可以是用户、组或服务账户,用于对 Kubernetes 进行授权。
我们将使用相同的快捷方法输出 YAML 清单的骨架,并在之后使用命令kubectl run no-mount --image nginx --dry-run=client -o yaml > no-mount-pod.yaml对其进行修改。
注意:如果你从上一章开始创建了一个新的类型集群,你需要安装 Vim 以便可以编辑文件。使用命令apt update && apt install -y vim。
现在我们已经将文件本地化了,我们可以使用我们的 Vim 文本编辑器来编辑它,所以输入命令vim no-mount-pod.yaml来打开它。在spec下面,缩进两个空格,你应该添加serviceAccountName: nomount-sa。它看起来像这样:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: no-mount
name: no-mount
spec:
serviceAccountName: nomount-sa
containers:
- image: nginx
name: no-mount
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
我们已经明确告诉 Pod 使用这个服务账户,所以让我们使用命令kubectl create -f no-mount-pod.yaml创建 Pod,并使用命令kubectl get po no-mount -o yaml | grep volumeMounts -A14检查它。你会注意到使用了正确的服务账户,并且没有看到任何卷挂载。这是好事。
root@kind-control-plane:/# kubectl get po no-mount -o yaml | grep
➥ serviceAccount
serviceAccount: nomount-sa
serviceAccountName: nomount-sa
root@kind-control-plane:/# kubectl get po no-mount -o yaml | grep
➥ volumeMounts -A14
root@kind-control-plane:/#
当创建另一个 Pod 时,我们将使用默认令牌,该令牌设置为自动将令牌挂载到 Pod,但我们在创建我们的 Pod 时将明确告诉它不要这样做。使用命令kubectl run default-no-mount --image nginx --dry-run=client -o yaml > default-no-mount-pod.yaml创建一个新的 Pod YAML 文件。再次,让我们用 Vim 打开文件;在spec下面,缩进两个空格,插入automountServiceAccountToken: false。我们不需要告诉 Pod 使用默认令牌,因为如果没有指定,它将自动使用默认服务账户。默认情况下,所有 Pod 都是这种情况。Pod YAML 将看起来像这样:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: default-no-mount
name: default-no-mount
spec:
automountServiceAccountToken: false
containers:
- image: nginx
name: default-no-mount
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
让我们使用命令kubectl create -f default-no-mount-pod.yaml创建 Pod。我们将看到 Pod 使用了默认服务账户,但没有卷挂载,这正是我们想要的。
root@kind-control-plane:/# kubectl get po default-no-mount -o yaml | grep
➥ serviceAccount
serviceAccount: default
serviceAccountName: default
root@kind-control-plane:/# kubectl get po default-no-mount -o yaml | grep
➥ volumeMounts -A14
root@kind-control-plane:/#
现在我们已经了解了如何创建服务账户并将它们挂载到 Pod(或显式地不将它们挂载到 Pod),让我们回顾一下将角色和角色绑定分配给服务账户的过程。这与其他任何角色的创建或角色绑定创建类似,但主题是服务账户而不是用户或组。首先创建一个只允许对资源pods执行list操作的Role。为此,我们可以运行命令kubectl create role pod-list --verb=list --resource=pods。我们将创建名为pod-list-bind的Role binding,通过运行kubectl create rolebinding pod-list-bind --role=pod-list --serviceaccount=default:nomount-sa将服务账户nomount-sa绑定到pod-list角色:
root@kind-control-plane:/# kubectl create role pod-list --verb=list -
➥ resource=pods
role.rbac.authorization.k8s.io/pod-list created
root@kind-control-plane:/# kubectl create rolebinding pod-list-bind -
➥ role=pod-list --serviceaccount=default:nomount-sa
rolebinding.rbac.authorization.k8s.io/pod-list-bind created
为了测试我们的角色和角色绑定是否工作正常,我们可以使用kubectl auth can-i命令,并确定我们是否可以列出此服务账户中的 Pod。为此,我们将运行命令kubectl auth can-i list pods --as system:serviceaccount :default:nomount-sa:
root@kind-control-plane:/# kubectl auth can-i list pods --as
➥ system:serviceaccount:default:nomount-sa
yes
服务账户被表示为 system:serviceaccount:default:nomount-sa,因为这是根据 RBAC 插件的服务账户的用户名。这是我们观察服务账户的角度,因此我们需要使用 system:serviceaccount 前缀来引用它,后面跟着命名空间(在我们的例子中是 default),然后是服务账户 API 资源名称。恭喜!您已经创建了一个服务账户,并通过 Kubernetes 中的 Role binding 将其分配给了角色!
您现在可以同时使用这个服务账户为多个 Pod 提供服务,只要这些 Pod 存在于同一个命名空间中。您永远不能为 Pod 使用来自不同命名空间的 Service Account。此外,为了使用 nomount-sa 服务账户为新 Pod 提供服务,您必须在每个 Pod 的 YAML 文件中指定要使用的服务账户,就像我们在前面的例子中所做的那样。您可能会考虑删除默认服务账户,但这是不可能的。服务账户资源有自己的控制器,当我们生成新的命名空间时,它会自动创建。
考试练习
创建一个名为 secure-sa 的新服务账户,并创建一个使用此服务账户的 Pod。确保令牌不会被暴露给 Pod。
创建一个名为 acme-corp-role 的新集群角色,该角色将允许对 Deployments、replicates 和 DaemonSets 执行 create 操作。将该集群角色绑定到服务账户 secure-sa,并确保服务账户只能在默认命名空间内创建分配的资源,而不会在其他地方创建。使用 auth can-i 验证 secure-sa 服务账户不能在 kube-system 命名空间中创建 Deployments,并将命令的结果以及命令本身输出到文件中,然后共享该文件。
摘要
-
任何尝试访问 Kubernetes 的东西都必须通过携带用于认证的令牌的证书进行认证。
-
认证插件用于 Kubernetes,以提供一个接受或拒绝用户请求的机制。
-
Kubernetes 有自己的证书签名请求对象,可以签名证书,使其对用户或尝试访问 Kubernetes API 的任何东西有效。
-
一个名为基于角色的访问控制(RBAC)的通用插件用于在 Kubernetes 集群中授予用户权限。
-
用户和组在 Kubernetes 中具有任意含义,因此没有用户数据库或目录结构。证书是唯一需要检查的东西,因此用户或组可以是任何名称。
-
您可以通过角色、集群角色、角色绑定和集群角色绑定来分配 RBAC 权限,每种都适用于不同的用例。
-
除了一个之外,所有角色、集群角色、角色绑定和集群角色绑定的组合都可以用于考试:那就是集群角色绑定到角色。
-
RBAC 中有两个主要主题:用户和服务账户。在 Kubernetes API 中,用户不是一个受管理的资源,而服务账户是。
-
在创建用户时,证书签名请求中的通用名称是必需的。你可以从 Kubernetes API 生成客户端证书,它是一个自己的证书颁发机构。
-
在创建考试角色时,你可以使用预演选项,从而节省一些时间来手动输入需要添加值的角色的 YAML。
-
内置组与 Kubernetes 中的常规组不同。内置组由 API 服务器管理,不能被修改。
-
当创建新的命名空间时,会自动创建 Service Accounts。除非在 YAML 清单中另有指定,否则 Service Account 中的令牌会挂载到 Pod。
-
为了防止令牌自动挂载到 Pod,你可以在 Pod YAML 中添加指令,并通过在 Service Account YAML 中添加指令来禁用从 Service Account 的挂载。你还可以在首次创建 Service Account 时禁用令牌的挂载。
-
考试期间的一个有用工具叫做
kubectl auth can-I,它可以测试用户和 Service Accounts 的权限。
4 在 Kubernetes 中部署应用程序
本章涵盖了
-
将 Pod 调度到节点
-
在 Pod 内创建多个容器
-
使用 Helm 模板引擎
-
请求和限制 Pod 可以使用的资源
-
将配置数据传递给 Pod
在本章中,我们将把我们的重点从考试目标的工作负载和调度部分转移到许多不同的调度方面及其复杂性。你会意识到没有一种方法适合所有人,这为在最适合容器化应用程序的环境中运行应用程序提供了自由。这包括从底层基础设施中预留资源以及解耦配置信息等敏感数据的能力。
工作负载和调度领域
本章涵盖了 CKA 课程的工作负载和调度领域的一部分。这个领域涵盖了我们在 Kubernetes 上运行应用程序的方式。它包括以下能力。
| 能力 | 章节部分 |
|---|---|
| 使用 ConfigMaps 和 Secrets 配置应用程序。 | 4.3 |
| 理解资源限制如何影响 Pod 调度。 | 4.3 |
| 对清单管理和常见模板工具的了解。 | 4.2 |
| 理解用于创建健壮、自我修复的应用程序部署的原语。 | 4.1 |
4.1 调度应用程序
将应用程序部署到 Kubernetes 上运行称为调度,当你从在节点上运行 Pod 的角度考虑时,这是有意义的。有许多方法可以将应用程序(调度 Pod)部署到 Kubernetes。正如我们在前面的章节中所发现的,控制 Pod 分配到节点的 Kubernetes 组件称为调度器。调度器不仅会告诉 Pod 在特定节点上运行,还会确保节点上有可用的资源,以便 Pod 可以成功运行,如图 4.1 所示。

图 4.1 调度器在验证资源可用性后允许 Pod 在工作节点上运行。
CKA 考试将测试你创建和更新应用程序部署的知识。这包括根据它们的标签将 Pod 调度到特定的节点。例如,考试提示可能说,“将标签‘disk=ssd’应用到名为‘kind-worker’的工作节点上,并使用节点选择器调度名为‘ssd-pod’的 Pod 到‘kind-worker’节点。”为了在考试中更好地处理这类任务,让我们使用命令k run ssd-pod --image=nginx --dry-run=client -o yaml > ssd-pod.yaml创建一个新的 Pod。这个命令将 Pod YAML 保存到名为ssd-pod.yaml的文件中。在我们开始修改 YAML 之前,我们必须使用命令k get no --show-labels显示我们集群中每个节点的现有标签:
root@kind-control-plane:/# k get no --show-labels
NAME STATUS ROLES AGE VERSION LABELS
kind-control-plane Ready control-plane,master 13d v1.23.4
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io
➥ /arch=amd64,kubernetes.io/hostname=kind-control-
➥ plane,kubernetes.io/os=linux,node-role.kubernetes.io/control-
➥ plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-
➥ external-load-balancers=
kind-worker Ready <none> 13d v1.23.4
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io
➥ /arch=amd64,kubernetes.io/hostname=kind-worker,kubernetes.io/os=linux
kind-worker2 Ready <none> 13d v1.23.4
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io
➥ /arch=amd64,kubernetes.io/hostname=kind-worker2,kubernetes.io/os=linux
考试技巧 在考试中,可能不会预先在节点上应用标签。你可以使用命令k get no --show-labels来检查节点的标签。`
现在,让我们给名为 kind-worker 的节点应用一个新的标签,以表明该节点具有 SSD 硬盘而不是 HDD。这样,Pod 就可以通过节点选择器调度到 kind-worker 节点。要给节点应用标签,我们使用 key=value 语法,这样我们可以在以后选择该标签。要应用具有键 disktype 和值 ssd 的标签,我们可以使用命令 k label no kind-worker disktype=ssd。然后我们可以再次运行 show labels 命令,以查看标签是否成功应用。
root@kind-control-plane:/# k label no kind-worker disktype=ssd
node/kind-worker labeled
root@kind-control-plane:/# k get no --show-labels
NAME STATUS ROLES AGE VERSION LABELS
kind-control-plane Ready control-plane,master 13d v1.23.4
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io
➥ /arch=amd64,kubernetes.io/hostname=kind-control-
➥ plane,kubernetes.io/os=linux,node-role.kubernetes.io/control-
➥ plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-
➥ external-load-balancers=
kind-worker Ready <none> 13d v1.23.4
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=ssd,
➥ kubernetes.io/arch=amd64,kubernetes.io/hostname=kind-
➥ worker,kubernetes.io/os=linux
kind-worker2 Ready <none> 13d v1.23.4
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io
➥ /arch=amd64,kubernetes.io/hostname=kind-worker2,kubernetes.io/os=linux
现在工节点已经标记,我们可以在 Pod YAML 中添加节点选择器,以确保 Pod 被调度到这个新标记的节点。请打开文本编辑器(如 Vim)中的文件 ssd-pod.yaml。一旦打开,我们可以在与容器一词平行的行中添加 nodeSelector:,然后在下面缩进两个空格添加 disktype: ssd。我们缩进两个空格是为了表示指定的参数与上面的块组合在一起(例如,disktype 包含在块中),如图 4.2 所示。

图 4.2 根据节点标签 disk=ssd 选择调度 Pod 的节点类型
在添加此内容后,您可以保存文件。我们可以使用命令 k apply -f ssd-pod.yaml 来创建 Pod 并将其调度到指定的节点,这是我们在 Pod YAML 中通过节点选择器指定的。现在,让我们通过运行命令 k get po -o wide 来验证 Pod 是否被调度到预期的节点:
root@kind-control-plane:/# k get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
➥ NOMINATED NODE READINESS GATES
ssd-pod 1/1 Running 0 25s 10.244.1.2 kind-worker
➥ <none> <none>
看起来 Pod 被调度到了正确的节点,节点选择器的规则也成功应用。做得好!
在其他情况下,您可能希望更改 Pod 的调度方式。然而,在大多数情况下,您希望将您的更改限制在 Kubernetes 的调度中。请注意,更改调度默认规则会改变 Kubernetes 的主要功能,即提高可用性和容错性。您可能需要将 Pod 调度到特定的节点(如前一个任务中所示),但通常调度器会正确且高效地分配工作负载。在图 4.3 中,您将看到控制平面节点和工作节点默认的标签,包括我们在本章早期为 kind-worker 应用到的 disktype 标签。

图 4.3 所有节点都可以有不同的标签,您可以通过选择这些标签来调度 Pod。
4.1.1 节点选择器
正如我们在上一节中讨论的,可能会有这样的时候,你希望将 Pod 安排到特定的节点。也许那个节点具有某种类型的 CPU 或内存,这对你的工作负载进行了优化。我们可以在 Pod YAML(以及 Deployment YAML)内部使用许多东西来帮助我们解决这个问题。现在让我们讨论其中的一些——节点选择器和清单中的节点名称属性。我们可以在 YAML 清单中添加其中一个(而不是两个),以便能够将 Pod 安排在特定的节点上。它们之间有什么区别呢?嗯,节点选择器稍微灵活一些,因为它通过标签来识别节点。这样,标签可以应用于多个节点,而不仅仅是单个节点。
使用节点名称而不是节点选择器正好相反。你可以通过名称指定特定的节点,但正如我们所知,我们不能有两个具有相同名称的节点,这限制了我们的选择。如果我们遇到节点故障怎么办?如果节点资源耗尽怎么办?通过将 Pod 安排在单个节点上创建单点故障并不是一个好主意。正如你在图 4.4 中所看到的,你可以通过标签或名称强制将 Pod 安排在特定的节点上。

图 4.4 在安排 Pod 时,你可以根据节点的名称或标签强制指定其安排的节点。
DaemonSet 是一种 Kubernetes 对象,确保集群中的每个节点始终运行一个 Pod 副本。即使你删除了 Pod,DaemonSet 也会在任何缺少该 DaemonSet Pod 的节点上重新启动 Pod。DaemonSet 使用节点名称属性为 DaemonSet 内部的所有 Pod 安排 Pod 到特定的节点。这确保了只有一个 Pod 被分配给一个节点,并且通过使用节点名称,它们确保不会意外地在单个节点上运行两个 Pod,如图 4.5 中的 kube-proxy Pod 就是 Kubernetes 集群中运行的示例之一。

图 4.5 kube-proxy Pods 是 DaemonSet 的一部分,并位于集群中的每个节点上。
让我们使用命令 k -n kube-system get po kindnet-2tlqh -o yaml | grep nodeName -a3 来查看我们集群中运行的其中一个 DaemonSet Pod,你将看到类似于图 4.6 的输出。

图 4.6 通过在 Pod YAML 中指定节点名称来安排 Pod
在这个命令中,我们正在获取位于kube-system命名空间中的 kindnet 守护进程 Pod 的 YAML 输出。然后,我们可以通过 grep 工具搜索 YAML 文件并找到节点名称的实例。如您所见,节点名称被添加到这个守护进程 Pod 的 YAML 中。请记住,Pod 名称对于您的集群可能会有所不同,所以将名称kindnet-2tlqh替换为您集群中的 Pod 名称。获取 Pod 名称的命令是k get po -A。此外,要查看您集群中运行的守护进程集,请输入命令k get ds。
希望您能更好地理解节点名称的使用位置及其使用原因。在大多数情况下,您将使用节点选择器,您可能更清楚地看到其好处。节点选择器在更多场景中使用,因为它可以应用于多个节点,选择节点标签。例如,您可以使用disk=ssd来标记节点,表示它们具有固态硬盘,这可能对需要高磁盘 I/O 的应用程序产生影响。
考试技巧:您可能在考试中看到节点选择器或节点名称,问题可能要求您向现有的 Pod YAML 中添加必要的值。因此,熟悉这里的放置和正确语法。
对于 CPU 或内存密集型应用程序也是如此,因为节点的资源会直接影响容器内运行的 Pod 中的应用程序如何运行。要查看节点选择器在我们集群中的使用情况,无需进一步寻找,只需查看已经在kube-system命名空间中运行的 core DNS Pods。要查看 CoreDNS Pod 的 YAML,我们将使用与守护进程 Pod 相同的方法,使用命令k -n kube-system get po coredns-64897985d-4th9h -o yaml | grep nodeSelector -a4,您将看到类似于图 4.7 的输出。

图 4.7 通过 Pod YAML 中指定的标签选择此 Pod 将在哪个节点上运行
注意:您的 CoreDNS Pod 名称可能不同,因此请使用命令k -n kube-system get po来查找您独特的名称。
在这个命令中,我们正在获取位于kube-system命名空间中的 CoreDNS Pod 的 YAML 输出,并搜索单词nodeSelector。请记住,Pod 名称对于您的集群可能会有所不同,所以将名称coredns-64897985d-4th9h替换为您集群中的 Pod 名称。获取 Pod 名称的命令是k get po -A。现在我们已经在本节中查看过节点名称和节点选择器,以及编辑 Kubernetes 对象的内联方法,让我们继续探讨另一种调度技术,称为亲和性,以优先选择某些节点。
4.1.2 节点和 Pod 亲和性
如我们所知,标签可以应用于 Pod 和节点。我们可以使用这些标签来应用亲和力。亲和力很像节点选择器,但其规则更为灵活。例如,节点选择器规则会选择带有标签的节点,而亲和力规则则会偏好带有标签的节点,但如果该节点标签不存在,也会接受调度到其他节点。
为了更好地理解亲和力,我们必须了解它解决的问题。在较大的 Kubernetes 集群中存在一个固有的问题,不仅 Pod 会来去,节点也是如此。因此,仅根据节点的名称或标签为 Pod 设置硬规则是不够的。亲和力提供了一种更灵活的规则集,以便应用程序可以调度到一个不断变化的集群环境中。如图 4.8 所示,您可以设置偏好一个节点的规则,但如果该节点不可用,它仍然有选项被调度到第二个节点。

图 4.8 亲和力偏好将 Pod 调度到带有标签 SSD 的节点,如果不存在则调度到节点 LINUX。
让我们通过设置亲和力规则的示例来了解。我们将从一个全新的 Pod 开始,并使用 dry run 来创建 Pod YAML 的模板,就像我们之前使用命令 k run affinity --image nginx --dry-run=client -o yaml > affinity.yaml 所做的那样。一旦我们在当前目录中有一个名为 affinity.yaml 的文件,让我们在文本编辑器中打开它并添加我们的节点亲和力规则。在 spec 下方,与 containers 行对齐,添加 affinity: 到该行。缩进两个空格,然后添加 nodeAffinity:,后面跟着 requiredDuringSchedulingIgnoredDuringExecution:。参见图 4.9 了解剩余的 YAML 语法,因为它相当广泛。

图 4.9 基于特定表达式偏好将 Pod 调度到节点的亲和力变化
在设置所需属性的下级节点亲和力中,调度器只有在满足此规则的情况下才能调度 Pod。这类似于 nodeSelector,但具有更灵活的语法。然而,对于调度器来说,首选属性是可选的。如果没有节点匹配此偏好,Pod 仍然会被调度。此外,您可以为每个首选规则指定介于 1 和 100 之间的权重,调度器将根据节点的其他优先函数的得分,基于总权重进行评估。总得分最高的节点将被优先考虑。现在我们已经添加了亲和力规则,让我们使用命令 k apply -f affinity.yaml 来调度我们的 Pod:
root@kind-control-plane:/# k apply -f affinity.yaml
pod/affinity created
root@kind-control-plane:/# k get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
➥ NOMINATED NODE READINESS GATES
affinity 1/1 Running 0 14s 10.244.1.6 kind-
➥ worker2 <none> <none>
pod-tolerate 1/1 Running 0 24h 10.244.2.16 kind-worker
➥ <none> <none>
从命令 k get po -o wide 的输出中,我们看到 Pod 被调度到了没有 disktype=ssd 标签的节点 (kind-worker2)。这是因为我们为这个首选规则设置的权重太低,并且因为 kind-worker 上已经有一个 Pod 在运行,所以 kind-worker2 节点的总权重更高(权重越高,Pod 被调度的概率越大)。如果您没有完全理解,或者想检查您的作业,可以使用以下命令应用此 YAML 创建您的 Pod:k apply -f https://raw.githubusercontent.com/chadmcrowell/k8s/main/manifests/pod-node-affinity.yaml。
与节点亲和力类似,Pod 亲和力是节点亲和力,即基于该节点上已存在的其他 Pod 的偏好。用更简单的话来说,如果一个 Pod 已经存在于节点上(例如,nginx),那么请继续将新的 Pod 调度到该节点(即具有 nginx Pod 的节点),如图 4.10 所示。Kubernetes 通过标签检查 Pod 是否存在。这对于需要运行在相同节点上以提高性能或延迟要求的 Pod 来说是有效的。

图 4.10 Pod 只会被调度到已经运行了 nginx Pod 的节点。
为了演示 Pod 亲和力,我们首先使用命令 k run nginx --image=nginx 创建一个具有标签 run=nginx 的 Pod。现在,让我们继续使用之前用于节点亲和力的相同 YAML 文件,但只更改几行,因为您将看到,设置 Pod 亲和力与设置节点亲和力非常相似。如果您还没有之前的 YAML 文件,可以使用 curl 和 -O 标志下载文件,并使用以下命令保存文件:curl -O https://raw.githubusercontent.com/chadmcrowell/k8s/main/manifests/pod-node-affinity.yaml。
请继续在您的文本编辑器中打开文件,将 Pod 的名称更改为 pod-affinity,以防止与之前演示中仍在运行的 Pod 发生命名冲突。此外,将 nodeAffinity 更改为 podAffinity;将 nodeSelectorTerms: 行更改为 - labelSelector:;从 matchExpressions 中移除连字符 (-);并将键更改为 run,值更改为 nginx。
最后,在 labelSelector 下方添加 topologyKey 以保持与单词 labelSelector 的一致性。拓扑键是 Pod 亲和力所必需的,它简单地表示节点标签键。在这种情况下,我们集群中的所有节点都有 kubernetes.io/hostname 键,因此这将匹配所有节点。参见图 4.11 比较节点亲和力和 Pod 亲和力。您可以删除拓扑键以下的所有内容,因为在这个例子中我们只关注所需的 Pod 亲和力规则。

图 4.11 Pod YAML 中 Pod 间亲和性与节点亲和性在结构上的不同
现在,我们的 nginx Pod 正在运行,我们已经输出了具有 Pod 间亲和性规则的新 Pod 的 YAML,我们可以继续使用命令 k apply -f pod-node-affinity.yaml 来调度 Pod。在调度 Pod 后,你应该会看到 Pod 已经通过运行命令 k get po -o wide 被调度到与名为 nginx 的 Pod 相同的节点,输出类似于图 4.12。

图 4.12 根据亲和性规则,Pod 已被调度到相同的节点。
在正常情况下,你可以看到 Pod 将会被调度到名为 kind-worker 的节点,但由于我们告诉调度器找到所有带有 run=nginx 标签的 Pod,并将名为 pod-affinity 的 Pod 调度到该节点,它覆盖了默认的调度设置。如果你得到不同的结果,或者可能根本无法调度你的 Pod,请将你的内容与该文件中的 YAML 进行比较,你可以使用以下命令下载该文件:curl -O https://raw.githubusercontent.com/chadmcrowell/k8s/main/manifests/pod-with-pod-affinty.yaml。
练习考试
将标签 disk=ssd 应用到一个节点上。创建一个名为 fast 的 Pod,使用 nginx 镜像,并确保它根据标签 disk=ssd 选择节点。
使用 k edit po fast 编辑 fast Pod,并将节点选择器更改为 disk=slow。注意,Pod 无法更改,YAML 已保存到临时位置。将 /tmp/ 中的 YAML 应用到强制删除并重新创建 Pod,使用单个强制命令。
使用 nginx 镜像创建一个新的 Pod,名为 ssd-pod,并使用节点亲和性根据标签 disk=ssd 的权重 1 选择节点。如果选择标准不匹配,它还可以选择带有标签 kubernetes .io/os=linux 的节点。
4.2 使用 Helm
在 Kubernetes 中配置应用程序以用于 Deployment 的所有不同方法中,存在一个配置错误和开发可重部署组件的问题。例如,当创建带有服务、入口、配置映射、角色和角色绑定的部署时,可能会很复杂,并且并不总是直观地了解应用程序开发者希望应用程序如何运行以根据最佳实践操作。Helm 是 Kubernetes 的包管理器,支持模板化以解决这个问题。
测试您使用 Helm 的知识将是考试的一部分;因此,了解它解决的问题以及如何使用它是很重要的。我们不必从头开始重新构建每个 YAML 文件,它们可以被打包并存储在存储库中供他人使用和分享。除了模板引擎之外,它类似于您最喜欢的 Linux 发行版中的包管理器(例如,Ubuntu 中的 apt),Helm 允许您从存储库(公共或私有)中拉取 Helm 图表并本地安装这些 Helm 图表。
要在 macOS 上安装 Helm,只需执行命令 brew install helm。要在 Windows 上安装 Helm,只需执行命令 choco install kubernetes-helm。要在 Ubuntu 或 Debian 上安装 Helm,执行以下命令将 Helm 添加到您的包存储库,并使用 apt 安装:
curl -fsSL -o get_helm.sh
➥ https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
一旦安装了 Helm,您就可以运行命令 helm version --short,您将看到类似于图 4.13 的输出,这将显示当前安装的 Helm 版本。

图 4.13 通过 CLI 显示 Helm 的版本。
接下来,我们可以安装一个 Helm 图表,它不过是一组打包成应用程序的 YAML 清单,该应用程序在 Kubernetes 中运行。为了连接到集群,Helm 使用与 kubectl 相同的 kubeconfig。由于 Helm 使用相同的 kubeconfig,它可以执行操作,这可能是好事也可能是坏事。对我们来说,现在没问题,因为我们需要管理员权限来创建与我们的 Helm 图表相关的资源。
我们可以使用位于 artifacthub.io/ 的公共存储库中的任何 Helm 图表。要使用 Helm 图表,我们首先必须添加包含 Helm 图表的存储库,否则 Helm 将不知道从哪里拉取图表。我们可以运行命令 helm repo add hashicorp https://helm.releases.hashicorp.com 将存储库添加到 Helm,然后运行命令 helm repo list 以查看它已被添加:
root@kind-control-plane:/# helm repo add hashicorp
➥ https://helm.releases.hashicorp.com
"hashicorp" has been added to your repositories
root@kind-control-plane:/# helm repo list
NAME URL
hashicorp https://helm.releases.hashicorp.com
现在已经添加了存储库,我们可以使用命令 helm search repo vault 在该存储库中搜索所有可用的图表。您会看到,在 HashiCorp 存储库中有一个名为 hashicorp/vault 的 Helm 图表,我们可以使用它:
root@kind-control-plane:/# helm search repo vault
NAME CHART VERSION APP VERSION DESCRIPTION
hashicorp/vault 0.21.0 1.11.2 Official HashiCorp
➥ Vault Chart
让我们再添加一个存储库!我认为在 kind 集群中安装一个负载均衡器会有所帮助。这将在我们讨论第六章中的通信时很有用。Kubernetes 中的负载均衡器允许从集群外部访问我们的应用程序。例如,如果我们将 Web 应用程序作为 Kubernetes 部署的一部分托管,我们将能够通过 LoadBalancer 服务公开它。这样,访问您应用程序的访客可以通过其 IP 地址访问应用程序的前端。不过,现在让我们专注于添加存储库和安装 Helm 图表。
我们可以使用与添加 HashiCorp 存储库相同的命令 helm repo add metallb https://metallb.github.io/metallb 添加存储库。
root@kind-control-plane:/# helm repo add metallb
➥ https://metallb.github.io/metallb
"metallb" has been added to your repositories
现在我们已经添加了仓库,我们可以使用命令 helm search repo metallb: 来搜索 metallb
root@kind-control-plane:/# helm search repo metallb
NAME CHART VERSION APP VERSION DESCRIPTION
metallb/metallb 0.13.5 v0.13.5 A network load-balancer
➥ implementation for Kube...
确实如此!现在,在我们安装它之前,让我们谈谈一个叫做 值文件 的东西。现在,我们知道 Helm 是一个模板引擎。模板引擎会读取一个包含值的文件,并将其插入到模板中,从而改变 Helm 图表的配置。这个值文件是用 YAML 编写的,其外观将类似于图 4.14 中 Pod 的配置。

图 4.14 如何创建一个值文件并将值应用到 Pod 清单的 Helm 中
然后,一旦我们确定了这些值,实际 Helm 图表中的模板化看起来类似于我们之前创建的常规 Pod YAML 文件,但它们使用模板语法 {{ .values.name }} 而不是填充的值,如图 4.15 所示。

图 4.15 与我们之前创建的常规 Pod YAML 文件相比,具有模板化值的 Helm 图表
现在我们已经知道了模板文件的作用(它将您的 YAML 文件模板化),您可能想知道我们如何将值文件应用到我们的 Helm 图表中。好吧,我们只需运行 helm install 命令并使用 --values 标志。所以,要安装 MetalLB Helm 图表,请执行命令 helm install metallb metallb/metallb --values values.yaml。但是等等!我们还没有创建值文件。现在让我们使用以下 YAML 并将其保存到名为 values.yaml 的文件中:
address-pools:
- name: default
protocol: layer2
addresses:
- 172.18.255.200-172.18.255.250
地址范围(172.18.255.200-250)位于您的 Docker 网桥网络地址范围内(您的范围可能不同)。要找到 kind Docker 网桥 CIDR,请使用命令 docker network inspect -f '{{.IPAM.Config}}' kind(输入 exit 退出 kind-control-plane 命令行)。一旦创建了 values.yaml 文件,您就可以运行命令 helm install metallb metallb/metallb --values values.yaml(输入 docker exec -it kind-control-plane bash 返回到 kind-control-plane 命令行):
root@kind-control-plane:/# helm install metallb metallb/metallb --values
➥ values.yaml
NAME: metallb
LAST DEPLOYED: Tue Sep 6 19:06:07 2022
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
MetalLB is now running in the cluster. Now you can configure it via its
➥ CRs. Please refer to the MetalLB official docs on how to use the CRs.
现在我们已经通过 Helm 部署了 MetalLB 负载均衡器,我们可以看到它不仅仅是一个单一的资源。它创建了一个 Deployment、一个 DaemonSet、四个 Secrets、两个 Service Accounts 和一个 ConfigMap,如图 4.16 所示(通过运行 k get po,deploy,ds,secret,sa,cm 获取)(我们只需运行 helm install,它就安装了所有这些!真酷,不是吗?这让我想起了我坐在餐厅里点了一道美味的菜。我不知道它是如何制作的,也不知道所有的配料是什么;它只是从厨房里热腾腾地端出来,味道如此美味!)

图 4.16 通过 Helm 图表简化 Kubernetes 调度并创建多个 Kubernetes 对象
要列出我们已安装的 Helm 图表,我们可以运行命令 helm ls:
root@kind-control-plane:/# helm ls
NAME NAMESPACE REVISION UPDATED
➥ STATUS CHART APP VERSION
metallb default 1 2022-09-06 19:06:07.164270451 +0000 UTC
➥ deployed metallb-0.13.5 v0.13.5
我们将在第六章中回到使用 MetalLB。现在,这只是一个使用 Helm 和使用自定义值安装 Helm 图表的好习惯。
4.3 Pod 元数据
在详细讨论了调度之后,让我们谈谈在 Kubernetes 中调度应用程序之前你可以进行的一些自定义设置。本节将介绍几个选项,但正如你所想象的那样,没有一种大小适合所有情况。并非所有 Kubernetes 对象(部署、Pod、机密、配置映射等)都是用相同的方式创建的。有时你需要考虑底层硬件,不仅是在选择将 Pod 调度到哪个节点方面,正如我们在第 4.1.2 节和第 4.1.3 节中讨论的那样,而且还要考虑资源限制、附加功能和每个应用程序可能需要的独特特性。让我们从资源请求和限制开始,这将帮助我们调度到具有充足资源(CPU 和 RAM)的节点上的 Pod。
4.3.1 资源请求和限制
资源请求与标签类似,它们向调度器提供信息,以确定 Pod 将被调度到哪个节点或放置在哪个节点上。请求以 Pod 运行最优所需的 CPU 和 RAM 的最小值的形式出现。限制也以 CPU 或 RAM 的值的形式出现,但确定 Pod 应该消耗的最大值,与最小值相对。这两个参数不必同时定义——这意味着你可以应用请求而不设置限制,反之亦然。同样,你可以在不指定 RAM 请求的情况下仅指定 CPU 请求。因此,这些字段相互独立,完全是可选的。
在 CKA 考试中,你将接受关于资源限制及其如何影响 Pod 调度知识的测试。一个可能的考试提示可能是:“节点‘worker-1’有 500MB 的内存和 0.5 个 CPU 可用。调度一个 Pod,允许这种内存和 CPU 限制,并且仍然调度到该节点。”知道如何修改 Pod YAML 以更改资源限制将使你能够在考试当天成功完成此类问题。
你可以通过将资源请求和限制添加到容器规范中,通过 YAML 为 Pod 应用资源请求和限制。与容器的名称和镜像一起,你可以在资源下方添加请求和限制作为值。为了最好地完成这项任务(而无需知道 YAML 中的确切位置),你可以再次通过执行命令k run nginx2 --image=nginx --dry-run=client -o yaml > nginx2.yaml来对 Pod 进行干运行。
当你在你喜欢的文本编辑器中打开文件时,你将已经看到资源部分。正如我们从 YAML 语言中了解到的,我们可以在 resources 单词下方简单地缩进两个空格,并开始定义我们的请求和限制。单词 resources 用于插入资源限制和请求。资源请求是你可以为 Pod 指定的 CPU 和内存的最小值。资源限制是你可以为相同内容指定的限制。我们将在本书的后面部分介绍资源限制和请求。现在,让我们删除 resources 单词后面的花括号,因为花括号表示一个空条目,所以我们没有为我们的 Pod 指定任何资源限制或请求。删除它们将产生相同的效果。对于这个容器,我们将请求 100 milicores 的 CPU 和 128 mebibytes,并将容器限制在不超过 2 个 CPU 和 1 个千兆字节的内存。你可以在图 4.17 中看到在 YAML 规范中插入请求和限制的位置。

图 4.17 在 Pod YAML 中何处插入 Pod 的资源请求
现在,你可能想知道为什么 CPU 和内存有不同的度量。在 Kubernetes 生态系统中,CPU 的限制和请求是以 CPU 单位来衡量的。一个 CPU 单位相当于 1 个物理 CPU 核心,或者 1 个在物理机器内部运行的虚拟核心,具体取决于节点是物理主机还是虚拟机。你可以用 milicores 来写这个值,而 1000m 的值就等同于 1 个 CPU。也可以允许分数请求,所以我们可以输入 0.5 CPU 的值(等于 500m)。内存是以 mebibytes 为单位来衡量的,这是一个基于 2 的幂的度量(1 个 mebibyte 等于 2²⁰ 或 1,048,576 字节)。
我们指定请求和限制的原因是,如果一个 Pod 运行的节点有足够的资源可用,那么容器使用比其请求的资源更多的资源是可能的(也是允许的)。然而,容器不允许使用超过其资源限制的资源。
4.3.2 多容器 Pod
在 Kubernetes 中,有时在同一个 Pod 中运行多个容器是有意义的。在某些情况下,这可能很有用,因为 Pod 内的每个容器都共享名称网络命名空间和存储,因此通信更快,你不必建立额外的服务和网络策略来在 Pod 之间通信。在同一个 Pod 内创建多个容器有几个原因。一个是用于日志收集,所有应用程序日志都会发送到一个专门用于日志的单独容器。另一个原因是确保另一个容器或服务的初始化——换句话说,从一个容器中运行命令将验证 MySQL 服务是否正在运行,例如。同一个 Pod 中的容器还可以共享存储。因此,你可以将卷附加到两个 Pod 上,并将数据同时流式传输到该卷。如果其中一个容器将日志输出到文件,然后另一个容器读取它们,这可能很有用。在这种情况下,第二个容器有时被称为“sidecar 容器”。这是因为它不是作为主应用程序运行;它只是协助收集日志。
让我们运行以下命令来创建一个 sidecar 容器,看看这个操作的实际效果:k run sidecar --image=busybox --command "sh" "c" "while true; do cat /var/log/nginx/error.log; sleep 20; done" --dry-run=client -o yaml > sidecar.yaml。现在,用你喜欢的文本编辑器打开文件sidecar.yaml。将c改为-c,并添加另一个容器作为当前 sidecar 容器的主体容器。此外,为了允许 sidecar 容器读取日志数据,我们将添加一个挂载到emptyDir卷类型的卷挂载,如图 4.18 所示。

图 4.18 向 Pod 添加额外的容器,用于从主应用程序读取日志
emptyDir卷是与容器同生共死的多种卷类型之一。这是一种临时存储,因此一旦 Pod 被删除,数据也会被删除并永远消失。
在同一个 Pod 中创建多个容器的另一个用例是确保主应用程序容器初始化。这被称为初始化容器,因为它的唯一任务是检查主容器或执行某些任务,一旦完成其工作,任务就完成了,不会继续运行。
你可以将初始化容器添加到任何 Pod 清单中,与容器的规范一致。为了快速创建我们的起始 YAML,让我们运行命令 k run init --image=busybox:1.35 --command "sh" "c" "echo The app is running! && sleep 3600" --dry-run=client -o yaml > init.yaml。然后,我们将按照单词 container 的方式输入 initContainers:。对于其他所有内容,我们将模仿现有的容器,除了命令。我们将运行 until 循环 until nslookup init-svc; do echo waiting for svc; sleep 2; done。完整的 YAML 应该看起来与图 4.19 中看到的内容相似。

图 4.19 初始化容器不会允许主应用程序在创建服务 init-svc 之前启动。
指定一个初始化容器与普通容器非常相似,但当你开始创建 Pod 时,你会更清楚地注意到它们之间的区别。在创建时,你会看到 Pod 进入挂起状态。它将保持挂起状态,直到你创建名为 init-svc 的服务。当你使用命令 k create svc clusterip init-svc --tcp 80 创建服务时,Pod 将从 Init:0/1 变为 running:
root@kind-control-plane:/# k apply -f init.yaml
pod/init created
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
init 0/1 Init:0/1 0 7s
root@kind-control-plane:/# k create svc clusterip init-svc --tcp 80
service/init-svc created
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
init 0/1 Init:0/1 0 79s
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
init 0/1 Init:0/1 0 4m17s
root@kind-control-plane:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
init-svc ClusterIP 10.96.162.73 <none> 80/TCP 6m44s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10h
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
init 1/1 Running 0 7m20s
注意,Pod 直到大约 7 分钟后才开始运行。所以如果你感到不耐烦,只需再等几分钟,直到 Pod 重新启动。
4.3.3 ConfigMaps 和 Secrets
因此,既然我们已经讨论了 Pods 请求 CPU 和内存,那么让我们通过讨论 Kubernetes 中的 ConfigMap 来看看 Pods 还可以请求什么。很多时候,你需要将配置数据传递给你的应用程序,无论是更改日志级别还是更改你的 Web 应用的背景颜色。最常见的方法是在 Kubernetes 中使用 ConfigMap 对象。
一旦创建了 ConfigMap 对象,你可以使用各种方法将其注入到 Pod 中。你可以通过环境变量或挂载到容器的卷将 ConfigMap 注入到 Pod 中。你可以从字面值(即用户名和密码)创建 ConfigMap,从文件(即 ca.crt)创建,或从目录中的多个文件创建。
你可以通过使用强制命令或创建 YAML 并将其检查到源代码中来创建 ConfigMap。因为我们更喜欢后者,让我们使用命令 k create configmap redis-config --from-literal=key1=config1 --from-literal=key2=config2 --dry-run=client -o yaml > redis-config.yaml 创建 YAML 并将其保存到文件中。此命令将创建一个名为 redis-config 的 ConfigMap,其中包含字面量键 key1 的值为 config1,以及字面量键 key2 的值为 config2。
我们将进行 dry run(即模拟创建资源)并最终将输出保存到名为 redis-config.yaml 的文件中。当我们用文本编辑器打开这个文件时,我们会注意到键值对可以逐行建立。或者,我们也可以通过指定键然后一个管道符号(|)来执行多行参数,就像我们在前面的章节中所做的那样。让我们替换文件中现有的键值对,以便将 ConfigMap 数据存储在 Redis 缓存中供以后使用。为此,删除以 key1 和 key2 开头的行,并在我们的 YAML 文件中的 data 下添加一个名为 redis-config 的键,通过在 YAML 文件中的多行参数下添加它,类似于图 4.20。

图 4.20 在 Kubernetes 中自动创建 ConfigMap 会将数据 Base64 编码。
现在我们已经为 ConfigMap 创建了 YAML 文件,保存并关闭文件。使用命令 k apply -f redis-config.yaml 创建 ConfigMap。
由于 ConfigMap 现在可供我们的应用程序使用,请继续创建一个 Redis Pod 的 YAML 文件,使用命令 k run init --image=redis:7 --port 6379 --command 'redis-server' '/redis-master/redis.conf' --dry-run=client -o yaml > redis.yaml。让我们为这个 Pod 添加一个用于 Redis 配置数据的卷挂载,以及一个 emptyDir 类型的卷用于任何临时数据。我们可以将卷和挂载路径插入到 YAML 规范中,如图 4.21 所示。

图 4.21 通过 emptyDir 卷将 ConfigMap 数据附加到容器中,确保卷名和 mountPath 名相同
我们将继续创建该 Pod,一旦它运行起来,我们将使用命令 k exec -it redis --redis-cli 在容器内放置一个带有 Redis CLI 提示的 shell。完成这些后,我们可以发出 Redis 特定的命令,这些命令是 CONFIG GET maxmemory 和 CONFIG GET maxmemory-policy,您将得到类似于图 4.22 的输出。

图 4.22 我们可以通过 Redis CLI 查看 ConfigMap 数据,并将其与原始 ConfigMap 数据相关联。
我们可以直接将这些与我们的 ConfigMap 数据匹配,并且它们匹配!这是将配置数据插入容器并使配置数据与主应用程序解耦的绝佳方式。
Secrets 与 ConfigMaps 类似,但不同之处在于它们存储的是密钥数据而不是应用程序配置数据。无论是数据库密码还是证书数据,您都可以使用 Base64 加密在 Kubernetes 中存储。然而,如果我说它是安全的,请不要相信我,因为它并不安全。任何有权访问您的集群的人都可以查看和读取密钥数据,因此永远不要将其作为您唯一的保护方法。
您可以使用熟悉的命令 k create secret generic dev-login --from-literal=username=dev --from-literal=password= 'S!B\*d$zDsb=' --dry-run=client -o yaml > dev-login.yaml 创建秘密。现在在您的文本编辑器中打开文件 dev-login.yaml,您会注意到用户名和密码都是 Base64 编码的。秘密值以 Base64 字符串的形式编码,并且默认情况下以未加密的方式存储,如图 4.23 所示。

我们名为 dev-login 的秘密中的所有数据都会自动进行 Base64 编码。
现在,使用命令 k apply -f dev-login.yaml 来创建秘密。我们可以通过将秘密挂载到容器内部的目录中来使其对 Pod 可用。首先,使用命令 k run secret-pod --image=busybox --command "sh" "c" "echo The app is running! && sleep 3600" --dry-run=client -o yaml > secret-pod.yaml 创建一个 Pod YAML 文件。让我们在文本编辑器中打开文件 secret-pod.yaml 并进行一些修改。我们将在 Pod 内部的容器中添加一个卷挂载,并添加一个类型为 Secret 的卷,Kubernetes 知道如何处理它。您可以在 Pod YAML 中指定此内容,如图 4.24 所示。

将 Pod 要访问的秘密数据挂载,确保数据可以从容器内的 /etc/secret-vol 访达。
在这一点上,我们可以通过运行命令 k exec secret-pod --cat /etc/secret-vol/username && echo 和 k exec secret-pod --cat /etc/secret-vol/password && echo 在 Pod 内部查看我们的秘密数据。您将看到类似于图 4.25 的输出。

通过在容器内运行命令可以获取秘密数据。
这不是向容器提供秘密数据的唯一方法;我们还可以通过 Pod 内的环境变量使用秘密。您可以在 kubectl run 命令后使用 --env 标志,例如 k run secret-env --image=busybox --command "sh" "c" "printenv DEV_USER DEV_PASS; sleep 8200" --env=DEV_PASS=password --dry-run=client -o yaml > secret-env.yaml。当您打开 secret-env.yaml 文件时,可以将 value: 替换为 valueFrom: 并删除 password 的值。在 valueFrom: 下方,您可以缩进两个空格并输入 secretKeyRef:。在其下方,您可以缩进两个空格并输入 name: 后跟之前创建的秘密名称,即 dev-login。然后,与名称对齐,您可以输入 key: 后跟 username,如图 4.26 所示。您可以为 Kubernetes 中的任何秘密或秘密值重复此操作。此外,多个 Pod 可以引用同一个秘密,因此我们不需要删除之前创建的 Pod(命名为 secret-pod)来在新的 Pod 中使用秘密。

图 4.26 您可以通过环境变量将秘密数据传递给 Pod。在容器内设置命令以打印环境变量(printenv)。别忘了将c改为-c!
考试练习
创建一个名为limited的 Pod,使用镜像httpd,并将 CPU 的资源请求设置为 1Gi,内存设置为 100Mi。
创建一个名为ui-data的 ConfigMap,其键值对如下。将 ConfigMap 应用到名为frontend的 Pod 上,使用镜像busybox:1.28,并通过以下环境变量传递给它:
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
摘要
-
术语调度指的是创建一个 Pod 并将其分配给一个节点。您可以通过节点选择器、节点名称或亲和规则来更改 Pod 分配到的节点。确保您知道这些调度更改的正确 YAML 语法。
-
您还可以应用标签来控制 Pod 的调度。就像节点选择器一样,您可以使用标签选择器根据 Pod 的标签将 Pod 调度到特定的节点。对于考试,了解如何将标签应用到节点和 Pod 上以影响调度。
-
就像 Linux 中的包管理器一样,Helm 是 Kubernetes 的包管理器和模板引擎。了解如何使用 Helm 进行考试以及如何使用 Helm 模板将应用程序部署到 Kubernetes。
-
使用资源请求和限制为 Pod 保留 CPU 和内存。这对于考试很重要,因为 Pod 或 Deployment 的 YAML 语法比大多数都要复杂一些。
-
在 Pod 内部创建多个容器有特定的原因。共享相同的网络命名空间和存储允许直接访问和双向通信。
-
在 Kubernetes 中使用 ConfigMaps 和 Secrets 将配置数据和敏感信息注入 Pod 中变得简单。确保您知道如何使用作为卷挂载到 Pod 中的 ConfigMaps 和 Secrets,以及环境变量。
5 在 Kubernetes 中运行应用程序
本章涵盖
-
为高可用性扩展应用程序
-
执行滚动更新和回滚
-
通过暴露部署来创建服务
-
在 Kubernetes 集群上执行维护任务
本章涵盖了 Kubernetes 的操作方面,我们通过介绍如何维护已在 Kubernetes 中运行的应用程序来介绍。上一章介绍了如何创建 Deployment(包括模板化)、调度属性、ConfigMaps 和 Secrets。本章涵盖了你在考试中完成诸如为应用程序提供额外资源、扩展应用程序、为应用程序提供一致的端点以及推出应用程序新版本等任务时将采用的一些常见方法。在继续工作负载和调度考试课程的基础上,本章与上一章一起将涵盖考试的 15%,并且是完成在 Kubernetes 中运行的应用程序的高可用性和自我修复的一部分。
工作负载和调度领域
本章涵盖了 CKA 课程的工作负载和调度领域的一部分。该领域涉及我们在 Kubernetes 上运行应用程序的方式。它包括以下能力。
| 能力 | 章节部分 |
|---|---|
| 了解如何扩展应用程序。 | 5.1 |
| 理解 Deployment 以及如何执行滚动更新和回滚。 | 5.1 |
| 理解用于创建健壮、自我修复的应用程序 Deployment 的原语。 | 5.2 |
5.1 应用程序编排
Deployment 是 Kubernetes 中的一个核心对象,通过它你可以实现高可用性、健壮性和自我修复。无状态应用程序——正如其名所示——不包含状态(数据),因此 Pod(及其内部的容器)可以被替换和重新启动,而不会影响应用程序的整体健康。在 Kubernetes 中,无状态应用程序通常通过 Deployment 进行管理。相比之下,Kubernetes 中的 StatefulSet 对象不能像那样轻松地替换或重新启动,因为状态化应用程序的运行需要数据。例如 MySQL,数据被写入主数据库表并复制到额外的只读数据库实例。考试中不会测试 StatefulSets,所以我们只关注 Deployment。
5.1.1 修改运行中的应用程序
到目前为止,在这本书中,我们特别关注 Pod,这很好,但很多时候,仅有一个 Pod 对于你的应用程序是不够的。在 Kubernetes 中,有一种通过创建 Deployment 来创建冗余和容错的方法。CKA 考试将测试你修改现有 Deployment 的能力。这包括更改镜像和更改 Pod 副本的数量。CKA 考试中的一个任务可能看起来像以下这样。
创建一个名为 apache 的部署,使用镜像 httpd:2.4.54 并包含三个 Pod 副本。部署创建后,将部署扩展到五个副本,并将镜像更改为 httpd:alpine。 |
|---|
如果你还没有访问现有的 Kubernetes 集群,附录 A 中解释了如何使用 kind 创建 Kubernetes 集群。一旦你的 kind 集群构建完成,请使用预安装在控制平面节点上的 kubectl 工具。你可以通过输入命令 docker exec -it kind-control-plane bash 并按照说明来获取控制平面节点的 Bash shell。
在 Kubernetes 的无状态应用中,创建相同应用的副本不是问题。这是因为所有微服务数据都与容器本身解耦;因此,无论请求是前往应用的第一个副本还是第十五个副本,都会得到相同的响应。让我们继续创建一个名为 apache 的部署,使用命令 k create deploy apache --image httpd:2.4.54 --replicas 3。现在我们已经创建了部署,我们可以使用命令 k get deploy 检查状态,我们会看到有三个副本。因为我们想将副本数量增加到五个,所以你需要运行命令 k scale deploy apache --replicas 5 来“扩展部署”,这将把部署从一个 Pod 扩展到两个 Pod:
root@kind-control-plane:/# k create deploy apache --image httpd:2.4.54 -
➥ replicas 3
deployment.apps/apache created
root@kind-control-plane:/# k get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
apache 3/3 3 3 14s
root@kind-control-plane:/# k scale deploy apache --replicas 5
deployment.apps/apache scaled
root@kind-control-plane:/# k get po
NAME READY STATUS
➥ RESTARTS AGE
apache-74f79bcc68-7lzqz 1/1 Running
➥ 0 7s
apache-74f79bcc68-89zwg 1/1 Running
➥ 0 33s
apache-74f79bcc68-v6drf 1/1 Running
➥ 0 33s
apache-74f79bcc68-x7bfw 1/1 Running
➥ 0 33s
apache-74f79bcc68-xdx4b 1/1 Running
➥ 0 7s
你会注意到 Pod 前缀与部署的名称相同,后面跟着一个哈希值后缀,最后五个是唯一的,因为在集群中不允许有重复的 Pod 名称。
现在我们有一个新的部署正在运行,让我们将部署的镜像从 httpd:2.4.54 更改为 httpd:alpine。我们可以通过运行命令 k set image deploy apache httpd=httpd:alpine 来更新镜像;通过执行命令 k get po,你会看到为部署创建了一整套全新的 Pod:
root@kind-control-plane:/# k get po
NAME READY STATUS
➥ RESTARTS AGE
apache-74f79bcc68-7lzqz 1/1 Running
➥ 0 26m
apache-74f79bcc68-89zwg 1/1 Running
➥ 0 26m
apache-74f79bcc68-v6drf 1/1 Running
➥ 0 26m
apache-74f79bcc68-x7bfw 1/1 Running
➥ 0 26m
apache-74f79bcc68-xdx4b 1/1 Running
➥ 0 26m
root@kind-control-plane:/# k set image deploy apache httpd=httpd:alpine
deployment.apps/apache image updated
root@kind-control-plane:/# k get po
NAME READY STATUS
➥ RESTARTS AGE
apache-698b8cccbd-45nzs 1/1 Running
➥ 0 2s
apache-698b8cccbd-bz4bm 1/1 Running
➥ 0 6s
apache-698b8cccbd-dx4j2 1/1 Running
➥ 0 6s
apache-698b8cccbd-jrz86 1/1 Running
➥ 0 2s
apache-698b8cccbd-z8vnl 0/1 ContainerCreating
➥ 0 6s
apache-74f79bcc68-89zwg 1/1 Terminating
➥ 0 27m
apache-74f79bcc68-v6drf 1/1 Terminating
➥ 0 27m
apache-74f79bcc68-x7bfw 1/1 Terminating
➥ 0 27m
apache-74f79bcc68-xdx4b 1/1 Terminating
➥ 0 26m
以这种方式修改部署意味着在新的部署版本(带有新镜像)推出时,应用没有停机时间。这也为服务最终用户提供了灵活性和更高的敏捷性,能够应对负载峰值,并能够以最小停机时间逐步更改应用和添加功能。Deployment 资源是 Kubernetes 中最常用的,因为它充分利用了原生生态系统,我们将在本章接下来的部分中更多地关注它。
5.1.2 应用维护
Deployment 是设计用来运行同一应用程序的多个实例,通过复制提供对最终用户的更高可用性。提供额外功能的 Deployment 抽象是 ReplicaSet。正如其名所示,ReplicaSet 的目的是在任何给定时间维护一组稳定的副本 Pod。这保证了指定数量的相同 Pod 的可用性。使用命令 k get rs 列出你的集群中的 ReplicaSets。你应该得到以下类似的输出:
root@kind-control-plane:/# k get rs
NAME DESIRED CURRENT READY AGE
apache-74f79bcc68 1 1 1 28m
在图 5.1 中,你会看到 Deployment 和 ReplicaSet 的表示,它们由运行 NGINX 的三个复制 Pod 组成,作为 Kubernetes 对象组合的一部分。

图 5.1 部署内的一个副本集,包含三个副本,显示了 Kubernetes 对象的命名方案
说到 Deployment 配置 YAML,让我们看看 Pod YAML 与 Deployment YAML 的区别。实际上比你想象的要简单。你基本上是将 Pod YAML 放在 Deployment YAML 中 spec: 下 template: 部分的区域。为了证明这一点,让我们执行两个不同的命令——一个用于创建 Pod YAML 文件,另一个用于创建 Deployment YAML 文件。创建 Pod YAML 文件的命令是 k run nginx --image nginx --dry-run=client -o yaml > pod.yaml,创建 Deployment 的命令是 k create deploy nginx --image nginx --dry-run=client -o yaml > deploy.yaml。你可以在图 5.2 中看到 pod.yaml 文件和 deploy.yaml 文件之间的相似性。

图 5.2 Pod 的 YAML 与 Deployment 的 YAML 的比较
通过 YAML 创建的 Deployment 规范与 Pod 规范完全相同,只是在它上面还有一个额外的 Deployment 规范作为父资源。因此,有一个 Deployment 规范,而在 Deployment 规范内部,还有 Pod 规范。这很简单,对吧?
我们可以使用本章开头练习中创建的 Deployment,或者使用命令 k create -f deploy.yaml 从我们刚刚创建的文件生成 Deployment。我们可以使用命令 k get deploy 查看 Deployment 状态,并在 READY 列中查看副本数量。我们将看到名为 apache 的 Deployment 有五个副本,因为我们没有在 kubectl create 命令中指定任何副本,所以名为 nginx 的 Deployment 只有一个副本。你可以再次使用命令 k get rs 列出 ReplicaSets:
root@kind-control-plane:/# k create deploy nginx --image nginx --dry-
➥ run=client -o yaml > deploy.yaml
root@kind-control-plane:/# k create -f deploy.yaml
deployment.apps/nginx created
root@kind-control-plane:/# k get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
apache 5/5 5 5 97m
nginx 1/1 1 1 6s
root@kind-control-plane:/# k get rs
NAME DESIRED CURRENT READY AGE
apache-698b8cccbd 5 5 5 74m
apache-74f79bcc68 0 0 0 101m
nginx-76d6c9b8c 1 1 1 4m38s
你会注意到输出中有两个 ReplicaSets 以 apache 开头,因为我们之前更改了镜像,Deployment 控制器创建了一个新的 ReplicaSet,因为它包含不同的配置。默认情况下,所有 Deployment 的滚动历史都会保留在系统中,这样你就可以随时回滚。我们将在本章后面回顾滚动和回滚。
您可以通过修改 deploy.yaml 文件,或输入命令 k scale deploy nginx --replicas 2 来像本章开头那样缩放此 Deployment。在修改 YAML 时,您可以在文本编辑器中打开文件,将显示为 replicas: 1 的行更改为 replicas: 2,如图 5.3 的 YAML 文件快照所示。保存文件后,您可以使用命令 k apply -f deploy.yaml 应用相同的 YAML 文件。您可能会收到资源缺少“last-applied configuration”的警告,但您可以安全地忽略此警告,因为 Deployment 仍然缩放到两个副本。
最后应用的配置在创建对象时作为注释添加到对象中。注释包含用于创建对象的配置文件的内容。警告表明此注释缺失;因此,新配置文件将与当前配置合并。
我们在这里使用 apply 而不是 create,因为 Deployment 已经存在。apply 命令会覆盖现有对象,或者如果资源不存在,则会创建它。create 命令只有在对象不存在时才会创建对象,如果对象已存在,则会返回错误。

图 5.3 通过更改 Deployment YAML 中的副本数量来缩放 Deployment。
Deployment 从一个 Pod 缩放到两个 Pod,您可以使用命令 k get deploy nginx -o yaml 查看副本的变化。在幕后,与该 Deployment 关联的 ReplicaSet 也被修改了。我们可以通过运行命令 k describe rs 来查看副本集中的事件;您会看到一个类似于图 5.4 的输出。

图 5.4 描述 ReplicaSet 以获取有关其配置更改的更多信息。
ReplicaSet 与 Deployment 同时创建。如果我们删除 Deployment,我们会看到 ReplicaSet 也会被删除。使用命令 k delete deploy nginx 删除 Deployment:
root@kind-control-plane:/# k delete deploy nginx
deployment.apps "nginx" deleted
root@kind-control-plane:/# k get deploy
No resources found in default namespace.
root@kind-control-plane:/# k get rs
No resources found in default namespace.
考试提示:如果您想一次性描述所有内容(即所有 ReplicaSet),而不是在命令行中逐个列出名称,您只需输入资源的名称(例如,k describe rs),输出将包括当前存在的所有资源。
因此,我们已经看到了如果我们修改 Deployment 副本数量时 ReplicaSet 会发生什么。如果我们修改 Deployment 图像,它将创建一个新的 ReplicaSet,就像我们之前看到的那样。ReplicaSet 包含了一组新的 Pod,具有新的名称和新的配置,如图 5.5 所示。

图 5.5 当 Deployment 图像更改时,会创建一个新的 ReplicaSet,并带有新的 Pod 副本集。
如果你足够快,你可以看到旧 Pod 正在终止,新 Pod 正在创建。你可以在运行 set image 命令后不超过 3 秒内运行 k get po 命令。这样做后,你会看到一个类似于图 5.6 的输出。你也可以运行命令 k get po -w 来实时观察 Pod 状态的变化。

图 5.6 更改 Deployment 中的 Pod 的镜像会导致新的 ReplicaSet。
你会发现 kubectl get po 命令的输出中有很多有趣的信息,包括 Pod 的名称实际上来自 ReplicaSet 的名称。此外,旧 ReplicaSet 的 Pod 不会终止,直到新 ReplicaSet 的所有 Pod 都启动并运行。这被称为滚动更新策略,确保应用程序即使在升级场景中也能继续运行。这防止了应用程序的停机,并确保如果有人试图从集群外部访问应用程序,不会断开任何连接,如图 5.7 所示。

图 5.7 Kubernetes 中的滚动更新策略,表示在部署新版本时,只有一定数量的副本 Pod 不可用。
这就是包含相同应用程序多个副本的无状态应用程序的好处。滚动更新策略在 Deployment 规范中定义,你可以通过运行命令 k edit deploy apache 来定位它。你会看到一个类似于图 5.8 的输出。如果在创建 Deployment 时没有定义策略,将应用默认策略,这被称为滚动更新。滚动更新就是我们刚才描述的,即旧 ReplicaSet 的 Pod 不会终止,直到新 ReplicaSet 的 Pod 准备就绪。另一种策略类型称为重新创建策略。重新创建策略会在新 ReplicaSet 的 Pod 运行之前终止旧 ReplicaSet 的 Pod。这种策略需要应用程序有一段时间的停机。

图 5.8 滚动更新策略设置为 rollingUpdate,最大激增为 25%,最大不可用为 25%。
如果你想将策略更改为重新创建,你需要将类型从 rollingUpdate 改为 Recreate,并删除以 rollingUpdate、maxSurge 和 maxUnavailable 开头的行。重新创建策略对许多 Deployment 来说是个好选择,因为它提供了最快的滚动更新策略,但正如我们讨论的那样,会有停机时间。
考试技巧:如果在考试中发现自己不知道 YAML 文件中的值应该放在哪里,可以使用 k explain 命令。例如,命令 k explain deploy.spec.strategy 会列出在 spec 字段中可输入的值。试试看吧!
在滚动更新发布策略中,max surge和max unavailable字段是可选的,可以是百分比或整数。max surge指定可以创建的 Pod 数量,超过部署中副本的数量。例如,在图 5.8 中,因为副本数量是三个,maxSurge是 25%,Pod 的数量不能超过四个(百分比向上取整)。max unavailable正如其名——可以不可用的 Pod 数量。在我们的例子中,由于maxUnavailable设置为 25%,一次最多只能有一个 Pod 不可用。
等等,还有更多!除了 ReplicaSet 之外,还有针对 Deployments 的跟踪能力,称为滚动发布。
5.1.3 应用程序滚动发布
应用程序滚动发布是 Kubernetes 管理员或开发者回滚到之前版本或记录部署的修订次数的一种方式,如图 5.9 所示。

图 5.9 显示了两个滚动发布修订版,以防你需要回滚到应用程序的早期版本。
每当在部署内部更改 Pod 时,都会创建一个新的修订版。我们可以通过输入命令k rollout history deploy apache来查看滚动发布的历史:
root@kind-control-plane:/# k rollout history deploy apache
deployment.apps/apache
REVISION CHANGE-CAUSE
1 <none>
2 <none>
在这种情况下,有两个修订版,一个是在我们使用k create deploy命令创建部署时的初始部署,另一个是在我们将镜像标签从alpine更改为2时。
如果你查看CHANGE-CAUSE列,你会看到它写着none。change-cause列是一个记录与你的部署更改相关的笔记的机会。所以,假设你想要在change-cause列中添加一条笔记,内容为“更新镜像标签从alpine到 2。”你可以通过输入命令k annotate deploy apache kubernetes.io/change-cause="updated image tag from alpine to 2"来应用这个更改到修订版 2:
root@kind-control-plane:/# k annotate deploy apache kubernetes.io/change-
➥ cause="updated image tag from alpine to 2"
deployment.apps/apache annotated
root@kind-control-plane:/# k rollout history deploy apache
deployment.apps/apache
REVISION CHANGE-CAUSE
1 <none>
2 updated image tag from alpine to 2
如你所见,修订版 2 的change-cause列已经被更改。现在你可以知道为什么部署被修订,以及从修订版 1 到修订版 2 发生了哪些变化。
有这样的修订历史记录允许你回滚到之前的修订版,这被称为回滚。假设当你部署了应用程序的新版本并更改了部署的镜像标签时,你在应用程序中引入了一个错误。你必须迅速回滚以防止应用程序的用户遇到这个错误。你可以通过命令k rollout undo deploy apache回滚到之前的修订版:
root@kind-control-plane:/# k rollout undo deploy apache
deployment.apps/apache rolled back
root@kind-control-plane:/# k rollout history deploy apache
deployment.apps/apache
REVISION CHANGE-CAUSE
2 updated image tag from alpine to 2
3 <none>
你已经回滚到了上一个修订版,这反过来又创建了一个新的修订版。修订版号永远不会倒退;它总是向前推进。你现在可以通过命令k annotate deploy apache kubernetes.io/change-cause="reverted back to the original"在修订版 3 的change-cause列中输入一条消息:
root@kind-control-plane:/# k annotate deploy apache kubernetes.io/change-
➥ cause="reverted back to the original"
deployment.apps/apache annotated
root@kind-control-plane:/# k rollout history deploy apache
deployment.apps/apache
REVISION CHANGE-CAUSE
2 updated image tag from alpine to 2
3 reverted back to the original
两个修订版都记录了备注。你再也看不到修订版 1 了,因为回滚到修订版 1 将与回滚到修订版 3 相同,所以为了节省重复,修订版 1 被删除。
假设你想检查发布的状态。也许发布并不顺利。这可能发生在镜像标签不可用的情况下。也许你输入错误,或者镜像标签已不再从你的镜像仓库中可用。你可以根据修订版使用命令 k rollout status deploy apache --revision 3 来检查你的发布状态:
root@kind-control-plane:/# k rollout status deploy apache --revision 3
deployment "apache" successfully rolled out
当我们回滚到修订版 1 时,我们的部署成功发布,这是好的。我们没有拼写错误或不可用的镜像。太好了!
最后,我想展示如何暂停发布,以便只有部署中的一些 Pod 在修订版 3 上,而一半的 Pod 在修订版 4 上。我们可以通过命令 k set image deploy apache httpd=httpd:2.4 和 k rollout pause deploy apache(注意:你必须在这两个命令之间的一秒内执行)来看到这个动作。当你获取发布状态时,你会看到一个消息说“等待部署‘apache’发布完成:3 个新副本中有 2 个已被更新……”,这意味着发布已暂停。
root@kind-control-plane:/# k set image deploy apache httpd=httpd:2.4
deployment.apps/apache image updated
root@kind-control-plane:/# k rollout pause deploy apache
deployment.apps/apache paused
root@kind-control-plane:/# k rollout status deploy apache
Waiting for deployment "apache" rollout to finish: 2 out of 3 new replicas
➥ have been updated...
现在有三分之二的新副本正在作为新修订版的一部分运行。让我们继续使用命令 k rollout resume deploy apache 恢复(取消暂停)部署,以便修订版可以完成:
root@kind-control-plane:/# k rollout resume deploy apache
deployment.apps/apache resumed
root@kind-control-plane:/# k rollout status deploy apache
deployment "apache" successfully rolled out
现在我们已成功发布到修订版 4,其中部署中 Pod 的新镜像设置为httpd:2.4。
考试提示:如果你不确定命令的顺序或需要常见命令的示例,请使用终端中的帮助菜单,使用命令 k rollout --help 和 k rollout status --help,你可以轻松地获得正确的命令语法。
5.1.4 暴露部署
暴露部署意味着部署可以被终端用户访问,可能是在集群外部(取决于你创建的服务类型)。我们将在第六章深入探讨服务,但我想提到有一个简单的方法来创建一个与部署相连的服务。你不必从头创建服务,可以使用命令 k expose deploy nginx --name nginx-svc --port 80 --type ClusterIP --dry-run=client -o yaml > nginx-svc.yaml。你将在图 5.10 中看到该命令创建的 YAML 快照。当然,因为我们喜欢在 Kubernetes 中以声明式的方式做事,所以我们将进行 dry run 并创建一个名为nginx-svc.yaml的文件。

图 5.10 通过服务在 Kubernetes 中选择部署
关于 Deployment 的一个重要部分是选择器。如果你还记得,我们在上一章中讨论了节点选择器和标签选择器,而在这里我们讨论的是 服务选择器,用于将服务关联到 Deployment 内的 Pods。这个特定的服务将与任何具有标签 app=nginx 的 Deployment 关联。这是我们继续阅读本书时需要了解的有用信息,并且它将作为下一章深入讨论服务的知识基础。
考试练习
使用 kubectl,创建一个名为 apache 的 Deployment,使用镜像 httpd:latest 并设置一个副本。当 Deployment 运行后,将副本数扩展到五个。
将 Deployment apache 的镜像从 httpd:latest 更新为 httpd:2.4.54。不要创建新的 YAML 文件或编辑现有资源(仅使用 kubectl)。
使用 kubectl,查看由于上一练习中的镜像更改而创建的 ReplicaSet 的事件。
使用 kubectl,回滚到名为 apache 的 Deployment 的上一个版本。
对于现有的名为 apache 的 Deployment,将 Deployment 的回滚策略更改为 Recreate。
5.2 应用程序维护
在你的应用程序在 Kubernetes 中运行的生命周期中,可能会有这样的时刻:底层节点需要维护,或者应用程序需要额外的资源来优化性能。这突显了 Deployment 的健壮性,因为它们可以在集群中从节点到节点移动 Pods,而无需停机。由于冗余和高可用性内置在 Kubernetes 生态系统中,我们将学习如何安全地进行维护以创建健壮的应用程序 Deployment。
对于考试,你需要知道如何将 Pod 移动到不同的节点。考试任务可能如下所示。
考试任务 群集 b8s 中的节点 kind-worker 正在经历内存泄漏问题。你必须首先禁用对节点 kind-worker 的新 Pods 调度,然后关闭节点进行维护。然后,驱逐当前在 kind-worker 上运行的所有 Pods。最后,一旦你确认 kind-worker 上没有正在运行的 Pods,再次启用调度。 |
|---|
对于这个考试任务,你将需要使用一个两节点 kind 集群,并且你需要将一个 Pod 转移到辅助节点,因此你至少需要在集群中有一个正在运行的 Pod。如果你无法访问两节点集群,我建议你按照附录 A 中的说明创建一个。一旦你使用命令 docker exec -it kind-control-plane bash 打开控制平面节点的 shell,你可以运行命令 k create deploy nginx -image nginx 以在集群中运行一个 Pod。现在你可以开始考试任务,通过禁用 kind-worker 节点的调度来开始。
要禁用对节点的调度,你可以使用命令 k cordon no kind-worker。你将得到类似以下的输出:
root@kind-control-plane:/# k cordon kind-worker
node/kind-worker cordoned
root@kind-control-plane:/# k get no
NAME STATUS ROLES AGE
➥ VERSION
kind-control-plane Ready control-plane 7m
➥ v1.25.0-beta.0
kind-worker Ready,SchedulingDisabled <none> 6m38s
➥ v1.25.0-beta.0
如果你紧接着运行命令k get no,你会注意到状态从Ready变为Ready,SchedulingDisabled。所以如果你尝试创建第二个 Pod,Pod 将保持挂起状态,因为调度器已将该节点标记为不可用。我们可以通过命令k create deploy nginx2 -image nginx来观察这一行为,然后通过k get po命令查看状态。输出应该类似于以下内容:
root@kind-control-plane:/# k create deploy nginx2 --image nginx
deployment.apps/nginx2 created
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
nginx-76d6c9b8c-hr9z6 1/1 Running 0 111s
nginx2-b648d744f-n6xb9 0/1 Pending 0 3s
我们需要将该节点上所有正在运行的 Pods 移动,以便进行维护。这个过程被称为节点驱逐,即在驱逐 Pods 并将它们移动到另一个节点。要在我们的双节点集群中完成此操作,我们首先必须移除应用于控制平面节点的污点,以防止 Pods 被调度到该节点。Pods 从工作节点(在我们驱逐它时)移动到控制平面节点。我们在第二章中讨论了污点,所以这应该是一个熟悉的概念。执行命令k taint no kind-control-plane node-role.kubernetes.io/control-plane:NoSchedule-以清除控制平面节点的污点。输出将类似于以下内容:
root@kind-control-plane:/# k taint no kind-control-plane node-
➥ role.kubernetes.io/control-plane:NoSchedule-
node/kind-control-plane untainted
现在控制平面节点已清除污点,我们可以使用命令k drain kind-worker --ignore-daemonsets来驱逐节点kind-worker。输出将类似于以下内容:
root@kind-control-plane:/# k drain kind-worker --ignore-daemonsets
node/kind-worker already cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-h7695, kube-
➥ system/kube-proxy-j8wbc
evicting pod default/nginx-76d6c9b8c-hr9z6
pod/nginx-76d6c9b8c-hr9z6 evicted
node/kind-worker drained
root@kind-control-plane:/# k get po -o wide
NAME READY STATUS RESTARTS AGE IP
➥ NODE
nginx-76d6c9b8c-4494r 1/1 Running 0 52s 10.244.0.7
➥ kind-control-plane
nginx2-b648d744f-n6xb9 1/1 Running 0 3m6s 10.244.0.6
➥ kind-control-plane
你会看到,再次运行命令k get po -o wide后,Pods 正在控制平面节点上运行。为了完成我们的考试任务,让我们再次通过运行命令k uncordon kind-worker来启用节点的调度。输出将类似于以下内容:
root@kind-control-plane:/# k uncordon kind-worker
node/kind-worker uncordoned
这完成了考试任务。你现在应该熟悉如何使用kubectl的隔离、驱逐和解除隔离选项将 Pods 从一个节点移动到另一个节点。
5.2.1 隔离和驱逐节点
隔离是一个有趣的词,不是吗?其官方含义是围绕形成障碍,意味着一个保护性障碍。在 Kubernetes 的上下文中,隔离的行为发生在节点上,并标记节点为不可调度。如果你隔离一个节点,这将使节点停机并准备进行常规维护,如图 5.11 所示。你可能想知道是什么类型的维护。嗯,你可能需要升级节点的 RAM,修补安全漏洞,或者完全更换它。

图 5.11 隔离节点以禁用调度。
正如你所见,当你执行命令k cordon kind-worker时,你禁用了对该节点的调度;因此,任何通常可以容忍此节点进行调度的 Pods 在隔离解除之前都不能这样做。
考试提示:在你隔离一个节点后,确保你解除隔离。如果节点已禁用调度,你可能会从最终分数中扣除一些分数。
那么,现有的运行在该节点上的 Pods 怎么办呢?您已经将该节点标记为不可调度,但这只适用于从那时起开始的调度。它不考虑如图 5.12 所示当前在该节点上运行的 Pods。您可能已经从本节的标题中猜到,将 Pod 从节点移除并在其他地方调度它们的行为被称为清空。

图 5.12 隔离节点并不意味着它会从节点中驱逐 Pod。
清空的行为正如其名——清空节点上所有当前运行的 Pods。如果 Pods 被 ReplicaSet 管理,调度规则仍然适用,并且实际上 Pod 并没有移动。它实际上是在另一个节点上被删除并重新创建的;只是确保在新的 Pod 运行之前,旧的 Pod 不会被移除,就像滚动更新一样。如果您还没有运行 Deployment,可以使用命令k create deploy nginx --image nginx来启动一个,您也可以跟随这个有趣的步骤。
在娱乐的精神下,让我们看看当节点被清空时,当前在该节点上运行的所有 Pods 会发生什么。为了清空节点并忽略在集群中每个节点上运行的 kube-proxy 和 kubenet DaemonSets,执行命令k drain kind-worker --ignore-daemonsets --force。在这种情况下,强制执行是必要的,因为其中一个 Pod 没有被 ReplicaSet 管理。您将看到类似于图 5.13 的输出。

图 5.13 清空节点移除了在指定节点上运行的所有 Pods,其中一些被永久删除。
正如您所看到的,发生了几个动作。首先,节点被清空,Pods 被移除,但其中一个 Pod 被删除并且没有返回。这是因为它没有被 ReplicaSet 管理,所以没有机制来重新调度该 Pod。其次,属于 ReplicaSet 的 Pod 处于挂起状态。
正如我们在上一章所学,控制平面节点被应用了一个污点,这要求必须有一个容忍才能成功调度 Pod 到它们。在这种情况下,因为 Pod 没有对该污点的容忍,它将保持挂起状态,直到应用污点、为 Pod 添加容忍或者添加一个没有污点的新节点。为了简化起见,让我们使用命令k taint no kind-control-plane node-role.kubernetes.io/master-来移除污点:
root@kind-control-plane:/# k taint no kind-control-plane node-
➥ role.kubernetes.io/master-
node/kind-control-plane untainted
root@kind-control-plane:/# k get po -o wide
NAME READY STATUS RESTARTS AGE IP
➥ NODE
nginx-85b98978db-fnnb6 1/1 Running 0 20m 10.244.0.5
➥ kind-control-plane
通过运行命令k get po -o wide,容器现在正在kind-control-plane节点上运行。在本章的后面部分,我们将尝试将节点加入集群,我们可以模拟另一个场景,其中 Pod 可能在没有将控制平面节点置于风险(通过限制资源)的情况下从挂起状态移动出来。现在,你可以使用命令k uncordon kind-worker取消对kind-worker节点的隔离。此外,你也可以使用命令k taint no kind-control-plane node-role.kubernetes.io/master :NoSchedule重新应用污点到控制平面节点。
5.2.2 添加应用程序资源(节点)
在你的 Kubernetes 集群的生命周期中可能会发生需要插入额外节点的情况——无论是你需要为你的应用程序提供更多资源,还是由于故障或计划内的停机而丢失了一个节点。无论原因如何,通过在 Kubernetes API 中启用引导令牌身份验证,将节点添加到现有的 kubeadm 集群都变得简单。一个考试问题可能如下所示。
| 考试任务 | 集群中有一个名为ik8s的第三个节点,但在你执行kubectl get nodes命令时,该节点没有出现。节点的名称是node02。通过重新创建join命令并确保在列出集群中的所有节点时节点处于Ready状态,允许node02加入集群。 |
|---|
当你首次创建 Kubernetes 集群时,kubeadm 会创建一个具有 24 小时 TTL 的初始引导令牌,但你可以在需要时创建额外的令牌。你可以通过打开文件/etc/kubernetes/manifests/kube-apiserver.yaml并查看command部分来查看我们的 kind Kubernetes 集群中启用的引导令牌机制。你将看到类似于图 5.14 的输出。
root@kind-control-plane:/# cd /etc/kubernetes/manifests/
root@kind-control-plane:/etc/kubernetes/manifests# ls | grep apiserver
kube-apiserver.yaml

图 5.14 在 Kubernetes API 中启用引导令牌身份验证。
引导令牌本质上是用于创建新集群或将新节点加入现有集群时使用的持有令牌。这些令牌最初是为 kubeadm 构建的,但也可以在不使用 kubeadm 的其他场景中使用,例如与第三方应用程序一起使用。引导令牌的工作方式与 Service Account 令牌非常相似,即令牌允许第三方应用程序通过 Kubernetes API 进行身份验证并与集群内的对象进行通信。
在将节点加入集群的上下文中,引导令牌在加入集群的节点和控制平面节点之间建立了双向信任。我们可以在 kind Kubernetes 集群中使用以下命令生成一个新的令牌:kubeadm token create --print-join-command:
root@kind-control-plane:/# kubeadm token create --print-join-command
kubeadm join kind-control-plane:6443 --token l5kotg.hiivo73eu000bbfu -
➥ discovery-token-ca-cert-hash
➥ sha256:13b3aac808908114d45b6ad91640babd8613d8136b21d405711a1204c68fa8a4
考试技巧:在考试当天,不要害怕使用帮助菜单(kubeadm -help)。帮助菜单将包含示例,在某些情况下,你可以直接复制粘贴到命令行中。
让我们继续向我们的 kind Kubernetes 集群添加一个新节点。为了创建一个新节点,我们首先必须创建一个辅助集群,然后从新集群中孤儿化节点,将其添加到我们的原始集群中。我们这样做是因为孤儿化节点将拥有运行此节点作为 Kubernetes 节点所需的所有必要先决条件(containerd、kubelet 和 kubeadm)。在 CKA 考试中,您可能会遇到需要添加到集群中的孤儿节点,但您很可能不需要安装先决条件。我们可以使用 config.yaml 文件创建新的 kind 集群,该文件我们在第二章中使用过,但略有修改。您将得到类似于图 5.15 的输出。
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker

图 5.15 使用两个节点配置创建一个名为 cka 的新集群。
现在我们已经创建了一个名为 cka 的新集群,我们的上下文已经切换到新集群。我们可以简单地运行命令 k delete no cka-worker 来从该集群中移除节点,如图 5.16 所示。

图 5.16 删除节点将其从上下文中移除,但它仍然作为一个 Docker 容器存在。
即使我们已经删除了 cka-worker,该节点仍然在运行,并且可以从 Kubernetes 外部访问。您可以使用命令 docker exec -it cka-worker bash 获取到它的 shell。现在让我们运行命令 kubeadm reset 来将此节点恢复到新鲜状态并清除它与 cka 集群的关联:
$ docker exec -it cka-worker bash
root@cka-worker:/# kubeadm reset
[reset] WARNING: Changes made to this host by 'kubeadm init' or 'kubeadm
➥ join' will be reverted.
[reset] Are you sure you want to proceed? [y/N]: y
[preflight] Running pre-flight checks
W0527 01:56:05.673626 3614 removeetcdmember.go:80] [reset] No kubeadm
➥ config, using etcd pod spec to get data directory
[reset] No etcd config found. Assuming external etcd
[reset] Please, manually reset etcd to prevent further issues
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Deleting contents of config directories: [/etc/kubernetes/manifests
➥ /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf
➥ /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf
➥ /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]
➥ [reset] Deleting contents of stateful directories: [/var/lib/kubelet
➥ /var/lib/dockershim /var/run/kubernetes /var/lib/cni]
The reset process does not clean CNI configuration. To do so, you must
➥ remove /etc/cni/net.d
The reset process does not reset or clean up iptables rules or IPVS tables.
If you wish to reset iptables, you must do so manually by using the
➥ "iptables" command.
If your cluster was setup to utilize IPVS, run ipvsadm --clear (or similar)
to reset your system's IPVS tables.
The reset process does not clean your kubeconfig files and you must remove
➥ them manually.
Please, check the contents of the $HOME/.kube/config file.
我们可以继续应用我们之前生成的 join 命令。这个命令将使用 kubeadm 将节点加入我们名为 kind 的集群,传递用于身份验证的令牌,并且传递 --discovery-token-ca-cert-hash 以验证由控制平面提供的根证书颁发机构(CA)的公钥。您将看到类似于图 5.17 的输出。

图 5.17 使用 join 命令将节点加入现有集群。
我们可以回到 kind 集群并从那个上下文查看我们的节点:
root@cka-worker:~# exit
exit
$ docker exec -it kind-control-plane bash
root@kind-control-plane:/# alias k=kubectl
root@kind-control-plane:/# k get no
NAME STATUS ROLES AGE VERSION
cka-worker Ready <none> 15m v1.23.4
kind-control-plane Ready control-plane 2d1h v1.24.0
kind-worker Ready <none> 2d1h v1.24.0
恭喜!您已成功将一个节点添加到现有集群中,使其成为三节点集群。现在您可以将 Pod 调度到它上面,就像在 Kubernetes 集群中的任何其他工作节点一样使用它。
练习考试
从一个三节点集群中,隔离一个工作节点。不指定 nodeSelector 调度一个 Pod。解除隔离工作节点并编辑 Pod,将新的节点名称应用到 YAML 文件中(将其设置为刚刚解除隔离的节点)。替换 YAML 文件后,看看 Pod 是否被调度到最近解除隔离的节点。
启动一个基本的 nginx 部署;从控制平面节点移除污点,这样 Pod 就不需要容忍就可以调度到它上面。在部署中添加一个 nodeSelector 到 Pod 规范,看看 Pod 是否现在正在控制平面节点上运行。
摘要
-
部署是 Kubernetes 中的常见资源,其中的 ReplicaSet 对于保持所需副本的数量运行至关重要。
-
滚动操作是分版本的,你可以选择为其他 Kubernetes 管理员或开发者留下备注。
-
你可以公开一个 Deployment,它为用户创建一个 Service,以便从集群外部访问应用程序。
-
维护是不可避免的,这意味着在某个时候,你必须知道如何进行隔离和排空以执行操作系统升级或向节点添加资源。
-
使用 kubeadm 升级控制平面组件,以便集群可以保持最新补丁,防止 CVE。
-
你可以轻松地将节点添加到现有的集群中,为在 Kubernetes 上运行的应用程序提供额外的资源。
6 Kubernetes 集群中的通信
本章涵盖
-
节点如何通过 CNI 和不同的 CNIs 进行通信
-
Pod 之间的通信是如何发生的
-
Kubernetes 中服务类型及其使用情况
-
将 IP 地址分配给 Pods
-
通过 DNS 进行通信以及如何使用 CoreDNS
-
使用 Ingress 和 Ingress 控制器
许多人发现 Kubernetes 中的网络很复杂,但我们将在此章中全面解析它,特别是因为它占 CKA 考试的 20%。有几个重要概念可以澄清很多困惑,而且因为我们已经介绍了容器内桥接网络的工作方式,我认为所有这些都将开始整合。到本章结束时,你将知道 Pods 在集群内部是如何相互通信的,这是考试服务和网络部分的核心。
服务和网络领域
本章涵盖了 CKA 课程的服务和网络领域。该领域包括节点和 Pods 在集群中相互通信的方式。它包括以下能力。
| 能力 | 章节部分 |
|---|---|
| 理解集群节点上的主机网络配置。 | 6.5 |
| 理解 Pods 之间的连接性。 | 6.2 |
| 理解 ClusterIP、NodePort 和 LoadBalancer 服务类型和端点。 | 6.4 |
| 了解如何使用 Ingress 控制器和 Ingress 资源。 | 6.3 |
| 了解如何配置和使用 CoreDNS。 | 6.1, 6.2 |
| 选择合适的容器网络接口插件。 | 6.5 |
6.1 配置 DNS
在 Kubernetes 集群内部,CoreDNS 负责将主机名解析为 IP 地址。截至 Kubernetes 1.12 版本,CoreDNS 已经成为默认的 DNS 服务器,并将出现在考试中。CoreDNS 也用于我们的 kind Kubernetes 集群。
CKA 考试要求你配置和使用 CoreDNS,包括将主机名解析为 IP 地址、更改 DNS 的工作方式以及了解 CoreDNS 配置的位置和如何更改它。例如,考试问题可能会说如下内容。
考试任务 在集群 k8s 中,将分配给新服务的 IP 地址更改为 100.96.0.0/16 的 CIDR 范围。将与集群 DNS 服务关联的 IP 地址更改为与新服务范围匹配。继续更改 kubelet 配置,以便新的 Pods 可以接收新的 DNS 服务 IP 地址,并且可以解析域名。编辑 kubelet ConfigMap,以便 kubelet 在原地更新并立即反映。升级节点以接收新的 kubelet 配置。最后,通过创建一个新的 Pod 并验证该 Pod 是否具有 DNS 服务的新的 IP 地址来测试这一点。 |
|---|
如果你还没有访问现有的 Kubernetes 集群,可以按照附录 A 中解释的 kind 创建一个 Kubernetes 集群。单个节点集群就足够完成此类任务。一旦你有了对 kind 集群的访问权限,使用命令docker exec -it kind-control-plane bash获取控制平面节点的 shell。一旦你有了 shell,使用命令alias k=kubectl将别名k设置为等于kubectl。在考试中,他们已经为你设置了此别名,所以熟悉使用k而不是反复输入kubectl是很好的。
让我们更改分配给集群中创建的每个服务的 Service CIDR 块。这是一个由 API 服务器控制的特性。我们可以在目录/etc/kubernetes/manifests中找到 API 服务器配置,文件名为kube-apiserver.yaml。让我们使用命令vim /etc/kubernetes/manifests/kube-apiserver.yaml打开此文件进行编辑;它将在 Vim 文本编辑器中打开。在 YAML 命令部分,我们将光标置于以- --service-cluster-ip-range开头的行,并将 CIDR 范围从 10.96.0.0/16 更改为 100.96.0.0/16(在 10 之后添加一个 0)。结果应该与图 6.1 中的 YAML 完全相同。

图 6.1 更改分配给每个新服务的 IP 地址的 Service CIDR 范围。
在将集群 IP 范围更改为 100.96.0.0/16 后,保存并关闭文件。这将自动重启 API 服务器,因此你需要等待最多 2 分钟以等待 API 服务器重新启动并运行任何额外的kubectl命令。
接下来,让我们更改与集群 DNS 服务关联的 IP 地址。我们将在本章后面深入探讨服务。现在,只需知道它是一个集群范围内的 DNS 通信机制。DNS 服务位于kube-system命名空间中,我们可以使用命令k -n kube-system get svc查看服务。你会看到名为kube-dns的服务。输出看起来像这样:
root@kind-control-plane:/# alias k=kubectl
root@kind-control-plane:/# k -n kube-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
➥ AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP
➥ 73s
考试技巧:在k之后直接输入命名空间以自动完成 Kubernetes 资源(按 Tab 键自动完成资源名称)。在考试中,它们的名称会更长且更复杂,容易出错。尽可能复制粘贴,并使用自动完成功能以防止出错!
要编辑此服务,我们可以输入命令k -n kube-system edit svc kube-dns,它将在 Vi 文本编辑器中打开 YAML 文件(你可能需要先使用命令apt update; apt install -y vim安装 Vim)。Vi 文本编辑器将类似于图 6.2。将光标移至以clusterIP开头的行,在键盘上按 I 键(插入模式),并将值 10.96.0.10 替换为 100.96.0.10(在第一个 10 之后添加一个零)。对于clusterIPs下的行也做同样的操作,这样它也将具有新的值 100.96.0.10。

图 6.2 在原地编辑 kube-dns Service,替换 clusterIP 和 clusterIPs 的值。`
替换完值后,按键盘上的 Esc 键退出插入模式,然后输入 :wq 保存并退出。你会收到以下消息:“services ‘kube-dns’ was not valid: spec.clusterIPs[0]: Invalid value: []string{"100.96.0.10"}: may not change once set.” 图 6.3 中的消息将出现在页面顶部。

图 6.3 编辑 kube-dns Service 后,你会收到一条消息,指出 clusterIP 的值可能无法更改。
这在这里是预期的,因为只有某些类型的参数可以更改处于运行状态的 Kubernetes 对象,所以请再次输入 :wq 并返回到命令提示符,忽略错误信息。当你返回到命令提示符时,你会得到一条消息,内容如下:
error: services "kube-dns" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-3485293250.yaml"
error: Edit cancelled, no valid changes were saved.
忽略错误;这也是我们预期的。YAML 存储的位置可能因你而异,但我们将使用该 YAML 强制替换 Service。为此,我们将输入命令 k replace -f /tmp/kubectl-edit-3485293250.yaml --force。输出将类似于以下内容:
root@kind-control-plane:/# k replace -f /tmp/kubectl-edit-3485293250.yaml -
➥ -force
service/kube-dns replaced
现在我们已经更改了 kube-dns Service,新的 IP 地址可以通过命令 k -n kube-system get svc 获取,正如你在下面的输出中看到的那样:
root@kind-control-plane:/# k -n kube-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
➥ AGE
kube-dns ClusterIP 100.96.0.10 <none> 53/UDP,53/TCP,9153/TCP
➥ 2m13s
为了让新创建的 Pod 接收新的 DNS 信息,我们需要修改 kubelet 配置。我们可以调整 kubelet 配置的两个地方。第一个地方是 YAML 文件,这是 kubelet 的配置 YAML 清单。让我们执行命令 vim /var/lib/kubelet/config.yaml 以打开 kubelet YAML 清单。当你打开它时,你会注意到有一个名为 clusterDNS 的部分。将值从 10.96.0.10 更改为 100.96.0.10(按 I 键进入插入模式),这是名为 kube-dns 的 Service 的新 Service IP 地址。结果应该如图 6.4 所示。

图 6.4 文件 /var/lib/kubelet/config.yaml 将 clusterDNS 替换为 DNS 的新 IP 地址。
一旦完成更改,您可以通过按键盘上的 Esc 键退出插入模式,然后输入 :wq 来保存文件并退出 Vim。如您所注意到的,这并没有做太多;它只是更改了一个不会影响集群的文件。要影响集群并立即实施我们的更改,您需要编辑与 kubelet 配置关联的 ConfigMap。为此,执行命令 k -n kube-system edit cm kubelet-config。您将看到与 config.yaml 文件中相同的 YAML 结构。再次找到以 clusterDNS 开头的行,并将值从 10.96.0.10 更改为 100.96.0.10(别忘了插入模式!)。完成此操作后,按 Esc 键并输入 :wq 来保存文件并退出以保存 ConfigMap。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system edit cm kubelet-config
configmap/kubelet-config edited
由于 kubelet 是当前正在节点上运行的守护进程,我们必须更新此配置的节点,以及重新加载守护进程并在节点上重启 kubelet 服务。首先,要更新节点上的 kubelet 配置,执行命令 kubeadm upgrade node phase kubelet-config。输出将类似于以下内容:
root@kind-control-plane:/# kubeadm upgrade node phase kubelet-config
[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'
W0914 17:44:33.203828 3618 utils.go:69] The recommended value for
➥ "clusterDNS" in "KubeletConfiguration" is: [10.96.0.10]; the provided
➥ value is: [100.96.0.10]
[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.
现在我们已升级了节点的 kubelet 配置,我们可以使用命令 systemctl daemon-reload 重新加载守护进程,并使用命令 systemctl restart kubelet 重新启动服务。您将不会得到输出;您将直接返回到命令提示符,所以只要没有错误消息,您已成功重新启动了 kubelet 服务。
考试技巧:您可能会在考试中遇到需要您启动、重启或重新加载 kubelet 守护进程的任务。命令 systemctl stop kubelet systemctl start kubelet、systmectl restart kubelet 和 systemctl daemon-reload 是值得记忆的。
最后,为了测试我们迄今为止所做的所有更改,我们可以创建一个新的 Pod 并检查 DNS IP 地址是否正确以及 DNS 是否能够解析 example.com。让我们执行命令 kubectl run netshoot --image=nicolaka/netshoot --command sleep --command "3600" 来在默认命名空间中创建一个新的 Pod。我喜欢使用这个镜像,因为它预先安装了 DNS 工具,这对于测试网络很有用。如果您想了解更多关于此镜像的详细信息,请访问 DockerHub 页面:hub.docker.com/r/nicolaka/netshoot。
我们运行 sleep 和 3600 这两个命令,以便 Pod 保持运行状态(3600 秒,或 60 分钟)。我们可以使用命令 k get po 检查 Pod 是否处于运行状态。输出应类似于以下内容:
root@kind-control-plane:/# kubectl run netshoot --image=nicolaka/netshoot -
➥ -command sleep --command "3600"
pod/netshoot created
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
netshoot 1/1 Running 0 40s
Pod 正在运行,因此现在您可以通过命令 k exec -it netshoot --bash 获取容器的 Bash shell。您会注意到您的提示符已更改,这意味着您已成功进入 Pod 内的容器。输出应类似于以下内容:
root@kind-control-plane:/# k exec -it netshoot --bash
bash-5.1#
现在您已经在容器中打开了 Bash shell,您可以运行命令cat /etc/resolv.conf来检查是否列出了正确的 DNS IP 地址。输出应该类似于以下内容:
root@kind-control-plane:/# k exec -it netshoot --bash
bash-5.1# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 100.96.0.10
options ndots:5
DNS 配置正确;因此,Pod 可以在集群中使用 CoreDNS 解析 DNS 名称。您可以使用命令nslookup example.com检查这个 Pod 是否能够解析 example.com 的 DNS 查询。nslookup是一个 DNS 工具,允许您查询名称服务器。输出应该看起来像这样:
bash-5.1# nslookup example.com
Server: 100.96.0.10
Address: 100.96.0.10#53
Non-authoritative answer:
Name: example.com
Address: 93.184.216.34
Name: example.com
Address: 2606:2800:220:1:248:1893:25c8:1946
因为容器正在使用正确的 DNS 服务器(100.96.0.10),所以它能够将 example.com 解析为 93.184.216.34;因此,我们的 CoreDNS 配置是成功的,并且所有前面的步骤都有效地自定义了 CoreDNS 以满足这个考试任务的需求。
总结来说,为了完成这个任务,我们修改了/etc/kubernetes/manifests/kube-apiserver.yaml中的 API 配置;我们编辑了kube-system命名空间中的名为kube-dns的服务;我们修改了/var/lib/kubelet/config.yaml中的kubeclet配置;我们更改了kube-system命名空间中的名为kubelet-config的 kubelet ConfigMap;然后我们升级了节点并重新加载了 kubelet 守护进程。
6.2 CoreDNS
CoreDNS 背后的魔法是能够快速解析域名,以便服务之间可以通信,更重要的是,以便应用程序(在 Pod 中运行)可以相互通信并在请求出现时进行通信。为了本地解决这个问题,您可以为每个 Pod 添加到/etc/hosts文件中的值,但这不是可扩展的。相反,我们有一个中心位置(对集群来说是中心的),Pod 可以查询主机名和 IP 地址的累积列表,如图 6.5 所示。

图 6.5 CoreDNS 为 Kubernetes 集群中主机名到 IP 地址的映射提供了一个中心位置。
6.2.1 配置文件
kubelet 在 Kubernetes DNS 配置中扮演着特殊角色。Kubelet 是一个直接存在于每个节点上的服务。正如我们在 6.1 节中看到的,我们可以通过systemctl启动、停止和重启该服务。kubelet 负责创建 CoreDNS Pod 并将配置注入到该 Pod 中。该配置文件位于/var/lib/kubelet/config.yaml目录中。这是一个特殊的配置文件,仅用于 kubelet,并将包括 kubelet 的认证、健康端点和最重要的集群 DNS。有趣的事实:使用 SkyDNS 构建的kube-dns曾经是 Kubernetes 中的主要 DNS 解析。即使 CoreDNS 取代了它,服务的名称仍然保留。CoreDNS 成为了一个更高效、更全面的选项,并在 Kubernetes 版本 1.11 中取代了kube-dns。
考试提示:在 Kubernetes 集群中有一个专门用于静态 Pod 的特殊目录。这个位置是/etc/kubernetes/manifests/,并且这个目录中的任何内容都将自动配置,无需调度器的意识。
有两个 ConfigMap——一个用于 kubelet,一个用于 CoreDNS——并且每个都包含它们各自应用过的配置,因为 kubelet 负责在 Kubernetes 集群中创建新的 Pods,而 CoreDNS 将是 DNS 服务器。你可以使用命令 k -n kube-system get cm 查看这些 ConfigMap。
6.2.2 DNS 复制
如你可能在我们的集群中注意到的,有两个 CoreDNS 实例作为 kube-system 命名空间中的 Pods 运行。你可以使用命令 k -n kube-system get po 查看它们。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system get po
NAME READY STATUS
➥ RESTARTS AGE
coredns-565d847f94-4pnf4 1/1 Running
➥ 0 143m
coredns-565d847f94-xstnx 1/1 Running
➥ 0 143m
etcd-kind-control-plane 1/1 Running
➥ 0 143m
kindnet-75z9k 1/1 Running
➥ 3 (46m ago) 143m
kube-apiserver-kind-control-plane 1/1 Running
➥ 0 94m
kube-controller-manager-kind-control-plane 1/1 Running
➥ 1 (95m ago) 143m
kube-proxy-s5vps 1/1 Running
➥ 0 143m
kube-scheduler-kind-control-plane 1/1 Running
➥ 1 (95m ago) 143m
正如你所见,有两个 CoreDNS Pods 正在运行。这是 Deployment 的好处,你可以轻松地将其扩展和缩减以获得更快的 DNS 解析。没有人喜欢长时间等待 DNS 查询。你可以使用命令 k -n kube-system get deploy 查看 CoreDNS Deployment。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 147m
这个 Deployment 被称为 CoreDNS,它类似于 Kubernetes 中的任何其他 Deployment。假设 DNS 查询花费了很长时间并导致我们的应用程序延迟,我们可以使用命令 k -n kube-system scale deploy coredns --replicas 3 缩放 CoreDNS Deployment,并看到现在有三个 Pod 副本。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system scale deploy coredns --replicas 3
deployment.apps/coredns scaled
root@kind-control-plane:/# k -n kube-system get po
NAME READY STATUS RESTARTS
➥ AGE
coredns-565d847f94-4pnf4 1/1 Running 0
coredns-565d847f94-lqkbd 1/1 Running 0
coredns-565d847f94-xstnx 1/1 Running 0
etcd-kind-control-plane 1/1 Running 0
kindnet-75z9k 1/1 Running 3
kube-apiserver-kind-control-plane 1/1 Running 0
kube-controller-manager-kind-control-plane 1/1 Running 1
kube-proxy-s5vps 1/1 Running 0
kube-scheduler-kind-control-plane 1/1 Running 1
缩放 DNS 服务器副本只是允许在执行 DNS 查询时获得更高的性能,这使得管理你的 DNS 服务器变得更加容易。
如果这个解决方案不存在,我们就必须手动将主机名应用到每个 Pod 的 IP 地址上。让我们通过将 CoreDNS Deployment 缩放到零并尝试与集群中的服务进行通信来模拟这种情况。要缩减 Deployment,我们将运行命令 k -n kube-system scale deploy Coredns --replicas 0。这将终止集群中当前运行的所有 CoreDNS Pods,看起来如下所示:
root@kind-control-plane:/# k -n kube-system scale deploy coredns -
➥ replicas 0
deployment.apps/coredns scaled
root@kind-control-plane:/# k get po -A
NAMESPACE NAME READY
➥ STATUS
default netshoot 1/1
➥ Running
kube-system coredns-565d847f94-ctlpt 1/1
➥ Terminating
kube-system coredns-565d847f94-z65fz 1/1
➥ Terminating
kube-system coredns-565d847f94-zv28n 1/1
➥ Terminating
kube-system etcd-kind-control-plane 1/1
➥ Running
kube-system kindnet-75z9k 1/1
➥ Running
kube-system kube-apiserver-kind-control-plane 1/1
➥ Running
kube-system kube-controller-manager-kind-control-plane 1/1
➥ Running
kube-system kube-proxy-s5vps 1/1
➥ Running
kube-system kube-scheduler-kind-control-plane 1/1
➥ Running
local-path-storage local-path-provisioner-684f458cdd-qgncs 1/1
➥ Running
集群中没有可用的 DNS,因此我们无法解析主机名。让我们创建一个 Deployment 和一个 Service。我们将在本章后面讨论 Service,但到目前为止,我们将使用这个 Service 与 Deployment 中的底层 Pods 进行通信。使用命令 k create deploy apache --image httpd; k expose deploy apache --name apache-svc --port 80 创建 Deployment 和 Service。输出将类似于以下内容:
root@kind-control-plane:/# k create deploy apache --image httpd; k expose
➥ deploy apache --name apache-svc --port 80
deployment.apps/apache created
service/apache-svc exposed
通过运行命令 k get svc 获取服务的 IP 地址。输出将类似于以下内容,其中我的集群 IP 地址为 100.96.102.73:
root@kind-control-plane:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
apache-svc ClusterIP 100.96.102.73 <none> 80/TCP 25m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6h36m
如果你之前练习中还有运行的 netshoot 容器,让我们获取它的 Bash shell。如果你已经删除了 netshoot 容器,你可以使用命令 kubectl run netshoot --image=nicolaka/netshoot --command sleep --command "3600" 重新启动它。让我们通过运行命令 k exec --it netshoot --bash 来获取它。你现在应该有一个新的提示符,看起来类似于以下内容:
root@kind-control-plane:/# k exec -it netshoot --bash
bash-5.1#
为了验证 DNS 是否工作,让我们尝试与我们刚刚创建的名为 apache-svc 的 Service 进行通信。我们可以使用命令 wget -O- apache-svc 来做这件事。你应该得到以下输出:
bash-5.1# wget -O- apache-svc
wget: bad address 'apache-svc'
为了解决这个问题,我们将主机名到 IP 的映射添加到 Pod 内部的 /etc/hosts 文件中。我们可以用一条命令来完成这个操作:echo “100.96.102.73 apache-svc” >> /etc/hosts。现在让我们再次运行 wget 命令,你应该得到标准的 Apache 主页,看起来像这样:
bash-5.1# wget -O- apache-svc
Connecting to apache-svc (100.96.102.73:80)
writing to stdout
<html><body><h1>It works!</h1></body></html>
- 100%
➥|**************************************************************************
➥************************| 45 0:00:00 ETA
written to stdout
这之所以有效,是因为我们在 Pod 上本地编写了自己的 DNS。正如你所见,如果你有数百个 Pod,这会很困难,而且我们知道,Pod 是短暂的,因此为每个 Pod 做这件事会非常麻烦。我希望这个练习证明了 CoreDNS 的重要性,并给你提供了一些测试 Service 通信的工具。
6.2.3 Pod 之间的连接性
让我们看看 Pod 是如何跨命名空间进行通信的,这样我们就可以更熟悉 Kubernetes 集群内部的 DNS 的工作方式。创建一个类似的 Deployment 和 Service,但将其创建在名为 c01383 的新命名空间内。
考试技巧:这些复杂的命名空间名称在考试中会经常出现,所以尽可能多地练习使用自动补全是个好主意。有关在 kind 中安装自动补全的说明,请参阅附录 B。
首先,让我们使用命令 k create ns c01383 创建一个命名空间。我们可以使用命令 k get ns 来查看我们集群中的命名空间。你会看到一个类似于以下内容的输出:
root@kind-control-plane:/# k create ns c01383
namespace/c01383 created
root@kind-control-plane:/# k get ns
NAME STATUS AGE
c01383 Active 2s
default Active 8h
kube-node-lease Active 8h
kube-public Active 8h
kube-system Active 8h
local-path-storage Active 8h
一旦我们创建了命名空间 c01383,我们就可以使用命令 k -n c01383 create deploy nginx --image nginx; k -n c01383 expose deploy nginx --name nginx-svc --port 80 在该命名空间内创建一个 Deployment 和 Service。你可以通过输入 k -n c01383 get deploy,svc 来查看 c01383 命名空间中的 Deployment 和 Service。输出将类似于以下内容:
root@kind-control-plane:~# k -n c01383 create deploy nginx --image nginx; k
➥ -n c01383 expose deploy nginx --name nginx-svc --port 80
deployment.apps/nginx created
service/nginx-svc exposed
root@kind-control-plane:/# k -n c01383 get deploy,svc
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 57s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-svc ClusterIP 100.96.138.162 <none> 80/TCP 57s
你认为如果我们从 Deployment 中的一个 Pod 连接到,我们能否到达默认命名空间中名为 apache-svc 的 Service?让我们试试!输入命令 k -n c01383 get po 来检索 Pod 名称。它将以 nginx 开头,然后是一个连字符,接着是一个代表 ReplicaSet 的唯一值,然后是另一个连字符,最后是代表 Deployment 的另一个唯一值。这最好通过图 6.6 中的输出来说明。

图 6.6 Pod 名称来自一个自动分配的唯一标识符,这可以防止 Pod 名称冲突。
现在我们已经知道了 Pod 的名称,我们可以使用命令 k -n c01383 exec -it nginx-76d6c9b8c-8lqgg --bash 打开一个 Bash shell 到它。你会看到你的提示符发生变化,这意味着你现在正在查看 Pod 内部的文件系统。在容器内部,运行命令 curl apache-svc。你应该得到结果“无法解析主机”,并且看起来像这样:
root@kind-control-plane:/# k -n c01383 exec -it nginx-76d6c9b8c-8lqgg --
➥ bash
root@nginx-76d6c9b8c-8lqgg:/# curl apache-svc
curl: (6) Could not resolve host: apache-svc
让我们尝试运行命令 curl apache-svc.default。它成功了!你应该看到以下内容:
root@nginx-76d6c9b8c-8lqgg:/# curl apache-svc.default
<html><body><h1>It works!</h1></body></html>
这是因为 Kubernetes 为每个服务分配了其唯一的域名,不同命名空间之间的域名是不同的。Kubernetes 集群中服务的 *完全限定域名(FQDN**) 是 <service-name>.<namespace-name>.svc.cluster .local,如图 6.7 所示。

图 6.7 集群中每个服务的域名约定示例
我们在本书第 6.1 节中查看 Pod 内的 resolv.conf 时已经看到了这个。让我们再次从我们已经在的 shell 中使用命令 cat /etc/resolv.conf 来查看它。输出应该类似于以下内容:
root@nginx-76d6c9b8c-8lqgg:/# cat /etc/resolv.conf
search c01383.svc.cluster.local svc.cluster.local cluster.local
nameserver 100.96.0.10
options ndots:5
以 search 开头的行将指示与该命名空间中的服务关联的 DNS 名称。在这个列表中你不会看到 default.svc.cluster.local,这就是为什么当我们尝试 apache-svc 这个名称时,名称无法解析。
Pods 也有一个全限定域名(FQDN),它就是 IP 地址,但 CoreDNS 会将点转换为破折号,因此看起来像这样:10-244-0-14.default.pod.cluster.local,其中 10.244.0.14 是默认命名空间内 Pod 的 IP 地址。我们可以执行相同的 curl 命令,但使用 Pod FQDN,如下所示:
root@kind-control-plane:/# k get po apache-855464645-npcf5 -o wide
NAME READY STATUS RESTARTS AGE IP
➥ NODE NOMINATED NODE READINESS GATES
apache-855464645-npcf5 1/1 Running 0 21h 10.244.0.14
➥ kind-control-plane <none> <none>
root@kind-control-plane:/# k -n c01383 exec -it nginx-76d6c9b8c-8lqgg --
➥ bash
root@nginx-76d6c9b8c-8lqgg:/# curl 10-244-0-14.default.pod.cluster.local
<html><body><h1>It works!</h1></body></html>
我们也可以执行命令 10-244-0-14.default.pod,由于 cluster.local 包含在 resolv.conf 的搜索条件中,DNS 会解析它。
练习考试
使用 exec 命令进入 Pod,并使用 cat 命令查看 DNS 解析器文件,以获取 Pod 用于解析域名的 DNS 服务器 IP 地址。
打开包含 kubelet 配置的文件,并将 clusterDNS 的值更改为 100.96.0.10。保存并退出文件。
停止并重新加载 kubelet 守护进程。验证服务是否处于活动状态并正在运行。
定位 kube-dns 服务。尝试就地编辑服务,将 clusterIP 和 ClusterIPs 的值更改为 100.96.0.10。当值无法更新时,使用正确的 kubectl 命令行参数强制替换服务。
编辑包含 kubelet 配置的 ConfigMap。将 clusterDNS 设置的 IP 地址值更改为 100.96.0.10。确保编辑资源时不要编写新的 YAML 文件。
将 CoreDNS 部署扩展到三个副本。验证 Pod 是否作为该部署的一部分被创建。
通过首先创建一个使用 apache 镜像的 Deployment,然后公开该 Deployment 来测试从 Pod 到服务的访问。从 netshoot 镜像创建一个 Pod,并验证你是否可以到达你刚刚创建的服务。
使用在之前练习中创建的 netshoot Pod,通过 DNS 名称在默认命名空间中定位服务。为了 DNS 搜索功能,尽可能使用最少的字母。
6.3 入口和入口控制器
当涉及到对运行在 Kubernetes 中的应用程序执行应用层(第 7 层)路由时,使用的术语是 Ingress。在我们讨论 Service 之前,让我们先了解 Ingress,因为它是在 Service 的基础上暴露应用程序的首选方法,并且考试目标指出您必须知道如何使用 Ingress 和 Ingress 控制器。它之所以是首选方法,是因为 Ingress 提供了一个单一的网关(只有一个入口)进入集群,并且可以使用基于路径的路由将流量路由到多个服务。与 Ingress 控制器一起,您可以在 Ingress 资源中设置这些路由,并且它们将被引导到每个服务,如图 6.8 所示。

图 6.8 展示了 Ingress 流量流向以及基于路径规则的多个服务的重定向
您将接受创建 Ingress 和 Ingress 控制器的测试,因此让我们回顾一下考试任务可能的样子。
在集群 ik8s 中,安装一个 Ingress 控制器以通过 Ingress 资源代理集群内的通信。然后,使用镜像 nginxdemos/hello:plain-text 创建一个名为 hello 的 Deployment。容器在端口 80 上暴露。创建一个名为 hello-svc 的 Service,该 Service 靶向 hello Deployment 的端口 80。然后,创建一个 Ingress 资源,这将允许您将 DNS 名称 hello.com 解析到 Kubernetes 中名为 hello-svc 的 ClusterIP 服务。 |
|---|
要完成这些考试任务,我们需要创建一个新的 kind 集群。请参阅附录 A,第 A.3 节,了解如何构建具有额外端口暴露和节点上应用标签的单节点 kind 集群。一旦您有权访问您的 kind 集群,使用命令 docker exec -it ingress-control-plane bash 获取控制平面节点的 shell。一旦您有了 shell,使用命令 alias k=kubectl 将您的别名 k 设置为等于 kubectl。在考试中,他们已经为您设置了此别名,因此使用 k 而不是反复输入 kubectl 是一个好习惯。
既然我们已经构建了集群并且有控制平面节点的 shell,让我们开始安装 Ingress 控制器的过程,这在考试中也是一个类似的过程。Ingress 控制器 YAML 创建了几个资源。您可以使用命令 k apply -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_06/nginx-ingress-controller.yaml 一次性应用所有这些资源。此命令的输出中,在集群中创建的资源将类似于以下内容:
root@ingress-control-plane:/# k apply -f
➥ https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-
➥ exam/main/ch_06/nginx-ingress-controller.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-
➥ admission created
Kubernetes 对象已在名为 ingress-nginx 的单独命名空间中创建,您可以使用命令 k -n ingress-nginx get all 一次性查看这些资源。输出将类似于以下内容:
root@ingress-control-plane:/# k get all -n ingress-nginx
NAME READY STATUS
➥ RESTARTS AGE
pod/ingress-nginx-admission-create-lccjf 0/1 Completed
➥ 0 27s
pod/ingress-nginx-admission-patch-bzjfm 0/1 Completed
➥ 0 27s
pod/ingress-nginx-controller-6c695f6cc7-ntjbt 1/1 Running
➥ 0 27s
NAME TYPE CLUSTER-IP
➥ EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller NodePort 10.96.134.48
➥ <none> 80:32745/TCP,443:31396/TCP 27s
service/ingress-nginx-controller-admission ClusterIP 10.96.130.242
➥ <none> 443/TCP 27s
NAME READY UP-TO-DATE AVAILABLE
➥ AGE
deployment.apps/ingress-nginx-controller 1/1 1 1
➥ 27s
NAME DESIRED CURRENT
➥ READY AGE
replicaset.apps/ingress-nginx-controller-6c695f6cc7 1 1
➥ 1 27s
NAME COMPLETIONS DURATION AGE
job.batch/ingress-nginx-admission-create 1/1 5s 27s
job.batch/ingress-nginx-admission-patch 1/1 5s 27s
验证以 ingress-nginx-controller 开头的 Pod 是否正在运行。这意味着您已准备好继续并创建 Deployment、Service 和 Ingress。
We’ll start with the deployment, which you can create with the command k
➥ create deploy hello --image nginxdemos/hello:plain-text --port 80\. The output will look like this:
root@ingress-control-plane:/# k create deploy hello -image
➥ nginxdemos/hello:plain-text -port 80
deployment.apps/hello created
现在我们可以通过命令 k expose deploy hello -name hello-svc -port 80 来通过暴露 Deployment 创建一个 Service。输出将如下所示:
root@ingress-control-plane:/# k expose deploy hello -name hello-svc -port 80
service/hello-svc exposed
当你输入 k get deploy,svc 时,你现在将看到以下内容:
root@ingress-control-plane:/# k get deploy,svc
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello 1/1 1 1 5m32s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/hello-svc ClusterIP 10.96.255.66 <none> 80/TCP 20s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 28m
让我们通过创建 Ingress 资源来完成考试任务。输入命令 k apply -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_06/hello-ingress.yaml 来创建 Ingress 资源。执行此命令后,你将看到以下输出:
root@ingress-control-plane:/# k apply -f
➥ https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-
➥ exam/main/ch_06/hello-ingress.yaml
ingress.networking.k8s.io/hello created
大约 20 秒后,通过输入命令 k get ing,你将看到 Ingress 资源,如下所示:
root@ingress-control-plane:/# k get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
hello <none> hello.com 80 9s
hello <none> hello.com localhost 80 15s
最后,让我们通过将 hello.com 添加到我们的 /etc/hosts 文件中来测试我们的设置。通过输入 vim /etc/hosts 打开 hosts 文件(你可能需要先运行 apt update; apt install -y vim 来安装 Vim)。在 hosts 文件中,输入控制平面节点的 IP 地址,你可以看到它位于 localhost 旁边,然后输入 hello.com。文件将类似于图 6.9 中的 hosts 文件内容。

图 6.9 添加主机条目以解析主机名 hello.com 到 172.18.0.2
保存此文件后,执行命令 curl hello.com。你将得到以下输出,这是之前部署的 hello 应用的输出:
root@ingress-control-plane:/# curl hello.com
Server address: 10.244.0.8:80
Server name: hello-665cb98d5f-cfmll
Date: 30/Sep/2022:20:38:01 +0000
URI: /
Request ID: fbba7de007d520e7f3e6ea9e0b4a69ad
输出是从 Pod 获得的响应,因此服务器地址将与 Pod 的 IP 地址匹配。你可以通过输入命令 k get po -o wide 看到这一点。
这就完成了我们的考试任务,安装了 Ingress 控制器,创建了一个名为 hello 的 Deployment,一个名为 hello-svc 的 Service,以及一个将 hello.com 解析到 hello-svc Service 的 Ingress 资源。
从考试任务中我们可以看到,我们只是简单地输入了 curl hello.com,它神奇地将我们路由到正确的 Service,进而路由到正确的 Pod。这个魔法是由准入控制器执行的。Ingress 控制器是一种拦截对 Kubernetes API 请求的软件。在我们的例子中,它是在 ingress-nginx 命名空间中运行的 Pod。这个 Pod 拦截了我们向 hello.com 发出的请求,并在 Ingress 资源的帮助下将请求重定向到 hello 应用。这只是一个简单的例子,但我们也可以向 URL 添加路径(例如,hello.com/app)并将请求重定向到不同的 Service。这个 Service 将是一个 ClusterIP Service,因为 Ingress 控制器已经暴露在外部,帮助集群中的流量进入。
让我们遍历 Ingress YAML 并修改 Ingress 资源。您可以使用命令 k edit ingress hello 编辑 Ingress 资源。现在 YAML 将会出现在 Vim 编辑器中。从顶部开始,如您在图 6.10 中所见,主机是客户端将访问您的应用程序(hello.com)的域名。这可能是一个您从域名注册商那里购买的域名,或者这可能是在集群本地的某个东西,正如我们选择的。HTTP 部分以及随后的文本定义了解析 HTTP(端口 80)流量的规则。规则说明,对 hello.com 主机发出的任何请求都将被导向端口 80 上的 hello-svc 服务。这是默认路由,因为没有指定显式路径(例如,hello.com/path)。要添加另一个规则——比如说,将来自 hello.com/app 的请求导向名为 nginx 的不同服务,通过端口 8080——我们将在 path: 下添加另一个块来指定该规则。结果将类似于图 6.10。

图 6.10 添加 Ingress 规则,路由到名为 nginx 的不同服务
在图 6.10 中,Ingress 规则显示了路由到两个不同服务的路由。这些服务被定义为 后端,这意味着一旦流量进入 Ingress,它将进入后端(幕后发生的事情)。在这种情况下,两个后端路由定义了它们各自路由的个别服务的具体细节。第一个路由到名为 hello-svc 的服务并使用端口 80。这要求我们在集群中有一个暴露在端口 80 上的 ClusterIP 服务。第二个路由是名为 nginx 的服务的后端。客户端必须输入 http://hello.com/app 才能路由到这个服务。您还会看到一个 pathType,因为每个 Ingress 路径都需要。
支持三种路径类型——Exact、Prefix 和 ImplementationSpecific。Exact 路径类型必须与路径完全匹配(区分大小写),路径才有效(例如,hello.com/app)。Prefix 路径类型匹配由正斜杠(/)分隔的 URL 路径,斜杠之间的字符是元素的名称前缀。ImplementationSpecific 路径类型将决定前缀或精确路径类型留给 Ingress 类来决定。
在 Ingress 资源的 YAML 中,您不仅可以选择基于路径的路由(hello.com/app),还可以选择子域名类型的路由。例如,当客户端输入 http://app.hello.com 时,您可以选择不同的后端,这可能会根据您拥有的应用程序类型更加灵活。然后 YAML 将变为类似以下内容:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
spec:
rules:
- host: hello.com
http:
paths:
- pathType: ImplementationSpecific
path: "/"
backend:
service:
name: hello-svc
port:
number: 80
- host: app.hello.com
http:
paths:
- pathType: Exact
path: "/"
backend:
service:
name: nginx
port:
number: 8080
6.4 服务
Pods 本质上是短暂的,并且可以在任何时候被杀死并重新启动。因此,它们的 IP 地址不断变化。Service 提供了一种让流量始终到达正确的 Pod 的方法,无论该 Pod 是否被移动到新的节点、被杀死或进行扩展。Service 为 Pods 提供了一个分布式负载均衡器,它们在同一个 Deployment 内的多个 Pod 之间均匀分配负载,并有助于检测这些 Pod 在集群中的确切位置,如图 6.11 所示。

图 6.11 Service 代表每个 Pod 接受进入的流量,进行流量均衡。
Service 为 Pod 或 Pod 集提供了一个一致的 IP 地址和 DNS 名称。这个一致的地址有助于保持现有连接并管理 Pod 来去时的流量路由。Pods 可以通过 Service 在集群内相互通信,无论 Pod 运行在哪个节点上,它都将能够作为集群范围内的 Service 的一部分定位到该 Pod。端点是 Pod 上暴露的端口,与 Pod 相关联,并代表 Pod 可以到达的目标。通过输入命令k get ep,我们可以看到端点列表,它们具有与 Service 相同的名称。输出将类似于以下内容:
root@kind-control-plane:/# k get ep
NAME ENDPOINTS AGE
apache-svc 10.244.0.14:80 129m
kubernetes 172.18.0.2:6443 8h
Service 的类型有 ClusterIP、NodePort 和 LoadBalancer。ClusterIP Service 旨在仅在集群内部使 Pod 可用,而 NodePort 和 LoadBalancer 类型的 Service 旨在暴露一个端口并创建对集群的外部访问。NodePort 在节点的 IP 地址上暴露一个端口,但 LoadBalancer 允许外部负载均衡器(集群外部)控制每个节点端口的流量。这允许你使用更常见的端口(80、443)而不是 NodePort 限制的端口 30,000-32,768。当你创建 NodePort Service 时,你实际上是在扩展一个 ClusterIP 类型的 Service,如图 6.12 所示。

图 6.12 一个负载均衡器类型的 Service,它是 NodePort 和 ClusterIP 类型 Service 的超集,提供了一个进入集群和底层 Pods 的单一点。
6.4.1 ClusterIP Service
集群 IP 服务仅用于集群内部通信。你通常会创建这样的服务来从应用程序的前端,在集群中的一个 Pod 中,与后端通信,后端位于不同的 Pod 中。你可以在创建服务时指定要使用的端口,或者你可以在之后修改它。像 Kubernetes 中的许多其他资源一样,你可以运行一个 imperative 命令来创建服务,例如k create svc clusterip internal-svc --tcp 8080:80,或者你可以使用命令k create svc clusterip internal-svc --tcp 8080:80 -dry-run=client -o yaml > svc.yaml创建一个 YAML 规范文件。现在运行后者命令,并通过使用命令vim svc.yaml在 Vim 中打开它来查看 YAML 规范。你会发现,如图 6.13 所示,在端口部分,有一个端口和一个目标端口。目标端口是容器本身上暴露的端口。并非所有容器都有暴露的端口,但以nginx镜像为例,端口 80 是暴露的。
暴露的端口在 Dockerfile 中定义(镜像是如何构建的),所以如果你想知道容器暴露了哪些端口,请查看该镜像的 Dockerfile。回到服务 YAML,端口在集群内部暴露,如图 6.12 所示,端口是 80。你可以有多个具有相同暴露端口的服务的服务,因为每个服务都有自己的 IP 地址和 DNS 名称,这允许传入请求区分不同的服务。只要服务名称不同,你就可以有任意多的服务,其端口 80 被暴露。注意在 YAML 规范中,这个服务有一个选择器,如图 6.13 所示。这就是服务如何绑定到 Pods 的方式。任何带有标签app=internal-svc的 Pod,在这种情况下,都将与该服务相关联。这与我们在第 4.1 节中提到的nodeSelector类似。

图 6.13 服务 YAML 规范
假设我们想要在这个服务中暴露另一个端口;我们也可以这样做。复制 YAML 文件中ports一词下面的所有内容,并将其粘贴到targetPort一词下面。现在我们可以将名称更改为search;端口和目标端口可以是 9200。结果应该类似于以下内容:
spec:
ports:
- name: 8080-80
port: 8080
protocol: TCP
targetPort: 80
- name: search
port: 9200
protocol: TCP
targetPort: 9200
selector:
app: internal-svc
type: ClusterIP
status:
loadBalancer: {}
这类似于可能运行在你的集群中的 Elasticsearch Pod。如果客户端连接到这个服务,那么具有端口 80 暴露的 Pod 以及具有端口 9200 暴露的 Pod 都可以通过访问一个服务来访问。
可以完全绕过服务,直接访问 Pod。这被称为无头服务。为了演示这一点,让我们在我们的 YAML 规范中添加一行。在规范下方,让我们插入行clusterIP: none。结果应该看起来像以下内容:
spec:
clusterIP: “None”
ports:
- name: 8080-80
port: 8080
protocol: TCP
targetPort: 80
- name: search
port: 9200
protocol: TCP
targetPort: 9200
这不仅会从服务中移除 IP 地址,而且当使用 DNS 通过服务查找 Pod 时,通信将直接发送到 Pod。在数据库集群的情况下,这是必需的,因为只有一个数据库副本负责写入数据库,而其他所有副本只允许读取。客户端可以轻松地查找与无头服务关联的 Pods,并确定哪个 Pod 负责写入数据库。
让我们使用命令 k create -f svc.yaml 创建这个无头多端口服务。名为 internal-svc 的服务没有像其他服务那样的集群 IP。此外,还有两组端口——8080 和 9200。输出将类似于以下内容:
root@kind-control-plane:/# k create -f svc.yaml
service/internal-svc created
root@kind-control-plane:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP
➥ PORT(S) AGE
apache-svc ClusterIP 100.96.102.73 <none>
➥ 80/TCP 22h
internal-svc ClusterIP None <none>
➥ 8080/TCP,9200/TCP 29s
kubernetes ClusterIP 10.96.0.1 <none>
➥ 443/TCP 28h
6.4.2 NodePort 服务
NodePort 服务不仅能够与集群中的所有其他组件进行内部通信,还可以在节点上公开一个静态端口,用于外部流量。这对于测试与 Pod 的通信很有用,但也有一些限制。您将需要知道节点的 IP 地址,并且您还必须知道哪个端口被公开,因为 NodePort 服务的可用端口范围是 30000–32768。
就像集群 IP 服务类型和 Kubernetes 中的许多其他资源一样,您可以通过执行命令 k create svc nodeport no --node-port 30000 --tcp 443:80 来创建一个服务,或者您可以通过命令 k create svc nodeport no --node-port 30000 --tcp 443:80 --dry-run=client -o yaml > nodeport.yaml 创建一个声明性的 YAML 文件。让我们在 Vim 中打开 YAML 文件,看看这与集群 IP 服务有何不同。要打开文件 nodeport.yaml,请输入 vim nodeport.yaml。您会看到它们非常相似,但类型不是 ClusterIP,而是 NodePort,并且在端口名称部分增加了一行。添加了 nodePort: 30000 这行,这是将在集群中的每个节点上公开的静态端口号,就像图 6.14 中显示的 YAML 那样。

图 6.14 创建 NodePort 服务的 YAML
就像在集群 IP 服务中一样,我们可以为这个服务分配多个端口和目标端口,但没有为无头服务提供选项。这是好事,因为如果节点被暴露,其他用户可以直接进入您的 Pod。让我们通过输入命令 k apply -f nodeport.yaml 创建一个 NodePort 服务,其输出将类似于以下内容:
root@kind-control-plane:/# k apply -f nodeport.yaml
service/no created
root@kind-control-plane:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP
➥ PORT(S) AGE
apache-svc ClusterIP 100.96.102.73 <none>
➥ 80/TCP 23h
internal-svc ClusterIP None <none>
➥ 8080/TCP,9200/TCP 21m
kubernetes ClusterIP 10.96.0.1 <none>
➥ 443/TCP 29h
no NodePort 100.96.95.252 <none>
➥ 443:30000/TCP 3s
NodePort 服务在端口下有一个额外的参数,表示从 30,000 个端口中使用的特定节点端口。Pods 将通过名为 no 的服务在节点的 100.96.95.252:30000 上可用。
6.4.3 负载均衡器服务
负载均衡器服务正如其名——一个在云中或裸机设备上配置的负载均衡设备。对于 CKA 考试,你不必担心实际的负载均衡器设备;你只需要知道如何创建负载均衡器服务及其相关的 YAML。让我们就这样做!输入命令k create svc loadbalancer lb-svc --tcp 8080:8080 --dry-run=client -o yaml > lb-svc.yaml。通过输入命令vim lb-svc.yaml打开文件。查看这个负载均衡器服务与图 6.15 中显示的 NodePort 和 ClusterIP 服务之间的差异。

图 6.15 创建负载均衡器服务的 YAML
你会注意到负载均衡器服务的 YAML 与 ClusterIP 服务的 YAML 之间没有太大的区别。唯一的区别是类型被设置为LoadBalancer。这意味着不需要使用节点端口来访问应用程序,这因此使得通信稍微容易一些。这里有一个选项可以将 NodePort 添加到 YAML 中,这意味着端口仍然被暴露,但节点端口不会作为访问应用程序的方法被暴露。
在我们创建负载均衡器服务之前,让我们在我们的 kind 集群中创建一个负载均衡器。一个与 kind 配合得很好的负载均衡器是 MetalLB。你可能还记得在第四章中用 Helm 安装了 MetalLB 负载均衡器。现在我们再次部署它,但这次我们将通过 YAML 清单来安装。运行命令k apply -f https:// raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_06/metallb-native.yaml来安装 MetalLB 负载均衡器。接下来,我们需要为你的集群配置负载均衡器。为此,输入k get no -o wide以找到你的节点 IP 地址。输出将类似于以下内容(但 IP 地址不同):
root@kind-control-plane:/# k get no -o wide
NAME STATUS ROLES AGE VERSION
➥ INTERNAL-IP
kind-control-plane Ready control-plane 32h v1.25.0-beta.0
➥ 172.18.0.2
在Internal-IP列下,复制前两个八位字节(对我来说是172.18),因为你将更改下一个文件的值,你可以使用命令curl -O https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_06/metallb-layer2-config.yaml下载该文件。一旦你下载了文件,使用命令vim metallb-layer2-config.yaml打开它。将地址下面的值更改为与你的节点 IP 地址匹配。输出应该如下所示:
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 172.18.255.1-172.18.255.50
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
在文件metallb-layer2-config.yaml中,我将地址更改为172.18.255.1-172.18.255.50以匹配我的节点 IP 地址的前两个八位字节。现在我们可以使用命令k create -f metallb-layer2-config.yaml应用 YAML。你应该会看到以下输出:
root@kind-control-plane:/# k create -f metallb-layer2-config.yaml
ipaddresspool.metallb.io/first-pool created
l2advertisement.metallb.io/example created
现在我们可以创建我们的 LoadBalancer 服务。不是创建一个新的,而是修改现有的名为apache-svc的服务,将其从 ClusterIP 服务更改为 LoadBalancer 服务。为此,输入命令k edit svc apache-svc,这将打开 Vim 中的 YAML 规范。向下滚动到以单词type开头的行,将ClusterIP更改为LoadBalancer(区分大小写)。别忘了按键盘上的 I 键进入插入模式!
一旦您进行了更改,请按键盘上的 Esc 键退出插入模式,并输入:wq以保存文件并退出。这将应用更改。现在您可以执行命令k get svc并看到以下内容:
root@kind-control-plane:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP
➥ PORT(S) AGE
apache-svc LoadBalancer 100.96.25.132 172.18.255.1
➥ 80:30499/TCP 30s
internal-svc ClusterIP None <none>
➥ 8080/TCP,9200/TCP 3h43m
kubernetes ClusterIP 10.96.0.1 <none>
➥ 443/TCP 32h
no NodePort 100.96.95.252 <none>
➥ 443:30000/TCP 3h22m
类型已更改为LoadBalancer,现在在External-IP列中有一个 IP 地址。这是 MetalLB 负载均衡器的 IP 地址,我们可以使用它来访问我们的应用程序(在 Pod 中运行)。输入curl 172.18.255.1,您应该得到以下结果:
root@kind-control-plane:/# curl 172.18.255.1
<html><body><h1>It works!</h1></body></html>
6.5 集群节点网络配置
当尝试在 Kubernetes 集群内部进行通信时,与从一台服务器到另一台服务器的通信并没有太大的区别,只是增加了一些功能。这些功能通常被称为网络覆盖层,它们是在您已有的网络之上的网络抽象。为什么是这样呢?因为 Kubernetes 集群中的每个 Pod 都需要自己的 IP 地址,您可能没有为每个 Pod 预留足够的 IP 地址,尤其是当集群扩展到数十个节点和可能数百个 Pod 时。这种覆盖层有时被称为VXLAN。
VXLAN 位于现有的物理网络之上,并使用封装协议在第三层网络上隧道第二层连接。当通信在节点之间来回传递时,如图 6.16 所示,当尝试到达一个 Pod 时,封装通过在数据包上放置一个头部来帮助进行正确的路由。

图 6.16 通过 CNI 从节点到节点的封装数据包移动
该覆盖层遵循一组规范和库,用于编写插件以配置 Linux 容器中的网络接口。这个覆盖层的术语是container network interface (CNI**)。CNI 是云原生计算基金会(CNCF)的一个项目,它不仅限于 Kubernetes,因为它是一个创建容器之间网络的通用框架。然而,当与 Kubernetes 一起使用时,它是一个在集群中运行的 DaemonSet,在根命名空间中创建一个虚拟网络接口,这是节点进出数据包流动的中介。
Kubernetes 中流行的 CNI 插件包括 Flannel、Calico、Weavenet、Cilium 等。在 CKA 考试中,仅使用 Flannel 和 Calico,因此我们将重点关注它们。kindnet DaemonSet 正在我们的集群中运行,是用于 kind 集群的 CNI。您可以通过输入命令k get ds -n kube-system来查看这一点。输出将类似于以下内容:
root@ingress-control-plane:/# k get ds -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE
➥ SELECTOR AGE
kindnet 1 1 1 1 1
➥ <none> 47h
kube-proxy 1 1 1 1 1
➥ kubernetes.io/os=linux 47h
这充当了一个 VXLAN。对于考试,你需要知道如何选择合适的 CNI 插件。我们可以通过首先创建一个没有 CNI 的新 kind 集群,使用附录 C 中定义的步骤来安装 flannel CNI。一旦你创建了一个新的 kind 集群,使用命令docker exec -it kind-control-plane bash和docker exec -it kind-worker bash分别获取到两个节点容器的一个 shell。当你有一个 Bash shell 到容器时,继续在kind-control-plane和kind-worker上运行命令apt update; apt install wget。这将安装 wget,这是一个我们可以用来从网络上下载文件的命令行工具,这正是我们将通过命令wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz所做的事情。因为这是一个 tarball 文件,你需要使用命令tar -xvf cni-plugins-linux-amd64-v1.1.1.tgz来解压它。输出将类似于以下内容:
root@kind-control-plane:/# tar -xvf cni-plugins-linux-amd64-v1.1.1.tgz
./
./macvlan
./static
./vlan
./portmap
./host-local
./vrf
./bridge
./tuning
./firewall
./host-device
./sbr
./loopback
./dhcp
./ptp
./ipvlan
./bandwidth
对于我们的情况,网桥文件是最重要的,因为它将为 Kubernetes 提供使用 Flannel 作为 CNI 所需的插件。它还需要位于特定的目录——/opt/cni/bin/——以便被集群识别。我们将使用命令mv bridge /opt/cni/bin/将文件 bridge 移动到该目录。
现在我们已经在kind-control-plane和kind-worker上安装了网桥插件,我们可以通过在控制平面节点的 shell 内部创建flannel Kubernetes 对象来安装 Flannel CNI——使用命令kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml。你可以使用命令kubectl get no来验证节点是否处于就绪状态。输出将类似于以下内容:
root@kind-control-plane:/# kubectl get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 16m v1.25.0-beta.0
kind-worker Ready <none> 16m v1.25.0-beta.0
我们还可以使用命令kubectl get po -A来验证 CoreDNS Pods 正在运行,并且flannel Pods 已被创建并正在运行。输出将类似于以下内容:
root@kind-control-plane:/# k get po -A
NAMESPACE NAME READY
➥ STATUS
kube-flannel kube-flannel-ds-d6v6t 1/1
➥ Running
kube-flannel kube-flannel-ds-h7b5v 1/1
➥ Running
kube-system coredns-565d847f94-txdvw 1/1
➥ Running
kube-system coredns-565d847f94-vb4kg 1/1
➥ Running
kube-system etcd-kind-control-plane 1/1
➥ Running
kube-system kube-apiserver-kind-control-plane 1/1
➥ Running
kube-system kube-controller-manager-kind-control-plane 1/1
➥ Running
kube-system kube-proxy-9hsvk 1/1
➥ Running
kube-system kube-proxy-gkvrz 1/1
➥ Running
kube-system kube-scheduler-kind-control-plane 1/1
➥ Running
local-path-storage local-path-provisioner-684f458cdd-8bwkh 1/1
➥ Running
现在,你在你的 kind 集群中已经安装了 Flannel CNI,并准备好使用 CNI 在节点之间进行通信,以及为往返的数据包提供封装。
考试练习
使用kubectl命令行,通过nginxdemos/hello:plain-text镜像创建一个名为hello的 Deployment。使用kubectl命令行暴露 Deployment 以创建一个名为hello-svc的 ClusterIP 服务,该服务可以通过端口 80 进行通信。使用正确的kubectl命令来验证它是一个具有正确端口暴露的 ClusterIP 服务。
将上一个练习中创建的hello-svc服务更改为 NodePort 服务,其中 NodePort 应为 30000。确保就地编辑服务,而不是创建新的 YAML 或发出新的命令。使用 curl 通过 NodePort 服务与 hello Deployment 内的 Pod 进行通信。
使用命令 k apply -f https:// raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_06/ nginx-ingress-controller.yaml 在集群中安装 Ingress 控制器。将 hello-svc 服务改回 ClusterIP 服务,并创建一个 Ingress 资源,当客户端请求 hello.com 时,将路由到 hello-svc 服务。
创建一个新的没有 CNI 的 kind 集群。安装 bridge CNI,然后安装 Calico CNI。在安装 CNI 后,验证 CoreDNS Pods 是否正在运行,并且节点处于就绪状态。
摘要
-
对于服务发现,在 Kubernetes 集群中,DNS 解析是通过 CoreDNS 进行的。我们探讨了如何通过 DNS 查询来发现 Pod 和服务。对于考试,你将需要知道如何配置和使用 CoreDNS。
-
CoreDNS 在
kube-system命名空间中的 Deployment 上运行。我们可以复制 CoreDNS Deployment 中的 Pod 以获得更快的域名解析。 -
kubelet 负责为每个 Pod 提供正确的 CoreDNS IP 地址,以便允许 Pod 进行 DNS 解析。对于考试,请记住在控制平面节点上存在
/etc/kubernetes/manifests目录,kubelet 将自动启动 Pods(称为静态 Pods)。 -
Pod 中的 DNS 解析器文件是
/etc/resolv.conf,它将根据集群中的服务具有某些搜索标准。resolv.conf文件还包含 DNS IP 地址。在考试中解析主机名时,如果 Pod 在不同的命名空间中,别忘了使用 Pod 的 FQDN。 -
Ingress 和 Ingress 控制器允许通过层 7 路由到集群,通过服务与 Pod 进行通信。Ingress 可以处理同一 Ingress 资源中多个服务的路由,对于考试,你需要知道如何配置这种路由。此外,你还需要知道如何使用 Ingress 控制器和 Ingress 资源。
-
Kubernetes 中有三种不同类型的 Service,所有这些都允许集群内 Pod 与 Pod 之间的通信。对于考试,你需要了解 Pod 之间的连接性。
-
kubelet 是在节点本身上运行的服务。你可以重新加载守护进程并重启服务,就像你更改创建 Pod 的配置一样。对于考试,你将需要知道如何停止、启动和重新加载 kubelet 守护进程。
-
Kubernetes 中的集群网络接口 (CNI) 通过封装数据包并为源和目标路由添加头部来帮助节点之间的通信。对于考试,你需要了解集群节点上的主机网络配置。
7 Kubernetes 中的存储
本章涵盖
-
在 Kubernetes 中创建卷
-
持久卷声明和存储类
-
在 Kubernetes 中的应用程序中使用存储
如我们从之前的练习中看到的,Pod 的生命周期很短,驻留在该 Pod 中的数据生命周期也短。为了解决这个问题并保留 Pod 使用的相关数据,我们创建了持久卷(PV)。在考试中,你将测试创建 PV、持久卷声明(PVC)和存储类的能力。我们将在本章中讨论这些主题。
存储领域
本章涵盖了 CKA 课程中的存储领域,该领域占考试题目的 10%。该领域涵盖了应用程序数据在 Kubernetes 中的使用方法,包括将存储挂载到 Pod 中以供容器使用。它包括以下能力。
| 能力 | 章节部分 |
|---|---|
| 理解存储类和 PVC。 | 7.1, 7.3 |
| 理解卷模式、访问模式和回收策略。 | 7.1.1, 7.1.2, 7.1.3 |
| 理解 PVC 原语。 | 7.2 |
| 了解如何配置具有持久存储的应用程序。 | 7.1, 7.2, 7.3, 7.4 |
7.1 持久卷
为了防止在 Kubernetes 中数据丢失,通过创建一个完全独立的 Kubernetes 对象,称为持久卷,将数据从 Pod 本身解耦,该对象对所有集群中的 Pod 都是可用的,因为 PV 不是命名空间化的。随着 Pod 的来去,数据并不绑定到 Pod 本身,因此它在其自己的管理资源中超越了 Pod 的生命周期。你可以继续向卷中添加数据对象,如果另一个 Pod 计划挂载相同的卷,持久卷将不会改变,如图 7.1 所示。实际的存储由主机、网络文件系统(NFS)服务器、亚马逊的简单存储解决方案(S3)存储桶或任何其他存储提供商支持。

图 7.1 一个挂载到 Pod 的持久卷(PV),它可以作为一个独立的实体
CKA 考试将测试你对三种存储类型的理解:(1) emptyDir卷,(2) hostPath卷,和(3) nfs卷,因此我们将重点关注这些类型(而不是许多其他类型)。例如,考试问题可能如下所述。
考试任务 在集群ek8s中,配置一个名为vol02833的hostPath PV,大小为 10 MB 的本地存储,来自工作节点主机node01。主机上的目录应该是/mnt/data。创建一个名为claim-02833的 PVC,将从卷vol02833中保留 90 MB 的存储。将其挂载到名为frontend0113的 Deployment 中的 Pod;容器内的挂载路径应该是/usr/share/nginx/html。YAML 文件的名称应该是deploy.yaml。 |
|---|
如果您还没有访问现有 Kubernetes 集群的权限,附录 A 中解释了如何使用 kind 创建 Kubernetes 集群。请确保创建一个双节点集群,因为我们将会使用工作节点的本地存储来创建 PV 资源。一旦您有权访问您的双节点集群,通过运行命令docker exec -it kind-control-plane bash来获取控制平面节点的 shell。一旦您有了 shell,通过命令alias k=kubectl将您的别名k设置为等于kubectl。在考试中,他们已经为您设置了此别名,所以熟悉使用k而不是反复输入kubectl是很好的。
要创建此 PV 资源,我们将使用 Kubernetes 文档来复制一些 YAML,您在考试期间也将能够这样做。这不仅是对考试日的良好实践,而且我们之所以这样做,是因为按照设计,我们无法像使用--dry-run=client创建部署的 YAML 模板那样创建 YAML 模板。通过文档访问 YAML 将使您熟悉 Kubernetes 文档以及如何导航它们。
考试技巧:在新考试环境中,使用快捷键 CTRL-SHIFT-C 来复制和 CTRL-SHIFT-V 来粘贴。这,加上知道如何搜索 Kubernetes 文档,将在您无法轻松使用kubectl创建持久存储时节省考试时间。
前往kubernetes.io/docs,并使用屏幕左侧的搜索栏搜索短语“使用持久卷”。点击名为“配置 Pod 以使用持久卷进行存储 | Kubernetes”的链接,并滚动到“创建持久卷”部分。该页面的完整 URL 是mng.bz/rW0y。将此页面的 YAML 复制并粘贴到名为pv.yaml的新文件中。结果应类似于图 7.2。

图 7.2 从 Kubernetes 文档复制并粘贴到名为pv.yaml的文件中的 YAML
我们将把 PV 的名称从task-pv-volume更改为vol02883(图 7.2,第 4 行),并将存储从10Gi更改为100Mi(图 7.2,第 10 行)。保存文件pv.yaml,并使用命令k create -f pv.yaml创建 PV。如果您执行命令k get pv,您将看到类似于以下内容的输出:
root@kind-control-plane:/# k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
➥ STORAGECLASS
vol02833 100Mi RWO Retain Available
➥ manual
从输出中您可以看到状态是可用的,这意味着它已准备好被领取。让我们在 Kubernetes 文档的同一页面上向下滚动来创建我们的领取。该页面再次是mng.bz/Bm92。将 YAML 粘贴到名为pvc.yaml的文件中。文件内容应类似于图 7.3。

图 7.3 一个名为pvc.yaml的 PVC YAML 文件,它是从 Kubernetes 文档中复制的
将 PVC 的名称从 task-pv-claim 更改为 claim-02833,并将存储从 3Gi 更改为 90Mi(图 7.3,第 11 行)。使用命令 k apply -f pvc.yaml 创建 PVC。一旦资源创建成功,您可以使用命令 k get pvc 查看 PVC,也可以使用命令 k get pv 查看 PV。该命令的输出将如下所示:
root@kind-control-plane:/# k get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
➥ AGE
claim-02833 Bound vol02833 100Mi RWO manual
➥ 4m
root@kind-control-plane:/# k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS
➥ CLAIM STORAGECLASS
vol02833 100Mi RWO Retain Bound
➥ default/claim-02833 manual
考试提示:您可以通过在资源名称之间放置逗号来同时列出多个 Kubernetes 资源。例如,执行命令 k get pv,pvc 以在默认命名空间中列出 PV 和 PVC。
如果您从输出中注意到,尽管我们在 PVC YAML 中指定了 90Mi,但容量是 100Mi。这是因为 PVC 将尝试找到最接近的 PV 来满足该请求,但不能仅保留 PV 的一部分。如果我们请求 110Mi,PVC 将不会满足,因为没有匹配的 PV 具有请求的存储量。
让我们继续创建一个 Deployment,这样我们就可以使用挂载到 Pod 容器中的卷。我们可以使用命令 k create deploy frontend0113 --image nginx --dry-run=client -o yaml > deploy.yaml 创建 Deployment YAML。一旦文件创建成功,我们将使用命令 vim deploy.yaml 打开它。文件内容应类似于图 7.4。

图 7.4 使用 kubectl 命令创建的 Deployment 的 YAML
在规范下,我们将通过在行 24 输入 volumes: 来将我们的卷 YAML 内联添加到 containers 中(您可以删除 status: {},因为它不是必需的)。在 volumes: 下,输入 - name: vol-33。在其下方,与 name 保持一致,输入 persistentVolumeClaim:。在 persistentVolumeClaim: 下,缩进两个空格并输入 claimName: claim-02833。最后,删除 creationTimestamp: null 这一行。结果将类似于图 7.5。

图 7.5 在 YAML 文件 deploy.yaml 中将 volumes 语法添加到 Deployment 规范
在创建 Deployment 之前,我们必须在容器内指定卷的挂载路径。我们将通过在行 23 下方添加一行来实现这一点。与资源保持一致,输入 volumeMounts:。在其下方,输入 - name: vol-33(必须与卷名称相同)。在 name: vol-33 下方,与名称保持一致,输入 mountPath: "/usr/share/nginx/html"。最终 Deployment YAML 的内容将类似于图 7.6。

图 7.6 添加名为 vol-33 的卷的完整 Deployment YAML
现在,我们可以保存我们的 YAML 文件,并使用命令 k apply -f deploy.yaml 创建 Deployment。您可以使用命令 k get deploy 列出 Deployment,并使用命令 k get po 列出 Deployment 内的 Pods。输出将类似于以下内容:
root@kind-control-plane:/# k get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
frontend0113 1/1 1 1 8s
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
frontend0113-6d6c77b9b6-62qbh 1/1 Running 0 11s
您会看到容器正在运行。这意味着我们的卷已成功挂载。为了证明这一点,请输入命令 k describe po,然后跟上是 Pod 的名称(Pod 的名称对您来说将是不同的)。我的命令是 k describe po frontend0113-6d6c77b9b6-62qbh。在输出的卷部分,您将看到名为 vol-33 的卷以及我们之前创建的 PVC。简略输出将类似于图 7.7。

图 7.7 k describe po 命令的简略输出,显示了挂载到 Pod 上的体积
现在,我们已经成功创建了一个 PV、一个 PVC 以及将卷挂载到 Pod 的 Deployment,我们已经成功完成了这个考试任务。
7.1.1 hostPath 的问题
使用 hostPath 类型的体积的问题在于,在多个节点集群中,数据会被写入集群中的所有节点,预期所有节点的存储配置都是相同的。如果不是这样,数据将只会写入一个节点。这意味着如果 Pod 被调度到某个节点,数据可能不会驻留在该节点上。
考试技巧:如果在考试中遇到一个表示存储必须超出 Pod 生命周期的题目,除非另有说明,否则 hostPath 将是您需要提供的卷类型。如果您遇到一个需要数据存在于除本地存储以外的题目,考试将说明应该使用哪种卷类型(例如,nfs)。
为了解决这个问题,您可以使用 nfs 卷类型。nfs 卷允许您将挂载到 Pod 的数据存储在网络的 nfs 共享中。nfs 类型也支持多个写入者;因此,许多 Pod 可以同时使用这个卷。因为我们实验室中没有运行 nfs 服务器,所以我将在这里展示一个使用 nfs PV 的 Pod 的简单示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 1Mi
accessModes:
- ReadWriteMany
nfs:
server: nfs-server.default.svc.cluster.local
path: "/"
mountOptions:
- nfsvers=4.2
PV 看起来与之前的不同,因为我们添加了包含服务器和路径的 nfs 字段。路径是挂载在 nfs 服务器上的位置。挂载选项基于您的服务器使用的 nfs 版本。一旦我们创建了这个 PV,我们就可以创建一个像这样的 PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: ""
resources:
requests:
storage: 1Mi
volumeName: nfs-pv
如您所见,声明的结构非常类似于之前的声明,只是访问模式不同。这是因为您可以写入 nfs 卷到多个源(Pod)。在 PVC 中,我们也可以通过指定 volumeName 来指定声明应绑定到的 PV,就像在最后一行那样。一旦我们有了 PV 和 PVC,我们就可以创建 Pod 的 YAML 如下:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nfs-pod
name: nfs-pod
spec:
containers:
- image: nginx
name: nfs-pod
volumeMounts:
- name: nfs-vol
mountPath: "/usr/share/nginx/html"
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: nfs-vol
persistentVolumeClaim:
claimName: nfs-pvc
再次,如果在您的实验室中运行此示例,它将失败,因为您的网络中没有运行的 NFS 服务器;然而,在考试中,情况将不同。如果有问题表示您必须使用 nfs 卷,您肯定会提供一个运行的 nfs 服务器,以服务器地址和路径的形式提供。
7.1.2 体积模式
卷模式是 Kubernetes 中正在访问的卷类型。默认情况下,如果您没有包含特定的模式,PV 将假定卷模式为FileSystem,而不是Block。最初我们不需要在 YAML 中指定它,但您可以通过运行命令k describe pv vol02833来查看它默认的模式。该命令的输出将类似于以下内容:
root@kind-control-plane:/# k describe pv vol02833
Name: vol02833
Labels: type=local
Annotations: pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection]
StorageClass: manual
Status: Bound
Claim: default/claim-02833
Reclaim Policy: Retain
Access Modes: RWO
VolumeMode: FileSystem
Capacity: 100Mi
Node Affinity: <none>
Message:
Source:
Type: HostPath (bare host directory volume)
Path: /mnt/data
HostPathType:
Events: <none>
如您所见,在Access Modes:和Capacity:之间是VolumeMode: FileSystem。文件系统模式旨在将卷挂载到 Pod 中作为一个目录。块模式由块设备支持,其中设备是空的(未格式化)。块设备的例子包括 Amazon Elastic Block Store (EBS)、Azure Disk、iSCSI 磁盘、FC(光纤通道)磁盘、CephFS,甚至是本地磁盘(就像我们在考试任务中使用的那样)。对于 CKA 考试,您不需要创建块存储本身,您只需要知道如何更改 YAML 规范中的volumeMode。因为我们仍然有 7.8 节中的文件pv.yaml,我们将打开它并添加一行。在storageClassName下面,内联添加volumeMode: Block。同时更改 PV 的名称,因为您不能有两个具有相同名称的卷。我们新的块存储卷的最终 YAML 将类似于图 7.8。

图 7.8 在Block模式下创建 PV 的第九行
您可以使用命令k apply -f pv.yaml创建这个新的 PV,您将看到 PV 紧挨着现有的 PV。输出将类似于以下内容:
root@kind-control-plane:/# k apply -f pv.yaml
persistentvolume/vol028333 created
root@kind-control-plane:/# k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS
➥ CLAIM STORAGECLASS
vol02833 100Mi RWO Retain Bound
➥ default/claim-02833 manual
vol028333 100Mi RWO Retain Available
➥ manual
您会注意到,在组成方面它们看起来完全相同,但一个是FileSystem模式,另一个是Block模式。
7.1.3 访问模式
访问模式决定了卷如何被访问,通过什么权限,以及从哪些在 Kubernetes 中运行的应用程序。您可能已经注意到,在我们的pv.yaml文件和我们的pvc.yaml文件中,我们都指定了访问模式为ReadWriteOnce。这意味着什么?从节点的角度来考虑,因为节点是访问卷的那个。ReadWriteOnce意味着只有一个节点可以挂载卷并对其进行读写。其他三种模式是ReadOnlyMany,意味着多个节点可以只读挂载卷;ReadWriteMany,意味着多个节点可以读写挂载卷;以及ReadWriteOncePod,意味着只有一个 Pod 在该节点上可以挂载该卷进行读写。
PV 可以有多个访问模式,而 PVC 只能有一个。即使 PV YAML 中指定了多个模式,节点一次也只能利用一个访问模式,因此您必须从四种模式中选择一个。例如,如果我们向我们的 PV YAML 添加了一个访问模式,如图 7.9 所示,您将不得不在 PVC 中选择ReadWriteOnce或ReadOnlyMany。

图 7.9 在 PV 的 YAML 中指定了多个访问模式,其中只有一个是可以在 PVC 中使用的
此外,最重要的是,某些卷只支持某些访问模式。例如,我们这里有的类型hostPath不支持ReadOnlyMany、ReadWriteMany或ReadWriteOncePod。这一点很重要,因为这可能在考试中作为一个陷阱问题出现。
考试技巧:考试可能不会明确说明访问模式是ReadOnlyMany还是ReadWriteOnce,因为它假设你知道hostPath类型的卷只支持ReadWriteOnce。另外,nfs卷支持ReadWriteOnce、ReadOnlyMany和ReadWriteMany。考试将只涵盖emptyDir、hostPath和nfs卷类型;emptyDir不是 PV 类型,因此没有访问模式。
每个访问模式的简写可以在表 7.1 中找到;然而,只有当描述 PV 或 PVC 时,你才会看到这些简写形式。
表 7.1 访问模式和它们的简写形式,用于 YAML 中的 PV 或 PVC
| 访问模式 | 简写 | 描述 |
|---|---|---|
ReadWriteOnce |
RWO |
只有一个节点可以挂载卷并对其进行读写。 |
ReadOnlyMany |
ROX |
多个节点可以挂载卷进行只读。 |
ReadWriteMany |
RWX |
多个节点可以挂载卷进行读写。 |
ReadWriteOncePod |
RWOP |
只有一个 Pod 在该节点上可以挂载该卷进行读写。 |
7.1.4 回收策略
在考试任务中应用于我们的 PV 的另一个默认值是回收策略。回收策略是用于决定在将 PVC 与其绑定删除后对卷进行什么操作。如果你执行命令k describe pv vol02833,你会看到默认的回收策略设置为Retain。describe命令的输出将类似于以下内容:
root@kind-control-plane:/# k describe pv vol02833
Name: vol02833
Labels: type=local
Annotations: pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection]
StorageClass: manual
Status: Bound
Claim: default/claim-02833
Reclaim Policy: Retain
Access Modes: RWO
VolumeMode: FileSystem
Capacity: 100Mi
Node Affinity: <none>
Message:
Source:
Type: HostPath (bare host directory volume)
Path: /mnt/data
HostPathType:
Events: <none>
在Claim和Access Modes之间是Reclaim Policy。此回收策略也可以设置为Recycle或Delete。Retain表示卷可以再次使用,因为它保留了卷上的数据。然后它可以被另一个声明使用。Recycle表示卷已被清理(即,rm -f),但此策略已被弃用,因此请改用Retain或Delete。Delete表示后端存储(即,Amazon EBS、Azure Disk)以及 Kubernetes 中的 PV 对象都已删除。只有 Amazon EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。
检验练习
创建一个名为volstore308的 PV,预留 22MB 的存储空间,不指定存储类。该卷将使用主机上的/mnt/data存储空间。
创建一个名为pv-claim-vol的 PVC,将之前创建的 PV 以正确的访问模式声明。
创建一个名为pod-access的 Pod,使用镜像centos:7,将使用之前步骤中的 PVC,并在容器内部挂载卷到/tmp/persistence。发布一个命令以保持容器活跃。你可以使用sleep 3600。
7.2 持久卷声明
正如我们在 7.1 节中的考试任务中体验到的,PVC 消耗了整个 PV,即使声称比请求量少 10Mi。为了满足存储请求,而不是等待一个完全匹配的量,PVC 将选择最接近该请求量的体积(图 7.10)。

图 7.10 PVC 将选择最接近的可用卷大小以满足请求。
PVC 的职责是为容器(在 Pod 内)保留此存储,因此一旦此请求处于已绑定状态,请求将不会释放卷,即使管理员尝试强制删除卷。例如,让我们尝试删除作为考试任务一部分创建的 PV vol02833。执行命令k delete pv vol02833 -force,你会看到类似的输出(在运行此命令后使用 CTRL-C 返回提示符):
root@kind-control-plane:/# k delete pv vol02833 --force
Warning: Immediate deletion does not wait for confirmation that the running
➥ resource has been terminated. The resource may continue to run on the cluster indefinitely.
persistentvolume "vol02833" force deleted
^Croot@kind-control-plane:/# k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS
➥ CLAIM STORAGECLASS
vol02833 100Mi RWO Retain Terminating
➥ default/claim-02833 manual
vol028333 100Mi RWO Retain
➥ Available manual
PV 将保持终止状态,直到 PVC 被删除。PVC 在被删除之前无法被删除。这防止了 Kubernetes 管理员意外地从正在运行的 Pod 中移除底层存储,这是一个特性而不是错误。尝试使用命令k delete pvc claim-02833 -force删除 PVC,你会看到相同的输出(再次使用 CTRL-C 来获取你的提示符)。输出将类似于以下内容:
root@kind-control-plane:/# k delete pvc claim-02833 --force
Warning: Immediate deletion does not wait for confirmation that the running
➥ resource has been terminated. The resource may continue to run on the
➥ cluster indefinitely.
persistentvolumeclaim "claim-02833" force deleted
^Croot@kind-control-plane:/# k get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES
➥ STORAGECLASS
claim-02833 Terminating vol02833 100Mi RWO manual
PVC 将继续处于终止状态,直到部署被删除(随后是 Pod)。在此期间,卷将继续挂载到 Pod 上并可用——就像在运行删除命令之前一样——并且不会影响当前访问 Pod 及其存储的应用程序。Pod 的阶段决定了其状态,可以通过命令k get po获取。卷可以处于以下阶段之一:
-
可用
-
已绑定
-
已发布
-
失败
我们在 7.1 节中首次创建 PV 时看到了这一点。这是 PVC 的要求:要请求卷,它必须可用。已绑定是 PV 的阶段,表示它已经被请求,因此对新的 PVC 不可用。已发布阶段很重要,因为如果 PV 在回收策略中设置为保留,它将被释放并等待新的 PVC 回收它。失败阶段表示在回收过程中,失败的卷未能有效地绑定到新的 PVC。
7.3 存储类
在跟随 7.1 节的考试任务时,你可能已经注意到我们在 YAML 中包含了一个存储类。存储类不过是一个“存储配置文件”,用于一组类似类型的存储。它很像标签,因为它不影响底层存储能力;它简化了在 Kubernetes 中创建持久存储的过程。你可以使用命令k get sc查看你集群中的现有存储类。输出将类似于以下内容:
root@kind-control-plane:/# k get sc
NAME PROVISIONER RECLAIMPOLICY
➥ VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION
standard (default) rancher.io/local-path Delete
➥ WaitForFirstConsumer false
在这个 Kubernetes 集群中,一个本地路径类型的卷是预先创建的。这是在集群创建过程中创建的,不是我自己手动创建的。你将在许多其他集群引导工具中体验到这一点(例如与 kind 一样),例如在 CKA 考试中使用 kubeadm,它将预配置存储类,你将被要求使用 PVC。以下是一个考试题目的例子。
在集群 ek8s 中,基于名为 standard 的现有存储类创建一个名为 claim-03833 的 PVC,申请 1 GB 的存储空间。将其挂载到名为 backend0113 的 Deployment 中的 Pod 上;容器内的挂载路径应为 /var/lib/mysql。YAML 文件的名称应为 backend.yaml。 |
|---|
使用与 7.1 节中相同的集群,复制之前的 pvc.yaml 文件,并使用命令 cp pvc.yaml newpvc.yaml 将其重命名为 newpvc.yaml。使用命令 vim newpvc.yaml 打开文件 newpvc.yaml。将名称从 claim-02833 更改为 claim-03833,将 storageClassName 从 manual 更改为 standard。最后,将 storage 从 90Mi 更改为 1Gi。文件的最终内容将类似于图 7.11。

图 7.11 使用名为 standard 的存储类的 PVC
要创建 PVC,随后动态配置 PV,保存并退出 Vim,然后输入命令 k apply -f newpvc.yaml。这里事情变得有趣,我必须提醒你,如果在考试中遇到这种情况,不要慌张。当你列出 PVC 时,你应该得到以下结果(这是一个好现象):
root@kind-control-plane:/# k get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
claim-02833 Bound vol028333 100Mi RWO manual
claim-03833 Pending standard
PVC 处于挂起状态,因为存储只有在创建一个 Pod(换句话说,一个“消费者”)来使用(消耗)该 PVC 时才会配置,这是智能的,因为你不希望预先配置大量未使用的卷。这是静态配置(我们在 7.1 节中执行的操作)与存储类发生的动态配置之间的主要区别。如果你运行命令 k describe pvc claim-03833,你会看到这一点得到反映。输出应该如下(已缩略):
Access Modes:
VolumeMode: FileSystem
Used By: <none>
Events:
Type Reason Age From
Message
➥ ---- ------ ---- ----
➥ -------
Normal WaitForFirstConsumer 50s (x26 over 6m51s) persistentvolume-
➥ controller waiting for first consumer to be created before binding
现在我们知道我们的 PVC 正在等待消费者,让我们创建一个!再次,让我们使用命令 cp deploy.yaml backend.yaml 将之前的 YAML 文件 deploy.yaml 复制到一个名为 backend.yaml 的新文件中。使用命令 vim backend.yaml 打开文件 backend.yaml。将名称和标签更改为 backend0113,将镜像更改为 mysql:8.0,将容器名称更改为 mysql,将 volumeMounts 的名称更改为 mysqldata,将 mountPath 更改为 "/var/lib/mysql",最重要的是将 PV 名称更改为 claim-03833。我们将添加 MySQL 密码的环境变量,并在容器上暴露端口 3306。最终的 YAML 将类似于图 7.12。

图 7.12 使用挂载在容器 /var/lib/mysql 中的 PVC 命名为 claim-03833 的 Deployment
让我们保存文件并退出 Vim。如果你没有完全捕捉到所有的更改和添加,你可以使用命令 curl -O https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_07/backend.yaml 下载文件。我们可以使用命令 k apply -f backend.yaml 创建 Deployment。几乎立即,我们会看到我们的 PVC 已经绑定,并且有一个新自动配置的 PV。要列出 PVC 和 PV,运行命令 k get pvc,pv。该命令的输出将类似于以下内容:
root@kind-control-plane:/# k get pvc,pv
NAME STATUS VOLUME
➥ CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/claim-02833 Bound vol028333
➥ 100Mi RWO manual 55m
persistentvolumeclaim/claim-03833 Bound pvc-5cc8fff1-e8a2-4934-ae17-
➥ fa4e3ecbcb40 1Gi RWO standard 54m
NAME CAPACITY
➥ ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
persistentvolume/pvc-5cc8fff1-e8a2-4934-ae17-fa4e3ecbcb40 1Gi RWO
➥ Delete Bound default/claim-03833 standard
persistentvolume/vol028333 100Mi RWO
➥ Retain Bound default/claim-02833 manual
你已经创建了一个使用标准存储类的 PVC,创建了一个将卷挂载到 Pod 的 Deployment,并完成了考试任务。
7.3.1 从存储类继承
如你可能已经注意到的,PV 继承了存储类的某些属性。其中一个是回收策略,设置为删除。其他的是卷类型(在我们的例子中是 hostPath)和卷绑定模式。我们可以使用命令 k get sc standard -o yaml 查看存储类的 YAML。输出应该如下(注释已缩短以方便阅读):
root@kind-control-plane:/# k get sc standard -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
creationTimestamp: "2022-10-06T21:10:24Z"
name: standard
resourceVersion: "264"
uid: 6caa1035-1584-4356-a060-90c923293a3b
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
这些设置会自动应用于使用存储类自动配置的每个 PV。
考试练习
创建一个名为 node-local 的存储类,使用提供者 kubernetes.io/no-provisioner。卷绑定模式应该是 WaitForFirstConsumer。
创建一个名为 claim-sc 的 PVC,将从之前创建的类中申请 39 MB 的卷。访问模式应该是 ReadWriteOnce。
创建一个名为 pod-sc 的 Pod,使用镜像 nginx,将使用之前步骤中的 PVC,并在容器内部挂载卷到 /usr/nginx/www/html。
7.4 非持久化卷
要充分实现 PV 的好处,你必须通过处理不持久化的卷来获得一些经验——这里我指的是 emptyDir 卷类型。这种卷类型使用容器本身的存储。这很可能会在考试中出现,所以让我们通过另一个考试场景来了解一下。
考试任务 在集群 ek8s 中创建一个名为 log-collector 的 Pod,该 Pod 使用类型为 emptyDir 的卷 logvol。在容器中将卷挂载到 /var/log。YAML 文件的名称应该是 log-collector.yaml。 |
|---|
我们可以继续使用从本章开始就一直在使用的同一个集群。使用命令 k run log-collector --image busybox --command sleep --command "3600" --dry-run=client -o yaml > log-collector.yaml 创建 Pod YAML 文件。使用命令 vim log-collector.yaml 打开文件 log-collector.yaml。在 restartPolicy 下方,我们将插入 volumes:。在下面一行,我们将插入 - name: logvol,并在下一行,与 name 平行,我们添加 emptyDir: {}(图 7.13)。

图 7.13 Pod YAML 规范的结尾,其中卷设置为类型 emptyDir
就像我们在 7.1 节中使用的 Deployment YAML 一样,我们还会将 volumeMounts 添加到容器中,其名称必须与我们在图 7.13 中添加的卷名称相同。在 Pod YAML 中 resources 下方插入 volumeMounts:,然后在其下方添加 - mountPath: /var/log。最后,与 mountPath 平行,输入 name: logvol。结果应该类似于以下内容:
1 apiVersion: v1
2 kind: Pod
3 metadata:
4 creationTimestamp: null
5 labels:
6 run: log-collector
7 name: log-collector
8 spec:
9 containers:
10 - command:
11 - sleep
12 - "3600"
13 image: busybox
14 name: log-collector
15 resources: {}
16 volumeMounts:
17 - mountPath: /var/log
18 name: logvol
19 dnsPolicy: ClusterFirst
20 restartPolicy: Always
21 volumes:
22 - name: logvol
23 emptyDir: {}
如你所见,这与我们在 7.1 节中挂载卷到 Deployment 的方式非常相似,除了我们更改了卷类型、名称和挂载路径。让我们使用命令 k create -f log-collector.yaml 创建 Pod。命令 k get po 的输出应该看起来像这样:
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
backend0113-7dbcbc574f-s45mx 1/1 Running 0 67m
frontend0113-6d6c77b9b6-h9hrj 1/1 Running 0 107m
log-collector 1/1 Running 0 11m
此外,如果你运行命令 k describe po log-collector,你应该在 Volumes: 部分看到卷实际上是以 EmptyDir 类型挂载的。以下是输出结果的缩写,你应该也能看到:
Volumes:
logvol:
Type: EmptyDir (a temporary directory that shares a pod's
➥ lifetime)
Medium:
SizeLimit: <unset>
你已成功创建了一个挂载类型为 EmptyDir 的卷的 Pod,并完成了任务。在使用此卷时,你应该了解数据不会在 Pod 生命周期结束后持续存在。如果 Pod 被终止,则数据也会被删除。
检验练习
创建一个名为 two-web 的 Pod,包含两个容器。第一个容器将命名为 httpd,并使用镜像 httpd:alpine3.17。第二个容器将命名为 nginx,并使用镜像 nginx:1.23.3-alpine。
两个容器都应该访问从容器本身本地存储共享的相同卷。
Container1 将卷挂载到 /var/www/html/,而 Container2 将卷挂载到 /usr/share/nginx/html/。
启动 Pod 并确保一切挂载和共享都正确。
摘要
-
要使数据在 Pod 生命周期结束后仍然持续存在,我们使用 PV。对于考试,如果你被要求在删除 Pod 后保留数据,请确保创建一个 PV。
-
Kubernetes 中有一个名为持久卷声明(PVC)的资源,用于为 Pod 预留或声明卷,它选择与请求容量最接近的卷大小。对于考试,你需要知道如何在 Deployment 或 Pod 中挂载卷。
-
在创建 PV 时,可以选择两种卷模式——
FileSystem(文件系统)和Block(块)。如果在 YAML 中未指定,则默认选择FileSystem模式。对于考试,寻找卷应该是目录的提示;在这种情况下,你会使用FileSystem卷类型。如果提到原始文件系统,你会使用Block卷模式。 -
对于 PV(持久卷)来说,有三种回收策略可供选择——
Retain(保留)、Recycle(回收)和Delete(删除)。回收策略告诉 PV 在从 Pod 卸载后应该做什么。对于这类考试问题,要注意像 retain 或 delete 这样的词。 -
有四种不同的访问模式:
ReadWriteOnce、ReadOnlyMany、ReadWriteMany和ReadWriteOncePod。访问模式决定了节点是否可以访问 PV,一次可以有多少节点访问 PV,以及它们是否有读和/或写权限。对于考试,检查是否需要多个节点访问卷;这将成为选择哪种访问模式的关键指标。 -
存储类是在 Kubernetes 中动态配置存储的一种方式。您不必创建 PV 即可使用存储类。当考试中的问题要求使用现有的存储类时,您只需创建一个 PVC 并选择要使用的存储类;卷将自动创建。
8 Kubernetes 故障排除
本章涵盖
-
监控和查看日志
-
确定高 CPU 或 RAM 使用情况
-
解决常见的集群问题
-
分析网络流量以识别通信问题
由于这是 CKA 考试中最大的主题(30%),我们将在本章中详细讲解故障排除。故障排除意味着修复应用程序、控制平面组件、工作节点和底层网络中的问题。在 Kubernetes 中运行应用程序时,可能会出现诸如 Pod、服务和工作部署等问题。
故障排除领域
本章涵盖了 CKA 课程中的故障排除领域,该领域占考试题目的 30%。这个领域包括我们用来发现和修复 Kubernetes 集群内部问题的技术,包括查看日志、捕获集群事件、网络问题和应用程序监控。它包括以下能力。
| 能力 | 章节部分 |
|---|---|
| 评估集群和节点日志。 | 8.1, 8.2 |
| 理解如何监控应用程序。 | 8.1 |
| 管理容器 stdout 和 stderr 日志。 | 8.1 |
| 故障排除应用程序故障。 | 8.1, 8.3 |
| 故障排除集群组件故障。 | 8.2 |
| 故障排除网络问题。 | 8.3 |
本章将帮助你理解容器在调试和将应用程序恢复到健康状态的过程中可能输出的日志。如果问题不是应用程序,可能是底层节点、底层操作系统或网络上的通信问题。在考试中,你将需要了解应用程序故障、集群级别问题和网络问题之间的区别,以及如何在最短的时间内进行故障排除和确定解决方案。
注意:本章中的练习涉及你必须采取的操作来“破坏”集群以提供故障排除的内容。对于考试,集群或集群对象已经损坏,因此你不需要太担心作为考试先决条件的初始操作。
8.1 理解应用程序日志
Kubernetes 管理员发现集群中问题发生原因的一种方式是通过查看日志。应用程序日志可以帮助你获取关于在 Pod 中运行的容器化应用程序内部发生情况的更详细的信息。容器引擎(例如,containerd)被设计为支持日志记录,通常将所有输出写入标准输出(stdout)和标准错误(stderr)流,并将这些流写入位于/var/log/containers目录中的文件(图 8.1)。

图 8.1 stdout 和 stderr 容器日志被发送到由 kubelet 管理的日志文件,位于/var/log/containers。
CKA 考试将测试你在 Pod 内部排查错误的能力。由于 Pod 错误和容器错误是同义的,从 Kubernetes 中的任何应用程序检索日志被简化了。该领域的一个考试问题示例如下。
考试任务 在集群ik8s中,在名为db08328的命名空间中,使用kubectl命令行(强制)创建一个名为mysql的部署,其镜像为mysql:8。列出db08328命名空间中的 Pods 以查看 Pod 是否正在运行。如果 Pod 没有运行,查看日志以确定 Pod 为何不在健康状态。一旦收集到必要的日志信息,对 Pod 进行更改以修复它并使其恢复到运行和健康状态。 |
|---|
如果你还没有访问现有的 Kubernetes 集群,你可以按照附录 A 中所述使用 kind 创建一个 Kubernetes 集群。你只需要一个单节点集群,所以遵循 A.1.1 节中的说明。一旦你通过命令docker exec -it kind-control-plane bash获得了控制平面节点的 shell,设置你的kubectl别名以及 Tab 补全功能,这将有助于你在考试中纠正错误并习惯使用 Tab 补全。你可以在附录 B 的末尾找到执行这些操作的说明,但这里再次列出要运行的命令(按顺序):
apt update && apt install -y bash-completion
echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
source ~/.bashrc
在考试当天,这些配置已经完成,所以无需担心需要记住这些命令。当你开始考试时,你将能够立即使用k别名和 Tab 补全功能。
在运行这些命令之后,根据说明使用命令k create ns db08328创建命名空间。你可以使用命令k get ns继续列出所有命名空间。输出将类似于以下内容:
root@kind-control-plane:/# k create ns db08328
namespace/db08328 created
root@kind-control-plane:/# k get ns
NAME STATUS AGE
db08328 Active 4s
default Active 11m
kube-node-lease Active 11m
kube-public Active 11m
kube-system Active 11m
local-path-storage Active 11m
现在你已经设置了正确的命名空间,你可以使用命令k config set-context --current --namespace db08328将上下文更改为db08328命名空间,这样你就不必在每次命令中输入命名空间。你可以使用命令更改上下文。输出将如下所示:
root@kind-control-plane:/# k config set-context --current --namespace
➥ db08328
Context "kubernetes-admin@kind" modified.
现在你已经将上下文设置为你将创建部署的命名空间,使用命令k create deploy mysql --image mysql:8创建名为mysql的部署。之后,你可以使用命令k get po列出 Pods。
考试技巧:请注意,你不必每次都使用-n选项来指定你的命名空间。我要提醒你,在考试中这可能会变得令人困惑,因为随着每个任务的进行,你也在设置上下文。所以请记住,你将执行此命令两次;因此,在某些情况下,每次都输入命名空间可能更容易,具体取决于每个任务中你需要处理的命名空间数量。
部署创建和 Pod 列表的输出应如下所示:
root@kind-control-plane:/# k create deploy mysql --image mysql:8
deployment.apps/mysql created
root@kind-control-plane:/# k get po -w
NAME READY STATUS RESTARTS AGE
mysql-68f7776797-w92l6 0/1 CrashLoopBackOff 1 (10s ago) 7m28s
在这种情况下,Pod 的状态处于 CrashLoopBackOff。Pod 可以有多个状态,包括 OOMKilled、ErrImagePull、ImagePullBackoff、FailedScheduling、NonZeroExitCode 和 CreateContainerConfigError。你可以在表 8.1 中查看失败的状态。
表 8.1 持久卷或持久卷声明的 YAML 中使用的访问模式和它们的简称
| 状态 | 含义 |
|---|---|
CrashLoopBackOff |
Pod 正在尝试启动,崩溃,然后循环重启。Kubernetes 将在重启之间等待递增的回退时间,以给你机会修复错误。 |
ImagePullBackOff |
Pod 无法启动,因为它无法在本地或远程容器注册库中找到指定的镜像。它将继续尝试,并使用递增的回退延迟。 |
ErrImagePull |
Pod 由于授权问题无法找到或拉取镜像而无法启动。 |
CreateContainerConfigError |
Pod 内的容器由于缺少运行所需的组件而无法启动。 |
RunContainerError |
Pod 内的容器由于容器运行时或容器入口点的问题而无法运行。 |
FailedScheduling |
Pod 无法调度到节点,可能是因为节点被标记为不可调度,应用了污点,或者节点无法满足要求。 |
NonZeroExitCode |
Pod 内的容器由于应用程序错误或缺少文件或目录而意外退出。 |
OOMKilled |
Pod 已被调度,但分配给它的内存限制已被超出。 |
A CrashLoopBackoff 表示 Pod 正在持续启动、崩溃、重启,然后再次崩溃,因此称为 崩溃循环。我们可以通过使用命令 k logs mysql-68f7776797-w92l6 来查看容器日志来了解为什么会发生这种情况。在这里,Tab 完成功能很有用,因为你可以开始输入 mysql,然后快速按下键盘上的 Tab 键,它将为你完成剩余的部分。Tab 完成功能将在考试中启用,但如果你想在你的集群中设置此功能,请参阅附录 B。命令 k logs mysql-68f7776797-w92l6 的输出将如下所示:
root@kind-control-plane:/# k logs mysql-68f7776797-w92l6
2022-12-04 16:51:13+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL
➥ Server 8.0.31-1.el8 started.
2022-12-04 16:51:13+00:00 [Note] [Entrypoint]: Switching to dedicated user
➥ 'mysql'
2022-12-04 16:51:13+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL
➥ Server 8.0.31-1.el8 started.
2022-12-04 16:51:13+00:00 [ERROR] [Entrypoint]: Database is uninitialized
➥ and password option is not specified
You need to specify one of the following as an environment variable:
- MYSQL_ROOT_PASSWORD
- MYSQL_ALLOW_EMPTY_PASSWORD
- MYSQL_RANDOM_ROOT_PASSWORD
这告诉我们我们想要知道的确切信息,即数据库密码需要在容器内部设置为一个环境变量。输出甚至给出了您可以选择的环境变量名称。如果您还记得第七章,我们创建了一个 mysql 部署,其中我们将密码设置为环境变量;让我们回顾一下并利用那些相同的技巧来解决我们这里的问题。回顾图 7.12,我们可以看到环境变量是与容器镜像的名称一起设置的;让我们使用命令 k edit deploy mysql 将此应用于我们当前正在运行的部署。首先,如果您从一个全新的集群开始,您需要运行命令 apt update; apt install vim 来使用 Vim 文本编辑器编辑部署。一旦部署打开,您可以在 YAML 中进行以下添加,如图 8.2 所示。

图 8.2 通过添加数据库密码的环境变量来编辑 mysql 部署。
一旦完成这些操作,通过按键盘上的 :wq 保存并退出编辑 mysql 部署。这将带您回到命令提示符,在那里您可以执行 k get po 命令来查看 Pod 是否现在处于运行状态。输出应该看起来像这样:
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
mysql-5dcb7797f7-6spvc 1/1 Running 0 12m
果然,Pod 现在的状态是 Running,这正是我们需要让 Pod 回到运行、健康状态并完成考试任务的情况。
8.1.1 容器日志详情
k logs 命令是一个方便的实用工具,用于查看 Pod 内容器的 stdout 和 stderr。如果一个 Pod 内部有两个容器,您必须添加一个 -c 并指定您想要访问的容器。例如,运行命令 k run busybox -image busybox -command ['while true; do echo "$(date)": "I am a busybox container"; sleep 5; done'] -dry-run=client -o yaml > pod.yaml 以生成 Pod 的 YAML。使用命令 vim pod.yaml 打开 pod.yaml 文件。我们将对 YAML 进行一些小的修改,首先将命令全部放在一行上,将单引号移动到方括号内,然后在 while 单词之前添加以下内容:'sh', '-c',。最终结果应该类似于图 8.3 中的 YAML 文件。

图 8.3 在 Kubernetes 中在容器内运行命令的 YAML 指定。
现在您已经创建了 YAML,与图 8.3 中的 YAML 行匹配,只需进行一个更改。复制以 command、image 和 name 开头的三行,并将它们粘贴到现有三行下方,以指定第二个容器。
考试技巧:在 Vim 中能够复制粘贴可以在考试中节省你时间!通过按键盘上的 Shift + V 选择行,然后使用上下箭头选择。一旦你选择了所有行,按 Y 键复制,按 P 键粘贴。这可能需要一些练习,但为了在 CKA 考试中节省时间,这是值得的!
现在你已经粘贴了这些行,将第二个容器的名称从 busybox 更改为 sidecar。同时,将句子 I am a busybox container 更改为 I am a sidecar container。其余部分保持不变。结果应该类似于图 8.4 中的 YAML,它创建了两个具有不同名称和不同命令的不同容器。

图 8.4 在 Pod YAML 清单中添加第二个容器
现在 YAML 已经完成,使用命令 k apply -f pod.yaml 创建 Pod。如果你没有跟上或者创建 Pod 有困难,运行命令 k apply -f https://raw.githubusercontent.com/chadmcrowell/ acing-the-cka-exam/main/ch_08/multi-container-pod-for-logging.yaml。要查看集群中默认命名空间内的 Pods,运行命令 k get po。输出应该类似于这样:
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
busybox 2/2 Running 0 1m
mysql-5dcb7797f7-6spvc 1/1 Running 2 (22h ago) 45h
要查看 Pod busybox 中名为 busybox 的第一个容器的日志,你可以运行命令 k logs busybox,或者为了更明确,你可以指定容器名称为 k logs busybox -c busybox。输出应该看起来像这样:
root@kind-control-plane:/# k logs busybox -c busybox
Tue Dec 6 14:36:26 UTC 2022: I am a busybox container
Tue Dec 6 14:36:31 UTC 2022: I am a busybox container
Tue Dec 6 14:36:36 UTC 2022: I am a busybox container
Tue Dec 6 14:36:41 UTC 2022: I am a busybox container
要查看同一 Pod 中名为 sidecar 的第二个容器的日志,运行命令 k logs busybox -c sidecar。输出应该看起来像这样:
root@kind-control-plane:/# k logs busybox -c sidecar
Tue Dec 6 14:18:59 UTC 2022: I am a sidecar container
Tue Dec 6 14:19:04 UTC 2022: I am a sidecar container
Tue Dec 6 14:19:09 UTC 2022: I am a sidecar container
要同时查看两个容器的日志,你可以输入 k logs busybox -all-containers。输出应该类似于这样:
root@kind-control-plane:/# k logs busybox -all-containers
Wed Dec 7 01:06:56 UTC 2022: I am a busybox container
Wed Dec 7 01:07:01 UTC 2022: I am a busybox container
Wed Dec 7 01:07:06 UTC 2022: I am a busybox container
Wed Dec 7 01:07:11 UTC 2022: I am a busybox container
Wed Dec 7 01:07:16 UTC 2022: I am a busybox container
Wed Dec 7 01:07:21 UTC 2022: I am a busybox container
Wed Dec 7 01:06:56 UTC 2022: I am a sidecar container
Wed Dec 7 01:07:01 UTC 2022: I am a sidecar container
Wed Dec 7 01:07:06 UTC 2022: I am a sidecar container
Wed Dec 7 01:07:11 UTC 2022: I am a sidecar container
Wed Dec 7 01:07:16 UTC 2022: I am a sidecar container
Wed Dec 7 01:07:21 UTC 2022: I am a sidecar container
如果你想要继续查看日志(流式日志),你可以运行命令 k logs nginx -all-containers -f。要返回提示符,请按键盘上的 Control-C。
考试技巧:帮助菜单在考试中真的能帮到你。与其试图记住命令,不如使用 kubectl 帮助菜单(例如,k logs -help)。最好的是,帮助菜单包含示例命令,你可以直接复制粘贴!
当你运行 k logs 命令时,你将得到与在日志目录中看到相同的输出。要查看日志目录,请查看节点上的 /var/log/containers 目录(Pod 运行的地方)。例如,你可以查看我们的控制平面节点上的 var/log/containers 目录并查看其内容。你将看到类似于图 8.5 的输出。

图 8.5 /var/log/containers 目录中每个运行容器对应的日志文件的简略输出
你会注意到每个日志文件的名字都是以 Pod 名称开头,后面跟着一串唯一字符(例如,mysql-5dcb7797...)。你可以查看这个文件的 内容以获取类似于 k logs 命令的输出。要查看日志文件的内容,输入命令 cat /var/log/containers/mysql-5dcb7797f7-6spvc_db08328_mysql-f9f53dc7de949452d848211d5d74aab36c2f5ae24a9d8b0b45577890d8b26ea2.log(你可以按 Tab 键完成文件名,这样你就不必全部输入). 输出将类似于图 8.6。

图 8.6 查看与特定 Pod 关联的日志文件
如果 k logs 命令没有返回任何日志输出怎么办?让我们看看图 8.7 中的决策树,以帮助我们通过故障排除决策过程。

图 8.7 故障排除 Pod 错误的决策过程
这可能是因为容器一开始就没有启动。在这种情况下,你的故障排除过程中的下一步应该是运行 k describe 命令来查看容器的日志。例如,运行命令 k run brick --image busybox --command 'while true; do echo "$(date)"; sleep 5; done'。这将返回一个错误。运行命令 k get po 来列出 Pods。你应该看到以下输出:
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
brick 0/1 RunContainerError 1 (0s ago) 2s
busybox 2/2 Running 0 7m57s
mysql-5dcb7797f7-6spvc 1/1 Running 2 (33h ago) 2d8h
当你运行 k logs brick 命令时,你没有收到任何输出,因为容器还没有启动,所以它还没有机会生成任何日志。如果你运行 k describe po brick 命令,你会看到这个容器没有启动的原因。输出应该类似于以下内容(为了上下文而省略):
root@kind-control-plane:/# k describe po brick
Name: brick
Namespace: db08328
Command:
while true; do echo "$(date)"; sleep 5; done
State: Waiting
Reason: RunContainerError
Last State: Terminated
Reason: StartError
Message: failed to create containerd task: failed to create shim
➥ task: OCI runtime create failed: runc create failed: unable to start
➥ container process: exec: "while true; do echo \"$(date)\"; sleep 5;
➥ done": executable file not found in $PATH: unknown
Exit Code: 128
Started: Thu, 01 Jan 1970 00:00:00 +0000
Finished: Wed, 07 Dec 2022 01:15:04 +0000
Ready: False
Restart Count: 2
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 19s default-scheduler Successfully
➥ assigned db08328/brick to kind-control-plane
Normal Pulled 18s kubelet Successfully
➥ pulled image "busybox" in 494.040497ms
Normal Pulled 17s kubelet Successfully
➥ pulled image "busybox" in 494.479219ms
Normal Pulling 5s (x3 over 19s) kubelet Pulling image
➥ "busybox"
Normal Created 4s (x3 over 18s) kubelet Created
➥ container brick
Warning Failed 4s (x3 over 18s) kubelet Error: failed to
➥ create containerd task: failed to create shim task: OCI runtime create
➥ failed: runc create failed: unable to start container process: exec:
➥ "while true; do echo \"$(date)\"; sleep 5; done": executable file not
➥ found in $PATH: unknown
Normal Pulled 4s kubelet Successfully
➥ pulled image "busybox" in 509.215883ms
Warning BackOff 3s (x3 over 17s) kubelet Back-off
➥ restarting failed container
如你从 k describe po brick 命令的输出中看到,容器未能启动,因为它无法启动内部进程,这在 k describe 命令的输出中的消息和事件中都有显示。
8.1.2 从容器内部进行故障排除
因为容器内部的网络命名空间与节点上的不同,你可能需要从容器内部本身进行故障排除。这在故障排除场景中很常见,因为你可能无法打开一个到处于错误状态的容器的 shell(如前所述)。运行命令 k run tool --image lansible/dnstools:latest -it -sh 来创建一个名为 tool 的新 Pod,并同时在那个 Pod 内部获取到容器的 shell。此时,你会看到你的提示符发生变化。结果将类似于以下内容:
root@kind-control-plane:/# k run tool --image lansible/dnstools:latest -it
➥ --sh
If you don't see a command prompt, try pressing enter.
/ #
现在,你可以在容器内部的网络命名空间中输入各种命令来进行故障排除。你可以运行命令 nslookup Kubernetes 来获取 Kubernetes 服务的 DNS 服务器。输出将类似于以下内容:
/ # nslookup kubernetes
Server: 10.96.0.10
Address: 10.96.0.10#53
** server can't find kubernetes: NXDOMAIN
一旦你退出容器,你会注意到容器仍在运行。
考试技巧:对于考试,如果你需要排除与 Pod 的通信问题,请使用 busybox 镜像并在创建时获取一个 shell。这将使容器在没有插入冗长命令的情况下继续运行。例如,运行命令 k run busybox --image busybox -it --sh。
因为容器仍在运行,你可以使用命令 k exec -it tool --sh 来重新进入容器内的 shell。输出将类似于以下内容:
/ # exit
Session ended, resume using 'kubectl attach tool -c tool -i -t' command
➥ when the pod is running
root@kind-control-plane:/# k exec -it tool --sh
/ #
现在你再次有了 shell,运行命令 curl -k http://10.96.0.1:443 向 Kubernetes 服务发送请求。输出将类似于以下内容:
/ # curl -k http://10.96.0.1:443
Client sent an HTTP request to an HTTPS server.
/ #
再次退出,现在你可以使用命令 k exec tool --cat /etc/resolv.conf 在容器内运行命令而不需要获取 shell。输出应该类似于以下内容:
/ # exit
root@kind-control-plane:/# k exec tool --cat /etc/resolv.conf
search kb6656.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
最后,你还可以使用命令 k run curlpod --image=nicolaka/netshoot --rm -it --sh 在删除 Pod 的同时进入 Pod 内的容器。这将给出与之前相同的结果,但一旦你退出,由于使用了 kubectl run 中的 --rm 选项,Pod 将被删除。输出将类似于以下内容:
root@kind-control-plane:/# k run curlpod --image=nicolaka/netshoot --rm -it
➥ --sh
If you don't see a command prompt, try pressing enter.
~ # exit
Session ended, resume using 'kubectl attach curlpod -c curlpod -i -t'
➥ command when the pod is running
pod "curlpod" deleted
练习题
运行命令 k run testbox --image busybox --command 'sleep 3600' 来创建一个名为 testbox 的新 Pod。检查容器是否正在运行。通过决策树找出原因,并修复 Pod 使其运行。
创建一个名为 busybox2 的新容器,使用镜像 busybox:1.35.0。检查容器是否处于运行状态。找出容器失败的原因,并对 Pod 的 YAML 文件进行修正以使其运行。
创建一个名为 curlpod2 的新容器,使用镜像 nicolaka/netshoot,并在创建时打开一个 shell。当 shell 打开到容器时,运行 nslookup 命令来检查 Kubernetes 服务。退出 shell 并查看为什么容器没有运行。修复容器使其继续运行。
8.2 集群组件故障
当你运行 k describe 命令时,并不总是会发现问题是容器本身;它也可能是控制平面组件的问题,这些组件控制着 Kubernetes 集群中的 Pod。我们在第二章中回顾了控制平面组件,以帮助你理解每个组件的功能,这在故障排除时很有帮助。例如,如果一个 Pod 处于挂起状态并且没有被分配到节点,你知道要调查调度器,因为这个组件负责将 Pod 调度到节点。或者如果 Deployment 没有正确扩展,你知道要查看控制器管理器,因为控制器管理器负责将期望状态与当前状态匹配。
我们知道,控制平面组件,如调度器、控制器管理器、etcd 和 API 服务器,都在 kube-system 命名空间中的控制平面节点上作为 Pods 运行。因此,调查这些 Pods 的过程与本章第一部分非常相似,包括运行 k logs 和 k describe 命令以查找容器日志和事件。我们还知道,这些控制平面组件的 YAML 清单位于 /etc/Kubernetes/manifests 目录中,任何有效的 YAML 文件都会自动在此运行。我们称这为 静态 Pod,因为调度器不知道这些 Pods,因此无法相应地调度它们。以下是一个考试示例问题。
考试任务 在集群 ik8s 中,在名为 ee8881 的命名空间中,使用 kubectl 命令行(命令式)创建一个名为 prod-app 的 Deployment,使用镜像 nginx。列出 ee8881 命名空间中的 Pods,以查看 Pod 是否正在运行。运行命令 curl https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/kube-scheduler.yaml --silent --output /etc/kubernetes/manifests/kube-scheduler.yaml 以对 kube-scheduler 进行更改,模拟集群组件故障。现在将 Deployment 从一个副本扩展到三个。再次列出 Pods,并查看 Deployment 中的额外两个 Pods 是否正在运行。找出为什么这两个额外的 Pods 没有运行,并修复调度器,使容器再次处于运行状态。 |
|---|
你可以继续使用我们之前用于考试任务的相同单节点集群。无需创建新的 kind Kubernetes 集群。
首先,使用命令 k create ns ee8881 创建 ee8881 命名空间。然后,使用命令 k config set-context --current --namespace ee8881 将上下文更改为这个新命名空间,以避免多次输入命名空间。输出应类似于以下内容:
root@kind-control-plane:/# k create ns ee8881
namespace/ee8881 created
root@kind-control-plane:/# k config set-context --current --namespace
➥ ee8881
Context "kubernetes-admin@kind" modified.
现在,你的上下文位于 ee8881 命名空间,你可以使用命令 k create deploy prod-app --image nginx 创建名为 prod-app 的 Deployment。紧接着,你可以运行 k get deploy,po 以查看正在运行的 Deployment 和其内的 Pods。输出应类似于以下内容:
root@kind-control-plane:/# k create deploy prod-app --image nginx
deployment.apps/prod-app created
root@kind-control-plane:/# k get deploy,po
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/prod-app 1/1 1 1 9s
NAME READY STATUS RESTARTS AGE
pod/prod-app-85c9dd4f9d-l7fmj 1/1 Running 0 9s
现在 Deployment 已创建,并且 Deployment 内的 Pod 处于运行状态,运行命令 curl https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/kube-scheduler.yaml --silent --output /etc/kubernetes/manifests/kube-scheduler.yaml 以模拟控制平面故障。紧接着,使用命令 k scale deploy prod-app -replicas 3 将 Deployment 从一个副本扩展到三个。使用 k get po 命令查看 Deployment 内的 Pods,你会看到 Pods 处于挂起状态。输出应类似于以下内容:
root@kind-control-plane:/# curl
➥ https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-
➥ exam/main/ch_08/kube-scheduler.yaml --silent --output
➥ /etc/kubernetes/manifests/kube-scheduler.yaml
root@kind-control-plane:/# k scale deploy prod-app --replicas 3
deployment.apps/prod-app scaled
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
prod-app-85c9dd4f9d-9clfl 0/1 Pending 0 7s
prod-app-85c9dd4f9d-l7fmj 1/1 Running 0 4m21s
prod-app-85c9dd4f9d-mbdk7 0/1 Pending 0 7s
在这种情况下,如果您运行 k logs prod prod-app-85c9dd4f9d-9clfl, 您将不会得到任何输出,因为容器从未能够启动并生成日志。但是,如果您运行命令 k -n kube-system logs kube-scheduler-kind-control-plane,您将收到有关容器为何无法启动的有用信息。如前所述,了解调度器在 Kubernetes 中的工作原理有助于您在故障排除时找到最快的解决方案路径。运行命令 k -n kube-system logs kube-scheduler-kind-control-plane | tail -2 将显示日志的最后两行,这对于我们了解错误已经足够。输出应该看起来像这样:
root@kind-control-plane:/# k -n kube-system logs kube-scheduler-kind-
➥ control-plane | tail -2
Error: unknown flag: --kkubeconfig
这是解决问题的线索。如果您使用命令 vim /etc/kubernetes/manifests/kube-scheduler.yaml 在 /etc/kubernetes/manifests/ 目录下查看调度器清单,您将看到——在文件顶部,在容器的命令部分——调度器失败的原因(图 8.8)。

图 8.8 在调度器容器内部运行的命令在传递 kubeconfig 时包含了一个额外的 k。
为了解决这个问题,我们可以简单地通过进入插入模式(按键盘上的 I 键)并删除额外的 k 来编辑文件。一旦我们修复了这个问题,我们可以通过首先按键盘上的 Esc 键,然后输入 :wq 来退出 Vim,这将带您回到可以看到调度器 Pod 自动修复自己的提示符。您可以使用命令 k get po -A(或 k -n kube-system get po)来查看这一点。输出应该看起来像这样:
root@kind-control-plane:/# k get po -A
NAMESPACE NAME READY
➥ STATUS RESTARTS AGE
db08328 brick 0/1
➥ CrashLoopBackOff 21 (2m49s ago) 85m
db08328 busybox 2/2
➥ Running 0 93m
db08328 mysql-5dcb7797f7-6spvc 1/1
➥ Running 2 (34h ago) 2d9h
db08328 nginx-76d6c9b8c-k22xs 1/1
➥ Running 0 48m
db08328 nginx-76d6c9b8c-vzkmw 1/1
➥ Running 0 46m
db08328 nginx-76d6c9b8c-w24w4 1/1
➥ Running 0 46m
ee8881 prod-app-85c9dd4f9d-9clfl 0/1
➥ ContainerCreating 0 20m
ee8881 prod-app-85c9dd4f9d-l7fmj 1/1
➥ Running 0 24m
ee8881 prod-app-85c9dd4f9d-mbdk7 0/1
➥ ContainerCreating 0 20m
kube-system coredns-565d847f94-75lsz 1/1
➥ Running 2 (34h ago) 2d10h
kube-system coredns-565d847f94-8stkp 1/1
➥ Running 2 (34h ago) 2d10h
kube-system etcd-kind-control-plane 1/1
➥ Running 3 (34h ago) 2d10h
kube-system kindnet-b9f9r 1/1
➥ Running 2 (34h ago) 2d10h
kube-system kube-apiserver-kind-control-plane 1/1
➥ Running 3 (34h ago) 2d10h
kube-system kube-controller-manager-kind-control-plane 1/1
➥ Running 3 (34h ago) 2d10h
kube-system kube-proxy-z4qvh 1/1
➥ Running 2 (34h ago) 2d10h
kube-system kube-scheduler-kind-control-plane 0/1
➥ ContainerCreating 0 2s
local-path-storage local-path-provisioner-684f458cdd-vkbln 1/1
➥ Running 3 (34h ago) 2d10h
通过运行 k get po -A 命令,您会注意到 prod-app 部署的 Pod 同时启动,这表明我们之前遇到的扩展问题已经解决,prod-app 部署中的所有三个副本现在都处于运行状态。这满足了我们的考试任务要求。
8.2.1 故障排除集群事件
现在我们已经审查了控制平面组件和其他应用程序的各个 Pod 日志,我们应该考虑 Kubernetes 中一个为 Kubernetes 资源状态的所有更改生成事件的对象。当一个 Pod 从挂起变为运行,或从运行变为失败时,会触发一个事件。这些事件——解决路径不像我们在上一个考试任务中看到的那样清晰——对于调试很有用。这个对象与常规日志事件不同,可以告诉您关于当前问题根源的有价值信息。使用命令 k get events -A 查看这些事件。-A 表示所有命名空间,因为在这个对象是 Kubernetes 中的命名空间资源。输出应该看起来像这样:
root@kind-control-plane:/# k get events -A
NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
db08328 35s Warning BackOff pod/brick Back-off restarting
➥ failed container
如您所见,关于我们之前正在处理的“砖块”Pod,返回了一些有价值的信息,包括原因和消息。
考试提示:对于考试,k get events命令可能会输出太多事件,包括可能对故障排除没有帮助的“正常”消息。使用以下命令来过滤掉正常事件,只显示警告和信息类型的事件:k get events --field-selector type!=Normal -A。
请记住,这些事件默认情况下只保留 1 小时,所以它们可能从考试开始到结束会有所不同。您可以在 API 服务器上设置--event-ttl选项来更改此设置,但我不建议这样做,除非考试任务明确要求您这样做。
过滤特定类型日志的逆操作是输出整个集群的信息以及集群中所有 Pod 的日志。要获取集群健康状况的详细视图,您可以运行以下命令:k cluster-info dump。输出将非常大,因此我已经修改了命令,只包括这里最后 10 行:
root@kind-control-plane:/# k cluster-info dump | tail -10
2022/12/07 02:40:28 [notice] 1#1: nginx/1.23.2
2022/12/07 02:40:28 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian
➥ 10.2.1-6)
2022/12/07 02:40:28 [notice] 1#1: OS: Linux 5.15.49-linuxkit
2022/12/07 02:40:28 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/12/07 02:40:28 [notice] 1#1: start worker processes
2022/12/07 02:40:28 [notice] 1#1: start worker process 32
2022/12/07 02:40:28 [notice] 1#1: start worker process 33
2022/12/07 02:40:28 [notice] 1#1: start worker process 34
2022/12/07 02:40:28 [notice] 1#1: start worker process 35
==== END logs for container nginx of pod ee8881/prod-app-85c9dd4f9d-mbdk7
➥ ====
您还可以使用 grep 这个命令来查找单词error或fail,这在您不知道问题来源时进行故障排除非常有效。例如,运行以下命令以仅获取包含单词error的行:k cluster-info dump | grep error。再次提醒,我已经修改了我的命令以缩短输出,但命令输出应该看起来像这样:
root@kind-control-plane:/# k cluster-info dump | grep error | tail -1
E1207 20:59:46.036248 1 leaderelection.go:330] error retrieving
➥ resource lock kube-system/kube-controller-manager: Get
➥ "https://172.18.0.2:6443/apis/coordination.k8s.io/v1/namespaces/kube-
➥ system/leases/kube-controller-manager?timeout=5s": dial tcp
➥ 172.18.0.2:6443: connect: connection refused
8.2.2 工作节点故障
有时候,当您在故障排除 Kubernetes 集群的节点时,查看节点状态以查看 kubelet 服务是否正在运行可能会有所帮助。就像检查 Pod 一样,您可以运行以下命令来找到 CPU 和 RAM 的状态,以及输出底部的事件:k describe no kind-control-plane。您可能会发现没有可用资源或节点处于失败状态。此外,考试中已经安装了指标服务器,所以在这个部分不要过多关注安装的细节。
指标服务器允许您实时检查 CPU 和内存使用情况,考试的重点将是如何在提供的终端中运行命令来查看这些指标。使用以下命令安装指标服务器:kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.5.0/components.yaml。如果您在自己的集群中跟随操作,您需要应用一个小补丁,这可以通过以下命令轻松完成:kubectl patch -n kube-system deployment metrics-server --type=json -p '[{"op":"add", "path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure -tls"}]'。等待几秒钟(最多 60 秒),您应该会有一个正在工作的指标服务器。使用以下命令进行测试:k top no。输出应该看起来像这样:
root@kind-control-plane:~# k top no
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
kind-control-plane 177m 4% 1735Mi 44%
如从输出中所示,此节点正在消耗 177m 的 CPU,占总 CPU 的 4%,以及 1820 MB 的内存,占总内存的 44%。这是一种检查你的 Pods 是否无法调度到节点的方法,因为如果它们不能,它们将使用此命令显示接近 100% 的利用率。
在检查可用磁盘空间方面,你可以运行命令 df -h,这是一个显示节点上可用磁盘空间的命令,而 -h 选项使输出更易于阅读。以下是输出应看起来像这样:
root@kind-control-plane:~# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 59G 29G 27G 52% /
tmpfs 64M 0 64M 0% /dev
shm 64M 0 64M 0% /dev/shm
/dev/vda1 59G 29G 27G 52% /var
tmpfs 2.0G 139M 1.8G 8% /run
tmpfs 2.0G 0 2.0G 0% /tmp
rootfs 2.0G 333M 1.6G 17% /usr/lib/modules
tmpfs 5.0M 0 5.0M 0% /run/lock
你也可以通过简单地运行命令 k get no 来显示节点的状态,就像我们在本书中多次做的那样。最后,要在节点上启动 kubelet 服务,请运行命令 systemctl start kubelet。你还可以使用 Journalctl 来查看 kubelet 服务的日志。运行命令 journalctl -u kubelet 以排除 kubelet 服务的任何问题。输出应类似于图 8.9(为了可读性进行了缩写)。

图 8.9 kubelet 日志的 journalctl 工具的缩写输出
kubelet 可能存在另一个问题是 kubelet 配置。在第六章中,我们在更改集群 DNS 的同时修改了 kubelet 配置。当 kubelet 启动时,一些证书会传递给 kubelet 以验证 Kubernetes API;集群域名和健康端点也在这里设置。在考试中,如果由于某种原因 kubelet 无法通过 systemctl start kubelet 命令启动,请检查 /var/lib/kubelet/config.yaml 文件以查找配置错误。
最后,在第二章中我们讨论了 kubelet 的 kubeconfig,它位于 /etc/kubernetes/ 目录下的名为 kubelet.conf 的文件。这有时会导致访问集群的问题。为了正确思考这个故障排除过程,请回顾图 8.10 中所示的决策树。

图 8.10 故障排除集群访问的决策过程
有时 kubelet 二进制文件完全缺失或位于不同的目录中。你可以使用命令 which kubelet 来查看 kubelet 二进制文件的位置。输出看起来类似于这样:
root@kind-control-plane:~# which kubelet
/usr/bin/kubelet
如果 /usr/bin/kubelet 目录与运行 systemctl status kubelet 时列出的不同,则存在问题。检查 /etc/systemd/system/kubelet.service.d/ 目录中的 10-kubeadm.conf 文件,以查看 ExecStart 是否设置为 /usr/bin/kubelet。当你更改 kubelet 配置时,请记住你必须重新加载守护进程然后启动服务。使用命令 systemctl daemon-reload``, 然后使用命令 systemctl restart kubelet 来这样做:
root@kind-control-plane:~# systemctl daemon-reload
root@kind-control-plane:~# systemctl restart kubelet
8.2.3 你指定了正确的主机或端口吗?
当你输入任何kubectl命令(例如,kubectl get po)时,你可能会收到的一条常见信息是:“服务器$SERVER:6443 的连接被拒绝 - 你指定了正确的主机或端口吗?”这条信息发生的原因要么是 API 配置错误,要么是你的 kubeconfig 设置不正确。为了在你的集群中看到这条信息,首先运行命令curl https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/kube-apiserver.yaml --silent --output /etc/kubernetes/manifests/kube-scheduler.yaml来修改 API 服务器配置。然后立即运行命令k get po来尝试列出集群中运行的 Pod。你会看到类似于以下内容的输出:
root@kind-control-plane:~# curl
➥ https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-
➥ exam/main/ch_08/kube-apiserver.yaml --silent --output
➥ /etc/kubernetes/manifests/kube-apiserver.yaml
root@kind-control-plane:~# k get po
The connection to the server kind-control-plane:6443 was refused - did you
➥ specify the right host or port?
如果你查看/etc/kubernetes/manifests/目录下的kube-apiserver.yaml文件,你会在第 44 行看到问题,那里的event-ttl缺少一个=符号。
考试提示:记住,你将参加考试的主机与控制平面服务器不同。在寻找/etc/kubernetes/manifests/目录之前,你将想要 SSH 到控制平面节点。
当你把那个等号放回去时,输出应该看起来类似于图 8.11。

图 8.11 包含正确格式化event-ttl的kube-apiserver.yaml文件的一部分
当你保存并退出此文件(:wq)时,你会发现信息并没有消失。
考试提示:不要慌张!你可能会以无法恢复的方式修改 API 配置。除了在更改之前备份kube-apiserver.yaml文件外,你还可以使用crictl检查 API 服务器 Pod 的状态。
这是可以的,因为我们可以使用crictl工具来重启容器。当不能使用kubectl命令而想查看底层 Pod 的状态时,crictl工具非常方便。使用命令crictl ps来查看容器,你将看到列表中的kube-apiserver容器。输出将类似于以下内容(此输出已缩略)并包括不同的容器 ID:
root@kind-control-plane:~# crictl ps
CONTAINER STATE NAME
d7b925224d332 Running kindnet-cni
84234147eb024 Running kube-apiserver
5dd8b3aeaa752 Running kube-controller-manager
5228bb1f5eb7c Running kube-scheduler
903193dd102db Running coredns
97268d17eb9fd Running kube-proxy
f9f8ea21cb4be Running etcd
这个输出的第一列是容器 ID。运行命令crictl stop 842来停止kube-apiserver容器。你可以使用容器 ID 的前三个字符,只要它在crictl ps列出的所有其他容器中是唯一的。然后立即运行命令crictl rm 842来杀死容器。几秒钟后,你会看到一个新的容器在其位置启动。你可以通过再次运行crictl ps来知道它是一个新容器,因为它的容器 ID 与之前不同。一旦你可以从crictl中看到状态为Running的容器,你就可以运行kubectl get po并看到它再次返回 Pod 列表。你将不再看到关于正确主机或端口的提示信息!
8.2.4 调试 kubeconfig
当收到“你有正确的主机或端口吗?”的消息时,另一种可能性是检查你的 kubeconfig 文件,看看它是否指定了正确的控制平面节点,证书是否有效,以及 KUBECONFIG 环境变量是否设置正确。当你安装 kubectl 或在你的 kind 集群中时,在你的主目录中创建了一个名为 .kube 的隐藏目录。这个目录包含服务器地址、用于验证集群的证书以及用户信息。你可以像查看其他文件一样查看这个文件,使用命令 cat ~/.kube/config,但还有一个用于查看 kubeconfig 的 kubectl 命令,即 k config view。这个命令的输出将类似于图 8.12。

图 8.12 命令 k config view 的输出,列出了 API 服务器、上下文和用户信息
考试技巧 这两个命令(cat ~/.kube/config 和 k config view)在考试中的结果可能不同。如果你被要求修复你的集群上下文,可能是因为你无法列出节点,运行命令 k config view --flatten > ~/.kube/config 以同步它们。这将合并配置文件并将输出保存到 ~/.kube/config 目录下的 config 文件中。
在考试中,这个 kubeconfig 文件可能不存在。或者你可能不小心删除或误配置了它。不要慌张!这个文件的一个副本存储在 /etc/kubernetes/ 目录中,名为 admin.conf。你可以使用这个文件以及 kubectl 命令,例如,通过运行命令 k get no --kubeconfig /etc/kubernetes/admin.conf,或者你可以设置一个名为 KUBECONFIG 的特殊环境变量。这个环境变量与 kubectl 命令行工具一起使用,并将你的 kubectl 命令指向这个变量值中存储的配置文件。运行命令 KUBECONFIG=/etc/kubernetes/admin.conf 来设置环境变量(在 Linux 系统中,使用全部大写字母创建变量名是常见的),然后你将能够再次访问你的集群。或者,你可以简单地使用命令 cp /etc/kubernetes/admin.conf ~/.kube/config 将文件从 /etc/Kubernetes/ 目录复制到 ~/.kube/ 目录。这些命令的输出将类似于以下内容:
root@kind-control-plane:~# ls .kube/
cache
root@kind-control-plane:~# k get no --kubeconfig /etc/kubernetes/admin.conf
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 5d23h v1.25.0-beta.0
root@kind-control-plane:~# KUBECONFIG=/etc/kubernetes/admin.conf
root@kind-control-plane:~# k get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 5d23h v1.25.0-beta.0
root@kind-control-plane:~# cp /etc/kubernetes/admin.conf ~/.kube/config
root@kind-control-plane:~# ls ~/.kube
cache config
当你运行 k config view 命令,如图 8.7 所示,你将注意到证书数据从输出中被删除。你可以使用命令 k config view -raw 来查看证书数据。输出将类似于以下内容:
root@kind-control-plane:~# k config view --raw
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk...
server: https://kind-control-plane:6443
name: kind
contexts:
- context:
cluster: kind
namespace: ee8881
user: kubernetes-admin
name: kubernetes-admin@kind
current-context: kubernetes-admin@kind
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JS...
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb...
你可以通过以下操作来显示证书数据,这些数据可以用来验证证书的真实性,并将其与 /etc/Kubernetes/pki/ 目录中的 ca.crt 证书文件进行匹配。首先,运行命令 k config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > ca-compare.crt 以 Base64 解码 k config view --raw 命令中的证书,并将输出存储在名为 ca-compare.crt 的文件中。然后,运行命令 cat /etc/kubernetes/pki/ca.crt >> ca-compare.crt 将 /etc/Kubernetes/pki/ca.crt 中的 ca.crt 证书追加到同一文件中。打开文件,并比较 BEGIN CERTIFICATE 和 END CERTIFICATE 之间的两行文本。这些命令的输出应如下所示:
root@kind-control-plane:~# k config view --raw -o
➥ jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -
➥ d > ca-compare.crt
root@kind-control-plane:~# cat /etc/kubernetes/pki/ca.crt >> ca-compare.crt
root@kind-control-plane:~# cat ca-compare.crt
-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIyMTIwNDE2MjEzNVoXDTMyMTIwMTE2MjEzNVowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKWj
sVQRplk9WcZwoQpls2wxXbOnAFL22CLsyGQmgzQZHgMSm3M/XCnHc5RZGQpv7mQS
GVgIzMjYZrpg/gm4U4TQEgLAqXRdQ2Rd08iJfUX3/onjyc/YPnfFtzNDJ4cFHkiX
mS0LwIUoOAb2dRQzitisvGiFhnr/bWl7QALOZBq2RzyhtrNBF18rRcWVUdmrQMqb
HEHsc2ZRefCVc7HSf2x8UnqOcRbgF413VmW+0R2+lOWka3c2tFqK86GHcKky2nY3
PYF+EE/HlLOBTlwb/okbKYWIf2eKaaNZ8Ypsj/aZGTAWu6Gt23S5Bgbe4WV4W7SZ
A+juTakjkPudFpTVvmcCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFGh+GTf8f/BLCKZuEnnItDDWDk5LMBUGA1UdEQQO
MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBABxG6ve/7gUzV/2nwIFl
JXPloCeFI+WeG1weKK+h98d4yYjGvVXjt9dwqCnIWGVG40KIYQvthf3kZcSpHpH0
GDNWrS4cvL8UvAzbuKH4opboGuoHAxGIslD0YgauoPRw3ofQSxMLeUnGfDN25CP4
g0XD5DwPANaPACFR7bKEDbcIfwAvMce6TcwWJ3QfG7e/Se/Z0LiasUNb7R7FxSFp
MKd21MlcqnnjqUfuGt42n23U08pkstfyO1nddqiMzFoXefFCjCDJ26kGeunNZsMb
2c8afwHwa8iAugXzmwhZ+XiNPgq0o3YRIz9oQLkEC24ojgM9sbJ55jZyN9TAvJCO
BHM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl
cm5ldGVzMB4XDTIyMTIwNDE2MjEzNVoXDTMyMTIwMTE2MjEzNVowFTETMBEGA1UE
AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKWj
sVQRplk9WcZwoQpls2wxXbOnAFL22CLsyGQmgzQZHgMSm3M/XCnHc5RZGQpv7mQS
GVgIzMjYZrpg/gm4U4TQEgLAqXRdQ2Rd08iJfUX3/onjyc/YPnfFtzNDJ4cFHkiX
mS0LwIUoOAb2dRQzitisvGiFhnr/bWl7QALOZBq2RzyhtrNBF18rRcWVUdmrQMqb
HEHsc2ZRefCVc7HSf2x8UnqOcRbgF413VmW+0R2+lOWka3c2tFqK86GHcKky2nY3
PYF+EE/HlLOBTlwb/okbKYWIf2eKaaNZ8Ypsj/aZGTAWu6Gt23S5Bgbe4WV4W7SZ
A+juTakjkPudFpTVvmcCAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFGh+GTf8f/BLCKZuEnnItDDWDk5LMBUGA1UdEQQO
MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBABxG6ve/7gUzV/2nwIFl
JXPloCeFI+WeG1weKK+h98d4yYjGvVXjt9dwqCnIWGVG40KIYQvthf3kZcSpHpH0
GDNWrS4cvL8UvAzbuKH4opboGuoHAxGIslD0YgauoPRw3ofQSxMLeUnGfDN25CP4
g0XD5DwPANaPACFR7bKEDbcIfwAvMce6TcwWJ3QfG7e/Se/Z0LiasUNb7R7FxSFp
MKd21MlcqnnjqUfuGt42n23U08pkstfyO1nddqiMzFoXefFCjCDJ26kGeunNZsMb
2c8afwHwa8iAugXzmwhZ+XiNPgq0o3YRIz9oQLkEC24ojgM9sbJ55jZyN9TAvJCO
BHM=
-----END CERTIFICATE-----
除了证书外,还要检查 kubeconfig 文件中的服务器地址是否正确。你可以通过比较 k config view 命令的输出与 k cluster-info 命令的输出来进行检查。通常,考试会使用 IP 地址而不是 DNS 名称。在这种情况下,你可以通过运行命令 ip addr | grep eth0 来检查控制平面服务器的私有 IP 地址(如果在考试中尝试此操作,请记住首先 SSH 到控制平面服务器)。输出应如下所示:
root@kind-control-plane:~# ip addr | grep eth0
9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
➥ state UP group default
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
22: veth0c1530f0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc
➥ noqueue state UP group default
inet 10.244.0.1/32 scope global veth0c1530f0
32: veth0dfc2be7@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc
➥ noqueue state UP group default
inet 10.244.0.1/32 scope global veth0dfc2be7
如果你移除了对 eth0 的 grep,你会看到 veth 接口——运行在控制平面节点上的容器的虚拟以太网设备,我们已经在第二章中详细讨论过,当时我们正在调查容器内的虚拟网络接口。
考试练习
使用命令 mv /etc/Kubernetes/manifests/kube-scheduler.yaml /tmp/kube-scheduler.yaml 将文件 kube-scheduler.yaml 移动到 /tmp 目录。
使用命令 k run nginx -image nginx 创建一个 Pod。列出 Pod,看看 Pod 是否处于运行状态。
通过查看事件和日志来确定 Pod 为什么没有启动。确定如何修复它,并将 Pod 恢复到运行状态。
运行命令 curl https://raw.githubusercontent.com/chadmcrowell/ acing-the-cka-exam/main/ch_08/10-kubeadm.conf --silent --output /etc/ systemd/system/kubelet.service.d/10-kubeadm.conf; systemctl daemon-reload; systemctl restart kubelet.。
检查 kubelet 的状态,并按照故障排除步骤解决 kubelet 服务的问题。
8.3 网络故障排除
如你所知,Pod 之间的通信是通过 CNI 进行的。如果节点不在就绪状态或容器无法创建 IP 地址,那么你就有集群内部或容器网络接口的网络问题。你已经从 CKA 考试手册中知道,你的一个集群将有一个回环 CNI,这可能需要或不需要对 CNI 进行故障排除或以某种方式修复 CNI。无论使用哪种 CNI,考试都会始终提供用于安装 CNI 的 YAML 文件或指向其位置的链接。这个问题经常出现,我想向你保证 你不需要 在考试中记住安装 CNI 的步骤。
8.3.1 配置故障排除
当尝试排除网络问题时,必须考虑各种因素。在你评估了决策树之后,解决方案可能仍然不清楚。你应该特别注意错误消息细节中的常见拼写错误。例如,考试中的一个问题可能看起来如下。
考试任务 在集群 ik8s 中,运行命令 k replace -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/kube-proxy-configmap.yaml -force 故意向集群中插入一个错误。立即之后,删除 kube-system 命名空间中的 kube-proxy Pod(它将自动重新创建)。列出命名空间中的 Pods,并查看 kube-proxy Pod 处于错误状态。查看日志以确定 kube-proxy Pod 为什么没有运行。一旦你收集了必要的日志信息,对 Pod 进行更改以修复它,并使其恢复到运行、健康的状态。 |
|---|
你可以继续使用之前考试任务中使用的相同单节点集群。没有必要创建一个新的 kind Kubernetes 集群。
首先,按照考试任务中的说明运行命令 k replace -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/kube-proxy-configmap.yaml -force。输出将如下所示:
root@kind-control-plane:/# k replace -f
➥ https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-
➥ exam/main/ch_08/kube-proxy-configmap.yaml --force
configmap "kube-proxy" deleted
configmap/kube-proxy replaced
使用命令 k -n kube-system delete po kube-proxy-k7dt6 删除 kube-proxy Pod(每个集群的 Pod 名称可能不同)。
考试技巧 为了节省时间,先输入带有命名空间的 kubectl 命令(例如,k -n kube-system...)。这允许你在键盘上按 Tab 键来自动补全该命名空间内资源的名称(例如,Pods)。这对于名称通常相当长的 Deployments 特别有用。它还可以防止输入错误,这会浪费你的时间,并在考试中可能让你失去一些分数。
我们可以使用命令 k -n kube-system get po 或 k get po -A 列出 kube-system 命名空间中的 Pods。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system get po
NAME READY STATUS
➥ RESTARTS AGE
coredns-565d847f94-75lsz 1/1 Running 3
➥ (120m ago) 7d
coredns-565d847f94-8stkp 1/1 Running 3
➥ (120m ago) 7d
etcd-kind-control-plane 1/1 Running 4
➥ (120m ago) 7d
kindnet-b9f9r 1/1 Running 22
➥ (120m ago) 7d
kube-apiserver-kind-control-plane 1/1 Running 1
➥ (120m ago) 3d18h
kube-controller-manager-kind-control-plane 1/1 Running 8
➥ (120m ago) 7d
kube-proxy-chc4w 0/1 CrashLoopBackOff 7
➥ (40s ago) 11m
kube-scheduler-kind-control-plane 1/1 Running 3
➥ (120m ago) 3d18h
metrics-server-d5589cfb-4ksgb 1/1 Running 2
➥ (120m ago) 3d17h
通过运行此命令,Pod kube-proxy-chc4w处于CrashLoopBackOff状态。请注意,Pod 名称对于每个集群都是不同的;因此,kube-proxy-后面的字符对于你来说将是不同的。让我们运行命令k -n kube-system logs kube-proxy-chc4w来查看日志并找出 Pod 失败的原因。此命令的输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system logs kube-proxy-chc4w
E1211 17:17:07.621228 1 server.go:500] "Error running ProxyServer"
➥ err="stat /var/lib/kube-proxy/kubeconfigd.conf: no such file or directory"
E1211 17:17:07.621298 1 run.go:74] "command failed" err="stat
➥ /var/lib/kube-proxy/kubeconfigd.conf: no such file or directory"
从这个输出中可以得到很多有用的信息——特别是/var/lib/kube-proxy/kubeconfigd.conf: no such file or directory这一行,它告诉我们 kube-proxy 的配置存在问题。我们知道这是因为以.conf结尾的文件通常与 Linux 系统上的配置文件相对应。此外,请注意kubeconfig这个词的末尾有一个d。为了双重检查这是否正确,请在 Kubernetes 文档(kubernetes.io/docs)中搜索;它会自动更正为“你是指 kubeconfig.conf 吗?”
在阅读了第六章之后,我们知道 kube-proxy 的配置存储在kube-system命名空间下的 ConfigMap 中。在 Vim 中打开它,看看是否能找到匹配/var/lib/kube-proxy/kubeconfigd.conf的行。很可能会在这个文件中需要做出修改,因此我们可以使用命令k -n kube-system edit cm kube-proxy在 Vim 中打开 ConfigMap 并做出必要的更改。文件的部分内容如图 8.13 所示。我们看到实际上有一行与我们的日志中的一行匹配;将kubeconfigd.conf改为kubeconfig .conf以查看是否可以修复问题。

图 8.13 编辑 kube-proxy 的 ConfigMap,我们注意到客户端连接中应该修改一行。
在我们做出更改并保存文件(:wq)之后,再次删除 kube-proxy Pod,以便 DaemonSet 可以重新创建它并应用我们在 ConfigMap 中刚刚做出的配置更改。为此,我们将执行命令k -n kube-system delete po kube-proxy-chc4w,然后立即使用命令k -n kube-system get po再次列出kube-system命名空间中的 Pod。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system delete po kube-proxy-chc4w
pod "kube-proxy-chc4w" deleted
root@kind-control-plane:/# k -n kube-system get po
NAME READY STATUS RESTARTS
➥ AGE
coredns-565d847f94-75lsz 1/1 Running 3 (158m ago)
➥ 7d1h
coredns-565d847f94-8stkp 1/1 Running 3 (158m ago)
➥ 7d1h
etcd-kind-control-plane 1/1 Running 4 (158m ago)
➥ 7d1h
kindnet-b9f9r 1/1 Running 22 (158m ago)
➥ 7d1h
kube-apiserver-kind-control-plane 1/1 Running 1 (158m ago)
➥ 3d19h
kube-controller-manager-kind-control-plane 1/1 Running 8 (158m ago)
➥ 7d1h
kube-proxy-ddnwz 1/1 Running 0
➥ 6s
kube-scheduler-kind-control-plane 1/1 Running 3 (158m ago)
➥ 3d19h
metrics-server-d5589cfb-4ksgb 1/1 Running 2 (158m ago)
➥ 3d18h
我们注意到 kube-proxy Pod 又处于运行状态。这完成了我们的考试任务,因为 Pod 再次回到运行状态,kube-proxy 的功能也得以恢复。
Kube-proxy 是一个可能在考试中需要故障排除的组件,因为它负责在集群中为 Pod 网络通信创建 iptables 规则(防火墙规则)。kube-proxy Pod 将确保对服务的请求能够到达与该服务关联的底层 Pod。您可以使用 netstat 工具看到 kube-proxy 正在监听和监控网络活动。考试中 netstat 工具已经安装好,但如果您想在您的集群中练习,请运行命令 netstat -plan | grep kube-proxy 以查看 kube-proxy 在集群中活跃并监听的端口。要安装 netstat,请运行命令 apt update; apt install net-tools。输出将类似于以下内容:
root@kind-control-plane:/# netstat -plan | grep kube-proxy
tcp 0 0 127.0.0.1:10249 0.0.0.0:* LISTEN
➥ 17091/kube-proxy
tcp 0 0 172.18.0.2:35430 172.18.0.2:6443
➥ ESTABLISHED 17091/kube-proxy
tcp6 0 0 :::10256 :::* LISTEN
➥ 17091/kube-proxy
Kube-proxy 在 Kubernetes 集群中的每个节点上作为 DaemonSet 运行。您可以通过运行命令 k get ds -A 列出所有命名空间中的 DaemonSet。输出将类似于以下内容:
root@kind-control-plane:~# k get ds -A
NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE
➥ AVAILABLE NODE SELECTOR AGE
kube-system kindnet 1 1 1 1 1
➥ <none> 3d11h
kube-system kube-proxy 1 1 1 1 1
➥ kubernetes.io/os=linux 3d11h
此组件在您首次初始化集群时安装。与其他集群组件不同,它没有清单,因此有些人会感到困惑。此外,CoreDNS 和 kube-proxy 可以通过命令 kubeadm init phase addon all 轻易地重置(即重新创建)。因此,如果在考试中 kube-system 命名空间中的 kube-proxy Pod 出错或处于 CrashLoopBackOff 状态,运行命令 kubeadm init phase addon all 将是一个快速解决问题的方法,因为它会使用默认配置(在集群首次初始化时设置)重新创建 Pod。还会为 kube-proxy 创建一个服务账户和一个 ConfigMap,它们位于 kube-system 命名空间中。您可以使用命令 k -n kube-system get cm,sa | grep kube-proxy 查看它们。输出将类似于以下内容:
root@kind-control-plane:~# k -n kube-system get cm,sa | grep kube-proxy
configmap/kube-proxy 2 3d11h
serviceaccount/kube-proxy 0 3d11h
8.3.2 服务故障排除
对于考试,您可能会遇到一个无法到达底层 Pod 的服务。为了正确地故障排除,请检查 Pod 在服务和部署中通信的标签和端口。例如,一个考试任务可能如下所示。
考试任务:在集群 ik8s 中,名为 kb6656 的命名空间中,运行命令 k apply -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/deploy-and-svc.yaml 以在集群中创建 Deployment 和 Service。这是一个运行在端口 80 上的 NGINX 应用程序,因此请尝试使用 curl 通过服务的 IP 地址和端口来访问应用程序。一旦您意识到无法通过 curl 与应用程序通信,请尝试确定原因。进行必要的更改,使用 curl 访问应用程序并返回到 NGINX 欢迎页面。 |
|---|
我们可以继续使用之前考试任务中使用的相同单节点集群。没有必要创建一个新的 Kubernetes 集群。首先,我们将使用命令 k create ns kb6656 创建命名空间。我们可以使用命令 k config set-context --current --namespace kb6656 切换到该命名空间。然后,我们将运行考试任务中给出的命令。该命令再次是 k apply -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/deploy-and-svc.yaml。这将创建部署和服务,我们可以使用命令 k get deploy,svc 查看它们。输出应该类似于以下内容:
root@kind-control-plane:/# k create ns kb6656
namespace/kb6656 created
root@kind-control-plane:/# k config set-context --current --namespace kb6656
Context "kubernetes-admin@kind" modified.
root@kind-control-plane:/# k get po
No resources found in kb6656 namespace.
root@kind-control-plane:/# k apply -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-
➥ exam/main/ch_08/deploy-and-svc.yaml
deployment.apps/nginx created
service/nginx-svc created
root@kind-control-plane:/# k get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 36s
root@kind-control-plane:/# k get deploy,svc
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1/1 1 1 2m20s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx-svc ClusterIP 10.96.119.24 <none> 3306/TCP
➥ 2m20s
一旦创建了部署和服务,我们就可以使用 curl 命令通过 curl -k http://10.96.119.24 来访问 NGINX 应用程序。输出应该类似于以下内容:
root@kind-control-plane:/# curl -k http://10.96.119.24
curl: (7) Failed to connect to 10.96.119.24 port 80 after 4 ms: Connection
➥ refused
注意它未能连接。你可能已经注意到,当我们列出服务时,端口是 3306,而不是 80。让我们将其更改为 80,看看是否可以解决这个问题。我们可以运行命令 k edit svc nginx-svc 来编辑服务暴露的端口。此命令将在 Vim 文本编辑器中打开 YAML 文件,并允许我们将端口从 3306 更改为 80。我们将保存并退出(:wq),然后再次尝试我们的 curl 命令。命令的输出将类似于以下内容:
root@kind-control-plane:/# k edit svc nginx-svc
service/nginx-svc edited
root@kind-control-plane:/# curl -k http://10.96.119.24
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
更改服务的端口解决了我们的问题。这是考试中你可能会被要求解决的问题之一。检查服务的端口是否与 Pod 的端口匹配(例如,在部署内部运行)。有时,服务和部署之间可能会发生标签不匹配。如果我们再次使用命令 k get svc -o yaml 查看服务,我们会看到选择器被设置为将所有流量导向带有标签 app=nginx 的 Pod。输出看起来类似于图 8.14。

图 8.14 k get svc -o yaml 的输出显示了选择器,它告诉服务将流量导向哪些 Pod。
如果我们使用命令 k get po --show-labels 列出 Pod 标签,我们会看到这个 NGINX 部署中的 Pod 确实有标签 app=nginx。输出将类似于以下内容:
root@kind-control-plane:/# k get po --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-6cdcf8f964-hdwzr 1/1 Running 0 20m app=nginx,pod-
➥ template-hash=6cdcf8f964
8.3.3 故障排除集群通信
除了 Kubernetes 中服务上的端口不正确之外,还有其他需要关注的问题,这需要你了解资源如何在 Kubernetes 中相互通信。一般来说,部署通过服务本身的标签选择器连接到服务。如果服务标签选择器不正确,应用程序将变得不可访问。此外,每个服务至少有一个端点,即 Pod IP 地址。如果没有服务端点,请再次检查标签选择器是否正确,以便服务可以将流量导向正确的部署或 Pod。
考试技巧 要比较正确的标签选择器,以及你的 YAML 语法是否正确,请运行命令 k create deploy nginx -image ngnix -dry-run=client -o yaml,然后运行 k expose deploy nginx。这将节省你的时间,因为你可以直接复制粘贴或轻松比较这两个文件。
摘要
-
要查看 Pod 中的日志,我们可以使用
kubectl命令行工具或查看/var/logs/containers目录。 -
有许多不同的 Pod 状态、日志和事件,可以作为故障排除决策过程的起点。一个 Pod 可能处于运行状态,但仍然无法通过前端访问。
-
我们可以查看集群范围内的事件,以确定错误来源以及集群内与该错误相关的内容。
-
我们可以使用指标服务器监控集群指标,该服务器已经在考试环境中安装。
-
我们可以通过查看
kube-system命名空间来识别我们的控制平面组件的问题。了解每个组件的功能将有助于你找到问题的根源。 -
我们可以通过查看 kube-proxy 来识别网络问题,因为它是集群内 Pod 到 Pod 之间防火墙流量的促进者。我们还可以查看我们的集群中是否安装了 CNI,以确定网络问题。
-
我们可以在考试中的 Service 和 Deployment 上查找正确的端口和标签,这有助于确定我们无法连接到应用程序的根本原因。
9 参加考试
本章涵盖
-
考试最重要的方面
-
准备考试当天
-
Kubernetes 文档综述
-
访问你的免费练习考试
-
访问免费的
kubectl命令速查表
我希望到现在你已经为 CKA 考试做好了准备。通过阅读这本书,你将有机会在 CKA 考试中取得优异成绩。本章致力于回顾本书内容,巩固任何薄弱环节,以便你能够轻松回顾前面的章节,并重新学习你可能已经忘记或需要进一步复习的原则和研究领域。
9.1 考试基础
我们在第一章中介绍了考试的内容,但本章指出了关于考试准备的最重要的话题。它还回顾了你的技术能力要求,因为你将在终端执行命令,并需要在考试中面对特定问题时立即记住要执行的操作。
9.1.1 知识点浓缩
让我们回顾一下到目前为止我们已经覆盖的能力和材料,以便我们能够正确地识别你感觉最强的领域。图 9.1 显示了能力图表。

图 9.1 CKA 考试当天需要回顾的能力
检查你的知识是一个好主意。例如,如果你在工作负载和调度方面感到薄弱,你应该回到第四章和第五章复习扩展、更新和回滚部署、使用 ConfigMaps 和 Secrets、使用请求和限制部署应用程序以及使用模板工具管理 YAML 清单。在这本书的第二章到第八章的顶部,你可以快速查看哪个能力与哪个章节相关,所以我建议你学习这些内容。每个章节开头都有表格,可以帮助你将能力映射到适用的章节。
9.1.2 考试集群
一旦你回顾了考试的能力,你也应该回顾考试集群配置。考试当天,你将连接到六个集群,每个集群将提供从集群到集群切换上下文的特定说明。图 9.2 列出了集群的名称、节点数量以及每个集群使用的 CNI。

图 9.2 用于 CKA 考试测试的每个集群的集群名称、节点和使用的 CNI
如果你真的想取得考试的好成绩,你应该至少设置两个集群,并熟悉在这两个集群之间切换上下文,无论是通过本地使用 kind Kubernetes,还是通过在网页浏览器中使用 killercoda.com。回顾附录 A,了解如何使用 kind Kubernetes 安装多节点集群。再次强调,切换上下文是考试任务说明中提供的一个命令,但将其牢记在心也很重要,因为考试所分配的 2 小时时间非常宝贵。
9.2 章节回顾
以下是对每个章节的快速回顾,从第二章开始。每个子节中将讨论与讨论的章节相关的关键词。这将帮助您复习这些术语,因为它们对于考试很重要。
9.2.1 第二章快速回顾
让我们回顾第二章的要点,因为本章涵盖了集群架构、集群升级、etcd 备份和恢复以及集群管理。进行这次快速回顾的原因是回忆您之前在章节中阅读的信息,并鼓励您在需要时重新阅读章节。
对于考试来说,记住控制平面节点和工作节点之间的区别是很重要的。它们都运行 Pods,但控制平面节点只会运行系统 Pods(即运行 Kubernetes 本身所必需的 Pods)。这些系统 Pods 有时被称为静态 Pods,这意味着它们不由 Kubernetes 调度器管理。
Kubelet 在集群中的每个节点上运行,但没有kubectl命令可以查看 kubelet,因为它本身就是节点上的系统服务的一部分。如果 kubelet 不在运行状态,Pods 将无法运行。查看第 2.1 节了解如何管理 kubelet 以及考试当天将向您展示的可能修复方案。当需要保存集群配置的数据时,它存储在运行在kube-system命名空间中的 Pod 上的etcd 数据存储中。
🮱 污点与容忍—控制平面如何应用污点,以及如何为该污点添加容忍。第 2.1.3 节。
🮱 命名空间—如何定位kube-system命名空间及其内部的 Pods,包括控制器管理器、调度器、API 服务器、kube-proxy、CoreDNS 和 etcd。第 2.1 节。
🮱 静态 Pods—如何通过控制平面节点上/etc/kubernetes/manifests目录中的 YAML 修改系统 Pods。第 2.2.1 节。
🮱 Kubeadm—如何升级 Kubernetes 组件,包括 API 服务器、控制器管理器、调度器、kube-proxy、CoreDNS 和 etcd。第 2.1.1 节。
🮱 Kubelet—Kubelet 作为 Linux 系统服务(一个守护进程)在每个节点上运行的方式。第 2.1.4 节。
🮱 Etcd—集群配置如何存储在 etcd 数据存储中,以及如何使用 etcdctl 命令行工具通过快照备份 etcd。第 2.2 节。
9.2.2 第三章快速回顾
让我们回顾第三章的要点,因为本章涵盖了基于角色的访问控制(RBAC)的管理以及集群的高可用性管理。如果在这次快速回顾中您有任何不确定的地方,请重新阅读第三章以进行更深入的复习。
对于第三章,记住用户和服务账户如何通过 Kubernetes API 访问集群是很重要的。任何试图访问集群的东西都会通过 RBAC 进行,以潜在地创建、读取、更新或删除 Kubernetes 对象。
第 3.1 节涵盖了“普通”用户和服务帐户,它们之间的区别,以及我们如何将角色和角色绑定应用于用户以限制权限。集群角色和集群角色绑定也可以应用于用户、组或服务帐户。请分别在第 3.2 节和第 3.3 节中回顾创建用户和服务帐户的内容。
🮱 kubectl—如何使用kubectl通过 kubeconfig 与集群交互(将/etc/kubernetes/admin.conf复制到~/.kube/config)。第 3.1 节。
🮱 RBAC—如何通过 RBAC 访问集群,以及认证、授权和访问控制的过程。第 3.1 节。
🮱 服务帐户—如何将服务帐户挂载到 Pod 上,以便 Pod 可以访问 API。第 3.3 节。
🮱 集群角色—如何赋予集群范围内的权限,以及如何将这些权限与用户、组或服务帐户关联。第 3.1.1 节。
🮱 角色—如何在命名空间内赋予权限,并将这些权限与位于给定命名空间内的用户、组或服务帐户关联。第 3.1.1 节。
9.2.3 第四章的快速回顾
让我们回顾第四章的要点,因为本章涵盖了 ConfigMaps;Secrets;资源限制;以及在 Kubernetes 中创建和管理 Deployments,包括使用模板工具。从本章中,重要的是要记住 Pod 是如何调度到节点的。
第四章介绍了通过节点选择器将 Pod 调度到特定节点的过程,包括在您的集群中应用节点选择器的练习,这包括对节点应用标签;然而,您也可以使用节点名称。您可以使用命令k get no -show-labels轻松检查应用到了您的节点上的标签。了解如何使用节点亲和性调度 Pod——即使选定的节点不可用——也是本章涵盖的内容。如果您想将 Pod 调度到已经运行了特定 Pod 的节点上怎么办?这被称为Pod 亲和性,并在第 4.1.2 节中介绍。
使用Helm是本考试的技能之一。Helm 是一种将包含不同 Kubernetes 资源的应用程序打包并使用一条命令通过 Helm 命令行部署的简单方法。在第 4.2 节中使用了在集群内部署metallb应用程序的示例,以及使用 Helm 安装vault应用程序。在第 4.2 节中,您还可以回顾如何添加 Helm 仓库,以及如何搜索现有仓库并将模板应用于与 Helm 一起使用的清单。
在你的集群内部节点上运行 Pod 时应用资源请求和限制对于考试来说很重要。你可以在第 4.3.1 节中看到这个示例,你可以在第 4.3.2 节中回顾如何在单个 Pod 内创建多个容器。ConfigMaps 和 Secrets 在第 4.3.3 节中介绍,该节描述了为 Redis Pod 创建 ConfigMap 以及为通过卷挂载附加到 Pod 的busybox Pod 创建 Secret。你应该通过阅读本章熟悉创建 ConfigMaps 和 Secrets 以及两种不同的使用方式。
🮱 节点选择器—如何使用节点选择器将 Pod 调度到特定的节点。第 4.1.1 节。
🮱 节点名称—如何通过指定节点名称在节点上运行 Pod,并且只在该节点上运行该 Pod。第 4.1.1 节。
🮱 节点亲和性—如何通过特定的标签配置 Pod 在节点上运行,如果节点不可用,仍然在不同的节点上运行 Pod,该节点具有不同的特性(例如,操作系统)。第 4.1.2 节。
🮱 ConfigMaps—如何为 Redis Pod 创建 ConfigMap 并通过卷挂载将其附加到 Pod。第 4.3.3 节。
🮱 Secrets—如何通过卷挂载将 Secret 附加到 Pod。第 4.3.3 节。
🮱 资源限制—如何将资源请求和限制应用到 Pod。第 4.3.1 节。
🮱 Helm—如何使用 Helm 图表部署应用程序,首先在本地搜索 Helm 仓库,如果需要,从仓库添加 Helm 仓库,并应用模板来定制图表。第 4.2 节。
9.2.4 第五章的快速回顾
让我们回顾第五章的要点,因为本章涵盖了如何扩展应用程序以及推出应用程序新版本的含义,以及如何通过管理部署在 Kubernetes 中实现自我修复。在许多方面,这章是前一章的延续;现在你已经知道如何创建 Deployment,第五章展示了部署运行后的维护方法。
在 Kubernetes 中,当我们想要为我们的应用程序添加更多 Pod 以实现冗余时,我们称之为扩展应用程序,因为它通过同一 Deployment 内的 Pod 数量进行扩展。通过滚动更新的概念,在 Kubernetes 中更新运行中的应用程序变得简单。通常,有一个服务附加到 Deployment 上,本章简要介绍了这一点,但你应该探索第六章以获得对服务的全面了解。
🮱 规模部署—如何调整 Deployment 副本的数量,改变给定 Deployment 中的 Pod 数量。第 5.1.1 节。
🮱 副本集—更改 Deployment 副本如何影响副本集,跟踪 Deployment 每个修订版本的 Pod 副本数量。第 5.1.2 节。
🮱 滚动更新—如何执行滚动更新,检查滚动历史,列出修订版本。第 5.1.3 节。
🮱 隔离和排空—如何将节点关闭以进行维护,这包括将那个节点的所有 Pod 移动到另一个节点。第 5.2 节。
🮱 添加节点—如何向现有集群添加新节点。第 5.2.2 节。
9.2.5 第六章快速回顾
让我们回顾第六章的要点,因为本章涵盖了 Kubernetes 集群内的网络,理解 DNS 以及 Pod 之间的通信,Kubernetes 中不同类型的 Service,以及容器网络接口插件。
在第 6.1 节中,介绍了集群 DNS 的概念,并在一个演示更改集群 DNS 配置的练习中进行了覆盖,包括 DNS 服务、ConfigMap 和 kubelet 系统服务配置。正如本章所述,CoreDNS 背后的魔法是能够非常快速地将主机名解析为 IP 地址,减少应用程序和运行在 Kubernetes 集群内部组件之间的延迟。这就是为什么在集群中作为 Deployment 运行多个 CoreDNS 实例的原因。
回顾外部世界如何通过 Ingress 访问运行在 Kubernetes 中的应用程序。我强烈建议重新阅读安装 Ingress 控制器、创建 ClusterIP Service 和 Ingress 资源的练习。这将是对考试有益的实践。回顾关于 Service 类型(包括 ClusterIP、NodePort 和 LoadBalancer Service)的更多细节。
🮱 CoreDNS—如何在 Kubernetes 中解析 DNS 名称,以及如何更改 DNS 服务器配置。第 6.1 节。
🮱 Pod 通信—如何在 Kubernetes 集群中从 Pod 到 Pod 以及从 Service 到 Service 进行通信。第 6.2.3 节。
🮱 Ingress—如何将一组 Pod(在 Service 内部)暴露给 Kubernetes 集群外部的端点,这包括创建 Ingress 控制器。第 6.3 节。
🮱 Service—如何创建和使用 ClusterIP、NodePort 和 LoadBalancer Service 类型。第 6.4 节。
9.2.6 第七章快速回顾
让我们回顾第七章的要点,因为本章涵盖了在 Kubernetes 中配置存储的多种方式,包括持久卷的概念,以及卷模式、访问模式和回收策略。本章还介绍了存储类和持久卷声明,以便于从运行在 Kubernetes 中的应用程序中轻松利用存储,提供了对这些卷的额外控制,包括哪些 Pod 或 Pods 可以访问卷以及卷的类型,如ReadWriteOnce、ReadWriteMany等,以及卷的文件系统类型与块类型。持久卷声明为 Pod 使用保留了任何类型的卷。存储类用于从特定类别的卷类型(例如,本地或云存储)自动配置卷。本章还进一步提供了为 Pod 创建临时卷的理由,用于创建emptyDir卷类型。
🮱 持久卷—如何在 Pod 内部创建持久卷和持久卷声明。第 7.1 节。
🮱 卷模式—如何在 Kubernetes 的持久卷中设置卷模式为Filesystem或Block。第 7.1.2 节。
🮱 访问模式—如何在 Kubernetes 中给 Pods 授予对持久卷的读写权限。第 7.1.3 节。
🮱 存储类—如何通过持久卷声明自动预配卷,它为 Pod 动态创建存储。第 7.3 节。
🮱 emptyDir—如何使用emptyDir卷类型为你的 Pod 创建临时存储,它随着 Pod 的创建和销毁而存在。第 7.4 节。
9.2.7 第八章快速回顾
让我们回顾第八章的要点,因为本章涵盖了在遇到集群、Pods、服务等问题时的许多故障排除方面。CKA 考试将测试你从运行在 Kubernetes 集群中的 Pods 获取日志的能力。
按照图 8.7 中的决策树进行操作,因为它是在有很多不同类型的错误导致你进入不同的故障排除路径时确定要做什么的好起点。Pod 的状态可能是解决特定问题的线索。在表 8.1 中,有八个不同的 Pod 状态需要审查,以及它们的含义。
控制平面本身可能存在问题—无论是 API 服务器还是控制器管理器,都可能存在配置错误。Pods,无论是系统 Pods 还是应用 Pods,都不是唯一会失败的组件;节点本身有时也会引导你进入故障排除路径。
我们在第三章中讨论了访问集群,如果你无法通过 kubeconfig 正确认证到集群,可能会出现问题。关于 Kubernetes 服务,无论是 ClusterIP、NodePort 还是 LoadBalancer 服务类型,考试中可能会有标签输入错误或端口缺失的地方。
🮱 Pod 日志—如何查看 Pod 日志以排查容器化应用程序中预期失败的原因。第 8.1 节。
🮱 网络故障排除—如何利用临时 Pod 来排查 Pod 到 Pod 的 DNS 问题。第 8.1.2 节。
🮱 调度器故障—如何排查和修复 Kubernetes 调度器的问题。第 8.2 节。
🮱 事件—如何分析集群内的整体事件日志,让你能够更深入地了解任何问题。第 8.2.1 节。
🮱 故障排除 kubeconfig—如何确定 kubeconfig 是否是你无法访问集群的原因,以及如何修复它。第 8.2.4 节。
🮱 服务连接问题—如何确定通信没有到达底层的 Pods,以及如何检查服务的端点。第 8.3.1 节。
9.3 Kubernetes 文档回顾
你可以在考试时打开文档的另一个标签页,为什么不充分利用这个机会呢?更具体地说,包含大量知识的 URL kubernetes.io/docs,可以在考试期间打开,而且最好的部分是它可搜索。首先,请查看网站地图,这将为你提供一个工作框架,尽管页面可能命名相似,但你仍然可以熟悉示例 YAML 文件的位置以及资源名称——如此熟悉以至于你将能够识别页面并直接滚动到特定部分,这样你就不需要在考试上浪费时间。记住,你只有 2 个小时,所以要明智地使用它。你可以在图 9.3 中查看网站地图。

图 9.3 Kubernetes.io 网站地图与 CKA 考试相关
当你查看网站地图时,你将开始将每一章中讨论的项目与相关的文档页面联系起来。页面间的导航将位于页面左侧。一旦你打开一个页面,你将看到另一个位于右侧的导航,它将带你到现有文档中的每个部分。网站和页面导航的示例如图 9.4 所示。

图 9.4 导航 Kubernetes 文档以帮助你在考试中
你还会在 Kubernetes 文档中看到一种在整个网站上搜索的方法。这对于考试来说非常有用,因为你可以搜索特定项目,而不是逐个链接导航。请注意,在搜索 Kubernetes 文档页面时,你可能会得到搜索结果,这些结果会带你到完全不同的网站,而你是不被允许访问的。你可以悬停在链接上,看看它将带你去哪里。如果它不包含前缀 kubernetes.io/docs,那么不要点击它。不过,如果你点击了,不用担心;浏览器不会允许你离开,它只会显示该网站似乎不存在。我在考试中就是这样做的,并意识到由于环境是受控的(考试是在虚拟机内部进行的),可以实施这样的防火墙规则。此外,还要利用 Firefox 中的“在页面中查找”功能,这是你将在考试中使用的浏览器。它允许你快速搜索整个页面,这样你就不必花费额外的时间使用右侧的页面导航链接。
9.4 实践考试
每次购买考试都会附赠一份由 KLLR SHLL 提供的免费练习考试券。请访问他们的网站killer.sh。当您使用 Linux Foundation 的凭证(与您在training.linuxfoundation.org/购买考试时使用的相同凭证)登录时,考试模拟器将自动出现在您的仪表板上。您将在同一模拟器中进行两次会话(包括 PSI 桥接的模拟);两次都有相同的问题。我建议将其视为真正的考试,因为它以相同的方式呈现,并将为考试日做好准备。此外,模拟器上的问题比真实考试上的问题更难,这将使真实考试看起来容易一些。有关考试模拟器的常见问题,请访问killer.sh/faq。图 9.5 显示了考试环境的样子,它与真实考试非常相似。

图 9.5 KLLR SHLL 提供的 CKA 考试模拟器环境
9.5 考试额外提示
您的练习越多,在考试中的表现就越好。多次完成这本书中的所有练习,这是将其巩固在记忆中的唯一方法。KLLR SHLL 有一个名为 Killercoda 的姐妹公司(killercoda.com),该公司有更多练习和类似考试的作业来完成。您还可以通过创建自己的场景来补充,这是重复和吸收概念的最佳方式。
请准备好您的身份证明,并准备好在您参加考试的房间中遵守相对严格的清洁规则。有关更多信息以及您可以在考试期间携带的物品的指南,请参阅mng.bz/d1vw,您还可以在此提前进行硬件兼容性检查。我强烈建议在考试日期前几天做这件事。准备好考试监考员要求您在房间内移动摄像头以验证您是否遵守了这些规则。在考试当天,大约在您的考试开始前 30 分钟登录 Linux Foundation 门户。您可以通过trainingportal.linuxfoundation.org/门户访问 Linux Foundation,您将看到您已安排的所有考试。
您的考试成绩将在考试完成后 24 小时内通过电子邮件发送给您。请记住,您有一次免费重考的机会,所以如果您第一次没有通过,请不要担心。如果您愿意,您可以在第二天回来安排重考。祝您好运!
感谢您购买此书并允许我引导您通过认证 Kubernetes 管理员考试的最佳尝试。真正让我高兴的是能在这段旅程中帮助您。
摘要
在本章中,你已经回顾了这本书中的章节,并且还得到了一些关于考试临近时如何准备的额外建议。记住以下几点:
-
回顾章节要点,真正掌握内容。确保你为所有能力域内可能出现的各种情况做好准备。
-
为了正确准备 CKA 考试,你必须反复练习,练习,然后再多练习一些。让它成为考试前的日常习惯。
-
像专业人士一样回顾官方 Kubernetes 文档,以充分利用考试中额外的开卷格式。
-
你将在考试中与多个集群一起工作。尽管提供了
kubectl命令来切换上下文,但请确保你能够舒适地检查你正在工作的正确集群,这是考试所要求的。 -
你有两个练习考试,所以明智地使用它们。它们将挑战你完成更困难的任务,并帮助你熟悉考试环境。
-
检查手册以及所有关于你的物理环境应该如何看起来规则,并提前准备好一切。
-
你在考试中有一次免费重考的机会,所以不要担心!你可以做到的!
附录 A:使用 kind 创建 Kubernetes 集群
本附录展示了如何使用 Docker 容器安装具有多个节点的 kind(Kubernetes in Docker)Kubernetes 集群,其中节点本身是 Docker 容器。你将下载并安装 kind,它将使用 kubeadm 工具(包含在 kind 中)来引导你的集群。本节的唯一先决条件是 Docker 和 kind,我们将一起安装它们。
A.1 安装所需的软件包
正如之前提到的,创建 kind Kubernetes 集群的唯一先决条件是 Docker 和 kind。Docker Desktop 是 Docker 公司的一系列软件产品,它使得构建、运行和共享容器化应用程序和微服务变得更加容易。如果你还没有安装 Docker Desktop,你可以访问以下链接下载它:docker.com/products/docker-desktop。Docker Desktop 可用于 Windows、macOS 和 Linux 操作系统。你可以使用操作系统的软件包管理器安装 kind,或者你也可以从这里下载 kind:github.com/kubernetes-sigs/kind/releases。Kind 也适用于 Windows、macOS 或 Linux。在本指南中,我们将使用版本 0.14.0。
A.1.1 安装 Docker Desktop
Docker Desktop 将作为我们使用 kind 工具构建的此实践集群的容器运行时。在第一章,我解释了容器运行时的作用,即作为在 Pod 内运行底层容器的系统服务。从现在开始,我将只称 Docker Desktop 为 Docker。请注意,我指的是 Windows、macOS 或 Linux 的容器运行时。以下是安装 Docker 到这些操作系统的说明:
-
打开网页浏览器并访问
www.docker.com/products/docker-desktop。 -
下载与你的操作系统匹配的安装程序。你可能还需要选择芯片架构(例如,macOS 的 M1 或 Intel 芯片)。
-
运行安装程序(.dmg、.exe、.deb 或 .rpm 文件扩展名)并接受所有默认设置。(你可能需要重新启动你的计算机。)
-
打开应用程序,你应该在任务栏中看到鲸鱼图标。
-
如果你看到表示 Docker 正在运行的鲸鱼图标,那么你就完成了!
注意:如果你在 Windows 上并且已经安装了 Docker,请确保你处于 Linux 容器模式。对于新安装,这是默认设置。
安装 Docker 后,你可以通过在终端(Docker Desktop 套件中的工具之一)中使用 Docker CLI 来验证它是否已准备好使用:打开终端或命令提示符并输入 docker ps:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
如果你得到这样的输出但没有错误,那么你已成功安装 Docker Desktop。命令 docker ps 列出所有正在运行的容器。如果你想知道为什么我们没有看到任何正在运行的容器,那是因为我们还没有创建任何容器。我们将在安装 kind 的下一步中这样做。
A.1.2 安装 kind
如果你认为安装 Docker 很容易,那么准备好被震撼吧!我们将只用一个命令来安装 kind。macOS 的命令是 brew install kind,Windows 的命令是 choco install kind,Linux 的命令是 curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.11.1/kind-linux-amd64 && chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind。
现在,在终端中,只需输入 kind,你将看到以下内容:
$ kind
kind creates and manages local Kubernetes clusters using Docker container
➥ 'nodes'
Usage:
kind [command]
Available Commands:
build Build one of [node-image]
completion Output shell completion code for the specified shell (bash,
➥ zsh or fish)
create Creates one of [cluster]
delete Deletes one of [cluster]
export Exports one of [kubeconfig, logs]
get Gets one of [clusters, nodes, kubeconfig]
help Help about any command
load Loads images into nodes
version Prints the kind CLI version
Flags:
-h, --help help for kind
--loglevel string DEPRECATED: see -v instead
-q, --quiet silence all stderr output
-v, --verbosity int32 info log verbosity
--version version for kind
Use "kind [command] --help" for more information about a command.
根据 kind 帮助的输出,你可以获得创建第一个集群的提示。
A.1.3 创建 kind Kubernetes 集群
输入以下命令以创建(启动)一个 kind Kubernetes 集群:kind create cluster。你的输出应该看起来像这样:
$ kind create cluster
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.21.1) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Have a nice day! 👋
就这些!祝贺你成功启动你的 Kubernetes 集群!现在,我们已经使用 kind 创建了一个 Kubernetes 集群,我们可以方便地使用 kubectl 工具,就像我们连接到以任何其他方式创建的其他 Kubernetes 集群一样。实际上,当我们创建一个 kind 集群时,kind 会自动将我们的 kubeconfig 上下文设置为它刚刚创建的集群(kind-kind)。首先,使用命令 docker exec -it kind-control-plane bash 进入我们的控制平面节点所在的 Docker 容器,然后使用命令 kubectl config get-contexts 查看你所在的上下文。

图 A.1 显示当前上下文,以查看你当前正在访问哪个集群。
A.1.4 安装 kubectl(可选)
kubectl 是你在考试期间将用于与 Kubernetes 交互的工具。它之所以是可选的,是因为你将要执行的 Docker 容器已经预装了 kubectl。如果你不想在容器内运行命令,那么请继续在你的本地机器上安装 kubectl。
kubectl 有许多发音。我听过它被读作“cube-cuttle”和“cube-eck-tell”。我读作“cube-C-T-L”,但“官方”的发音是“cube-control”。你应该问一个 Kubernetes 管理员的第一件事是,“你是怎么发音 kubectl 的?”(当然是在开玩笑);这将是一个很好的破冰方式,也是和朋友们谈论 Kubernetes 的有趣方式。
kubectl 工具可以在 Linux、macOS 和 Windows 上安装。我将在本书中指导你安装 kubectl,你也可以参考官方安装说明:kubernetes.io/docs/tasks/tools/install-kubectl。
对于 macOS,你可以使用 brew 来安装 kubectl。Brew 是 macOS 的包管理器,类似于 Linux 中的 apt 或 yum。如果你还没有安装 brew,你可以通过访问 brew.sh 来安装它。一旦 brew 安装完成,你只需运行命令 brew install kubectl 即可。`
对于 Windows,你可以使用名为 Chocolatey 的 Windows 包管理器。如果你还没有安装 Chocolatey,请访问 chocolatey.org/install。一旦你安装了 Chocolatey,你只需运行命令 choco install kubernetes-cli 即可。`
对于 Linux,你可以运行以下命令,该命令将下载二进制文件并将其移动到你的 /usr/local/bin 目录:curl -LO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && sudo chmod +x kubectl && sudo mv kubectl /usr/local/bin/kubectl.
现在你已经安装了 kubectl,你可以使用命令 kubectl version --client –short 来验证它是否已安装:
$ kubectl version --client --short
Client Version: v1.23.1
恭喜!你已经安装了 kubectl 并可以继续与你的 kind Kubernetes 集群进行交互。
A.2 创建多节点集群
现在,我们可以创建一个多节点集群来模拟考试环境,帮助你更熟悉创建、访问和故障排除每个节点上的组件,无论是控制平面还是工作节点。这将与 CKA 考试相似,考试将主要有一个控制平面和两个工作节点。以防将来有所变化,你可以在这里访问考试文档:docs.linuxfoundation.org/tc-docs/certification/tips-cka-and-ckad#cka-and-ckad-environment。
首先,我们必须删除我们之前创建的 kind 集群,所以让我们用命令 kind delete cluster 来做这件事。这将删除我们迄今为止一直在使用的集群,并允许我们从头开始创建一个全新的、三节点的集群。
为了执行三个节点的集群配置,我们将使用 kind create cluster 命令,并添加配置参数和一个配置文件(也用 YAML 编写),该文件指定要创建多少个节点。让我们创建这个集群配置文件,并将其命名为 config.yaml,通过将以下内容复制粘贴到你的命令行(仅限 macOS 和 Linux):
cat << EOF | tee config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
EOF
如果你使用的是 Windows,可以使用以下命令来创建三个节点的 kind 集群:
echo "kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker" | tee .\config.yaml
现在我们有了 kind 配置,我们可以将这个配置传递给 kind create cluster 命令,并创建一个包含一个控制平面节点和两个工作节点的集群。通过输入命令 kind create cluster --config config.yaml 来完成此操作:
$ kind create cluster --config config.yaml
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.21.1) 🖼
✓ Preparing nodes 📦 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Have a question, bug, or feature request? Let us know!
➥ https://kind.sigs.k8s.io/#community 🙂
一旦集群创建完成,我们可以使用命令 kubectl get no 来验证我们现在总共有三个节点:
$ kubectl get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 98s v1.21.1
kind-worker Ready <none> 70s v1.21.1
kind-worker2 Ready <none> 70s v1.21.1

图 A.2:由 kind 构建的三个节点 Kubernetes 集群将系统 Pod 和应用 Pod 分开。
恭喜,你现在已经创建了一个三节点 Kubernetes 集群!
A.3 kind 的高级配置
为了跟随本书中的某些示例,你需要设置一个具有更高级配置的 kind 集群。以下示例创建了一个带有节点标签和端口 80 暴露的单节点 kind 集群。我们创建一个 config2.yaml 文件,类似于我们在上一节中创建的方式,但我们添加了一些额外的行。让我们通过在终端中复制粘贴以下内容来创建名为 config2.yaml 的集群配置文件(仅适用于 macOS 和 Linux):
cat << EOF | tee config2.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 80
labels:
ingress-ready: true
EOF
如果你在 Windows 上,请使用以下命令创建 config2.yaml 文件:
echo "kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 80
labels:
ingress-ready:true" | tee .\config.yaml
现在我们已经创建了 config2.yaml 文件,就像我们在上一节中做的那样,我们可以将这个文件传递给 kind create cluster 命令,并使用那些额外的节点标签和端口创建我们的集群。让我们运行命令 kind create cluster --config config2.yaml:。
$ kind create cluster --config config2.yaml
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.21.1) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
一旦集群创建完成,我们可以使用命令 kubectl get no --show-labels 验证我们现在在节点上有一个标签。我们还可以使用命令 docker port kind-control-plane 验证节点上的端口是否已暴露。你应该得到类似以下的输出:
$ kubectl get no --show-labels && docker port kind-control-plane [20:03:30]
NAME STATUS ROLES AGE VERSION LABELS
kind-control-plane Ready control-plane 105s v1.27.0 beta.kubernetes.io/
arch=amd64,beta.kubernetes.io/os=linux,ingress-ready=true,kubernetes.io/
arch=amd64,kubernetes.io/hostname=kind-control-plane,kubernetes.io/
os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/
exclude-from-external-load-balancers=
6443/tcp -> 127.0.0.1:55726
80/tcp -> 0.0.0.0:80
我们现在有一个单节点集群,它可以接受通过端口 80 的流量,并且 Pod 可以根据其标签 "ingress-ready=true" 选择节点。
注意:如果你已经有一个 kind 集群正在运行,要创建第二个集群,你必须给新集群一个不同于 kind 的名字。为了给你的 kind 集群起一个自定义的名字,请在 kind create 命令的末尾添加 --name。例如,如果我想将我的集群命名为 cka 并使用 config2.yaml 文件,我会运行命令 kind create cluster --config config2.yaml --name cka。
附录 B. 为 kind 集群设置上下文
本附录向您展示如何在具有多个 kubeconfig 文件的 Kubernetes 集群中设置上下文。您将学习如何使用 kubectl config 确定您当前所在的上下文以及如何切换到不同的上下文,这应该有助于您在 CKA 考试中舒适地访问多个集群。
B.1 使用 kubeconfig 设置上下文
正如您在第三章中学到的,您可以有多个与不同集群相关的 kubeconfig 文件,或者您可以将所有集群访问信息合并到一个 kubeconfig 文件中。您现在可以阅读单个 kubeconfig 文件及其内容。此 kubeconfig 文件(命名为 admin.conf)位于 /etc/kubernetes/ 目录中。在引导过程中,该文件被复制,重命名为 config 并放置在 ~/.kube/ 目录中。为什么是这样呢?因为 /etc 是一个系统目录,需要 root 权限才能访问。由于您是以普通用户(而非 root)运行 kubectl 命令,将其复制到您的家目录允许对文件拥有完全所有权。只需运行命令 kind get kubeconfig --name "kind" 就可以查看该 kubeconfig 文件的内容(~/.kube/config)。同样,您可以使用命令 kubectl config view --minify 来查看 kubeconfig 文件的内容。
或者,您可以使用位于不同位置的 kubeconfig 文件,该文件可以通过 KUBECONFIG 环境变量进行设置。如果您再次进入 kind-control-plane 容器中的 shell,您会看到环境变量已经设置。您可以使用命令 echo $KUBECONFIG 来输出环境变量:
$ docker exec -it kind-control-plane bash
root@kind-control-plane:/# echo $KUBECONFIG
/etc/kubernetes/admin.conf
假设您有一个名为 config2 的附加 kubeconfig 文件。此文件位于 ~/Downloads 目录中,但您仍然希望 kubectl 使用它来访问您的 Kubernetes 集群。您可以通过输入 export KUBECONFIG=~/Downloads/config2 来告诉 kubectl 使用该 kubeconfig 文件对集群进行身份验证,从那时起,它将使用该 config2 kubeconfig 文件访问集群。如果您想使用两个不同的 kubeconfig 文件,这两个文件位于不同的目录中,您可以输入 export KUBECONFIG=~/.kube/config:~/Downloads/config2 并使用它们。要访问每个集群,您可以通过使用命令 kubectl config use-context 来切换上下文:
$ export KUBECONFIG=~/Downloads/config2:~/.kube/config
$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
docker-desktop docker-desktop docker-desktop
* k8s k8s k8s
kind-kind kind-kind kind-kind
$ kubectl config use-context kind-kind
Switched to context "kind-kind".
$ kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
docker-desktop docker-desktop docker-desktop
k8s k8s k8s
* kind-kind kind-kind kind-kind
您需要知道如何切换上下文以进行考试;然而,考试说明将告诉您如何操作。考试中将有多个上下文,因为每个问题都包含在多个集群上执行的一个或多个任务。每次考试提示您进行操作以完成给定任务时,您都需要切换到新的上下文。
B.2 为 kubectl 设置别名
你可以为 kubectl 设置一个缩写名称,因为反复输入 kubectl 可能会耗费时间。这被称为别名,最常用的别名是 k。例如,设置别名后,你可以输入 k get no 而不是 kubectl get no。要设置别名,请在控制平面 Bash shell 中输入命令 alias k=kubectl(例如,docker exec -it kind-control-plane bash)。然后输入命令 k get no 以验证别名是否设置正确。输出应类似于以下内容:
root@kind-control-plane:/# k get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 23h v1.25.0-beta.0
如果你想在退出 shell 并重新进入后设置保持设置,你可以将其添加到主目录中的 .bashrc 文件中。一种简单的方法是在 Bash shell 中输入命令 echo 'alias k=kubectl' >> ~/.bashrc 到控制平面节点。然后输入 source ~/.bashrc 以立即实施此设置,或者简单地注销并重新登录。输出应类似于以下内容:
root@cka-control-plane:/# echo 'alias k=kubectl' >> ~/.bashrc
root@cka-control-plane:/# source ~/.bashrc
root@cka-control-plane:/# k get no
NAME STATUS ROLES AGE VERSION
cka-control-plane Ready control-plane 19m v1.25.0-beta.0
B.3 设置 kubectl 自动完成
在考试中设置自动完成功能非常重要,因为 Kubernetes 资源包含复杂名称,容易出错。尽可能使用复制粘贴,并在使用 kubectl 导航命名空间和资源名称时使用自动完成功能。例如,输入 k -n c10 然后按 Tab 键将自动完成命名空间名称为 k -n c103832034。使用自动完成功能可以在考试中节省时间,并有助于防止出错。
在 kind 中设置自动完成参数的问题在于按照以下顺序运行以下命令:
apt update && apt install -y bash-completion
echo 'source <(kubectl completion bash)' >> ~/.bashrc
echo 'source /usr/share/bash-completion/bash_completion' >> ~/.bashrc
echo 'alias k=kubectl' >> ~/.bashrc
echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc
source ~/.bashrc
在命令行中逐个运行这些命令,在控制平面节点(即,docker exec -it kind-control-plane bash)的 shell 中。
附录 C. 在 kind 集群中安装 CNI
本附录展示了如何在你的 kind 集群中安装一个新的 CNI。我们将安装 Flannel 和 Calico,这两个都用于 CKA 考试,我们将逐步说明如何在 kind 集群内完成这个过程。这包括创建一个没有 CNI 的 kind 集群,安装网桥 CNI 插件,然后安装 Flannel 或 Calico。
C.1 不使用 CNI 创建 kind 集群
在我们创建一个新的 Kubernetes 集群之前,我们必须首先创建一个 YAML 文件,我们可以使用这个文件作为 kind create 命令的输入。创建一个名为 config.yaml 的文件,并将内容粘贴如下:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
disableDefaultCNI: true
nodes:
role: control-plane
role: worker
现在,运行命令 kind create cluster --image kindest/node:v1.25.0-beta.0 --config config.yaml 来根据我们在 config.yaml 文件中指定的配置创建一个 kind 集群。输出将类似于以下内容:
$ kind create cluster --image kindest/node:v1.25.0-beta.0 --config
➥ config.yaml
[21:44:59]
Creating cluster "kind" ...
✓ Ensuring node image (kindest/node:v1.25.0-beta.0) 🖼
✓ Preparing nodes 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
Have a question, bug, or feature request? Let us know!
➥ https://kind.sigs.k8s.io/#community 🙂
C.2 安装网桥 CNI 插件
现在我们已经创建了集群,让我们通过命令 docker exec -it kind-control-plane bash 和 docker exec -it kind-worker bash 分别获取到两个节点容器的 shell,一次一个。一旦你有了 Bash shell,就可以在 kind-control-plane 和 kind-worker 上运行命令 apt update; apt install wget。这将安装 wget,这是一个命令行工具,我们可以用它从网络上下载文件,这就是我们将使用 wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz 命令所做的事情。因为这是一个 tarball 文件,你需要使用命令 tar -xvf cni-plugins-linux-amd64-v1.1.1.tgz 来解压它。输出将类似于以下内容:
root@kind-control-plane:/# tar -xvf cni-plugins-linux-amd64-v1.1.1.tgz
./
./macvlan
./static
./vlan
./portmap
./host-local
./vrf
./bridge
./tuning
./firewall
./host-device
./sbr
./loopback
./dhcp
./ptp
./ipvlan
./bandwidth
网桥文件对于我们来说是最重要的,因为它将为 Kubernetes 提供使用 Flannel 作为 CNI 所必需的插件。此文件还需要位于特定的目录中,以便被集群识别。那个目录是 /opt/cni/bin/,因此我们将使用命令 mv bridge /opt/cni/bin/ 将文件 bridge 移动到那个目录。
C.3 安装 Flannel CNI
现在我们已经在 kind-control-plane 和 kind-worker 上安装了网桥 CNI 插件,我们可以在控制平面节点的 shell 内创建 Flannel Kubernetes 对象来安装 Flannel CNI,命令为 kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml。你可以使用命令 kubectl get no 验证节点现在是否处于就绪状态。输出将类似于以下内容:
root@kind-control-plane:/# kubectl get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 16m v1.25.0-beta.0
kind-worker Ready <none> 16m v1.25.0-beta.0
我们也可以使用命令 kubectl get po -A 验证 CoreDNS Pods 是否正在运行,以及 Flannel Pods 是否已创建并正在运行。输出将类似于以下内容:
root@kind-control-plane:/# k get po -A
NAMESPACE NAME READY
➥ STATUS
kube-flannel kube-flannel-ds-d6v6t 1/1
➥ Running
kube-flannel kube-flannel-ds-h7b5v 1/1
➥ Running
kube-system coredns-565d847f94-txdvw 1/1
➥ Running
kube-system coredns-565d847f94-vb4kg 1/1
➥ Running
kube-system etcd-kind-control-plane 1/1
➥ Running
kube-system kube-apiserver-kind-control-plane 1/1
➥ Running
kube-system kube-controller-manager-kind-control-plane 1/1
➥ Running
kube-system kube-proxy-9hsvk 1/1
➥ Running
kube-system kube-proxy-gkvrz 1/1
➥ Running
kube-system kube-scheduler-kind-control-plane 1/1
➥ Running
local-path-storage local-path-provisioner-684f458cdd-8bwkh 1/1
➥ Running
这将完成 Flannel 安装的设置。
C.4 创建一个新的集群类型
安装 Calico CNI 与安装 Flannel 非常相似,只是用于创建 Kubernetes 对象的 YAML 文件不同。因此,再次浏览 C.1 节和 C.2 节,然后从那里继续。如果你已经有一个 kind 集群在运行,你可以执行命令kind delete cluster来删除现有的集群,或者你可以使用命令kind create cluster --image kindest/node:v1.25.0-beta.0 --config config.yaml --name cka在现有集群旁边创建一个名为cka的新集群。你将看到类似于以下输出的内容:
$ kind create cluster --image kindest/node:v1.25.0-beta.0 --config
➥ config.yaml
--name cka
➥ [9:39:37]
Creating cluster "cka" ...
✓ Ensuring node image (kindest/node:v1.25.0-beta.0) 🖼
✓ Preparing nodes 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-cka"
You can now use your cluster with:
kubectl cluster-info --context kind-cka
Thanks for using kind! 😊
如果你选择在已有的集群旁边安装一个新的集群以获取到节点 shell,你必须使用正确的前缀来引用它们(例如,cka)。例如,如果你在跟随教程并选择了cka作为你的集群名称,要获取控制平面节点的 shell,你应该输入docker exec -it cka-control-plane bash。要获取工作节点的 shell,你应该输入docker exec -it cka-worker bash。现在我们已经跟上了进度,让我们继续安装 Calico 作为我们的 CNI。
C.5 安装 Calico CNI
从 C.2 节开始,那里你安装了 Calico CNI 插件,现在让我们安装实现 Calico CNI 所需的 Kubernetes 对象。在你控制平面的 shell 中(例如,docker exec -it cka-control-plane bash),你可以使用命令kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.0/manifests/calico.yaml来创建这些 Kubernetes 对象。你将看到类似于以下输出的内容:
root@cka-control-plane:/# kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
poddisruptionbudget.policy/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
serviceaccount/calico-node created
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.project
➥ calico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.or
➥ g created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/caliconodestatuses.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.
➥ org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico
➥ .org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org
➥ created
customresourcedefinition.apiextensions.k8s.io/ipreservations.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations
➥ .crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.
➥ projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.
➥ projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
daemonset.apps/calico-node created
deployment.apps/calico-kube-controllers created
现在你已经安装了安装 Calico CNI 所需的 Kubernetes 对象,你可以使用命令kubectl get no来验证节点是否处于就绪状态。你将看到类似于以下内容的输出:
root@cka-control-plane:/# kubectl get no
NAME STATUS ROLES AGE VERSION
cka-control-plane Ready control-plane 61m v1.25.0-beta.0
cka-worker Ready <none> 61m v1.25.0-beta.0
你还可以通过使用命令kubectl get po -A看到,CoreDNS Pods 以及 Calico Pods 现在已经在kube-system命名空间中启动并运行。输出将类似于以下内容:
root@cka-control-plane:/# kubectl get po -A
NAMESPACE NAME READY
➥ STATUS
kube-system calico-kube-controllers-58dbc876ff-l9w9t 1/1
➥ Running
kube-system calico-node-g5h7s 1/1
➥ Running
kube-system calico-node-j8g9r 1/1
➥ Running
kube-system coredns-565d847f94-b6jv4 1/1
➥ Running
kube-system coredns-565d847f94-mb554 1/1
➥ Running
kube-system etcd-cka-control-plane 1/1
➥ Running
kube-system kube-apiserver-cka-control-plane 1/1
➥ Running
kube-system kube-controller-manager-cka-control-plane 1/1
➥ Running
kube-system kube-proxy-9ss5r 1/1
➥ Running
kube-system kube-proxy-dlp2x 1/1
➥ Running
kube-system kube-scheduler-cka-control-plane 1/1
➥ Running
local-path-storage local-path-provisioner-684f458cdd-vbskp 1/1
➥ Running
这就完成了 Calico CNI 的安装。
附录 D. 解决考试练习题
本附录将按章节顺序向您介绍解决考试练习题的方法,从第一章开始。这将帮助您进入正确的思维模式,以启动 CKA 考试任务的可能解决方案。如果您已经阅读了本书,解决方案应该是显而易见的,但本附录不会立即向您提供答案,目的是为了帮助您为考试做准备。
D.1 第一章考试练习
本章的考试练习题比其他章节少,因为这是一章介绍性章节。练习题更具探索性,我们将在这里进行回顾。
D.1.1 列出 API 资源
当在集群内部列出 API 资源时,您应该首先考虑使用kubectl命令行工具。这无疑是 easiest 方法。如果您手头不知道命令,您总是可以通过简单地输入kubectl来使用帮助菜单,这将提供一些线索。帮助菜单的输出应该类似于以下内容(已缩写):
root@kind-control-plane:/# kubectl
kubectl controls the Kubernetes cluster manager.
Find more information at: https://kubernetes.io/docs/reference/kubectl/
Basic Commands (Beginner):
create Create a resource from a file or from stdin
run Run a particular image on the cluster
set Set specific features on objects
...
Other Commands:
alpha Commands for features in alpha
api-resources Print the supported API resources on the server
api-versions Print the supported API versions on the server, in the
➥ form of "group/version"
config Modify kubeconfig files
plugin Provides utilities for interacting with plugins
version Print the client and server version information
Usage:
kubectl [flags] [options]
如果您查看帮助菜单中名为“其他命令”的部分,您会看到“打印服务器上支持的 API 资源”的命令是api-resources。因此,解决这个练习的命令是kubectl api-resources。
D.1.2 列出服务
在第 1.7 节中介绍了如何在 Linux 操作系统上列出集群中的服务,这与我们在第七章中讨论的 Kubernetes 服务不同。每当您看到术语Linux、守护进程或系统服务时,您应该考虑节点本身上的系统组件,而在 Kubernetes 的上下文中,服务是完全不同的资源。要列出与 Kubernetes 相关的节点上的服务,我们运行命令systemctl list-unit-files --type service --all,然后我们可以使用 Linux 中的 grep 功能进一步搜索list-unit-files命令的结果。完整的命令将类似于以下内容:
root@kind-control-plane:/# systemctl list-unit-files --type service --all |
➥ grep kube
kubelet.service enabled enabled
系统服务很多,这就是我们为什么使用 grep 功能只列出我们需要的那个,即 kubelet。在 CKA 考试的上下文中,kubelet 是唯一位于节点本身上的系统服务。要将输出发送到名为services.csv的文件中,我们可以在现有命令中添加> services.csv。因此,完整的命令将是systemctl list-unit-files --type service --all | grep kubelet > services.csv。
D.1.3 kubelet 服务的状态
对于这个考试练习,适用的规则与上一个练习相同。kubelet 服务将是节点本身运行的唯一系统服务。当你考虑对 kubelet 服务进行任何操作时,请考虑使用 systemctl。你已经在之前的练习中看到了它的使用。systemctl 是控制 systemd 的标准实用工具,systemd 是控制所有系统服务的总服务。获取任何 systemd 服务状态的命令是 systemctl status。你可以在帮助菜单中找到这个提示,所以如果你在考试中完全忘记了,也不要担心。(让帮助菜单成为你的朋友,使用命令 systemd -h。)因此,完整的命令是 systemctl status kubelet。使用上一次练习中的相同方法,我们将输出到名为 kubelet-status.txt 的文件中,完整的命令是 systemctl status kubelet > kubelet-status.txt。
D.1.4 使用声明式语法
正如我们在第 1.8 节中学到的,声明式语法有助于保留我们的 Kubernetes 配置的历史记录。与命令式不同,命令式是一系列按顺序执行的命令,声明式允许我们定义配置的最终状态,Kubernetes 控制器将使其成为现实。要创建一个包含 Pod 规范的 YAML 文件,我们可以使用 Vim 文本编辑器。
使用命令 vim chap1-pod.yaml 创建并打开文件。一旦文件在 Vim 中打开,你就可以编写 YAML。作为替代方案,并且这是我多次在书中推荐你做的,以节省你在考试中的时间,你可以让 kubectl 命令行为你编写 YAML,使用命令 k run pod --image nginx --dry-run=client -o yaml > chap1-pod.yaml。结果将是以下内容:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
containers:
- image: nginx
name: pod
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
无论你选择哪种方式来完成这个练习都行,但要知道后者是一个我推荐在考试中使用的快捷方式。
D.1.5 列出 Kubernetes 服务
这是我们区分系统服务(作为节点上 Linux 的一部分)和我们所说的 Kubernetes 服务的地方。正如练习中提到的,列出你在 Kubernetes 集群中创建的服务,其中“在 Kubernetes 集群中创建”是引导你使用 kubectl 工具而不是 systemctl 工具的关键词。
要列出服务,我们可以使用 kubectl 帮助菜单来找到正确的命令。就像许多其他会列出 Kubernetes 资源的操作一样,将其视为对 API 的 GET 请求。我们正在列出 API 中的内容,并且使用 kubectl 工具来做这件事。我们列出集群中所有内容的方法是附加 --all-namespaces 或简写为 -A。因此,完整的命令是 kubectl get svc -A,或者它也可能是另一个答案,即 kubectl get services --all-namespaces。输出将类似于以下内容:
root@kind-control-plane:~# kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP
➥ PORT(S) AGE
default kubernetes ClusterIP 10.96.0.1 <none>
➥ 443/TCP 56d
kube-system kube-dns ClusterIP 10.96.0.10 <none>
➥ 53/UDP,53/TCP,9153/TCP 56d
D.2 第二章考试练习
这些练习题位于第二章的末尾。练习题的数量与上一章大致相同,因此我们将以相同的目的进行练习。
D.2.1 缩短 kubectl 命令
我们在第一章中简要地讨论了这个问题,但在考试中别名已经为您设置好了。这是一个您需要为将要使用的本地集群(以及您在工作中将要使用的集群)进行练习的练习。设置别名相当直接;然而,有两种方法可以做到这一点,因为执行命令alias k=kubectl是正确的;这将在您退出当前的 Bash 会话时重置。为了使其持久,我们可以使用命令echo "alias k=kubectl" >> ~/.bashrc将其添加到您的 Bash 配置文件中。这是一个更持久的命令,因为您可以注销并重新登录到您的机器,别名将保持不变。
D.2.2 列出正在运行的 Pod
我们已经到达了将要使用kubectl命令行工具的阶段,您应该非常熟悉这个工具。您将在尝试解决考试中的几乎所有任务时使用它。要列出kube-system命名空间中运行的 Pod,您应该想到关键字get,因为它是kubectl(或别名k)在列出大多数 Kubernetes 资源后常见的单词。此外,这个练习要求我们显示 Pod IP 地址,因此我们正在寻找详细输出。在显示 Pod 和节点的 IP 地址时,考虑“输出宽”。当您将这些全部组合起来,完整的命令是k get po -n kube-system -o wide。输出应该看起来类似于以下内容:
root@kind-control-plane:~# k get po -n kube-system -o wide
NAME READY STATUS RESTARTS
➥ AGE IP NODE
coredns-565d847f94-75lsz 1/1 Running 7 (28h ago)
➥ 56d 10.244.0.11 kind-control-plane
coredns-565d847f94-8stkp 1/1 Running 7 (28h ago)
➥ 56d 10.244.0.7 kind-control-plane
etcd-kind-control-plane 1/1 Running 8 (28h ago)
➥ 56d 172.18.0.2 kind-control-plane
kindnet-b9f9r 1/1 Running 27 (28h ago)
➥ 56d 172.18.0.2 kind-control-plane
kube-apiserver-kind-control-plane 1/1 Running 5 (28h ago)
➥ 52d 172.18.0.2 kind-control-plane
kube-controller-manager-kind-control-plane 1/1 Running 19 (67m ago)
➥ 56d 172.18.0.2 kind-control-plane
kube-proxy-ddnwz 1/1 Running 4 (28h ago)
➥ 49d 172.18.0.2 kind-control-plane
kube-scheduler-kind-control-plane 1/1 Running 14 (67m ago)
➥ 52d 172.18.0.2 kind-control-plane
metrics-server-d5589cfb-4ksgb 1/1 Running 8 (28h ago)
➥ 52d 10.244.0.10 kind-control-plane
现在您已经得到了输出结果,您可以将它保存到名为pod-ip-output.txt的文件中,使用命令k get po -n kube-system -o wide > pod-ip-output.txt。
D.2.3 查看 kubelet 客户端证书
我们在本章中了解到,Kubernetes 中的每个组件都有一个证书授权机构以及客户端证书或服务器证书。这就是客户端-服务器模型的工作方式,最常见的一个例子就是万维网。
当您想到证书时,您应该总是想到/etc/kubernetes目录,因为它存储了所有的证书文件和配置。无论是位于/etc/kubernetes/pki还是/etc/kubernetes/pki/etcd,您都会找到适当的证书文件,因为它们都有适当的标签。在这种情况下,从控制平面节点,我们可以使用命令cd /etc/Kubernetes/pki来更改目录。如果我们使用ls命令列出内容,我们会得到以下类似的输出:
root@kind-control-plane:/# cd /etc/kubernetes/pki
root@kind-control-plane:/etc/kubernetes/pki# ls
apiserver-etcd-client.crt apiserver-kubelet-client.crt apiserver.crt
➥ ca.crt etcd front-proxy-ca.key front-proxy-
➥ client.key sa.pub
apiserver-etcd-client.key apiserver-kubelet-client.key apiserver.key
➥ ca.key front-proxy-ca.crt front-proxy-client.crt sa.key
结果,正如您能解读的,是 kubelet 客户端证书的位置,您可以使用命令echo “/etc/kubernetes/pki” > kubelet-config.txt将其输出到名为kubelet-config.txt的文件中。
D.2.4 备份 etcd
如我们从第二章的阅读中了解到的,etcd 是集群所有配置的数据存储。它是跟踪在 kube-system 命名空间中运行的 Pods 的工具,同时也保留关于 kubelet 配置的数据。它维护自己的服务器证书,因为 API 服务器必须对其进行身份验证才能访问其内部数据。这是 Kubernetes 的一个重要组成部分,因此证明了为什么我们需要对其进行备份。
要与 etcd 数据存储进行接口交互,我们使用一个名为 etcdctl 的工具,它也提供了一个帮助菜单——以防你在考试中遇到困难。输入命令 etcdctl -h 以获取我们可以运行的命令列表,用于备份 etcd 数据存储。
注意:在考试中你不需要做这件事,但如果你在家里的实验室里做这个练习(例如,使用友好的 Kubernetes),请运行以下命令来安装 etcdctl 命令行工具并将其设置为版本 3:apt update; apt install -y etcd-client; export ETCDCTL_API=3。
当版本设置为 3 时,帮助菜单包括 snapshot save 命令。此外,如果你使用命令 etcdctl snapshot save -h 查看snapshot save 的帮助页面,你会看到描述“将 etcd 节点后端快照存储到指定的文件中。”在考试当天忘记命令时,像这样的帮助页面在许多方面都是一项宝贵的资源,这就是为什么我在这里详细说明它的原因。
如前一段(以及第二章)所述,etcd 数据存储有自己的服务器证书,这意味着你必须对其进行身份验证才能访问其内部数据。这意味着我们必须将证书颁发机构(CA)、客户端证书和密钥与我们的备份请求一起传递。幸运的是,我们已经知道所有证书都位于 /etc/Kubernetes/pki/etcd 目录中。因此,我们可以在全局选项 --cacert、--cert 和 --key(我们也可以从帮助页面中看到全局选项)的现有位置引用它们。最终的命令是 etcdctl snapshot save etcdbackup1 --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key。一旦备份完成,我们可以运行命令 etcdctl snapshot status etcdbackup1 > snapshot-status.txt 来获取备份的状态,并将其重定向到文件。这些命令的结果将类似于以下内容:
root@kind-control-plane:~# etcdctl snapshot save etcdbackup1 --cacert
➥ /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/server.crt
➥ --key /etc/kubernetes/pki/etcd/server.key
2023-01-30 17:41:32.602411 I | clientv3: opened snapshot stream;
➥ downloading
2023-01-30 17:41:32.624918 I | clientv3: completed snapshot read; closing
Snapshot saved at etcdbackup1
root@kind-control-plane:~# etcdctl snapshot status etcdbackup1
9ec8949e, 1662, 807, 1.7 MB
root@kind-control-plane:~# etcdctl snapshot status etcdbackup1 > snapshot-
➥ status.txt
D.2.5 恢复 etcd
如果你还没有完成前面的练习,接下来的练习将无法工作,因为它会从你进行备份的点继续。所以,现在我们有了名为etcdbackup1的快照,我们可以使用etcdctl命令行工具进行恢复,有一个选项是恢复而不是备份。再次,如果我们运行命令etcdctl snapshot -h,我们会看到可用的命令是restore、save和status。选择restore命令,并运行命令etcdctl snapshot restore -h以获取更多信息。从帮助菜单的输出中,看起来我们可以指定一个数据目录。这将允许我们恢复到一个 etcd 已经访问过的目录。我们知道当前 etcd 数据目录的目录是/var/lib。我们知道这一点是因为 etcd 的清单,它位于/etc/kubernetes/manifests目录中。我们可以运行命令cat /etc/kubernetes/manifests/etcd.yaml | tail -10。输出将看起来像这样:
root@kind-control-plane:~# cat /etc/kubernetes/manifests/etcd.yaml |
➥ tail -10
volumes:
- hostPath:
path: /etc/kubernetes/pki/etcd
type: DirectoryOrCreate
name: etcd-certs
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd-data
status: {}
这意味着我们可以指定一个类似的目录,比如说/var/lib/etcd-restore。完整的命令是etcdctl snapshot restore snapshotdb --data-dir /var/lib/etcd-restore。
D.2.6 升级控制平面
每次你在考试中看到“升级”这个词,就想到 kubeadm。在考试前多次使用 kubeadm 进行升级是一个好习惯。我总是喜欢使用帮助菜单来找到我的方法,所以,让我们再次运行命令kubeadm -h来查看我们可用于升级的选项。从输出中,可用的选项中包括upgrade命令。如果我们使用命令kubeadm upgrade -h在帮助页面中再深入一级,我们可以推断出plan将检查我们的集群以选择可用的版本。让我们试试,使用命令kubeadm upgrade plan。输出将会非常长,但重要部分看起来像这样:
Upgrade to the latest version in the v1.24 series:
COMPONENT CURRENT TARGET
kube-apiserver v1.24.7 v1.24.10
kube-controller-manager v1.24.7 v1.24.10
kube-scheduler v1.24.7 v1.24.10
kube-proxy v1.24.7 v1.24.10
CoreDNS v1.8.6 v1.8.6
etcd 3.5.3-0 3.5.3-0
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.24.10
如果我们比较版本,我们看到我们可以从 v1.24.7 升级到 v1.24.10。输出甚至给出了要运行的精确命令,这非常方便!让我们运行命令kubeadm upgrade apply v1.24.10。
注意:你可能会收到错误信息“指定的升级版本‘v1.24.10’高于 kubeadm 版本‘v1.24.7’。”你可以通过运行命令apt update; apt install -y kubeadm=1.24.10-00.来修复此信息。
D.3 第三章考试练习
这些练习是相互关联的,所以我建议你一次性完成第三章的所有考试练习。这将为你准备 CKA 考试提供最佳实践。
D.3.1 创建角色
要在 Kubernetes 中创建角色,我们使用kubectl命令行工具。确保使用帮助菜单,因为通常有一些示例可以直接复制粘贴到你的终端进行考试。例如,如果你运行命令k create role -h,你会得到几个示例,如下所示:
Examples:
# Create a role named "pod-reader" that allows user to perform "get",
➥ "watch" and "list" on pods
kubectl create role pod-reader --verb=get --verb=list --verb=watch -
➥ resource=pods
# Create a role named "pod-reader" with ResourceName specified
kubectl create role pod-reader --verb=get --resource=pods --resource-
➥ name=readablepod --resource-name=anotherpod
# Create a role named "foo" with API Group specified
kubectl create role foo --verb=get,list,watch --resource=rs.extensions
# Create a role named "foo" with SubResource specified
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status
这不是个玩笑;您可以将这些内容复制粘贴到考试时的终端中,我建议您这样做。对于这个练习,因为它将允许为服务账户使用 create 动词,我们将从帮助菜单中复制第一个示例,并将其修改为 kubectl create role sa-creator --verb=create --resource=sa.。
这将给我们一个新的角色,允许我们创建服务账户。我们可以通过命令 k get role sa-creator -o yaml 看到这个角色存在并且具有适当的权限。输出将类似于以下内容:
root@kind-control-plane:~# k get role sa-creator -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: "2023-02-17T02:54:46Z"
name: sa-creator2
namespace: default
resourceVersion: "190882"
uid: 2517a9db-0a1c-4b3b-bf40-59fa799b5fd8
rules:
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- create
D.3.2 创建角色绑定
以下练习只能在您已经创建了 sa-creator 角色的情况下完成。如果您还没有创建,请完成本章的第一个考试练习。与创建角色类似,我们可以使用帮助菜单来找到正确的命令。让我们运行命令 k create rolebinding -h,我们将看到一个有用的示例,我们可以根据需要修改它。输出中的示例应类似于以下内容:
Examples:
# Create a role binding for user1, user2, and group1 using the admin
➥ cluster role
kubectl create rolebinding admin --clusterrole=admin --user=user1 -
➥ user=user2 --group=group1
让我们复制并粘贴这个示例,并将其修改为 kubectl create rolebinding sa-creator-binding --role=sa-creator --user=sandra。一旦创建了角色绑定,您可以使用命令 k get rolebinding sa-creator-binding -o yaml 来验证设置。输出应类似于以下内容:
root@kind-control-plane:~# k get rolebinding sa-creator-binding -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: "2023-02-17T03:03:28Z"
name: sa-creator-binding
namespace: default
resourceVersion: "191629"
uid: 0191d224-654b-44fb-8824-2b4e68028fef
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: sa-creator
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: sandra
Using auth can-i
再次,我们将使用帮助菜单来为我们提供正确的命令提示。命令 k auth can-i -h 的输出将给出以下示例:
Examples:
# Check to see if I can create pods in any namespace
kubectl auth can-i create pods --all-namespaces
# Check to see if I can list deployments in my current namespace
kubectl auth can-i list deployments.apps
# Check to see if I can do everything in my current namespace ("*" means
➥ all)
kubectl auth can-i '*' '*'
# Check to see if I can get the job named "bar" in namespace "foo"
kubectl auth can-i list jobs.batch/bar -n foo
# Check to see if I can read pod logs
kubectl auth can-i get pods --subresource=log
# Check to see if I can access the URL /logs/
kubectl auth can-i get /logs/
# List all allowed actions in namespace "foo"
kubectl auth can-i --list --namespace=foo
我们可以推断出要运行的命令是 kubectl auth can-i create sa --as sandra。运行该命令的结果将类似于以下内容:
root@kind-control-plane:~# k auth can-i create sa --as sandra
yes
D.3.3 创建新用户
通过阅读第三章,您应该明白用户只是一个构造,而不是用户数据库中的实际用户。这意味着尽管我们创建了用户 Sandra,我们只是在创建一个证书,其中通用名称是 Sandra。Kubernetes 没有用户这一概念,但可以与其他身份提供者集成。对于考试,您需要知道如何生成这个证书,所以我建议您多次进行这个练习,因为 Kubernetes 文档中(您可以在考试期间打开)没有直接的答案。让我们首先使用 openssl 命令行工具,这个工具将在考试中可用(已安装),并且所有 Linux 系统都自带,包括您用于练习实验室的系统。让我们使用命令 openssl genrsa -out sandra.key 2048. 生成一个 2048 位加密的私钥。输出将类似于以下内容:
root@kind-control-plane:/# openssl genrsa -out sandra.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
......................................................+++++
.......................+++++
e is 65537 (0x010001)
现在,让我们使用我们刚刚创建的私钥制作一个证书签名请求文件,我们最终会将它提供给 Kubernetes API。在这里,我们指定用户 Sandra 为证书签名请求的通用名称非常重要,使用命令 openssl req -new -key carol.key -subj "/CN=sandra" -out sandra.csr:
root@kind-control-plane:/# openssl req -new -key carol.key -subj
➥ "/CN=carol/O=developers" -out carol.csr
root@kind-control-plane:/# ls | grep carol
carol.csr
carol.key
接下来,将 CSR 文件存储在环境变量中,因为我们稍后会需要它。为此,使用命令export REQUEST=$(cat sandra.csr | base64 -w 0)将 CSR 文件的 Base64 编码版本存储在名为REQUEST的环境变量中。
然后,使用以下多行命令从该请求创建 CSR 资源:
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: sandra
spec:
groups:
- developers
request: $REQUEST
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
EOF
这将在一个命令中创建资源并输入请求。你可以使用命令k get csr查看请求。输出将看起来像这样:
root@kind-control-plane:/# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR
➥ CONDITION
sandra 4s kubernetes.io/kube-apiserver-client kubernetes-admin
➥ Pending
你可以使用命令kubectl certificate approve Sandra批准请求,你会看到条件从pending变为Approved,Issued:
root@kind-control-plane:/# kubectl certificate approve sandra
certificatesigningrequest.certificates.k8s.io/sandra approved
root@kind-control-plane:/# kubectl get csr
NAME AGE SIGNERNAME REQUESTOR
➥ CONDITION
sandra 2m12s kubernetes.io/kube-apiserver-client kubernetes-admin
➥ Approved,Issued
现在它已经被批准,你可以从已签名的证书中提取客户端证书,使用 Base64 解码,并使用命令kubectl get csr sandra -o jsonpath='{.status.certificate}' | base64 -d > carol.crt将其存储在名为sandra.crt的文件中:
root@kind-control-plane:/# kubectl get csr sandra -o
➥ jsonpath='{.status.certificate}' | base64 -d > sandra.crt
D.3.4 将 Sandra 添加到 kubeconfig
如果你想要承担 Sandra 的角色,你必须将用户(以及证书,这是重要部分)添加到 kubeconfig 文件中(我们使用该文件来运行kubectl)。要将用户 Sandra 添加到我们的上下文中,我们将运行命令kubectl config set-context carol --user=sandra --cluster=kind。一旦运行此命令,你会注意到上下文已经通过运行命令kubectl config get-contexts添加。你会看到类似以下的输出:
root@kind-control-plane:/# kubectl config set-context sandra --user=sandra
➥ --cluster=kind
Context "carol" created.
root@kind-control-plane:/# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
sandra kind sandra
* kubernetes-admin@kind kind kubernetes-admin
左侧current列中的星号表示我们目前正在使用哪个上下文。因此,要切换上下文,请运行命令kubectl config use-context sandra。你会注意到星号将当前上下文更改为Sandra。
D.3.5 创建新的服务账户
如我们从第三章阅读中得知,服务账户的令牌会自动挂载到 Pod 上,要防止该令牌的挂载需要特殊的配置设置。第一步是查看帮助菜单。我总是从帮助菜单开始,因为在紧张的时刻(考试中),你有时会失去思路,也许会忘记使用哪个命令或命令和选项的顺序。这种情况发生在我们所有人身上!别担心,记住,如果你需要,帮助菜单就在那里。它有存在的理由。例如,你可以运行命令k create -h来查看可用命令的列表,你会发现serviceaccount被列为可用命令之一。让我们运行命令k create serviceaccount -h来查看特定于服务账户的选项。输出将给我们提供很多选项,但最值得注意的是,它将展示以下示例:
Examples:
# Create a new service account named my-service-account
kubectl create serviceaccount my-service-account
我们可以复制并粘贴这个示例,并将其更改为 kubectl create serviceaccount secure-sa,,这将创建我们在这个练习中需要的 Service Account。然后,为了确保令牌不会暴露给 Pod,我们可以运行命令 k get sa secure-sa -o yaml > secure-sa.yaml,然后运行命令 echo "automountServiceAccountToken: false" >> secure-sa.yaml 来确保令牌不会自动挂载到 Pod。要应用更改,运行命令 k apply -f secure-sa.yaml,这将应用配置更改以禁用为使用此 Service Account 的所有 Pod 自动挂载令牌。
D.3.6 创建新的集群角色
我们在本章的第一个练习中创建了一个角色;现在我们将以类似的方式创建一个集群角色。我希望你现在已经熟悉了使用帮助菜单来查找创建集群角色的 kubectl 命令的优秀示例。我将从命令 k create clusterrole -h 中的第一个示例开始,并将其更改为 kubectl create clusterrole acme-corp-role --verb=create --resource=deploy,rs,ds。接下来,我们将创建一个角色绑定(注意它没有提到集群角色绑定),我们将称之为 acme-corp-role-binding,将其绑定到 secure-sa Service Account,并确保 Service Account 只能在默认命名空间中创建 Deployments、ReplicaSets 和 DaemonSets(而不是 kube-system 命名空间)。我们将使用命令 k create rolebinding -h 中的示例,并将其更改为 kubectl create rolebinding acme-corp-role-binding --clusterrole=acme-corp-role --serviceaccount=default:secure-sa。然后,我们将使用命令 kubectl -n kube-system auth can-i create deploy --as system:serviceaccount:default:nomount-sa 来检查角色。你应该得到响应 no。
D.4 第四章考试练习
第四章考试练习围绕在 Kubernetes 中调度 Deployments 或 Pods。你不需要为考试部署有状态集,因为它没有列为考试标准的对象。当你想到调度时,只需想到创建一个 Pod——无论是否在 Deployment 中——并将其放置在节点上。在本章中,有方法可以控制 Pod 放置在哪个节点上,这就是我们将关注的练习内容。
D.4.1 应用标签并创建 Pod
这个练习仅仅涉及给一个节点应用一个标签。如果你不知道从哪里开始,有一个帮助菜单可以提供帮助!让我们运行命令 kubectl -h | grep label 来查看是否有 label 在可用的命令中。输出将类似于以下内容:
root@kind-control-plane:~# k -h | grep label
delete Delete resources by file names, stdin, resources and
➥ names, or by resources and label selector
label Update the labels on a resource
要给节点 kind-worker 标签,我们可以运行命令 k label no kind-worker disk=ssd。然后,我们可以使用命令 k get no -show-labels 来显示我们节点上的标签。输出将类似于以下内容:
root@kind-control-plane:~# k get no --show-labels
NAME STATUS ROLES AGE VERSION LABELS
kind-control-plane Ready control-plane 8d v1.24.7
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,ingress-
➥ ready=true,kubernetes.io/arch=amd64,kubernetes.io/hostname=kind-
➥ control-plane,kubernetes.io/os=linux,node-role.kubernetes.io/control-
➥ plane=,node.kubernetes.io/exclude-from-external-load-balancers=
kind-worker Ready <none> 8d v1.24.7
➥ beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disk=ssd,kube
➥ rnetes.io/arch=amd64,kubernetes.io/hostname=kind-
➥ worker,kubernetes.io/os=linux
我们可以看到标签已成功应用,现在我们可以使用命令k run fast --image nginx -dry-run=client -o yaml > fast.yaml创建 Pod 的 YAML。让我们打开文件并更改两行,这将使 Pod 调度到带有标签disk=ssd的节点。在文件的最后,就在单词status上方,与restartPolicy对齐,我们将添加以下行:
nodeSelector:
disk: ssd
保存你的更改并关闭文件。现在你有了 YAML 文件,你可以使用命令k create -f fast.yaml创建 Pod。确保它被调度到正确的节点,使用命令k get po -o wide。输出将类似于以下内容:
root@kind-control-plane:~# k get po -o wide
NAME READY STATUS RESTARTS AGE
➥ IP NODE NOMINATED NODE READINESS GATES
fast 1/1 Running 1 (36h ago) 8d
➥ 10.244.162.144 kind-worker <none> <none>
D.4.2 编辑正在运行的 Pod
当你使用k edit po命令编辑正在运行的 Pod 时,你只能更改 YAML 中的某些字段。这是正常行为,即使你无法直接更改,Kubernetes 也会在/tmp/目录下的一个文件中保存 Pod YAML 的副本。让我们运行命令k edit po fast来查看它看起来像什么,这将打开 Vim 编辑器中的 Pod YAML。你可以通过按键盘上的斜杠(/)键并跟随着单词nodeSelector(例如,在 Vim 中输入/nodeSelector)然后按 Enter 键来搜索nodeSelector。Vim 文本编辑器将首先突出显示注释中的单词nodeSelector,因此按 N 键进入下一个结果,这将是我们正在寻找的结果。然后你可以按键盘上的 I 键进入插入模式。将文本从disk: ssd更改为disk: slow。当你完成这个练习时,必须更改的 YAML 部分将看起来像以下内容:
nodeName: kind-worker
nodeSelector:
disk: slow
preemptionPolicy: PreemptLowerPriority
现在你已经更改了 YAML 文件,你可以保存并退出文件;然而,你会收到一个警告信息,指出 Pod 更新可能不会更改除spec.containers[*].image之外的字段。这没关系,因为你将继续退出文件。只有在这种情况下,文件才会被保存到/tmp目录。当你退出文件时,你收到的信息将类似于以下内容:
root@kind-control-plane:~# k edit po fast
error: pods "fast" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-
➥ 589741394.yaml"
error: Edit cancelled, no valid changes were saved.
即使收到了错误,这也还是可以的。这是我们将存储在/tmp目录中的 YAML 应用的部分,并强制 Pod 重新启动。执行此操作的命令是k replace -f /tmp/kubectl-edit-589741394.yaml --force(YAML 文件的名称将因你而异)。这将导致当前运行的 Pod 被删除,并创建一个新的 Pod(具有新的名称)。输出将类似于以下内容:
root@kind-control-plane:~# k replace -f /tmp/kubectl-edit-589741394.yaml -
➥ force
pod "fast" deleted
pod/fast replaced
一旦完成,你可以使用命令k get po fast; k get po fast -o yaml | grep disk.来检查 Pod 是否使用新的配置正在运行。
root@kind-control-plane:~# k get po fast; k get po fast -o yaml | grep disk:
NAME READY STATUS RESTARTS AGE
fast 1/1 Running 0 4m39s
disk: slow
D.4.3 为新 Pod 使用节点亲和性
如我们从第四章阅读中了解到的,节点亲和性是根据对具有特定标签的节点的一定偏好来调度 Pod。例如,在本练习中,我们表示 Pod 应该被调度到具有标签 disk=ssd 作为首选的节点。然而,有一个备用计划,以防没有具有 disk=ssd 标签的可用节点。备用计划是将 Pod 调度到具有标签 Kubernetes.io/os=linux 的节点。
让我们从使用命令 k run ssd-pod -image nginx -dry-run=client -o yaml > ssd-pod.yaml 创建 Pod 的 YAML 开始。现在打开文件 ssd-pod.yaml 并插入节点亲和性配置。对于考试,我会在 Kubernetes 文档中利用这一点,您可以在考试期间打开它。因此,在网页浏览器中打开网站 kubernetes.io/docs,在搜索栏中输入 node selector 并按 Enter。选择第一个链接,命名为 Assigning Pods to Nodes,然后点击页面右侧的链接 Affinity and Anti-affinity。您将看到此页面上列出的 YAML,您可以将其复制并粘贴到现有的 ssd-pod.yaml 文件中。您需要复制的是 spec: 下的整个亲和性部分。我们将对其进行轻微修改,使其看起来如下:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disk
operator: In
values:
- ssd
一旦将内容粘贴到文件 ssd-pod.yaml 中,您就可以保存并退出。您可以使用命令 k apply -f ssd-pod.yaml 创建 Pod。使用命令 k get po -o wide 检查 Pod 是否已成功调度到正确的节点。输出应类似于以下内容:
root@kind-control-plane:~# k apply -f ssd-pod.yaml
pod/ssd-pod created
root@kind-control-plane:~# k get po -o wide
NAME READY STATUS RESTARTS AGE
➥ IP NODE NOMINATED NODE READINESS GATES
ssd-pod 1/1 Running 0 16s
➥ 10.244.162.152 kind-worker <none> <none>
D.5 第五章考试练习
这些练习与上一组练习不同,因为它们更多地与当前运行的 Deployments 和 Pods 的维护有关。这些练习将涉及扩展、更新镜像和查看 Deployment 的滚动更新。这对于考试很有帮助,因为您可能需要将应用程序部署到 Kubernetes 的新版本,或者可能需要回滚到以前的版本。
D.5.1 在 Deployment 中扩展副本
对于考试来说,很可能已经有一个 Deployment 在运行,但为了在我们的实践实验室中模拟这种情况,我们不得不自己创建一个。这个练习最重要的部分是扩展操作,而不是创建 Deployment。让我们先运行命令 k create deploy apache --image httpd:latest。这将创建 Deployment,并且由于我们没有在命令中指定,Deployment 将只有一个副本。您可以使用命令 k get deploy,po 验证 Deployment 内的 Pod 是否已创建。输出将类似于以下内容:
root@kind-control-plane:~# k get deploy,po
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/apache 1/1 1 1 3m30s
NAME READY STATUS RESTARTS AGE
pod/apache-67984dc457-5mcvj 1/1 Running 0 3m30s
现在 Pod 已经启动并运行,我们可以将 Deployment 从单个副本扩展到五个副本。扩展副本的命令是 k scale deploy apache -replicas 5。一旦我们运行该命令,我们将看到另外四个 Pod 被启动。为了验证这是否发生,再次运行命令 k get deploy,po。输出现在将看起来像这样:
root@kind-control-plane:~# k scale deploy apache --replicas=5
deployment.apps/apache scaled
root@kind-control-plane:~# k get deploy,po
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/apache 3/5 5 3 5m57s
NAME READY STATUS RESTARTS
➥ AGE
pod/apache-67984dc457-5mcvj 1/1 Running 0
➥ 5m57s
pod/apache-67984dc457-bcs6q 1/1 Running 0
➥ 3s
pod/apache-67984dc457-dwzl9 0/1 ContainerCreating 0
➥ 3s
pod/apache-67984dc457-kl7rq 0/1 ContainerCreating 0
➥ 3s
pod/apache-67984dc457-rdgh5 1/1 Running 0
➥ 3s
D.5.2 更新镜像
在上一个练习中,我们创建了一个名为 apache 的 Deployment,我们将在本练习中继续使用相同的 Deployment。如果你还没有完成上一个练习,请在开始这个练习之前完成它。正如我们在第五章中了解到的,在 Deployment 中更新镜像会导致 Kubernetes 创建一个新的 ReplicaSet,并将此操作记录为新的滚动。我们可以使用命令 k set image deploy apache httpd=httpd:latest httpd=httpd:2.4.54 来更新镜像。你可以使用命令 k get deploy apache -o yaml | grep image 验证 Deployment 是否包含正确的镜像。输出将看起来像这样:
root@kind-control-plane:~# k get deploy apache -o yaml | grep image
- image: httpd:2.4.54
imagePullPolicy: Always
D.5.3 查看 ReplicaSet 事件
每次你更改镜像,以及部署的其他特性,都会创建一个新的 ReplicaSet。这是因为 Pods 的配置发生了变化;因此,旧的 Pods 会被终止,新的 Pods 会被创建。正如我们在第五章中了解到的,这一切都是由 ReplicaSet 处理的,它是控制器,用于帮助部署新版本的 Deployment。我们可以通过运行命令 k describe rs apache-67984dc457 来查看事件。请注意,ReplicaSet 的名称对于你来说可能会有所不同。输出包含大量信息,但重要的是在末尾的事件部分,它应该看起来像这样:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 58m replicaset-controller Created pod:
➥ apache-67984dc457-kl7rq
Normal SuccessfulCreate 58m replicaset-controller Created pod:
➥ apache-67984dc457-rdgh5
Normal SuccessfulCreate 58m replicaset-controller Created pod:
➥ apache-67984dc457-dwzl9
Normal SuccessfulCreate 58m replicaset-controller Created pod:
➥ apache-67984dc457-bcs6q
Normal SuccessfulDelete 7m59s replicaset-controller Deleted pod:
➥ apache-67984dc457-rdgh5
Normal SuccessfulDelete 6m45s replicaset-controller Deleted pod:
➥ apache-67984dc457-bcs6q
Normal SuccessfulDelete 6m44s replicaset-controller Deleted pod:
➥ apache-67984dc457-5mcvj
Normal SuccessfulDelete 6m43s replicaset-controller Deleted pod:
➥ apache-67984dc457-dwzl9
Normal SuccessfulDelete 6m43s replicaset-controller Deleted pod:
➥ apache-67984dc457-kl7rq
D.5.4 回滚到之前的应用程序版本
现在我们已经成功推出到新版本(通过更改部署镜像),我们可以通过执行命令 k rollout undo deploy apache 轻松回滚到上一个版本,使用之前的镜像。现在,通过运行命令 k rollout status deploy apache,然后 k rollout history deploy apache,我们可以看到我们当前处于哪个版本。
root@kind-control-plane:~# k rollout undo deploy apache
deployment.apps/apache rolled back
root@kind-control-plane:~# k rollout status deploy apache
Waiting for deployment "apache" rollout to finish: 1 old replicas are
➥ pending termination...
Waiting for deployment "apache" rollout to finish: 1 old replicas are
➥ pending termination...
Waiting for deployment "apache" rollout to finish: 1 old replicas are
➥ pending termination...
Waiting for deployment "apache" rollout to finish: 4 of 5 updated replicas
➥ are available...
deployment "apache" successfully rolled out
root@kind-control-plane:~# k rollout history deploy apache
deployment.apps/apache
REVISION CHANGE-CAUSE
2 <none>
3 <none>
D.5.5 更改滚动策略
要更改滚动策略,我们必须修改 Deployment YAML。我们可以通过运行命令 k edit deploy apache 来轻松完成此操作。这将打开 Vim 文本编辑器中的 Deployment YAML。我们可以找到以 strategy 开头的行,并将类型更改为 Recreate。策略下的其余 YAML 可以删除。最终结果将看起来像这样:
app: apache
strategy:
type: Recreate
template:
一旦你将策略从 RollingUpdate 更改为 Recreate,你可以保存并退出文件。Deployment 将自动更新。不会发生滚动,因为这只会影响下一个滚动阶段。退出 Deployment YAML 后的结果将看起来像这样:
root@kind-control-plane:~# k edit deploy apache
deployment.apps/apache edited
作为额外加分,你可以再次执行之前的练习,并看到在创建新 Pods 之前,所有 Pods 都已被终止,因为这正是我们打算更改的部署策略。
D.5.6 隔离和解除隔离节点
对于这个练习,我们首先创建一个三节点集群。请参阅附录 A 了解如何使用 kind Kubernetes 创建多节点集群。当三节点集群启动并运行,并且你已经运行了命令 docker exec -it kind-control-plane 以获取对控制平面节点(其中已安装 kubectl)的访问权限后,你可以继续这个任务。
隔离一个节点意味着我们将它标记为不可调度。这并不一定意味着从节点中驱逐 Pods。你必须运行 drain 命令来完成这个操作。要隔离一个节点,请运行命令 k cordon kind-worker。你可以通过运行命令 k get no 来验证此节点上的调度已被禁用。输出将类似于以下内容:
root@kind-control-plane:/# k cordon kind-worker
node/kind-worker cordoned
root@kind-control-plane:/# k get no
NAME STATUS ROLES AGE
➥ VERSION
kind-control-plane Ready control-plane 58s
➥ v1.26.0
kind-worker Ready,SchedulingDisabled <none> 38s
➥ v1.26.0
kind-worker2 Ready <none> 38s
➥ v1.26.0
现在,我们可以调度一个 Pod,并且它应该被调度到节点 kind-worker2,因为我们刚刚隔离了节点 kind-worker。要调度一个 Pod,让我们运行命令 k run nginx --image nginx。然后,我们可以使用命令 k get po -o wide 检查 Pod 是否被调度到正确的节点。输出应该类似于以下内容:
root@kind-control-plane:/# k run nginx --image nginx
pod/nginx created
root@kind-control-plane:/# k get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
➥ NOMINATED NODE READINESS GATES
nginx 0/1 ContainerCreating 0 5s <none> kind-worker2
➥ <none> <none>
接下来,我们将使用命令 k uncordon kind-worker 解除节点 kind-worker 的隔离状态。现在节点已解除隔离,我们可以再次向其调度 Pods。请通过添加节点选择器将 Pod 从节点 kind-worker2 移动到节点 kind-worker。我们将在 Pod YAML 中使用命令 k edit po nginx 添加节点选择器(你可能需要运行命令 apt update; apt install -y vim 重新在你的新 kind 集群上安装 Vim)。这将使用 Vim 文本编辑器打开 YAML 文件。在文件中找到以 nodeName 开头的行(第 29 行)。将此行从 nodeName: kind-worker2 更改为 nodeName: kind-worker。保存并退出文件。这将提示你一个消息,表明 Pod 更新可能不会更改除 spec.containers[*].image 之外的字段,这是正常的,因为你将继续退出文件。一旦你退出文件,你会注意到它在 /tmp 目录中存储了一个 YAML 的副本。你可以运行命令 k replace -f /tmp/kubectl-edit-3840075995.yaml --force 来终止旧的 nginx Pod,这将创建一个新的 Pod 并将其调度到节点 kind-worker。我们可以通过运行命令 k get po -o wide 来验证这一点。
root@kind-control-plane:/# k edit po nginx
error: pods "nginx" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-
➥ 3840075995.yaml"
error: Edit cancelled, no valid changes were saved.
root@kind-control-plane:/# k replace -f /tmp/kubectl-edit-3840075995.yaml
➥ --force
pod "nginx" deleted
pod/nginx replaced
root@kind-control-plane:/# k get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
➥ NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 4m17s 10.244.1.2 kind-worker
➥ <none> <none>
我们可以看到,实际上它被调度到了节点 k``ind-worker。
D.5.7 从节点中移除污点
考试可能已经有一个 Deployment 在运行,但为了你的实践实验室环境,你可能没有,所以我们将首先创建一个 Deployment 来模拟这种考试场景。我们可以使用之前练习中使用的相同集群。
我们首先使用命令 k create deploy nginx -image nginx 创建一个名为 nginx 的 Deployment,使用 nginx 镜像。然后,我们将使用命令 k taint no kind-control-plane node-role.kubernetes.io/control-plane:NoSchedule- 从控制平面节点移除污点。现在污点已经被移除,我们可以将 Pod 调度到该节点,无需包含对污点的容忍。让我们继续这样做,但首先,我们需要修改 Deployment YAML。我们可以使用命令 k edit deploy nginx``, 来打开 Vim 文本编辑器中的 Deployment YAML。我们可以在 Pod 规范中添加一个节点选择器,通过插入以下内容:
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
nodeSelector:
kubernetes.io/hostname: kind-control-plane
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
一旦我们添加了节点选择器行,新的配置将自动应用,因为 Deployment 控制器将识别到变化并重新调度 Pod 到控制平面节点。我们可以使用命令 k get po -o wide 验证这一点。输出将类似于以下内容:
root@kind-control-plane:/# k edit deploy nginx
deployment.apps/nginx edited
root@kind-control-plane:/# k get po -o wide
NAME READY STATUS RESTARTS AGE IP
➥ NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 27m 10.244.1.2
➥ kind-worker <none> <none>
nginx-cd5574b4f-qbz9z 1/1 Running 0 3m46s 10.244.0.5
➥ kind-control-plane <none> <none>
D.6 第六章考试练习
第六章的考试练习变得更加复杂,因为我们开始处理集群内部的 DNS 和通信。这些练习是相互关联的,所以我建议您从第一个开始,然后继续第二个、第三个等等。如果您试图在练习的中间开始,您将无法继续进行,除非您已经完成了前面的练习。我们将在这里详细说明如何解决这些练习,以便您为考试做好最佳准备。
D.6.1 将执行进入 Pod
对于考试,您已经有了已经创建的 Pod,但为了在您自己的个人实验室环境中(例如,kind Kubernetes)进行练习,请继续创建 Pod 作为先决条件。如果您是从第五章的练习继续,您可以使用我们创建的名为 nginx 的 Pod。如果您是从零开始,您可以使用命令 k run nginx -image nginx 创建一个 Pod。一旦创建了 Pod,您可以使用容器内的命令检查它是否正在运行。为了获取 Pod 用于解析域名(在运行时注入到每个 Pod 中)的 DNS 服务器的 IP 地址,我们可以运行命令 k exec -it nginx --cat /etc/resolv.conf。命令的输出将类似于以下内容:
root@kind-control-plane:/# k exec -it nginx --cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
D.6.2 更改 DNS 服务
要更改 DNS 服务 IP 地址,首先更改 API 服务器 YAML 配置中的 CIDR 范围。我们可以通过更改位于 /etc/kubernetes/manifests 的 YAML 文件来完成,该文件名为 kube-apiserver.yaml。让我们打开它并修改 YAML 中的 service-cluster-ip 值。我们将运行命令 vim /etc/kubernetes/manifests/kube-apiserver.yaml,这将使用 Vim 文本编辑器打开文件,然后我们可以将 CIDR 从 10.96.0.0/6 更改为 100.96.0.0/6。一旦我们做出更改,我们可以保存并退出,更改将自动生效。你可能需要等待最多 5 分钟,以便在 kube-system 命名空间中重新创建 API 服务器 Pod。现在,找到 DNS 服务,它始终位于 kube-system 命名空间中。让我们运行命令 k -n kube-system get svc 来查看服务。命令的输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
➥ AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP
➥ 107m
集群 IP 是 10.96.0.10,在这个练习中,我们将将其更改为 100.96.0.10。我们可以使用命令 k -n kube-system edit svc kube-dns 来更改服务 IP 地址,这将在一个 Vim 文本编辑器中打开服务的 YAML。一旦 YAML 打开,我们可以更改位于 spec 下方两个值。我们将把该 IP 地址的两个实例从 10.96.0.10 更改为 100.96.0.10。一旦我们做出更改,我们可以保存并退出。我们可能会看到警告信息,表明你无法更改此值。你可以继续退出文件 (!q),并且 YAML 文件的副本将被存储在 /tmp 目录中。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system edit svc kube-dns
error: services "kube-dns" is invalid
A copy of your changes has been stored to "/tmp/kubectl-edit-
➥ 2356510614.yaml"
error: Edit cancelled, no valid changes were saved.
让我们运行一个 YAML 的替换,这将终止服务并创建一个新的服务,其中包含我们为 DNS 的新 IP 地址。为此,我们将运行命令 k replace -f /tmp/kubectl-edit-2356510614.yaml --force 来替换服务的实例。
D.6.3 修改 kubelet 配置
现在我们已经修改了 kube-dns 服务,我们需要修改 Pod 的 kubelet 配置以获取新的 DNS IP 信息。这可以通过修改 /var/lib/kubelet/ 目录下名为 config.yaml 的文件来完成。让我们使用命令 vim /var/lib/kubelet/config.yaml 打开该文件。一旦文件在 Vim 中打开,我们可以将 clusterDNS 的值从 10.96.0.10 更改为 100.96.0.10。完成此操作后,你可以保存并退出文件。现在,我们已经更改了服务配置,我们需要使用命令 systemctl daemon-reload 和 systemctl restart kubelet 重新加载 kubelet 守护进程,以在节点上重启 kubelet 服务。最后,我们将使用命令 systemctl status kubelet 验证服务是否处于活动状态并正在运行。输出将包含大量信息,但重要的是服务处于活动状态并正在运行,这可以在输出的开头找到:
root@kind-control-plane:/# systemctl status kubelet
kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor
➥ preset: enabled)
Drop-In: /etc/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: active (running) since Sat 2023-02-18 19:04:20 UTC; 1min 20s
➥ ago
Docs: http://kubernetes.io/docs/
D.6.4 编辑 kubelet ConfigMap
要定位 ConfigMap,它类似于 Service,位于 kube-system 命名空间中,我们可以运行命令 k -n kube-system get cm。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system get cm
NAME DATA AGE
coredns 1 134m
extension-apiserver-authentication 6 134m
kube-proxy 2 134m
kube-root-ca.crt 1 133m
kubeadm-config 1 134m
kubelet-config 1 134m
我们在这个练习中特别关注的 ConfigMap 被命名为 kubelet-config。我们可以使用命令 k -n kube-system edit cm kubelet-config 来编辑 ConfigMap。这将在一个 Vim 文本编辑器中打开 ConfigMap 的 YAML 文件。向下滚动到以 clusterDNS 开头的行,并将下面的值从 10.96.0.10 更改为 100.96.0.10。我们可以保存并退出文件以自动应用更改。输出将类似于以下内容:
root@kind-control-plane:/# k -n kube-system edit cm kubelet-config
configmap/kubelet-config edited
现在我们已经升级了节点,因为 kubelet 是当前在节点上运行的守护进程,我们必须更新此配置的节点,以及重新加载守护进程并在节点上重新启动 kubelet 服务。首先,为了更新节点上的 kubelet 配置,执行命令 kubeadm upgrade node phase kubelet-config。输出将类似于以下内容:
root@kind-control-plane:/# kubeadm upgrade node phase kubelet-config
[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'
W0914 17:44:33.203828 3618 utils.go:69] The recommended value for
➥ "clusterDNS" in "KubeletConfiguration" is: [10.96.0.10]; the provided
➥ value is: [100.96.0.10]
[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.
现在我们已经升级了节点的 kubelet 配置,我们可以使用命令 systemctl daemon-reload 重新加载守护进程,并使用命令 systemctl restart kubelet 重新启动服务。您将不会收到输出;您将直接返回到命令提示符,所以只要没有错误消息,您已成功重新启动 kubelet 服务。
D.6.5 缩放 CoreDNS 部署
Kubernetes 中的 DNS 服务作为 Deployment 运行。为了缩放此 Deployment,我们将运行用于缩放任何其他 Deployment 的相同命令,即 k -n kube-system scale deploy Coredns -replicas 3。然后,您可以使用命令 k -n kube-system 检查副本是否已缩放。输出应类似于以下内容:
root@kind-control-plane:/# k -n kube-system scale deploy coredns --replicas
➥ 3
deployment.apps/coredns scaled
root@kind-control-plane:/# k -n kube-system get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 3/3 3 3 141m
D.6.6 从 Pod 验证 DNS 更改
现在我们已经对 DNS 进行了这些更改,我们将验证新创建的 Pod 是否也接收到了这些更改。创建名为 netshoot 的 Pod 并使用 netshoot 镜像的命令是 kubectl run netshoot --image=nicolaka/netshoot --command sleep --command "3600"。我们运行 sleep 和 3600 这两个命令,以便 Pod 保持运行状态(3,600 秒,或 60 分钟)。我们可以使用命令 k get po 检查 Pod 是否处于运行状态。输出应类似于以下内容:
root@kind-control-plane:/# kubectl run netshoot --image=nicolaka/netshoot -
➥ -command sleep --command "3600"
pod/netshoot created
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
netshoot 1/1 Running 0 40s
Pod 正在运行,因此现在您可以通过容器获取 Bash shell。为此,执行命令 k exec -it netshoot -bash。您会注意到您的提示符已更改,这意味着您已成功进入 Pod 内的容器。输出应类似于以下内容:
root@kind-control-plane:/# k exec -it netshoot --bash
bash-5.1#
现在您已经在容器中打开了 Bash shell,您可以使用命令 cat /etc/resolv.conf 来检查是否列出了正确的 DNS IP 地址。输出应类似于以下内容:
root@kind-control-plane:/# k exec -it netshoot --bash
bash-5.1# cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 100.96.0.10
options ndots:5
这意味着 DNS 已正确配置;因此,Pod 能够在集群中使用 CoreDNS 解析 DNS 名称。你可以使用命令 nslookup example.com 检查这个 Pod 是否能够解析到 example.com 的 DNS 查询。Nslookup 是一个 DNS 工具,允许你查询名称服务器。输出应如下所示:
bash-5.1# nslookup example.com
Server: 100.96.0.10
Address: 100.96.0.10#53
Non-authoritative answer:
Name: example.com
Address: 93.184.216.34
Name: example.com
Address: 2606:2800:220:1:248:1893:25c8:1946
D.6.7 创建 Deployment 和 Service
要使用镜像 nginxdemos/hello:plain-text 创建名为 hello 的 Deployment,可以运行命令 k create deploy hello --image nginxdemos/hello:plain-text。然后,我们可以使用命令 k expose deploy hello --name hello-svc --port 80 来暴露 Deployment。为了验证我们是否正确创建了服务,我们将运行命令 k get svc。前述命令的输出将如下所示:
root@kind-control-plane:/# k create deploy hello --image
➥ nginxdemos/hello:plain-text
deployment.apps/hello created
root@kind-control-plane:/# k expose deploy hello --name hello-svc --port 80
service/hello-svc exposed
root@kind-control-plane:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-svc ClusterIP 100.96.226.92 <none> 80/TCP 2s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 153m
D.6.8 将 ClusterIP 服务改为 NodePort
要更改服务类型,我们可以运行命令 k edit svc hello-svc,这将打开 YAML 文件在 Vim 文本编辑器中。在 spec 下,我们可以将起始行包含 type 的行(第 32 行)从 type: ClusterIP 改为 type: NodePort。然后,我们可以在端口列表下添加 nodePort: 3000(直接位于 port: 80 之下)。更改后的最终 YAML 将如下所示:
ipFamilyPolicy: SingleStack
ports:
- nodePort: 30000
port: 80
protocol: TCP
targetPort: 80
selector:
app: hello
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}
保存并退出文件后,更改将自动应用。我们可以使用命令 k get svc 再次查看服务。输出将如下所示:
root@kind-control-plane:/# k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-svc NodePort 100.96.226.92 <none> 80:30000/TCP 5m5s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 159m
要通过节点端口通过服务访问应用程序,我们必须获取 Pod 的位置(它在哪个节点上)和节点的 IP 地址。我们可以使用命令 k get po -o wide; k get no -o wide 来查看这些信息。输出将如下所示:
root@kind-control-plane:/# k get po -o wide; k get no -o wide
NAME READY STATUS RESTARTS AGE IP
➥ NODE NOMINATED NODE READINESS GATES
hello-5dc6ddf4c4-qrwq4 1/1 Running 0 12m 10.244.2.7
➥ kind-worker2 <none> <none>
nginx 1/1 Running 0 62m 10.244.1.3
➥ kind-worker <none> <none>
nginx-cd5574b4f-qbz9z 1/1 Running 0 130m 10.244.0.5
➥ kind-control-plane <none> <none>
NAME STATUS ROLES AGE VERSION INTERNAL-IP
➥ EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kind-control-plane Ready control-plane 166m v1.26.0 172.18.0.7
➥ <none> Ubuntu 22.04.1 LTS 5.15.49-linuxkit containerd://1.6.12
kind-worker Ready <none> 165m v1.26.0 172.18.0.3
➥ <none> Ubuntu 22.04.1 LTS 5.15.49-linuxkit containerd://1.6.12
kind-worker2 Ready <none> 165m v1.26.0 172.18.0.2
➥ <none> Ubuntu 22.04.1 LTS 5.15.49-linuxkit containerd://1.6.12
我们可以看到 Pod 位于 kind-worker2 上,IP 地址为 172.18.0.2。现在让我们使用命令 curl 172.18.0.2:30000 来访问应用程序。输出将如下所示:
root@kind-control-plane:/# curl 172.18.0.2:30000
Server address: 10.244.2.7:80
Server name: hello-5dc6ddf4c4-qrwq4
Date: 18/Feb/2023:19:47:09 +0000
URI: /
Request ID: fb4b47a4fe62ed734b2c023d802bf46e
D.6.9 安装 Ingress 控制器和 Ingress 资源
根据本练习的说明,使用命令 k apply -f https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_06/nginx-ingress-controller.yaml 安装 Ingress 控制器。一旦 Ingress 控制器安装完成,我们可以使用命令 k edit svc hello-svc 将 hello-svc 服务改回 ClusterIP 服务。这将打开服务在 Vim 文本编辑器中,我们可以将类型从 NodePort 改回 ClusterIP,并确保删除包含节点端口号的行(nodeport: 30000)。最终的 YAML 将如下所示:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: hello
sessionAffinity: None
type: ClusterIP
现在我们可以创建一个 Ingress 资源,这是我在考试期间会利用打开 Kubernetes 文档的地方。从网站kubernetes.io/docs打开浏览器,在搜索栏中输入ingress并按 Enter 键。点击第一个链接,名为 Ingress,然后在页面右侧点击 Ingress Resource。你可以直接从页面复制粘贴到你的终端。让我们使用命令vim ingress.yaml创建一个名为ingress.yaml的文件,将 YAML 文件稍微修改一下,并粘贴如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: hello.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello-svc
port:
number: 80
一旦你设置了 YAML 文件,你可以使用命令k apply -f ingress.yaml应用 YAML。然后运行命令k get ing列出 hello Ingress 资源。输出将类似于以下内容:
root@kind-control-plane:/# k apply -f ingress.yaml
ingress.networking.k8s.io/hello created
root@kind-control-plane:/# k get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
hello <none> hello.com 80 2s
D.6.10 安装容器网络接口(CNI)
要安装一个不带 CNI 的 kind Kubernetes 集群,请参阅附录 C。一旦你创建了集群并运行了 Docker exec -it kind-control-plane命令来访问控制平面节点(该节点已安装kubectl),让我们继续这个练习。
要安装一个桥接 CNI,我们首先确保已经安装了 wget。让我们使用命令apt update; apt install wget在控制平面和工作节点上安装它。一旦 wget 安装完成,我们将运行命令wget https://github.com/containernetworking/plugins/releases/download/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz来下载 CNI 插件。我们将使用命令tar -xvf cni-plugins-linux-amd64-v1.1.1.tgz解压文件,然后运行命令mv bridge /opt/cni/bin将文件移动到 bin 目录。现在我们可以使用命令kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.0/manifests/calico.yaml安装 Calico CNI。当你运行命令k get no时,你会看到节点的状态从Not Ready变为Ready。输出将类似于以下内容:
root@kind-control-plane:/# k get no
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane 11m v1.25.0-beta.0
kind-worker Ready <none> 10m v1.25.0-beta.0
D.7 第七章考试练习
以下练习基于存储,并且与前面的章节类似,需要按顺序完成。在完成第一个练习之前,你将无法完成第二个练习。对持久卷、持久卷声明和存储类有良好的理解对于考试至关重要。
D.7.1 创建持久卷
要创建一个持久卷声明(PVC),我们可以参考 Kubernetes 文档,考试期间我们可以将其保持打开状态。这将允许我们直接从页面复制粘贴到我们的终端,这样可以最大限度地利用你在考试中有限的时间。访问 kubernetes.io/docs 并使用屏幕左侧的搜索栏搜索术语 使用持久卷。点击名为“配置 Pod 使用持久卷进行存储 | Kubernetes”的链接,并滚动到“创建持久卷”部分。该页面的完整 URL 是 kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolume。将此页面的 YAML 复制粘贴到一个名为 pv.yaml 的新文件中。
我们将持久卷的名称从 task-pv-volume 更改为 volstore308,并将存储从 10Gi 更改为 22Mi。保存文件 pv.yaml 并使用命令 k create -f pv.yaml 创建持久卷。如果你执行 k get pv 命令,你将看到类似于以下内容的输出:
root@kind-control-plane:/# k get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
➥ STORAGECLASS
volstore308 100Mi RWO Retain Available
➥ manual
D.7.2 创建持久卷声明
要创建 PVC,我们将参考之前提到的文档中的同一页,希望你仍然保持它打开。如果没有,这里是有链接:kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolumeclaim。将 YAML 复制粘贴到一个名为 pvc.yaml 的文件中(使用命令 vim pvc.yaml)。
我们将把 PVC 的名称从 task-pv-claim 更改为 pv-claim-vol,并将存储从 3Gi 更改为 90Mi。使用命令 k apply -f pvc.yaml 创建 PVC。一旦资源创建成功,你可以使用命令 k get pvc 查看 PVC,也可以使用命令 k get pv 查看持久卷(PV)。命令的输出将如下所示:
root@kind-control-plane:/# k get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
➥ AGE
pv-claim-vol Bound vol02833 100Mi RWO manual
➥ 4m
D.7.3 创建使用声明的 Pod
要创建一个名为 pod-access 的 Pod 的 YAML,并使用 centos:7 镜像,我们将运行命令 k run pod-access --image centos:7 --dry-run -o yaml > pod-access.yaml。一旦文件保存,我们可以使用命令 vim pod-access.yaml 在 Vim 文本编辑器中打开它。一旦文件打开,我们可以在 containers: 下添加将卷通过之前练习中创建的声明附加的章节。在 volumeMounts: 下,添加 - name: vol 和 mountPath: /tmp/persistence。为了保持容器活跃,我们可以在 - command: 后面添加 sleep 命令,后面跟着 - sleep 和 - "3600"。结果将如下所示:
containers:
- command:
- sleep
- "3600"
image: centos:7
name: pod-access
resources: {}
volumeMounts:
- name: vol
mountPath: /tmp/persistence
volumes:
- name: vol
persistentVolumeClaim:
claimName: pv-claim-vol
您可以使用命令 k apply -f pod-access.yaml 创建 Pod。
D.7.4 创建存储类
要创建名为 node-local 的存储类,我们可以再次参考 Kubernetes 文档。从 kubernetes.io/docs,让我们在搜索栏中搜索存储类。点击页面右侧的“Local”链接的第一个结果,名为 Storage Classes。将页面上的 YAML 直接复制粘贴到您的终端中。我们将完全按照原样粘贴,并将名称更改为 node-local 到一个名为 sc.yaml 的新文件中。最终结果如下所示:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: node-local
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
存储类可以使用命令 k apply -f sc.yaml 创建。我们可以使用命令 k get sc 查看存储类。
D.7.5 为存储类创建持久卷声明
要为名为 claim-sc 的存储类创建 PVC,我们将使用之前创建的 PVC 并对其进行修改。让我们使用命令 cp pvc.yaml pvc-sc.yaml 复制该文件。我们将 PVC 的名称更改为 claim-sc,将 claim 更改为 39Mi,并将访问模式更改为 ReadWriteOnce。此 PVC 的最终 YAML 应如下所示:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim-sc
spec:
storageClassName: claim-sc
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 39Mi
我们可以使用命令 k apply -f pvc-sc.yaml 创建 PVC。
D.7.6 从存储类创建 Pod
要使用 nginx 镜像创建名为 pod-sc 的 Pod,我们可以运行命令 k run pod-sc --image nginx --dry-run=client -o yaml > pod-sc.yaml。一旦文件已保存,我们可以使用命令 vim pod-sc.yaml 在 Vim 文本编辑器中打开它。一旦文件打开,我们可以在 containers: 下添加将通过之前练习中创建的 claim 挂载的卷的部分。在 volumeMounts: 下,添加与容器名称和镜像一致的 volumeMounts:。在 volumeMounts: 下,添加名称 - name: vol 和 mountPath: /tmp/persistence 在其下方。为了保持容器活跃,我们可以在 command: 下方添加 sleep 命令,后跟 - sleep 和 - "3600"。结果将如下所示:
containers:
image: nginx
name: pod-sc
resources: {}
volumeMounts:
- name: vol
mountPath: /tmp/persistence
command:
- sleep
- “3600”
volumes:
- name: vol
persistentVolumeClaim:
claimName: claim-sc
您可以使用命令 k apply -f pod-sc.yaml 创建 Pod。
D.8 第八章考试练习
第八章全部关于故障排除,因此这些练习中的许多都需要您在集群中模拟某些“损坏”的情况。只需知道,对于考试,这些可能已经以组件已经损坏的方式安排,而在这里我们的实践实验室环境中,我们需要设置场景,这可能需要额外的几个步骤。
D.8.1 修复 Pod YAML
运行练习中的命令,即 k run testbox --image busybox --command 'sleep 3600'。当你这样做时,你会看到状态是 RunContainerError。故障排除的第一步是使用命令 k logs testbox 检查日志。但结果不会返回任何日志,因此我们必须通过决策树并描述 Pod。为此,我们可以运行命令 k describe po testbox。我们可以看到 describe 命令的事件,看起来如下所示:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 119s default-scheduler Successfully
➥ assigned default/testbox to kind-worker
Normal Pulled 116s kubelet Successfully
➥ pulled image "busybox" in 2.754916379s
Normal Pulled 114s kubelet Successfully
➥ pulled image "busybox" in 622.019612ms
Normal Pulled 98s kubelet Successfully
➥ pulled image "busybox" in 710.339798ms
Normal Created 73s (x4 over 116s) kubelet Created
➥ container testbox
Warning Failed 73s (x4 over 116s) kubelet Error: failed
➥ to create containerd task: failed to create shim task: OCI runtime
➥ create failed: runc create failed: unable to start container process:
➥ exec: "sleep 3600": executable file not found in $PATH: unknown
Normal Pulled 73s kubelet Successfully
➥ pulled image "busybox" in 526.759076ms
Warning BackOff 45s (x7 over 114s) kubelet Back-off
➥ restarting failed container
Normal Pulling 33s (x5 over 118s) kubelet Pulling image
➥ "busybox"
Normal Pulled 32s kubelet Successfully
➥ pulled image "busybox" in 497.474074ms
错误是“无法创建 containerd 任务”,这意味着在再次启动容器之前,我们需要修改 YAML。我们可以通过命令 k edit po testbox 来编辑 Pod,并更改以 command 开头的行。将 sleep 3600 替换为下一行上的 3600,并用引号和另一个破折号包围。YAML 的最终结果应该是这样的:
containers:
- command:
- sleep
- "3600"
image: busybox
当你尝试保存并退出时,你会得到一个错误消息,但你只需简单地退出并忽略该消息。你会看到文件已经在 /tmp 目录中保存了一个副本。你可以用命令 k replace -f /tmp/kubectl-edit-8848373.yaml 来替换 Pod YAML。请注意,YAML 文件的名字将和这里的不同。这将创建一个新的 Pod,你现在会看到 Pod 正在运行。
root@kind-control-plane:/# k replace -f /tmp/kubectl-edit-4023269864.yaml -
➥ -force
pod "testbox" deleted
pod/testbox replaced
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
testbox 1/1 Running 0 2s
D.8.2 修复 Pod 镜像
要创建一个名为 busybox2 的新容器,使用 busybox:1.35.0 镜像,运行命令 k run busybox2 -image busybox:1.35.0。通过运行命令 k get po,你会看到以下结果:
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
busybox2 0/1 Completed 2 (20s ago) 23s
我们可以通过首先尝试获取日志来遵循决策树,但这个容器没有日志。然后我们可以通过命令 k describe po busybox2 来描述 Pod。Pod 描述中的事件并没有告诉我们太多,所以它似乎正在正常工作。我们可以通过一个简单的命令来防止容器完成。让我们运行命令 k run busybox2 --image busybox:1.35.0 -it -sh,这将打开容器的 shell。当我们退出 shell 时,我们可以运行 k get po 并看到容器正在运行。
D.8.3 修复已完成的 Pod
要创建一个名为 curlpod2 的新容器,使用 nicolaka/netshoot 镜像,我们将运行命令 k run curlpod --image nicolaka/netshoot -it --sh。这将打开容器内的 shell。让我们运行命令 nslookup kubernetes 并退出 shell。我们再次看到 Pod 已经完成。这是因为容器镜像中没有命令来保持其运行。我们可以通过命令 kubectl run curlpod --image=nicolaka/netshoot --command sleep --command "3600" 来添加这个命令。
D.8.4 修复 Kubernetes 调度器
将文件 kube-scheduler.yaml 移动以备份。这总是一个好主意,尤其是在考试时,你不能简单地重建集群。让我们运行命令 cp /etc/Kubernetes/manifests/kube-scheduler.yaml /tmp/kube-scheduler.yaml。然后我们可以通过运行命令 vim /etc/Kubernetes/manifests/kube-scheduler.yaml 来修改现有的文件。一旦我们在 Vim 文本编辑器中打开它,将 kube-scheduler 末尾的额外 r 添加到单词末尾,使其变为 kube-schedulerr。保存并退出文件。
现在调度器配置已经被修改,让我们通过命令 k run nginx -image nginx 来看看是否可以调度一个 Pod,并通过运行命令 k get po 来检查 Pod 的状态。输出将如下所示:
root@kind-control-plane:/# k run nginx --image nginx
pod/nginx created
root@kind-control-plane:/# k get po
NAME READY STATUS RESTARTS AGE
curlpod 1/1 Running 1 (6m37s ago) 8m1s
nginx 0/1 Pending 0 1s
这是我们知道问题所在,但让我们假装我们不知道的情况之一。使用命令 k -n kube-system describe po kube-scheduler-kind-control-plane 查看事件。事件将如下所示:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Created 3m22s (x4 over 4m7s) kubelet Created container kube-
➥ scheduler
Warning Failed 3m22s (x4 over 4m6s) kubelet Error: failed to create
➥ containerd task: failed to create shim task: OCI runtime create failed:
➥ runc create failed: unable to start container process: exec: "kube-
➥ schedulerr": executable file not found in $PATH: unknown
Warning BackOff 2m53s (x12 over 4m5s) kubelet Back-off restarting
➥ failed container
Normal Pulled 2m41s (x5 over 4m7s) kubelet Container image
➥ "registry.k8s.io/kube-scheduler:v1.25.0-beta.0" already present on machine
你还会注意到 Pod 处于 crashloopbackoff 状态。你可以看到错误信息“无法启动容器进程。”
D.8.5 修复 kubelet
让我们运行命令 curl https://raw.githubusercontent.com/chadmcrowell/acing-the-cka-exam/main/ch_08/10-kubeadm.conf --silent --output /etc/systemd/system/kubelet.service.d/10-kubeadm.conf; systemctl daemon-reload; systemctl restart kubelet. 这将以一种影响 kubelet 的方式破坏集群。让我们使用命令 systemctl status kubelet 检查 kubelet 服务的状态。我们注意到服务处于非活动状态,并看到进程存在错误。让我们检查 /etc/systemd/system/kubelet.service.d 目录下的文件,该文件名为 10-kubeadm.conf。我们会注意到文件中有一个不正确的 bin 目录,它应该是 usr/bin 而不是 /usr/local/bin。让我们将文件更改为 /usr/bin 并看看是否可以修复问题。使用命令 vim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 打开文件。一旦文件打开,我们可以按照以下方式更改内容:
# This eventually leads to kubelet failing to start, see:
➥ https://github.com/kubernetes-sigs/kind/issues/2323
ExecStartPre=/bin/sh -euc "if [ ! -f /sys/fs/cgroup/cgroup.controllers ] &&
➥ [ ! -d /sys/fs/cgroup/systemd/kubelet ]; then mkdir -p
➥ /sys/fs/cgroup/systemd/kubelet; fi"
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS
➥ $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS --cgroup-root=/kubelet
当你的文件看起来像这样时,保存并退出文件。然后使用命令 systemctl daemon-reload 重新加载守护进程,随后使用命令 systemctl restart kubelet 重新启动 kubelet 服务。使用命令 systemctl status kubelet 检查 kubelet 服务的状态,现在服务应该处于活动状态并正在运行。


浙公网安备 33010602011771号