KCNA-认证指南-全-
KCNA 认证指南(全)
原文:
annas-archive.org/md5/3c59e81954943f2641132e7cf4e8e00d译者:飞龙
序言
迄今为止,Kubernetes 已发布超过 20 个版本,甚至在 2014 年首次出现几年后,仍然如火如荼。Kubernetes 与其他云原生技术一起,正在深刻重塑一些世界上最先进和最具进步性的公司中的 IT 格局。
根据Linux 基金会的第十届年度开源职位报告,云计算和容器被列为最受欢迎的技能类别。因此,精通 Kubernetes 和云原生技能对于提升你的职业生涯并在一些顶尖企业工作至关重要。获得 Kubernetes 和云原生认证助理资格可以帮助你脱颖而出,并证明你在该领域的能力。
本书将带你从零开始进行云原生之旅,教授 Kubernetes 的理论和实践方面。你将学习如何使用 Docker 构建、配置和运行容器;如何启动最小化的 Kubernetes 集群;如何使用 Kubernetes 部署、配置和管理容器化应用;如何使用 CI/CD 自动化软件交付,等等。你将打下坚实的基础,以便第一次尝试通过 KCNA 考试,并全面了解当今的行业标准。
本书分为五个部分:
-
云时代
-
执行容器编排
-
学习 Kubernetes 基础
-
探索云原生
-
KCNA 考试与下一步
在第一部分,你将了解云原生计算,解释行业如何发展以及为什么现代应用程序通常运行在容器中。接下来,你将学习 Docker,并动手在本地运行容器。
在第三部分,最大的一部分,你将学习 Kubernetes:它的特性、架构、API 和组件。你将找到最佳实践、回顾问题以及大量的实践作业来支持你的学习旅程。在第四部分,我们将重点介绍云原生架构以及来自云原生生态系统的其他技术。我们将看到如何监控、观察和交付云原生应用。最后,在第五部分,你将找到模拟考试和 KCNA 考试通过技巧,以及一些关于获得认证后如何继续前进的建议。
本书适合谁
Kubernetes 和云原生认证助理(KCNA)是一个为有意通过展示对 Kubernetes 基础知识和技能的理解而晋升到专业级别的候选人设计的预备专业认证。
一名认证的 KCNA 将确认其对整个云原生生态系统的概念性知识,特别是 Kubernetes。KCNA 将展示候选人对 Kubernetes 和云原生技术的基本知识,包括如何使用基本的kubectl命令部署应用、Kubernetes 的架构、理解云原生的全貌和项目,以及了解云原生安全的基础知识。
无论是刚毕业的 IT 专业学生、开发人员、系统管理员,还是 DevOps 工程师,不论经验如何,任何对学习 Kubernetes 和云原生技术感兴趣的人都会觉得本书既实用又易于跟随。要求具备 IT 基础知识(Git)、操作系统以及命令行界面的基础,但无需具备 Kubernetes、Docker 或云原生技术的先前知识即可开始学习。
本书内容概述
第一章,从云到云原生和 Kubernetes,讲述了计算技术在过去 20 多年中的演变。它解释了云是什么,它是如何出现的,以及随着容器技术的引入,IT 环境如何发生了变化。你将学习到 IaaS、PaaS、SaaS 和 FaaS 等基础概念,并初步了解 Kubernetes。
第二章,CNCF 和 Kubernetes 认证概述,介绍了云原生计算基金会(CNCF)及其母组织——Linux 基金会。将分享这些基金会的背景、它们的起源,以及它们生态系统中的项目。这一章将讲解 CNCF 社区、治理结构、云角色和 Kubernetes 认证路径。
第三章,容器入门,对容器技术进行了更深入的探讨,深入了解容器技术及其生态系统,并介绍了常用的 Docker 工具。本章还包括实践任务。
第四章,探索容器运行时、接口和服务网格,带你更深入地探索容器运行时、网络和接口,并了解服务网格。你将学习容器如何通过网络相互通信,Kubernetes 中有哪些容器接口,并了解服务网格及其应用。
第五章,使用 Kubernetes 编排容器,开始覆盖 KCNA 认证中最重要且可能是最难的部分——Kubernetes 基础。你将了解 Kubernetes 的功能及架构基础,它的 API、组件,以及最小的可部署单元——Pod。实践部分包括通过 minikube 本地安装 Kubernetes。
第六章,使用 Kubernetes 部署和扩展应用,将进一步探索 Kubernetes 的功能和丰富的生态系统。本章概述了其他 Kubernetes 资源及其用途;讨论如何使用 Kubernetes 实现应用的自愈和扩展,如何使用 Kubernetes 服务发现,以及如何使用 Kubernetes 运行有状态的工作负载。更多的 Kubernetes 实践练习是本章的重要组成部分。
第七章,使用 Kubernetes 进行应用部署和调试,展示了如何控制工作负载在 Kubernetes 上的部署、调度器如何工作,以及如何调试在 K8s 上运行的应用程序。本章同时涉及了 Kubernetes 基础知识和 KCNA 考试的云原生可观察性领域的内容。
第八章,遵循 Kubernetes 最佳实践,讨论了 Kubernetes 的网络和流量控制的网络策略、使用基于角色的访问控制(RBAC)限制访问、将 Helm 作为 K8s 的包管理工具等。本部分的最后一章包括了一些实践练习。
第九章,理解云原生架构,更详细地探讨了云原生的各个方面。本章分享了云原生及其架构的核心概念。此章节还涵盖了 KCNA 考试云原生架构领域的其他要求。
第十章,在云中实施遥测和可观察性,强调了基于观察来监控和优化云原生应用程序,以确保最佳性能并考虑成本的必要性。本章进一步涵盖了 KCNA 的云原生可观察性领域的要求。
第十一章,自动化云原生应用交付,讲解了云原生应用生命周期。你将学习云原生应用开发和交付的最佳实践,并了解自动化如何帮助更好地开发和更快地交付。
第十二章,通过模拟考试备考 KCNA 考试,分享了一些通过考试的技巧,并包括了两场模拟考试,帮助在最后的准备阶段测试知识。
第十三章,前进的道路,以关于如何推进和下一步该做什么的建议结束本书,帮助你成功开启云原生职业生涯。
为了最大限度地利用本书
你将需要安装多个工具,因此需要一个具有管理员权限的系统。
| 本书涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Kubernetes | Windows、macOS 或 Linux(推荐使用 macOS 或 Linux) |
| minikube | |
| Docker | |
| Prometheus |
如果你正在使用本书的数字版,我们建议你自己输入代码,或者通过本书的 GitHub 仓库访问代码(下文会提供链接)。这样做有助于避免与复制粘贴代码相关的潜在错误。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件,链接为 github.com/PacktPublishing/Becoming-KCNA-Certified。如果代码有更新,GitHub 仓库中的代码也会进行更新。
我们还提供了来自我们丰富图书和视频目录中的其他代码包,您可以在 github.com/PacktPublishing/ 进行查看!
下载彩色图片
我们还提供了包含本书中截图和图表的彩色图像 PDF 文件,您可以在此处下载:packt.link/OnZI3。
使用的约定
本书中使用了多种文本约定。
文本中的代码:表示文本中的代码单词、表格名称、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入等。以下是一个示例:“让我们删除我们在本章开头创建的旧 nginx-deployment。”
代码块设置如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kcna-pv-claim
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
当我们希望引起您对某个代码块中特定部分的注意时,相关的行或项会以粗体显示:
Normal Scheduled 85s default-scheduler Successfully assigned kcna/liveness-exec to minikube
Normal Pulled 81s kubelet Successfully pulled image "k8s.gcr.io/busybox" in 3.4078911s
Warning Unhealthy 41s (x3 over 51s) kubelet Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
Normal Killing 41s kubelet Container liveness failed liveness probe, will be restarted
Normal Pulling 11s (x2 over 85s) kubelet Pulling image "k8s.gcr.io/busybox"
粗体:表示术语、重要单词或词组。例如,重要要点可以用 粗体 显示。
提示或重要说明
如下所示
联系我们
我们欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件联系我们,邮箱地址是 customercare@packtpub.com,并在邮件主题中注明书名。
勘误表:虽然我们已尽力确保内容的准确性,但仍难免出现错误。如果您在本书中发现错误,我们将非常感激您能向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。
盗版:如果您在互联网上发现我们作品的任何非法版本,无论是何种形式,我们将非常感激您能提供相关位置或网站名称。请通过电子邮件 copyright@packt.com 将链接发送给我们。
如果您有兴趣成为作者:如果您在某个领域具有专业知识,并且有兴趣撰写或参与书籍的编写,请访问 authors.packtpub.com.
分享您的想法
阅读完 《成为 KCNA 认证专家》 后,我们希望听到您的想法!请点击这里直接进入此书的亚马逊评论页面 并分享您的反馈。
您的评论对我们和技术社区都非常重要,它将帮助我们确保提供优质内容。
下载本书的免费 PDF 副本
感谢您购买本书!
您是否喜欢随时随地阅读,但又无法随身携带纸质书籍?或者您的电子书购买的格式与您选择的设备不兼容?
不用担心,现在每本 Packt 的书都可以免费获得一份无 DRM 的 PDF 版本。
随时随地,在任何设备上阅读。直接从您喜爱的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
这些好处还不止于此,您还可以独享折扣、新闻简报以及每天在您的收件箱里获取的优质免费内容。
按照以下简单步骤获取这些好处:
- 扫描下方的 QR 码或访问以下链接

packt.link/free-ebook/9781804613399
-
提交您的购买证明
-
就这样!我们将直接将您的免费 PDF 和其他好处发送到您的电子邮件中
第一部分:云时代
在本部分中,您将快速了解云计算自问世以来的演变,传统 IT 在云之前的运作方式,以及今天的云原生景观。我们将讨论云原生计算基金会(CNCF)、Linux 基金会,并查看它们的项目和认证。
本部分包括以下章节:
-
第一章,从云到云原生和 Kubernetes
-
第二章,CNCF 和 Kubernetes 认证概述
第一章:从云计算到云原生与 Kubernetes
本章将展示过去 20 多年中计算如何演变,云计算是什么及其如何出现,以及 IT 环境随着容器的引入发生了怎样的变化。你将学习到基础设施即服务(IaaS)、平台即服务(PaaS)、软件即服务(SaaS)和函数即服务(FaaS)等基础知识,同时还会了解从单体架构到微服务架构的过渡,并初步了解 Kubernetes。
本章并没有直接对应特定的 KCNA 考试目标,但这些主题对任何希望将自己职业与现代基础设施挂钩的人来说至关重要。如果你已经熟悉基本术语,可以通过直接查看回顾问题来快速验证你的知识。如果不熟悉,不要惊讶于本章没有深入探讨,因为这是一个入门章节,我们将在后续章节中深入探讨所有主题。
本章将涵盖以下主题:
-
云计算与云之前(B.C.)
-
云计算的演变与云原生
-
容器与容器编排
-
单体应用与微服务应用
-
Kubernetes 及其起源
让我们开始吧!
云计算与云之前(B.C.)
云计算引发了一场重大革命并加速了创新,但在我们学习云之前,先来看看云时代之前的做法。
在使用云计算这个术语之前,一台物理服务器通常只能同时运行一个操作系统(OS)。这些系统通常只承载一个应用程序,这意味着两件事:
-
如果一个应用程序不被使用,那么它运行的服务器上的计算资源就会浪费
-
如果一个应用程序非常活跃并且需要更大的服务器或更多服务器,通常需要几天甚至几周的时间来采购、交付、布线并安装新硬件。
接下来,让我们看看计算中的一个重要方面——虚拟化。
虚拟化
虚拟化技术和虚拟机(VM)最早出现在 1960 年代,但直到 2000 年代初,像 XEN 和基于内核的虚拟机(KVM)这样的虚拟化技术才开始成为主流。
虚拟化允许我们在一台物理服务器上通过使用虚拟机监控程序(hypervisor)运行多个虚拟机(VM)。虚拟机监控程序是一种作为硬件资源(如 CPU 和 RAM)模拟器的软件。它实际上通过将底层物理服务器的处理器时间和内存划分给多个虚拟机,从而允许你共享这些资源。
这意味着每个虚拟机将非常类似于物理服务器,但使用虚拟 CPU、内存、磁盘和网络卡,而非物理组件。每个虚拟机还将运行操作系统,并可以在其上安装应用程序。下图展示了在同一物理服务器上运行两个虚拟机的虚拟化部署:

图 1.1 – 传统与虚拟化部署的对比
这种在所谓的客户虚拟机之间共享硬件资源的概念,使得硬件的利用变得更加高效,减少了计算资源的浪费。也就是说,我们可能不需要为了运行另一个应用程序而购买一台全新的服务器。
虚拟化带来的明显好处如下:
-
需要更少的物理硬件
-
需要更少的数据中心人员
-
更低的采购和维护成本
-
更低的能耗
此外,配置一个新的虚拟机(VM)只需几分钟,而不是等待几天或几周来购买新硬件。然而,要超越公司数据中心已有硬件的容量,我们仍然需要订购、配置并布线新的物理服务器和网络设备——但随着云计算的引入,这一切都发生了变化。
云
从最基础的层面来看,云就是按需虚拟化。它允许我们在客户请求时,作为一种服务生成可通过网络访问的虚拟机(VM)。
云计算
这就是将计算资源作为服务提供的方式,其中实际的硬件由云服务提供商拥有和管理,而非企业的 IT 部门。
云计算引发了计算领域的重大革命。如今,构建和运行应用程序以及虚拟机不再需要购买和管理自己的硬件。云服务提供商全面负责硬件采购、安装和维护,并通过在共享硬件上为数百上千个客户提供安全的服务来确保资源的高效利用。每个客户仅需为其所使用的资源付费。如今,常见的云类型包括以下三种:
-
公共云 – 最受欢迎的类型。公共云由第三方公司运营,任何付费客户都可以使用。公共云通常同时为成千上万的组织提供服务。公共云提供商的例子包括亚马逊网络服务(AWS)、微软 Azure 和 谷歌云平台(GCP)。
-
私有云 – 仅供一个通常较大的组织或企业使用。其操作和维护可能由该组织本身或私有云提供商完成。例子包括 Rackspace 私有云和 VMware 私有云。
-
混合云 – 这是公共云与私有云的结合,适用于某个组织拥有私有云,但同时也使用一些公共云服务的情况。
然而,云不仅仅是通过网络访问的虚拟机。云服务提供商提供了数十种、数百种服务。今天,您几乎可以立即请求并使用网络附加存储、虚拟网络设备、防火墙、负载均衡器、带有 GPU 或专用硬件的虚拟机、托管数据库等服务。
现在,让我们更详细地了解云服务如何交付和消费。
云计算和云原生的发展
除了今天可以找到的各种云服务外,云服务的提供方式也有所不同。通常可以区分四种云服务交付模型,以满足不同的需求:
-
IaaS – 最灵活的模型,提供基本服务:虚拟机、虚拟路由器、块设备、负载均衡器等等。这个模型也假设客户承担更多责任。IaaS 的用户可以访问他们的虚拟机,必须配置操作系统,安装更新,并设置、管理和保护他们的应用程序。AWS 弹性计算云(EC2)、AWS 弹性块存储(EBS)和 Google Compute Engine 虚拟机都是 IaaS 的示例。
-
PaaS – 通过免去安装操作系统更新或进行任何低级别维护的需求,帮助客户专注于应用程序的开发和管理。作为 PaaS 客户,您仍然需要负责您的数据、身份与访问以及应用程序生命周期。示例包括 Heroku 和 Google App Engine。
-
SaaS – 将责任进一步移离客户。通常,这些是完全托管的应用程序,即开即用,例如 Slack 或 Gmail。
-
FaaS – 一种在 2010 年左右出现的较新交付模型。如今也被称为无服务器。FaaS 的客户负责定义由事件触发的功能。这些功能可以使用流行的编程语言编写,客户无需担心服务器或操作系统管理、部署或扩展等问题。FaaS 的示例包括 AWS Lambda、Google Cloud Functions 和 Microsoft Azure Functions。
这些模型听起来可能有点复杂,让我们用汽车和交通工具做一个简单的类比。
本地传统数据中心 就像是拥有自己的车。你购买了它,并且负责它的保险和维护、坏件的更换、定期检查等等。
IaaS 更像是租车一段时间。你支付月租费,开车,给车加油,洗车,但你实际上并不拥有这辆车,当你不再需要它时,你可以把它还回去。
PaaS 可以与共享汽车进行类比。你不拥有汽车,不需要洗车、维护,甚至大多数时候不需要加油,但你仍然自己开车。
按照这个类比,SaaS 就像打出租车。你不需要拥有车甚至不需要开车。
最后,从用户的角度来看,无服务器或FaaS可以与公共汽车进行比较。你只需上车并直达目的地——无需维护、无需驾驶、无需拥有。
希望这能让事情更加明晰。传统的本地部署设置中,公司完全负责组织、硬件维护、数据安全等方面,而在云中适用的则是所谓的共享责任模型。
共享责任模型
定义了云服务提供商与云客户的责任。这些责任取决于提供的服务——在 IaaS 服务的情况下,客户的责任比 PaaS 或 SaaS 服务更多。例如,云服务提供商始终负责防止未经授权访问数据中心设施,以及确保电力供应和底层网络连接的稳定性。
以下图示形象地展示了不同责任之间的差异:

图 1.2 – 云交付模型比较
随着过去 20 年云技术和云服务提供商的发展,运行在云上的应用架构也发生了变化;一个新术语应运而生——云原生。大多数时候,它指的是一种架构方法,但你也会经常遇到云原生应用或云原生软件。
云原生
是一种在现代、动态基础设施(如云)上构建和运行应用的方式。它强调具有高弹性、可扩展性、高度自动化、易于管理和可观察性的应用工作负载。
尽管有云这个词,但这并不意味着云原生应用必须严格运行在公共、私有或混合云中。你可以开发一个云原生应用,并在本地运行它,例如使用 Kubernetes。
云原生不应与云服务提供商(CSPs)或简称云提供商混淆,也不应与云优先混淆,请记住以下几点:
云原生 ≠ CSP ≠ 云优先
为了完整性,让我们定义一下另外两个概念。
云服务提供商(CSP)是指提供云计算服务的第三方公司,例如 IaaS、PaaS、SaaS 或 FaaS。云优先(Cloud-first)仅仅意味着一种策略,在这种策略下,云是优化现有 IT 基础设施或启动新应用的默认选择。
如果这些定义目前还没有完全理解,也不用担心——我们将专门有一节来详细解释云原生的所有方面。现在,让我们简要介绍一下容器及其编排。
容器与容器编排
从高层次来看,容器是另一种轻量级虚拟化形式,也称为操作系统级虚拟化。然而,容器与虚拟机(VM)不同,具有各自的优缺点。
主要的区别在于,使用虚拟机时,我们可以在一台物理服务器上切分并共享多个虚拟机,每个虚拟机运行自己的操作系统。而使用容器时,我们可以在多个容器之间共享操作系统内核,每个容器都有自己的虚拟操作系统。让我们更详细地看看这个过程。
容器
这些是便携式的软件单元,包含应用代码、运行时、依赖项和系统库。容器共享一个操作系统内核,但每个容器可以拥有自己的隔离操作系统环境,带有不同的包、系统库、工具、存储、网络、用户、进程和组。
便携性很重要,需要详细说明。将应用程序打包成容器镜像后,可以保证它在另一台主机上运行,因为容器包含了自己的隔离环境。在另一台主机上启动容器不会干扰其环境或被容器化的应用程序。
容器的一个主要优势是,它们比虚拟机更加轻量和高效。容器消耗的资源(CPU 和 RAM)比虚拟机少,启动几乎是即时的,因为它们不需要启动一个完整的操作系统和内核。例如,如果一台物理服务器能够运行 10 个虚拟机,那么同一台物理服务器可能能够运行 30、40,甚至更多的容器,每个容器都有自己的应用程序(具体的数量取决于许多因素,包括工作负载类型,因此这些数字仅用于演示,并不代表任何公式)。
容器的磁盘大小也比虚拟机(VM)小得多,因为它们不会打包完整的操作系统和成千上万的库。容器镜像中仅包含带有依赖项的应用程序和一组最小的操作系统包。这使得容器镜像既小巧、便携,又容易下载或分享。
容器镜像
这些基本上是容器操作系统环境的模板,我们可以使用它们来创建多个具有相同应用程序和环境的容器。每次执行镜像时,都会创建一个容器。
从数字角度来看,一个流行的 Linux 发行版的容器镜像,如Ubuntu Server 20.04,大约为 70 MB,而相同的 Ubuntu Server 的 KVM QCOW2 虚拟机镜像大约为 500 MB。专门的 Linux 容器镜像,如Alpine,可能只有 5 到 10 MB,并提供安装和运行应用程序所需的最低功能。
容器与其运行的地方无关——无论是在物理服务器、内部虚拟机还是云端,容器都可以在这些位置中的任何一个上运行,只要有容器运行时的支持。
容器运行时
容器运行时是运行容器所需的特殊软件,它负责根据下载的容器镜像创建、启动、停止和删除容器。容器运行时的例子包括 containerd、CRI-O 和 Docker Engine。
图 1.3 展示了虚拟化和容器化部署的差异:

图 1.3 – 虚拟化与容器化部署的对比
现在,你可能会问自己,如果容器如此优秀,为什么还会有人使用虚拟机(VM),而且云服务提供商为什么仍然提供这么多虚拟机类型?
下面是虚拟机相比容器有优势的场景:虚拟机由于隔离性更强,提供了更好的安全性,因为它们不会直接共享相同的宿主机内核。这意味着,如果运行在容器中的应用程序被黑客攻破,那么黑客能够攻破同一宿主机上其他容器的可能性要比常规虚拟机高得多。
我们将在后面的章节中深入探讨操作系统级虚拟化背后的技术,并探索虚拟机和容器之间的底层差异。
随着容器的流行和广泛应用,容器的管理逐渐成为一个挑战,特别是在大规模管理容器时。行业需要一些工具来编排和管理基于容器的应用程序的生命周期。
这与公司和团队必须操作的容器数量不断增加有关,因为随着基础设施工具的不断发展,应用架构也在发生变化,从大型单体架构转变为小型、分布式、松耦合的微服务架构。
单体架构与微服务架构
为了理解单体应用和基于微服务的应用之间的差异,让我们回顾一个现实中的例子。假设一家企业运营着一个在线酒店预订业务,所有预订都是客户通过公司网站进行的,并且完成支付。
这种类型的 Web 应用程序的传统单体架构会将所有功能打包到一个复杂的软件中,这个软件可能包括以下内容:
-
客户仪表盘
-
客户身份与访问管理
-
根据条件搜索酒店
-
计费与支付供应商集成
-
酒店预订系统
-
票务与支持聊天
单体应用将所有业务和用户逻辑紧密耦合(打包)在一起,并且必须一起开发和更新。这意味着,如果需要修改计费代码,整个应用程序都必须进行更新。更新后,还必须进行仔细测试,并发布到生产环境中。每一个(即便是小的)变更都可能导致整个应用崩溃,并使得业务在较长时间内不可用。
在微服务架构下,同样的应用程序可以被拆分成几个小模块,通过网络相互通信,并各自完成自己的功能。例如,计费可以通过四个较小的服务来实现:
-
汇率转换器
-
信用卡供应商集成
-
银行电汇处理
-
退款处理
本质上,微服务是一组小型应用,每个应用负责完成自己的小任务。这些小型应用通过网络互相通信,并共同协作,成为一个更大应用的一部分。
下图展示了单体架构与微服务架构之间的差异:

图 1.4 – 单体架构与微服务架构的比较
通过这种方式,Web 应用的其他部分也可以拆分成多个独立的小型应用(微服务),它们通过网络进行通信。此方法的优点包括以下几点:
-
每个微服务可以由其独立的团队开发
-
每个微服务可以单独发布和更新
-
每个微服务可以独立于其他服务进行部署和扩展
-
单个微服务的故障只会影响应用的一个小部分功能
微服务是云原生架构的重要组成部分,我们将在第九章《理解云原生架构》中详细回顾微服务的优势与挑战。暂时让我们回到容器,探讨为什么它们需要进行编排。
当每个微服务被打包成容器时,容器的总数可能会很快达到几十个甚至几百个,特别是在大型和复杂应用中。在这样的复杂分布式环境下,情况可能迅速失控。
容器编排系统帮助我们管理大量容器。它通过将应用容器分组到部署中,并自动化以下操作,简化了容器的管理:
-
根据工作负载扩展微服务
-
发布微服务的新版本及其更新
-
根据主机的使用情况和需求调度容器
-
自动重启失败的容器或将流量切换到其他容器
目前,有许多容器和工作负载编排系统可供选择,包括以下几种:
-
Kubernetes
-
OpenShift(也称为Open Kubernetes 发行版(OKD))
-
Hashicorp Nomad
-
Docker Swarm
-
Apache Mesos
正如你从书名中已经知道的,我们将仅专注于 Kubernetes,并且不会在这五种方案之间做任何比较。事实上,Kubernetes 的市场份额遥遥领先,多年来,它已成为编排容器的事实标准平台。你可以充满信心地专注于学习 Kubernetes,至少目前可以忽略其他的容器编排方案。
Kubernetes 及其起源
让我们首先从简要的历史开始。Kubernetes 的名称源自希腊语,意思是领航员或舵手 —— 一个操纵船只的人(这也是为什么标志中有一个舵轮)。舵轮上有七根条,而数字七对 Kubernetes 具有特殊意义。最初负责 Kubernetes 的团队称其为七号项目 —— 这是根据著名电视系列《星际迷航》中的七个九分之七角色而命名的。

图 1.5 – Kubernetes 标志
Kubernetes 最初由 Google 开发,并于 2014 年作为开源项目发布。当时,Google 早已运行其服务于容器中超过十年,并且 Kubernetes 的发布引发了行业的另一场小革命。那时,许多企业已经意识到使用容器的好处,并需要简化大规模容器编排的解决方案。正如我们即将看到的,Kubernetes 成为了这个解决方案。
Kubernetes(K8s)
Kubernetes 是一个用于容器编排的开源平台。Kubernetes 具有可扩展和声明式 API,允许您自动达到资源的期望状态。它支持灵活的调度、自动扩展、滚动更新和基于容器的负载自愈。
(在线和文档中,经常可以遇到更短的缩写 K8s —— 其中八是“K”和“s”之间字母的数量。)
Kubernetes 继承了许多来自 Borg 的特性和最佳理念 —— Borg 是 Google 的一个内部容器集群管理系统,多年来支持运行数千个不同的应用程序。许多 Borg 工程师参与了 Kubernetes 的开发,并能够根据多年运营大规模容器的经验来解决相关痛点。
在初版发布后不久,Kubernetes 迅速吸引了开源社区的注意,并吸引了来自全球各地的许多才华横溢的贡献者。今天,Kubernetes 是 GitHub 上三大最大的开源项目之一(github.com/kubernetes),拥有超过 80,000 个星和 3,000 名贡献者。它还是第一个从云原生计算基金会(CNCF)毕业的项目,CNCF 是一个从 Linux 基金会分拆出来的非营利组织,旨在推动容器和云原生技术的发展。
Kubernetes 最重要的特性之一是期望状态的概念。Kubernetes 的运行方式是我们定义所需的应用程序容器状态,Kubernetes 将自动确保达到这个状态。Kubernetes 不断观察所有部署容器的状态,并确保此状态与我们请求的一致。
让我们考虑以下示例。假设我们在具有三个主机的 Kubernetes 集群上运行一个简单的微服务应用程序。我们定义了一个规格,要求 Kubernetes 运行以下内容:
-
两个相同的前端容器
-
三个相同的后端容器
-
两个用于数据持久化的容器
出乎意料的是,三个主机中的一个发生故障,导致在前端和后端上运行的两个容器不可用。Kubernetes 会观察到集群中主机的数量变化以及前端和后端容器的数量减少。Kubernetes 会自动在另外两个运行中的主机上启动一个前端容器和一个后端容器,将系统恢复到预期状态。这个过程被称为自我修复。
Kubernetes 能做的不仅仅是调度和重启失败的容器——我们还可以定义一个 Kubernetes 规格,要求根据当前的需求自动增加微服务容器的数量。例如,在前面的例子中,我们可以指定,在工作负载增加时,我们希望运行五个前端副本和五个后端副本。或者,在应用需求低时,我们可以自动将每个微服务容器的数量减少到两个。这个过程被称为自动扩缩。
这个例子展示了 Kubernetes 的基本功能。在第三部分,我们将深入探索更多 Kubernetes 特性,并亲自尝试其中的一些功能。
重要提示
虽然作为一个容器编排工具,Kubernetes 本身并没有容器运行时。相反,它与流行的容器运行时(如containerd)集成,并且可以在 Kubernetes 集群内与多个运行时一起工作。
你经常会看到 Kubernetes 集群的相关内容,因为一个典型的 Kubernetes 安装会被用来管理分布在多个主机上的数百个容器。单主机的 Kubernetes 安装仅适合用于学习或本地开发,而不适用于生产环境。
总结来说,Kubernetes 为大规模容器的采用铺平了道路,并且它是一个蓬勃发展的开源生态系统,每年都有新的项目从 CNCF 毕业。在本书中,我们将深入探讨 Kubernetes 的 API、组件、资源、功能和操作方面,并了解更多可以与 Kubernetes 一起使用以扩展其功能的项目。
概述
在本章中,我们学习了云计算和容器的概念,以及过去 20 到 30 年计算领域的演变。在云计算时代之前,传统的部署方式将一个或少量应用程序部署在每台物理服务器上,这导致了很多低效和资源浪费,硬件利用率低且拥有成本高。
当虚拟化技术出现时,使用虚拟机(VMs)可以在一个物理服务器上运行多个应用程序。这解决了传统部署的缺陷,并且能够更快速且成本显著降低地交付新应用程序。
虚拟化为通过四种不同模型提供的云服务铺平了道路:IaaS、PaaS、SaaS 和 FaaS 或无服务器。根据云服务和交付模型,客户的责任有所不同。
这种进步从未停止——现在,云原生作为一种构建和运行应用程序的方法已经出现。云原生应用程序设计和构建时强调可扩展性、弹性、易管理性和高度自动化。
近年来,容器技术发展迅速并获得了动力。容器在操作系统级别使用虚拟化,每个容器代表一个虚拟操作系统环境。与 VM 相比,容器更快速、更高效、更具可移植性。
容器使我们能够基于微服务架构开发和管理现代应用程序。与传统的单体应用程序相比,微服务更先进——一体化、巨兽应用程序。
虽然容器是运行云原生应用程序最有效的方式之一,但难以管理大量容器。因此,最好使用像 Kubernetes 这样的编排器来管理容器。
Kubernetes 是一种开源容器编排系统,起源于 Google,自动化容器的许多运维方面。Kubernetes 将根据提供的规格自动安排、启动、停止和重新启动容器,并根据当前需求增加或减少容器数量。Kubernetes 使得实施自愈和根据当前需求自动扩展成为可能。
问题
在每章末尾,您将找到总结性问题,以测试您的理解。问题可能有多个正确答案。正确答案可在附录的评估部分找到:
-
以下哪项描述了物理服务器上的传统部署方式(选两个)?
-
简易维护
-
未充分利用的硬件
-
低能耗
-
高前期成本
-
-
VM 相比容器有哪些优势?
-
它们更可靠
-
它们更具可移植性
-
它们更安全
-
它们更轻量级
-
-
描述 VM 和容器之间差异的是哪两个(选两个)?
-
VM 镜像较小而容器镜像较大
-
VM 镜像较大而容器镜像较小
-
VMs 共享操作系统内核而容器不共享
-
容器共享操作系统内核而 VMs 不共享
-
-
容器在哪个层级操作?
-
编排器层级
-
虚拟化层级
-
编程语言层级
-
操作系统级别
-
-
容器镜像通常包含以下哪两个内容(选两个)?
-
操作系统内核
-
一个最小化的 OS 库和软件包集合
-
图形化桌面环境
-
一个打包的微服务
-
-
与虚拟机相比,容器有哪些优势(选择多个)?
-
它们更安全
-
它们更轻量
-
它们更具可移植性
-
启动速度更快
-
-
启动和运行容器需要哪些软件?
-
容器运行时
-
一个虚拟机监控程序
-
Kubernetes
-
VirtualBox
-
-
以下哪些可以用于编排容器?
-
containerd
-
CRI-O
-
Kubernetes
-
无服务器
-
-
以下哪些是云服务交付模型(选择多个)?
-
IaaS, PaaS
-
SaaS, FaaS
-
DBaaS
-
无服务器
-
-
以下关于云原生的说法哪一项是正确的?
-
它是一种架构方法
-
它与云提供商相同
-
它类似于云优先
-
它是仅在云中运行的软件
-
-
以下哪些描述适用于云原生应用程序(选择两个)?
-
高度自动化
-
高可扩展性和高韧性
-
只能在私有云中运行
-
只能在公有云中运行
-
-
以下关于单体应用程序的说法哪一项是正确的?
-
它们容易更新
-
它们的组件通过网络相互通信
-
它们包含所有的业务逻辑和接口
-
它们可以轻松扩展
-
-
以下哪些关于微服务的说法是正确的(选择多个)?
-
它们只能用于后端
-
它们作为更大应用的一部分协同工作
-
它们可以由多个团队开发
-
它们可以独立部署
-
-
以下哪些可以通过 Kubernetes 完成(选择多个)?
-
在故障时自我修复
-
自动扩展容器
-
启动虚拟机
-
在不同主机上调度容器
-
-
哪个项目为 Kubernetes 提供了灵感?
-
OpenStack
-
Docker
-
Borg
-
OpenShift
-
第二章:CNCF 和 Kubernetes 认证概览
在这一章中,你将了解云原生计算基金会(CNCF)及其母组织——Linux 基金会。我们将了解这些基金会背后的内容,它们是如何出现的,以及它们的生态系统中托管了哪些项目。我们还将讨论 CNCF 社区、治理结构、云角色以及 Kubernetes 认证路径。
虽然这是本书中技术性最弱的一章,但你将在此学到的内容涵盖了 KCNA 认证中的大约一半云原生架构领域的知识点,所以确保你在本章结束时回答所有的复习问题。
在这一章中,我们将涵盖以下主题:
-
开源软件(OSS)和开放标准
-
Linux 和 CNCF
-
CNCF 社区与治理结构
-
云角色与人物画像
-
Kubernetes 认证路径
让我们开始吧!
开源软件和开放标准
如果你曾经从事过任何 IT 职位,那么你很有可能接触过开源软件(OSS)这一术语,了解它的含义,并且很可能已经使用过其中的一些软件。
什么是开源软件(OSS)?
开源软件是指软件的源代码是公开可访问的,任何人都可以研究、修改并用于任何目的。
软件不能仅仅因为它可以在互联网上轻松找到或可以从暗网下载而被视为开源软件。软件被视为开源,当它是根据某种开源许可证发布的,比如 Apache 2.0 或 GNU 通用公共许可证 v3。那些许可证赋予用户修改源代码的权利,甚至可以将其用于商业用途。没错——你可以使用开源软件来构建新的软件并出售,而无需向任何人支付许可费用。或者,你也可以修改现有的开源软件,增加新特性,并将其出售或提供相关的支持服务。
虽然开源软件的历史可以追溯到计算机早期的日子,但真正诞生其中最成功的开源软件是在 90 年代初。没错,你猜对了——我们在谈论的是 Linux。
Linux 内核是 1991 年由Linus Torvalds启动的开源项目中最著名的例子之一。三十年来,该项目吸引了大量志愿者和热心的程序员,他们愿意无偿贡献自己的时间、技能和精力。截至写作时,已经有超过 15,000 人贡献了他们的时间、技能和努力,共同打造了一个核心操作系统,这个操作系统支持了全球 100%的超级计算机和大约 95%的世界服务器。
有很多成功的开源项目——Kubernetes、OpenStack、Terraform、Ansible、Prometheus 和 Django 都是开源的。即使是像谷歌这样拥有强大工程资源的公司,也意识到了开源社区的力量,以及开源生态系统如何推动项目发展。这正是 Kubernetes 项目的经历。
自 Kubernetes 早期以来,已经吸引了众多热情的工程师,并成为 CNCF 孵化的第一个项目。但在我们继续学习 CNCF 的更多内容之前,让我们讨论另一个重要话题——开放标准。
开放标准
2015 年,随着 CNCF 的成立,另一个重要事件发生了——开放容器倡议(OCI)开始启动。
OCI
OCI 是一个开放的治理结构,旨在定义围绕容器格式和运行时的开放行业标准。
OCI 拥有一个技术社区,行业参与者可以在其中贡献,帮助构建中立、可移植且开放的容器规范。
它还提供了参考实现和工具,兑现容器在应用程序可移植性方面的承诺。这些标准帮助我们避免了供应商锁定(当客户被迫继续使用某个产品或服务,因为很难切换到其他供应商时)并帮助确保不同的项目可以顺利集成与协作。例如,正如我们在上一章所学,容器运行时并不属于 Kubernetes 的一部分,因此我们需要确保不同的容器运行时与 Kubernetes 完全兼容。
在撰写本文时,OCI 为容器生态系统提供了三个重要的规范:
-
镜像规范(image-spec)定义了如何构建和打包应用程序为符合 OCI 标准的容器镜像
-
运行时规范(runtime-spec)定义了容器的生命周期和执行环境
-
分发规范(distribution-spec)定义了通过所谓的注册表促进和标准化容器镜像分发的协议
那么,OCI 标准为什么如此重要?
让我们来看一个简单的交通运输类比。假设每个汽车制造商都有自己固定车轮与车轴连接的方法。如果轮胎制造商没有进行标准化,他们会很难为每个品牌和型号生产车轮。但由于大家都同意采用特定的车轮尺寸参数,如宽度、直径、螺栓孔数量等,这使得制造商能够生产适配任何市场上汽车型号的车轮。因此,你可以根据汽车的规格购买车轮,并且有信心它们能够匹配你的车。
类似于容器,我们有镜像、运行时和分发标准,这些标准允许任何人开发 100%兼容的软件。OCI 标准的建立促进了容器生态系统的进一步扩展,带来了新的软件。在 OCI 出现之前,构建容器镜像只有一种方式——使用 Docker。今天,我们有了像Kaniko、Podman、Buildah等项目。
目前,你不需要知道这些差异的具体细节——只需要记住,OCI 通过提供开放规范,标志着容器及其生态系统发展的一个重要节点。在接下来的章节中,我们将深入探讨 Docker 和 OCI 标准的某些方面。
根据 OCI,它并不寻求成为一个营销组织,也不定义完整的技术栈或解决方案要求——它致力于避免标准化正在进行创新和讨论的技术领域(github.com/opencontainers/tob/blob/main/CHARTER.md)。帮助建立 OCI 并在其倡议中仍然发挥重要作用的组织是 Linux 基金会。与许多开源项目一样,志愿者们在该领域工作,并希望贡献他们的时间,将技术推向下一个层次,或者弥合现有项目之间的差距。
我们已经在这里提到过 CNCF 和 Linux 基金会几次。现在,让我们更深入了解它们。
Linux 和 CNCF
Linux 基金会是一个非营利组织,成立于 2000 年,源于开放源码开发实验室与自由标准组织的合并。基金会最初是为了标准化、推广和支持 Linux 的采纳而创建的,但自那时以来,它在开源社区中的作用已大大扩展。
今天,Linux 基金会的支持成员包括许多《财富》500 强公司,如谷歌、IBM、三星、Meta 等。基金会除了 Linux 内核外,还主持许多项目。这些项目包括 汽车级 Linux、Ceph(存储)、XEN(虚拟机监控器)、实时 Linux、OpenAPI Initiative(OAI)等。即使你之前从未听说过这些项目,也不必担心——你在考试中不会被问到这些内容。
近年来,Linux 基金会通过会议、认证、培训和新计划扩展了其项目。其中一个计划是 云原生计算基金会(CNCF),该基金会于 2015 年成立。
CNCF
2015 年 7 月 21 日成为整个开源社区的一个重要日期——Kubernetes 1.0 发布了。随着发布,作为 K8s 背后的关键推动力和贡献者,谷歌与 Linux 基金会合作,成立了 CNCF。Kubernetes 成为了新基金会的种子技术和第一个孵化项目。CNCF 的使命是推动容器和云原生技术的发展,并与行业对接(www.cncf.io/about/who-we-are/):
“基金会的使命是让云原生计算普及。”
云原生技术使组织能够在现代动态环境中构建和运行可扩展的应用程序,如公共云、私有云和混合云。容器、服务网格、微服务、不变基础设施和声明式 API 就是这种方法的典型例子。
这些技术使得松耦合系统变得更具弹性、可管理和可观察。结合强大的自动化,它们使工程师能够频繁且可预测地进行高影响力的变更,同时最小化工作量。
云原生计算基金会通过培育和维持一个开源、供应商中立的项目生态系统,推动这一范式的采纳。我们将最先进的模式民主化,使这些创新对每个人都可访问。”
我们在第一章《从云到云原生与 Kubernetes》中讲解了云计算、容器和微服务的基础知识,接下来的章节将深入探讨这些以及 CNCF 使命声明中提到的其他话题。
如今,CNCF 得到了超过 450 个成员的支持,并在云原生生态系统中发挥着重要作用。它提供治理并支持开源项目,使其成熟并确保其处于生产就绪状态。
说到成熟度,CNCF 有三个显著的级别:
-
沙盒:这是早期阶段项目的入口点,这些项目可以为 CNCF 使命增值。新项目如果与现有项目互补,可能会与其对齐。
-
孵化:这是一个已在生产环境中成功使用的项目,拥有持续的代码提交和贡献流,同时具有文档、规范和版本控制方案。
-
毕业:这是一个拥有来自多个组织的贡献者、遵循核心基础设施倡议最佳实践,并通过了独立第三方安全审核的项目。该项目还应定义治理和贡献者流程,并且证明其已被真实用户使用。
每个由 CNCF 托管的项目都有一个相应的成熟度级别。项目通过展示其获得了最终用户采用、健康的代码变更频率,以及来自 不同组织的贡献者,来提高其成熟度。
此外,所有 CNCF 项目必须遵守IP 政策并采用行为规范。行为规范定义了可接受的行为以及不可接受的行为,以创建一个积极和富有同理心的协作环境。另一方面,IP 政策关注知识产权,决定应适用哪种开源许可证(通常,源代码使用 Apache 2.0 许可证,文档和图片使用 Creative Commons Attribution 4.0 International 许可证)。
CNCF 社区与治理
随着 Kubernetes 等开源项目的势头不断增长,它们吸引了更多的贡献者,这总是一个好兆头。然而,较大的社区可能会失控,并在没有适当治理的情况下迅速变得 混乱。虽然 CNCF 不要求其托管的项目遵循任何特定的治理模型,但为了使项目毕业,必须明确规定治理和提交者流程。CNCF 遵循 最小可行治理 原则,这意味着项目自我治理,CNCF 机构仅在需要帮助或项目出现问题时介入。
说到其结构,CNCF 有三个主要机构:
-
管理委员会 (GB): 负责 CNCF 的市场营销、预算和其他商业决策
-
技术监督委员会 (TOC): 负责定义和维护技术愿景,批准新项目,并根据反馈调整现有项目
-
终端用户社区 (EUC): 提供来自终端用户和组织的反馈,以改进云原生生态系统中的整体体验
TOC 还决定一个项目是否达到了更高的成熟度水平。项目可以在孵化阶段无限期停留,但通常期望在 2 年内毕业。
如你所知,Kubernetes 是 CNCF 中第一个孵化的项目,也是第一个在 2018 年达到 毕业 状态的项目。没错,Kubernetes 跳过了沙盒阶段,因为它是在 CNCF 成立的同时首次发布的。自那时以来,已有 10 多个项目毕业,约有 100 个项目目前处于孵化或沙盒阶段。在下一部分,我们将探讨云和云原生中常见的角色和人物。
云角色和人物
云原生不仅仅是关于技术和架构,还涉及人员和工作环境中高效的协作。因此,在使用云基础设施和云原生生态系统的组织中,常常会有一些特定的职位和角色。了解这些角色以及它们的职责是 KCNA 考试的要求。
在采用云和云原生的现代组织中,可以遇到以下角色:
-
云架构师或云解决方案架构师
-
DevOps 工程师
-
DevSecOps 工程师
-
FinOps 工程师
-
站点可靠性工程师 (SRE)
-
云工程师
-
数据工程师
-
全栈开发工程师
这个列表并不详尽,你有时可能会看到这些角色的不同变体,但这应该能给你一个大致的概念。现在,让我们明确这些角色和职责之间的区别:
- 云(解决方案)架构师:不出所料,架构师负责设计云基础设施和云原生应用程序的架构。它必须具有高度的韧性、可观察性、可扩展性,并且具有较高的自动化程度。
云架构师通常还负责云战略和选择云提供商(公有云、私有云或混合云)以及合适的服务(IaaS/PaaS/SaaS/无服务器架构)。架构师角色要求具备广泛的技术和非技术领域知识。例如,他们必须了解资本支出或前期成本(CAPEX)与运营支出或简而言之是运行成本(OPEX)的区别。传统数据中心是高 CAPEX 的典型例子,而公有云则是零或几乎为零的 CAPEX,成本大多为运营成本(OPEX)。
-
DevOps 工程师:Dev代表开发,而Ops代表运营。DevOps 工程师是能够弥合开发人员与运营人员之间差距的人。通常,DevOps 工程师既有软件开发经验,也有系统管理员或基础设施工程师的经验。这些知识使他们能够有效地自动化云中的基础设施以及整个应用生命周期,包括开发、测试和发布。DevOps 工程师通常需要至少掌握一种编程语言(例如 Python、Ruby 或 Golang),以及一些自动化工具(如 Terraform、Ansible、Puppet 等)和持续集成与持续部署/交付(CI/CD)系统。如今,你通常也会看到 Kubernetes 经验作为 DevOps 岗位的要求之一。DevOps 文化强调学习优于责备,倡导共享责任和强有力的跨团队协作,并促进持续的反馈循环。
-
DevSecOps 工程师:这个角色与 DevOps 工程师非常相似,但更加注重安全性。在 DevSecOps 中,安全性在应用和基础设施生命周期的早期就开始介入,并且需要与安全团队密切合作。
-
FinOps 工程师:Fin代表财务,而Ops代表运营。FinOps 工程师使团队能够跟踪预算、提供透明度,并在云端进行成本优化。这个角色要求深入了解云端的各种定价模型和服务,以找到最佳和最具成本效益的解决方案。
-
网站可靠性工程师 (SRE):SRE 的职责包括维护和优化云基础设施和应用程序。SRE 在某种程度上与 DevOps 相似,但更侧重于运营部分,尤其是满足应用程序可用性要求或在服务水平协议 (SLAs) 或 服务级目标 (SLOs) 中定义的目标。SLA 规定了服务提供商与客户之间在质量、可用性和责任方面的承诺水平。SLO 是 SLA 的具体可衡量特征,如可用性或响应时间。SRE 经常参与值班轮换(即,他们随时准备在发生事故时作出反应)。
-
云工程师:这个角色与 DevOps 类似,但侧重于云服务提供商的云特性或服务。通常,DevOps 工程师的角色需要更广泛的技能,而云工程师则需要更深入了解特定的公共云服务。
-
数据工程师:在这个角色中,工程师需要处理数据保护、数据存储、性能和可用性要求,以及用于数据分类、保留和分析的各种工具。随着企业积累越来越多的数据,他们需要有效利用这些数据。因此,数据工程师目前的需求量很大。
-
全栈开发者:这是一个广泛的角色,开发者需要能够处理应用程序的前端部分(例如,用户界面 (UI))和后端部分。后端是一个通用术语,描述的是不对终端用户可见或无法访问的软件实现。有时,全栈开发者还可能涉及一些云基础设施和 DevOps 工具的基础经验。
虽然这些角色和职位在全球范围内的需求极高,但如果你来自完全不同的背景,或者刚刚从大学毕业且没有相关的工作经验,获得这些职位的机会可能并不容易。获得认证可以使你的个人资料脱颖而出,并大大提高你获得面试机会的几率。在本书的最后,你将找到一些职业建议,但现在让我们先看看 CNCF 提供了哪些 Kubernetes 认证以及认证路径是怎样的。
Kubernetes 认证路径
在撰写本文时,CNCF 提供了四个 Kubernetes 认证考试:
-
Kubernetes 和云原生 助理 (KCNA)
-
认证 Kubernetes 应用程序 开发者 (CKAD)
-
认证 Kubernetes 管理员 (CKA)
-
认证 Kubernetes 安全 专家 (CKS)
CKA 是第一个于 2017 年发布的考试,原有效期为 2 年,但后来有效期延长至 3 年。CKAD 和 KCNA 的有效期也为 3 年;只有 CKS 考试通过后有效期为 2 年。
在所有考试中,CKS 是最难的,要求考生持有有效且未过期的 CKA 认证,以证明他们已经具备足够的 K8s 专业知识。这意味着虽然你可以随时购买 CKS,但除非你先通过 CKA,否则无法参加此考试。所有其他考试都可以按任意顺序进行;然而,推荐的路径如下图所示:

图 2.1 – CNCF Kubernetes 认证路径
KCNA 是列表中最简单的考试,也是唯一不需要动手操作的考试——即,它是选择题。其他所有认证都需要动手操作,并要求你在多个 K8s 集群上通过终端完成活动。
但不要对 KCNA 考试抱有过高期望。事实上,在云领域的入门级认证中,它是一个相对较难的认证。即使你在相关领域工作了几年,没有任何 Kubernetes 经验和一些准备,通常也不太可能通过考试。在尝试实际考试之前,确保你已经回答完所有的回顾性问题,并完成了本书结尾提供的模拟考试。
以下是关于 KCNA 考试的一些重要信息:
-
60 道选择题
-
90 分钟完成考试
-
需要 75%及以上的分数才能通过
-
考试会在约 24 小时内自动评分
-
可在任何地方在线参加(需要摄像头和麦克风)
-
价格包括一次免费重考
以下是 KCNA 考试中测试的领域:
-
Kubernetes 基础知识(46%)
-
容器编排(22%)
-
云原生架构(16%)
-
云原生可观察性(8%)
-
云原生应用交付(8%)
如你所见,Kubernetes 占据了考试的主要部分。因此,本书的最大篇幅(第三部分,学习 Kubernetes 基础知识)专门讲解 K8s。除此之外,KCNA 考生应能够确认自己对云原生及其生态、项目和实践的概念性知识。这包括上一章中的高层次定义,这些内容可能会在 KCNA 中被提问。
其他三个考试专注于与 Kubernetes 相关的不同方面。CKAD 考试测试应用部署和管理方面,而 CKA 更侧重于 Kubernetes 的搭建和管理。然而,这些考试涉及许多共同领域,如果你通过了其中一个,经过一些额外的准备,你也能通过另一个。最后,CKS 聚焦于安全性,需要在 Kubernetes 方面有丰富的经验。
总结
在本章节中,我们了解了开源软件(OSS)以及开放标准在 OCI 方面的重要性。OCI 定义了镜像、运行时和分发规范,使任何人都能开发完全兼容的容器软件。例如,Kubernetes 不包括自己的容器运行时;相反,它实现了对标准化运行时接口的支持,使其能够与多种容器运行时协同工作。开放的、定义明确的标准为云原生生态系统和 CNCF 中的许多新项目铺平了道路。
接下来,我们回顾了 CNCF 和 Linux 基金会的历史。CNCF 是在 Kubernetes 的第一个版本发布时成立的,并且成为了第一个孵化的项目。CNCF 将项目成熟度分为三个级别:沙箱、孵化和毕业。
CNCF 有三个主要的组织:管理委员会(GB)、技术监督委员会(TOC)和最终用户社区(EUC)。TOC 负责决定 CNCF 项目的成熟度。一个项目达到毕业状态的要求之一是必须有定义的治理和提交者流程。
云原生需要合适的人来做工作,这一点得到了许多市场上需求量很大的角色和身份的支持。我们已经看过并比较了不同的角色,以理解云架构师的责任与 DevOps 工程师或全栈开发人员的区别。
最后,我们回顾了 Kubernetes 认证路径,并深入研究了你正在准备的 KCNA 考试。确保在进入下一个章节之前回答所有复习问题,我们将在下一个章节中探讨 Docker 和运行容器。
问题
在我们总结时,以下是一些问题,供你测试本章内容的理解。你可以在附录中的评估部分找到答案:
-
以下哪些是 CNCF 中有效的项目成熟度状态(请选择多个)?
-
沙箱状态
-
已发布状态
-
毕业状态
-
孵化状态
-
-
哪个组织是为了建立容器行业标准而成立的?
-
开放容器基金会
-
云原生容器倡议
-
云原生容器基金会
-
开放容器倡议
-
-
以下哪些要求必须满足,CNCF 项目才能达到毕业状态(请选择多个)?
-
为未来 3-5 年制定项目开发和维护计划
-
拥有实际用户和定义的治理及提交者流程
-
拥有来自多个组织的贡献者
-
遵循核心基础设施倡议的最佳实践
-
-
以下哪个 CNCF 组织决定一个项目是否达到了另一个成熟度水平?
-
最终用户社区(EUC)
-
管理委员会(GB)
-
技术监督委员会(TOC)
-
技术监督委员会(TOC)
-
-
以下哪些规范是 OCI 提供的(请选择多个)?
-
镜像规范
-
运行时规范
-
执行规范
-
分发规范
-
-
以下哪些是 CNCF 项目在任何成熟阶段所需的(选择两个)?
-
接受 CNCF 行为准则
-
接受 CNCF 知识产权政策
-
接受 GNU GPL v.3 许可证
-
接受 Linux 基金会作为项目所有者
-
-
以下哪些是 DevOps 文化强调的(选择多个)?
-
共享责任
-
学习而非责备
-
强大的跨团队协作
-
开发人员应该遵循运维团队
-
-
以下哪个组织的使命是推动容器和云原生技术的发展,并使行业保持一致?
-
Linux 基金会
-
开放容器倡议
-
云原生容器基金会
-
云原生计算基金会
-
-
以下哪些可能是云架构师的职责之一(选择两个)?
-
选择云服务提供商和合适的服务
-
设计云基础设施架构
-
将应用程序部署到生产环境
-
在云中维护应用程序
-
-
DevOps 和 DevSecOps 工程师有什么区别?
-
DevOps 只负责运维
-
DevSecOps 只处理安全方面的问题
-
DevSecOps 类似于 DevOps,但更注重安全性
-
DevSecOps 必须拥有与安全相关的认证
-
-
以下哪些描述了 SRE(选择两个)?
-
SRE 需要与云服务提供商现场合作
-
SRE 不参与任何操作
-
SRE 负责维护和优化基础设施和应用程序
-
SRE 需要确保应用程序的 SLA 和 SLO 得以满足
-
-
云工程师和 DevOps 工程师有什么不同(选择两个)?
-
DevOps 工程师对云一无所知
-
云工程师对云服务有更深入的了解
-
DevOps 工程师通常具备更广泛的技能
-
DevOps 工程师需要值班,而云工程师则不需要
-
-
拥有全栈开发人员团队有什么好处?
-
全栈开发人员可以处理前端和后端工作
-
全栈开发人员将应用程序部署到云端
-
全栈开发人员编写代码更快
-
全栈开发人员编写更简洁的代码
-
-
为什么拥有开放标准很重要(选择两个)?
-
他们帮助我们避免厂商锁定
-
它们使不同的软件兼容
-
他们确保软件没有漏洞
-
它们防止通过软件获利
-
-
以下哪些技术是 DevOps 工程师可能会使用的(选择多个)?
-
前端技术(例如,JavaScript、HTML 和 CSS)
-
自动化工具(例如,Terraform、Ansible 和 Puppet)
-
CI
-
CD
-
第二部分:执行容器编排
在本部分中,您将了解容器的起源、它们的实用性,以及导致它们在众多组织(从小型初创公司到全球企业)广泛采用的原因。我们将涵盖理论和实践方面的内容,如如何构建和运行容器,以及为何和何时需要编排。
本部分包含以下章节:
-
第三章,容器入门
-
第四章, 探索容器运行时、接口和服务网格
第三章:开始使用容器
本章我们将更深入地了解容器,深入探讨容器技术和容器生态系统,并发现常用的工具。
一句中国古老的谚语说:“我听到的,我忘记;我看到的,我记住;我做的, 我理解。”
从本章开始,我们将亲自动手,尝试构建镜像和运行容器,以获得更深入的理解和第一手实践经验。尽管 KCNA 是一项选择题考试,但亲自实践非常重要,这段经验将对你未来的学习有所帮助。不要只读代码片段——确保完全执行它们,尤其是在你没有容器使用经验的情况下。 你需要一台运行最新版本的 Linux、Windows 或 macOS 的计算机,以及一个可用的互联网连接。
本章我们将涵盖以下主题:
-
介绍 Docker
-
探索容器技术
-
安装 Docker 并运行容器
-
构建容器镜像
技术要求
本章使用的所有示例文件和代码片段已上传到本书的 GitHub 仓库:github.com/PacktPublishing/Becoming-KCNA-Certified。
介绍 Docker
Docker 已经存在多年,因此你可能听说过它。对许多人来说,Docker 这个名字本身就是 容器 的代名词。然而,叫做 Docker 的东西有很多,容易让人混淆:
-
Docker Inc.
-
Docker Engine
-
dockerd(Docker 守护进程)
-
Docker CLI
-
Docker Hub
-
Docker Registry
-
Docker Swarm
-
Docker Compose
-
Docker Desktop
-
Dockershim
让我们逐一澄清这些内容。
首先,Docker 公司(作为一家公司)并没有发明容器技术,但它创建了易于使用的工具,帮助推动了容器技术的广泛采用。该公司成立于 2008 年,最初名为 dotCloud。
Docker Engine 是一个开源软件包,用于构建和容器化应用程序。它是一种客户端-服务器软件,由一个名为 docker 的守护进程服务组成。
容器化
容器化是将软件应用代码与依赖项(如库、框架等)一起打包到容器中的过程。容器可以在不同环境之间独立移动,而不依赖于基础设施的操作系统。
当你安装 Docker 引擎时,你实际上安装了两样东西——dockerd 服务和 CLI。dockerd 会持续运行并监听命令,对容器进行操作,如启动新容器、停止现有容器、重启容器等。这些命令可以通过 docker CLI 或常用工具如 curl 来发出。本章示例将使用 docker CLI。
下一项是 Docker Hub (hub.docker.com/),一个公共容器镜像注册表。如你所知,容器镜像是一个预定义的静态模板,我们用它作为启动新容器的基础。那么,我们从哪里获取镜像呢?Docker Hub 就是其中一个地方。它是 Docker 公司提供的在线仓库服务,成千上万的容器镜像存放在其中,包含不同的环境(Ubuntu、Centos、Fedora 和 Alpine Linux),以及流行的软件如 Nginx、Postgres、MySQL、Redis 和 Elasticsearch。Docker Hub 允许你查找、分享和存储容器镜像,这些镜像可以通过互联网轻松拉取(下载)到你需要创建新容器的主机上。值得一提的是,Docker Hub 并不是唯一的此类服务——其他的服务还包括 Quay (quay.io/)、Google Container Registry (cloud.google.com/container-registry) 和 Amazon Elastic Container Registry (aws.amazon.com/ecr/)。
接下来我们来看 Docker Registry,它现在由 CNCF 管理,作为一个名为 Distribution 的项目。它是一个开源的服务器端应用程序,可以用于存储和分发 Docker 镜像。与 Docker Hub 的主要区别在于,Docker Registry 是你可以直接取来、安装并在你的组织内运行的软件,而且无需付费,而 Docker Hub 是一个 服务式注册表,提供一些额外的付费功能。Docker Registry 可用于存储和提供你的 开发 团队正在开发的容器镜像。
接下来是 Docker Swarm,它的目的在于集群管理和容器编排。Swarm 与 Kubernetes 类似;然而,它仅与 Docker 引擎兼容(意味着不支持其他容器运行时),并且与 Kubernetes 相比,功能要少得多且定制性有限。这也是它没有像 Kubernetes 那样广泛采用的原因。
Docker Compose 是另一个 Docker 工具,它允许你定义和共享多容器应用程序的规格。使用 Compose,你可以在一个 YAML 格式的文件中定义多个需要相互通信的容器,作为一个应用的一部分。例如,你可以启动一个包含数据库的 Django Web 应用,运行在两个容器中,并定义数据库必须先启动,同时暴露容器的某些端口。Compose 可能对于一些本地开发使用 Docker 很有帮助,但它与 Kubernetes 不兼容,因此我们不打算进一步讨论它。
Docker Desktop 是一个结合了 Docker 引擎、docker CLI、Docker Compose、Kubernetes 以及一些其他工具的 Windows/macOS 工具,它配有 图形用户界面(GUI)。没错——Docker Desktop 甚至将 Kubernetes 和 K8s 客户端打包在一起,供本地开发使用。Docker Desktop 对非商业用途免费,但如果在组织中使用,则需要付费。也有一个适用于 Ubuntu 和 Debian Linux 的测试版。
Dockershim 是一种软件兼容层,旨在使 Kubernetes(准确地说是其 kubelet 组件)能够与 dockerd(Docker 守护进程)进行通信。正如你可能记得的那样,Kubernetes 没有自己的容器运行时(用于执行基本容器操作的软件,如启动、停止和删除)。在早期版本中,Kubernetes 仅支持 Docker 来操作容器。随着容器生态系统的发展,dockerd 并没有一个符合 OCI 标准的接口,因此创建了 Kubernetes 与 dockerd 之间的翻译层,称为 Dockershim。自 Kubernetes 1.20 版本起,Dockershim 已被弃用,并且在 1.24 版本中完全从 K8s 中移除。
最后,我们已经到达了列表的末尾。尽管多年来出现了许多替代方案,Docker 引擎和 Docker 工具仍然被全球成千上万的开发团队和组织积极使用。下图展示了如何使用 Docker CLI 与 Docker 守护进程进行通信,后者从 Docker Registry 中获取镜像并在本地创建容器:

图 3.1 – Docker 架构
在接下来的章节中,我们将安装一些 Docker 工具,看看它的实际操作,最终亲手操作容器。
探索容器技术
在进入实践部分之前,我们仍需要弄清楚容器背后的技术以及是谁创造了它。Linux 容器背后的技术其实早在很久之前就已经开发出来,并且基于两个核心内核功能:
-
cgroups(控制组)
-
命名空间
cgroups
cgroups 是一种机制,允许将进程组织成层次结构的组。这些组如何使用资源(如 CPU、内存、磁盘 I/O 吞吐量等)可以被限制、监控和控制。
cgroups 最初由谷歌的工程师开发,并于 2007 年首次发布。自 2008 年初以来,cgroups 功能被合并到 Linux 内核中,并一直存在至今。2016 年,发布了 cgroups 的修订版本,现在称为 cgroups 版本 2。
即使在 cgroups 之前,Linux 的命名空间功能在 2002 年就已经开发出来。
Linux 内核命名空间
这个 Linux 特性允许你以某种方式划分内核资源,让一组进程看到一组资源,而另一组进程看到不同的资源。Linux 命名空间用于将进程相互隔离。
命名空间有不同类型,每种类型有其独特的属性:
-
root(超级用户)但仅限于其自身的命名空间。 -
1感谢命名空间的存在。 -
网络命名空间:这使你能够为一组进程运行独立的网络堆栈,拥有自己的路由表、IP 地址、连接跟踪等。
-
挂载命名空间:这使你能够在命名空间内拥有独立的挂载点。这意味着命名空间中的进程可以拥有不同的挂载,而不会影响主机文件系统。
-
进程间通信(IPC):这使你能够隔离 Linux 的进程间通信机制,如共享内存、信号量和消息队列。
-
UNIX 时间共享(UTS):这使你能够为不同的进程设置不同的主机名和域名。
这听起来可能很复杂,但别担心——命名空间和 cgroups 并不属于 KCNA 考试的内容,因此你不需要了解每个命名空间及其作用。然而,由于它们是容器技术的核心,了解它们的基本概念会有所帮助,而且如果你能够解释容器是如何在底层工作的,还会得到额外的加分。
总结一下,cgroups 和 命名空间 是容器的构建块。cgroups 允许你监控和控制进程(或一组进程)的计算资源,而命名空间则在不同的系统级别隔离进程。这两种功能也可以在没有容器的情况下使用,很多软件都利用了这些功能。
足够的理论,接下来让我们动手实践!在下一部分,我们将安装 Docker 工具并启动第一个容器。
安装 Docker 并运行容器
如果你使用的是 Windows 或 macOS,可以从 https://docs.docker.com/desktop/ 下载并安装 Docker Desktop。如果你使用的是最新版本的 Ubuntu Linux,也有 Docker Desktop 的版本可供下载。如果你使用的是其他 Linux 发行版,则需要安装 Docker Engine。你可以在 https://docs.docker.com/engine/install/ 上找到针对你的发行版的详细安装指南。请选择一个 稳定 版本进行安装。
如果你重新启动计算机,请确保 Docker Desktop 正在运行。在 Linux 上,你可能需要在终端中执行以下代码:
$ sudo systemctl start docker
如果你希望系统重启时自动启动,你可以运行以下命令:
$ sudo systemctl enable docker
无论你安装的是哪种操作系统或工具(桌面版或引擎版),都会随附我们将要使用的 Docker 命令行工具,简称为 docker。
首先,让我们通过检查版本来确保 Docker 已正确安装并正在运行。打开终端并输入以下命令:
$ docker --version
Docker version 20.10.10, build b485636
重要提示
如果你在 Linux 上,并且在安装后没有将你的用户添加到 docker 组中,你需要使用超级用户权限调用 Docker CLI,因此所有的 docker 命令应该加上 sudo 前缀。对于上面的示例,命令将是 sudo docker --version。
你的输出可能会稍有不同——也许你安装了更新的版本。如果之前的命令没有成功,但 Docker 已经安装,确保 Docker Desktop(如果你使用 macOS 或 Windows)或 Docker 守护进程(如果你使用 Linux)正在运行。
现在,让我们启动第一个带有 Ubuntu 22.04 的容器:
$ docker run -it ubuntu:22.04 bash
你将看到的输出应该类似于以下内容:
Unable to find image ‘ubuntu:22.04’ locally
22.04: Pulling from library/ubuntu
125a6e411906: Pull complete
Digest: sha256:26c68657ccce2cb0a31b330cb0be2b5e108d467f641c62e13ab40cbe c258c68d
Status: Downloaded newer image for ubuntu:22.04
root@d752b475a54e:/#
哇!我们现在在 Ubuntu 容器内运行 bash。镜像可能需要几秒钟才能下载,但一旦准备好,你将看到命令行提示符在新创建的容器内以 root 用户身份运行。
那么,当我们调用 docker run 时,究竟发生了什么?
docker run 在新容器内执行命令;它需要容器镜像的名称,其中命令将被执行(在上面的例子中是 ubuntu),可以选择镜像的标签(此处是 22.04),以及要执行的命令(此处是简单的 bash)。
-i 参数与 --interactive 相同,表示我们希望以交互模式运行命令。-t,即 --tty,将分配一个伪 TTY(模拟终端)。
如你所记得,镜像是容器环境的模板。我们请求了一个带有版本 22.04 的 ubuntu 环境。在输出的前几行中,我们看到该镜像在本地未找到:
Unable to find image ‘ubuntu:22.04’ locally
22.04: Pulling from library/ubuntu
125a6e411906: Pull complete
如果之前没有下载过带有特定标签的镜像,它将自动从 Docker Hub 库中下载(拉取),并且你应该能够看到下载进度。
现在,让我们退出容器并尝试重新运行它。在终端中输入 exit:
root@d752b475a54e:/# exit
exit
现在,执行我们之前做过的相同命令:
$ docker run -it ubuntu:22.04 bash
root@e5d98a473adf:/#
这次速度更快了吗?是的,因为我们已经在本地缓存了 ubuntu:22.04 镜像,所以不需要再下载它。因此,容器立即启动了。
你有没有注意到这次 root@ 后面的主机名不同了——e5d98a473adf 与 d752b475a54e?(注意:你将在这里看到你独特的容器名称。) 这是因为我们启动了一个基于相同 ubuntu 镜像的新容器。当我们启动一个新容器时,并不会修改只读源镜像;相反,我们在镜像上创建了一个新的可写文件系统层。下面的图表展示了这种分层方法:

图 3.2 – 容器层
当我们启动一个容器时,我们添加了一个新的层,这使得容器镜像的副本可以进行修改。通过这种方式,我们可以从相同的基础镜像创建任意数量的容器,而无需修改初始的只读镜像层。这种方法的主要优点是在容器层中,我们仅存储与镜像层的差异,这意味着在大规模使用时可以显著节省磁盘空间。
镜像也可以由多个层组成,其中一个层可能源自另一个层。在接下来的章节中,我们将学习如何构建新镜像并将我们喜欢的软件包含在其中。
随时探索我们的容器环境,并在完成后exit退出:
$ docker run -it ubuntu:22.04 bash
root@e5d98a473adf:/# echo “Hello World” > /root/test
root@e5d98a473adf:/# hostname
e5d98a473adf
root@e5d98a473adf:/# date
Sun May 1 13:40:01 UTC 2022
root@e5d98a473adf:/# exit
exit
当我们在第一个容器中执行exit时,它退出了;稍后,当我们再次执行docker run时,一个新的容器被创建。现在,由于两个容器都已经退出,我们在磁盘上有一个镜像层,以及基于ubuntu:22.04基础镜像的两个不同容器层。
由于容器层仅跟踪与基础镜像的差异,直到所有容器层被删除之前,我们无法删除基础镜像。让我们通过运行以下代码获取本地镜像的列表:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 22.04 d2e4e1f51132 39 hours ago 77.8MB
如果我们尝试使用docker rmi命令删除ubuntu:22.04镜像,我们会遇到一个错误:
$ docker rmi ubuntu:22.04
Error response from daemon: conflict: unable to remove repository reference “ubuntu:22.04” (must force) – container e5d98a473adf is using its referenced image d2e4e1f51132
我们还可以执行docker ps命令来查看所有正在运行的容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
一个空表格表示当前没有容器在运行。
最后,我们可以执行docker ps --all来查看本地系统上的所有容器,包括那些已退出的容器:
$ docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e5d98a473adf ubuntu:22.04 “bash” 8 minutes ago Exited (0) 2 minutes ago vibrant_jenn
d752b475a54e ubuntu:22.04 “bash” 18 minutes ago Exited (0) 12 minutes ago cool_perl
尝试使用docker rm CONTAINER ID删除那些已退出的容器:
$ docker rm d752b475a54e
d752b475a54e
$ docker rm e5d98a473adf
e5d98a473adf
现在,镜像也应该被删除:
$ docker rmi ubuntu:22.04
Untagged: ubuntu:22.04
Untagged: ubuntu@sha256:26c68657ccce2cb0a31b330cb0be2b5e108d467f641c62e13ab40cbe c258c68d
Deleted: sha256:d2e4e1f511320dfb2d0baff2468fcf0526998b73fe10c8890b4684bb 7ef8290f
Deleted: sha256:e59fc94956120a6c7629f085027578e6357b48061d45714107e79f04 a81a6f0c
sha256是镜像层的摘要;它们是唯一且不可变的标识符。如果我们为ubuntu镜像分配一个不同的标签而不是22.04,并尝试从 Docker Hub 再次拉取(下载)相同的镜像,Docker 会识别出我们已经拥有这个摘要的镜像,并且除了重新标记它之外,不会做任何事情。
让我们再试一件事——拉取另一个没有标签的 Docker 镜像。如果你仅仅pull镜像,那么不会启动容器,但这样可以节省下次从该镜像启动新容器时的下载时间:
$ docker pull centos
Using default tag: latest
latest: Pulling from library/centos
a1d0c7532777: Pull complete
Digest: sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473 f432b177
Status: Downloaded newer image for centos:latest
docker.io/library/centos:latest
如你所见,如果我们没有明确指定标签,默认会使用latest。
在接下来的章节中,我们将进一步了解latest标签的含义、标签的基本概念以及如何使用 Docker 构建镜像。
构建容器镜像
现在我们知道如何启动容器和拉取镜像,我们将学习如何创建新的容器镜像。由于镜像层是不可变的,你可以通过在现有镜像之上添加新层来创建你选择的软件的新镜像。使用 Docker 可以通过两种方式来实现:
-
交互模式
-
使用 Dockerfile
交互式方法是从现有容器创建镜像。假设你启动了一个 Ubuntu 22.04 环境的容器,安装了附加包并暴露了端口80。要创建新镜像,我们可以使用docker commit命令:
$ docker commit CONTAINER_ID [REPOSITORY[:TAG]]
镜像名称将采用REPOSITORY:TAG格式。如果没有指定标签,则会自动添加latest。如果没有指定仓库,则镜像名称将是唯一标识符(UUID)。标签和名称(与镜像仓库的名称相同)可以在构建后更改或应用。
虽然交互式方法快捷且简单,但在正常情况下不应使用它,因为它是一个手动、容易出错的过程,生成的镜像可能会更大,且包含许多不必要的层。
构建镜像的第二个、更好的选择是使用 Dockerfile。
Dockerfile
Dockerfile 是一个包含构建镜像指令的文本文件。它支持运行 shell 脚本、安装附加包、添加和复制文件、定义默认执行的命令、暴露端口等。
让我们来看一个简化版的 Dockerfile:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y curl vim
LABEL description=”My first Docker image”
正如你可能已经猜到的,FROM指令定义了我们将要构建的镜像的基础镜像和标签。基础镜像也可以是我们之前构建的本地镜像,或者是来自镜像仓库的镜像。RUN指令执行apt-get update,然后安装curl和vim包。LABEL是你想要添加到镜像中的任何元数据。如果你将前面的内容复制到一个名为Dockerfile的文件中,你就可以通过在同一文件夹中调用docker build来构建一个新镜像:
$ docker build . -t myubuntuimage
[+] Building 11.2s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 153B 0.0s
=> [internal] load .dockerignore
0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:22.04 0.0s
=> [1/2] FROM docker.io/library/ubuntu:22.04 0.0s
=> [2/2] RUN apt-get update && apt-get install -y curl vim 9.9s
=> exporting to image 1.1s
=> => exporting layers
1.1s
=> => writing image sha256:ed53dcc2cb9fcf7394f8b03818c02e0ec4 5da57e89b550b68fe93c5fa9a74b53 0.0s
=> => naming to docker.io/library/myubuntuimage 0.0s
使用-t myubuntuimage,我们指定了镜像的名称,但没有实际的标签。这意味着默认会为镜像应用latest标签:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myubuntuimage latest ed53dcc2cb9f 6 minutes ago 176MB
centos latest 5d0da3dc9764 7 months ago 231MB
关于latest标签,我们需要澄清一些内容,因为它可能会产生误导:
-
如果在构建过程中没有指定标签,
latest会默认应用。 -
如果在镜像下载或容器运行时没有指定标签,默认会拉取
latest。 -
latest不会动态更新;你可以将任何镜像标记为latest——即使是同一镜像的旧版本。
因此,最佳实践是使用更具描述性的标签来标记镜像,而不是依赖latest。例如,可以使用打包应用程序的递增版本(如 v.0.32、v.1.7.1 等)作为标签,甚至可以使用构建时间戳。时间戳使我们能够确定镜像的构建时间,而无需检查每个镜像的元数据。
让我们快速回顾一下 Dockerfile 中支持的指令。我们已经了解了FROM、RUN和LABEL,但还有更多:
-
ADD:用于将文件和目录添加到Docker 镜像中(从构建位置或远程 URL)。 -
COPY:用于将文件复制到Docker 镜像内部。 -
CMD:用于定义 Docker 镜像的默认可执行文件(只有最后一个CMD指令会被尊重)。CMD可以在容器运行时被轻松覆盖。 -
ENTRYPOINT:类似于CMD,允许我们在启动容器时定义镜像的可执行文件。它可以与CMD一起使用。 -
EXPOSE:告诉我们,镜像中的应用程序在运行时会监听特定的网络端口。 -
ENV:用于设置镜像中的任何环境变量。 -
USER:设置RUN、CMD或ENTRYPOINT指令的用户名。 -
VOLUME:用于创建一个挂载点并标记其与外部挂载卷一起使用(例如,从启动容器的主机中)。 -
WORKDIR:设置RUN、CMD和ENTRYPOINT指令的工作(当前)目录。
关于 CMD 和 ENTRYPOINT 的简要说明:它们相似,但并不相同。我们可以在 Dockerfile 中指定 CMD、ENTRYPOINT 或两者。如果同时指定,则 CMD 作为 ENTRYPOINT 的参数。由于 CMD 在运行时更容易被覆盖,因此通常 ENTRYPOINT 是可执行文件,而 CMD 是参数。例如,我们可以将 ENTRYPOINT 设置为 /bin/cat,并使用 CMD 给出要连接的文件路径(如 /etc/hosts、/etc/group 等)。对于 Docker Hub 上的许多公共镜像,ENTRYPOINT 默认设置为 /bin/sh -c。
这个列表并不是 Dockerfile 支持的指令的完整参考,但它列出了涵盖 99% 场景的最常用指令。此外,你通常不会在笔记本电脑或本地工作站上构建容器;相反,你会使用现代 CI/CD 系统或 Docker Hub 上的自动构建作为替代。
现在,让我们了解当容器被使用时,开发工作流可能是什么样子的:
- 软件开发人员使用自己选择的编程语言编写应用程序代码——例如,Python、Java、Ruby、Node.js、Golang 或其他任何语言。
重要说明
无需学习新的编程语言——任何在 Linux 环境中运行的软件也可以在容器中运行。
-
代码经过测试并推送到 GitHub 仓库或其他版本控制系统。当源代码发生变化时,CI/CD 或第三方解决方案会被触发,应用程序会根据定义的 Dockerfile 打包到容器镜像中。
-
Dockerfile 指令用于将代码复制并在容器镜像层内运行和安装。根据选择的语言和操作系统环境,这些指令有所不同。例如,Node.js 应用程序可能需要运行
yarn install,而 Python 应用程序则需要使用pip命令进行安装,等等。 -
镜像被构建、标记并推送(上传)到镜像注册中心。这可能是例如 Docker Hub 中的私有仓库,云服务提供商提供的仓库,甚至是你公司内部维护的注册中心。
-
此时,镜像可以由容器编排工具,如 Kubernetes,或者具有容器运行时的服务器,甚至只是由安装了 Docker 工具的其他团队成员下载并运行。
如你所记得,容器的主要特点之一是 可移植性——一个在某个主机上运行的容器也能在另一个主机上运行。这意味着你可以有一个包含 Alpine Linux 的容器镜像,并在你的 Fedora Linux 笔记本电脑上或基于 Ubuntu 的 Kubernetes 集群上运行它。
等等——我们能在 Windows 上运行 Linux 容器,或者反过来吗?其实不能。首先,我们需要区分 Linux 容器和 Windows 容器。
重要提示
本书的所有内容和 KCNA 考试本身都仅涉及 Linux 容器。
即使你在 Windows 上运行 Docker Desktop,它在后台也使用了一个最小化的 Linuxkit 虚拟机。Windows 容器是不同的,可能使用今天在微软操作系统中可用的两种隔离模式之一(WSL 2 或 Hyper-V)。如果你在 Windows 上运行,Docker Desktop 允许你在 Windows 容器 和 Linux 容器 之间切换。但请记住,全球超过 90% 的服务器运行的是 Linux,因此除非你打算在容器中运行仅限 Windows 的应用程序,否则你只需要学习和使用 Linux 容器。
总结
在这一章中,我们通过 (Linux) 容器获得了经验,并了解到容器背后的技术已经存在多年,基于 cgroups 和内核 namespaces。
Docker 推出了旨在为开发人员和工程师提供一种通用且简单的方式来打包和共享应用程序的工具。在容器出现之前,应用程序通常能够在开发环境中正常工作,但在生产环境中由于未满足的依赖关系或安装了不正确的版本而无法运行。容器通过将应用程序与所有依赖项和系统包捆绑在一个称为容器镜像的模板中解决了这个问题。
容器镜像可以存储在支持私有和公有仓库的注册中心,并允许你与不同的团队共享它们。Docker Hub、Quay 和 Google Container Registry(GCR)是目前一些最知名的容器镜像注册中心,可以通过互联网访问。被推送(上传)到注册中心的镜像可以通过容器编排工具,如 Kubernetes,或者仅通过具有容器运行时的服务器,通过互联网进行拉取(下载)。
镜像用于创建容器,因此容器是镜像的运行实例。当容器以可写的文件系统启动时,会在不可变镜像层之上创建一层。容器和镜像可以有多个层,并且我们可以从单个镜像启动任意多个容器。与虚拟机相比,容器更加轻量,并且启动速度非常快。
我们还学习了,使用 Docker 构建容器镜像时,可以利用交互式或 Dockerfile 方法。通过 Dockerfile,我们定义一组指令来构建包含容器化应用程序的镜像。
在下一章中,我们将继续探索容器,学习 Kubernetes 提供的运行时和可插拔接口。
问题
总结时,以下是一些问题,供你测试本章内容的知识。你可以在评估部分的附录中找到答案:
-
以下哪项特性描述了容器(选择两个)?
-
环境之间的可移植性
-
镜像体积大
-
镜像体积小
-
高安全性
-
-
以下哪些是正确的(选择两个)?
-
应用程序在容器中打包容易
-
应用程序在虚拟机中打包容易
-
容器镜像易于共享
-
虚拟机镜像易于共享
-
-
开发者需要学习哪种编程语言来在容器中运行他们的代码?
-
Dockerlang
-
Golang
-
Python
-
无 – 容器允许与操作系统环境支持的语言相同的语言
-
-
以下哪些问题是容器解决的(选择两个)?
-
环境之间的不满足依赖关系
-
应用程序代码中的错误
-
需要测试应用程序代码
-
长时间的虚拟机启动时间
-
-
以下哪项是容器使用的(选择两个)?
-
cgroups -
hwmon -
acpi -
kernel namespaces
-
-
以下哪些可以用于共享容器镜像(选择两个)?
-
Docker Hub
-
Docker Swarm
-
Docker Registry
-
Docker Compose
-
-
以下哪些关于容器镜像的说法是正确的(选择两个)?
-
它们只能通过 Dockerfile 构建
-
它们包含不可变的文件系统层
-
最新的镜像始终标记为 latest
-
它们可以通过交互式方式构建
-
-
以下哪些适用于启动新容器时(选择两个)?
-
会创建一个新的可写文件系统层
-
请求的镜像总是被拉取
-
如果本地找不到请求的标签(SHA 摘要),则会拉取镜像
-
加载新的 Linux 内核
-
-
以下哪些关于容器镜像标签的说法是正确的(选择两个)?
-
每个镜像必须有标签
-
在构建时,最新标签会自动应用,除非被覆盖
-
同一镜像不能有多个标签
-
同一镜像可以有多个名称(仓库)和标签
-
-
如何使用 Docker 工具创建新容器?
-
docker run -
docker exec -
docker spawn -
docker launch
-
进一步阅读
本章提供了容器生态系统的概述以及通过 KCNA 考试所需的知识,但并没有涵盖 Docker 工具的所有功能,也没有详细描述 cgroups 和 namespaces。如果你希望更进一步,建议你查阅以下资源:
-
cgroups v1:
www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt -
Docker 入门:
docs.docker.com/get-started/ -
编写 Dockerfile 的最佳实践:
docs.docker.com/develop/develop-images/dockerfile_best-practices/
第四章:探索容器运行时、接口和服务网格
在本章中,我们将进一步探讨容器运行时、网络、接口,并学习服务网格。我们将了解现存的运行时实现及其差异,学习容器如何通过网络相互通信,Kubernetes 中存在哪些容器接口,并了解什么是服务网格及其应用。我们还将通过之前安装的 Docker 工具进行一些额外的练习,以支持我们的学习之旅。
本章内容将涉及 KCNA 认证的容器编排领域,这部分是考试中第二大部分,因此请确保回答本章末尾的所有问题。
以下是我们将要涵盖的主题:
-
容器运行时
-
容器网络
-
容器存储
-
容器安全
-
介绍服务网格
开始吧!
容器运行时
正如你从前几章了解到的,容器可以在虚拟机、云端、本地、裸金属服务器上运行,甚至仅仅在你的笔记本电脑上运行。负责执行基本操作的软件,如从镜像仓库下载镜像并创建、启动、停止或删除容器,被称为容器运行时。我们已经学习了 Docker 工具和运行时,但实际上还有更多的运行时存在,包括以下几种:
-
Containerd
-
CRI-O
-
kata
-
gVisor
在深入了解运行时细节之前,我们需要理解什么是容器运行时接口(CRI)。
CRI
CRI 是一个插件接口,允许 Kubernetes 使用不同的容器运行时。在引入 CRI 之前的 Kubernetes 早期版本中,只能使用 Docker 作为运行时。
如你所记得,Kubernetes 没有自己的运行时来执行基本的容器操作,因此它需要一个运行时来管理容器,并且该运行时必须是 CRI 兼容的。例如,Docker 引擎不支持 CRI,但大多数其他运行时,包括containerd或CRI-O,都支持 CRI。本质上,CRI 定义了 Kubernetes 与所选运行时之间的通信协议,使用的是gRPC(高性能的远程过程调用框架),如图 4.1所示:

图 4.1 – 容器运行时与 CRI 的集成
最初,Kubernetes 中没有 CRI 实现,但随着新容器运行时的开发,将它们全部集成到 Kubernetes 中变得越来越困难。因此,解决方案是定义一个标准接口,以便与任何运行时兼容。Kubernetes 版本 1.5 中引入 CRI 后,允许在单个 K8s 集群中使用多个容器运行时,并且也使得开发兼容的运行时变得更容易。今天,containerd是 Kubernetes 新版本中最常用的运行时。
但为什么你需要在同一个集群中运行不同的容器运行时呢?这是一个相对高级的场景,背后的主要原因是某些容器运行时可以为更敏感的容器工作负载提供更好的安全性。因此,当我们讨论容器及其运行时时,我们需要区分三种主要类型:
-
命名空间 – 最快且最常用的类型,它基于 Linux 内核的 cgroups 和 namespaces 功能,这些我们在上一章中已介绍。此类型共享相同的内核来运行多个容器,因此被认为是所有容器类型中安全性最低的。例子包括 Docker、containerd 和 CRI-O。
-
虚拟化 – 最慢的容器类型,实际上需要像虚拟机一样使用虚拟机监控程序。每个容器都在其自己的轻量级虚拟机中启动,拥有自己的专用内核。此类型被认为是最安全的,因为它为容器工作负载提供了最大程度的隔离。虚拟化容器的启动速度仍然比虚拟机快,而且它们相对于传统虚拟机的优势在于它们能够轻松与容器编排系统(如 Kubernetes)集成。Kata 项目是虚拟化容器的一个例子。
-
沙盒 – 这是一种介于其他两者之间的容器类型,提供比命名空间容器更好的安全性,同时又比虚拟化容器更快。更好的安全性通过另一个隔离层来实现,这一层拦截来自容器工作负载的系统调用。gVisor 是一个来自 Google 的开源项目,允许创建沙盒容器。
虽然这听起来可能非常复杂,但在 KCNA 考试的范围内,你不需要了解容器运行时的所有细节。如果你以后参加 CKS 考试或有特殊用途使用 沙盒 或 虚拟化 容器,这些知识才会用得上。现在,请确保记住哪些容器运行时是存在的,并且在大多数场景中,命名空间 容器是常用的。同时,不要把 CRI 和 OCI 混淆,我们在 第二章中已经讲过,CNCF 和 Kubernetes 认证概述。
重要提示
开放容器倡议 (OCI) 提供了容器行业规范(镜像、运行时和分发规范),而 CRI 是 Kubernetes 的一部分,它使得可以以可插拔的方式与 K8s 一起使用不同的容器运行时。
在实际操作中,你并不会直接与容器运行时进行交互,而是使用像 Kubernetes 或 Docker Swarm 这样的编排系统。我们还可以使用 CLI 来与容器运行时进行交互,就像我们使用 Docker CLI,或者在使用containerd运行时时,你可以使用ctr或nerdctl CLI。
接下来,在以下部分中,我们将学习更多关于容器网络的内容。
容器网络
到目前为止,我们只尝试过创建单个容器。然而,在现实世界中,我们需要处理成十上百个容器。随着微服务架构的广泛应用,应用程序被拆分为多个较小的部分,这些部分通过网络互相通信。一个应用程序可能由前端部分、多个后端服务和数据库层组成,其中前端接收到的用户请求会触发与后端的通信,而后端则会与数据库进行交互。当每个组件都运行在自己独立的容器中,并且跨多个服务器时,理解它们如何相互通信是至关重要的。网络是容器和 Kubernetes 的一个重要部分,理解这些如何工作是非常具有挑战性的。目前,我们只会简单触及容器间通信的表面,更多关于暴露容器和 K8s 细节的内容将在后续章节中介绍。
让我们回到前一章中安装的 Docker 工具,并尝试启动另一个 Ubuntu 容器。
重要提示
在尝试启动容器之前,请确保 Docker Desktop 正在运行。如果你之前没有启用自动启动,可能需要手动启动它。在 Linux 的 Docker Engine 上,你可能需要执行$ sudo systemctl start docker。
打开终端并运行以下命令:
$ docker run -it ubuntu:22.04 bash
因为该镜像被精简到最小化以节省空间,所以没有预安装像net-tools这样的基本软件包。我们可以通过调用apt update和apt install来在容器内安装这些包:
root@5919bb5d37e3:/# apt update; apt -y install net-tools
… SOME OUTPUT OMITTED …
Reading state information... Done
The following NEW packages will be installed:
net-tools
… SOME OUTPUT OMITTED …
Unpacking net-tools (1.60+git20181103.0eebece-1ubuntu5) ...
Setting up net-tools (1.60+git20181103.0eebece-1ubuntu5) ...
root@5919bb5d37e3:/#
现在我们已经安装了net-tools,可以在容器内使用ifconfig工具。你看到的输出应该类似于此:
root@5919bb5d37e3:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
RX packets 14602 bytes 21879526 (21.8 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3127 bytes 174099 (174.0 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 5 bytes 448 (448.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5 bytes 448 (448.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
我们还可以通过在容器内调用route工具来查看容器的路由表。输出将类似于以下内容:
root@9fd192b5897d:/# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0
正如我们所看到的,我们的容器具有eth0接口,IP 地址是172.17.0.2。在你的情况下,地址可能不同,但重要的是,我们的容器默认会有自己独立的网络堆栈,包括自己的(虚拟)接口、路由表、默认网关等等。
如果我们现在打开另一个终端窗口并执行docker network ls,我们将看到使用哪些驱动程序支持哪些网络类型。输出将类似于以下内容:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
c82a29c5280e bridge bridge local
83de399192b0 host host local
d4c7b1acbc0d none null local
有三种基本的网络类型:
-
bridge– 这是我们创建的 Docker 容器的默认类型和驱动程序。它允许连接到同一主机上的桥接网络的容器相互通信,并与其他容器隔离(这些容器也可以连接到自己的桥接网络)。借助主机的网络地址转换(NAT)和IPtables,容器可以与外界进行通信。 -
host- 这是一种类型,用于在不需要网络隔离时创建容器。使用主机网络生成的容器不会与创建它的主机系统的网络隔离。例如,您可以启动一个带有 Apache Web 服务器监听80端口的容器,并且它将立即可以从同一网络中的任何其他主机访问,除非受到防火墙的保护。 -
none- 这是一个很少使用的选项,意味着容器的所有网络都将被禁用。
注意,docker network ls 输出中的这些类型具有 local 范围,这意味着它们可以用于我们在 Docker 中生成容器的单个主机上。但是它们不会允许在一个服务器上创建的容器直接与在另一个服务器上创建的容器通信(除非使用主机网络,这类似于在没有涉及容器的情况下直接在主机上运行应用程序)。
为了在多个主机之间建立容器相互通信的网络,我们需要所谓的覆盖网络。覆盖网络将多个服务器连接在一起,允许位于不同主机上的容器之间进行通信。
覆盖网络
一个覆盖网络是在另一个网络顶部运行的虚拟网络,通常使用数据包封装 - 覆盖网络数据包位于另一个数据包内,该数据包被转发到特定主机。
无论您是在运行 Kubernetes、Docker Swarm 还是其他解决方案来编排容器,实际上,您总是会为您的工作负载运行多个主机,并且运行在这些主机上的容器需要使用覆盖网络相互通信。
谈到 Kubernetes,类似于 CRI,它实现了容器网络接口(CNI),允许以可插拔的方式使用不同的覆盖网络。
CNI
CNI 是一个允许 Kubernetes 使用不同的覆盖网络插件的接口。
CNI 的引入使得第三方能够开发符合 Kubernetes 并提供其自身独特特性的解决方案,例如容器网络中的流量加密或网络策略(防火墙规则)。
今天在 Kubernetes 中使用的一些 CNI 网络插件包括flannel、Cilium、Calico 和 Weave,仅举几例。Kubernetes 还支持同时使用多个插件,如Multus(一个多网络插件);但这是 KCNA 考试范围外的高级主题。在书籍的第三部分,学习 Kubernetes 基础中,我们将进一步深入了解 Kubernetes 中的网络,但现在是时候深入研究容器存储了。
容器存储
容器设计时是轻量级的,正如我们之前看到的那样,即使是ifconfig和ping等基本工具也可能不包含在容器镜像中。这是因为容器代表了操作系统环境的最小版本,我们只安装我们要容器化的应用及其依赖项。通常,容器镜像中不需要预安装很多包或工具,除非是应用程序运行所必需的。
容器默认不保持状态,这意味着如果你在容器文件系统内放置了一些文件,并且在删除容器后,这些文件将完全消失。因此,通常我们称容器为无状态的,而容器中的磁盘文件则是短暂的。
这并不意味着我们不能使用容器来存储重要数据,以便在容器失败或应用退出时能够持久化数据。
注意
如果容器中运行的应用失败、崩溃或简单地终止,容器默认也会停止。
通过使用外部存储系统,确实可以保留容器中的重要数据。
外部存储可以是一个通过协议如iSCSI附加到容器的块卷,也可以是一个网络文件系统(NFS)挂载,举个例子,或者外部存储也可以仅仅是容器主机上的一个本地目录。外部存储有很多选择,但我们通常将外部容器存储称为卷。
一个容器可以有多个附加的卷,而这些卷可以由不同的技术、协议和硬件支持。卷还可以在容器之间共享,或者从一个容器中分离并附加到另一个容器。卷的内容存在于容器生命周期之外,使我们能够解耦容器和应用数据。卷使我们能够在容器中运行需要写入磁盘的有状态应用程序,无论是数据库、应用程序还是其他任何文件。
让我们回到带有 Docker 工具的计算机,尝试在终端中运行以下命令:
$ docker run -it --name mycontainer --mount source=myvolume,target=/app ubuntu:22.04 bash
当我们运行并将tty附加到容器时,我们应该能够看到我们的新myvolume挂载在容器的/app目录下:
root@e642a068d4f4:/# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 126G 7.9G 112G 7% /
tmpfs 64M 0 64M 0% /dev
tmpfs 3.0G 0 3.0G 0% /sys/fs/cgroup
shm 64M 0 64M 0% /dev/shm
/dev/vda1 126G 7.9G 112G 7% /app
tmpfs 3.0G 0 3.0G 0% /proc/acpi
tmpfs 3.0G 0 3.0G 0% /sys/firmware
root@e642a068d4f4:/# cd /app/
root@e642a068d4f4:/app#
发生的情况是,Docker 在开始时自动为我们的容器创建并附加了一个local卷。Local 意味着该卷由容器启动时所在主机上的一个目录提供支持。
重要提示
本地存储可以用于测试或某些开发工作,但绝不适用于生产工作负载和关键业务数据!
如果我们现在在/app写入任何文件,它们将会持久化:
root@e642a068d4f4:/app# echo test > hello_world
root@e642a068d4f4:/app# cat hello_world
test
root@e642a068d4f4:/app# exit
exit
即使我们通过调用docker rm来删除容器:
$ docker rm mycontainer
mycontainer
通过调用docker volume ls,我们可以看到当前在主机上存在的卷:
$ docker volume ls
DRIVER VOLUME NAME
local myvolume
要获取有关卷的更多详细信息,我们可以使用docker volume inspect命令:
$ docker volume inspect myvolume
[
{
"CreatedAt": "2022-05-15T18:00:06Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/myvolume/_data",
"Name": "myvolume",
"Options": null,
"Scope": "local"
}
]
此时,你可以自由地自己尝试更多关于卷的操作。例如,你可以创建一个新容器并附加现有卷,以确保数据仍然存在:
$ docker run -it --name mycontainer2 --mount source=myvolume,target=/newapp ubuntu:22.04 bash
root@fc1075366787:/# ls /newapp/
hello_world
root@fc1075366787:/# cat /newapp/hello_world
test
现在,说到 Kubernetes,你可能已经猜到了——类似于 CRI 和 CNI,K8s 实现了 Container Storage Interface(CSI)。
CSI
CSI 允许使用可插拔的存储层。外部存储系统可以通过 CSI 以标准化的方式集成到 Kubernetes 中使用。
CSI 允许供应商和云服务提供商为他们的存储服务或硬件设备实现支持。例如,有一个Amazon Elastic Block Store(EBS)CSI 驱动程序,它允许你通过 Kubernetes 完全管理 AWS 云中 EBS 卷的生命周期。还有一个NetApp Trident CSI 项目,它支持各种 NetApp 存储设备,容器可以在 Kubernetes 中使用这些设备。现在,还有许多其他与 CSI 兼容的存储解决方案。
Kubernetes 在管理存储方面非常强大;它可以在集群中的主机和容器之间自动配置、附加和重新附加卷。我们将在 第六章,使用 Kubernetes 部署和扩展应用程序 中更详细地学习 Kubernetes 在有状态应用程序中的功能,现在让我们继续学习容器安全性。
容器安全性
容器安全性是一个高级且复杂的话题,即使是入门级的 KCNA 认证,你也需要了解一些基础知识。正如我们所学,命名空间容器是最常用的容器,它们共享底层操作系统的内核。这意味着在一个容器中运行的进程无法看到其他容器中运行的进程或主机上运行的进程。然而,在同一主机上运行的所有进程仍然使用相同的内核。如果其中一个容器被攻破,那么主机和其他所有容器都有可能被攻破。
让我们回到 Docker 配置,进行快速演示。启动一个 Ubuntu 容器,像之前一样运行 uname -r 命令,查看使用的是哪个内核版本:
$ docker run -it ubuntu:22.04 bash
root@4a3db7a03ccf:/# uname -r
5.10.47-linuxkit
你看到的输出取决于你的主机操作系统和内核版本。如果你看到另一个版本,不要感到惊讶。例如,你可能会看到这样的输出:
5.13.0-39-generic
现在,退出容器,启动另一个较旧版本的 Ubuntu:16.04:
$ docker run -it ubuntu:16.04 bash
Unable to find image 'ubuntu:16.04' locally
16.04: Pulling from library/ubuntu
58690f9b18fc: Pull complete
b51569e7c507: Pull complete
da8ef40b9eca: Pull complete
fb15d46c38dc: Pull complete
Digest: sha256:20858ebbc96215d6c3c574f781133ebffdc7c18d98af 4f294cc4c04871a6fe61
Status: Downloaded newer image for ubuntu:16.04
root@049e8a43181f:/# uname -r
5.10.47-linuxkit
root@049e8a43181f:/#
看到了吗?我们使用了一个已经有超过 5 年历史的Ubuntu:16.04镜像,但所使用的内核版本与第一个容器中的完全相同。即使你使用的是不同版本的 Linux,主机操作系统的内核版本也会被使用。
那么,我们如何保护运行命名空间容器的主机内核呢?也许最著名的两项技术是AppArmor(适用于 Ubuntu)和Security-Enhanced Linux(SELinux,适用于 Red Hat 和 CentOS Linux 家族)。从本质上讲,这些项目允许你为所有用户应用程序和系统服务强制执行访问控制策略。还可以限制对特定文件或网络资源的访问。SELinux 还有一个特殊工具,可以帮助为运行在容器中的应用程序生成安全配置文件(github.com/containers/udica)。Kubernetes 与 AppArmor 和 SELinux 都有集成,允许你将配置文件和策略应用于由 K8s 管理的容器。
接下来,作为root用户运行容器被视为一种不良实践且具有安全风险。在 Linux 中,root用户是一个 ID 为0、组 ID 为0的用户(UID=0,GID=0)。在我们的所有动手练习中,我们都在容器内使用了root用户:
root@4a3db7a03ccf:/#
root@4a3db7a03ccf:/# id -u
0
在实际生产环境中,你应该考虑以非root用户运行应用程序,因为root本质上是一个超级管理员,可以在系统中执行任何操作。现在有趣的部分来了——容器中的root用户也可以是主机上运行容器的root用户(非常不好的做法!)。或者,得益于 Linux 内核的命名空间功能,容器内的root用户可以映射到主机操作系统上的另一个用户 ID(例如UID=1001)。这仍然不是完美的,但如果容器被攻破,容器中的root不会自动获得主机操作系统上的root权限。
注意
在镜像构建过程中,可以指定容器中应用程序使用的用户和组。你可以简单地在Dockerfile中添加USER mynewuser指令来定义要使用的用户。你可能需要在此之前先创建该用户,可以通过在指令上方添加如下内容:RUN useradd -r -u 1001 mynewuser
最后但同样重要的是,请记住你在环境中使用的容器镜像。如果你访问 Docker Hub(hub.docker.com/)或任何其他在线容器注册表,你会发现许多第三方镜像,任何人都可以下载并运行。你可能会遇到一个恰好满足你需求的镜像。例如,某个镜像可能打包了你想尝试的工具或应用程序(例如,监控你正在运行的数据库)。但是,它也可能打包了恶意代码。因此,确保只在容器中运行可信的代码。
最好自己构建镜像并将其存储在自己的仓库中,因为第三方公共镜像仓库完全不受你的控制。它们的所有者可能随时删除或替换镜像,甚至将仓库设为私有。你可能不会立即注意到这一点,这可能导致在镜像无法下载时发生事故。最后,现在有很多工具可以执行容器镜像扫描以检测安全漏洞(Clair、Dagda 和 Anchore 等工具)。这些工具可以集成到镜像构建过程中,以减少使用过时软件包或安装已知存在安全漏洞的软件的风险。
现在我们对容器安全性和网络有了更多了解,我们将研究服务网格——一种用于管理流量和保护云原生应用的新兴技术。
介绍服务网格
在深入了解服务网格的定义之前,我们先快速回顾一下之前学习的关于云原生应用架构的内容。
现代的云原生应用依赖于微服务,这些微服务作为更大应用的一部分协同工作,并通过网络相互通信。这些微服务被打包为容器镜像,并借助如 Kubernetes 这样的编排系统运行。云原生应用的特点是高度动态的,运行的容器数量根据当前的负载和基础设施事件或故障发生变化。
假设你负责运行公司开发的一个由 20 个不同微服务组成的应用。你已经为所有服务实现了自动扩展,并且在高负载时,运行的容器数量超过一百个(例如,每个服务的多个容器副本分布在多个云实例上)。即使使用 Kubernetes 有效地编排这些容器,你仍然希望确保应用可靠运行,基础设施安全,并且在出现问题时能够及时检测并迅速采取行动。这时,服务网格就派上用场了。
服务网格
服务网格是一个专用的基础设施层,用于确保服务之间的通信是安全、可观察和可靠的。
服务网格是一个特殊的层,用于处理服务与服务之间的通信。这里的服务通常是运行在容器中并由 Kubernetes 编排的微服务。从技术上讲,服务网格可以在没有 Kubernetes 甚至容器的情况下使用,但实际上,大多数情况下,服务网格是与由 Kubernetes 编排的容器一起使用的。以下是一些服务网格的例子:
-
Linkerd
-
Istio
-
Open Service Mesh (OSM)
-
Consul Connect Service Mesh
列表中的前三个实际上是开源的 CNCF 项目,尽管它们的成熟度不同。
那么,在服务网格的背景下,安全通信意味着什么呢?
在前面的部分,我们介绍了容器安全的基础知识,但我们尚未深入探讨如何保护容器之间的网络通信。保护网络通信通常是所谓零信任安全方法的一部分。
零信任
零信任是一种方法,其中无论是在网络内部还是外部,默认情况下任何人都不被信任。必须经过验证才能访问连接到网络的服务。
传统的网络安全方法是基于保护基础设施的边界,也就是说,外部很难获得网络访问权限,但在网络内部,每个人默认都是受信任的。显然,如果攻击者能够突破边界安全并访问内部网络,他们很可能会获得其他地方的访问权限,包括机密数据。这就是为什么越来越多的企业正在实施零信任(Zero Trust)方法,服务网格在这一过程中非常有帮助。
服务网格的主要优势之一是,您不需要对应用程序代码进行任何更改就可以使用服务网格及其功能。服务网格是在平台层上实现的,这意味着一旦在平台上安装,所有应用程序(例如容器中的微服务)都可以受益于其特性。通过服务网格,容器之间的所有流量可以自动加密和解密,且运行在其中的应用程序不需要修改任何一行代码。
在没有服务网格的情况下,传统方法实现这一目标可能需要管理 SSL 证书,在过期时请求并续期,甚至可能需要进一步修改应用程序或基础设施层。
实际上,上述列表中的所有服务网格都为连接到网格的容器之间的所有 TCP 流量提供相互认证的传输层安全(mTLS)。它与常规的TLS相似,后者通过证书展示服务器身份,区别在于mTLS的情况下,双方都必须自我认证才能开始通信。这意味着客户端也需要提供一个证书,服务器会进行验证。在我们的示例中,客户端和服务器是连接到服务网格的容器中的两个服务。同样,mTLS 可以完全自动启用,无需在应用程序部分做任何额外的工作。
在探讨其他特性之前,我们首先更好地了解一下服务网格是如何工作的。服务网格层通过一系列轻量级网络代理与微服务进行接口对接,所有微服务之间的流量都会通过这些代理在其自己的基础设施层中进行路由。通常,代理与每个服务一起运行在所谓的sidecar容器中,这些 sidecar 代理一起形成了服务网格网络,如图 4.2所示。

图 4.2 – 服务网格概述
服务网格通常由两部分组成:
-
数据平面 – 由运行在微服务容器旁边的网络代理组成。例如,在Linkerd服务网格中,使用的是linkerd-proxy,而在Istio中,使用的是Envoy代理的扩展版本。
-
控制平面 – 由多个组件组成,负责配置网络代理、服务发现、证书管理和其他功能。
为了让服务网格与 Kubernetes 兼容,它必须支持 K8s 服务网格 接口(SMI)。
SMI
这是一个定义标准、通用且可移植的 API 集合的规范,旨在以供应商无关的方式平滑集成服务网格。SMI的作用与CRI、CNI和CSI相同,但用于服务网格。
在可观察性方面,服务网格提供了网格内所有通信的详细遥测数据。所有代理自动收集的指标使得操作人员和工程师能够排查故障、维护和优化他们的应用程序。通过服务网格,我们可以追踪调用和服务依赖关系,并检查流量流动和单个请求。这些信息对于审计服务行为、响应时间以及在复杂的分布式系统中检测异常非常有帮助。
最后,服务网格提供了流量管理和可靠性功能。具体的功能可能因项目而异,因此一个服务网格提供的某些功能可能另一个没有提供。为了举例说明,让我们看看Linkerd网格提供了什么:
-
负载均衡 – 适用于HTTP、HTTP/2和gRPC请求以及TCP连接。服务网格还可以自动检测最快的服务端点,并将请求发送到那里。
-
自动重试和超时 – 这允许你通过透明地进行重试,优雅地处理暂时性的服务故障。
-
流量拆分 – 这允许你动态地将一部分服务流量从一个服务切换到另一个服务,以实现新的服务版本的复杂发布策略。
-
故障注入 – 这允许你人为地引入错误和故障,以测试系统或连接的服务的影响。
总的来说,服务网格是一个复杂且高级的话题,我们仅仅触及了最低要求的基础知识,以便通过 KCNA 考试。如果你对更多的内容感兴趣,建议查看进一步阅读部分。
你此时可能会问自己一个问题:覆盖网络和服务网格有什么区别?我们为什么需要两者?
简短的回答是,大多数覆盖网络运行在开放系统互联(OSI)模型的较低层(网络层 3),而服务网格则运行在 OSI 模型的第 7 层,专注于服务和高层应用协议(如果你不熟悉 OSI 模型,可以查看进一步阅读部分)。二者的功能并不是互相替代的,服务网格仍在获得势头,这意味着并不是每个基于微服务或容器化的 Kubernetes 应用都会使用服务网格。从技术上讲,我们也并不总是必须与容器一起使用覆盖网络,正如我们在 Docker 的练习中所看到的,但在接下来的章节中,我们将看到为什么使用它是有利的。
总结
在本章中,我们学到了很多关于容器运行时、容器接口和服务网格的知识。容器运行时是管理基本容器操作的底层软件,如镜像下载、容器启动或删除。Kubernetes 没有自己的运行时,但它提供了接口,允许你使用不同的运行时、不同的网络插件、不同的存储解决方案以及不同的服务网格。这些接口分别被称为 CRI、CNI、CSI 和 SMI,它们的引入使得在使用 K8s 时具有了很大的灵活性。
我们还了解了容器运行时类型及其区别。命名空间容器是最流行和轻量的,但它们的安全性不如其他类型的容器。虚拟化容器是最慢的,但它们提供最大安全性,因为每个容器使用独立的 Linux 内核。沙箱容器填补了两者之间的空白——它们比命名空间容器更安全,比虚拟化容器更快。
在容器网络方面,有很多选择。对于集群中的容器间通信,我们通常会使用覆盖网络。Kubernetes 通过 CNI 支持第三方网络插件,这些插件提供了不同的功能和能力。也可以在非隔离的网络环境中运行容器,例如直接在启动容器的主机的网络命名空间中运行。
容器本身是无状态的,意味着它们默认情况下不会保留磁盘上的数据。要在容器中运行一个有状态的应用程序,我们需要挂载外部存储卷,这些存储卷可以是从 iSCSI 块设备到特定厂商或云提供商的解决方案,甚至是简单的本地磁盘。Kubernetes 通过可插拔的 CSI 提供了很大的灵活性,使得将外部存储集成到由 K8s 管理的容器中变得非常方便。
我们还触及了容器安全性的基础知识。命名空间容器共享相同的内核,这就是为什么确保没有容器被攻破如此重要的原因。有一些安全扩展,如AppArmor和SELinux,它们通过可配置的配置文件为内核增加额外的保护层,此外还有一些最佳实践帮助最小化风险。
其中一种做法是使用常规的(非 root)用户帐户在容器中运行,另一种做法是确保在容器中执行受信任的代码。建议构建自己的镜像并将其保存在自己的注册中心,而不是使用来自未知第三方仓库的镜像。此外,您还可以将自动漏洞扫描作为镜像构建过程的一部分。
最后,我们学习了服务网格——一种特殊的基础设施层,它允许在不修改应用程序代码的情况下保护服务之间的网络通信。服务网格还提供了丰富的可观察性和流量管理功能,甚至允许你自动重试请求并拆分流量。
在接下来的章节中,我们将进入 KCNA 考试和本书的一个重要部分——即 Kubernetes 容器编排。现在,确保回答以下所有回顾问题来测试你的知识。
问题
在我们总结时,这里有一组问题供您测试关于本章节内容的知识。您将在附录的评估部分找到答案:
-
以下哪项是负责启动和停止容器的软件?
-
容器虚拟化管理程序
-
容器守护进程
-
Kubernetes
-
容器运行时
-
-
以下哪些是有效的容器类型(可多选)?
-
超空间的 -
沙箱化的 -
命名空间 -
虚拟化的
-
-
以下哪项是沙箱化容器的示例?
-
Kata
-
gVisor
-
Docker
-
containerd
-
-
以下哪项是虚拟化容器的示例?
-
Docker
-
containerd
-
gVisor
-
Kata
-
-
以下哪项允许你在 Kubernetes 中使用不同的容器运行时?
-
CSI
-
SMI
-
CNI
-
CRI
-
-
以下哪项允许你在 Kubernetes 中使用不同的服务网格?
-
CRI
-
SMI
-
CNI
-
CSI
-
-
为什么命名空间容器被认为不太安全?
-
它们使用旧的内核功能
-
他们需要 Kubernetes 来运行
-
它们共享主机内核
-
它们共享主机网络
-
-
哪种容器类型被认为是最轻量和最快的?
-
虚拟化的
-
沙箱化的
-
命名空间
-
超空间的
-
-
以下哪些存储解决方案可以与 Kubernetes 一起使用?
-
任何支持 NFS v4.1 的
-
任何与 CSI 兼容的
-
任何与 CNI 兼容的
-
任何第三方云提供商存储
-
-
服务网格正常运行需要改变应用程序代码的哪些部分?
-
应用程序必须用 Golang 重新编写
-
应用程序需要公开 SMI
-
应用程序必须是无状态的
-
无需应用程序更改
-
-
以下哪项是服务网格的特性(可多选)?
-
mTLS
-
流量管理
-
可观察性
-
流量压缩
-
-
服务网格数据平面包含哪些组件?
-
轻量级网络防火墙
-
轻量级网络代理
-
轻量级负载均衡器
-
轻量级 Web 服务器
-
-
以下哪些是服务网格(选择多个)?
-
Istio
-
Prometheus
-
Falco
-
Linkerd
-
-
以下哪项被认为是容器安全的最佳实践(选择多个)?
-
以
UID=0身份运行应用程序 -
扫描容器镜像以发现漏洞
-
以非 root 身份运行应用程序
-
使用 Kubernetes 运行容器
-
-
以下哪些技术可以用来提升容器安全性(选择多个)?
-
AppArmor
-
Ansible
-
SELinux
-
Firewalld
-
-
使用公共容器镜像仓库时,你可能会遇到哪些潜在问题(选择多个)?
-
第三方镜像可能随时被删除
-
第三方镜像可能因速率限制而无法下载
-
第三方镜像可能包含恶意软件
-
第三方镜像在开发环境中可能工作正常,但在生产环境中可能会失败
-
-
Kubernetes 可以生成哪些容器?
-
命名空间容器
-
K8s 不会生成容器;是运行时生成容器
-
虚拟化容器
-
沙箱容器
-
-
通常用于多主机容器网络的是哪种技术?链接
进一步阅读
若要了解更多本章涉及的主题,请查看以下资源:
-
Linkerd 概述:
linkerd.io/2.12/overview/ -
关于 Istio:
istio.io/latest/about/service-mesh/ -
开放系统互联(OSI):
en.wikipedia.org/wiki/OSI_model
第三部分:学习 Kubernetes 基础
在本部分中,你将从基础开始学习 Kubernetes:架构、资源和组件、特性以及使用案例。你将安装 Kubernetes,并通过 minikube 获得实际操作经验。你将学习如何运行无状态和有状态的工作负载,调试应用程序,并遵循 Kubernetes 的最佳实践。
本部分包含以下章节:
-
第五章,使用 Kubernetes 编排容器
-
第六章,使用 Kubernetes 部署和扩展应用程序
-
第七章,使用 Kubernetes 进行应用部署与调试
-
第八章,遵循 Kubernetes 最佳实践
第五章:使用 Kubernetes 编排容器
在本章及接下来的几章中,我们将介绍 KCNA 认证中最重要、也许是最难的部分——Kubernetes 基础知识。它占据了总考试题目的近一半(46%),所以理解所有细节至关重要。我们将一步步进行,同时也会获得实际的 Kubernetes 经验,帮助你记住通过考试所需的所有内容。
在本章中,我们将学习 K8s 架构的特性和基础知识,它的 API、组件,以及最小的可部署单元——Pod。我们将借助minikube项目,在本地安装并运行 Kubernetes,帮助我们一步步完成学习。
我们将覆盖以下主题:
-
Kubernetes 架构
-
Kubernetes API
-
K8s – 容器编排的瑞士军刀
-
使用 minikube 安装并探索 Kubernetes
让我们开始吧!
Kubernetes 架构
正如你所知,Kubernetes 用于编排运行在多个服务器上的容器,这些服务器组成了 Kubernetes 集群。那些服务器通常被称为节点,节点可以是运行在本地、云端的虚拟机,或者是裸金属服务器。你甚至可以将不同类型的节点组合在一个 Kubernetes 集群中(例如,几个由虚拟机表示的节点加上其他几个裸金属服务器节点)。
Kubernetes 中有两种区别明显的节点类型:
-
控制平面节点(有时也称为主节点)
-
工作节点
容器化的应用运行在工作节点上,而 K8s 集群管理组件运行在控制平面节点上。我们可以在图 5.1中看到更详细的说明。

图 5.1 – Kubernetes 组件
控制平面节点运行多个专门的 K8s 服务,并对 Kubernetes 集群进行全局决策,如调度容器化应用。控制平面节点还负责管理集群中的工作节点。
以下五个服务在控制平面节点上运行:
-
API 服务器(kube-apiserver):一个核心服务,公开 Kubernetes 的 HTTP API,用于内部和外部集群的通信。集群内的所有操作都通过 API 服务器进行——例如,当你查询集群或特定应用的状态,或者启动一个新的容器时。
-
集群数据存储(etcd):存储所有 Kubernetes 集群状态和配置的地方。etcd 是一个开源的分布式键值存储系统,用于此目的,它是唯一的有状态组件。
-
调度器(kube-scheduler):一个决定应用容器将在集群中的哪个工作节点上运行的组件。影响调度决策的因素包括单个应用的需求、节点负载、硬件或策略限制等。
-
Controller manager (kube-controller-manager):这是一个运行各种控制器进程的组件,如 Node、Job 或 Deployment 控制器。这些控制器监视集群中各资源的当前状态,并在当前状态与期望状态不一致时采取行动。
-
可选的 Cloud controller manager (cloud-controller-manager):这是一个让你通过运行特定于提供商的控制器进程,将 Kubernetes 集群与云提供商集成的组件。例如,它允许你为容器化应用创建负载均衡器,或者判断一个工作节点的云实例是否已被删除。Cloud controller manager 是在 K8s 部署在本地时不需要的组件。
让我们继续讨论工作节点的组件:
-
Kubelet:一个代理,确保分配给节点的容器正在运行并保持健康。Kubelet 还会向 API 服务器报告状态。
-
Proxy (kube-proxy):这是一个网络代理,帮助实现 Kubernetes Service 功能。Proxy 在节点上维护网络规则,以允许容器从 K8s 集群内外进行通信。
-
Container runtime:这是一个负责基本容器操作的软件。得益于 CRI,Kubernetes 可以使用不同的容器运行时。目前最流行的运行时之一是 containerd。
注意
Kubelet 不管理通过 Kubernetes API 以外的方式创建的容器。例如,通过其他方式在工作节点上创建的容器,Kubernetes 是无法识别的。
今天,工作节点组件也运行在控制平面节点上。没错——在 Kubernetes 控制平面中,你不仅会有 scheduler、kube-apiserver、etcd 和 kube-controller-manager,还会有 kubelet、kube-proxy 和 runtime。这意味着工作组件会在集群中的所有 Kubernetes 节点上运行。
为什么会这样?原因是控制平面组件本身就部署在容器中,因此可以通过所谓的静态 pods 由 Kubernetes 管理。或者,控制平面组件可以通过 systemd 启动和管理,但这种方法如今越来越不流行了。
什么是 pod?
Pod 是可以在 Kubernetes 中创建的最小可部署单元。Pod 是一组共享存储、网络和容器运行规范的一个或多个容器。
你可以将一个 pod 看作是 Kubernetes 对容器的封装,在 K8s 集群中部署任何应用时,你总是会处理 pod。即使你只需要运行一个由单个容器组成的小型简单应用,你也需要定义一个包含该单一容器的 pod。换句话说,没有 pod 封装就无法在 Kubernetes 上运行容器。
在一个 Pod 中有两个或多个容器的情况也非常常见,其中 Pod 中的第二个或第三个容器作为辅助容器来帮助主容器。这种情况发生在多个容器需要协同工作并共享资源时。这种辅助容器被称为边车容器。在上一章中,我们学习了服务网格,它利用边车容器将代理与应用容器一起部署。另一个可能在同一个 Pod 中运行多个容器的例子是从主容器中运行的应用程序收集监控指标。边车容器还可以用于日志聚合——例如,边车容器可能会收集并转发主应用容器的日志到长期存储中,如图 5.2所示:

图 5.2 – 包含两个容器的 Pod 示例
注意
同一个 Pod 中的容器始终被共置并共同调度在同一个节点上。
除了边车容器,还有另一种类型的容器,称为初始化容器(Init Containers)。它们对于运行设置脚本和初始化工具非常有用,这些工具是容器化应用所需的。
initContainers
这些容器在 Pod 中的其他容器启动之前按顺序执行。直到所有initContainers完成,其他容器才会启动,initContainers 每次 Pod 启动时都会运行。
除了允许你在 Kubernetes 上运行共置和独立的容器,Pods 还具有更多的功能,包括以下内容:
-
共享存储:Pod 中的所有容器都可以访问共享的卷,从而允许容器共享数据。
-
共享网络:Pod 中的所有容器共享网络命名空间,并拥有一个 IP 地址和网络端口。容器没有独立的 IP 地址,但 Pod 有,且 Pod 中的容器可以通过localhost简单地相互通信。此外,由于共享网络,Pod 中的两个容器不能监听相同的网络端口。
-
共享内存:Pod 中的容器可以使用标准的 Linux 进程间通信,如SystemV 信号量或POSIX 共享内存。
现在,我们已经多次提到过 Kubernetes 集群,这意味着我们需要至少两个节点。技术上,你可以在单个节点上运行 Kubernetes,同时结合控制平面和工作节点的功能。但是,你绝不应该在生产环境中这样做。这仅适用于学习或 开发用途。
在现实场景中,我们至少会运行三个控制平面节点和多个工作节点。这些节点应该分布在多个故障域中(通常由云服务提供商称为可用区),这些故障域可能是由单独的数据中心组成,通过高速带宽网络互联。在单个服务器或可用区出现故障的情况下,这样的 Kubernetes 集群仍然可以保持运行。
集群中只有一个控制平面节点不足以满足生产环境的需求,因为在出现故障时,你将无法查询集群和应用程序的状态,无法启动带有容器的新 pod,也无法进行任何更改。而且,你不希望丢失你的etcd 数据存储,它保存着有关集群的所有信息。
因此,像许多集群系统一样,最佳实践是运行奇数个控制平面节点;例如三个或五个。拥有奇数个节点有助于防止分脑情境,在网络故障的情况下,集群的两个部分将无法建立多数(例如,四个节点分成两部分可能会导致不一致或无法正常工作)。
注意
这种做法不适用于工作节点,在集群中运行两个、四个、七个甚至 200 个工作节点是可以的。
我知道这有很多内容需要消化,但当我们开始使用 Kubernetes 并部署我们的第一个几个 pod 时,事情会变得更加清晰和简单。稍后在本章中,我们将更详细地了解 pod 规格,但现在,让我们先了解更多关于 Kubernetes API 以及如何使用它。
Kubernetes API
正如我们之前所学,Kubernetes API 服务器是所有集群操作的主要入口。当我们想了解集群的状态、节点或 pod 的数量或其他资源及其状态时,我们需要使用 Kubernetes API。所有操作都是如此,例如创建新 pod 或修改其他资源的规格。简而言之,API 服务器是 K8s 的大脑。
与 Kubernetes API 交互有多种方式:
-
使用
kubectl与 K8s 集群交互,并管理或调试在 Kubernetes 中运行的应用程序。 -
dashboard,然而并不支持kubectl所提供的所有功能。 -
客户端库:支持多种编程语言,包括 Golang、Java、Python、JavaScript 等。它们允许你编写使用 Kubernetes API 的软件,并帮助处理常见的任务,如身份验证。
-
使用
curl或wget,你可以直接访问 Kubernetes API。这种方式不常用,但有时还是有帮助的。
这个列表并不详尽,今天,你还可以找到许多其他工具(如 kubectl)。
在前几章中,我们提到过 Kubernetes API 是声明式的。
声明式 API
声明式 API 意味着你声明 Kubernetes 资源和 Kubernetes 控制器的期望状态,并不断确保 Kubernetes 对象的当前状态(例如,某个应用的 Pod 数量)与声明的期望状态保持一致。
因此,Kubernetes 的 API 与命令式方法不同,在命令式方法中你会指示服务器该做什么。在通过 API 定义了期望状态后,Kubernetes 会使用其 kube-controller-manager,指示运行在无限控制循环中的控制器检查资源状态是否与期望状态一致,并在不一致时进行协调。例如,我们已经指示 K8s 运行我们的应用程序并保持三个副本,如果其中一个副本最终崩溃,Kubernetes 将自动检测到只剩两个副本在运行,并会生成一个新的 Pod 来运行我们的应用程序:

图 5.3 – Kubernetes 控制循环
注意
控制器管理器的无限控制循环有时也被称为协调循环。
由于 Kubernetes 的开发速度非常快,其 API 不断演进。新的 API 资源可以频繁添加,而旧的资源或字段会根据 Kubernetes 的弃用政策在多个发布周期后被移除。为了便于进行这些更改,K8s 支持多个 API 版本和 API 分组,并在较长时间内与现有的 API 客户端保持兼容。例如,同一资源可以有两个 API 版本:v1 和 v1beta1。你可能最初使用 v1beta1 版本创建了一个资源,但你仍然可以在几个版本发布期间,使用 v1 或 v1beta1 API 版本对该资源进行更改。
每个新的 Kubernetes 功能都有一个定义的生命周期,相关的 API 会在多个 K8s 版本中从 alpha 发展到 beta,再到正式发布的状态,如 图 5.4 所示:

图 5.4 – Kubernetes 功能生命周期
值得一提的是,Kubernetes 的 alpha 功能通常是禁用的。然而,你可以通过设置所谓的 feature gates 来启用这些功能。Beta 和稳定版的 Kubernetes 功能默认是启用的。
在 Kubernetes 中,对现有功能的新重大更改的实现通常从 Kubernetes 增强提案 (KEP) 开始。这些是详细的规范文档,概述了更改的动机、目标和设计。你可以在 Kubernetes GitHub 仓库中找到现有的 KEP(github.com/kubernetes/enhancements/tree/master/keps)。
因为 Kubernetes 是一个复杂的项目,包含许多组件,所以它有多个操作领域,包括存储、网络、扩展等。这些领域通常由 Kubernetes 特别兴趣小组(SIGs)来覆盖。SIGs 是专注于 Kubernetes 特定部分的小型开发者社区。由于 K8s 是一个开源项目,任何人都可以成为 SIG 的一员,修复问题、审查代码和提出改进建议。
最后但同样重要的是,Kubernetes API 是高度可扩展的,扩展方式有两种:
-
使用 自定义资源定义(CRDs):一种不需要编程的方法
-
使用 聚合层:一种需要编程的方法,但它允许你对 API 行为有更多控制
这两种方法都允许你为 Kubernetes 添加额外的功能,超出标准 Kubernetes API 所提供的范围。对于 KCNA 考试的范围,你不需要了解太多细节,但随着你实践经验的积累,你会发现可扩展的 API 是 Kubernetes 的一项非常强大的功能,它使我们能够在不需要了解或修改现有 K8s 源代码的情况下添加独特的功能。
现在我们已经了解了 Kubernetes API,让我们进一步了解 Kubernetes 的一些功能,正是这些功能使其成为容器编排的第一选择。
K8s——容器编排的瑞士军刀
我们多次提到 Kubernetes 非常适合运行由许多微服务组成的云原生应用,这些微服务打包在容器中。那到底是为什么呢?
Kubernetes 提供了许多功能,极大地简化了大规模容器集群的操作。我们已经知道,可以使用 Kubernetes 自动扩展容器的数量或重启故障的容器。那么,Kubernetes 还有哪些其他功能呢?
-
自动化滚动更新和回滚:允许你以受控方式部署应用的新版本和配置更改,监控应用的健康状况并确保它始终运行。如果出现问题,K8s 还允许你回滚到应用的先前版本、镜像或配置。
-
服务发现与负载均衡:允许集群中不同的微服务轻松找到彼此。在一组表示相同微服务的 Pod 中,每个 Pod 都会有一个 IP 地址,但整个集合会有一个单一的 DNS 名称,从而简化服务发现和负载分配。
-
秘密和配置管理:允许你管理微服务配置和机密,而无需重新构建容器镜像或暴露敏感凭证——例如,当服务需要访问数据库或有许多配置参数需要变化时。
-
自愈:允许你自动重启由于任何原因失败的容器,如果工作节点停止响应,自动将容器重新调度到另一个节点,重启未通过预定义健康检查的容器,并且仅当应用程序完全启动并准备好服务时,才将请求路由到容器。
-
水平扩展:允许通过增加或减少运行应用程序的 Pod 数量来横向扩展容器化应用程序。这可以手动完成,也可以自动完成,例如,基于 CPU 使用率进行扩展。
-
批处理执行:允许你调度容器的执行,并灵活管理批处理处理或 CI 工作负载。
-
自动垃圾回收:允许根据请求的资源、当前集群利用率或其他需求,自动确定最佳的工作节点来启动容器。它还允许定义工作负载优先级,以处理不同的关键和非关键应用程序。
-
存储编排:允许你集成和管理自己选择的存储系统。当 Pod 启动时,Kubernetes 可以自动配置和挂载存储卷,并根据需要将卷重新挂载到不同的节点。
这是一长串内容,但仍然不是 100% 完整的。在上一节中,我们看到可以扩展 Kubernetes API 来添加新功能。如今,Kubernetes 拥有一个丰富的生态系统,拥有众多扩展 Kubernetes 的项目,甚至可以让你管理除容器之外的其他工作负载。没错,Kubernetes 不仅可以用来编排容器。我们应该提到几个项目:
-
KubeVirt:一个用于通过 Kubernetes 编排虚拟机与容器一起使用的项目。这通常用于当工作负载无法轻松容器化的情况,或在进行应用程序容器化的过程中,有些应用程序仍然运行在虚拟机中。
-
Kubeless:一个运行在 Kubernetes 上的无服务器计算框架。它为你的 K8s 集群增加了 FaaS 能力,并可以作为云提供商 FaaS 服务的替代方案。
-
Knative:另一个 Kubernetes 的无服务器计算框架,最近被 CNCF 接受。它最初由 Google 创建,自 2018 年以来一直在积极开发。
-
OpenFaas:另一个可以与 Kubernetes 一起使用或独立使用的无服务器框架。与其他两个无服务器框架类似,它支持多种编程语言来编写函数,包括Golang、Java、Python、Ruby、C# 等。
对于 KCNA 考试或 CNCF 认证的其他 Kubernetes 认证,你不需要了解这些项目的进一步细节。只需记住,借助 KubeVirt,能够编排虚拟机,并且可以通过如 Knative 等项目在 Kubernetes 上提供 FaaS。如果你想了解更多这些项目的信息,可以参考本章末尾的进一步阅读部分中的链接。
注意
即使有了 Kubernetes 的帮助,也可以管理虚拟机,但它仍然主要用于协调容器。
在我们开始安装 Kubernetes 并尝试其众多功能之前,让我们先看看一个例子,展示 K8s 如何成为开发工作流的一部分,如 图 5.5 所示:

图 5.5 – 使用 Kubernetes 的简单开发工作流示例
简化的工作流程可能如下所示:
-
开发者编写一个新微服务的代码,并将其提交到 GitHub 仓库。正如你可能记得的那样,开发者无需学习新的编程语言即可在容器中运行应用程序。
-
应该有一个
Dockerfile,定义将应用程序打包为容器镜像所需的步骤。在构建容器镜像之前,CI 管道中会执行自动化测试。如果测试成功,将构建 Docker 镜像并推送到镜像仓库。 -
接下来,容器的部署会在 Kubernetes 集群中触发。要在 Kubernetes 上运行容器,我们需要在
kubectl工具中定义一个 pod 规格,并确保其已配置为与我们的 K8s 集群协同工作。 -
当规格应用时,Kubernetes 会负责找到合适的工作节点,从镜像仓库下载容器镜像,并启动我们的容器化应用程序的 pod。我们可以定义它以运行多个副本,以满足高可用性要求并平衡负载。
-
这个过程可以重复多次,Kubernetes 可以以滚动更新的方式处理新应用版本的镜像部署,例如,每次只会替换一个副本。
一个 pod 只是 Kubernetes 对象的一个例子,它是最小的可部署单元。在下一章,我们将了解其他使用 Kubernetes 控制器并提供更高级功能的资源。
Kubernetes 对象
Kubernetes 对象是持久化的条目,表示集群的状态,包括哪些容器化应用程序在运行,在哪些节点上运行,应用程序可用的资源及其相关策略(例如,重启策略、调度要求等)。
Kubernetes 对象在我们应用规格定义的那一刻就会被创建,它们本质上是一个 意图记录。一旦对象被创建,Kubernetes 会确保该对象存在,并处于 期望状态。达到期望状态所需的时间取决于许多因素,可能只需要一秒钟,或者如果例如从镜像仓库下载的容器镜像很大且网络性能较差,则可能需要几分钟。
注意
Kubernetes 规格定义文件也称为 Kubernetes 清单。它是 API 对象的 JSON 或 YAML 格式的规范。
现在我们已经熟悉了一些基础知识,让我们亲自尝试一下。在接下来的章节中,我们将安装一个单节点的 Kubernetes 来获得实践经验。
安装并探索使用 minikube 的 K8s
今天,许多项目允许你快速启动一个简单的 K8s 集群或用于学习或本地开发的单节点 Kubernetes。我们将使用 minikube,这是一个由官方 Kubernetes SIG 支持的项目,专注于集群部署及其生命周期管理。其他项目也能达到类似效果,比如 Kind 和 CRC。
重要注意事项
miniKube、Kind 和其他一些项目并非为生产就绪的 Kubernetes 集群而设计。请不要使用这些设置来运行重要的工作负载!
快速确认你的系统满足 minikube 的要求:
-
最新版本的 Linux、macOS 或 Windows
-
2+ 个 CPU 核心
-
2+ GB 的可用内存
-
20+ GB 的可用磁盘空间
-
网络连接
-
管理员/超级用户权限
-
容器或虚拟机管理器(可以使用前面章节中的 Docker 引擎安装)
首先,在浏览器中打开 minikube 启动文档(minikube.sigs.k8s.io/docs/start/),然后在 安装 部分选择你的操作系统。确保选择 稳定 版本,并在运行 macOS 或 Linux 时选择正确的 CPU 架构。
注意
如果你没有安装 curl 工具,也可以直接复制文档中的 URL,粘贴到新的浏览器标签页并保存到计算机,就像下载其他文件一样。在这种情况下,installation 命令应该在保存 minikube 二进制文件的同一目录下执行。
打开终端并执行适合你操作系统的命令,如果需要,输入密码。例如,在 macOS 上使用 x86-64 CPU 时,输出可能如下所示:
$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 68.6M 100 68.6M 0 0 38.5M 0 0:00:01 0:00:01 --:--:-- 38.7M
$ sudo install minikube-darwin-amd64 /usr/local/bin/minikube
Password:
到目前为止,我们准备好借助 minikube 的 start 命令启动本地 Kubernetes:
$ minikube start
😄 minikube v1.25.2 on Darwin 12.4
✨ Automatically selected the docker driver. Other choices: hyperkit, ssh
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
💾 Downloading Kubernetes v1.23.3 preload ...
> preloaded-images-k8s-v17-v1...: 505.68 MiB / 505.68 MiB 100.00% 40.26 Mi
> gcr.io/k8s-minikube/kicbase: 379.06 MiB / 379.06 MiB 100.00% 14.81 MiB p
🔥 Creating docker container (CPUs=2, Memory=4000MB) ...
🐳 Preparing Kubernetes v1.23.3 on Docker 20.10.12 ...
▪ kubelet.housekeeping-interval=5m
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🌟 Enabled addons: storage-provisioner, default-storageclass
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
注意
你可能有一个较新的 Kubernetes 版本,minikube start 的输出也会有所不同。如果遇到错误,输出中很可能会有指向相关问题的链接,帮助你解决问题。如果你使用的是与前面章节中不同的系统,你需要先安装 Docker 引擎。有关详细信息,请参考 第三章,容器入门,以及 minikube 的驱动程序文档页面(minikube.sigs.k8s.io/docs/drivers/)。对于其他问题,请查看本章 进一步阅读 部分中的 minikube 故障排除指南。
现在我们已经启动了 Kubernetes,应该能够使用kubectl CLI 工具访问其 API。你可以自己下载kubectl,但 minikube 可以为你完成这项工作。推荐让minikube来做这件事,因为它会自动选择正确的版本。你只需第一次运行任何带有kubectl的命令——例如列出集群中所有 Kubernetes 节点的命令:
$ minikube kubectl get nodes
> kubectl.sha256: 64 B / 64 B [--------------------------] 100.00% ? p/s 0s
> kubectl: 50.65 MiB / 50.65 MiB [-------------] 100.00% 54.17 MiB p/s 1.1s
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane,master 18m v1.23.3
Minikube 已经为我们下载了 K8s CLI 并执行了命令。不出所料,我们只有一个名为minikube的节点,它的角色是control-plane和master。
让我们看看当前在 Kubernetes 中运行的内容。你可以使用kubectl get pods命令列出 Pods:
$ minikube kubectl get pods
No resources found in default namespace.
如我们所见,目前没有任何内容在运行,因为我们刚刚启动了一个新的集群。让我们使用一个额外的选项重新运行相同的命令——也就是--all-namespaces(注意kubectl和get之间的两个额外破折号;它们用于将kubectl与minikube的参数分开,因为这两个命令各自有不同的参数集):
$ minikube kubectl -- get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-64897985d-x28hm 1/1 Running 0 31m
kube-system etcd-minikube 1/1 Running 0 32m
kube-system kube-apiserver-minikube 1/1 Running 0 32m
kube-system kube-controller-manager-minikube 1/1 Running 0 32m
kube-system kube-proxy-hwv2p 1/1 Running 0 32m
kube-system kube-scheduler-minikube 1/1 Running 0 32m
kube-system storage-provisioner 1/1 Running 0 32m
输出已经发生了很大变化,现在我们可以看到在本章开头学习的所有 Kubernetes 组件:kube-apiserver、kube-controller-manager、kube-proxy、kube-scheduler、etcd以及其他几个在kube-system命名空间中的独立 Pods 中运行的组件。
Kubernetes 命名空间
Kubernetes 命名空间提供了一种分组机制,用于在集群内区分 Kubernetes 对象。通常,Kubernetes 命名空间用于按团队、项目或应用程序分组工作负载。kube-system命名空间是为 Kubernetes 的组件保留的。
现在,让我们看看在我们全新的 Kubernetes 中有哪些命名空间:
$ minikube kubectl -- get namespaces
NAME STATUS AGE
default Active 56m
kube-node-lease Active 56m
kube-public Active 56m
kube-system Active 56m
default命名空间,顾名思义,实际上就是一个标准的命名空间,容器工作负载将在该命名空间中默认创建。kube-node-lease是另一个为节点心跳(检查节点是否运行)的 Kubernetes 保留命名空间,kube-public是一个自动创建的命名空间,用于公共资源,例如集群发现所需的资源。
一般做法是为每个应用程序、合作工作的微服务组或每个团队创建新的命名空间。让我们创建一个新的命名空间并命名为kcna,通过执行kubectl -- create namespace kcna:
$ minikube kubectl -- create namespace kcna
namespace/kcna created
注意
你还可以设置一个别名来代替minikube kubectl,正如 minikube 文档中建议的那样($ alias kubectl="minikube kubectl --"),以便省去每次都写minikube的麻烦。(确保在不再使用minikube时删除它。)
现在,让我们将一个容器化的Nginx Web 服务器部署到我们新的kcna命名空间中。我们可以通过添加--namespace参数来始终设置我们想要执行kubectl命令的命名空间:
$ minikube kubectl -- create -f https://k8s.io/examples/pods/simple-pod.yaml --namespace kcna
pod/nginx created
这里,我们提供了一个 Pod 规范文件,该文件位于 k8s.io/ 网页上显示的示例中。Kubernetes CLI 足够智能,可以下载文件,验证规范并应用它,创建它所定义的对象——在本例中,是一个包含 Nginx 的单一 Pod。如果我们自己编写这个简单的 Pod 规范文件并以 YAML 格式保存,它会像这样:
$ cat simple-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
让我们看看该规范文件的每一行代表什么。
apiVersion: v1 定义了我们用于创建该对象的 Kubernetes API 版本。如你所知,Kubernetes 的 API 会从 alpha 发展到 beta,再到稳定版本。v1 是本示例中的稳定版本:
-
kind: Pod– 定义了我们描述的对象类型。 -
metadata:– 定义对象的元数据,如name或其他注解。 -
name: nginx– 定义 Pod 的名称。 -
spec:– 定义了我们描述对象期望状态的块。 -
containers:– 定义了作为该 Pod 一部分的容器列表。 -
-name:nginx– Pod 中第一个容器的名称。多个容器可以在一个 Pod 中一起运行。 -
image: nginx:1.14.2– 镜像的名称(nginx),可以选择在前面加上镜像仓库 URL,后面跟上镜像标签(1.14.2)。如果在一个 Pod 中运行多个容器,您需要为每个容器定义镜像和名称。 -
ports:– 这是一个可选的信息块,用于告知我们哪些端口将被暴露。这些端口是容器中进程正在监听的端口。然而,不指定此块并不会阻止端口暴露。 -
- containerPort: 80– 这是本示例中的端口80。
注
YAML 格式中缩进非常重要,缺少一个空格或多出一个空格都会导致格式错误。如果应用规范时存在解析错误,kubectl 会报错。建议从本书的 GitHub 仓库复制示例文件,以避免拼写和格式错误。
好的,那么,我们的 Nginx Pod 出了什么问题?
$ minikube kubectl -- get pods --namespace kcna
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 10m
它处于 Running 状态,并且 Pod 中的 1/1(一个中的一个)容器已就绪。Pod 可能处于多种状态:
-
Pending: 规范已被 Kubernetes 接受,当前 Pod 正在等待调度,或等待从镜像仓库下载所请求的容器镜像。 -
Running: Pod 已被分配到某个节点,并且 Pod 中的所有容器都已创建。至少有一个容器正在运行。 -
Succeeded: Pod 中的所有容器已成功完成/退出,且以 良好 的退出代码(例如零)退出。当容器中的应用程序正常关闭时,会发生这种情况。 -
Failed: Pod 中的所有容器都已终止,并且至少有一个容器失败;例如,容器以非零退出代码退出。 -
Unknown:无法获取 pod 的状态。这可能发生在 Pod 应该运行的节点无法访问时,例如,由于网络问题。 -
ErrImagePull:清单中指定的镜像无法获取(拉取)。这可能是由于镜像名称错误或标签错误,导致镜像在镜像库中不存在。
此外,你可能会遇到 ContainerCreating 或 Terminating 状态,分别描述 Pod 容器的启动或终止阶段。
目前,我们的 Nginx pod 什么也不做;除了它的默认静态页面外,不提供任何应用程序或内容。在下一章中,我们将学习如何使用 kubectl describe 命令在 Kubernetes 中暴露和访问 nginx pod 中的应用程序。
$ minikube kubectl -- describe pods nginx --namespace kcna
你会发现关于 pod 的大量信息,例如运行它的节点、启动时间、IP 地址、环境变量、最近的事件等。此信息对于我们调试失败的 pod 或应用程序非常有帮助。
既然我们已经稍微使用过 kubectl,现在也来试试 Kubernetes 仪表板。Minikube 提供了一个便捷的单命令仪表板安装,使用 minikube dashboard:
$ minikube dashboard
🔌 Enabling dashboard ...
▪ Using image kubernetesui/dashboard:v2.3.1
▪ Using image kubernetesui/metrics-scraper:v1.0.7
🤔 Verifying dashboard health ...
🚀 Launching proxy ...
🤔 Verifying proxy health ...
🎉 Opening http://127.0.0.1:55089/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/ in your default browser...
此时,仪表板将被安装到新的 kubernetes-dashboard 命名空间中,浏览器应会自动在新标签页中打开它(如果没有自动打开,请尝试执行 minikube dashboard --url 以获取仪表板的 URL)。你需要在左上角的下拉菜单中切换到另一个命名空间,因为 default 命名空间中当前没有任何内容。如果你切换到 kcna 命名空间,你将看到我们的 nginx pod,而如果切换到 kube-system,你将看到我们在本章中描述的 Kubernetes 控制平面组件。你可能会注意到,除了 pod,还有 Deployment、Daemon Set 和 Replica Set 工作负载。它们将在下一章中讨论,届时我们将看到如何使用它们来创建 pod,并了解这些 K8s 资源的特点。
随时可以自己探索更多的仪表板;当你完成后,可以使用仪表板或通过 kubectl delete 命令删除我们的 Nginx pod。要中断 minikube dashboard,你可以使用 Ctrl + C 快捷键:
$ minikube kubectl -- delete pods nginx --namespace kcna
pod "nginx" deleted
注意
kubectl 是一个用户友好的工具,允许你输入 pod、pods 或简单地输入 po,它们的意思是一样的。它还拥有很多便捷的简短名称,例如 ns,代表命名空间。要列出所有简短名称,请运行 minikube kubectl api-resources。
如果你在进入下一章之前准备关闭工作站,你也可以通过执行 minikube stop 命令临时停止你的 Kubernetes 节点:
$ minikube stop
✋ Stopping node "minikube" ...
🛑 Powering off "minikube" via SSH ...
🛑 1 node stopped.
这一章内容较长且非常密集,恭喜你已经走到了这一部分!在进入下一章之前,先休息一下,并确保回答提供的问题。
概要
在本章中,我们终于接触到了 Kubernetes。我们了解了很多关于它的架构、组件和 API。Kubernetes 集群由控制平面(也称为主节点)和工作节点组成,其中控制平面节点运行 K8s 管理组件,工作节点在 kubelet、容器运行时 和 kube-proxy 的帮助下运行实际的容器化应用程序。在主节点组件中,有 kube-apiserver、etcd、kube-scheduler、kube-controller-manager,以及可选的 cloud-controller-manager。
我们看到 pod 是 Kubernetes 中最小的可部署单元,它允许我们在 K8s 上运行单独的容器,也可以将多个容器一起运行。一个 pod 内的容器是耦合的,可以共享存储、网络和内存。pod 中的辅助容器通常被称为 sidecar,它可以通过做日志聚合等任务来帮助主容器运行主应用程序。
Kubernetes API 是 声明式 的。当我们使用 K8s 时,我们描述集群中资源的期望状态;Kubernetes 确保在对象创建后,当前状态会达到期望状态。由于开发的快速发展,Kubernetes API 被分组并多版本化,默认情况下仅启用 beta 和 稳定(GA)特性。有多种方式可以访问 Kubernetes API,其中 kubectl CLI 和 仪表板 是最常用的方式。K8s 的一个特色是它能够通过 CRD 和聚合层扩展其 API。
说到功能,Kubernetes 提供了很多强大功能,从自动滚动发布、服务发现、和密钥管理,到自动扩展、自我修复,甚至存储编排。我们将在接下来的章节中实践其中许多功能。还可以借助 KubeVirt 和 Knative 等独立项目,使用 Kubernetes 来管理虚拟机或提供 FaaS。
在最后一节中,我们使用 minikube 项目安装了一个简单的单节点 Kubernetes 部署,并了解了 Kubernetes 命名空间 的概念,用于资源的隔离和分组。我们还创建了一个包含单个 Nginx 容器的 pod,并探索了 YAML 格式的最简 pod 规格定义。
在下一章中,我们将学习其他 Kubernetes 资源及其使用方法。我们将学习如何使用 Kubernetes 配置和扩展多容器应用程序,如何使用 Kubernetes 运行有状态工作负载,以及了解如何暴露在 K8s 上运行的应用程序。
问题
在我们总结时,这里有一系列问题供您测试有关本章内容的知识。您可以在附录的评估部分找到答案:
-
以下哪项是 Kubernetes 中最小的可调度单元?
-
容器
-
边车
-
Pod
-
部署
-
-
在一个 pod 中运行的容器共享以下哪些选项(可选择多个)?
-
名称
-
存储
-
网络
-
内存
-
-
以下 Kubernetes 节点类型有哪些(选择多个)?
-
Secondary
-
主节点
-
工作节点
-
Primary
-
-
以下哪些是控制平面节点的组件(选择多个)?
-
Docker, kube-scheduler, cloud-controller-manager
-
kube-master-server, kubelet, kube-proxy
-
kube-scheduler, kube-controller-manager
-
kube-api-server, etcd
-
-
以下哪个 K8s 集群配置可以推荐?
-
1 个主节点,5 个工作节点
-
2 个主节点,3 个工作节点
-
2 个主节点,20 个工作节点
-
3 个主节点,10 个工作节点
-
-
以下哪些组件用于存储 Kubernetes 集群状态?
-
kube-api-server
-
kube-volume
-
kubelet
-
etcd
-
-
以下哪个组件是 Kubernetes 用来下载镜像并启动容器的?
-
Kubelet
-
容器运行时
-
etcd
-
kube-proxy
-
-
以下哪个组件负责 Kubernetes 控制器流程?
-
kube-api-server
-
kube-proxy
-
kube-controller-manager
-
kube-scheduler
-
-
以下哪些可以用来访问 Kubernetes API(选择多个)?
-
kubeadmin
-
kubectl
-
kubelet
-
dashboard
-
-
Kubernetes 有一个声明式 API,这意味着什么?
-
我们总是需要声明一个 YAML 规格文件来使用 K8s API
-
我们声明期望的状态,K8s 将会在某个时刻达到这个状态
-
我们告诉 Kubernetes 如何处理哪些资源
-
我们声明期望的状态,K8s 将会不断努力达成这个状态
-
-
以下哪些 Kubernetes API 版本默认启用(选择多个)?
-
Alpha
-
Beta
-
Gamma
-
稳定
-
-
如何扩展 Kubernetes API 添加新功能(选择多个)?
-
代码资源定义
-
聚合层
-
扩展层
-
自定义资源定义
-
-
以下哪个项目允许你将 Kubernetes 扩展到容器编排之外(选择多个)?
-
Knative 用于 FaaS
-
Linkerd 用于 IPAM
-
Kvirt 用于虚拟机编排
-
KubeVirt 用于虚拟机编排
-
-
什么有助于检测 Kubernetes 资源当前状态与期望状态之间的差异?
-
容器运行时
-
Kubernetes 调度器
-
自定义资源定义
-
协调循环
-
-
什么是运行在 pod 中的次要容器?
-
Flatcars
-
Sidecars
-
Podcars
-
Helpcars
-
-
以下哪个格式用于编写 Kubernetes 规格文件?
-
CSV
-
Protobuf
-
YAML
-
Marshal
-
-
以下哪个 Kubernetes 组件负责将新 pod 分配到节点上?
-
kube-api
-
kube-proxy
-
kube-scheduler
-
kube-controller-manager
-
-
以下哪个 K8s CLI 命令可以用来列出
development命名空间中的 pods?-
kubectl list pods -``n development -
kubectl get pods --``namespace development -
kubectl show pods --``namespace development -
kubectl getpods --all-namespaces
-
-
以下哪个 K8s CLI 命令可以用来列出集群中的所有命名空间?
-
kubectl listnamespaces --all-namespaces -
kubectlshow namespaces -
kubectlget namespaces -
kubectlget all
-
-
以下哪个 pod 状态表示其容器正在执行?
-
执行中
-
成功
-
运行中
-
ContainerCreated
-
-
以下哪些 Pod 状态表示其所有容器都在运行(可多选)?
-
100%
-
1/2
-
2/2
-
1/1
-
进一步阅读
若要了解本章中涵盖的更多内容,请查看以下资源:
-
Minikube 故障排除指南:
minikube.sigs.k8s.io/docs/handbook/troubleshooting/ -
常用的
kubectl命令:kubernetes.io/docs/reference/kubectl/cheatsheet/ -
K8s Pod 概念:
kubernetes.io/docs/concepts/workloads/pods/ -
K8s 命名空间概念:
kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
第六章:使用 Kubernetes 部署和扩展应用程序
在这一章中,我们将继续探索 Kubernetes 丰富的功能和生态系统。我们将了解 Kubernetes 中还存在哪些资源及其目的,如何实现应用程序的自我修复和扩展,如何使用 Kubernetes 的服务发现,如何在 Kubernetes 中运行有状态的工作负载。我们还将使用上一章中安装的 minikube Kubernetes 进行若干练习(如果你跳过了这一部分,请查看第5 章的最后部分)。
这是本书中最密集且最重要的一章之一,所以请确保在继续前进之前回答所有问题并完成所有实际操作任务。如果你觉得某些部分难以理解,可以读两遍并参考进一步阅读部分。
我们即将涵盖以下激动人心的话题:
-
Deployments、ReplicaSets 和 DaemonSets
-
运行有状态的工作负载
-
应用程序配置与服务发现
-
确保应用程序处于正常运行并健康状态
所以,直接进入正题吧!
Deployments、ReplicaSets 和 DaemonSets
正如我们在上一章中看到的,Kubernetes 中有比Pods和命名空间更多的资源。首先让我们来了解一下Deployments。
Deployment
这是一个声明式更新 Pod 和 ReplicaSet 的封装器。你在 Deployment 资源规格中描述所需状态后,Kubernetes Deployment 控制器会按照配置的速率将当前状态更改为所需状态。
听起来有些复杂,但本质上 Deployment 是用来控制 Pods 并管理这些 Pods 的应用程序生命周期的。Pods 是最小的部署单元,用来封装容器,但它们不提供任何高级 Kubernetes 功能,如自我修复、滚动更新或自动扩展。然而,Deployment 是提供这些功能的。
因为 Pods 本身没有弹性,一个失败的应用程序容器会将 Pod带下去。这就是为什么在实际操作中,你通常会使用 Kubernetes 中的高级资源之一,如 Deployment,来在失败时自动重新创建 Pods。Deployment 控制器不断监视它所管理的 Pods 的当前状态,并确保所需数量的 Pods 在运行。我们很快会展示如何实现这一点。
ReplicaSet
ReplicaSet 用于确保在任何给定时间运行指定数量的副本 Pod。ReplicaSet 也被 Deployment 使用,以确保所需数量的 Pod 运行(即使只有一个 Pod 应当运行)。
与 ReplicaSet 相比,Deployment 是一个更高级的封装资源,它管理着 ReplicaSet 并提供其他有用的功能。ReplicaSet 不允许你实现自定义更新编排,因此建议使用 Deployment 而不是直接使用 ReplicaSet。
让我们回到上一章中使用的 minikube Kubernetes 设置,进行一个快速演示。如果你之前已经停止了集群,请先使用 minikube start 命令启动它:
$ minikube start
😄 minikube v1.25.2 on Darwin 12.4
✨ Using the docker driver based on existing profile
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🔄 Restarting existing docker container for "minikube" ...
🐳 Preparing Kubernetes v1.23.3 on Docker 20.10.12 ...
▪ kubelet.housekeeping-interval=5m
🔎 Verifying Kubernetes components...
▪ Using image kubernetesui/dashboard:v2.3.1
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
▪ Using image kubernetesui/metrics-scraper:v1.0.7
🌟 Enabled addons: storage-provisioner, default-storageclass, dashboard
🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
如果你不确定 minikube Kubernetes 的状态,你也可以使用 minikube status 命令。确保 host、kubelet 和 apiserver 处于 Running 状态:
$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured
之前,我们在 kcna Kubernetes 命名空间中创建了一个运行 Nginx Web 服务器的简单 pod。现在,让我们创建一个相同的 nginx Web 服务器的 Deployment,但带有三个副本(即三个 Pods):
$ minikube kubectl -- create -f https://k8s.io/examples/controllers/nginx-deployment.yaml --namespace kcna
deployment.apps/nginx-deployment created
完整的 Deployment 规范(可以在本书随附的 GitHub 仓库中的 nginx-deployment.yaml 文件中找到)如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
这在某种程度上类似于我们之前使用的 pod 规范,但有一些不同之处,例如以下几点:
-
kind: Deployment -
apps/v1表示 API 版本 -
在元数据中增加了一个
app: nginx标签 -
Pods 的数量由
replicas: 3定义。 -
有一个用于通过
app:nginx标签匹配 Pods 的选择器 -
它为模板添加了一个
app:nginx标签
spec: 字段下的 selector: 定义了这个 Deployment 如何找到它管理的 Pods。在这个示例中,它选择了带有 app: nginx 标签的 Pods。
template 块具有与我们在上一章中用于独立 pod 情境中的 containers: 规格相同的 image: 和 ports: 字段。此外,它还包含带有 app: nginx 标签的元数据,该标签将添加到此规范创建的每个 pod 中。再次强调,标签对于 Deployment 找到其 Pods 是必须的。
让我们检查一下在应用了 Deployment 规范后,kcna 命名空间发生了什么:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-9456bbbf9-cl95h 1/1 Running 0 10m
nginx-deployment-9456bbbf9-ghxb2 1/1 Running 0 10m
nginx-deployment-9456bbbf9-nvl7r 1/1 Running 0 10m
我们可以看到三个 nginx Pods,每个都有一个独特的名称。这些名称对你来说可能略有不同,因为字符串的第二部分是随机生成的。现在,让我们用 kubectl get replicasets 命令查询 kcna 命名空间中的 ReplicaSets:
$ minikube kubectl -- get replicasets -n kcna
NAME DESIRED CURRENT READY AGE
nginx-deployment-9456bbbf9 3 3 3 12m
好的,我们看到一个 ReplicaSet;然而,我们并没有定义它!是 nginx Deployment 自动创建了一个 ReplicaSet,以保持所需的 Pod 数量。所以,实际上是 Deployment 在 ReplicaSets 上工作;ReplicaSets 在 Pods 上工作;Pods 是在容器之上的包装层,如 图 6.1 所示。你还可以看到 ReplicaSet 获得了一个唯一的 ID,最终的 Pods 会在其名称中继承这个 ID。

图 6.1 – Deployment、ReplicaSet 和 Pod 的层次结构
让我们做一个快速实验,删除我们 Deployment 创建的三个 nginx Pods 中的一个,看看会发生什么(你需要指定一个 Pod 的名称,因为 Pod 名称 是唯一的):
$ minikube kubectl -- delete pods nginx-deployment-9456bbbf9-cl95h -n kcna
pod "nginx-deployment-9456bbbf9-cl95h" deleted
现在,即使你打字非常快,你也可能不会注意到已删除的 Pod 已经终止并且一个新的 Pod 被创建了。接下来,获取 kcna 命名空间中 Pods 的列表:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-9456bbbf9-9zv5c 1/1 Running 0 3s
nginx-deployment-9456bbbf9-ghxb2 1/1 Running 0 42m
nginx-deployment-9456bbbf9-nvl7r 1/1 Running 0 42m
然后,我们有一个新的 Pod,其 AGE 为 3 秒,状态为 Running,而旧的已删除的 Pod(nginx-deployment-9456bbbf9-cl95h)已经完全消失。这就是我们一直谈论的Kubernetes 自愈魔法!仅仅几秒钟内,Kubernetes 就检测到 Nginx 部署的当前状态发生了变化,因为只有两个副本(Pods)在运行,而期望的状态是三个副本。Kubernetes 的协调循环启动了,并创建了第三个 nginx Pod 副本。
自愈是非常棒的,它帮助我们保持应用在某些情况下正常运行,例如节点硬件故障(当然,前提是你在生产环境中运行多个 Kubernetes 节点);当应用在某些请求类型下崩溃时;以及在计划内或计划外的维护期间,当我们需要将负载迁移到另一个节点时。
但这仅仅是开始。让我们假设我们预计应用会收到大量请求,因此我们需要提前做好准备,并在应用中增加额外的副本。在 Kubernetes 中,只需执行一个 kubectl scale deployment 命令就可以做到这一点:
$ minikube kubectl -- scale deployment nginx-deployment --replicas 5 -n kcna
deployment.apps/nginx-deployment scaled
如果你足够快地检查相关的 ReplicaSet 状态,你可能会看到新 Pods 正在生成:
$ minikube kubectl -- get replicaset -n kcna
NAME DESIRED CURRENT READY AGE
nginx-deployment-9456bbbf9 5 5 4 52m
就这样!不久之后,两个新 Pods 就已经启动并运行:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-9456bbbf9-9zv5c 1/1 Running 0 30m
nginx-deployment-9456bbbf9-ghxb2 1/1 Running 0 52m
nginx-deployment-9456bbbf9-hgjnq 1/1 Running 0 23s
nginx-deployment-9456bbbf9-nvl7r 1/1 Running 0 52m
nginx-deployment-9456bbbf9-pzm8q 1/1 Running 0 23s
注意
显然,在单节点 K8s 集群中添加更多的应用副本并不会对性能或服务可用性带来太多实用性。在生产环境中,你应该始终运行多节点的 Kubernetes 集群,并将应用的副本分布在多个节点上。我们在这里进行这些练习是基于单节点 Kubernetes 实例,仅用于演示和教学目的。
接下来,让我们看看如何使用 Deployments 执行滚动更新。滚动更新非常重要,因为它有助于确保快速的软件开发周期和频繁的发布,并且使我们能够在没有零停机时间的情况下进行更新,避免对客户造成影响。
零停机时间
零停机时间是一种部署方法,在这种方法下,更新后的应用能够正常处理请求,不会中断或出错。
通过滚动更新,我们可以执行以下操作:
-
将应用变更从一个环境推广到另一个环境(例如,新的镜像版本、配置或标签)
-
在出现任何问题时回滚到先前的版本
-
定义每次可以替换多少个应用副本
让我们通过 Nginx 部署来看看这一切是如何运作的。我们将使用 kubectl 更新 nginx 容器镜像版本标签为 1.20。首先,检查我们的部署是否完好:
$ minikube kubectl -- get deployment -n kcna
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 5/5 5 5 9h
现在,修改镜像为 nginx:1.20:
$ minikube kubectl -- set image deployment/nginx-deployment nginx=nginx:1.20 -n kcna
deployment.apps/nginx-deployment image updated
然后观察更改镜像后 Nginx Pods 的状态变化(你必须快速观察 这个过程!):
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-7b96fbf5d8-dwskw 0/1 ContainerCreating 0 2s
nginx-deployment-7b96fbf5d8-grkv6 0/1 ContainerCreating 0 2s
nginx-deployment-7b96fbf5d8-jcb4p 0/1 ContainerCreating 0 2s
nginx-deployment-9456bbbf9-9zv5c 1/1 Running 0 6h
nginx-deployment-9456bbbf9-ghxb2 1/1 Running 0 9h
nginx-deployment-9456bbbf9-hgjnq 1/1 Running 0 2h
nginx-deployment-9456bbbf9-nvl7r 1/1 Running 0 9h
nginx-deployment-9456bbbf9-pzm8q 1/1 Terminating 0 2h
从我们五个 Nginx 部署的副本中,我们看到其中一个处于Terminating状态,四个处于Running状态,另外三个新副本处于ContainerCreating状态。稍过片刻,我们可能会看到最后几个使用旧 Nginx 镜像的 Pods 进入Terminating状态,四个新副本进入Running状态,还有一个进入ContainerCreating状态:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-7b96fbf5d8-6dh9q 0/1 ContainerCreating 0 2s
nginx-deployment-7b96fbf5d8-dwskw 1/1 Running 0 25s
nginx-deployment-7b96fbf5d8-grkv6 1/1 Running 0 25s
nginx-deployment-7b96fbf5d8-jcb4p 1/1 Running 0 25s
nginx-deployment-7b96fbf5d8-zt7bj 1/1 Running 0 4s
nginx-deployment-9456bbbf9-ghxb2 1/1 Terminating 0 9h
nginx-deployment-9456bbbf9-nvl7r 1/1 Terminating 0 9h
不久之后,所有旧的 Pods 都会消失,最后一个新的 Pod 会进入Running状态。我们还可以通过对任何新 Pod 执行kubectl describe pod命令来验证是否使用了新镜像(在 Windows 上使用findstr代替grep命令):
$ minikube kubectl -- describe pod nginx-deployment-7b96fbf5d8-dwskw -n kcna | grep Image
Image: nginx:1.20
现在,如果部署的应用的新镜像不正确,或者它有可能导致 Pods 崩溃的 bug,该怎么办呢?就像更新 Kubernetes 部署一样,我们可以回滚到之前的部署版本。Kubernetes 会追踪每个更改,并为每个版本分配一个修订版本,我们可以使用kubectl rollout history命令查看:
$ minikube kubectl -- rollout history deployment -n kcna
deployment.apps/nginx-deployment
REVISION CHANGE-CAUSE
1 <none>
2 <none>
注意
CHANGE-CAUSE 是一个可选描述,可以通过向部署添加注释来设置。例如,我们可以执行以下命令:kubectl -n kcna annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.20"。
如果我们意识到需要将部署回滚到以前的修订版本,只需调用kubectl rollout undo,并可以选择指定确切的、可能是较旧的部署修订版本。我们可以尝试回滚到nginx部署的前一个修订版本(当前修订版本为2):
$ minikube kubectl -- rollout undo deployment/nginx-deployment -n kcna
deployment.apps/nginx-deployment rolled back
片刻之后,所有 Pods 都会按照滚动更新的方式重新创建。我们可以使用kubectl get pods命令,并加上额外的-o yaml选项来验证镜像版本标签是否已回到1.14.2(命名可能与你的情况不同,从你的 输出列表中选择任何一个 pod):
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-9456bbbf9-6xpq2 1/1 Running 0 22s
nginx-deployment-9456bbbf9-75m7d 1/1 Running 0 22s
nginx-deployment-9456bbbf9-hbglw 1/1 Running 0 22s
nginx-deployment-9456bbbf9-hxdjd 1/1 Running 0 16s
nginx-deployment-9456bbbf9-mtxzm 1/1 Running 0 17s
$ minikube kubectl -- get pod nginx-deployment-9456bbbf9-6xpq2 -n kcna -o yaml
apiVersion: v1
kind: Pod
… LONG OUTPUT OMITTED …
spec:
containers:
- image: nginx:1.14.2
imagePullPolicy: IfNotPresent
name: nginx
… LONG OUTPUT OMITTED …
你会看到非常长的输出,包含该 Pod 的所有详细信息。你也可以结合kubectl get和-o yaml选项查询其他 Kubernetes 资源(如namespaces、Deployments等,我们即将学习的)以获取关于该对象的完整信息。在此阶段,你不需要理解输出的每一行,但了解imagePullPolicy非常有用,它定义了如何从注册表中拉取容器镜像的规则。该策略可以是以下之一:
-
IfNotPresent– 这是默认设置。当请求的name:tag组合在 Pod 被调度到的节点上尚未存在(缓存)时,镜像才会被下载。 -
Always- 这意味着每次启动包含相应容器的 Pod 时,镜像注册中心会请求一个镜像摘要(从镜像标签解析得到)。如果该镜像的确切摘要已经在节点本地缓存,它将被使用;否则,Kubernetes 的 kubelet 将从目标节点的注册中心拉取该镜像。 -
Never- 这意味着 kubelet 不会尝试从注册中心拉取镜像。镜像应该提前以某种方式传送到节点,否则容器将无法启动。
此外,我们可以通过一些可选的设置和超时来控制滚动更新过程。最重要的两个设置如下:
-
maxUnavailable- 这定义了滚动更新过程中不可用的最大 Pod 数。可以用百分比(例如,25%)或绝对数字(例如,3)来指定。 -
maxSurge- 这定义了可以创建的 Pods 数量,超过了所需副本数。它也可以以百分比或绝对数值指定。例如,如果设置为25%,那么旧和新 Pods 的总数将不超过所需副本数的125%。
最后,如果我们不想进行滚动更新,我们可以选择Recreate策略,这意味着所有现有的 Pods 会一次性被销毁,只有在所有旧 Pods 被终止之后,新的 Pods 才会被创建。显然,这种策略无法实现零停机更新,因为应用的所有 Pods 至少会停机几秒钟。可以通过在相应部署的 YAML 规格文件中定义.spec.strategy.type设置来配置该策略。
现在我们了解了部署的相关内容,接下来让我们讲解一下sshd,它是一个允许我们通过安全****外壳协议远程登录系统的服务。
DaemonSet
DaemonSet 是一个 Pods 的封装器,它确保 Kubernetes 集群中的所有或某些节点每个运行目标 Pod 的单个副本。如果集群中增加了更多节点,DaemonSet 会确保在新节点加入集群后,自动在该节点上生成一个 Pod。
在 Kubernetes 中,Deployment 被认为是适用于各种用户工作负载的通用资源,而 DaemonSet 的典型用例如下:
-
在每个 Kubernetes 节点上运行日志收集服务(例如,Fluent Bit等软件)
-
在每个节点上运行节点监控守护进程(例如,Prometheus的节点出口程序)
-
在每个节点上运行集群存储守护进程
类似于 ReplicaSet,DaemonSet 会确保达到期望的状态,这意味着在 Pod 发生故障的情况下,它会自动重新创建一个新的 Pod。默认情况下,DaemonSet 会在集群中的所有工作节点上创建 Pod,但也可以选择集群中的特定节点或控制平面节点(如何做将在下一章的第七章中讨论)。DaemonSet 无法做的是设置每个节点的副本数量,因为 DaemonSet 每个节点只会运行一个 Pod。DaemonSet 的 spec 文件与 Deployment 的 spec 文件非常相似,只有一些差异,例如 kind: DaemonSet 或缺少 replicas: 设置。
接下来,我们不会现在创建一个 DaemonSet,因为一个合适的演示需要一个多节点的 Kubernetes 集群。如果你有兴趣,可以查看章节末尾的进一步阅读部分,自己动手尝试一下。在接下来的部分,我们将展示如何使用 Kubernetes 运行需要在磁盘上持久化信息的应用程序。
运行有状态的工作负载
到目前为止,我们在 Kubernetes 中尝试的所有操作并没有回答一个重要问题——如果我们需要在 Pod 重启之间保持应用程序的状态该怎么办?默认情况下,写入容器文件系统中的数据是不会持久化的。如果你只是将最近 Nginx 示例中的部署 spec 拷贝过来,并将镜像替换为 PostgreSQL,这还不够。技术上讲,你的 PostgreSQL Pod 会启动,数据库会运行,但写入该数据库实例的任何数据都不会在 Pod 重启后保留。不过,当然,Kubernetes 也为有状态的应用程序提供了解决方案。
如你可能还记得,来自第四章的内容,《探索容器运行时、接口和服务网格》一章中,Kubernetes 提供了一个 容器存储接口(CSI),它允许你将各种存储解决方案集成到 K8s 集群中。为了通过 Kubernetes API 为 Pods 增加外部存储,我们需要可以动态分配的 卷。让我们从两个新的资源定义开始:
-
持久卷 (PV):这是集群中的一块存储,可以通过动态(由 K8s 在请求时自动分配)或静态(例如,由集群管理员以某种方式预配置并在 K8s 中暴露)方式进行配置。
-
持久卷声明 (PVC):这是用户对存储的请求,消耗 PVs。
当我们希望为容器化的应用程序使用持久存储时,我们需要定义一个 PVC 的 spec,格式为 YAML,类似以下内容:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: kcna-pv-claim
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
然后,可以在部署和 Pod 中将此 PVC 作为一个卷进行引用。声明允许你请求特定的大小(如前面例子中的 3Gi)和以下四种 accessModes 中的一种:
-
ReadWriteOnce– 这允许卷被单个节点以读写模式挂载。此模式可以允许此节点上的多个 Pod 访问该卷。 -
ReadOnlyMany– 这允许卷被一个或多个节点以只读方式挂载。 -
ReadWriteMany– 这允许卷被多个节点以读写模式挂载。这应由存储解决方案和协议(例如,NFS)支持。 -
ReadWriteOncePod– 这与ReadWriteOnce相同,但有一个严格的限制:整个集群中只有一个 Pod 能够写入该卷。
由于 PVs 是 Kubernetes 集群中的实际存储资源,我们可能会遇到没有适合的 PV 来满足 PVC 请求的情况。在这种情况下,Kubernetes 可以根据 PVC 规范中指定的存储类动态地提供 PV(在前面的示例中是storageClassName: standard)。
存储类
存储类提供了一种分类集群中不同存储选项的方法。这些存储类可能在性能、支持的访问模式和协议、备份策略等方面有所不同。
还可以通过在 PVC 规范中设置storageClassName: ""(空字符串),指示 Kubernetes 仅使用已经提供(可能是静态提供)的可用 PV。在动态 PV 提供的情况下,卷的大小将始终与 PVC 规范中请求的大小一致。然而,如果我们要求仅使用已存在的 PV,我们可能会得到一个比 PVC 资源请求中指定的更大的卷(例如,虽然请求了3Gi,但如果集群中最接近的可用 PV 是5Gi,则会使用该5Gi卷,并且该容器可以使用全部5Gi)。
让我们回到 minikube 设置,看看这个如何发挥作用。首先,使用先前的规范创建kcna-pv-claim(文件可以从本书的 GitHub 仓库下载):
$ minikube kubectl -- create -f kcna-pv-claim.yaml -n kcna
persistentvolumeclaim/kcna-pv-claim created
现在,获取集群中所有 PV 的列表(在这种情况下,名称将是唯一的):
$ minikube kubectl -- get pv -n kcna
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-6b56c062-a36b-4bd5-9d92-f344d02aaf5c 3Gi RWO Delete Bound kcna/kcna-pv-claim standard 74s
PV 已被 Kubernetes 自动分配,速度非常快!此时,我们可以开始在我们的部署或 Pod 规范中使用kcna-pv-claim作为卷。让我们删除在本章开头创建的旧nginx-deployment:
$ minikube kubectl -- get deployment -n kcna
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 5/5 5 5 2d
$ minikube kubectl -- delete deployment nginx-deployment -n kcna
deployment.apps "nginx-deployment" deleted
并创建另一个新 PV,并附加我们的新卷。为此,我们需要对原来的nginx-deployment.yaml规范文件进行一些更改(修改后的版本可以在 GitHub 上找到):
$ cat nginx-deployment-with-volume.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-with-volume
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
volumeMounts:
- name: kcna-volume
mountPath: "/usr/share/nginx/html"
volumes:
- name: kcna-volume
persistentVolumeClaim:
claimName: kcna-pv-claim
除了为部署指定一个新名称(nginx-deployment-with-volume)并将副本数设置为1外,其他更改如下:
-
我们在相应的
nginx容器下添加了volumeMounts:块,说明哪个卷(kcna-volume)应挂载到哪个路径("/usr/share/nginx/html"– 这是用于静态 HTML 内容的位置)。 -
此外,我们还定义了
volumes:块,将kcna-volume映射到我们在前一步创建的名为kcna-pv-claim的 PVC。
注意
volumeMounts 位于每个容器的部分,因为同一个 Pod 中的不同容器可以挂载不同的(或相同的)卷。volumes 块与 containers 在同一层级,它应该列出所有将在 Pod 中使用的卷。
现在,让我们创建一个修改过的 nginx 部署,看看会发生什么:
$ minikube kubectl -- create -f nginx-deployment-with-volume.yaml -n kcna
deployment.apps/nginx-deployment-with-volume created
$ minikube kubectl -- get pod -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-with-volume-6775557df5-bjmr6 1/1 Running 0 39s
目前看起来一切都没有变化,但我们可以使用 kubectl exec -it 命令,通过启动一个新的 shell 进程进入容器。你可能记得我们在第三章中做过类似的事情,当时我们使用了 docker run -it。你需要在这里指定你唯一的 Pod 名称:
$ minikube kubectl -- -n kcna exec -it nginx-deployment-with-volume-6775557df5-bjmr6 -- bash
root@nginx-deployment-with-volume-6775557df5-bjmr6:/#
让我们检查一下我们要求的 /usr/share/nginx/html 路径下是否有挂载卷:
root@nginx-deployment-with-volume-6775557df5-bjmr6:/# mount | grep nginx
/dev/vda1 on /usr/share/nginx/html type ext4 (rw,relatime)
就是这样!我们动态分配的 PV 被自动挂载到我们的 Pod 所在的节点上。如果 Pod 死亡,卷上的数据将被保留,如果新的 Pod 在另一个节点上启动,Kubernetes 会处理卷的卸载和重新挂载到正确的节点,达到我们在规格文件中描述的期望状态。为了确保数据确实被持久化,我们可以在容器内做个小练习。让我们安装 curl 工具并尝试对 localhost 进行测试:
root@nginx-deployment-with-volume-6775557df5-bjmr6:/# apt update
… LONG OUTPUT OMITTED …
root@nginx-deployment-with-volume-6775557df5-bjmr6:/# apt -y install curl
Reading package lists... Done
Building dependency tree
Reading state information... Done
… LONG OUTPUT OMITTED …
root@nginx-deployment-with-volume-6775557df5-bjmr6:/ # curl localhost
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.14.2</center>
</body>
</html>
接下来,让我们在 /usr/share/nginx/html 路径下创建一个简单的一行 index.html 文件,再次尝试运行 curl:
root@nginx-deployment-with-volume-6775557df5-bjmr6:/# echo "Kubernetes Rocks!" > /usr/share/nginx/html/index.html
root@nginx-deployment-with-volume-6775557df5-bjmr6:/# curl localhost
Kubernetes Rocks!
本练习的最后一步由你来完成。退出容器(可以通过输入 exit 命令或按 Ctrl + D)并使用 kubectl delete pods 命令删除 Pod,待新 Pod 启动后再登录进去。检查我们创建的 index.html 文件是否仍然存在于挂载点,并且文件中是否包含正确的 Kubernetes Rocks! 字符串。
尽管在 Kubernetes 部署中使用 PV 是常规做法,但另一个工作负载资源专门用于管理有状态的应用。
StatefulSet
StatefulSet 是一个用于管理 Pod 部署和扩展的资源,保证这些 Pod 的顺序性和唯一性。
这意味着,由 StatefulSet 创建的 Pods 具有稳定的命名(没有随机生成的 UUID),并且允许按顺序、优雅地部署,以及按顺序滚动更新。除此之外,StatefulSet 还可以为每个 Pod 副本分配一个 PV。这意味着每当你想通过添加新副本来扩展应用时,不需要为每个副本定义并应用新的 PVC。让我们快速看一下 StatefulSet 的一个示例规格:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
volumeMounts:
- name: nginx-html
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: nginx-html
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 1Gi
如你所见,PVC 规格本质上是位于 volumeClaimTemplates 块下的 StatefulSet 规格的一部分。你可以自由地应用这个 StatefulSet 规格并观察发生了什么。你应该会看到三个新的 PVC 和三个新的 Pod 自动创建,并且 PV 会被自动分配并附加。
尽管这看起来一开始可能比较复杂,但想想看,如果没有 Kubernetes,你需要做多少手动步骤才能达到相同的结果。你需要花多长时间来创建多个卷、下载容器镜像、配置并启动容器?Kubernetes 使得许多操作任务变得非常简单,在接下来的部分中,我们将进一步了解 Kubernetes 如何让你配置运行在容器中的应用,以及服务发现如何工作。
应用配置与服务发现
到目前为止,我们已经探索了 Kubernetes 的许多功能和资源,但如何进行应用配置呢?我们可以在构建过程中将配置文件或环境变量添加到容器镜像中,但这是错误的。如果这样做,即使是最小的配置更改,你也必须重新构建容器镜像。而且,当需要为不同环境设置不同配置时,你还需要维护多个相同应用的镜像。这样会导致混乱、复杂和容易出错,因此不要这样做。
在 Kubernetes 中,更好的做法是使用ConfigMaps和Secrets。
ConfigMap
ConfigMap 是一个用于存储非机密数据和配置设置的资源,以键值对的形式,可以在 Pods 内部作为环境变量、命令行参数或配置文件使用。ConfigMap 不提供保密性或加密功能,因此不适合用于存储机密信息,例如密码或访问令牌。
Secret
Secret 是一个用于存储敏感数据的资源,如密码、令牌和访问密钥。与 ConfigMap 类似,Secret 也可以作为环境变量或配置文件在 Pods 内部使用。
ConfigMap 和 Secrets 都允许我们将配置与容器镜像解耦,从而实现更好的应用可移植性,并使同一个容器镜像可以在不同环境中重用。
让我们通过一个简单的例子来探索。假设你正在开发一个需要访问数据库的 web 应用。该应用的设计是查找 DATABASE_HOST、DATABASE_USERNAME 和 DATABASE_PASSWORD 环境变量。在这种情况下,你可以使用 ConfigMap 来设置 DATABASE_HOST,并使用 Secret 来保存用户名和密码的信息。这些配置会在容器内与应用一起使用,并允许我们在不同环境中使用不同的设置(例如,在开发、测试和生产环境中使用不同的数据库和密码)。
除了将 ConfigMap 和 Secrets 映射到容器内的环境变量外,我们还可以像挂载常规文件一样将它们挂载到容器内。这是通过我们在前一节学习 PV 和 PVC 时提到的卷概念来实现的。
让我们回到键盘,使用 kubectl create secret 命令来创建一个简单的 Secret:
$ minikube kubectl -- create secret generic kcna-secret --from-literal="username=kcnauser" --from-literal="password=topsecret" -n kcna
secret/kcna-secret created
注意
不用说,也可以通过定义一个带有kind: Secret的 YAML 规范文件,并像我们之前对待其他资源一样,使用kubectl create -f来创建 Secrets。
接下来,找到我们在上一节中使用的nginx-statefulset规范文件,并将其修改为将新创建的kcna-secret作为额外的卷挂载到/etc/nginx/kcna.secret。请尝试自行完成,但如果遇到任何困难,以下是规范文件中相关的修改(完整的修改版规范文件也可以在 GitHub 上找到):
… BEGINNING OF THE SPEC OMITTED …
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
volumeMounts:
- name: nginx-html
mountPath: /usr/share/nginx/html
- name: nginx-kcna-secret
mountPath: /etc/nginx/kcna/
volumes:
- name: nginx-kcna-secret
secret:
secretName: kcna-secret
volumeClaimTemplates:
- metadata:
name: nginx-html
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 1Gi
注意
在 Kubernetes 中,除了删除并重新创建资源外,还可以修改已创建的资源。然而,某些字段和资源是不可变的,不能实时修改。
现在,让我们使用kubectl apply -f命令应用修改后的规范文件(规范文件名为statefulset_with_secret.yaml,如下所示):
$ minikube kubectl -- apply -f statefulset_with_secret.yaml -n kcna
statefulset.apps/nginx-statefulset configured
因为我们添加了一个新的卷,所以 Pod 将会在之后直接重新创建:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-statefulset-0 1/1 Running 0 12s
nginx-statefulset-1 1/1 Running 0 15s
nginx-statefulset-2 1/1 Running 0 18s
让我们执行进入其中一个 pod,看看我们的 Secret 是否已正确挂载:
$ minikube kubectl -- -n kcna exec -it nginx-statefulset-0 -- bash
root@nginx-statefulset-0:/# cat /etc/nginx/kcna/username
kcnauser
root@nginx-statefulset-0:/# cat /etc/nginx/kcna/password
topsecret
好了,Secret 已经挂载到我们的 nginx 容器中。值得一提的是,Kubernetes 使得各种组合变得可能:Secrets(以及单独的密钥)可以作为环境变量使用;Secrets 可以从现有文件创建;Secrets 可以用来存储和挂载 SSL 证书或 SSH 密钥;K8s Secrets 中的单独密钥可以挂载到不同的路径等等。
ConfigMap 和 Secrets 在功能上非常相似,但它们的用途是存储通用配置。例如,我们可以创建一个新的ConfigMap,并将 nginx 配置挂载到容器中的/etc/nginx/nginx.conf,从而覆盖默认的配置文件。
在 KCNA 考试的范围内,你不需要了解所有细节,但随着你开始使用 Kubernetes,你会遇到需要进行服务发现的情况,因此,如果你有时间,随时可以查看章节末尾的进一步阅读部分中的链接。
接下来,我们将讨论 Kubernetes 中的服务发现。
服务发现
服务发现提供了设备以及这些设备所提供的服务在网络上的自动检测功能。
正如你可能记得的那样,在微服务架构中,我们有许多小服务需要通过网络相互通信。这意味着服务发现起着至关重要的作用,因为它帮助服务找到它们的对应服务,例如,一个后端服务需要发现它要连接的数据库。幸运的是,Kubernetes 也通过基于域名系统(DNS)的服务发现机制解决了这个问题。
Kubernetes 实现了一个内部 DNS 系统,跟踪应用程序及其名称和相应的 Pod IP(每个 Pod 启动时都会获得一个唯一的集群范围的 IP 地址)。这使得不同的应用可以轻松找到彼此的端点,通过解析应用名称到 Pod IP。此时,Kubernetes 服务资源就派上用场了。
服务
服务是一种抽象层,能够实现依赖 Pod 之间的松耦合。它是一个资源,允许你在集群内部发布应用名称,并将应用暴露出来,使其可以从集群外部访问。
Kubernetes Pod 的生命周期相对较短。如果我们添加新的卷、更新部署镜像,或者节点出现故障,在所有这些情况下,Pod 会使用新名称和新 IP 地址重新创建。这意味着我们不能依赖 Pod 名称,而应该使用服务,它将通过匹配 Kubernetes 标签和 选择器来定向一个或多个 Pod。
标签和选择器
标签是可以附加到任何 Kubernetes 对象的简单键/值元数据对,可以在创建期间或创建后添加。标签可以包含应用程序名称、版本标签或任何其他对象分类。
选择器允许识别一组 Kubernetes 对象。例如,可以使用标签选择器来查找具有相同 app 标签的对象组,如 图 6.2 所示。

图 6.2 – Kubernetes 中的服务抽象
图 6.2 演示了一个服务如何选择所有带有 app: nginx 标签的 Pod。这些 Pod 可以是由 Deployment 创建的,也可以是任何其他带有该标签的 Pod。你可以通过在 kubectl get 命令中添加 --show-labels 参数来列出对象的标签,例如:
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-with-volume-6775557df5-f6ll7 1/1 Running 0 23h app=nginx,pod-template-hash=6775557df5
nginx-statefulset-0 1/1 Running 0 46m app=nginx,controller-revision-hash=nginx-statefulset-6fbdf55d78,statefulset.kubernetes.io/pod-name=nginx-statefulset-0
nginx-statefulset-1 1/1 Running 0 46m app=nginx,controller-revision-hash=nginx-statefulset-6fbdf55d78,statefulset.kubernetes.io/pod-name=nginx-statefulset-1
nginx-statefulset-2 1/1 Running 0 46m app=nginx,controller-revision-hash=nginx-statefulset-6fbdf55d78,statefulset .kubernetes.io/pod-name=nginx-statefulset-2
看,我们的 nginx 部署 Pod 以及 nginx-statefulset 中的 Pod 都有相同的 app=nginx 标签,因为 Deployment 和 StatefulSet 都在其规范模板中定义了这个标签:
template:
metadata:
labels:
app: nginx
现在,让我们创建一个服务,目标是所有带有该标签的 Pod。以下是一个简单的规范示例,目标是选定的 Pod 的端口80:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
接下来,创建服务:
$ minikube kubectl -- create -f nginx-service.yaml -n kcna
service/nginx created
$ minikube kubectl -- get service -n kcna
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 10.105.246.191 <none> 80/TCP 36s
创建后,你应该能够看到服务背后的端点,它们实际上是带有 app=nginx 标签的运行中 Pod 的 IP 地址。列出端点可以通过执行 kubectl get endpoints 命令来完成,如下所示:
$ minikube kubectl -- get endpoints -n kcna
NAME ENDPOINTS AGE
nginx 172.17.0.2:80,172.17.0.6:80,172.17.0.7:80 + 1 more... 4m
如果我们现在再次进入其中一个 Pod,运行curl nginx(我们创建的服务名称),应该会得到响应。在容器中安装 curl 后,执行几次(5-10 次):
$ minikube kubectl -- -n kcna exec -it nginx-statefulset-0 -- bash
root@nginx-statefulset-0:/# apt update && apt -y install curl
… LONG OUTPUT OMITTED …
root@nginx-statefulset-0:/# curl nginx
Kubernetes Rocks!
root@nginx-statefulset-0:/# curl nginx
Kubernetes Rocks!
root@nginx-statefulset-0:/# curl nginx
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.14.2</center>
</body>
</html>
然后我们得到不同的响应!我们当前运行的四个 Pod 中,有一个 Pod 包含我们在本章早些时候创建的自定义index.html文件,而其他三个没有。
发生的情况是,我们创建的服务在所有可用的nginx Pod IP 之间进行负载均衡。如果我们扩展副本数量,或者进行相反的操作,服务还会自动更新端点列表。
现在,让我们看看存在哪些服务类型,以及它们允许你完成什么任务:
-
ClusterIP: 这种类型将在内部集群 IP 上暴露应用程序。只有在同一集群中运行的 Pod 才能访问此类服务。这是默认类型,除非在规范中被覆盖。
-
NodePort: 这种类型将应用程序暴露在集群中每个节点的相同静态端口上。用户可以通过请求任何节点的 IP 和配置的端口,从集群外部访问该应用程序。
-
LoadBalancer: 这种类型通过云服务提供商的负载均衡器将应用程序暴露到集群外部。
-
mybestservice.app.com通过返回ExternalName并不像其他服务类型那样充当应用程序请求的代理。
这意味着,实际上,当你需要将 Kubernetes 中运行的应用程序暴露到集群外部时,通常会使用LoadBalancer类型(假设你的云服务提供商或本地基础设施提供负载均衡器)。而在集群内需要相互通信的多个应用程序之间,你将使用默认的ClusterIP类型。例如,当你的后端部署需要与作为 StatefulSet 运行的数据库通信,而该数据库不应暴露到互联网时。
接下来是本章的最后一部分。在进行所有练习时,你可能会想,当 Pod 正在运行时,Kubernetes 是如何知道应用程序实际上正在运行的。如果应用程序在响应请求之前需要时间怎么办?我们怎么知道应用程序没有陷入死锁?让我们弄明白这一点!
确保应用程序处于存活和健康状态
默认情况下,Kubernetes 确保集群中应用程序的期望状态已达到。当进程退出或节点失败时,它会重启并重新创建失败的容器。然而,仅仅这样可能不足以判断 Pod 内部运行的应用程序是否健康。为了确保工作负载处于存活和健康状态,Kubernetes 实现了探针的概念。
探针
探针是由 Kubernetes kubelet 在容器上执行的诊断。诊断可以是执行容器内任意命令或 TCP 探测,或是 HTTP 请求。
Kubernetes 提供了三种类型的探针,如下所示:
-
Liveness: 确保容器中的进程存活,如果没有,则重启容器。在应用程序发生死锁时,重启容器通常有助于使应用程序在出现故障时仍保持可用。
-
就绪性:确保应用程序已经准备好接收流量。当一个 Pod 中的所有容器都准备好并且所有的就绪性探针都成功时,该 Pod 被认为是就绪的。
-
启动探针:允许您知道容器中的应用程序何时启动。如果配置了启动探针,它将在启动探针成功之前禁用活跃性和就绪性探针。这对于启动较慢的应用程序可能是必要的,以避免在应用程序启动之前由于活跃性探针失败而导致它们被终止。
所有这些探针的目的是提高容器化应用程序的可用性,但它们覆盖了不同的场景。例如,如果探针失败,活跃性探针会导致容器重新启动。长时间运行的复杂应用程序最终可能过渡到故障状态,这时 Kubernetes 的活跃性探针就发挥了作用。
注意
当单个容器的活跃性探针失败时,不会重新创建整个 Pod。只有 Pod 中的特定容器会被重新启动。这与容器中的应用程序退出并且 Pod 被如 Deployment、ReplicaSet 或 StatefulSet 等控制器重新创建的情况不同。
当容器中的应用程序无法处理流量时,需要就绪性探针。一些应用程序可能需要很长时间才能启动,因为它们需要加载大量数据集到内存中,或者需要执行一些耗时的初始化配置。应用程序还可能依赖外部服务。在所有这些情况下,我们不希望终止并重新启动容器;而是希望避免向它发送任何流量。
就绪性探针有助于确定 Service 后面的哪些 Pods 已经准备好接收连接并处理流量。如果容器未通过就绪性探针,它的 Pod IP 会被自动从 Service 的端点列表中移除。这有助于防止将用户请求路由到一个尚未正常工作的应用程序副本。
注意
如果同时定义了活跃性和就绪性探针,前者不会等待后者成功。可以为探针设置初始延迟(通过initialDelaySeconds设置),或者使用startupProbe临时禁用活跃性和就绪性检查。
每个探针可以执行自定义命令、执行 HTTP 请求或进行 TCP 探测。除此之外,活跃性和就绪性探针可以通过多个参数进行调整:检查执行的频率(可通过periodSeconds配置)、等待探针完成的时间(可通过timeoutSeconds配置)或探针在放弃之前应该重试的次数阈值,具体取决于探针的类型。
现在,让我们来检查以下定义了简单活跃性探针的 Pod:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: k8s.gcr.io/busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
当 pod 容器启动时,它会在 /tmp/healthy 路径下创建一个空文件,等待 30 秒,然后删除该文件。之后,容器将再等待 600 秒后退出。健康检查探针会在初始检查延迟 5 秒后每 5 秒执行一次 cat /tmp/healthy 命令。
让我们创建规格并查看其实际效果:
$ minikube kubectl -- create -f https://k8s.io/examples/pods/probe/exec-liveness.yaml -n kcna
pod/liveness-exec created
起初,pod 运行正常,它的健康检查成功,重启计数器显示 0 次重启:
$ minikube kubectl -- get pod -n kcna
NAME READY STATUS RESTARTS AGE
liveness-exec 1/1 Running 0 59s
稍后,我们可以看到发生了重启:
$ minikube kubectl -- get pod -n kcna
NAME READY STATUS RESTARTS AGE
liveness-exec 1/1 Running 1 (20s ago) 95s
如果我们描述该 pod,可以看到事件的时间线:
$ minikube kubectl -- describe pod liveness-exec -n kcna
… LONG OUTPUT OMITTED …
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 85s default-scheduler Successfully assigned kcna/liveness-exec to minikube
Normal Pulled 81s kubelet Successfully pulled image "k8s.gcr.io/busybox" in 3.4078911s
Warning Unhealthy 41s (x3 over 51s) kubelet Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
Normal Killing 41s kubelet Container liveness failed liveness probe, will be restarted
Normal Pulling 11s (x2 over 85s) kubelet Pulling image "k8s.gcr.io/busybox"
Normal Created 10s (x2 over 81s) kubelet Created container liveness
Normal Pulled 10s kubelet Successfully pulled image "k8s.gcr.io/busybox" in 1.2501457s
Normal Started 9s (x2 over 81s) kubelet Started container liveness
我们现在接近这一长篇章的结束。到目前为止,你已经学到了许多关于 Kubernetes 特性以及一些其高级资源的内容。这里讲解的许多深入细节不要求通过 KCNA 考试,但它们对开始使用 Kubernetes 至关重要,如果你决定获得 CKA 或 CKAD 认证,它们无疑会帮助你。如果你没有完全掌握这一章的内容,这不会妨碍你通过 KCNA 考试,但尽量现在多了解一些。请查看进一步阅读部分的资源,必要时进行额外的研究。
总结
在这一章中,我们已经看到 Kubernetes 的自愈能力以及 K8s 的资源调和循环如何使其在极短时间内达到所需的资源状态。由于 Pods 本身没有任何从故障中恢复的机制,我们通常使用 Kubernetes 的Deployments来确保所请求数量的应用副本正在运行。Deployments 还允许我们执行可控的滚动更新、回滚和零停机部署,以支持需要频繁发布版本的快速软件开发周期。
DaemonSet 是一种资源,适用于我们需要在每个节点或特定节点集群中运行一个副本的场景。DaemonSets 通常用于在集群中运行日志记录或监控代理。
StatefulSet 是一种管理有状态工作负载的 Kubernetes 资源。它使我们能够轻松地将卷集成到 Pods 中,以保持容器重启后的持久数据,并自动化 PVs 的动态配置。
接下来,我们探索了为在 Kubernetes 中运行的应用提供配置和敏感信息的方法。ConfigMaps 适用于通用的非机密数据,而 Secrets 则用于存储密码、令牌等信息。ConfigMaps 和 Secrets 本质上都是可以挂载到容器内部特定文件路径的卷。
我们还学习了服务发现的重要性,它允许应用程序在 Kubernetes 集群内部找到并与彼此通信。Service 资源允许将应用程序及其 Pods 通过不同的 Service 类型(如 LoadBalancer,NodePort 或 ClusterIP)暴露到集群内部和外部。
最后但同样重要的是,我们已经探索了确保 Kubernetes 中运行的应用程序始终处于活动和健康状态的选项。Kubernetes 提供了三种类型的探针(liveness,readiness 和 startup),它们的作用是验证应用程序在启动时或定期在常规间隔时的状态。如果应用程序未通过 liveness 探针,它的容器会被重新启动;如果未通过 Readiness 探针,它将不会接收任何流量,且 Pod 的 IP 会从服务端点列表中排除。Startup 探针用于启动缓慢的应用程序,这些应用程序需要更多时间才能处理其他探针或实际流量。
注意
随时删除本章中创建的任何 Kubernetes 资源,除非你计划稍后再回来使用它们。
在接下来的章节中,我们将继续探索 Kubernetes 及其特性。我们将学习关于调度控制、资源请求以及调试在 Kubernetes 上运行的应用程序的方法。确保回答所有回顾问题,并查看 进一步阅读 部分,如果你想深入了解本章中的主题。
问题
在总结时,这里有一系列问题,帮助你测试自己对本章内容的理解。你可以在 评估 部分的 附录 中找到答案:
-
以下哪个 Kubernetes 资源可以在其运行的节点失败时帮助恢复应用程序(可选择多个)?
-
Pod
-
Service
-
StatefulSet
-
Deployment
-
-
以下哪个 Kubernetes 资源确保定义的副本数始终运行(可选择多个)?
-
Pod
-
ReplicaSet
-
Deployment
-
DaemonSet
-
-
以下哪个 Kubernetes 资源允许我们执行滚动更新和零停机部署?
-
Service
-
Deployment
-
ReplicaSet
-
DeploySet
-
-
以下哪项描述最准确地阐明了 Pods 与各类 Kubernetes 控制器(资源)之间的关系?
-
Pods 管理资源
-
Pods 由容器运行时管理
-
Pods 始终由一个 Kubernetes 控制器管理
-
Pods 可以由一个 Kubernetes 控制器管理
-
-
标签选择器的作用是什么?
-
它们帮助确定集群中每个 Pod 的用途
-
它们帮助区分更重要的 Pods 和不太重要的 Pods
-
它们仅仅是辅助元数据
-
它们允许我们通过标签对资源进行分组和选择
-
-
以下哪个镜像拉取策略将在镜像未被节点缓存时才从注册中心下载镜像?
-
IfNotCached -
IfNotPresent -
IfNotAvailable -
Always
-
-
服务如何确定哪些 Pods 已经准备好接收流量?
-
准备好的 Pods 会有
ready: true标签 -
只有由部署(Deployment)管理的 Pods 才能接受来自服务(Service)的流量
-
一个 Pod 的就绪探针必须成功
-
一个 Pod 的启动探针必须成功
-
-
哪种类型的探针会延迟其他探针的执行?
-
延迟
-
存活性
-
启动
-
就绪性
-
-
哪个规格设置控制由 Deployment 管理的 Pod 数量?
-
podnum
-
副本
-
容器
-
实例
-
-
哪个 Kubernetes 控制器最适合需要将数据保存到磁盘的应用程序?
-
Deployment -
DaemonSet -
ReplicaSet -
StatefulSet
-
-
以下哪个选项允许 Kubernetes 控制器检测与期望状态的偏差?
-
副本控制器
-
Kubelet
-
协调循环
-
存活性探针
-
-
哪种类型的服务允许在集群内暴露应用程序?
-
LoadBalancer -
ClusterIP -
InternalIP -
NodePort
-
-
在 Kubernetes 中,服务发现背后使用了什么技术?
-
Avahi
-
Iptables
-
NTP
-
DNS
-
-
以下哪些服务类型适合将应用程序暴露到 Kubernetes 集群外部(请选择多个)?
-
ClusterIP -
NodePort -
LoadBalancer -
ExternalIP
-
-
以下哪个资源适合存储并注入通用配置到容器中?
-
ConfigMap
-
Secret
-
SettingMap
-
PV
-
-
Kubernetes 中哪个对象表示实际的存储卷?
-
StatefulSet -
PVC
-
PV
-
SV
-
-
哪种资源适合表示容器中应用程序的敏感信息?
-
ConfigMap
-
Secret
-
卷
-
PVC
-
-
哪种探针失败时会重启容器?
-
存活性
-
就绪性
-
启动
-
存活性
-
进一步阅读
要了解更多关于本章涵盖的主题,请查看以下资源:
-
动态卷提供:
kubernetes.io/docs/concepts/storage/dynamic-provisioning/ -
管理 Kubernetes 密钥:
kubernetes.io/docs/tasks/configmap-secret/ -
创建和使用 ConfigMaps:
kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/ -
Kubernetes 探针配置:
kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
第七章:使用 Kubernetes 进行应用程序部署和调试
在本章中,我们将了解如何控制工作负载在 Kubernetes 上的部署,Kubernetes 调度器是如何工作的,以及当应用程序在 K8s 上运行时出现问题时,如何进行调试。本章同时涵盖了Kubernetes 和 Cloud Native Associate(KCNA)认证考试的 Kubernetes 基础 和 Cloud Native Observability 域的内容。
如前所述,我们将继续使用我们的 minikube Kubernetes 进行一些练习,所以保持你的设置准备好。我们将涵盖以下主题:
-
Kubernetes 中的调度
-
资源请求和限制
-
在 Kubernetes 中调试应用程序
让我们继续我们的 Kubernetes 之旅!
Kubernetes 中的调度
我们已经在 第五章 简要介绍了 Kubernetes 调度器(kube-scheduler)的工作原理。调度器是 K8s 控制平面的组件,决定 pod 会在哪个节点上运行。
调度
调度是将 Pods 分配给 Kubernetes 节点以供 kubelet 执行的过程。调度器会在无限循环中监控新创建的 Pods,查看哪些 Pods 尚未分配 节点,并为每个发现的 Pod 找到合适的节点来运行它。
默认的 kube-scheduler 调度器会分两阶段为 pod 选择节点:
-
过滤:第一阶段是调度器确定可以运行 pod 的节点集合。这包括检查节点是否具有足够的容量以及其他特定 pod 的需求。如果集群中没有适合的节点,这个列表可能为空,且在这种情况下,pod 会停留在未调度状态,直到需求或集群状态发生变化。
-
评分:第二阶段是调度器在第一阶段过滤的节点中进行排名,选择最合适的 pod 部署位置。列表中的每个节点都会被评分,最终选择得分最高的节点。
让我们通过一个实际的例子来说明。假设我们有一个应用程序需要具备特定硬件的节点。例如,你运行机器学习(ML)工作负载,可以利用 GPU 进行更快的处理,或者一个应用程序要求特定的 CPU 代际,而这个 CPU 可能并非集群中的每个节点都具备。在所有这种情况下,我们需要指示 Kubernetes 限制可用于 pod 的适合节点列表。方法有多种:
-
在 pod 规范中指定
nodeSelector字段,并给节点打标签 -
在 pod 规范中指定一个确切的
nodeName字段 -
使用亲和性和反亲和性规则
-
使用 pod 拓扑 分布约束
现在让我们回到 minikube 设置,通过使用 minikube node add 命令(此操作可能需要 一些时间)来扩展集群,添加一个新的节点:
$ minikube node add
😄 Adding node m02 to cluster minikube
❗ Cluster was created without any CNI, adding a node to it might cause broken networking.
👍 Starting worker node minikube-m02 in cluster minikube
🚜 Pulling base image ...
🔥 Creating docker container (CPUs=2, Memory=2200MB) ...
🐳 Preparing Kubernetes v1.23.3 on Docker 20.10.12 ...
🔎 Verifying Kubernetes components...
🏄 Successfully added m02 to minikube!
到此为止,我们应该已经拥有一个包含两个节点的 minikube 集群!让我们查看节点列表:
$ minikube kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane,master 22d v1.23.3
minikube-m02 Ready <none> 84s v1.23.3
注意
如果你的系统没有足够的资源来运行另一个 Kubernetes 节点,你也可以像之前一样仅使用一个节点。但是,在这种情况下,你需要调整本章中遇到的示例命令来标记第一个节点。
现在我们将创建之前的 nginx 部署的修改版,要求节点上附加purpose: web-server标签。相应的Deployment规范可能是这样的:
$ cat nginx-deployment-with-node-selector.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-with-node-selector
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
nodeSelector:
purpose: web-server
注意
如果你还没有删除上一章练习中的资源,可以通过分别在kcna命名空间中执行kubectl delete deployment、kubectl delete sts或kubectl delete service来删除它们。
接下来,创建前述的 nginx 部署规范:
$ minikube kubectl -- create -f nginx-deployment-with-node-selector.yaml -n kcna
deployment.apps/nginx-deployment-with-node-selector created
让我们检查发生了什么;例如,查询kcna命名空间中的 Pod:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-with-node-selector-7668d66698 -48q6b 0/1 Pending 0 1m
看,它开始了——创建的 Nginx Pod 卡在了Pending状态。让我们通过kubectl describe命令检查更多细节(你的 Pod 命名 可能不同):
$ minikube kubectl -- describe pods nginx-deployment-with-node-selector-7668d66698-48q6b -n kcna
… LONG OUTPUT OMITTED …
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 1m default-scheduler 0/2 nodes are available: 2 node(s) didn't match Pod's node affinity/selector.
信息很明确——我们已经请求了一个具有特定标签的节点来运行我们的 nginx 部署 Pod,但没有可用的具有该标签的节点。
我们可以通过添加--show-labels参数来检查节点上有哪些标签:
$ minikube kubectl -- get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
minikube Ready control-plane,master 22d v1.23.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=minikube,kubernetes.io/os=linux,minikube.k8s.io/commit=362d5fdc0a3dbee389b3d3f1034e8023e72bd3a7,minikube.k8s.io/name=minikube,minikube.k8s.io/primary=true,minikube.k8s.io/updated_at=2022_06_19T17_20_23_0700,minikube.k8s.io/version=v1.25.2,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
minikube-m02 Ready <none> 19m v1.23.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io /os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=minikube-m02,kubernetes.io/os=linux
默认标签包括有关节点角色、CPU 架构、操作系统等一些有用信息。现在我们为新添加的节点标记与 nginx 部署所需要的相同标签(节点名称在你的情况下可能类似,请 相应调整):
$ minikube kubectl -- label node minikube-m02 "purpose=web-server"
node/minikube-m02 labeled
过了一会儿,我们可以看到 nginx Pod 正在被创建:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
nginx-deployment-with-node-selector-7668d66698 -48q6b 0/1 ContainerCreating 0 22m
通过添加-o wide选项,我们可以看到 Pod 被分配到哪个节点:
$ minikube kubectl -- get pods -n kcna -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-with-node-selector-7668d66698-48q6b 1/1 Running 0 23m 172.17.0.2 minikube-m02 <none> <none>
这展示了通过nodeSelector向 Kubernetes 调度器提供位置指令的最常见方式。
让我们继续讨论 Kubernetes 提供的其他调度控制。nodeName应该很明显——它允许我们指定希望工作负载被调度到哪个节点。亲和性和反亲和性规则则更有趣。从概念上讲,亲和性类似于nodeSelector,但有更多的自定义选项。
nodeAffinity 和 podAffinity
这些规则允许你将 Pod 调度到集群中的某些节点(nodeAffinity),或者调度到已经运行指定 Pod 的节点(podAffinity)。
nodeAntiAffinity 和 podAntiAffinity
亲和性(affinity)的反义词。这些规则允许你将 Pod 调度到与指定节点不同的节点(nodeAntiAffinity),或者将 Pod 调度到已有指定 Pod 运行的不同节点(podAntiAffinity)。
换句话说,亲和性规则用于将 Pod 吸引到某些节点或其他 Pod 上,而反亲和性则是相反——将 Pod 推离某些节点或其他 Pod。亲和性也可以分为两种类型——硬性和软性:
-
requiredDuringSchedulingIgnoredDuringExecution——硬性要求,意味着除非满足规则,否则 Pod 不会被调度。 -
preferredDuringSchedulingIgnoredDuringExecution– 软性要求,意味着调度器会尝试找到满足要求的节点,但如果不可用,pod 仍会被调度到其他任何节点上。
注意
IgnoredDuringExecution 意味着如果 Kubernetes 已经调度了 pod 后节点标签发生变化,pod 将继续在同一节点上运行。
我们清单上的最后一项是 pod 拓扑 分布约束。
拓扑分布约束
这些规则使我们能够控制 Pods 在集群中的分布,跨越不同的故障域,如区域、可用区 (AZs)、节点或其他用户定义的拓扑。
本质上,这些规则使我们能够控制 Pods 的运行位置,考虑到集群的物理拓扑。在今天的云环境中,我们通常会在每个云提供商运营的区域内拥有多个 AZ。
AZ
这指的是一个或多个离散的数据中心,具有冗余的电力、网络和互联网连接。
在 Kubernetes 中,最佳实践是将控制平面和工作节点运行在多个可用区(AZ)中。例如,在 eu-central-1 区域,Amazon Web Services (AWS) 当前有三个 AZ,因此我们可以在每个 AZ 中运行一个控制平面节点,并在每个 AZ 中运行多个工作节点。在这种情况下,为了实现高可用性 (HA) 和高效的资源利用,我们可以对工作负载应用拓扑分布约束,以控制 Pods 在节点和可用区之间的分布,如图 7.1所示:

图 7.1 – Pods 在集群中分布的示例
通过这种方式,我们可以保护工作负载免受单个节点故障以及可能影响整个 AZ 的云提供商故障的影响。此外,还可以结合不同的方法和规则,进行更精确和细粒度的 Pods 安排控制。例如,我们可以将拓扑分布约束与 nodeAffinity 和 podAntiAffinity 规则结合使用,应用于同一个部署。
注意
可以为每个 pod 组合多个规则,例如,nodeSelector 与硬性 nodeAffinity 规则(requiredDuringSchedulingIgnoredDuringExecution)一起使用。两个规则都必须满足才能调度 pod。在至少有一个规则不满足的情况下,pod 将处于 Pending 状态。
总体来说,Kubernetes 的调度可能一开始看起来有些复杂,但随着经验的积累,你会发现它的丰富特性非常强大,可以帮助我们处理复杂的场景以及非常大规模和多样化的 K8s 安装。就 KCNA 考试的范围而言,你不需要了解深入的细节,但如果你有时间,建议查看本章末尾的进一步阅读部分。
资源请求和限制
在我们之前探索 K8s 调度器功能时,你是否曾想过 Kubernetes 是如何知道 哪个节点是集群中最适合特定 Pod 的节点?如果我们创建一个没有亲和性设置、拓扑约束或节点选择器的 Deployment,Kubernetes 如何决定集群中哪个位置最适合运行我们想要的应用?
默认情况下,K8s 不知道每个容器在调度的 Pod 中需要多少资源(CPU、内存等)来运行。因此,为了让 Kubernetes 做出最佳调度决策,我们需要让 K8s 了解每个容器正常运行所需的资源。
资源请求
资源请求是可选的指定,表明每个 Pod 中的容器需要多少资源。如果 Pod 所在的节点有可用资源,容器可以使用超过请求的资源。指定的请求量将在 Pod 被调度到的节点上保留。
Kubernetes 还允许我们对容器可以消耗的资源设置硬性限制。
资源限制
资源限制是对正在运行的容器最大可消耗资源的可选指定,这些限制由 kubelet 和容器运行时强制执行。
例如,我们可以设置 nginx 容器需要 250 MiB。如果这个容器所在的 Pod 被调度到一个总内存为 8 GiB 的节点上,且该节点上运行的 Pod 不多,那么我们的 nginx 容器可能会使用 1 GiB 或更多内存。然而,如果我们额外设置了 1 GiB 的限制,运行时将防止 nginx 超过该限制。如果进程试图分配更多内存,节点的内核将强制终止该进程并报出 Out Of Memory (OOM) 错误,容器将被重启。
对于 CPU,限制和请求是通过绝对单位来衡量的,其中 1 CPU 单位等于 1 0.5 CPU,也就是 500m 单位,其中 m 代表 milliCPU,如你所猜测的,它允许我们以这种方式指定 CPU 的一部分。与内存不同,如果进程试图消耗超出限制的 CPU 时间,它不会被杀死;而是会被限制使用。
注意
当指定了资源限制但未指定请求,并且未设置默认请求(例如,默认值可能从命名空间设置中继承)时,Kubernetes 将复制指定的限制并将其用作请求。例如,500 MiB 的限制会导致请求也为 500 MiB。
是时候看到这些操作了!让我们回到 Minikube 配置,尝试在 kcna 命名空间中创建以下带有单个容器的示例 Pod:
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
requests:
memory: "100Mi"
limits:
memory: "200Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
执行以下命令:
$ minikube kubectl -- create -f memory-request-limit.yaml -n kcna
pod/memory-demo created
其中的应用是一个简单的 stress 测试工具,能够生成可配置的负载和内存消耗。根据前面规范中指定的参数,它正好消耗 150 Mi 内存。由于 150 Mi 小于设置的限制(200 Mi),一切正常。
现在,让我们修改 stress 规格中的参数,将 150M 改为 250M。相应的更改在以下代码片段中突出显示:
memory: "200Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "250M", "--vm-hang", "1"]
删除旧的 pod,并应用更新后的规格,假设文件现在名为 memory-request-over-limit.yaml:
$ minikube kubectl -- delete pods memory-demo -n kcna
pod "memory-demo" deleted
$ minikube kubectl -- apply -f memory-request-over-limit.yaml -n kcna
pod/memory-demo created
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
memory-demo 0/1 ContainerCreating 0 3s
如果你输入足够快,你应该能够看到 OOMKilled 状态,最终还会看到 CrashLoopBackOff:
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
memory-demo 0/1 OOMKilled 0 5s
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
memory-demo 0/1 CrashLoopBackOff 2 (31s ago) 54s
此外,你还可以运行 minikube kubectl -- describe po memory-demo -n kcna 查看更多细节:
… LONG OUTPUT OMITTED …
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: OOMKilled
Exit Code: 1
因为该进程分配了 250 MiB 内存,并设置了 150 MiB 的限制,所以它被杀死。请记住,如果你在一个 pod 中运行多个容器,而该 pod 中至少有一个容器没有运行,则整个 pod 会停止接受请求。
总结一下,请求和限制非常重要,最佳实践是为所有在 Kubernetes 中运行的工作负载配置这两者,因为 Kubernetes 并不知道你的应用需要多少资源,而你可能会导致集群中的工作节点过载或未充分利用,从而影响稳定性,尤其是在资源请求未定义时。另一方面,资源限制有助于防止出现异常的 pod 和有 bug 的应用,它们可能会泄漏内存或试图使用所有可用的 CPU 时间,影响邻近的工作负载。
完成后,如果需要,可以删除 kcna 命名空间中的 pod 和其他资源。接下来,我们将继续探索 Kubernetes,并学习如何调试在 Kubernetes 上运行的应用。
在 Kubernetes 中调试应用
当你开始使用 Kubernetes 运行各种应用时,你最终会遇到需要调试至少部分应用的情况。有时,应用可能会遇到导致崩溃的 bug——可能是配置错误,或者在某些场景下表现异常。Kubernetes 提供了多种机制来帮助我们找出容器化负载和各个 pod 容器的问题,包括以下内容:
-
从 pod 中所有容器获取日志,并获取之前 pod 运行的日志
-
查询集群中最近发生的事件
-
从 pod 转发端口到本地环境
-
在 pod 容器内运行任意命令
日志在调试过程中起着至关重要的作用,帮助我们理解哪些地方没有按预期工作。应用通常支持多个日志级别,按信息的严重性和详细程度分类,例如接收到的请求及其负载;中断的连接;无法连接到数据库或其他服务等。
常见的日志级别有INFO、WARNING、ERROR、DEBUG和CRITICAL,其中ERROR和CRITICAL级别只会记录被认为是错误或重大问题的事件。INFO和WARNING级别可能提供关于发生了什么或可能表示应用程序问题的通用信息,而DEBUG通常会记录应用程序发生的所有事件,提供最详细的内容。顾名思义,启用DEBUG日志级别来帮助调试问题通常是明智的。尽管这种分类方式在业界非常标准,但一些软件可能有自己定义的日志详细程度方式,因此请参考相应的文档和配置示例。目前,标准的日志表示格式是 JSON,并且几乎所有编程语言和各种应用程序的开发库都广泛支持这一格式。
在日志架构方面,最佳做法是使用单独的后端来存储、分析和查询日志,以便独立于 Kubernetes 节点、Pod 和容器的生命周期持久化日志记录。这种方法称为集群级日志。Kubernetes 本身并不提供原生的日志存储解决方案,只会在每个节点上保留最新的日志。然而,有许多日志解决方案可以与 Kubernetes 无缝集成,例如Grafana Loki或Elastic Stack(ELK)等。集群级日志的对立面是为集群中运行的每个 Pod(应用程序)单独配置日志。这种方式不推荐在 Kubernetes 中使用。
为了收集每个 Kubernetes 节点的日志进行聚合和长期存储,通常使用节点日志代理。这些是小型的容器化代理,运行在每个节点上,并将所有收集的日志推送到日志后端服务器。由于它们需要在集群中的每个节点上运行,因此通常将它们定义为DaemonSet。这种设置的示意图如图 7.2所示:

图 7.2 – 使用节点日志代理进行日志收集与聚合
让我们暂时回到我们的 minikube 设置,看看如何获取应用程序日志。我们从一个基本的 Pod 开始,它只是将当前的日期和时间写入标准 输出(stdout):
$ minikube kubectl -- apply -f
https://k8s.io/examples/debug/counter-pod.yaml -n kcna
pod/counter created
$ minikube kubectl -- get pods -n kcna
NAME READY STATUS RESTARTS AGE
counter 1/1 Running 0 22s
执行kubectl logs以获取容器日志。如果一个 Pod 有多个容器,你需要额外指定想要获取日志的特定容器,可以使用--container参数:
$ minikube kubectl -- logs counter -n kcna
0: Sun Aug 7 13:31:21 UTC 2022
1: Sun Aug 7 13:31:22 UTC 2022
2: Sun Aug 7 13:31:23 UTC 2022
3: Sun Aug 7 13:31:24 UTC 2022
4: Sun Aug 7 13:31:25 UTC 2022
5: Sun Aug 7 13:31:26 UTC 2022
6: Sun Aug 7 13:31:27 UTC 2022
7: Sun Aug 7 13:31:28 UTC 2022
8: Sun Aug 7 13:31:29 UTC 2022
9: Sun Aug 7 13:31:30 UTC 2022
10: Sun Aug 7 13:31:31 UTC 2022
11: Sun Aug 7 13:31:32 UTC 2022
12: Sun Aug 7 13:31:33 UTC 2022
注意
也可以通过在kubectl logs命令中添加--previous参数,来获取上一个容器执行的日志(在容器被重启之前)。
我们在这里看到的实际上是容器内应用程序写入的stdout和stderr内容(kubectl logs)。
然而,如果一个应用没有配置将日志记录到stdout和stderr,则可以添加一个日志侧车(sidecar)——一个在同一 Pod 中运行的独立容器,它捕获主容器的日志并将其转发到日志服务器或自身的stdout和stderr输出流。这样的设置在图 7.3中有示意图:

图 7.3 – 使用侧车容器流式传输日志
接下来是可以提供有价值的见解,帮助你了解 Kubernetes 集群中发生了什么的事件。
Kubernetes 事件
这是一个记录 Kubernetes 资源状态变化的记录——例如,当节点变为NotReady或PersistentVolume(PV)未能挂载时,对节点、Pod 及其他资源的变化。
事件是有命名空间的,可以使用kubectl get events命令查询最近的事件。例如,如果我们最近扩展了一个 nginx 部署,我们可能会看到类似这样的内容:
$ minikube kubectl -- get events -n kcna
LAST SEEN TYPE REASON OBJECT MESSAGE
5s Normal Scheduled pod/nginx-deployment-with-node-selector-7668d66698-bx7kl Successfully assigned kcna/nginx-deployment-with-node-selector-7668d66698-bx7kl to minikube-m02
3s Normal Pulled pod/nginx-deployment-with-node-selector-7668d66698-bx7kl Container image "nginx:1.14.2" already present on machine
3s Normal Created pod/nginx-deployment-with-node-selector-7668d66698-bx7kl Created container nginx
3s Normal Started pod/nginx-deployment-with-node-selector-7668d66698-bx7kl Started container nginx
5s Normal SuccessfulCreate replicaset/nginx-deployment-with-node-selector-7668d66698 Created pod: nginx-deployment-with-node-selector-7668d66698-bx7kl
5s Normal ScalingReplicaSet deployment/nginx-deployment-with-node-selector Scaled up replica set nginx-deployment-with-node-selector-7668d66698 to 2
注意
默认情况下,只有过去一小时内发生的事件会被保留。这个时长可以在kube-apiserver配置中增加。
接下来是另一个在调试或开发过程中使用 Kubernetes 时非常有用的功能——kubectl命令,你可以配置它将本地端口上的连接转发到 K8s 集群中任意 Pod 的指定远程端口。命令的语法如下所示:
kubectl port-forward POD_NAME LOCAL_PORT:REMOTE_POD_PORT
如果你需要像在工作站本地运行一样访问 Kubernetes 负载,这非常有帮助。例如,你有一个 Web 应用在 Pod 中的80端口上监听,你将本地端口8080转发到该 Pod 的远程端口80。在端口转发运行时,你可以通过localhost:8080在本地访问该应用。
端口转发不是 KCNA 考试的一部分;然而,如果你有时间,可以随时查看本章结尾的进一步阅读部分,里面有关于如何使用它的示例。
我们列表中的最后一点是关于在已经运行的 Pod 容器中启动进程。我们之前已经做过这件事,在第六章,使用 Kubernetes 部署和扩展应用中。
相应的kubectl exec命令允许我们在容器中启动临时的、任意的进程,大多数情况下,你可能会用它来启动一个交互式模式的 shell(bash或sh)。这感觉就像使用安全外壳(SSH)协议登录到容器中。以下是一个示例:
kubectl exec -it POD_NAME -c CONTAINER_NAME bash
Kubernetes 还允许我们在本地系统和远程容器之间复制文件。相关命令是kubectl cp,它的工作方式与 Linux 的scp工具非常相似,但在 Kubernetes 的上下文中使用。
exec 和 cp 都非常实用于理解在调试的应用程序容器内发生的事情。它们允许我们快速验证配置设置、执行 HTTP 请求,或获取未写入 stdout 或 stderr 的日志。
总结
总的来说,在本章中,我们学习了在 K8s 中运行和操作工作负载的一些重要方面。我们了解了 pod 调度在 Kubernetes 中是如何工作的,它的各个阶段(nodeSelector、nodeName、亲和性和反亲和性设置,以及拓扑分布约束)。Kubernetes 调度器的丰富功能使我们能够涵盖控制工作负载在集群节点上如何分配的所有可能场景。通过这些控制,我们可以将一个应用程序的 Pods 分布到多个可用区(AZ)中的节点上以实现高可用性;将需要特殊硬件(例如 GPU)的 Pods 仅调度到具有该硬件的节点上;使用亲和性将多个应用程序集中在同一节点上运行;等等。
接下来,我们了解到资源请求帮助 Kubernetes 做出更好的调度决策,而资源限制则是为了保护集群和其他 Pod 免受不良应用程序的影响,或者简单地限制资源利用。当容器达到指定的内存限制时,它的进程会被终止(并重新启动容器);而当达到分配的 CPU 单元时,进程会被限速。
之后,我们探讨了在 K8s 上调试应用程序的方法。日志是任何问题分析中最基础和最重要的工具之一。Kubernetes 遵循 stdout 和 stderr,解决方案是在与应用程序同一 pod 中运行一个日志 sidecar 容器,以流式传输日志。
调试应用程序并了解集群中发生的情况的其他实用方法包括 Kubernetes 事件、端口转发,以及执行任意进程,如 kubectl exec 命令。
接下来是 Kubernetes 部分的最后一章,我们将在其中学习一些最佳实践并探索 K8s 的其他操作方面。如果你想更深入了解本节中描述的内容,请查看进一步阅读部分。
问题
在总结时,这里有一组问题供你测试自己对本章内容的理解。你可以在附录的评估部分找到答案:
-
以下哪些阶段是 Kubernetes 调度的一部分(可以选择多个)?
-
扩展
-
启动
-
过滤
-
打分
-
-
如果 Kubernetes 调度器无法将一个 pod 分配到节点上,会发生什么?
-
它将停留在
CrashLoopBackOff状态 -
它将停留在
Pending状态 -
它将停留在
NotScheduled状态 -
它将强制在某个控制平面节点上运行
-
-
以下哪些调度器指令在条件无法满足(软亲和性或软反亲和性)时,不会阻止 pod 被调度?
-
requiredDuringSchedulingIgnoredDuringExecution -
preferredDuringSchedulingIgnoredDuringExecution -
neededDuringSchedulingIgnoredDuringExecution -
softAffinity
-
-
应该使用 Kubernetes 调度器的哪个功能来控制 Pods 如何在不同的故障域(如可用区、节点等)中分布?
-
Pod 故障域约束
-
Pod 拓扑分布约束
-
podAntiAffinity -
nodeName
-
-
podAffinity在 Kubernetes 中的作用是什么?-
将 Pods 调度到集群中的特定节点
-
将两个或更多的 Pods 组合在一起以获得更好的性能
-
将 Pods 调度到其他 Pods 已经运行的节点上
-
将 Pods 调度到其他 Pods 已经运行的不同节点上
-
-
Kubernetes 中资源请求的目的是什么?
-
它们帮助规划集群扩展
-
它们定义了集群中更重要的工作负载
-
它们帮助选择集群中适合工作负载的硬件
-
它们有助于在集群中优化 Pod 的放置
-
-
如果容器的内存限制设置为
500Mi,但尝试分配550Mi,会发生什么情况?-
550Mi在 10% 的误差范围内,因此容器将正常分配内存 -
Pods 的限制比容器更高,因此内存分配将正常工作
-
容器进程将因为 OOM 错误被杀死
-
当容器进程超过
500Mi时,将会卡住
-
-
如果限制设置为
1.5,且该命名空间没有默认值,那么容器的 CPU 请求值是多少?-
0.0 -
0.75 -
1.5 -
1.0
-
-
如果一个 Pod 的容器请求总共为
10.0CPU 单位,但集群中最大的节点只有8.0个 CPU,会发生什么情况?-
请求不是硬性要求;Pod 将被调度
-
请求是硬性要求;Pod 将处于
Pending状态 -
由于
preferredDuringScheduling选项,Pod 无论如何都会被调度 -
由于集群资源不足,Pod 将处于
CrashLoopBackOff状态
-
-
通常用于调试的最大详细级别是哪一个日志级别?
-
INFO -
ERROR -
MAXIMUM -
DEBUG
-
-
集群级别日志在 Kubernetes 中意味着什么样的日志存储?
-
K8s 将所有集群日志聚合到控制平面节点
-
K8s 需要独立的日志收集和聚合系统
-
K8s 提供了一个完整的日志存储和聚合解决方案
-
K8s 仅为最重要的集群健康日志提供存储
-
-
Kubernetes 中的节点日志代理做什么(选择多个)?
-
仅从工作节点收集日志
-
收集所有节点的日志
-
将日志从工作节点发送到控制平面节点
-
将日志发送到聚合和存储到日志后端
-
-
Kubernetes 中日志侧车代理的作用是什么?
-
从无法记录到
stdout和stderr的应用程序收集并流式传输日志 -
为了在节点故障时提供日志的备份副本
-
允许在
ERROR和DEBUG等详细级别上记录日志 -
使
kubectl logs命令正常工作的目的
-
-
以下哪个
kubectl命令允许我们在容器中运行一个任意进程?-
kubectl run -
kubectl start -
kubectl exec -
kubectl proc
-
-
以下哪个命令将返回已失败并重新启动的 Pod 容器的日志?
-
kubectl logsPOD_NAME --previous -
kubectllogs POD_NAME -
kubectl previouslogs POD_NAME -
kubectl logs
-
-
哪个 Kubernetes 调度器功能提供了一种简单的方法,将 Pods 限制到具有特定标签的节点上?
-
kubectl local -
nodeConstrain -
nodeSelector -
nodeName
-
深入阅读
要了解本章中涉及的更多主题,请查看以下资源:
-
调度和分配 Pods 到节点:
kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ -
Pod 拓扑分布约束:
kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ -
Kubernetes 中的资源管理:
kubernetes.io/docs/concepts/configuration/manage-resources-containers/ -
使用端口转发访问 K8s 集群中的应用程序:
kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/ -
获取运行中容器的 Shell:
kubernetes.io/docs/tasks/debug/debug-application/get-shell-running-container/
第八章:遵循 Kubernetes 最佳实践
我们终于到达了 Kubernetes 部分的最后一章!恭喜你来到这里——你现在已经完成了成为Kubernetes 和云原生助理(KCNA)认证的一半以上!
本章将讨论一些操作 Kubernetes 的最佳实践以及一些安全漏洞和解决方法。
我们将学习 Kubernetes 网络和网络策略用于流量控制;使用基于角色的访问控制(RBAC)来限制访问;使用Helm作为 K8s 的包管理器,等等。和之前一样,我们需要上一章的 minikube 设置来进行一些实践练习。
本章的主题包括以下内容:
-
Kubernetes 网络基础
-
RBAC
-
Helm——K8s 的包管理器
-
Kubernetes 最佳实践
让我们开始吧!
Kubernetes 网络基础
毫不夸张地说,K8s 网络可能是最难理解的部分,即使是经验非常丰富的工程师和运维人员,也可能会感到困难。希望你还记得在第四章中提到,Kubernetes 实现了容器网络接口(CNI),这使我们可以使用不同的覆盖网络插件进行容器网络配置。然而,市面上有很多 CNI 提供商(例如Flannel、Calico、Cilium、Weave和Canal等),因此很容易感到困惑。这些提供商依赖于不同的技术,如边界网关协议(BGP)或虚拟扩展局域网(VXLAN),以提供不同级别的覆盖网络性能,并提供不同的功能。
但不用担心——在 KCNA 的范围内,你不需要了解太多细节。目前,我们将介绍 Kubernetes 网络基础知识。
请查看下图:

图 8.1 – Kubernetes 网络模型
如图 8.1所示,Kubernetes 集群中有三种类型的通信:
-
localhost,因为它们作为一个单元共同部署。 -
Pod 到 Pod——在覆盖网络层级上的通信(有时称为Pod 网络),跨越集群中所有节点。覆盖网络使得一个节点上的 pod 能够与集群中任何其他节点上的 pod 进行通信。这种通信通常称为东西流量。
-
使用
NodePort或LoadBalancer类型将一个 pod 或一组具有相同应用程序的 pods 暴露到集群外部。与外部世界的通信也称为南北流量。
在实践中,当一个 Pod 需要与其他 Pod 通信时,这还会涉及 Kubernetes 的服务发现机制。由于 Kubernetes 中每个新启动的 Pod 会自动获得一个在平面覆盖网络中的 IP 地址,因此几乎不可能在任何配置中引用 IP 地址,因为地址会不断变化。相反,我们将使用 ClusterIP 服务,它会在新 Pod 启动或旧 Pod 终止时自动跟踪所有端点列表的变化(详见 第六章)。Kubernetes 还允许使用 IP 地址管理(IPAM)插件来控制 Pod IP 地址的分配。默认情况下,集群中的所有 Pod 使用单一的 IP 池。使用 IPAM 插件,可以将覆盖网络 IP 池细分为更小的块,并根据注释或 Pod 启动所在的工作节点来分配 Pod IP 地址。
接下来,重要的是要理解,集群中的所有 Pod 网络中的 Pod 默认情况下 可以相互通信,没有任何限制。
注意
Kubernetes 命名空间不提供网络隔离。命名空间 A 中的 Pod 可以通过其在 Pod 网络中的 IP 地址访问命名空间 B 中的 Pod,反之亦然,除非通过 NetworkPolicy 资源加以限制。
NetworkPolicy 是一种资源,允许我们以应用程序为中心的方式控制 Kubernetes 中的网络流量。NetworkPolicy 允许我们定义 Pod 如何与其他 Pod(通过标签选择器选择)、其他命名空间中的 Pod(通过命名空间选择器选择)或 IP 地址块范围内的 Pod 通信。
网络策略本质上是 Kubernetes 中的一种 Pod 级别防火墙,它允许我们指定哪些流量可以进出与选择器匹配的 Pod。一个简单的例子是,当你有一个每个 Kubernetes 命名空间下包含多个微服务的应用程序时。你可能想要在这种情况下禁止命名空间之间的 Pod 通信,以便更好地实现隔离。另一个例子是:你可能希望限制对运行在 Kubernetes 中的数据库的访问,只允许需要访问它的 Pod,因为允许集群中的每个 Pod 访问数据库会带来安全风险。
但是,为什么我们在 Kubernetes 中需要应用网络策略呢?
随着应用程序从单体架构转向微服务架构,这增加了大量基于网络的通信。单体应用程序的绝大部分通信发生在 内部,因为它是一个大的可执行程序,而微服务依赖于消息总线和 Web 协议交换数据,这导致了大量的 东西向 网络流量,这些流量也应当得到安全保护。
在底层,网络策略由 CNI 提供商实现,为了使用网络策略,提供商应支持这些策略。例如,在我们的 minikube Kubernetes 中,NetworkPolicy 定义不会对集群中的流量产生任何影响。不过,如果你想深入了解 K8s 网络和网络策略,请查看进一步阅读部分。
接下来,我们将深入探讨 RBAC,看看它如何帮助保障 Kubernetes 集群的安全。
RBAC
你可能已经注意到,在我们的 minikube 集群中,我们对所有资源和命名空间都有无限制的访问和控制。虽然这对于学习目的来说是可以的,但在运行和操作生产系统时,你很可能需要限制访问权限。这时,Kubernetes RBAC 就变得非常有用。
Kubernetes RBAC
这是 Kubernetes 中的主要安全机制,确保用户仅根据其分配的角色访问资源。
使用 K8s RBAC 可以做的一些示例:
-
限制对特定命名空间(例如生产命名空间或某个应用的命名空间)的访问,仅限于一小部分人群(例如具有管理员角色的人员)
-
限制对某些资源的只读访问
-
限制对特定资源组的访问(例如,Pod、Service、Deployment、Secret 或任何其他资源)
-
限制对与 Kubernetes API 交互的应用的访问
Kubernetes RBAC 是一个非常强大的机制,它允许我们实施最小权限原则,这被认为是访问管理的最佳实践。
最小权限原则
这是指每个用户或账户仅获得完成其工作或流程所需的最小权限。
至于 KCNA 考试的范围,这几乎涵盖了你需要了解的关于 Kubernetes 中访问限制的所有内容。不过,本书的目的是将你带得更进一步,更接近操作 Kubernetes 集群的真实场景,因此我们将深入探讨。
让我们看看当你执行 kubectl apply 或 kubectl create 并提供一些资源规范时会发生什么:
-
kubectl会从KUBECONFIG环境变量中指定的文件读取 Kubernetes 配置。 -
kubectl会发现可用的 Kubernetes API。 -
kubectl会验证提供的规范(例如,检查 YAML 格式是否正确)。 -
将请求与载荷中的规范一起发送到
kube-apiserver。 -
kube-apiserver接收请求并验证请求的真实性(例如,是谁发起的请求)。 -
如果在上一步请求的用户已通过身份验证,则会执行授权检查(例如,是否允许该用户创建/应用所请求的更改?)。
这是 RBAC 发挥作用的地方,它帮助 API 服务器决定是否允许该请求。在 Kubernetes 中,使用多个 RBAC 概念来定义访问规则:
-
ALLOW权限和没有DENY规则,角色没有明确允许的内容将会被拒绝。角色是一个命名空间资源,创建时需要指定命名空间。 -
ClusterRole—与Role相同,但它是一个非命名空间资源。用于集群范围的权限,如一次性授予对所有命名空间内所有资源的访问权限,或授予对集群范围内资源(如节点)的访问权限。
-
ServiceAccount—一种为运行在Pod内的应用程序提供身份的资源。它本质上与普通的User相同,但专门用于需要与 Kubernetes API 交互的非人工身份。Kubernetes 中的每个 Pod 都总是与一个服务账户关联。
-
RoleBinding—这是一个实体,用于在特定命名空间内将 Role 或ClusterRole中定义的权限应用并授予User、Group(用户组)或ServiceAccount。
-
ClusterRoleBinding—与RoleBinding类似,但仅适用于ClusterRole,可以将规则应用于所有命名空间。
图 8.2 演示了将Role A和ClusterRole B通过RoleBinding应用于Namespace E中的Group D用户和ServiceAccount C。规则是累加的,意味着将ClusterRole B和Role A规则合并后,允许的所有操作都将被允许:

图 8.2 – 通过 RoleBinding 应用 Role 和 ClusterRole 规则
虽然 Kubernetes RBAC 初看可能比较复杂,但一旦你开始实践应用它,它会变得更加简单和清晰。你会发现 RBAC 机制非常灵活和精细,可以覆盖所有可能的场景,包括当 Pod 内的应用程序需要访问 Kubernetes API 时的情况。
让我们来看看以下简单的pod-reader角色定义:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: kcna
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"] # the actions allowed on resources
它可以通过RoleBinding授予在kcna命名空间内的 Pod 资源的只读访问权限,如以下代码片段所示:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: kcna
subjects:
# subjects can be multiple users, groups or service accounts
- kind: User
name: jack # name is case sensitive
apiGroup: rbac.authorization.k8s.io # the standard API group for all RBAC resources
roleRef:
# roleRef specifies the binding to a Role or ClusterRole
kind: Role # either a Role or ClusterRole
name: pod-reader # name of the Role or ClusterRole to bind to
apiGroup: rbac.authorization.k8s.io
继续在我们的 minikube 游乐场中首先创建一个Role,然后创建一个RoleBinding资源:
$ minikube kubectl -- create -f role.yaml -n kcna
role.rbac.authorization.k8s.io/pod-reader created
$ minikube kubectl -- create -f rolebinding.yaml -n kcna
rolebinding.rbac.authorization.k8s.io/read-pods created
RoleBinding引用了用户jack作为唯一主体,但单个RoleBinding也可以用来引用任意数量的用户、组和服务账户。
现在,在测试权限时,Kubernetes 有一个非常整洁的功能,允许我们在没有实际用户凭据(如 x509 客户端证书)的情况下检查权限。相应的kubectl auth can-I命令使我们能够验证特定用户、组或服务账户的允许和不允许的操作。尝试以下操作:
$ minikube kubectl -- auth can-i get pods --as=jack
no
但是,嘿,我们不是在之前的pod-reader角色定义中允许了用户jack进行get操作吗?是的,但是仅限于kcna命名空间!让我们通过指定命名空间再试一次:
$ minikube kubectl -- auth can-i get pods -n kcna --as=jack
yes
看起来好多了。那么 Pod 的创建或删除呢?让我们尝试以下操作:
$ minikube kubectl -- auth can-i create pods -n kcna --as=jack
no
$ minikube kubectl -- auth can-i delete pods -n kcna --as=jack
no
正如预期的那样,这是不允许的,就像我们为 kcna 命名空间中其他资源(除了 pod)创建的角色和绑定所限制的一样。你可能已经注意到角色定义中的动词非常精确——我们指定了 get、watch 和 list,它们并不相同:
-
watch是一个动词,允许我们实时查看资源的更新。 -
list只允许我们列出资源,但无法获取特定对象的更多细节 -
get允许我们检索资源的信息,但你需要知道资源的名称(要找出这个信息,你需要使用list动词)
当然,也有写权限的动词,如 create、update、patch 和 delete,它们可以作为角色定义规范的一部分。
如果你想了解更多关于 RBAC 的信息,可以自行探索并查看章节末尾的进一步阅读部分的资料。接下来,我们将学习 Kubernetes 包管理器的内容。
Helm —— Kubernetes 的包管理器
Kubernetes 的包管理器——这可能听起来有些困惑。我们使用系统包构建镜像,并通过 Docker 或其他工具将其推送到镜像仓库。那么,我们为什么还需要包管理器呢?
注意
本节内容并不是 KCNA 考试的必备知识;然而,强烈推荐阅读它,因为它可能帮助你在实际使用 Kubernetes 时避免一些错误。
想象一下以下场景——你正在为一家小企业管理几个 Kubernetes 集群。这些 Kubernetes 集群在规模和配置上类似,并且运行完全相同的应用程序,但分别用于不同的环境,如开发、测试和生产。开发团队推动微服务架构,现在大约有 50 个微服务在 Kubernetes 上运行,作为更大应用的一部分协同工作。
管理这些 Kubernetes 规范的天真方法是为每个微服务和每个环境创建单独的规范文件。要维护的 YAML 文件数量可能轻易超过 100 个,并且它们可能会包含大量重复的代码和设置,长期来看更难以管理。肯定有更好的方法,使用像 Helm 这样的包管理器就是其中一种可能的解决方案。
让我们更详细地澄清一下。Helm 不是用于构建容器镜像并将应用程序可执行文件打包其中的工具。Helm 用于对 Kubernetes 规范进行标准化管理,这些规范代表了我们希望在 Kubernetes 集群中运行的负载。
Helm
这是一个用于自动化创建、打包、部署和配置 Kubernetes 应用程序的工具。它有助于在 Kubernetes 上定义、安装和更新应用程序。
回到之前的 50 个微服务和 3 个环境的例子,使用 Helm,你可以创建一次可重用的模板,避免编写重复的规格文件,并且可以根据应用程序应该部署的环境,简单地应用不同的配置值。
接下来,你会意识到你运行的 50 个微服务中有 20 个依赖于独立的Redis实例,为了避免重复 20 次编写相同的 Redis 部署规格并使用不同的名称,你创建了一个模板化、可重用的单一规格,并可以简单地作为需求添加到其他需要它的应用程序中。
为了更好地理解 Helm,我们来谈谈它的三个主要概念:
-
Helm chart——这是一个包含运行 Kubernetes 应用程序所需的所有 K8s 资源定义(规格)的包。可以把它看作 Kubernetes 的等效物,就像 Linux 的DEB包、RPM包或Homebrew配方一样。
-
Helm 仓库——这是收集和共享charts的地方;它可以被视为 Kubernetes 的等效物,就像Python 包索引(PyPI)或Perl 综合档案网络(CPAN)对于 Perl 一样。Charts可以从仓库中下载,也可以上传到仓库。
-
Helm 发布——这是在 Kubernetes 集群中运行的chart实例。一个chart可以多次安装到同一个集群中,每次安装时都会创建一个新的发布。对于之前提到的 Redis 示例,我们可以有一个 Redis chart,在同一个集群中安装 20 次,每次安装都会有自己的发布和发布名称。
总而言之,Helm 将 charts 安装到 Kubernetes 中,每次安装时都会创建一个新的发布。通过 Helm 仓库,非常容易找到并重用适用于 Kubernetes 运行的常见软件的现成 charts。通过指定 charts 之间的依赖关系,也可以轻松地将多个 charts 一起安装,作为一个应用程序共同工作。
Helm 附带了一个 CLI 工具,也叫做helm。使用helm CLI 工具,我们可以搜索 chart 仓库、打包 charts、安装、更新和删除发布,并且几乎可以做 Helm 允许的所有操作。Helm 使用与kubectl相同的 Kubernetes 配置文件,并直接与 Kubernetes API 交互,如图 8.3所示:

图 8.3 – Helm v3 架构
Helm 还使得应用程序的更新和回滚变得更加简单。如果发布的变更出现问题,一个简单的命令——helm rollback——可以在几秒钟或几分钟内将你带回到之前的发布版本。使用 Helm 回滚与我们之前在第六章中尝试过的 Kubernetes 部署回滚类似,但不同之处在于 Helm 可以回滚任何图表规格的更改。例如,你修改了一个属于 Helm 图表的 Secret 规格文件,并触发了 helm upgrade 来推出这些更改。几分钟后,你发现这些更改破坏了图表应用程序,需要快速恢复到先前的版本。你可以执行 helm rollback 命令,指定可选的发布修订版和发布名称,迅速恢复到工作版本。
在此,我们不会深入探讨 Helm 或进行任何动手实验,因为 Helm 并不属于 KCNA 考试的一部分。本节的目标是为你快速介绍 Helm——一个大大简化 Kubernetes 上应用管理的工具。Helm 是一个包含 if/else/with/range 等功能的工具。
你还可以考虑其他工具,如Kustomize和YTT,它们实现了相同的目标,但采用了不同的方法。这些工具都不属于 KCNA 考试的一部分,但和往常一样,进一步阅读部分将包括这些工具的相关资源,如果你愿意深入了解。
Kubernetes 最佳实践
虽然 KCNA 认证并不是一个安全性聚焦的认证,但你需要了解一些关于 Kubernetes 和 Cloud Native 的基本知识和最佳实践,现在是时候讨论这些内容了。
Kubernetes 的文档建议了Cloud Native 安全的 4Cs:Cloud、Clusters、Containers 和 Code——这是一个具有四层深入防御的方法:

图 8.4 – Cloud Native 安全的 4Cs
在这种方法中,内圈安全性建立在外层的基础上。这样,Code 层受到 Container、Cluster 和 Cloud 层的保护,你无法仅通过在 Code 层进行安全防护来解决基础层存在的不良安全标准和实践问题,就像即使外层有很强的安全性,你也不能忽视保护最内层的需要。让我们更详细地了解一下,看看 4Cs 中的每一层代表了什么。
从基础层开始,云或其他基础设施(例如企业数据库或共置服务器)作为 Kubernetes 集群的可信基础。如果 Cloud 层存在漏洞或配置错误,那么构建在其上的组件就无法保证安全。
在书的开始部分,我们讨论了云中的共享责任模型的含义,其中云提供商和用户都必须采取措施,以确保工作负载的安全。因此,始终参考并遵循云提供商的安全文档。
说到集群层,Kubernetes 有许多最佳实践——具体来说,包括etcd加密、RBAC 配置、限制对节点的访问、限制 API 服务器访问、保持 Kubernetes 版本更新等。但不用担心——你不需要记住这些内容就能通过 KCNA 考试。
接下来是容器层。如你在第四章中可能记得的那样,容器有命名空间、沙箱和虚拟化三种类型,它们各有优缺点,虚拟化最安全但笨重,命名空间最轻量,但共享同一宿主机内核,因此安全性较低。运行哪种容器取决于工作负载及其他要求。此外,避免在容器中以root用户身份运行应用程序。如果root容器被攻破,那么整个节点和所有其他容器都可能面临被攻破的高风险。
到达中间层时,核心是代码层。你不应运行你不信任的源代码——例如,如果你不知道代码的来源或具体功能。我们在第四章中也详细讨论了这个方面。你在某处找到的容器镜像可能会封装恶意代码,运行这些代码会在你的环境中为攻击者打开一个后门。至少,你应该自己构建和测试运行的代码,并将漏洞扫描自动化,作为容器镜像构建过程的一部分。
如果你在不安全或公共网络上运行 Kubernetes 集群,考虑实施服务网格来加密所有 pod 流量。否则,Kubernetes 默认的覆盖网络会以未加密的方式传输所有数据,尽管一些 CNI 提供商也支持传输层安全协议(TLS)。考虑使用网络策略来隔离并进一步保护你的工作负载。正确的做法是默认拒绝所有 pod 之间的通信,并为每个应用程序和微服务制定定制的允许规则。是的,你可以在一个集群中同时使用服务网格和网络策略,它们的使用并不互相排斥。
最后,在处理 Kubernetes 时,有一些基本的良好实践。它们可能是我们所学内容的重复,但“温故而知新”总比事后吃苦要好:
- 使用控制器 创建
简单的 pod 规格不提供容错性及任何额外功能,如滚动更新。使用Deployment、StatefulSet、DaemonSet或Job控制器来创建 pod。
- 使用命名空间 组织工作负载
将所有内容都部署到一个默认的命名空间中很快会变得一团糟。创建多个命名空间以更好地组织工作负载,并简化操作。命名空间对于 RBAC 配置以及通过网络策略限制流量也非常有用。
- 使用资源请求 和限制
这些是 Kubernetes 做出最佳调度决策和保护集群免受恶意应用程序占用所有资源导致节点崩溃所必需的。
- 使用就绪和 存活探针
这些可以确保请求仅在 Pod 准备好 处理时到达。如果我们没有定义 readinessProbe 且应用程序启动过慢,那么所有转发到该 Pod 的请求都会失败或超时。livenessProbe 同样重要,因为它可以在容器的进程陷入死锁或卡住时重新启动容器。
- 尽可能使用小的容器镜像
避免在构建的容器镜像中安装可选的包,并尽量去除所有不必要的包。大镜像下载时间较长(因此,Pod 启动时也会花费更多时间),并且占用更多磁盘空间。像Alpine这样的专用精简镜像大小仅为 5-10 MB。
- 使用标签 和注解
向 Kubernetes 资源添加元数据,以便组织集群中的工作负载。这对操作和追踪不同应用程序之间的交互非常有帮助。K8s 文档建议包括 name、instance、version、component、part-of 和其他标签。标签用于标识资源,而注解则用于存储关于 K8s 资源的额外信息(如 last-updated、managed-by 等)。
- 使用多个节点和 拓扑感知
使用奇数个控制平面节点(如 3 或 5)来避免 脑裂 情况,并尽可能使用分布在多个故障域(如可用区或AZs)的多个工作节点。应用 Pod 拓扑分布约束或反亲和性规则,以确保微服务的所有副本不会都运行在同一个节点上。
这个列表可以扩展为更多的内容,但这些应该足够让你在使用 Kubernetes 时朝着正确的方向前进。监控和可观测性的话题将在后续章节中进一步讨论。
总结
这样,我们就完成了 Kubernetes 部分的内容——做得好!
记住——你动手实践越多,学习和理解 Kubernetes 及其概念的速度就越快。如果某些点仍然有些模糊,那也没关系。你可以随时回去重新阅读一些部分,并查看每章结尾的 进一步阅读 部分。如果有任何问题,请参考官方的 Kubernetes 文档。
本章讨论了 Kubernetes 集群中发生的三种类型的网络通信,并且默认情况下,集群中的两个 Pod 之间没有任何限制通信。因此,使用网络策略来只允许必要的通信并拒绝其余通信是一个好主意,出于安全原因。并非所有 CNI 提供商都支持网络策略,因此在规划 Kubernetes 安装时,确保检查这一点。
集群中的每个新 Pod 都会自动获得覆盖网络中的 IP 地址,Kubernetes 也会在 Pod 被终止时自动清理该 IP 地址。然而,在任何配置中使用 Pod 的 IP 地址并不实际,我们应该使用 Kubernetes 服务来处理 东-西 和 南-北 的通信。
接下来,我们学习了 Kubernetes 的 RBAC 功能,以及它们如何限制对 API 的访问。强烈建议为任何由多个人访问的集群,或者在 Kubernetes 中运行的应用程序与 K8s API 交互时,实施 RBAC 规则。
管理大量微服务和环境可能是一个挑战,一个包管理工具会非常有用。Helm 是一个强大的工具,用于打包、配置和部署 Kubernetes 应用程序。我们已经看到,Helm 引入了额外的概念,如 chart、仓库和发布。
关于安全性,Kubernetes 提出了 4Cs 分层方法:云,集群,容器,和代码。每一层都需要采取相应的实践和措施,只有这些层一起工作,才能确保基础设施和工作负载的安全。根据安全要求和 K8s 集群的设置,可能需要使用虚拟化容器而不是命名空间容器,并集成服务网格以加密 Pod 流量。
最后,我们总结了七个基本的 Kubernetes 实践,基于本章和前几章的材料,这些实践应有助于你朝着正确的方向前进。在接下来的章节中,我们将继续探索云原生世界,并学习关于云原生架构的内容。
问题
在我们总结时,以下是一些问题,供你测试自己对本章内容的理解。你可以在附录的评估部分找到答案:
-
以下哪个是 Pod 之间网络流量的另一种说法?
-
东-西
-
东-北
-
东-西
-
南-北
-
-
可以应用什么来限制 Pod 之间的流量?
-
PodPolicy -
PodSecurityPolicy -
TrafficPolicy -
NetworkPolicy
-
-
哪些层是云原生安全性 4Cs 的组成部分?
-
云,联合,集群,代码
-
云,集群,容器,代码
-
云,联合,容器,代码
-
代码,控制器,集群,云
-
-
Pod A 在命名空间 A 中运行,Pod B 在命名空间 B 中运行。它们可以通过各自的 IP 地址通信吗?
-
不是,因为不同的命名空间通过防火墙隔离
-
是的,但只有在它们运行在同一个工作节点上时
-
是的,如果没有通过
NetworkPolicy限制 -
不,因为不同的命名空间有不同的 IP 无类域间路由(CIDR)块
-
-
同一 Pod 中的两个容器如何通信?
-
通过网络策略
-
通过
localhost -
通过
NodeIP服务 -
通过
ClusterIP服务
-
-
以下哪种服务类型通常用于内部 Pod 到 Pod 的通信?
-
InternalIP -
LoadBalancer -
ClusterIP -
NodePort
-
-
可以用什么来加密集群中 Pod 之间的通信?
-
NetworkPolicy -
服务网格
-
EncryptionPolicy -
安全服务
-
-
以下哪种容器类型提供最大的隔离?
-
虚拟化
-
Namespaced
-
隔离
-
沙盒化
-
-
可以用什么来限制对 Kubernetes API 的访问?
-
服务网格
-
Helm
-
网络策略
-
RBAC
-
-
为什么构建自己的容器镜像很重要?
-
新构建的镜像通常体积更小
-
由于版权和许可限制
-
新构建的镜像总是包含最新的软件包
-
网络上的镜像可能包含恶意软件
-
-
以下哪项可以用来为 Pods 提供容错能力(可以选择多个)?
-
服务
-
部署
-
Ingress
-
StatefulSet
-
-
为什么最好使用三个控制平面节点,而不是四个?
-
因为四个节点消耗太多资源,三个节点就足够了
-
不等数目的节点有助于防止脑裂情况
-
更多节点会使覆盖的 Pod 网络变慢
-
更多节点会带来更多的运维负担,尤其是在版本升级时
-
-
为什么不建议在
ConfigMap配置中使用 Pod 的 IP 地址?-
因为 Pods 是短暂的
-
因为 Pod 的 IP 无法从互联网访问
-
因为 Pods 使用的是旧版 IPv4 协议
-
因为很难记住 IP 地址
-
-
将请求转发到正在运行的 Pod 可能会导致超时错误的原因是什么(可以选择多个)?
-
Kubernetes API 过载,影响所有 Pods
-
网络策略规则增加额外的网络延迟
-
Pod 中的进程卡住且没有设置
livenessProbe -
Pod 中的进程仍在启动中,且没有设置
readinessProbe
-
-
哪个 RBAC 实体用于给应用程序分配身份?
-
Role -
ServiceAccount -
RoleBinding -
ServiceIdentity
-
进一步阅读
要了解更多本章覆盖的主题,请查看以下资源:
-
网络策略:
kubernetes.io/docs/concepts/services-networking/network-policies/ -
使用 miniKube Kubernetes 的网络策略:
minikube.sigs.k8s.io/docs/handbook/network_policy/ -
Helm 快速入门指南:
helm.sh/docs/intro/quickstart/ -
Kustomize:
kustomize.io/ -
YTT:
carvel.dev/ytt/ -
推荐的 Kubernetes 标签:
kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ -
Kubernetes 在生产环境中:
kubernetes.io/docs/setup/production-environment/
第四部分:探索云原生
本部分将更详细地解释云原生背后的概念,什么使得应用成为云原生应用,哪些概念应该应用于云原生应用,以及如何在现代环境中交付和运营这些应用。
本部分包含以下章节:
-
第九章,理解云原生架构
-
第十章,在云中实现遥测与可观察性
-
第十一章,自动化云原生应用交付
第九章:理解云原生架构
本章将继续深入探讨云原生的各个方面。我们将看到哪些概念属于云原生,云原生的架构是什么,讨论韧性和自动扩展,并了解一些最佳实践。本章涵盖了云原生架构领域的进一步要求,这部分内容占Kubernetes 和云原生认证工程师(KCNA)考试问题的 16%。
本章没有实践部分,因此我们不会进行动手练习,但它仍然非常重要,因为它是通过 KCNA 考试并在该领域进一步发展的基础。我们将关注以下主题:
-
云原生架构
-
韧性与自动扩展
-
无服务器
-
云原生最佳实践
如果你跳过了本书的前两章,发现这里讨论的一些术语不清楚,请返回并阅读第一章和第二章,以弥补知识空白。
云原生架构
在前两章中,我们已经介绍了云原生的定义。让我们再检查一次,以便快速回顾。
云原生
这是一种在现代动态基础设施上构建和运行应用程序的方法,例如云计算。它强调高韧性、可扩展性、高度自动化、易于管理和可观察性的应用程序工作负载。
尽管有“云”这个词,云原生应用程序并不严格要求运行在云中。它是一种可以在本地构建和运行应用程序时也能采用的方法。没错——你也可以在本地构建具有韧性、可扩展性、高度自动化的云原生应用程序,尽管它们并不运行在云中。
重要的是要理解,仅仅选择一个知名的公共云服务提供商,并在其服务基础上构建应用程序(无论是 IaaS、PaaS、SaaS 还是 FaaS),并不意味着你的应用程序自动成为云原生。例如,如果你的应用程序启动时需要人工干预,那么它就无法自动扩展,也无法在发生故障时自动重启,因此它就不是云原生的。
另一个例子:你有一个由若干微服务组成的 Web 应用程序,但它是手动部署和配置的,没有任何自动化,因此无法轻松更新。这不是一种云原生方法。如果你此时忘记了微服务的概念,下面再给你一个定义。
微服务
微服务是作为大型应用或服务的一部分协同工作的较小应用程序。每个微服务可能负责大型应用中的单一功能,并通过网络与其他微服务进行通信。这与单体应用程序相反,后者将所有功能和逻辑打包成一个大的可部署单元(紧耦合)。
实际上,为了实现一个云原生应用程序,你很可能会采用微服务。微服务是松耦合的,这使得它们可以独立扩展、开发和更新。微服务解决了许多问题,但随着数量的增加,也带来了更多的操作开销,这就是为什么许多云原生技术,如 Kubernetes 和 Helm,旨在使微服务的部署、管理和扩展变得更加简单。
再次严格来说,运行由 Kubernetes 管理的微服务并不是实现云原生架构的必要条件。但这种由 Kubernetes 协调的容器化、小型、松耦合的应用程序组合非常有效,并为云原生铺平了道路。相比于使用许多虚拟机(VMs)和自制的 Shell 脚本,通过 Kubernetes 和容器来实现弹性、自动扩展、可控的滚动更新和可观测性要容易得多。Kubernetes 还具有与基础设施无关的特点,这使得它在许多环境和用例中都具有很大的吸引力。它可以运行在裸金属服务器上、虚拟机中、公共云和私有云中,且可以作为托管服务使用,或者运行在由云资源和本地数据中心组成的混合环境中。
现在,在深入探讨一些方面之前,让我们高层次地看一下云原生带来的好处:
-
缩短市场时间(TTM) – 高度的自动化和简便的更新使得新功能能够迅速交付,从而为许多企业提供了竞争优势。
-
成本效益 – 云原生应用程序根据需求扩展,这意味着只需为实际需要的资源付费,避免了浪费。
-
更高的可靠性 – 云原生应用程序能够自我修复并自动从故障中恢复。这意味着减少了系统的停机时间,从而改善了用户体验。
-
可扩展性和灵活性 – 微服务可以单独扩展、开发和更新,这使我们能够处理各种场景,并为开发团队提供更多灵活性。
-
无供应商锁定 – 通过正确的方法和开源技术的使用,云原生应用程序可以在不同的基础设施或云提供商之间迁移,且几乎不需要任何努力。
这个列表还可以继续,但应该足以让你了解为什么大多数现代应用程序遵循云原生方法和实践。接下来,我们将重点讨论云原生的一些重要方面,比如弹性和自动扩展。
弹性和自动扩展
听起来可能有些好笑,但为了设计和构建弹性系统,我们需要预期事物会失败并分崩离析。换句话说,为了打造弹性系统,我们需要为故障设计,并提供让应用程序和基础设施能够自动从故障中恢复的方法。
弹性
这表明应用程序和基础设施能够在故障发生时自动恢复。无需人工干预的恢复能力通常称为自我修复。
我们已经在第六章中看到过自我修复的实际应用,当时 Kubernetes 检测到期望状态和当前状态不一致,并迅速启动了额外的应用程序副本。这要归功于 Kubernetes 的协调循环。
当然,也有方法在没有 Kubernetes 的情况下构建具有韧性的应用程序和基础设施。例如,Amazon Web Services (AWS) 公有云提供了 自动伸缩组,允许您在一个组内运行所需数量的虚拟机(VM),并根据负载自动增加或减少虚拟机的数量。如果虚拟机发生故障,系统会检测到并通过创建新的虚拟机来做出响应。当组内的 CPU 利用率达到某个预定义的阈值时,可以创建新的虚拟机来分担负载。这引出了另一个重要的概念:自动伸缩。
自动伸缩
这是一种能够自动增加或减少计算资源以满足当前需求的能力。
不用多说,如今不同的云服务提供商提供了多种配置自动伸缩的选项。这些选项可以基于各种指标和条件,其中 CPU 或内存利用率仅仅是常见的例子。
过去,在传统 IT 时代,应用程序和基础设施是根据系统的峰值使用情况来设计的,这导致了硬件的高度低效利用和高昂的运行成本。自动伸缩是一个巨大的改进,它已成为云原生的最重要特性之一。
当我们谈论自动伸缩时,它适用于应用程序及其运行的基础设施,因为仅仅伸缩其中一个是远远不够的。让我们通过以下例子来解释——你管理着多个微服务应用程序,这些应用在典型负载下需要 10 个工作节点才能运行。如果创建了额外的工作节点并将其加入到 K8s 集群中,那么在以下情况之一发生之前,这些新节点上将不会有任何 Pod 运行:
-
应用程序的副本数量增加,以便创建新的 Pod。
-
一些现有的 Pod 会退出并由控制器(如 Deployment、StatefulSet 等)重新创建。
这是因为 Kubernetes 不会在新节点加入集群时重新调度已经运行的 Pod。所以,除了增加新节点外,还需要增加微服务副本的数量。为什么仅仅增加副本数就不够? 这样做最终会使 CPU/内存利用率达到上限,导致 Kubernetes 节点无法响应,因为负载将会为资源而争斗。
一般来说,伸缩可以分为两种类型,如图 9.1所示:

图 9.1 – 水平扩展与垂直扩展的比较
让我们更详细地了解一下:
-
水平扩展—当我们增加或减少实例(虚拟机或节点)的数量时,就像前面的例子那样。水平扩展也称为向外扩展(添加新的虚拟机)和向内扩展(终止虚拟机)。
-
垂直扩展—当我们保持实例数量不变,但更改其配置(如 vCPU 数量、内存 GB、磁盘大小等)时。垂直扩展也称为向上扩展(增加 CPU/RAM 容量)和向下扩展(减少 CPU/RAM 容量)。
为了简化记忆,你可以将水平自动扩展记作数字的自动增加(或减少),而将垂直自动扩展记作大小的增加(或减少)。云原生微服务架构通常采用水平自动扩展,而传统的单体应用程序通常采用垂直扩展。垂直自动扩展也更具限制性,因为即使是今天可用的最大虚拟机和裸机服务器,也无法突破某些技术限制。因此,水平扩展是更为推荐的方式。
从前面的章节中,我们已经知道可以通过 Kubernetes 在一个简单的命令中更改部署副本的数量;然而,这不是自动的,而是手动扩展。除此之外,Kubernetes 中有三种机制可以实现自动扩展:
-
水平 Pod 自动扩展器(HPA)—当负载上升时,它会更新工作负载资源(如 Deployment、StatefulSet),以添加更多 Pods,而当负载下降时,它会移除 Pods 以匹配需求。HPA 需要配置副本的上下限(例如,最少 3 个副本,最多 10 个副本)。
-
垂直 Pod 自动扩展器(VPA)—它会更新工作负载资源的请求和限制(如 CPU、内存、大页面大小)。它可以减少资源请求过多的容器的请求和限制,并根据历史使用情况扩展资源请求和限制,适用于资源请求过少的工作负载。与 HPA 不同,VPA 不会更改副本数量,如在图 9.2中所示。VPA 还可以选择性地允许我们定义请求的最小和最大边界。
-
集群自动扩展器—它通过增加或移除工作节点来调整 Kubernetes 集群的大小,适用于当因资源不足而导致某些 Pods 无法运行,或者存在长时间未使用的节点且这些节点上的 Pods 可以迁移到集群中的其他节点时。建议限制集群中节点的最大数量,以防止集群过大。集群自动扩展器还需要与您的云服务提供商集成才能正常工作:

图 9.2 – Kubernetes HPA 和 VPA 的比较
这些机制尤其在结合使用时非常强大。如你所记得,我们需要同时扩展应用程序和运行它的基础设施,因此仅使用 HPA 或仅使用 Cluster Autoscaler 是不够的。实际上,所有三种机制可以一起使用,但这是一个复杂的主题,你在没有足够 K8s 经验之前不应该轻易接触。就 KCNA 的范围而言,你只需要了解什么是自动扩展以及 Kubernetes 提供了哪些自动扩展机制。
到头来,自动扩展是云原生的重要组成部分,它有助于在工作负载性能、基础设施规模和成本之间找到平衡。
接下来,我们将进一步了解 Serverless——一种近年来获得广泛应用的计算演进模式。
Serverless
在第一章中,我们简要介绍了 Serverless 的定义——一种在 2010 年左右出现的较新云交付模型,通常被称为功能即服务或FaaS。
Serverless
这是一种计算模型,代码以小函数的形式编写,这些函数在不需要管理任何服务器的情况下构建和运行。这些函数由事件触发(例如,用户在网页上点击按钮、上传文件等)。
尽管名字中有“Serverless”,但事实上,Serverless 计算仍然依赖于底层的真实硬件服务器。然而,运行这些函数的服务器完全抽象与应用开发分离。在这种模式下,提供商负责运行代码所需的所有操作:配置、扩展、维护、安全补丁等。由于你无需关心服务器,因此这种模式被称为 Serverless。
除了不需要例行的服务器操作外,Serverless 还带来了一些优势。开发团队可以简单地将代码上传到 Serverless 平台,一旦部署,应用程序及其运行的基础设施将根据需求自动扩展。
当 Serverless 函数处于空闲状态时,大多数云提供商不会收费,因为没有事件触发函数执行,因此没有产生费用。如果有 10,000 个事件触发 10,000 次函数执行,那么在大多数情况下,提供商会按精确执行时间计费。这与典型的按需付费云虚拟机不同,后者不管实际的 CPU/RAM 利用率如何,只要虚拟机在运行,你就需要支付所有运行时间。一个示例 Serverless 架构如图 9.3所示:

图 9.3 – Serverless 架构示例
注意
API 网关是 Serverless 的一部分,允许我们为应用程序的 REST API 定义端点,并将这些端点与实现实际逻辑的函数连接起来。API 网关通常处理用户认证和访问控制,并且经常提供额外的可观察性功能。
值得一提的是,许多流行的编程语言(如 Java、Golang、Python 和 Ruby 等)都得到了提供 Serverless 服务的云服务商的支持,但并不是所有供应商都允许使用我们自己的容器镜像。事实上,目前提供 Serverless 服务的大多数公共云服务商都依赖于其自有的技术。因此,如果你为 AWS Lambda(AWS 提供的 Serverless 服务)开发了一个 Serverless 应用,那么将其迁移到 Google Cloud Functions(Google Cloud 提供的 Serverless 服务)将需要付出相当大的努力。
除了完全托管的云 Serverless 平台,今天还有一些开源替代方案可供选择,这些方案减少了供应商锁定的风险。例如,以下 Serverless 框架可以安装在 Kubernetes 上,并将函数代码打包成容器运行:
-
OpenFaaS
-
CloudEvents
-
Knative
-
Fn
Knative 和 CloudEvents 目前是由 Cloud Native Computing Foundation(CNCF)策划的项目。
总结来说,FaaS 可以看作是云计算模型(IaaS、PaaS 和 SaaS)的进化,适用于 Cloud Native 架构。虽然 Serverless 的市场份额仍在增长,但显然它不会取代常见的虚拟机和托管平台服务,因为我们在这里简要提到的一些限制:
-
为了持久化数据,Serverless 应用必须与其他有状态组件进行交互。因此,除非你完全不需要保持状态,否则你将不得不涉及数据库和其他存储选项。
-
在大多数情况下,对运行时配置的控制非常有限。举例来说,当使用云服务提供商的 FaaS 时,你将无法更改操作系统或 Java 虚拟机(JVM)的参数。
-
Cold start——容器或基础设施初始化以执行函数代码需要一些时间(通常在几十秒的范围内)。如果某个函数长时间未被调用,下次调用时将遭遇冷启动延迟。解决办法是定期调用函数以保持其 预热。
-
监控、日志记录和调试 Serverless 应用通常更加困难。虽然你不需要关心服务器以及 CPU 或磁盘利用率等指标,但在运行时调试函数的方式有限。你将无法像在本地 IDE 中那样逐行运行代码。
-
供应商锁定的风险。正如前面提到的,FaaS 服务尚未标准化,因此从一个供应商迁移到另一个供应商需要大量的工作。
这个列表并不详尽,但希望它能让你明白为什么你不应该急于让开发团队完全迁移到 Serverless。无论是云服务提供商还是开源 FaaS 服务,近年来都已经有了很大的改进,因此许多限制可能会在不久的将来得到解决。
再次强调,我们在这里探讨的内容比 KCNA 认证所要求的要深入一些。你不会被问及无服务器架构的局限性,但你需要理解计费模型的概念,并能列举几个允许你在 Kubernetes 上运行自己的 FaaS 项目。
在本章的最后部分,我们将总结一些关于云原生的关键点。
云原生最佳实践
随着世界变化的加速,用户的需求越来越高,IT 领域也必须做出相应的变化以满足这些期望。如今,如果一个网页打开需要 30 秒钟,很多人都无法忍受,而且如果网上银行系统停运一个小时,人们会抱怨。
云原生通过引入全新的应用构建和运行方式,显著改善了该领域的情况。云原生应用程序设计时考虑到了故障,并能够从大多数故障中自动恢复。大量的重点放在了让应用程序和基础设施都具备弹性上。这可以通过多种方式实现,不论是否使用 Kubernetes。如果使用 Kubernetes,请确保运行多个控制平面和工作节点,并将它们分布在不同的故障域中,例如云服务提供商的可用区(AZs)。始终使用如 Deployment 等控制器运行应用的至少两个副本(pod),并确保它们分布在集群的拓扑结构中。若 pod 发生故障,K8s 的调和循环将启动,并自动修复应用。
一些公司通过在基础设施中引入随机故障,采取了更进一步的措施来提高弹性,使它们能够检测到需要改进或重设计的薄弱环节。例如,Netflix 就以其混沌猴子工具而闻名,这个工具会随机终止虚拟机和容器,促使工程师构建高度弹性的服务。
排在下一个重点的是自动扩展——这对于性能和成本效率都是至关重要的。自动扩展必须为应用程序和基础设施同时实现。如果使用 Kubernetes,请确保至少设置 HPA 和集群自动扩展器。并且不要忘记为所有工作负载配置资源请求和限制,因为它有助于 K8s 以最优方式调度 pod。
在应用架构方面,应用松耦合原则——开发执行小任务的微服务,并让它们作为更大应用的一部分协同工作。如果场景适合,考虑使用事件驱动的无服务器架构。在许多情况下,无服务器架构可能更具成本效益,且几乎不需要运维。
关于角色方面,我们在第二章中已经了解到,组织需要为合适的岗位聘用合适的人才。这不仅仅是聘请 DevOps 和站点可靠性工程师来处理基础设施,还涉及团队协作和企业文化的支持,促进不断变化和实验。通过不断学习,能获得宝贵的见解,并促进架构和系统设计的改进。
此外,我们之前简要提到过,云原生应用应该具备高度的自动化和易于管理的特点。在第十一章中,我们将详细探讨自动化如何帮助更快速、更可靠地交付软件。在接下来的第十章中,我们将讨论遥测和可观测性。
总结
在本章中,我们学习了云原生架构、应用及其特性。并非所有在云中运行的应用都自动变为云原生应用。实际上,云原生原则也可以成功地应用于本地部署,而不仅仅是云中。我们简要讨论了云原生的好处,并深入探讨了两个核心特性——弹性和自动扩展。虽然云原生应用并不严格要求必须使用 Kubernetes 来运行,但 K8s 凭借其自愈能力和多种自动扩展机制,使得实现更加轻松:HPA、VPA和集群自动扩展器。
接下来,我们介绍了 Serverless 或 FaaS——一种更新的、基于事件驱动的计算模型,具备自动扩展功能,几乎不需要任何操作维护。使用 Serverless 时,我们无需负责操作系统、安全补丁或服务器生命周期管理。Serverless 的计费基于实际使用情况,通过实际函数调用次数和执行时间来计算。Serverless 技术可以用来实现云原生应用,但也要注意其局限性。
最后,我们总结了本章以及之前章节中关于云原生的要点,在第二章中已有提到。在接下来的章节中,我们将重点讨论如何监控云原生应用,并了解如何实现遥测和可观测性。
问题
在我们结束时,这里有一组问题,供你测试自己对本章内容的理解。你可以在附录的评估部分找到答案:
-
以下哪个选项有助于提高 Kubernetes 的弹性?
-
资源请求
-
多容器 Pod
-
调和循环
-
Ingress 控制器
-
-
以下哪种 Kubernetes 自动扩展器允许我们根据负载自动增加或减少 Pod 的数量?
-
VPA
-
HPA
-
RPA
-
集群自动扩展器(Cluster Autoscaler)
-
-
以下哪种 Kubernetes 自动扩展器基于统计数据调整容器的资源请求和限制?
-
VPA
-
HPA
-
RPA
-
集群自动扩展器(Cluster Autoscaler)
-
-
为什么下调应用程序和基础设施的扩展很重要?
-
为了减少可能的攻击面
-
为了避免触及云服务提供商的限制
-
为了减少网络流量
-
在计算资源空闲时减少成本
-
-
什么最能描述水平扩展?
-
向同一服务实例添加更多 CPU
-
向同一服务添加更多副本/实例
-
向同一服务实例添加更多 RAM
-
将 Pods 调度到其他已运行 Pod 的不同节点
-
-
哪种扩展方法适用于 Cloud Native 应用程序?
-
集群扩展
-
云扩展
-
垂直扩展
-
水平扩展
-
-
以下哪些项目允许我们在 Kubernetes 上运营自己的无服务器平台(选择多个)?
-
KubeVirt
-
KEDA
-
Knative
-
OpenFaaS
-
-
什么特征表征了无服务器计算(选择多个)?
-
服务器不再需要
-
它支持所有编程语言
-
它是基于事件的
-
提供商负责服务器管理
-
-
关于微服务扩展,哪种说法是正确的?
-
单独的微服务可以扩展进出
-
只有所有微服务可以同时进行扩展和收缩
-
微服务不需要扩展——只需要扩展基础设施
-
微服务最适合垂直扩展
-
-
哪种应用程序设计原则最适合 Cloud Native?
-
自愈
-
紧耦合
-
解耦
-
松耦合
-
-
什么描述了一个高度弹性的应用程序和基础设施?
-
在出现问题时自动关闭的能力
-
自动从大多数故障中恢复的能力
-
在故障情况下保持状态的能力
-
执行滚动更新的能力
-
-
什么代表了无服务器应用程序的最小部分?
-
网关
-
方法
-
容器
-
函数
-
-
以下哪个关于无服务器的说法是正确的?
-
仅按实际使用量计费
-
它是免费的,因为不涉及服务器
-
按固定小时价格计费
-
按与 IaaS 服务相同的方式计费
-
-
以下哪些是 Cloud Native 应用程序的特征(选择多个)?
-
高可扩展性
-
高效率
-
高弹性
-
高可移植性
-
-
通常应该扩展什么以适应负载?
-
应用程序及其运行的基础设施
-
负载均衡器和入口
-
应用程序 Pod 的数量
-
Kubernetes 工作节点的数量
-
-
哪种弹性测试工具可以随机引入基础设施中的故障?
-
Chaos Monster
-
Chaos Kube
-
Chaos Donkey
-
Chaos Monkey
-
进一步阅读
要了解本章中涉及的主题,请查看以下资源:
-
Kubernetes HPA 演练:
kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/ -
Kubernetes 集群自动扩展器:
github.com/kubernetes/autoscaler -
Chaos Monkey:
github.com/Netflix/chaosmonkey -
OpenFaaS:
www.openfaas.com/ -
Knative:
knative.dev/docs/
第十章:在云中实施遥测与可观察性
正如我们已经知道的,云原生应用程序通常由多个小型服务组成,这些服务通过网络进行通信。云原生应用程序会频繁更新并用新版本替换,在本章中,我们强调基于观察来监控和优化这些应用程序,以便在考虑成本的情况下获得最佳性能。
本章涵盖了 KCNA 考试中云原生可观察性领域的进一步要求,占总考试题目的 8%。以下是我们将重点关注的主题:
-
遥测与可观察性
-
Prometheus 用于监控和告警
-
FinOps 和成本管理
让我们开始吧!
遥测与可观察性
随着传统单体架构向分布式松耦合微服务架构的演变,对高质量遥测的详细需求迅速变得显而易见。在进一步阐述之前,首先让我们定义一下在 IT 基础设施上下文中什么是遥测。
遥测
指监控和收集系统性能数据进行分析,帮助识别问题。遥测是日志、指标和追踪的统称,也称为遥测类型或信号。
由于云原生应用程序本身就是分布式的,因此追踪和跟踪各部分之间的所有通信在故障排除、找出瓶颈以及提供应用程序性能洞察方面发挥着重要作用。
三种遥测信号(日志、指标和追踪)帮助我们更好地理解应用程序和基础设施在任何给定时刻的状态,并在需要时采取纠正措施。
可观察性
是指基于来自被观察系统的遥测信号持续生成洞察的能力。换句话说,一个可观察的系统就是那种通过正确的数据在正确的时间提供正确的决策来清晰了解其状态的系统。
让我们通过一个例子来解释什么是正确的数据在正确的时间做出正确的决策。假设你在 Kubernetes 上操作微服务,其中大多数服务将数据持久化到由持久化****存储卷(PV)支持的数据库层。
显然,你需要确保所有数据库都在正常运行,并且存储设备上为存储卷提供的磁盘空间足够。如果你仅仅收集并分析服务的日志,那将不足以决定何时扩展存储容量。例如,使用数据库的服务可能会突然崩溃,因为它们无法再向数据库写入数据。数据库的日志将指向存储空间已经用尽,并且急需更多的容量。
在这种情况下,日志有助于找到罪魁祸首,但它们并不是在正确的时间提供的正确数据。正确的数据应该是从存储设备收集的连续磁盘利用率度量。正确的时间应该是预定义的阈值(假设设备已满 70%),这样操作员有足够的时间做出正确的决策,比如扩展或释放容量。在凌晨 2 点告知操作员数据库存储已满且服务中断显然不是最佳做法。
这就是为什么仅依赖一个遥测信号几乎永远不够,我们应该同时具备这三者以确保可观测性。可观测性是更快响应事故、提高生产力和优化性能的关键之一。然而,拥有更多的信息并不一定意味着系统更可观察。有时,信息过多可能会产生相反的效果,使得从噪声中辨别出有价值的见解变得更加困难(例如,由应用程序的最大调试级别生成的过多日志记录)。
现在让我们更详细地了解每个遥测信号,从日志开始。
日志
事件是由应用程序、操作系统或设备(例如防火墙、负载均衡器等)记录的文本描述。
日志记录所代表的事件几乎可以是任何事情,从服务重启和用户登录到接收到有效载荷的 API 请求或代码中执行特定方法等。日志通常包括时间戳、文本消息以及进一步的信息,如状态码、严重性级别(DEBUG、INFO、WARNING、ERROR、CRITICAL)、用户 ID 等。
注意
日志的严重性级别以及如何在 Kubernetes 中访问容器日志的说明已经在第七章的在 Kubernetes 中调试应用程序一节中详细讨论过。如果您之前因为任何原因跳过了它,请务必回去查看。
下面是 Nginx Web 服务器记录的示例日志消息,该消息在处理来自 IP 66.211.65.62的客户端的HTTP v1.1 GET请求时生成,时间为 2022 年 10 月 4 日:
66.211.65.62 - - [04/Oct/2022:19:12:14 +0600] "GET /?q=%E0%A6%A6%E0%A7%8B%E0%A7%9F%E0%A6%BE HTTP/1.1" 200 4556 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
还可以从消息中提取其他信息,如:
-
响应状态码
200(HTTP OK) -
发送给客户端的字节数
4556 -
URL 和查询字符串
/?q=%E0%A6%A6%E0%A7%8B%E0%A7%9F%E0%A6% -
以及用户代理
Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html),这实际上告诉我们请求是由 Google 的网页爬虫发出的。
根据应用程序的不同,日志格式可以进行调整,只包含您想要的信息,并跳过任何不必要的细节。
下一项信号是度量。我们来弄清楚它们是什么。
度量
度量是常规的性能测量,描述了应用程序或系统在一段时间内的表现,以时间序列格式呈现。
常见的例子包括像 CPU 使用率、RAM 利用率、打开的连接数或响应时间等指标。如果你仔细想一想,单独的、不规律的测量并不能提供系统状态的大量价值或洞察力。可能会出现短暂的利用率高峰,持续不到一分钟,或者反过来:利用率在一段时间内下降。
通过单个测量,无法对应用程序或系统的行为进行任何预测或分析。使用时间序列,我们可以分析一个或多个度量标准的变化,确定趋势和模式,并预测即将发生的变化。这就是为什么应该定期收集度量标准,通常在 30 秒到几分钟之间的短时间间隔内。时间序列还可以作为图表进行可视化表示,如下图10**.1所示:

图 10.1 – CPU 使用率度量可视化(X = 时间,Y = 利用率)。
虽然根据图表,在 12:30 到 13:15 之间 CPU 使用率似乎有显著增加,但显示期间的最大利用率始终在 10%以下,表明系统严重未利用。应始终收集基本的 CPU、内存或磁盘使用率等基本度量标准,但通常这些是不足以做出正确决策的。
因此,建议收集多个应用程序特定的度量标准,包括每分钟的 API 请求次数、队列中等待的消息数量、请求响应时间、打开的连接数等等。这些度量标准中的一些非常适合做出正确的自动扩展决策,另一些则可以提供有关应用程序性能宝贵的见解。
目前关于度量标准就介绍到这里。接下来我们将继续讲述请求追踪。
追踪
是对通过分布式系统组件传递的请求的完整跟踪。它允许查看哪些组件参与处理特定请求、处理时间多长以及沿途发生的任何额外事件。追踪是追踪请求的结果。
现在想象一下以下情况。您正在调查您运行的基于分布式微服务的应用程序的较长响应时间,但请求或负载的数量并没有太大变化。由于大多数应用程序请求经过多个服务,您需要验证每个服务,找出表现不佳的服务(一个或多个)。但是,如果集成了像Jaeger或Zipkin这样的追踪工具,它将允许您追踪请求并存储和分析结果。追踪将显示哪个服务具有较长的响应时间,并可能减慢整个应用程序。然后可以在如下图10**.2所示的仪表板上查看收集到的追踪结果:

图 10.2 – Jaeger 仪表板中的示例追踪视图。
总的来说,追踪对可观察性贡献巨大。它有助于理解流量的流向,并能快速检测到瓶颈或问题。与日志和度量值一起,这三种遥测类型是现代应用程序和基础设施监控的必备工具。如果没有它们,运维人员将是盲目的,无法确保所有系统正常运行并按预期执行。实现遥测和可观察性的正确方式可能需要一些努力和时间,但它总是值得的,因为最终它会在解决问题时为你节省大量时间。
说到实现,有一个 CNCF 项目叫做 OpenTelemetry(简称 OTel)。它提供了一套标准化的、与供应商无关的 API、SDK 和工具,用于接收、转换和发送数据到可观察性后端。支持大量开源和商业协议及编程语言(C++、Java、Go、Python、PHP、Ruby、Rust 等),使得几乎可以将遥测集成到任何应用程序中。虽然在 KCNA 范围内并非严格要求,但如果你想了解更多,下面的进一步阅读部分将提供相关链接。
接下来,在下一部分我们将学习 Prometheus —— 云原生可观察性工具中的佼佼者。
Prometheus 用于监控和告警
在 2012 年首次亮相后,Prometheus 迅速凭借其丰富的功能和 时间序列数据库 (TSDB) 获得了广泛关注,TSDB 使得度量数据可以持久化,便于查询、分析和预测。有趣的是,Prometheus 的灵感来源于 Google 的 Borgmon —— 一个用于监控 Google Borg(Kubernetes 的前身)工具。
2016 年,Prometheus 被接受为第二个 CNCF 项目(仅次于 K8s),并在 2018 年达到了毕业状态。今天,Prometheus 被视为监控和告警的行业标准,广泛用于 Kubernetes 和其他 CNCF 项目中。
够了,历史背景我们先放一边,直接进入正题。首先,什么是 TSDB?
TSDB
是一种优化用于存储带有时间戳数据的数据库。这些数据可以是被跟踪和聚合的测量值或事件。在 Prometheus 的情况下,这些数据是从应用程序和基础设施的各个部分定期收集的度量值。这些度量值保存在 Prometheus TSDB 中,可以使用其强大的PromQL查询语言进行查询。
度量数据是如何被收集的?一般来说,有两种收集监控度量数据的方法:
-
/metricsURL 是通过一个简单的 HTTPGET请求调用的。这是 Prometheus 的主要工作方式。服务应该使度量数据可用并保持更新,Prometheus 应定期通过GET请求访问/metrics来获取数据。 -
Pushgateway是通过 HTTPPUT请求完成的。它对于那些无法通过抓取(pull)获取服务指标的情况非常有用,比如由于网络限制(服务在防火墙或 NAT 网关后面)或当指标源的生命周期非常短暂(例如快速批处理作业)时。
当我们说到指标数据时,它不仅仅指指标名称和值,还包括时间戳和通常的附加标签。标签表示指标的某些属性,可以包含抓取该指标的服务器的主机名、应用程序的名称,或者其他几乎任何内容。标签对于使用 Prometheus 的 PromQL 语言进行分组和查询指标数据非常有帮助。让我们看一个示例指标:
nginx_ingress_controller_requests{
cluster="production",
container="controller",
controller_class="k8s.io/ingress-nginx",
endpoint="metrics",
exported_namespace="kcna",
exported_service="kcnamicroservice",
host="kcnamicroservice.prd.kcna.com",
ingress="kcnamicroservice",
instance="100.90.111.22:10254",
job="kube-system-ingress-nginx-controller-metrics",
method="GET",
namespace="kube-system",
path="/",
pod="kube-system-ingress-nginx-controller-7bc4747dcf-4d246",
prometheus="kube-monitoring/collector-kubernetes",
status="200"} 273175
在这里,nginx_ingress_controller_requests 是指标名称,273175 是指标的值(表示请求数),{} 中的其他内容是标签。正如你所看到的,标签对于缩小指标范围至关重要,能够帮助确认它适用于哪个服务,或者它究竟表示什么。在这个例子中,它显示的是 kcnamicroservice 服务在 kcna 命名空间下的 HTTP GET 请求被 HTTP 200 OK 响应的计数。
Prometheus 的另一个优秀功能是仪表盘,它允许我们直接在 Prometheus UI 中可视化 TSDB 中的数据。虽然它非常容易绘制图表,但其功能有些有限,这也是为什么许多人使用 Grafana 来进行可视化和指标分析的原因。
现在让我们思考一下,如何在无法修改的应用程序中使用 Prometheus 收集指标?我们指的是那些没有在 /metrics 端点上公开指标的服务,或者那些并非在你公司内部开发的服务。这同样适用于没有内置 Web 服务器的软件,如数据库、消息总线,甚至基本的操作系统统计数据(CPU、RAM、磁盘利用率)。针对这种情况的解决方案叫做 Prometheus Exporter。
Prometheus Exporter
这是一个小工具,它弥合了 Prometheus 服务器与那些原生不导出指标的应用程序之间的差距。Exporter 聚合来自进程或服务的自定义指标,格式为 Prometheus 支持的格式,并通过 /metrics 端点暴露供 Prometheus 收集。
本质上,Exporter 是一个简化的 Web 服务器,它知道如何从需要监控的应用程序中捕获指标,并将这些指标转换为 Prometheus 格式以供收集。我们以 PostgreSQL 数据库为例,PostgreSQL 本身并不公开任何指标,但我们可以与它一起运行一个 exporter,该 exporter 会查询数据库并提供可观测数据,这些数据会被拉入 Prometheus 的时序数据库(TSDB)。如果在 Kubernetes 上运行,典型的做法是将 exporter 与被监控的服务放在同一个 Pod 中,作为 Sidecar 容器(如果你错过了,Sidecar 容器的介绍可以参考 第五章)。
今天,你会发现许多现成的导出器可用于流行的软件,如MySQL、Redis、Nginx、HaProxy、Kafka等。然而,如果没有可用的导出器,使用任何流行的编程语言和 Prometheus 客户端库编写一个也并不难。
说到 Kubernetes 和 Prometheus,它们之间有无缝的集成。Prometheus 具有开箱即用的能力来监控 Kubernetes,并自动发现你在 K8s 集群中运行的工作负载的服务端点。除了 Kubernetes,Prometheus 还支持各种 PaaS 和 IaaS 服务,包括来自 Google Cloud、Microsoft Azure、Amazon Web Services 等的服务。
有了这些,我们已经涵盖了使用 Prometheus 进行监控的部分,在进入告警之前,让我们先看一下图 10.3:

图 10.3 – Prometheus 架构。
如你所见,Prometheus 服务器将从其目标中拉取指标,这些目标可以是静态配置的,也可以通过服务发现动态发现。短生命周期的作业或位于 NAT 后的服务也可以通过Pushgateway推送它们的指标。这些指标可以通过PromQL在Prometheus Web UI或第三方工具中查询、显示和可视化。
接下来,我们将学习关于 Prometheus Alertmanager及其通知功能的内容。
告警
监控系统中的响应元素是由指标变化或某个阈值的突破触发的。
告警用于通知团队或值班工程师应用程序或基础设施状态的变化。通知可以采取电子邮件、短信或聊天消息等形式。例如,Alertmanager,正如你已经猜到的,是 Prometheus 中负责告警和通知的组件。
当某个指标(或多个指标的组合)突破预设阈值并维持在该阈值以上或以下若干分钟时,通常会触发告警。告警定义基于 Prometheus 表达式语言,并接受数学运算,从而允许灵活地定义条件。事实上,Prometheus 的一个独特功能是能够预测某个指标何时会达到某个阈值,并提前触发告警,在实际发生之前提醒你。这样,你可以定义一个告警,在主机磁盘空间不足时提前五天通知你。例如,这得益于 Prometheus 的 TSDB,它保持时间序列数据,并允许分析数据及其变化速率。
总体而言,Prometheus 是云原生时代监控的终极解决方案。它通常用于监控 Kubernetes 本身以及运行在 Kubernetes 上的工作负载。如今,您会发现很多软件原生支持 Prometheus,通过 /metrics 端点以 Prometheus 格式暴露度量数据。许多语言提供的客户端库使得将 Prometheus 支持直接集成到您自己的应用程序中成为可能。这个过程有时被称为直接插桩,因为它引入了 Prometheus 的原生支持。对于不提供原生支持的应用程序和软件,您可能会找到导出器来提取数据并以 Prometheus 指标格式提供以供收集。
现在,有些人可能迫不及待想要亲自动手操作 Prometheus,但事实上我们已经比 KCNA 考试所需的更详细地介绍了它。尽管如此,仍然鼓励您查看进一步阅读部分,并尝试将 Prometheus 部署到我们的 miniKube K8s 集群上。现在,我们将进入成本管理的话题。
FinOps 和成本管理
随着传统数据中心和共置环境迅速过渡到云计算,云服务的费用也很快显现出可能相当昂贵的特点。事实上,如果您看到公共云提供商的账单,通常会发现所有东西都被计量,所有东西都有费用:跨可用区流量和互联网流量、对象数量或空间使用、API 请求数量、互联网 IP、不同的虚拟机规格、分级存储和额外的 IOPS、关闭的虚拟机存储,等等。价格有时还会因地区而异,这使得提前估算费用变得困难。这导致了近年来 FinOps 的出现。
FinOps
是一种云财务管理学科和文化实践。它帮助组织通过工程、财务、技术和业务团队的协作,基于数据驱动的支出决策,获得最大的商业价值。
在 DevOps 强调开发与运维之间的协作时,FinOps 将财务也加入到这个组合中。它帮助团队管理他们的云支出,并强调工程和业务团队之间的协作需求,作为持续改进和优化过程的一部分。尽管您不需要了解细节以通过 KCNA 考试,但仍然鼓励您在进一步阅读部分了解更多关于 FinOps 的内容。
当我们谈论云计算时,默认假设我们可以随时提供和终止资源,如虚拟机,并且我们只需为虚拟机运行的时间付费。这就是所谓的按需容量或按需定价模型,这是当今最流行的云服务消费方式。您使用它——您为此付费,如果什么也不运行——那么就不会收费。
然而,公共云服务提供商通常还提供另外两种选项:
-
保留实例 – 这些是你为较长时间(通常为一年或更长时间)预定的虚拟机或裸金属服务器,并且需要提前支付费用。保留实例相比于常规的按需定价,通常有非常好的折扣(30-70%),但你将失去一定的灵活性。这意味着即使你不再需要这些资源,你仍然需要为保留的资源付费。
-
抢占式实例(有时也叫做可抢占实例) – 这些实例可以在任何时候被云服务提供商终止(删除)。抢占式实例是云服务提供商提供的剩余和备用容量,通常会有大幅折扣(60-90%)相较于按需容量。在某些情况下,你需要竞标(类似拍卖)抢占式实例,只要你的出价高于其他竞标者,你的实例就会继续运行。
那么,你应该使用哪种实例类型呢?
对这个问题没有简单的答案,因为有许多变量会影响最终的决策,而且每种情况的答案都不同。一个基本的原则是,只为持续的工作负载或始终需要的最小容量购买保留实例,这样可以运行你的应用程序。对于非关键性的工作负载、批处理和各种非实时分析,可以使用抢占式实例。那些可以重新启动并稍后完成的工作负载非常适合使用抢占式实例。而按需实例则可以用于其他所有情况,包括临时扩展以应对更高的负载。正如你从前一章节中记得的,自动扩展是云原生架构的主要特点之一,通常情况下,你会在这里使用按需实例。
然而,云中的有效成本管理不仅仅依赖于正确的容量类型(按需/保留/抢占式)。实例的大小(规格)也必须正确。这就是所谓的合理调整大小。
合理调整大小
是一种持续的过程,旨在将实例大小与工作负载的性能和容量需求相匹配,同时考虑成本因素。
我们已经知道,自动扩展对于成本效益至关重要,自动扩展也可以视为合理调整大小策略的一部分。你不希望在负载较低时运行太多低效能的实例,而在负载较高时又缺乏足够的实例来处理负载。自动扩展应该瞄准容量/性能与相关基础设施成本之间的最佳平衡点。但除了实例的数量,实例的大小(如 CPU 数量、内存 GB 数、网络吞吐量等)也是非常重要的。
例如,运行 40 个 Kubernetes 工作节点作为虚拟机,每个只有 4 个 CPU 和 8 GB 的内存,可能比运行 20 个工作节点,每个有 8 个 CPU 和 16 GB 内存的成本还要高,尽管它们的总 CPU 和内存数相同。此外,许多提供商提供基于不同 CPU 代次和优化特定工作负载的实例。有些实例可能针对高网络吞吐量进行了优化,而有些则针对低延迟磁盘操作进行了优化,因此可能更适合您的应用程序。所有这些都应作为正确资源规划和云端成本管理策略的一部分加以考虑。
摘要
在本章中,我们学习了很多关于遥测和可观察性(Observability)的内容。三种遥测类型或信号分别是 日志、指标 和 追踪,它们从不同的角度提供了对系统的宝贵洞察。一个可观察的系统是一个不断被监控的系统,我们通过遥测数据来了解系统的状态,这些数据充当了证据。
我们还了解了一些项目,如 OpenTelemetry,它可以帮助进行仪表化并简化实施遥测所需的工作。我们对如 Zipkin 和 Jaeger 等项目进行了简要介绍,这些项目用于追踪,并且仔细看了 Prometheus——一个功能齐全的监控平台。
Prometheus 支持 Push 和 Pull 两种操作模型用于指标收集,但主要使用 Pull 模型定期抓取 (/metrics 端点) 的指标数据,并以时间序列格式保存到 TSDB 中。将指标存储在 TSDB 中可以让我们使用 Grafana 等软件可视化数据,并定义告警,当某个指标超出预设的阈值时,通过首选通道通知我们。
另一个关于 Prometheus 的优点是 Kubernetes 集成。Prometheus 支持自动发现运行在 Kubernetes 中的目标,这使得运维人员的工作更加轻松。对于那些没有原生提供 Prometheus 格式指标的软件,可以运行 Exporters——这些小工具能够汇总来自服务或应用程序的指标,并通过 /metrics 端点以 Prometheus 格式暴露出来以供收集。如果你控制着所运行应用程序的源代码,也可以通过多种编程语言中提供的客户端库来为 Prometheus 提供支持。这被称为 直接仪表化。
最后,我们还需要了解 FinOps 和云中的成本管理。最常见的是所谓的按需容量,它们在云中被消费。这意味着资源可以在需要时配置,在不再需要时删除,并且只按它们运行的时间计费。这与预留容量不同,后者是为较长时间支付的实例,预付后可以获得较大的折扣,但如果未使用,仍然需要付费。而Spot或Preemptible实例是云服务商可能随时终止的备用容量。它们是三者中最便宜的选择,但对于需要最大正常运行时间的关键工作负载可能不是最佳选择。
最后但同样重要的是,我们已经涵盖了合理配置(Rightsizing)。这是一个根据性能需求和成本之间的平衡,找到适合当前工作负载的最佳实例大小和实例数量的过程。
下一章节将介绍云原生应用程序的自动化和交付。我们将学习最佳实践,并看到如何更快速、更可靠地交付更好的软件。
问题
正确答案可以在 TBD 找到
-
以下哪些是有效的遥测信号(可以选择多个)?
-
跟踪
-
Ping
-
日志
-
指标
-
-
以下哪种是 Prometheus 进行指标收集的主要操作模式?
-
推送
-
拉取
-
提交
-
合并
-
-
以下哪个选项允许在缺少原生应用程序支持时通过 Prometheus 收集指标?
-
在 Kubernetes 中运行应用程序
-
安装 Pushgateway
-
安装 Alertmanager
-
安装应用程序导出器
-
-
以下哪个信号是 Prometheus 收集的?
-
日志
-
指标
-
跟踪
-
审计
-
-
哪个组件可以用来让应用程序将指标推送到 Prometheus?
-
Zipkin
-
Grafana
-
Alertmanager
-
Pushgateway
-
-
哪种遥测信号最适合查看请求如何穿越基于微服务的应用程序?
-
日志
-
跟踪
-
指标
-
Ping
-
-
哪个软件可以用来可视化存储在 Prometheus TSDB 中的指标?
-
Zipkin
-
Kibana
-
Grafana
-
Jaeger
-
-
以下哪些软件可以用于分布式应用程序的端到端跟踪(可以选择多个)?
-
Prometheus
-
Grafana
-
Jaeger
-
Zipkin
-
-
什么使得查询过去的 Prometheus 指标成为可能?
-
Alertmanager
-
TSDB
-
PVC
-
Graphite
-
-
Prometheus 默认从哪个端点收集指标?
-
/``collect -
/``prometheus -
/``metric -
/``metrics
-
-
Prometheus 指标的格式是什么?
-
时间序列
-
跟踪
-
Span
-
图表
-
-
以下哪个选项允许应用程序直接进行仪表化以提供 Prometheus 格式的指标?
-
K8s 服务发现
-
Pushgateway
-
导出器
-
客户端库
-
-
一个定期任务只需要 30 秒就能完成,但 Prometheus 的抓取间隔是 60 秒。收集该任务的指标的最佳方式是什么?
-
将指标推送到 Pushgateway
-
将抓取间隔缩短到 30 秒
-
将抓取间隔缩短到 29 秒
-
使用 Kubernetes CronJob 替代 job
-
-
以下哪个是合理配置(Rightsizing)的关键部分?
-
FinOps
-
预留实例
-
自动伸缩
-
自动化
-
-
在实施自动扩展时,以下哪些因素应该被考虑?
-
CPU 利用率指标
-
RAM 利用率指标
-
CPU + RAM 利用率指标
-
CPU、RAM 和应用程序特定指标
-
-
以下哪些实例类型由多个公有云提供商提供(请选择多个)?
-
按需
-
无服务器
-
Spot
-
预留
-
-
以下哪种实例类型适合用于负载稳定没有波动的持续工作负载,并且应运行几年不间断?
-
按需
-
无服务器
-
Spot
-
预留
-
-
以下哪种实例类型适合用于批量处理和周期性任务,并且如果最低价格是主要优先考虑因素,可以中断任务?
-
按需
-
无服务器
-
Spot
-
预留
-
进一步阅读
-
OpenTelemetry:
opentelemetry.io/docs/ -
Jaeger:
www.jaegertracing.io/ -
Grafana:
grafana.com/grafana/ -
Prometheus 查询语言:
prometheus.io/docs/prometheus/latest/querying/basics/ -
Prometheus 导出器:
prometheus.io/docs/instrumenting/exporters/ -
Kubernetes 用于 Prometheus 的指标:
kubernetes.io/docs/concepts/cluster-administration/system-metrics/ -
FinOps 简介:
www.finops.org/
第十一章:自动化云原生应用交付
本章我们将重点介绍云原生应用生命周期。我们将了解云原生应用的开发和交付最佳实践,并看看自动化如何帮助更好地开发和更快地交付。
本章涵盖了 KCNA 考试中有关云原生应用交付领域的所有知识,该领域占总考试题目的 8%。我们将要讨论的主题如下:
-
云原生应用交付
-
CI/CD 和 GitOps
-
基础设施即代码(IaC)
这是本书的最后一章技术内容。你几乎准备好参加考试并成为 Kubernetes 和 Cloud Native 助理了。继续加油!
云原生应用交付
现代应用通常以较高的速度开发,这需要高效且强大的交付流程。这就是为什么云原生应用交付是一个相当复杂但高度自动化的过程,包含多个阶段。
首先,开发人员编写代码并将其提交到版本控制系统(VCS),如Git、Mercurial 或 Subversion,目前 Git 是事实上的标准。然后代码被构建、测试和发布。自动化这些阶段可以加速整个软件交付过程,并使得发布小而频繁且经过充分测试的软件版本成为可能。
发布
软件的一个版本,包括要交付给用户的变更(新功能、增强、修复等)。每个发布都有语义版本控制,其中 v1.0.0 通常代表第一个稳定版本(更多关于语义版本控制的信息,请参见进一步阅读部分)。
发布(作为动词)也常用于描述将软件部署到生产环境的过程,例如将新软件版本提供给最终用户。
严格来说,交付的应用不一定非得是云原生应用。然而,由于云原生架构通常基于微服务,手动执行所有构建-测试-发布阶段几乎是不可能的。想象一下,如果你必须为 30 个不同的微服务做这些工作 30 次——这将是慢的、容易出错且繁琐的工作。这就是为什么自动化成为现代应用生命周期中不可或缺的一部分,并为我们带来了许多好处,包括以下几点:
-
更快的交付时间和更频繁的更新
-
通过自动化流程获得更稳定的发布
-
减少人工工作提高生产力
-
通过自动化测试减少缺陷
-
可重复的结果
显然,除了构建-测试-发布,每个服务还应部署、操作和监控,正如下面图 11.1所示:

图 11.1 – 现代应用生命周期。
这个循环是无限的,阶段的自动化使得即使一天发布多次变更和更新也变得可能。事实上,这还节省了大量开发者的时间,使他们能够专注于开发新功能和修复 bug。如果出现任何问题,应该能够轻松快速地回滚更改(之前在第八章中讨论了如何使用 Kubernetes 和 Helm 来实现这一点)。
现在我们了解了 Cloud Native 应用交付的基本知识,让我们深入探讨 CI/CD 和 GitOps。
CI/CD 和 GitOps
你可能听说过,CI代表持续集成,CD代表持续交付,有时也代表持续部署。
持续集成
是一种面向开发者的实践和过程自动化。通过 CI,代码更改会定期被构建、测试并集成到共享仓库中(分支/主干等)。
CI 被视为解决开发中同时进行的过多更改可能相互冲突并且难以合并的问题。最终目标是确保软件始终处于可工作状态,如果自动化测试失败,团队必须先解决问题,然后才能继续开发。
持续交付
通常指的是 Pipeline 中的自动化,在这里经过测试的代码更改标志着一个发布,该发布被上传到软件包仓库或容器镜像。从那里起,镜像或软件包可以在批准后部署。
持续交付可以看作是一种对齐开发和业务团队的方式,其目的是确保部署新代码所需的时间和精力最小化。在持续交付中,通常需要人工批准才能将发布版本部署到生产环境。
持续部署
是将更改从源代码仓库自动部署到开发、测试或生产环境的进一步自动化。它本质上是持续交付,只不过当所有测试和检查通过后,部署会自动发生。
示意图如下所示,见图 11.2:

图 11.2 – CI/CD/CD 管道
毋庸置疑,任何组织中实现 CI 或 CD 自动化的方式有很多种,没有单一的正确方式。我们可以使用不同的工具,引入自定义检查或部署计划,涉及不同的利益相关者进行审批等。然而,有一件事总是存在的,那就是Pipeline。
CI/CD Pipeline
是实现自动化流程的顶级组件。Pipeline 通常由多个任务和阶段组成,其中一个任务或阶段的输出可以作为下一个任务/阶段的输入。Pipeline 可能会调用多个工具来编译代码、运行单元测试、执行代码分析、创建二进制文件、将代码打包成容器等。
我们在谈论 CI/CD 时为什么常提到流水线,是因为软件交付中的许多过程都有顺序执行的步骤。你可以把它看作一个管道,源代码从一端进入,构建和测试后的包从另一端出来,或者包可能被部署到另一端。这个过程会像图 11.1中展示的那样一遍遍重复。
说到包和变更集,通常认为最好的做法是频繁发布和部署小的更改,而不是偶尔做大的更改,特别是在实施持续部署时。当变更集较小时,出现问题的风险要低得多,因此建议定期发布和部署经过测试的小更改,而不是一次性部署许多更改作为爆炸式更新。
现在我们已经介绍了这个概念,接下来提到一些可以帮助我们实现 CI/CD 自动化的工具。今天有许多工具可用,尽管功能集各不相同,但它们都可以用来构建和运行 CI/CD 流水线。某些工具可能更适合你的技术栈,提供更好的 VCS 支持,某些提供 Kubernetes 和各种 IaaS/PaaS 集成,某些则仅作为服务提供。作为服务提供的 CI/CD 系统意味着供应商将负责其维护、更新、扩展等,按月收费。在为团队选择一个工具之前,确保进行一些研究。
这里列出了一些你可能想了解的流行 CI/CD 系统:
-
ArgoCD
-
Jenkins
-
Gitlab CI
-
Tekton
-
GitHub Actions
-
Spinnaker
-
FluxCD
-
GoCD
-
CircleCI
-
TravisCI
注意
名字中带有CI或CD并不意味着你只能用该工具来实现CI或CD。一些工具可以同时用于实现CI和CD,而一些则不行。
GitOps
之前,我们学习了DevOps、DevSecOps、FinOps,现在还有一个Ops要介绍——GitOps。它于 2017 年推出,是对现代基础设施和 Cloud Native 应用交付的行业最佳实践和 CI/CD 的进一步演进。
GitOps
是一个结合了 DevOps 实践的操作框架,如 Git 版本控制、协作、合规性、声明性配置和 CI/CD。

图 11.3 – GitOps 概述。
GitOps 基于三个核心元素:
合并请求(MR)+基础设施即代码(IaC)+ CI/CD
-
master)分支和提交消息及评论作为审计日志。 -
IaC是下一个章节的主题,但简单来说,它是一种将期望的基础设施配置和设置描述为代码,并存储在 Git 仓库中的实践。IaC 是声明性配置的一个例子,作为你基础设施的真实来源。
-
CI/CD是 GitOps 的一个关键组成部分,它的目的是不仅自动化前一节中描述的交付阶段,还能避免手动更改并消除基础设施漂移。GitOps 不容忍手动更改。
基础设施(配置)漂移
这就是基础设施的实时状态与IaC配置中定义的内容不匹配的情况。漂移可能是由手动操作、应用程序进行意外更改、软件漏洞等原因引起的。
漂移可能导致不确定性、应用程序故障,甚至引入安全漏洞。这就是为什么消除基础设施漂移非常重要,如果实现得当,GitOps 能够很好地完成这项工作。如果有人做了手动更改,那么在下一个 MR 被接受并合并到事实来源Git 仓库时,这些手动更改将被覆盖,漂移将被消除。
此外,一些高级 GitOps 工具,如ArgoCD、FluxCD和Jenkins X,具备持续监控 Git 仓库中的变化并将其传播到连接的实时环境中的能力。系统会自动重新同步,并将环境恢复到期望状态——任何在实时环境中的手动更改都会在几秒钟内被覆盖。从 Git 合并的代码中的新变化会自动快速部署。
需要记住的一点是,ArgoCD、FluxCD和Jenkins X需要在 Kubernetes 集群上运行。这并不意味着没有 Kubernetes 就无法实现 GitOps,但由于 Kubernetes 拥有开发完善的生态系统及其终极的协调循环功能,它能将集群资源带回到期望状态,使用 Kubernetes 实现 GitOps 要容易得多(如果你忘了的话,参考第五章)。
让我们通过以下示例来看 GitOps 和 K8s 是如何互相补充的。假设你的团队在 Kubernetes 上运行微服务,规格定义存储在 Git 仓库中,Git 仓库作为事实来源。你使用 ArgoCD 来进行 GitOps,将 K8s 清单部署到目标 Kubernetes 集群。最近,你团队里有一位新同事,他的任务是停用一个不再需要的微服务。
不幸的是,这位新同事并没有完全了解流程,开始使用 kubectl 手动删除 Kubernetes 资源,这种做法是 GitOps 的反模式。由于错误,他/她删除了错误的 K8 部署,该部署属于生产环境中的另一个微服务。Kubernetes 控制循环开始工作,终止了由该部署管理的 Pods,导致错误的微服务停机。幸运的是,ArgoCD 开启了自动同步,并检测到两个受影响的微服务的状态已经偏离了位于 Git 仓库中的定义。ArgoCD 启动并迅速创建了缺失的部署以及其他手动删除的资源。服务很快恢复上线。一位同事提出了一个 MR,正确地修改了 Git 中所需的状态,并且得到了队友的批准并合并。如果错误的更改被合并到 Git 中,恢复它们也同样简单,只需回滚相应的提交即可。
图 11.4 演示了一个 GitOps 流程的示例:

图 11.4 – 使用 ArgoCD 和 Kubernetes 的 CI/CD 和 GitOps 示例。
如果你决定在你的团队或组织中实施类似的流程,请考虑到 ArgoCD 适用于 GitOps 和 CD,但要实现 CI,你还需要一个额外的工具。比如说,Argo Workflows、Gitlab CI 或类似的工具。
总结一下,GitOps 是一个非常强大的框架,主要关注 CD 和 CI。它允许以非常快的速度交付软件,最大化稳定性,提高生产力,并且通过自动化手动操作为团队释放更多的生产时间。所有配置都是透明的、可审计的,并且可以复审。为了获得最佳结果和最大效率,你还需要实施 CI 来自动化应用程序的测试和构建。
最后,让我们更详细地讨论一下 IaC。
基础设施即代码(IaC)
IaC 是通过定义文件以声明性方式管理和配置基础设施的实践。你在定义文件中描述所需的状态,并让工具将配置应用到你的环境中。
不用担心,尽管出现了Code这个词,但这并不意味着你需要学习C++、Java或其他高级编程语言。大多数基础设施即代码(IaC)工具允许使用YAML或类似的可读性高的标记语言来定义配置。
IaC 自动化并替代了基础设施配置和供应的手动操作。从本质上讲,所有可以通过云提供商的 UI 或 CLI 手动完成的操作,都可以通过 IaC 工具来实现。你无需每次都通过控制面板点击来配置和供应虚拟机,而是可以在 IaC 中描述你所需的虚拟机数量和类型,并将该配置保存在 Git 或其他版本控制中。通过调用带有所需配置的 IaC 工具,它将与云提供商的 API 建立通信,并创建所描述的资源。如果资源之前已经存在并且配置正确,IaC 工具则不会进行进一步的更改。IaC 可以供应的资源包括虚拟机、负载均衡器、网络附加存储、防火墙规则、对象存储桶等更多资源。
今天,Terraform 是市场上最受欢迎的 IaC 工具之一。它支持主要的公有云和私有云提供商以及他们大多数的 IaaS/PaaS/SaaS/FaaS 服务。
Terraform 接受 JSON 格式以及它自己的 .tf 定义文件格式,下面是一个小示例,帮助你了解它的格式:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "eu-central-1"
}
resource "aws_instance" "kcna_demo_vm" {
ami = "ami-051835d754b74795c"
instance_type = „t3.medium"
tags = {
Name = "KCNAExampleInstance"
}
}
如果应用该定义文件,它将在 AWS 公共云的 eu-central-1 区域中供应一个 t3.medium 类型的虚拟机,并标记为 KCNAExampleInstance。该虚拟机也可以在其他定义文件中再次引用,例如,如果你想附加一个存储块设备到该虚拟机上。
对于 KCNA 的范围,你不需要记住 .tf 格式的具体细节,但你需要记住 IaC 的优势,这些已经在前面一节中提到过,因为 IaC 是 GitOps 的关键部分:
-
更快的设置和可复现的结果。
-
可读且可审查的配置,可以存储在 VCS 中。
-
通过定义期望状态来消除基础设施漂移。
值得一提的是,一些云提供商有自己的、特定于厂商的 IaC 工具,这些工具可能会为其云服务提供更好的支持,但显然不会与其他云提供商兼容。CloudFormation——由 AWS 专为 AWS 开发的 IaC 就是一个这样的例子。
如果你还记得我们在 第五章 和 第六章 中做的练习,我们基本上是用 IaC 配置了 Kubernetes。Kubernetes 使用声明式资源定义来描述工作负载,但也可以用于供应负载均衡器(使用 LoadBalancer 服务类型)和持久存储(作为 PV 和 PVC)。当你在公共云环境中运行它并应用规范时,Kubernetes 云控制器管理器将与提供商 API 进行交互,确保资源被供应并处于期望的状态。
总结
在本章中,我们学习了云原生应用程序的交付。现代应用程序需要 CI/CD 来自动化所有软件交付阶段。
在 CI 集中于构建-测试-发布阶段时,持续交付则专注于将软件打包成容器镜像或可安装的软件包,并分别上传到容器注册表或软件包仓库中。
另一个 CD——持续部署专注于将经过测试和打包的发布自动部署到各个实时环境中。
接下来,我们已经看到GitOps如何在CI/CD的基础上发展,添加了可审查的合并(或拉取)请求,并与 IaC 结合使用。这导致了一个最终的、高度自动化且强大的流程,尤其在 Kubernetes 环境中表现出色。帮助实现 GitOps 的一些流行工具包括ArgoCD、FluxCD和Jenkins X。
IaC(基础设施即代码)是通过定义文件以声明式方式管理和配置基础设施的实践。使用 IaC 可以消除基础设施漂移,并使基础设施配置可审查且可重现。Terraform 是当前最受欢迎的 IaC 工具之一。
采用 IaC 和 GitOps 意味着不允许手动更改、不允许手动部署或实时重新配置。整个团队应当了解并遵循该流程。Git 仓库充当真实来源,CI/CD 系统确保达到并维持这一预期状态。
至此,我们已经完成了最后一个技术章节!祝贺你,做得好!在接下来的两章中,你将找到一份模拟考试,以评估你的准备情况,以及如何通过考试的提示和考试后的步骤。
问题
正确答案可以在 __ 待定 __ 处找到
-
CI 代表什么?
-
持续交互
-
持续改进
-
持续集成
-
持续检查
-
-
以下哪些阶段是 CI 自动化的重点(可多选)?
-
发布
-
构建,测试
-
部署
-
打包
-
-
以下哪些定义是正确的 CD 定义(可多选)?
-
持续调试
-
持续交付
-
持续销毁
-
持续部署
-
-
以下哪些被视为 CI/CD 实践?
-
整个团队一起进行频繁的大规模发布
-
频繁的小规模且完全自动化的发布
-
频繁的小规模、经过充分测试且自动化的发布
-
不频繁的大规模自动化发布
-
-
以下哪些工具适用于 GitOps(可多选)?
-
Jenkins X
-
FluxCD
-
TravisCI
-
ArgoCD
-
-
以下哪些元素是 GitOps 的一部分(可多选)?
-
Kubernetes
-
CI/CD
-
IaC
-
合并(或拉取)请求
-
-
GitOps 使用的是哪个 VCS?
-
Bitbucket
-
Subversion
-
Git
-
Mercurial
-
-
什么是基础设施漂移?
-
当基础设施完全自动化并可以自动驾驶时
-
当由于自动扩展事件导致云中 VM 的数量增加时
-
当 VM 的性能在一天中的不同时间根据需求变化时
-
当实际的基础设施状态与 IaC 定义的预期状态不符时
-
-
团队中已建立的 GitOps 流程不应该做什么?
-
监控系统状态
-
开放(拉取)合并请求
-
实时环境中的手动更改
-
因为测试已经自动化,所以需要审查队友的代码
-
-
通常在 IaC 中定义的内容是什么(请选择多个)?
-
系统架构
-
虚拟机和负载均衡器
-
数据库迁移
-
防火墙规则
-
-
GitOps 的好处是什么(请选择多个)?
-
更快、更稳定的软件交付
-
不再需要监控实时环境
-
由于高度自动化,团队拥有更多自由时间
-
所有 Kubernetes 操作都由 CI/CD 处理
-
-
CI 的好处是什么(请选择多个)?
-
自动部署已测试的代码
-
自动打包已测试的代码
-
没有开发人员的时间花费在运行构建或测试上
-
使用自动化测试进行问题检测
-
-
Release 的正确定义是什么?
-
要交付给用户的软件版本
-
K8s 集群中应用程序的当前状态
-
停止进一步开发的决定
-
完整的 CI/CD 流水线运行
-
-
以下哪项是 CI/CD 工具?
-
CloudFormation
-
Terraform
-
GitlabCI
-
Subversion
-
-
在 GitOps 中,什么通常触发部署?
-
推送到任何 Git 分支
-
基础设施漂移事件
-
已批准并合并的 MR(PR)
-
Git 中的手动 rebase
-
-
以下哪些关于 GitOps 的说法是正确的(请选择多个)?
-
覆盖手动更改
-
这仅在 Kubernetes 中可行
-
使用声明式配置
-
只能与一些流行的编程语言一起使用
-
-
以下哪个阶段不是 CI/CD 的一部分(请选择多个)?
-
监控
-
构建
-
计划
-
代码
-
进一步阅读
-
语义化版本控制:
semver.org/ -
什么是 GitOps:
www.weave.works/blog/what-is-gitops-really -
ArgoCD:
argo-cd.readthedocs.io/ -
ArgoCD 示例:
github.com/argoproj/argocd-example-apps -
FluxCD:
fluxcd.io/ -
GitLab 用于 CI/CD:
docs.gitlab.com/ee/ci/ -
Terraform:
www.terraform.io/
第五部分:KCNA 考试和后续步骤
在这一结束部分,您将找到考试准备和通过的技巧,以及两个模拟考试来验证您的知识。最后,我们将讨论获得认证后,如何启动或推进您的云原生职业生涯。
这一部分包含以下章节:
-
第十二章, 通过模拟试卷为 KCNA 考试做准备
-
第十三章, 前进的道路
第十二章:通过模拟试卷进行 KCNA 考试的练习
在本章中,您将找到一些关于如何做好最后准备并应对Kubernetes 和云原生关联(KCNA)考试的技巧。接着是两个模拟考试,让您可以测试自己的知识,并确保自己准备好参加正式考试。
关于 KCNA 考试
正如您在第二章中可能记得的,KCNA 是一个入门级考试,涵盖kubectl命令、Kubernetes 资源、架构和特性。考试的几乎一半(46%)内容是关于 Kubernetes 的,如果您没有进行本书中的任何实操练习,或者没有任何 K8s 的先前经验,不建议尝试此考试。
与 CKA、CKAD 和 CKS 不同,KCNA 更为广泛,测试了其他领域和能力,包括云原生和微服务架构、持续集成/持续部署(CI/CD)、可观察性等。然而,即使您没有使用 Prometheus、Jaeger、Argo CD 或 Helm 的实际经验,您也能根据前几章中呈现的理论通过考试。
考试本身是在线进行的,远程监考,您可以在全球任何地方参加,只要网络连接可靠。远程监考意味着您需要配备网络摄像头和麦克风,并且在整个考试期间,您和您的桌面将被录制。禁止使用多屏显示,因此请准备仅使用一个屏幕。
如果您的网络连接中断,您可以重新加入考试,但由于计时器仍会继续,您将会失去时间。您不得在公共场所(如咖啡馆)或有其他人在场的房间内参加考试,并且在考试期间不允许进食。允许从透明容器中饮用水。
注意事项
不要作弊或留下任何笔记和提示,因为这样会导致考试被终止!您的周围环境将通过网络摄像头进行检查!
有关定价和最新信息,请参阅官方来源:
在那里,您可以找到候选人手册、考试安排说明和常见组织问题的答案。
注意事项
在安排考试后,请确保满足系统要求,并完成检查清单,以便参加考试。请提前完成,因为迟到将被视为缺考!
考试当天请确保携带有效的政府颁发的带有照片的身份证,并且带有电话——这一点将在考试开始时进行验证。
准备考试的技巧
如果你已经阅读了所有章节并完成了章节末的所有习题,那么推荐的做法是尝试这里的模拟考试,同时记录所用时间。你将有 90 分钟来完成 60 道题目,这意味着每道题目约有 1.5 分钟的时间。
之后,计算你的得分:
-
如果模拟考试得分达到 80%或以上,你应该已经准备好参加真正的 KCNA 考试。赶紧安排考试时间吧!
-
如果得分接近 75%,请回去检查你出错的章节或至少总结一下相关主题的内容。做完章节末的习题后,过一两天再进行一次模拟考试。
KCNA 的及格分数是 75%,如果你在模拟考试中的得分明显低于此分数,意味着你需要更多的准备。
就在考试前,确保你得到充足的睡眠,并进行一些除了 KCNA 准备之外的活动。不要熬夜复习,因为这可能会对考试当天的表现产生负面影响。如果你感到压力和焦虑,试着调整你的心态。万一出现问题,你还有一次完全免费的重考机会!既然第二次考试已经包含在价格里,为什么还要给自己增添压力呢?但如果你仔细阅读了本书,你可能根本不需要第二次尝试。
关于如何解答考试题目的一些建议。近年来,我参加了 10 多场专业认证考试,并且都在第一次尝试时通过了,除了一个。以下是我通常的做法:
-
仔细阅读每道题目。
-
如果你不知道答案或不确定,标记该题并继续下一题。
-
切勿在某一道题目上花费过多时间,因为你可能没有足够的时间完成所有题目。
-
只有在完成所有题目后,再回来看标记的题目。
现在,是时候开始模拟考试了。准备好纸和笔,设定 90 分钟计时器,开始吧!
注意
这里的模拟考试题目考察的领域和能力与真实考试相同,但这些并不是来自真实的 KCNA 考试!发布或公开真实的考试题目违反了你在考试前签署的保密协议。绝对不要这么做!
模拟考试 A
正确答案可以在评估部分的附录中找到。
注意
除非另有说明,否则每道题只有一个正确答案。
-
你计划在你的组织中部署一个 Kubernetes 集群。要求使控制平面具有高可用性。应该部署多少个控制平面节点?
-
1
-
2
-
3
-
4
-
-
你正在生产环境中操作一个 Kubernetes 集群,节点运行在多个故障域(也叫可用区,或AZs)中。你需要确保 Pod 在预定义的故障域中启动。可以使用哪个 K8s 功能?
-
PVC
-
Pod 拓扑分布约束
-
Pod 网络策略
-
Pod 安全策略
-
-
你被要求将一个新应用程序部署到 Kubernetes 集群中。你需要实施哪个检查来持续确保该应用程序健康运行?
-
Prometheus 探针
-
容器运行时检查
-
调和检查
-
存活性和就绪性探针
-
-
以下哪个
kubectl命令可用于获取名为microservice-a的部署的详细信息?-
kubectl getpod microservice-a -
kubectl describepod microservice-a -
kubectl explaindeployment microservice-a -
kubectl describedeployment microservice-a
-
-
你正在操作一个 Kubernetes 集群,并收到了一个请求,要在容器中运行一个新应用程序,该应用程序将驻留在其自己的 虚拟机 (VMs) 中,与其他工作负载隔离。哪项技术可以帮助你实现这一目标?
-
命名空间容器运行时
-
虚拟化容器运行时
-
沙箱容器运行时
-
隔离容器运行时
-
-
你正在将一个新的无状态微服务应用程序部署到 Kubernetes 集群中。该应用程序必须在集群节点发生故障时自动恢复,并且应该定期滚动更新。哪个 K8s 资源最适合这种情况?
-
StatelessSet
-
StatefulSet
-
副本集
-
部署
-
-
以下哪项是 Kubernetes 中最小的可调度工作负载单元?
-
部署
-
容器
-
Pod
-
服务
-
-
你正在将一个新的有状态应用程序部署到 Kubernetes 集群中,且该应用程序必须在磁盘上持久化数据。哪个 Kubernetes 控制器最适合这种情况?
-
副本集
-
部署
-
StatelessSet
-
StatefulSet
-
-
你正在操作多个 Kubernetes 集群,并且为了实现 GitOps,你已经开始评估几个工具。以下哪个工具适合 GitOps?
-
Argo CD
-
GitLab CI
-
Prometheus
-
Travis CI
-
-
你正在管理一个运行在大型公共云中的 Kubernetes 集群。需求经常波动,你需要自动化集群大小的变更,在需求高时增加工作节点,在负载低时减少节点。你会使用哪项技术?
-
自我修复
-
水平 Pod 自动伸缩器 (HPA)
-
垂直 Pod 自动伸缩器 (VPA)
-
集群 自动伸缩器 (CA)
-
-
你正在调试一个运行在 Kubernetes 中的应用程序,需要获取刚刚终止的名为
microservice-b的 Pod 的日志。哪个kubectl命令可以实现这一操作?-
kubectl getlogs microservice-b -
kubectl logsmicroservice-b --previous -
kubectllogs microservice-b -
kubectl get logsmicroservice-b --previous
-
-
你正在调试 Kubernetes 中的应用程序,需要检查名为
microservice-c的 Pod 中名为app的容器的日志。你可以使用哪个kubectl命令?-
kubectllogs microservice-c -
kubectl get logsmicroservice-c app -
kubectl getlogs microservice-c -
kubectl logs microservice-c -``c app
-
-
你正在管理一个 Kubernetes 集群,需要在每个节点上运行一个日志代理,将日志发送到集中式日志系统。可以使用哪个 Kubernetes 资源控制器来管理这个日志代理?
-
DaemonSet
-
部署
-
StatefulSet
-
ReplicaSet
-
-
以下哪个是 Kubernetes 控制器背后的主要操作概念?
-
就绪探针
-
无服务器
-
调解循环
-
滚动更新
-
-
以下哪些项目可以用来实现你自己的无服务器平台?选择两个。
-
Knative
-
Prometheus
-
OpenFaaS
-
Jaeger
-
-
以下哪些技术可以用来在 Kubernetes 中操作虚拟化容器运行时?
-
containerd
-
gVisor
-
Kata
-
Docker
-
-
你被要求部署一个应用程序到 Kubernetes,该应用程序需要一个小的辅助服务与主应用程序一起运行。该辅助服务应与主应用程序共享磁盘上的数据。可以使用哪些资源?
-
两个 Pod 与网络策略
-
两个 Pod 与亲和性策略
-
带有单个容器的 Pod
-
带有多个容器的 Pod
-
-
你被要求帮助迁移到 Kubernetes 的应用程序。该应用程序无法将日志输出到标准输出(stdout)或标准错误(stderr)。可以做些什么来将其日志发送到中央日志聚合系统?
-
没什么—应该要求供应商首先实现该功能
-
应用程序应运行在带有持久卷(PV)附加的 Pod 内,用于存储日志
-
应该在所有节点上作为 DaemonSet 运行一个日志代理
-
应该在 Pod 中的应用程序旁边使用日志侧车容器
-
-
以下哪种格式今天通常用于应用程序日志?
-
CSV
-
JSON
-
YAML
-
SQL
-
-
作为你组织中的流程改进的一部分,你需要为多个开发团队自动化、测试和构建程序。如果没有必要自动部署更改到各种环境,哪些工具和流程会有所帮助?
-
跟踪
-
GitOps
-
CD
-
CI/CD
-
-
以下哪些项目可以用来实现服务网格?选择两个:
-
Linkerd
-
containerd
-
Istio
-
dockerd
-
-
你刚刚为一个新项目部署了一个专用的 Kubernetes 集群,并且已经可以访问其他几个集群。为了从工作站访问额外的 Kubernetes API,需要做些什么?
-
向
kubecon文件添加新凭证 -
向
ssh-config文件添加新凭证 -
向
kube-proxy文件添加新凭证 -
向
kubeconfig文件添加新凭证
-
-
你被要求允许多个开发团队使用同一个 Kubernetes 集群。可以采取哪些措施来逻辑上将他们的工作负载分开?
-
让每个团队为其启动的每个 Pod 分配自己的标签
-
为每个团队创建一个命名空间
-
使用亲和性规则将每个团队的 Pod 调度到特定节点
-
使用虚拟化容器运行时,如 Kata
-
-
你的开发团队正在为它正在开发的一个新微服务添加遥测工具。它正在询问应在哪个端点暴露 Prometheus 兼容的指标以供收集。以下哪项是正确的选择?
-
/``metrics -
/``metric -
/``prometheus -
/``collect
-
-
云原生安全的四个 C 是什么?
-
代码,提交,遵守,云
-
代码,容器,集群,云
-
代码,提交,集群,云
-
代码,容器,提交,共享
-
-
你被分配设计一个 Kubernetes 设置,其中所有 Pod 到 Pod 的流量都将被加密。以下哪种解决方案适用?
-
使用集群网络策略
-
部署服务网格到集群
-
使用 RBAC
-
要求开发人员为集群中的每个应用添加 TLS
-
-
在 Kubernetes 中,服务网格的一个组件必须是所有工作负载的一部分,是什么?
-
每个 Pod 前面都有一个 LoadBalancer 类型的服务
-
密钥注入到所有 Pod
-
配置映射注入到所有 Pod
-
Sidecar 容器注入到所有 Pod
-
-
以下哪些是 开放容器倡议 (OCI) 规范的一部分?选择所有适用项:
-
镜像规范
-
运行时规范
-
分发规范
-
Pod 规范
-
-
以下哪些是 CNCF 组织的项目的正确成熟度级别?
-
沙盒,孵化中,已通过
-
沙盒,孵化中,已毕业
-
沙盒化,命名空间化,虚拟化
-
Alpha,Beta,发布
-
-
你被要求部署一个追踪工具来分析一个复杂的微服务架构应用程序。以下哪项可能是一个选项?
-
Prometheus
-
Traefik
-
Knative
-
Jaeger
-
-
以下哪些是高度可靠系统的特性?选择所有正确答案。
-
流量加密
-
最新内核版本
-
期望状态
-
自愈
-
-
以下哪项技术是 Kubernetes 默认服务发现机制的核心?
-
Iptables
-
DNS
-
SSL
-
DHCP
-
-
你被要求为一个运行在 Kubernetes 上的应用实现自动扩展(根据负载)。以下哪些工具将有帮助?
-
CA
-
部署 自动扩展器 (DA)
-
水平节点 自动扩展器 (HNA)
-
HPA
-
-
一家公司正在寻找一位了解整个应用生命周期并将安全性作为其核心部分的专业人才。公司应该寻找以下哪种职位?
-
系统管理员
-
DevSecOps 工程师
-
云解决方案架构师
-
数据科学家
-
-
以下哪些是有效的容器运行时?选择所有正确答案:
-
Xen
-
Kubernetes
-
gVisor (
runsc) -
containerd
-
-
有哪些方式可以扩展 Kubernetes API,添加新特性?请选择两个:
-
代码资源定义
-
自定义资源定义
-
扩展层
-
聚合层
-
-
你已经为一个新的应用程序准备了一个声明式 Kubernetes 规范文件。该文件命名为
microservice-d.yaml。你将如何使用kubectl将其部署到集群?-
kubectl deploy -``f microservice-d.yaml -
kubectl apply -``f microservice-d.yaml -
kubectl run -``f microservice-d.yaml -
kubectl exec -``f microservice-d.yaml
-
-
您的团队正在评估 Kubernetes 作为容器编排工具,并希望了解以下哪些功能是开箱即用的。选择两个:
-
请求追踪
-
IP 地址管理(IPAM)
-
服务发现
-
完全流量加密
-
-
以下哪项被认为是良好的 CI/CD 实践?
-
频繁的大规模发布由整个团队一起完成
-
频繁、小规模且完全自动化的发布
-
不频繁、大规模且自动化的发布
-
频繁的小规模、经过充分测试且自动化的发布
-
-
使用 Kubernetes 控制器(如 Deployment 或 StatefulSet)与静态定义简单 Pods 相比,有哪些优势?
-
控制的 Pods 启动和运行速度更快
-
它们允许自愈和滚动更新
-
它们允许定义亲和性设置
-
它们允许通过服务暴露 Pods
-
-
以下哪项是关于无服务器架构(Serverless)的有效说法?
-
它仅在公共云中可用
-
它仅与 Kubernetes 配合使用
-
它不使用任何服务器硬件
-
它将所有服务器管理操作抽象化
-
-
团队负责人要求您帮助优化开发过程。开发人员花费大量时间自己测试和构建包。您会建议什么?
-
让开发人员安装更新的 IDE
-
实现 CI/CD 流水线
-
将应用程序迁移到 Kubernetes
-
将应用程序迁移到大型裸金属服务器
-
-
一个旧应用无法轻易容器化,必须继续在虚拟机中运行,但通过 Kubernetes 管理。以下哪个项目允许我们将 Kubernetes 扩展到容器编排之外,还能管理虚拟机?
-
KubeVirt
-
Kubeless
-
Swarm
-
Istio
-
-
以下哪种格式通常用于编写 Kubernetes 配置文件?
-
CSV
-
YAML
-
HTML
-
XML
-
-
以下哪些元素是 GitOps 的一部分?选择两个:
-
Kubernetes
-
基础设施即代码(IaC)
-
Jenkins
-
合并(拉取)请求
-
-
以下哪些是有效的遥测信号?
-
日志、度量、追踪
-
日志、ping、轨迹
-
日志、元数据、追踪
-
日志、测量、追踪
-
-
在 Kubernetes 环境中,集群级别的日志记录意味着什么?
-
当集群中每个节点的日志存储在控制平面节点的
/``var/log路径下时 -
当所有容器的日志都发送到与 K8s 生命周期无关的独立后端时
-
当每个 Pod 都有自己的日志配置和独立的日志存储位置时
-
当所有集群事件都记录到单独的日志文件中
-
-
您正在故障排除一个表现异常的应用,并决定启用最大日志详细度以获取尽可能多的细节。对应的日志级别是什么?
-
CRITICAL -
WARNING -
INFO -
DEBUG
-
-
您被要求为一个新应用配置自动扩展。哪种遥测信号类别最适合?
-
度量
-
应用日志
-
Kubernetes 事件
-
追踪
-
-
您的组织已启动成本优化计划,并希望减少每月的云账单。以下哪项建议最合适?
-
将关键工作负载切换到抢占实例
-
降低日志级别以节省存储空间
-
根据负载实现自动扩展
-
从微服务迁移到单体架构
-
-
是什么使得查询过去的 Prometheus 度量指标成为可能?
-
警报管理器
-
时间序列数据库(TSDB)
-
PVC
-
Graphite
-
-
以下哪个 CNCF 机构负责批准新的 CNCF 项目并对现有项目进行对齐?
-
董事会
-
最终用户社区
-
治理 委员会 (GB)
-
技术监督 委员会 (TOC)
-
-
以下哪项是 Prometheus 度量指标的格式?
-
跟踪
-
JSON
-
时间序列
-
跨度
-
-
在拥有既定 GitOps 流程的团队中,以下哪项绝对不应执行?
-
打开(拉取)合并请求
-
生产环境中的手动更改
-
审查队友的代码
-
监控系统状态
-
-
你的团队正在运行一个使用 containerd 运行时的 Kubernetes 集群。为什么这会成为一个对新应用程序(具有严格安全要求)的担忧?
-
containerd 不是最快的运行时,可能成为瓶颈
-
containerd 不支持网络策略
-
containerd 依赖共享内核
-
containerd 不支持 Pod 安全策略
-
-
与虚拟机相比,容器有哪些优势?选择两个:
-
容器消耗的资源比虚拟机少
-
容器比虚拟机更安全
-
容器启动时间更短
-
容器不需要操作系统更新
-
-
每年发布多少次新的 Kubernetes 版本?
-
1
-
2
-
3
-
5
-
-
以下哪个允许我们在 Kubernetes 中使用不同的容器运行时?
-
CSI
-
CNI
-
SMI
-
CRI
-
-
有人说 Kubernetes 有一个声明式的 API。这意味着什么?
-
我们始终需要声明一个 YAML 规范文件才能使用 K8s API
-
我们声明期望的状态,K8s 会一次性实现它
-
我们声明期望的状态,K8s 会不断尝试实现它
-
我们明确指示 Kubernetes 如何处理哪些资源
-
-
以下哪个容器运行时在主机内核与容器之间添加了一个中间内核层,将容器沙箱化?
-
containerd
-
gVisor
-
Kata
-
dockerd
-
就是这样!你能在 90 分钟内完成吗?希望你能做到。现在,检查正确答案并根据以下简单公式计算你的得分:

如果你对结果不满意,或者想要更有信心,可以阅读更多你犯错的主题,然后再做一次模拟考试 B。
确保在尝试之间休息一下。有时,最好休息一天甚至两天,让一切沉淀下来。祝你好运!
模拟考试 B
正确答案可以在评估部分的附录中找到。
-
以下哪些是有效的遥测信号?
-
测量、跟踪、日志
-
Ping、跟踪、日志
-
元数据、跟踪、日志
-
度量、跟踪、日志
-
-
你的组织正在运行 Kubernetes,开发团队问是否可以使用它运行无服务器应用程序。以下哪些项目可以用来在 Kubernetes 上实现你自己的无服务器平台?请选择两个:
-
Knative
-
OpenFaaS
-
KubeVirt
-
KubeConf
-
-
你正在规划组织中的 Kubernetes 集群的生产部署。控制平面应该是高可用的。应该部署多少个控制平面节点?
-
2
-
4
-
3
-
1
-
-
以下哪个是 Kubernetes 中最小的、可独立调度的工作负载单元?
-
Pod
-
容器
-
部署
-
服务
-
-
你已经为一个新应用准备了 Kubernetes 规范文件。该文件名为
kcna-microservice.yaml。你将如何使用kubectl将其部署到集群?-
kubectl deploy -``f kcna-microservice.yaml -
kubectl apply -``f kcna-microservice.yaml -
kubectl run -``f kcna-microservice.yaml -
kubectl exec -``f kcna-microservice.yaml
-
-
你组织中的某些应用程序不能轻易地容器化,必须继续在虚拟机中运行,但要通过 Kubernetes 管理。以下哪个项目允许我们扩展 Kubernetes 来管理虚拟机?
-
KubeVirt
-
Kubeless
-
Swarm
-
Knative
-
-
在 Kubernetes 中,应用程序会根据一天中的不同时间承受不同的负载。你被要求为其实现自动扩展,以适应负载波动。以下哪个工具可以提供帮助?
-
CA
-
HPA
-
HNA
-
DA
-
-
应用程序出现问题,你被要求找出根本原因。你决定启用最大日志详细程度。以下哪个日志级别提供最多的细节?
-
INFO -
WARNING -
ERROR -
DEBUG
-
-
以下哪些是 CNCF 策划的项目的成熟度级别?
-
沙箱、孵化中、已完成
-
沙箱、孵化中、已毕业
-
沙箱、命名空间、已毕业
-
Alpha、Beta、Release
-
-
以下哪些类型的节点是 Kubernetes 的组成部分?
-
从属节点和工作节点
-
控制平面节点和工作节点
-
控制平面节点和从属节点
-
主节点和从节点
-
-
哪项技术支撑着 Kubernetes 的默认服务发现机制?
-
Iptables
-
SSL
-
DNS
-
DHCP
-
-
你被要求部署一个追踪工具来分析一个基于分布式微服务的应用。以下哪些选项可以考虑?请选择两个:
-
Zipkin
-
Traefik
-
Prometheus
-
Jaeger
-
-
你正在调试 Kubernetes 中的应用程序,需要检查名为
kcna的microservicePod 中容器的日志。如何使用kubectl来查看?-
kubectllogs microservice -
kubectl get logs microservice -``c kcna -
kubectl get logsmicroservice kcna -
kubectl logs microservice -``c kcna
-
-
以下哪些是 OCI 规范的一部分?请选择所有适用项:
-
Kubernetes 规范
-
运行时规范
-
分发规范
-
镜像规范
-
-
你的团队正在评估 Kubernetes 用于容器编排,并希望了解默认提供的网络相关功能。请选择两个:
-
请求追踪
-
IPAM
-
服务发现
-
全流量加密
-
-
以下哪些 Kubernetes 资源允许我们在节点故障时恢复(自愈)应用程序?选择两个:
-
部署
-
Pod
-
StatefulSet
-
服务
-
-
您被要求在 Kubernetes 中部署一个新应用程序。哪个检查功能有助于不断确保应用程序正常运行且健康?
-
部署探测
-
容器运行时检查
-
和解检查
-
存活探测和就绪探测
-
-
安装了
kubectl版本 1.24 后,您可以管理哪些 Kubernetes 集群版本?选择所有适用的选项:-
1.24
-
1.25
-
1.23
-
1.21
-
-
Kubernetes 中所有工作负载必须成为服务网格的哪个关键元素的一部分?
-
在每个 Pod 前部署负载均衡器服务
-
将代理配置注入到所有 Pod 中
-
Service ConfigMap 注入到所有 Pod 中
-
将 Sidecar 容器注入到所有 Pod 中
-
-
以下哪个 Kubernetes 组件用于存储有关集群及其状态的信息?
-
etcd -
kubelet -
kube-store -
PVC
-
-
以下 CNCF 机构负责批准新的 CNCF 项目并对现有项目进行调整?
-
TOC
-
终端用户社区
-
GB
-
董事会
-
-
您收到了一个新的基于微服务的应用程序的安全要求,不应在具有共享内核的主机上运行。以下哪个可能是解决方案?
-
使用 Docker
-
使用命名空间容器运行时
-
使用虚拟化容器运行时
-
使用 Pod 安全策略
-
-
您在云环境中运行一个分布在多个可用区的 Kubernetes 集群。您需要确保应用程序 Pod 在所有可用区运行。可以使用哪个 K8s 特性?
-
StatefulSet
-
Pod 拓扑传播约束
-
Pod 网络策略
-
Pod 可用性策略
-
-
以下哪个
kubectl命令可以用来获取有关名为microservice-kcna的部署的详细信息?-
kubectl getpod microservice-kcna -
kubectl describepod microservice-kcna -
kubectl describedeployment microservice-kcna -
kubectl explaindeployment microservice-kcna
-
-
以下哪个是有效的容器运行时?选择所有正确答案:
-
KVM
-
Kubernetes
-
gVisor(
runsc) -
containerd
-
-
以下哪个最能描述高度弹性的应用程序和基础架构?
-
在出现问题时能够自动关闭的能力
-
自动从大多数故障中恢复的能力
-
在故障发生时保持状态的能力
-
能够执行滚动更新的功能
-
-
您正在审查新的 Kubernetes 版本的发布说明,结果发现您正在使用的资源之一已被弃用。弃用资源要被移除需要多长时间?
-
大约 2 年
-
4 个月
-
2 个月
-
6 个月
-
-
您的开发团队正在为正在开发的新微服务添加与 Prometheus 兼容的遥测仪器。Prometheus 默认会抓取哪个端点?
-
/``metrics -
/``metric -
/``prometheus -
/``collect
-
-
你正在部署一个应用程序,该应用程序缺少原生支持 Prometheus 指标暴露。在这种情况下,以下哪项可以帮助我们收集指标?
-
在 Kubernetes 中运行应用程序
-
安装
Pushgateway -
安装
Alertmanager -
为应用程序安装 Prometheus Exporter
-
-
云原生安全的四个 C 是什么?
-
代码、提交、合规、云
-
代码、容器、集群、共置
-
代码、协作、集群、云
-
代码、容器、集群、云
-
-
以下哪个 Kubernetes 组件用于下载容器镜像并启动容器?
-
kubelet -
容器运行时
-
etcd -
kube-scheduler
-
-
以下关于 Serverless 的说法哪个是正确的?
-
它仅在云中可用
-
它仅与 Kubernetes 一起使用
-
它完全不使用服务器硬件
-
它将所有服务器管理操作抽象化
-
-
以下哪个是 Serverless 应用程序中最小的部分?
-
Gateway
-
函数
-
提交
-
容器
-
-
你刚刚获得了访问 Kubernetes 集群的凭据,并希望查看有哪些命名空间。以下哪个
kubectl命令可以列出集群中的所有命名空间?-
kubectl listnamespaces --all-namespaces -
kubectlshow namespaces -
kubectlget namespaces -
kubectl listall namespaces
-
-
当 Kubernetes 调度器无法将 Pod 分配到节点时会发生什么?
-
它将停留在
Pending状态 -
它将强制在控制平面节点之一上运行
-
它将停留在
NotScheduled状态 -
它将在五次调度尝试后消失。
-
-
你正在为一个具有严格安全要求的微服务架构应用程序寻找解决方案。所有 Pod 之间的网络通信必须加密。以下哪项是合适的选择?
-
部署服务网格
-
强制执行 K8s 安全策略
-
设置 Kubernetes RBAC
-
使用 K8s 网络策略
-
-
以下哪种格式通常用于应用程序日志?
-
CSV
-
SSL
-
YAML
-
JSON
-
-
你被要求评估服务网格解决方案。以下哪个项目可以使用?请选择两个:
-
Linkerd
-
Swarm
-
Istio
-
Traefik
-
-
你正在管理一个 Kubernetes 集群,需要在每个节点上运行日志代理,将日志发送到集中式日志系统进行存储和处理。以下哪个 Kubernetes 资源控制器最合适?
-
DaemonSet
-
Deployment
-
StatefulSet
-
ReplicaSet
-
-
你正在调试一个在 Kubernetes 上运行的应用程序,需要获取刚刚终止的名为
microservice-kcna的 Pod 的日志。以下哪个kubectl命令可以帮助你做到这一点?-
kubectl get logsmicroservice-kcna -p -
kubectl logsmicroservice-kcna -p -
kubectllogs microservice-kcna -
kubectl get logsmicroservice-kcna –previous
-
-
你正在阅读最佳实践指南,其中推荐在 Kubernetes 上设置集群级日志。这到底对日志存储意味着什么?
-
K8s 需要独立的日志收集和聚合系统
-
K8s 会在控制平面节点上汇总所有集群日志。
-
K8s 自带日志存储和聚合解决方案
-
K8s 仅存储最重要的集群健康日志
-
-
以下哪个 Kubernetes 规范设置配置了 Deployment 管理的 Pod 数量?
-
podnum
-
Replicas
-
容器
-
实例
-
-
你正在评估不同的工具,以便为开发团队构建 CI/CD 管道,自动化构建-测试-发布流程。以下哪些可以考虑?选择两个:
-
Prometheus
-
Jenkins
-
Linkerd
-
GitLab CI
-
-
一位同事分享了访问他们刚部署的新 Kubernetes 集群的凭证。你需要做什么才能从工作站访问其 API?
-
将新凭证添加到
kubecon文件中 -
将新凭证添加到
kubernetes-conf文件中 -
将新凭证添加到
kube-proxy文件中 -
将新凭证添加到
kubeconfig文件中
-
-
你正在准备在 Kubernetes 集群中部署一个新应用,并需要为其提供非默认的配置文件。以下哪种 K8s 资源适合存储和注入容器的通用配置?
-
SettingMap
-
ConfigMap
-
PV
-
Ingress
-
-
你正在评估将微服务应用部署到 Kubernetes 集群中的选项。该应用必须能够从单个 K8s 节点故障中自动恢复,并应通过滚动更新进行更新。该应用不需要本地存储数据。哪种 K8s 资源最适合这种情况?
-
StatelessSet
-
StatefulSet
-
ReplicaSet
-
Deployment
-
-
你的团队在公共云上运行 Kubernetes,由于需求波动,希望根据当前需求动态地添加和移除集群节点。以下哪项可以帮助你实现这一目标?
-
K8s 节点自动扩展器
-
K8s CA
-
K8s HPA
-
K8s VPA
-
-
以下哪种 Kubernetes 集群配置适用于高度可用的基础设施设置?
-
3 个控制平面和 10 个工作节点
-
1 个控制平面和 10 个工作节点
-
2 个控制平面和 10 个工作节点
-
3 个控制平面和 1 个工作节点
-
-
以下关于容器和虚拟机的说法正确的是?选择两个:
-
应用程序易于打包到容器中
-
应用程序易于打包到虚拟机中
-
容器镜像易于共享
-
虚拟机镜像体积小
-
-
一个开发团队已联系并请求帮助,提升其工作流程并提高开发人员生产力。你可以推荐以下哪些选项?
-
部署服务网格
-
切换到 Go 或 Python 等其他语言
-
构建 CI/CD 管道
-
迁移到更好的云提供商
-
-
以下哪项允许直接对应用程序进行仪器化,以提供 Prometheus 格式的指标?
-
K8s 服务发现
-
客户端库
-
Exporters
-
Pushgateway
-
-
几个部门要求在公司 Kubernetes 集群中创建一个独立的分区,并为其分配自己的用户和配额。以下哪项可以帮助实现?
-
命名空间运行时
-
共享运行时
-
Pod 配额策略
-
Kubernetes 命名空间
-
-
开发团队计划很快部署一个新应用程序,要求你为其配置自动扩展。哪类遥测最适合用于自动扩展决策?
-
指标
-
跟踪
-
Ping
-
日志
-
-
最近你们团队新加入了一位同事,他们遵循 GitOps 工作流程。以下哪项是他们不应该做的,因为它违反了既定的 GitOps 过程?
-
审核任何拉取请求或合并请求
-
手动更改环境
-
使用声明式规范将工作负载部署到 Kubernetes
-
响应生产环境中产生的任何警报
-
-
以下哪项最能描述水平扩展?
-
向同一服务实例添加更多 CPU
-
向同一服务实例添加更多内存
-
向同一服务添加更多副本/实例
-
在服务实例前添加一个额外的负载均衡器
-
-
开发团队要求自动化应用程序的测试、构建和发布,但软件不应该自动部署到任何环境中。以下哪项可以提供帮助?请选择两项:
-
GitOps
-
Flux CD
-
CI/CD 流水线
-
Jenkins
-
-
以下哪项特征描述了无服务器计算?请选择两项:
-
服务器不再需要
-
它支持所有编程语言
-
它是基于事件的
-
供应商负责服务器管理
-
-
为什么使用 Kubernetes 资源控制器(如 Deployments)是部署工作负载的首选方式?
-
它们让工作负载运行得更快
-
它们增加了自愈、扩展和滚动更新功能
-
它们优化了 CPU 和内存使用,整体上消耗更少的资源
-
它们允许在不重启 Pods 的情况下更改容器镜像
-
-
Kubernetes 资源控制器背后的主要操作机制是什么?
-
CI/CD
-
无服务器
-
就绪探针
-
协调循环
-
-
开发团队正在努力将遥测功能集成到所有用不同编程语言编写的应用程序中。他们在询问是否有任何开源项目可以帮助。你会建议哪一个?
-
Knative
-
Istio
-
OpenTelemetry
-
Traefik
-
恭喜你完成了第二次模拟考试,做得很好!
希望你的成绩达到 75% 或更高,这将增强你参加真正认证考试的信心。正如你所看到的,许多问题本质上是小场景,你必须找到最佳解决方案。相比章节末尾的问题,一些问题也较为简单,因为许多章节的目的是让你超越 KCNA 合格标准的要求。事实上,对于 KCNA 的范围,你需要理解核心概念,并熟悉工具及其用途。
不要期待非常深奥或复杂的问题,但要期待来自 KCNA 课程所有领域的广泛问题,且大量关注 Kubernetes。
现在,继续安排 KCNA 考试。祝你好运,并希望你也能阅读下一章,也是最后一章,在那里我将分享一些如何在云原生领域继续前进并提升职业生涯的建议。
第十三章:前进的道路
就是这样!恭喜你到达了本书的最后一章!希望这段旅程充满了激动人心的时刻,你也渴望继续学习并向前迈进。
本章我们将讨论一些最后的话题:
-
推进云原生职业生涯
-
为开源做贡献
-
进一步阅读
通过 KCNA 考试并没有本章内容的先决条件,但以下信息将为你提供一些关于成为 KCNA 认证后,接下来职业目标和里程碑的指导。
推进云原生职业生涯
假设你已经参加了考试,并且希望你顺利通过。那么接下来是什么呢?
首先,花点时间庆祝并接受祝贺。将你的证书上传到 LinkedIn,并发布一条关于你成就的动态。建立联系并标记我;我总是乐意为你点赞并听听你的经验!
你可以通过以下链接找到我的 LinkedIn 个人资料:
注意事项
如果你还没有 LinkedIn 个人资料,那就应该立即着手解决这个问题。赶紧创建一个,并与过去和现在的同事建立联系。LinkedIn 是全球最大的职业社交网络,它可以帮助你保持联系、了解行业趋势,甚至可能帮助你找到下一个梦想工作。
无论你在职业生涯的哪个阶段,不管是刚从大学毕业后进入 IT 行业,还是从完全不同的领域转行,记住实践和持续学习是成功的关键。
在本书中,我们在理论上讨论了许多内容,因为 KCNA 本质上是一个理论考试(与 CKA、CKAD、CKS 等不同)。它不仅涉及 Kubernetes 和容器,还有许多其他技术,这些都是追求云原生职业的基础。CI/CD、监控、IaC 和自动化等都需要实践经验才能深入理解这些主题。
显然,在面试时你不会被要求设置一个实际的 Prometheus 导出器或编写警报,但仅仅知道如何在理论上做到这一点是不够的。当然,并不是每个职位都要求你编写 IaC 和 CI/CD 流水线,但如果你瞄准的是 DevOps、SRE 和其他工程类角色,这将是你日常职责的一部分。所以,赶快动手实践我们在理论中涉及的技术,查看教程和快速入门指南,必要时再读一本书。
接下来,我想提一下编程语言对工程职位的实用性。即便是对 Python 等高级编程语言的基本了解也能提供很大帮助。作为一名工程师,你经常会遇到需要自动化小任务和重复操作的情况。记住,手动修改,尤其是在生产和在线环境中,是容易出错并且通常是危险的。因此,掌握一些 Python、Ruby 或 Golang 技能来进行自动化和脚本编写,会让你更高效。而且,不要求你具备深入的知识或编写复杂的数据结构。具备基础技能并熟悉一些标准库就足够了。
如果你在这个行业已经有一段时间,你可能知道 Linux 操作系统家族在云计算和 IT 基础设施领域占据主导地位(超过 90% 的服务器运行 Linux)。自从 Linux 成为大多数服务器工作负载的事实标准操作系统,尤其是在云计算和 Web 应用领域,已经过去了很长时间。如今,Kubernetes 正在成为在云端和本地部署现代应用程序的标准平台。
通过开始学习 Kubernetes 和云原生方法,你已经朝着正确的方向迈出了重要的一步。继续保持良好的工作状态,保持好奇心,跟上趋势和技术的步伐。即使在获得理想的工作后,也要继续学习!IT 是变化最快的行业之一,要成为最顶尖、需求量最大的人才,必须不断发展技能。这就是为什么我个人对云计算、容器、无服务器、Kubernetes 和服务网格之后的下一步充满了兴奋。
接下来,我们将简要讨论开源贡献的重要性。
参与开源项目
如你所知,Kubernetes、Prometheus、Helm 以及 CNCF 策划的 120 多个项目都是开源的。刚开始参与开源项目可能会有挑战,但它是一个有回报的过程,能够帮助你学习、建立新技能,有时甚至能教会他人。
为什么要这样做呢?这里有一些很好的理由:
-
改进软件:即使是最优秀、最成熟的项目也有可能存在漏洞和回归问题,这并不是秘密。如果你遇到错误、意外行为或发现 bug,至少你应该做到以下几点:
-
检查该问题是否在项目社区中已有记录。
-
如果没有,报告它。
-
最后,如果你感觉足够自信,尝试找出根本原因并向源代码提交补丁。
-
这样可以确保整个社区从中受益。
-
提升你的技能:这不仅仅是关于编程和软件开发。用户界面、设计、文档编写和组织管理等都可以应用于开源项目,并且你的贡献很可能会被其他、更有经验的社区成员和项目维护者审查。他们的反馈可能对你的成长非常有价值。
-
结识志同道合的人: 你会发现许多温暖而友好的开源社区,在这里你可以向他人学习,有时甚至在共同项目中找到导师。全球各地的开源项目峰会和会议是一个分享知识并享受乐趣的绝佳方式。
-
强化你的个人简介: 根据定义,所有对开源的贡献都是公开的。在简历中提到这些贡献,让你从人群中脱颖而出,并证明你能够做些什么。
尽管如果你刚开始从事 IT 职业,可能更难开始做贡献,但这样做将帮助你建立技能、学习,并最终获得社区的支持。
注意
记住,即使是小小的贡献也很重要!
事实上,在过去的几年里,我遇到了一些大学生,他们已经向知名开源项目提交了贡献。因此,开始做贡献永远不嫌早!许多项目有所谓的 低垂果实 —— 这些是容易解决的问题或漏洞。这可能是一个很好的起点,帮助你熟悉项目和贡献流程。
总结一下,我鼓励你去探索开源项目和社区,做出贡献,并始终保持学习。再次祝你考试顺利,云原生职业成功!在网上见!
深入阅读
-
为 CNCF 做贡献:
contribute.cncf.io/ -
CNCF 事件:
www.cncf.io/events/ -
CNCF 项目:
www.cncf.io/projects/ -
GitHub 上流行的项目:
github.com/trending
以下是一些关于 Kubernetes 的未来资源:
-
Kubernetes Ingress:
kubernetes.io/docs/concepts/services-networking/ingress/ -
Kubernetes 在生产环境中的应用:
kubernetes.io/docs/setup/production-environment/ -
Kubernetes 常见任务:
kubernetes.io/docs/tasks/
测评
在接下来的页面中,我们将回顾本书中每一章的实践问题,并提供正确答案。
第一章 – 从云到云原生和 Kubernetes
-
B, D
-
C
-
B, D
-
D
-
B, D
-
B, C, D
-
A
-
C
-
A, B, D
-
A
-
A, B
-
C
-
B, C, D
-
A, B, D
-
C
第二章 – CNCF 和 Kubernetes 认证概述
-
A, C, D
-
D
-
B, C, D
-
C
-
A, B, D
-
A, B
-
A, B, C
-
D
-
A, B
-
C
-
C, D
-
B, C
-
A
-
A, B
-
B, C, D
第三章 – 开始使用容器
-
A, C
-
A, C
-
D
-
A, D
-
C, D
-
A, C
-
B, D
-
A, C
-
B, D
-
A
第四章 – 探索容器运行时、接口和服务网格
-
D
-
B, C, D
-
B
-
D
-
D
-
B
-
C
-
C
-
B
-
D
-
A, B, C
-
B
-
A, D
-
B, C
-
A, C
-
A, C
-
B
-
D
第五章 – 使用 Kubernetes 编排容器
-
C
-
B, C, D
-
B, C
-
C, D
-
D
-
D
-
B
-
C
-
B, D
-
D
-
B, D
-
B, D
-
A, D
-
D
-
B
-
C
-
C
-
B
-
C
-
C
-
C, D
第六章 – 使用 Kubernetes 部署和扩展应用程序
-
C, D
-
B, C
-
B
-
D
-
D
-
B
-
C
-
C
-
B
-
D
-
C
-
B
-
D
-
B, C
-
A
-
C
-
B
-
D
第七章 – 使用 Kubernetes 进行应用程序部署和调试
-
C, D
-
B
-
B
-
B
-
C
-
D
-
C
-
C
-
B
-
D
-
B
-
B, D
-
A
-
C
-
A
-
C
第八章 – 遵循 Kubernetes 最佳实践
-
C
-
D
-
B
-
C
-
B
-
C
-
B
-
A
-
D
-
D
-
B, D
-
B
-
A
-
C, D
-
B
第九章 – 理解云原生架构
-
C
-
B
-
A
-
D
-
B
-
D
-
C, D
-
C, D
-
A
-
D
-
B
-
D
-
A
-
A, C
-
A
-
D
第十章 – 在云中实现遥测和可观察性
-
C, D
-
B
-
D
-
B
-
D
-
B
-
C
-
C, D
-
B
-
D
-
A
-
D
-
A
-
C
-
D
-
A, C, D
-
D
-
C
第十一章 – 自动化云原生应用交付
-
C
-
A, B
-
B, D
-
C
-
A, B, D
-
B, C, D
-
C
-
D
-
C
-
B, D
-
A, C
-
C, D
-
A
-
C
-
C
-
A, C
-
A, C, D
第十二章 – 为 KCNA 考试做准备
模拟考试 A
-
C
-
B
-
D
-
D
-
B
-
D. 部署是最佳选项,因为 ReplicaSet 不允许滚动更新,而 StatefulSet 对无状态应用程序来说并不需要。
-
C
-
D
-
A
-
D
-
B
-
D
-
A. DaemonSet 是最佳选项,因为它可以确保每个 Kubernetes 节点上都有一个副本在运行。
-
C
-
A, C
-
C
-
D. 同一个 Pod 中的容器可以共享文件系统挂载并通过本地主机进行通信。一个 Pod 中的所有容器将始终在同一个节点上运行。
-
D
-
B
-
D. CI/CD 是正确答案,因为我们需要构建和测试自动化,但不需要自动部署。在这种情况下,CD 代表持续交付。
-
A, C
-
D
-
B. 使用命名空间是最佳选项,因为它可以通过实施 RBAC 策略进一步限制访问权限。团队可以被限制在只有一个命名空间内。
-
A
-
B
-
B
-
D
-
A, B, C
-
B
-
D
-
C, D
-
B
-
D. HPA 是正确答案,因为我们需要在问题的背景下扩展应用程序。显然,最终需要一个集群自动缩放器来调整节点数量。
-
B
-
C, D
-
B, D
-
B
-
B, C
-
D
-
B
-
D
-
B
-
A
-
B
-
B, D
-
A
-
B
-
D
-
A
-
C. 其他选项可能会影响稳定性或使应用程序操作变得更加困难。
-
B
-
D
-
C
-
B
-
C
-
A, C
-
C. 本书编写时,大约每年发布 3 个版本。
-
D
-
C
-
B
模拟考试 B
-
D
-
A, B
-
C
-
A
-
B
-
A
-
B
-
D
-
B
-
B
-
C
-
A, D
-
D
-
B, C, D
-
B, C
-
A, C
-
D
-
A, B, C. 默认情况下,kubectl 应该能与当前版本及上下一个版本兼容。
-
D
-
A
-
A
-
C
-
B
-
C
-
C, D
-
B
-
A
-
A
-
D
-
D
-
B
-
D
-
B
-
C
-
A
-
A
-
D
-
A, C
-
A
-
B
-
A
-
B
-
B, D
-
D
-
B
-
D. 部署是最佳选项,因为它满足所有要求,并且该应用程序是无状态的。
-
B
-
A
-
A, C
-
C
-
B
-
D. Kubernetes 命名空间允许配置资源配额。
-
A
-
B
-
C
-
C, D. GitOps 对于持续部署非常有帮助,FluxCD 是一个 GitOps 工具,所以 A 和 B 都是错误的答案。
-
C, D
-
B
-
D
-
C


浙公网安备 33010602011771号