Kubernetes-秘籍第二版-全-

Kubernetes 秘籍第二版(全)

原文:zh.annas-archive.org/md5/7c40fa318945ff99ace93836e87f20cf

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎阅读Kubernetes Cookbook,感谢您选择了这本书!通过这本书,我们希望能帮助您解决围绕 Kubernetes 的具体问题。我们编写了 100 多个配方,涵盖了诸如设置集群、使用 Kubernetes API 对象管理容器化工作负载、使用存储原语、配置安全性等主题。无论您是初次接触 Kubernetes 还是已经使用了一段时间,我们希望您能在这里找到一些有用的内容,以改善您对 Kubernetes 的体验和使用。

适合阅读本书的人群

本书适用于 DevOps 领域的任何人。您可能是一个应用程序开发人员,偶尔需要与 Kubernetes 交互,或者是一个平台工程师,为组织中的其他工程师创建可重用的解决方案,或者介于两者之间。本书将帮助您成功地在 Kubernetes 丛林中导航,从开发到生产。它涵盖了核心 Kubernetes 概念以及来自更广泛生态系统的解决方案,这些解决方案几乎已成为行业中的事实标准。

我们为什么写这本书

我们多年来一直是 Kubernetes 社区的一部分,看到初学者甚至更高级用户遇到的许多问题。我们希望分享我们在生产环境中运行 Kubernetes 以及在 Kubernetes 上开发的知识——即,为核心代码库或生态系统做出贡献,并编写在 Kubernetes 上运行的应用程序所积累的经验。考虑到自第一版书籍出版以来,Kubernetes 的采用率一直在增长,因此着手撰写本书的第二版是完全合理的。

导读本书

本食谱书包含 15 章。每一章由按照标准 O'Reilly 配方格式编写的配方组成(问题、解决方案、讨论)。您可以从头到尾阅读本书,也可以跳转到特定章节或配方。每个配方都是独立的,当需要理解其他配方中的概念时,会提供适当的参考。索引也是一个非常强大的资源,因为有时一个配方也展示了一个特定的命令,索引突出显示这些联系。

Kubernetes 版本说明

在撰写本文时,Kubernetes 1.27是最新的稳定版本,于 2023 年 4 月底发布,这也是我们在整本书中使用的基线版本。然而,这里提出的解决方案通常适用于旧版本;如果情况不是这样,我们将明确指出,并提到所需的最低版本。

Kubernetes 按照每年三次发布的节奏进行。发布周期大约为 15 周;例如,1.26 版本在 2022 年 12 月发布,1.27 版本在 2023 年 4 月发布,1.28 版本在 2023 年 8 月发布,本书进入生产阶段时。Kubernetes 的发布版本指南表明,您可以预期对最新三个次要版本的功能提供支持。Kubernetes 社区支持大约 14 个月的活跃补丁发布系列。这意味着 1.27 版本中的稳定 API 对象将至少支持到 2024 年 6 月。然而,因为本书中的示例通常仅使用稳定的 API,如果您使用更新的 Kubernetes 版本,这些示例仍应有效。

技术你需要理解

这本中级书籍需要您对几个开发和系统管理概念有基本的理解。在深入阅读本书之前,您可能希望复习以下内容:

bash(Unix shell)

这是 Linux 和 macOS 上的默认 Unix shell。熟悉 Unix shell,如编辑文件、设置文件权限和用户权限、在文件系统中移动文件以及进行一些基本的 shell 编程,将是有益的。有关一般介绍,请参考卡梅隆·纽汉姆(Cameron Newham)的《学习 bash Shell》,第三版,或卡尔·阿尔宾(Carl Albing)和 JP Vossen 的《bash Cookbook》,第二版,均由 O’Reilly 出版。

包管理

本书中的工具通常有多个依赖项,需要通过安装一些软件包来满足。因此,您的机器上需要了解软件包管理系统。例如,在 Ubuntu/Debian 系统上可能是apt,在 CentOS/RHEL 系统上可能是yum,在 macOS 上可能是Homebrew。无论如何,请确保您知道如何安装、升级和删除软件包。

Git

Git 已经成为分布式版本控制的标准。如果您还不熟悉 Git,我们推荐阅读 Prem Kumar Ponuthorai 和 Jon Loeliger 合著的《使用 Git 进行版本控制》,第三版(O’Reilly)。除了 Git,GitHub 网站是一个创建托管库的绝佳资源。要了解 GitHub,请查看GitHub 培训套件网站

Go

Kubernetes 是用 Go 编写的。Go 已经在 Kubernetes 社区及其他领域广泛应用作为一种流行的编程语言。本书不涉及 Go 编程,但介绍了如何编译几个 Go 项目。有一些关于如何设置 Go 工作空间的基本理解将会很有帮助。如果您想了解更多,可以从 O’Reilly 的视频培训课程《Go 编程入门》开始。

本书使用的约定

本书使用以下印刷约定:

斜体

表示新术语、URL、电子邮件地址、文件名和文件扩展名。

等宽

用于程序清单,以及在段落内用来指代程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。还用于命令和命令行输出。

等宽粗体

显示用户需要直接键入的命令或其他文本。

等宽斜体

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

提示

此元素表示提示或建议。

注意

此元素表示一般说明。

警告

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

使用代码示例

本书提供下载的补充材料(Kubernetes 清单、代码示例、练习等)可在https://github.com/k8s-cookbook/recipes获取。您可以克隆此存储库,转到相关章节和配方,并直接使用代码:

$ git clone https://github.com/k8s-cookbook/recipes

注意

此存储库中的示例并非旨在表示优化设置以在生产中使用。它们为您提供了运行示例中所述示例的基本最低要求。

如果您在使用代码示例时遇到技术问题或问题,请发送电子邮件至support@oreilly.com

本书旨在帮助您完成工作。通常情况下,如果本书提供示例代码,则可以在您的程序和文档中使用它。除非您重复使用本书中的大部分代码,否则不需要联系我们进行许可。例如,编写一个使用本书中几个代码块的程序不需要许可。出售或分发包含 O’Reilly 书籍示例的 CD-ROM 需要许可。通过引用本书回答问题并引用示例代码不需要许可。将本书中大量示例代码合并到您产品的文档中需要许可。

我们感谢,但不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Kubernetes Cookbook,作者 Sameer Naik、Sébastien Goasguen 和 Jonathan Michaux(O’Reilly)。版权所有 2024 年 CloudTank SARL,Sameer Naik 和 Jonathan Michaux,978-1-098-14224-7。”

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

O’Reilly 在线学习

注意

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

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

如何联系我们

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

  • O'Reilly Media, Inc.

  • 1005 Gravenstein Highway North

  • Sebastopol, CA 95472

  • 800-889-8969(美国或加拿大)

  • 707-829-7019(国际或当地)

  • 707-829-0104(传真)

  • support@oreilly.com

  • https://www.oreilly.com/about/contact.html

我们为本书设置了一个网页,列出勘误、示例以及其他任何附加信息。您可以访问此页面:https://oreil.ly/kubernetes-cookbook-2e

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

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

在 Twitter 上关注我们:https://twitter.com/oreillymedia

在 YouTube 上关注我们:https://youtube.com/oreillymedia

致谢

感谢整个 Kubernetes 社区开发如此出色的软件,并成为一群伟大的人——开放、友善且总是乐于助人。

Sameer 和 Jonathan 很荣幸与 Sébastien 共同合作撰写本书的第二版。我们对 Roland Huß、Jonathan Johnson 和 Benjamin Muschko 提供的审阅表示感谢,他们在改进最终产品方面提供了宝贵的帮助。我们还感谢 O'Reilly 的编辑 John Devins、Jeff Bleiel 和 Ashley Stussy,与他们合作非常愉快。

第一章:Kubernetes 入门

在本章中,我们介绍了一些可以帮助你开始使用 Kubernetes 的配方。我们向你展示了如何在不安装 Kubernetes 的情况下使用它,并介绍了诸如命令行界面(CLI)和仪表板之类的组件,这些组件允许你与集群交互,以及 Minikube,一个可以在笔记本电脑上运行的一体化解决方案。

1.1 安装 Kubernetes 命令行工具 kubectl

问题

你希望安装 Kubernetes 命令行界面,以便与 Kubernetes 集群交互。

解决方案

最简单的选择是下载最新的官方版本。例如,在 Linux 系统上获取最新稳定版本,请输入以下命令:

$ wget https://dl.k8s.io/release/$(wget -qO - https://dl.k8s.io/release/
stable.txt)/bin/linux/amd64/kubectl

$ sudo install -m 755 kubectl /usr/local/bin/kubectl

使用Homebrew 软件包管理器,Linux 和 macOS 用户也可以安装 kubectl

$ brew install kubectl

安装完成后,请通过列出其版本来确保 kubectl 可正常工作:

$ kubectl version --client
Client Version: v1.28.0
Kustomize Version: v5.0.4-0.20230...

讨论

kubectl 是官方的 Kubernetes 命令行工具,并且是开源软件,这意味着如果需要的话,你可以自行构建 kubectl 的可执行文件。详见配方 15.1 以了解如何在本地编译 Kubernetes 源代码。

值得注意的是,Google Kubernetes Engine 用户(参见配方 2.11)可以使用 gcloud 安装 kubectl

$ gcloud components install kubectl

此外,请注意,在最新版本的 Minikube 中(参见配方 1.2),你可以将 kubectl 作为 minikube 的子命令来运行一个与集群版本匹配的 kubectl 可执行文件:

$ minikube kubectl -- version --client
Client Version: version.Info{Major:"1", Minor:"27", GitVersion:"v1.27.4", ...}
Kustomize Version: v5.0.1

另见

1.2 安装 Minikube 以运行本地 Kubernetes 实例

问题

你希望在本地机器上用于测试、开发或培训目的使用 Kubernetes。

解决方案

Minikube 是一个工具,可以让你在本地机器上轻松使用 Kubernetes。

要在本地安装 Minikube CLI,你可以获取最新的预构建版本或从源代码构建。要在基于 Linux 的机器上安装最新版本的 minikube,请执行以下操作:

$ wget https://storage.googleapis.com/minikube/releases/latest/
minikube-linux-amd64 -O minikube 

$ sudo install -m 755 minikube /usr/local/bin/minikube

这将把 minikube 可执行文件放在你的路径中,并使其可以从任何位置访问。

安装完成后,你可以使用以下命令验证 Minikube 的版本:

$ minikube version
minikube version: v1.31.2
commit: fd7ecd...

讨论

Minikube 可以部署为虚拟机、容器或裸金属。在创建 Minikube 集群时,可以使用 --driver 标志进行配置。当未指定此标志时,Minikube 将自动选择最佳的运行时环境。

虚拟化管理程序是创建和管理虚拟机的软件或硬件组件。它负责分配和管理主机系统的物理资源(CPU、内存、存储、网络),允许多个虚拟机(VM)在同一物理硬件上并发运行。Minikube 支持多种虚拟化管理程序,例如VirtualBoxHyperkitDocker DesktopHyper-V等。驱动程序页面提供了支持的运行时概述。

Minikube 还可以使用容器运行时在主机上创建集群。此驱动仅在基于 Linux 的主机上可用,在这些主机上可以本地运行 Linux 容器,无需使用虚拟机。虽然基于容器的运行时不像虚拟机那样提供相同级别的隔离,但它确实提供了最佳的性能和资源利用率。在撰写本文时,Minikube 支持Docker 引擎Podman(实验性支持)。

其他可用于在 Linux 容器上运行本地 Kubernetes 集群的工具如下:

  • Docker Desktop 中的 Kubernetes(参见配方 1.6)

  • 种类(参见配方 1.5)

  • k3d

参见

1.3 在本地使用 Minikube 进行开发

问题

您希望在本地使用 Minikube 进行 Kubernetes 应用程序的测试和开发。您已经安装并启动了 minikube(参见配方 1.2),并且想知道一些额外的命令来简化开发体验。

解决方案

使用 minikube start 命令在本地创建 Kubernetes 集群:

$ minikube start

默认情况下,集群将分配 2 GB 的 RAM。如果您不喜欢默认设置,可以覆盖参数,例如内存和 CPU 数量,并选择特定的 Kubernetes 版本为 Minikube VM——例如:

$ minikube start --cpus=4 --memory=4096 --kubernetes-version=v1.27.0

此外,您可以通过覆盖默认值来指定集群节点的数量为一个节点:

$ minikube start --cpus=2 --memory=4096 --nodes=2

要检查 Minikube 集群的状态,请执行以下操作:

$ minikube status
minikube
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

minikube-m02
type: Worker
host: Running
kubelet: Running

类似地,要检查运行在 Minikube 内的 Kubernetes 集群的状态,请执行以下操作:

$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.64.72:8443
CoreDNS is running at https://192.168.64.72:8443/api/v1/namespaces/
kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

使用 Minikube 创建的 Kubernetes 集群利用主机机器的资源,因此在完成后,请不要忘记使用 minikube stop 停止它,以释放系统资源。

讨论

Minikube CLI 提供了一些命令,可以让您的生活更轻松。CLI 提供了内置帮助,您可以使用它自行发现子命令——以下是一小部分:

$ minikube
...
Basic Commands:
  start            Starts a local Kubernetes cluster
  status           Gets the status of a local Kubernetes cluster
  stop             Stops a running local Kubernetes cluster
  delete           Deletes a local Kubernetes cluster
...
Configuration and Management Commands:
  addons           Enable or disable a minikube addon
...

除了 startstopdelete,您还应该熟悉 ipsshtunneldashboarddocker-env 命令。

提示

如果由于任何原因你的 Minikube 变得不稳定,或者你想重新开始,你可以使用minikube stopminikube delete来删除它。然后minikube start将给你一个新的安装。

1.4 在 Minikube 上启动你的第一个应用程序

问题

你已经启动了 Minikube(参见 Recipe 1.3),现在你想在 Kubernetes 上启动你的第一个应用程序。

解决方案

例如,你可以使用两个kubectl命令在 Minikube 上启动Ghost微博平台:

$ kubectl run ghost --image=ghost:5.59.4 --env="NODE_ENV=development"
$ kubectl expose pod ghost --port=2368 --type=NodePort

手动监视 Pod,查看其何时开始运行:

$ kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
ghost-8449997474-kn86m   1/1       Running   0          24s

现在,你可以使用minikube service命令在 Web 浏览器中自动加载应用程序服务的 URL:

$ minikube service ghost

讨论

kubectl run命令被称为生成器;它是一个便利命令,用于创建Pod对象(参见 Recipe 4.4)。kubectl expose命令也是一个生成器,一个便利命令,用于创建Service对象(参见 Recipe 5.1),将网络流量路由到你的部署启动的容器。

当你不再需要该应用程序时,可以删除Pod以释放集群资源:

$ kubectl delete pod ghost

另外,你应该删除由kubectl expose命令创建的ghost服务:

$ kubectl delete svc ghost

1.5 使用 kind 在本地运行 Kubernetes

问题

kind是一种在本地运行 Kubernetes 的替代方法。它最初设计用于测试 Kubernetes,但现在也经常用于在笔记本上轻松尝试基于 Kubernetes 的解决方案。你可以在本地使用 kind 来测试和开发你的 Kubernetes 应用程序。

解决方案

使用 kind 的最低要求是 Go 和一个 Docker 运行时。kind 在任何平台上都很容易安装,例如使用brew

$ brew install kind

然后,创建集群就像这样简单:

$ kind create cluster

删除它同样很容易:

$ kind delete cluster

讨论

因为 kind 最初是为了测试 Kubernetes 而开发的,其核心设计原则之一是它应该很好地支持自动化。如果你计划自动部署用于测试目的的 Kubernetes 集群,你可能考虑使用 kind。

参见

1.6 在 Docker Desktop 中使用 Kubernetes

问题

Docker Desktop 是建立在 Docker Engine 之上的一个提供多种有用开发者工具的产品,包括内置版本的 Kubernetes 和一个关联的负载均衡器来将流量路由到集群中。这意味着你可以安装一个单一工具,并且可以访问几乎你需要的所有内容。你可以在本地使用 Docker Desktop 来测试和开发你的 Kubernetes 应用程序。

解决方案

安装 Docker Desktop并确保在安装过程中启用了 Kubernetes。

您可以在 Docker Desktop 的设置面板中激活和停用 Kubernetes,如 图 1-1 所示。如果您仅使用 Docker Desktop 的 Docker 引擎而不使用 Kubernetes,可以执行此操作以节省计算机资源。正如所示,设置面板还显示了 Docker Desktop 提供的 Kubernetes 版本,这在调试时可能很有用,因为某些解决方案可能对可以运行的 Kubernetes 的最低或最高版本有要求。

Docker Desktop Kubernetes 设置面板快照

图 1-1. Docker Desktop Kubernetes 设置面板快照

值得注意的是,嵌入 Docker Desktop 的 Kubernetes 版本落后于最新版本几个版本,而 Minikube 更倾向于保持更新。

如 图 1-2 所示,Docker Desktop 工具栏菜单允许您轻松在不同的本地集群之间切换 kubectl 上下文,这意味着您可以同时运行 Minikube 和 Docker Desktop 的 Kubernetes,但可以在它们之间切换(尽管我们不建议这样做)。有关如何直接从 kubectl 执行此操作的信息,请参阅配方 1.7。

kubectl 的 Docker Desktop 上下文切换器快照

图 1-2. Docker Desktop 的 kubectl 上下文切换器快照

讨论

虽然这是一个快速且简便的开始 Kubernetes 的方式,但请注意 Docker Desktop 不是开源的,而且免费版本仅限个人、小型企业、学生和教育工作者以及非商业开源开发者使用。

另一方面,用于运行 Minikube 的 Docker 引擎具有 Apache 2.0 许可证,Minikube 本身也是如此。

1.7 切换 kubectl 上下文

问题

默认情况下,kubectl 总是配置为与特定的 Kubernetes 集群通信,这个配置是 上下文 的一部分。如果您忘记了 kubectl 设置为哪个集群,想要在不同集群之间切换,或者想要更改其他与上下文相关的参数,则可以使用本文提供的方法。

解决方案

要查看 kubectl 可用的上下文,请使用 kubectl config get-contexts 命令:

$ kubectl config get-contexts
CURRENT   NAME             CLUSTER          AUTHINFO         NAMESPACE
          docker-desktop   docker-desktop   docker-desktop
          kind-kind        kind-kind        kind-kind
*         minikube         minikube         minikube         default

正如您从输出中看到的,在这种情况下,kubectl 可用于三个 Kubernetes 集群,并且当前上下文设置为与 minikube 集群通信。

要切换到 kind-kind 集群,请执行以下命令:

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

讨论

如果您希望使用本地的 kubectl 访问远程集群,可以通过编辑 kubeconfig 文件来实现。在官方文档中了解更多关于 kubeconfig 文件的信息。

1.8 使用 kubectx 和 kubens 切换上下文和命名空间

问题

你希望找到一个更简单的方法来切换上下文(即集群)和命名空间,因为切换上下文的命令很长,而且很难记住。

解决方案

kubectxkubens 是一对流行的开源脚本,可以大大简化 kubectl 切换上下文和切换命名空间的操作,这样你就不必为每个命令显式设置命名空间名字。

有很多可用的安装选项。如果你能使用 brew,那么你可以尝试这样做:

$ brew install kubectx

然后您可以像这样轻松列出可用的 kubectl 上下文:

$ kubectx
docker-desktop
kind-kind
minikube

并且同样简单地切换上下文:

$ kubectx minikube
Switched to context "minikube".

类似地,kubens 允许您轻松列出和切换命名空间:

$ kubens
default
kube-node-lease
kube-public
kube-system
test

$ kubens test
default
Context "minikube" modified.
Active namespace is "test".

从那时起,所有的命令都将在所选命名空间的上下文中执行:

$ kubectl get pods
default
No resources found in test namespace.

参见

第二章:创建 Kubernetes 集群

在本章中,我们讨论了多种设置完整 Kubernetes 集群的方法。我们涵盖了低级别的标准化工具(kubeadm),这些工具也是其他安装程序的基础,并向您展示了控制平面和工作节点的相关二进制文件位置。我们演示了如何编写 systemd 单元文件以监控 Kubernetes 组件,并最终展示了如何在 Google Cloud 平台和 Azure 上设置集群。

2.1 为 Kubernetes 集群准备新节点

问题

您希望为创建新的 Kubernetes 集群或将其添加到现有集群的节点准备一个新的节点,并配置所有必需的工具。

解决方案

要为基于 Ubuntu 的主机准备 Kubernetes 集群,首先需要启用 IPv4 转发并启用 iptables 以查看桥接流量:

$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

$ sudo modprobe overlay
$ sudo modprobe br_netfilter

$ cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

$ sudo sysctl --system

为了与kubeadm工具兼容,节点上需要关闭交换分区:

$ sudo apt install cron -y
$ sudo swapoff -a
$ (sudo crontab -l 2>/dev/null; echo "@reboot /sbin/swapoff -a") | sudo crontab -
|| true

集群节点需要实现 Kubernetes 容器运行时接口(CRI)。cri-o 就是其中之一。cri-o 的版本应与 Kubernetes 版本匹配。例如,如果您要引导一个 Kubernetes 1.27 集群,请相应地配置 VERSION 变量:

$ VERSION="1.27"
$ OS="xUbuntu_22.04"

$ cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:
stable.list
deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:
/stable/$OS/ /
EOF

$ cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:
stable:cri-o:$VERSION.list
deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:
/stable:/cri-o:/$VERSION/$OS/ /
EOF

$ curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:
/stable:/cri-o:/$VERSION/$OS/Release.key | \
    sudo apt-key add -
$ curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:
/stable/$OS/Release.key | \
    sudo apt-key add -

$ sudo apt-get update

$ sudo apt-get install cri-o cri-o-runc cri-tools -y

然后重新加载 systemd 配置并启用 cri-o:

$ sudo systemctl daemon-reload
$ sudo systemctl enable crio --now

kubeadm 工具用于从头开始引导 Kubernetes 集群,以及加入现有集群。使用以下命令启用其软件仓库:

$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/etc/apt/keyrings/k8s-archive-keyring.gpg]
https://apt.kubernetes.io/
kubernetes-xenial main
EOF

$ sudo apt-get install -y apt-transport-https ca-certificates curl
$ sudo curl -fsSLo /etc/apt/keyrings/k8s-archive-keyring.gpg \
    https://dl.k8s.io/apt/doc/apt-key.gpg

$ sudo apt-get update

现在您可以安装所有引导 Kubernetes 集群节点所需的工具。您将需要以下工具:

  • kubelet 二进制文件

  • kubeadm 命令行界面

  • kubectl 客户端

运行此命令以安装它们:

$ sudo apt-get install -y kubelet kubeadm kubectl

然后将这些软件包标记为被拒绝更新,以防止它们被自动升级:

$ sudo apt-mark hold kubelet kubeadm kubectl

您的 Ubuntu 主机现在已准备好成为 Kubernetes 集群的一部分。

讨论

kubeadm 是一个设置工具,提供了 kubeadm initkubeadm joinkubeadm init 用于引导 Kubernetes 控制平面节点,而 kubeadm join 用于引导工作节点并将其加入集群。实质上,kubeadm 提供了使得最小可行集群运行起来所需的动作。kubelet 是在每个节点上运行的 节点代理

除了 cri-o 外,还有其他容器运行时值得研究的是 containerdDocker EngineMirantis Container Runtime

2.2 引导 Kubernetes 控制平面节点

问题

您已经为 Kubernetes 初始化了一个 Ubuntu 主机(见配方 2.1),现在需要引导一个新的 Kubernetes 控制平面节点。

解决方案

安装了kubeadm二进制文件后,您已准备好开始引导 Kubernetes 集群。在节点上初始化控制平面,使用以下命令:

$ NODENAME=$(hostname -s)
$ IPADDR=$(ip route get 8.8.8.8 | sed -n 's/.*src \([^\ ]*\).*/\1/p')
$ POD_CIDR=192.168.0.0/16
警告

控制平面节点应至少具有两个虚拟 CPU 和 2 GB 内存。

现在使用 kubeadm 初始化控制平面节点:

$ sudo kubeadm init --apiserver-advertise-address=$IPADDR \
    --apiserver-cert-extra-sans=$IPADDR  \
    --pod-network-cidr=$POD_CIDR \
    --node-name $NODENAME \
    --ignore-preflight-errors Swap
[init] Using Kubernetes version: v1.27.2
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
...

init命令的输出包含配置信息,用于设置kubectl与您的集群通信。一旦配置了kubectl,您可以使用以下命令验证集群组件的健康状态:

$ kubectl get --raw='/readyz?verbose'

要获取集群信息,请使用:

$ kubectl cluster-info

讨论

用户工作负载不会安排在控制平面节点上执行。如果您正在创建一个实验性的单节点集群,那么您需要对控制平面节点进行标记,以便在控制平面节点上安排用户工作负载:

$ kubectl taint nodes --all node-role.kubernetes.io/control-plane-

参见

2.3 安装用于集群网络的容器网络插件

问题

您已经引导了一个 Kubernetes 控制平面节点(参见配方 2.2),现在需要安装一个 pod 网络插件,以便 pod 可以彼此通信。

解决方案

您可以在控制平面节点上使用以下命令安装 Calico 网络插件:

$ kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/
v3.26.1/manifests/calico.yaml

讨论

您必须使用与您的集群兼容并且符合您需求的容器网络接口(CNI)插件。有许多插件实现了 CNI。查看 Kubernetes 文档中关于可用插件的非详尽列表

2.4 向 Kubernetes 集群添加工作节点

问题

您已经初始化了 Kubernetes 控制平面节点(参见配方 2.2),并安装了一个 CNI 插件(参见配方 2.3),现在您想要向集群添加工作节点。

解决方案

使用 Ubuntu 主机初始化 Kubernetes,如配方 2.1 所示,在控制平面节点上执行以下命令以显示集群join命令:

$ kubeadm token create --print-join-command

现在,在工作节点上执行join命令:

$ sudo kubeadm join --token *<token>*
警告

工作节点应至少具有一个 vCPU 和 2 GB RAM。

返回到您的控制平面节点终端会话,您将看到您的节点加入:

$ kubectl get nodes
NAME     STATUS   ROLES           AGE   VERSION
master   Ready    control-plane   28m   v1.27.2
worker   Ready    <none>          10s   v1.27.2

您可以重复这些步骤以向 Kubernetes 集群添加更多的工作节点。

讨论

工作节点是您的工作负载运行的地方。当集群开始耗尽资源时,您会开始注意到新 pod 的挂起状态。此时,您应该考虑通过添加更多的工作节点来增加集群资源。

2.5 部署 Kubernetes 仪表板

问题

您已经创建了一个 Kubernetes 集群,现在您想要使用用户界面在集群上创建、查看和管理容器化工作负载。

解决方案

使用 Kubernetes 仪表板,这是一个基于 Web 的用户界面,用于将容器化应用程序部署到 Kubernetes 集群,并管理集群资源。

提示

如果您使用 Minikube,可以通过启用dashboard插件简单地安装 Kubernetes 仪表板:

$ minikube addons enable dashboard

要部署 v2.7.0 Kubernetes 仪表板,请执行以下操作:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/
v2.7.0/aio/deploy/recommended.yaml

然后验证部署是否准备就绪:

$ kubectl get deployment kubernetes-dashboard -n kubernetes-dashboard
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-dashboard   1/1     1            1           44s

2.6 访问 Kubernetes 仪表板

问题

您已在集群上安装了 Kubernetes 仪表板(参见 Recipe 2.5),并希望从 Web 浏览器访问仪表板。

解决方案

您需要创建一个ServiceAccount,具有管理集群权限。创建名为sa.yaml的文件,并包含以下内容:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard

使用此命令创建ServiceAccount

$ kubectl apply -f sa.yaml

要访问 Kubernetes 仪表板,您需要创建与此帐户关联的认证令牌。保存在以下命令的输出中打印的令牌:

$ kubectl -n kubernetes-dashboard create token admin-user
eyJhbGciOiJSUzI1NiIsImtpZCI6...

由于 Kubernetes 仪表板是集群本地服务,您需要设置与集群的代理连接:

$ kubectl proxy

访问http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/workloads?namespace=_all,您现在可以打开 Kubernetes 仪表板并使用之前创建的认证令牌进行身份验证。

在浏览器中打开的 UI 中,您将看到图 2-1 中所示的页面。

仪表板应用程序创建视图的快照

图 2-1. 仪表板应用程序创建视图的快照
提示

如果您使用 Minikube,您只需执行以下操作:

$ minikube dashboard

讨论

要创建一个应用程序,请点击右上角的加号(+),选择“从表单创建”选项卡,给应用程序命名,并指定您要使用的容器镜像。然后点击部署按钮,您将看到一个新视图,显示部署、pod 和副本集。在 Kubernetes 中有数十种重要的资源类型,例如部署、pod、副本集、服务等,我们将在本书的其余部分详细探讨。

在创建 Redis 容器后,图 2-2 中的快照展示了典型的仪表板视图。

带有 Redis 应用程序的仪表板概览

图 2-2. 带有 Redis 应用程序的仪表板概览

如果您返回到终端会话并使用命令行客户端,则会看到相同的内容:

$ kubectl get all
NAME                         READY   STATUS    RESTARTS   AGE
pod/redis-584fd7c758-vwl52   1/1     Running   0          5m9s

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   19m

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/redis   1/1     1            1           5m9s

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/redis-584fd7c758   1         1         1       5m9s

您的 Redis pod 将运行 Redis 服务器,如下面的日志所示:

$ kubectl logs redis-3215927958-4x88v
...
1:C 25 Aug 2023 06:17:23.934 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 25 Aug 2023 06:17:23.934 * Redis version=7.2.0, bits=64, commit=00000000,
modified=0, pid=1, just started
1:C 25 Aug 2023 06:17:23.934 # Warning: no config file specified, using the
default config. In order to specify a config file use redis-server
/path/to/redis.conf
1:M 25 Aug 2023 06:17:23.934 * monotonic clock: POSIX clock_gettime
1:M 25 Aug 2023 06:17:23.934 * Running mode=standalone, port=6379.
1:M 25 Aug 2023 06:17:23.935 * Server initialized
1:M 25 Aug 2023 06:17:23.935 * Ready to accept connections tcp

2.7 部署 Kubernetes Metrics Server

问题

您已经部署了 Kubernetes 仪表板(参见 Recipe 2.5),但在仪表板中看不到 CPU 和内存使用信息。

解决方案

Kubernetes 仪表板需要Kubernetes Metrics Server来可视化 CPU 和内存使用情况。

提示

如果您使用 Minikube,只需启用metrics-server附加组件即可安装 Kubernetes Metrics Server:

$ minikube addons enable metrics-server

要部署最新版本的 Kubernetes Metrics Server,请执行以下操作:

$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/
latest/download/components.yaml

然后验证部署是否就绪:

$ kubectl get deployment metrics-server -n kube-system
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
metrics-server   1/1     1            1           7m27s

如果看到部署未进入就绪状态,请检查 pod 日志:

$ kubectl logs -f deployment/metrics-server -n kube-system
I0707 05:06:19.537981   1 server.go:187] "Failed probe"
probe="metric-storage-ready" err="no metrics to serve"
E0707 05:06:26.395852   1 scraper.go:140] "Failed to scrape node" err="Get
\"https://192.168.64.50:10250/metrics/resource\": x509: cannot validate
certificate for 192.168.64.50 because it doesn't contain any IP SANs"
node="minikube"

如果你看到错误消息“无法验证证书”,则需要将标志 --kubelet-insecure-tls 添加到 Metrics Server 部署中:

$ kubectl patch deployment metrics-server -n kube-system --type='json'
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value":
"--kubelet-insecure-tls"}]'

提示

Metrics Server 启动后,可能需要几分钟才能使其可用。如果还没有准备好状态,则请求指标可能会产生错误。

一旦 Metrics Server 启动,Kubernetes 仪表板将显示 CPU 和内存使用统计信息,如 Figure 2-3 所示。

仪表板节点视图快照

Figure 2-3. 仪表板集群节点视图

讨论

使用 kubectl top 命令也可以在命令行中查看节点和 Pod 的指标:

$ kubectl top pods -A
NAMESPACE     NAME                               CPU(cores)   MEMORY(bytes)
kube-system   coredns-5d78c9869d-5fh78           9m           9Mi
kube-system   etcd-minikube                      164m         36Mi
kube-system   kube-apiserver-minikube            322m         254Mi
kube-system   kube-controller-manager-minikube   123m         35Mi
kube-system   kube-proxy-rvl8v                   13m          12Mi
kube-system   kube-scheduler-minikube            62m          15Mi
kube-system   storage-provisioner                22m          7Mi

类似地,要查看节点指标,请执行以下操作:

$ kubectl top nodes
NAME       CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
minikube   415m         10%    1457Mi          18%

参见

2.8 从 GitHub 下载 Kubernetes 版本

问题

你想要下载官方发布的 Kubernetes 版本,而不是从源代码编译。

解决方案

Kubernetes 项目为每个版本发布一个归档。你可以在特定版本的 CHANGELOG 文件中找到归档的链接。转到项目页面的 CHANGELOG 文件夹,并打开你选择的版本的 CHANGELOG 文件。在文件中,你会找到一个链接,用于下载该版本的 kubernetes.tar.gz 文件。

例如,如果你想要下载 v1.28.0 版本,可以打开 CHANGELOG-1.28.md,在标题为“Downloads for v1.28.0”的部分中,你将找到下载 kubernetes.tar.gz 的链接(https://dl.k8s.io/v1.28.0/kubernetes.tar.gz)。

$ wget https://dl.k8s.io/v1.28.0/kubernetes.tar.gz

如果你想从源代码编译 Kubernetes,请参见 Recipe 15.1。

讨论

CHANGELOG 文件还列出了 kubernetes.tar.gz 归档的 sha512 哈希值。建议你验证本地下载的 kubernetes.tar.gz 归档的完整性,以确保其未被任何方式篡改。为此,请生成本地下载归档的 sha512 哈希值,并与 CHANGELOG 中列出的哈希值进行比较:

$ sha512sum kubernetes.tar.gz
9aaf7cc004d09297dc7bbc1f0149....  kubernetes.tar.gz

2.9 下载客户端和服务器二进制文件

问题

你已经下载了一个发布归档(参见 Recipe 2.8),但它不包含实际的二进制文件。

解决方案

发布归档不包含发布二进制文件(为了保持发布归档的小巧性)。因此,你需要单独下载二进制文件。为此,请运行 get-kube-binaries.sh 脚本,如下所示:

$ tar -xvf kubernetes.tar.gz
$ cd kubernetes/cluster
$ ./get-kube-binaries.sh

完成后,你将在 client/bin 中获得客户端二进制文件:

$ ls ../client/bin
kubectl		kubectl-convert

并且包含服务器二进制文件的归档位于 server/kubernetes 文件夹中:

$ ls ../server/kubernetes
kubernetes-server-linux-amd64.tar.gz   kubernetes-manifests.tar.gz	  README
...

讨论

如果您想跳过下载整个发布存档并快速下载客户端和服务器二进制文件,您可以直接从下载 Kubernetes获取它们。在此页面上,您将找到各种操作系统和架构组合的二进制文件的直接链接,如 Figure 2-4 所示。

下载 kubernetes.com 的屏幕截图,列出了适用于 Darwin 操作系统的 k8s v1.28.0 版本的二进制文件

图 2-4. downloadkubernetes.com,列出了适用于 Darwin 操作系统的 Kubernetes v1.28.0 版本的二进制文件

2.10 使用 systemd 单元文件运行 Kubernetes 组件

问题

你已经使用了 Minikube(参见 Recipe 1.2)进行学习,并且知道如何使用kubeadm引导一个 Kubernetes 集群(参见 Recipe 2.2),但你想要从头开始安装一个集群。

解决方案

为此,您需要使用 systemd 单元文件运行 Kubernetes 组件。您只寻找通过 systemd 运行kubelet的基本示例。

检查kubeadm如何通过 systemd 单元文件配置 Kubernetes 守护进程的启动方式,可以帮助你理解如何自行操作。如果你仔细查看kubeadm的配置,你会发现kubelet在集群中的每个节点上(包括控制平面节点)都在运行。

这是一个示例,您可以通过登录到任何使用kubeadm构建的集群节点(参见 Recipe 2.2)来复制:

$ systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
     Loaded: loaded (/lib/systemd/system/kubelet.service; enabled;
     vendor preset: enabled)
    Drop-In: /etc/systemd/system/kubelet.service.d
             └─10-kubeadm.conf
     Active: active (running) since Tue 2023-05-30 04:21:29 UTC; 2h 49min ago
       Docs: https://kubernetes.io/docs/home/
   Main PID: 797 (kubelet)
      Tasks: 11 (limit: 2234)
     Memory: 40.2M
        CPU: 5min 14.792s
     CGroup: /system.slice/kubelet.service
             └─797 /usr/bin/kubelet \
                --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf \
                --kubeconfig=/etc/kubernetes/kubelet.conf \
                --config=/var/lib/kubelet/config.yaml \
                --container-runtime-endpoint=unix:///var/run/crio/crio.sock \
                --pod-infra-container-image=registry.k8s.io/pause:3.9

这为你提供了 systemd 单元文件在 /lib/systemd/system/kubelet.service 中的链接以及在 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 中的配置。

单元文件很简单——它指向了 /usr/bin 中安装的 kubelet 二进制文件:

[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/home/
Wants=network-online.target
After=network-online.target

[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

配置文件告诉您如何启动kubelet二进制文件:

[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/
bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
EnvironmentFile=-/etc/default/kubelet

ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS
$KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS

所有指定的选项,如--kubeconfig,由环境变量$KUBELET_CONFIG_ARGS定义,都是kubelet二进制文件的启动选项。

讨论

systemd 是一个系统和服务管理器,有时被称为初始化系统。现在已成为 Ubuntu 和 CentOS 上的默认服务管理器。

刚才展示的单元文件仅处理kubelet。您可以为 Kubernetes 集群的所有其他组件(即 API 服务器、控制器管理器、调度器、代理)编写自己的单元文件。Kubernetes the Hard Way 提供了每个组件的单元文件示例。

然而,你只需要运行kubelet。确实,注意到配置选项 --pod-manifest-path 允许你传递一个目录,kubelet 将在其中查找并自动启动 manifests。使用 kubeadm,这个目录用于传递 API 服务器、调度器、etcd 和控制器管理器的 manifests。因此,Kubernetes 自行管理,并且 systemd 仅管理kubelet进程。

为了说明这一点,您可以列出基于 kubeadm 的集群中 /etc/kubernetes/manifests 目录的内容:

$ ls -l /etc/kubernetes/manifests
total 16
-rw------- 1 root root 2393 May 29 11:04 etcd.yaml
-rw------- 1 root root 3882 May 29 11:04 kube-apiserver.yaml
-rw------- 1 root root 3394 May 29 11:04 kube-controller-manager.yaml
-rw------- 1 root root 1463 May 29 11:04 kube-scheduler.yaml

查看 etcd.yaml 清单的详细信息,您可以看到它是一个运行 etcd 的单容器 Pod

$ cat /etc/kubernetes/manifests/etcd.yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/etcd.advertise-client-urls: https://10.10.100.30:2379
  creationTimestamp: null
  labels:
    component: etcd
    tier: control-plane
  name: etcd
  namespace: kube-system
spec:
  containers:
  - command:
    - etcd
    - --advertise-client-urls=https://10.10.100.30:2379
    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
    - --client-cert-auth=true
    - --data-dir=/var/lib/etcd
    - --experimental-initial-corrupt-check=true
    - --experimental-watch-progress-notify-interval=5s
    - --initial-advertise-peer-urls=https://10.10.100.30:2380
    - --initial-cluster=master=https://10.10.100.30:2380
    - --key-file=/etc/kubernetes/pki/etcd/server.key
    - --listen-client-urls=https://127.0.0.1:2379,https://10.10.100.30:2379
    - --listen-metrics-urls=http://127.0.0.1:2381
    - --listen-peer-urls=https://10.10.100.30:2380
    - --name=master
    - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
    - --peer-client-cert-auth=true
    - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
    - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --snapshot-count=10000
    - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    image: registry.k8s.io/etcd:3.5.7-0
    ...

参见

2.11 在 Google Kubernetes Engine 上创建 Kubernetes 集群

问题

您想在 Google Kubernetes Engine (GKE) 上创建一个 Kubernetes 集群。

解决方案

要使用 GKE,您首先需要几件事:

Google Cloud SDK 包含用于从命令行与 GCP 服务交互的 gcloud CLI 工具。安装 SDK 后,使用身份验证 gcloud 访问您的 GCP 项目:

$ gcloud auth login

使用 gcloud 命令行界面,使用 container clusters create 命令创建 Kubernetes 集群,如下所示:

$ gcloud container clusters create oreilly --zone us-east1-b

默认情况下,这将在指定的区域或区域中创建一个具有三个工作节点的 Kubernetes 集群。主节点由 GKE 服务管理,无法访问。

提示

如果不确定用于 --zone--region 参数的区域或地区,执行 gcloud compute zones listgcloud compute regions list 并选择附近的一个。通常,区域比地区需要的资源更少。

使用完集群后,请不要忘记将其删除以避免收费:

$ gcloud container clusters delete oreilly --zone us-east1-b

讨论

您可以通过使用Google Cloud Shell,一个纯在线基于浏览器的解决方案,跳过 gcloud CLI 的安装。

您可以使用此命令列出您现有的 GKE 集群:

$ gcloud container clusters list --zone us-east1-b
NAME     ZONE        MASTER_VERSION     MASTER_IP     ...  STATUS
oreilly  us-east1-b  1.24.9-gke.2000    35.187.80.94  ...  RUNNING
注意

gcloud CLI 允许您调整、更新和升级您的集群:

...
COMMANDS
...
     resize
        Resizes an existing cluster for running
        containers.
     update
        Update cluster settings for an existing container
        cluster.
     upgrade
        Upgrade the Kubernetes version of an existing
        container cluster.

参见

2.12 在 Azure Kubernetes 服务上创建 Kubernetes 集群

问题

您想在 Azure Kubernetes Service (AKS) 上创建一个 Kubernetes 集群。

解决方案

要创建 AKS 集群,您需要以下内容:

首先确保安装了 Azure CLI 版本 2.0 或更高,并登录到 Azure:

$ az --version | grep "^azure-cli"
azure-cli                         2.50.0 *

$ az login
To sign in, use a web browser to open the page https://aka.ms/devicelogin and
enter the code XXXXXXXXX to authenticate.
[
  {
    "cloudName": "AzureCloud",
    "id": "****************************",
    "isDefault": true,
    "name": "Free Trial",
    "state": "Enabled",
    "tenantId": "*****************************",
    "user": {
      "name": "******@hotmail.com",
      "type": "user"
    }
  }
]

创建名为 k8s 的 Azure 资源组,以容纳所有您的 AKS 资源,如 VM 和网络组件,并且便于稍后进行清理和拆除:

$ az group create --name k8s --location northeurope
{
  "id": "/subscriptions/************************/resourceGroups/k8s",
  "location": "northeurope",
  "managedBy": null,
  "name": "k8s",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": null,
  "type": "Microsoft.Resources/resourceGroups"
}
提示

如果不确定用于 --location 参数的区域,执行 az account list-locations 并选择附近的一个。

现在,您已设置了资源组 k8s,可以像这样创建具有一个工作节点(在 Azure 术语中称为 agent)的集群:

$ az aks create -g k8s -n myAKSCluster --node-count 1 --generate-ssh-keys
{
  "aadProfile": null,
  "addonProfiles": null,
  "agentPoolProfiles": 
    {
      "availabilityZones": null,
      "count": 1,
      "creationData": null,
      "currentOrchestratorVersion": "1.26.6",

请注意,az aks create 命令可能需要几分钟才能完成。完成后,该命令将返回一个包含有关已创建集群信息的 JSON 对象。

因此,在 Azure 门户中,您应该看到类似于 [Figure 2-5 的东西。首先找到 k8s 资源组,然后导航到部署选项卡。

Azure 门户的屏幕截图,显示位于  资源组中的 AKS 集群

图 2-5. Azure 门户,显示位于 k8s 资源组中的 AKS 集群

您现在可以连接到集群:

$ az aks get-credentials --resource-group k8s --name myAKSCluster

您现在可以在环境中四处查看并验证设置:

$ kubectl cluster-info
Kubernetes master is running at https://k8scb-k8s-143f1emgmt.northeurope.cloudapp
  .azure.com
Heapster is running at https://k8scb-k8s-143f1emgmt.northeurope.cloudapp.azure
  .com/api/v1/namespaces/kube-system/services/heapster/proxy
KubeDNS is running at https://k8scb-k8s-143f1emgmt.northeurope.cloudapp.azure
  .com/api/v1/namespaces/kube-system/services/kube-dns/proxy
kubernetes-dashboard is running at https://k8scb-k8s-143f1emgmt.northeurope
  .cloudapp.azure.com/api/v1/namespaces/kube-system/services/kubernetes-dashboard
  /proxy
tiller-deploy is running at https://k8scb-k8s-143f1emgmt.northeurope.cloudapp
  .azure.com/api/v1/namespaces/kube-system/services/tiller-deploy/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

$ kubectl get nodes
NAME                                STATUS   ROLES   AGE   VERSION
aks-nodepool1-78916010-vmss000000   Ready    agent   26m   v1.24.9

事实上,正如您从输出中所见,我们已经创建了一个单节点集群。

提示

如果您不想或无法安装 Azure CLI,则可以使用来自浏览器的 Azure Cloud Shell 作为替代方案。

当你完成发现 AKS 后,请不要忘记通过删除资源组 k8s 来关闭集群并删除所有资源:

$ az group delete --name k8s --yes --no-wait

尽管 az group delete 命令立即返回,由于存在 --no-wait 标志,但可能需要长达 10 分钟才能删除所有资源并实际销毁资源组。您可能需要在 Azure 门户中检查以确保一切按计划进行。

参见

2.13 在 Amazon Elastic Kubernetes Service 上创建 Kubernetes 集群

问题

您想要在 Amazon Elastic Kubernetes Service (EKS) 上创建一个 Kubernetes 集群。

解决方案

要在 Amazon EKS 中创建集群,您需要以下内容:

安装 AWS CLI 后,请验证客户端身份以访问您的 AWS 帐户:

$ aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: eu-central-1
Default output format [None]:

eksctl 工具是 Amazon EKS 的官方 CLI。它使用您配置的 AWS 凭据来验证 AWS。使用 eksctl,创建集群:

$ eksctl create cluster --name oreilly --region eu-central-1
2023-08-29 13:21:12 [i]  eksctl version 0.153.0-dev+a79b3826a.2023-08-18T...
2023-08-29 13:21:12 [i]  using region eu-central-1
...
2023-08-29 13:36:52 [✔]  EKS cluster "oreilly" in "eu-central-1" region is ready

默认情况下,eksctl 在指定区域创建一个具有两个工作节点的集群。您可以通过指定 --nodes 标志来调整此参数。

提示

为了获得最低的延迟,请选择距离您最近的 AWS 区域

当您不再需要 EKS 集群时,请将其删除以避免为未使用的资源收费:

$ eksctl delete cluster oreilly --region eu-central-1

参见

第三章:学习使用 Kubernetes 客户端

本章汇集了围绕 Kubernetes CLI kubectl 的基本用法的实用技巧。请参阅 第一章 了解如何安装 CLI 工具;对于高级用例,请参阅 第七章,我们将展示如何使用 Kubernetes API。

3.1 列出资源

问题

你想要列出特定类型的 Kubernetes 资源。

解决方案

使用kubectlget动词,加上资源类型。要列出所有 pod,请执行以下操作:

$ kubectl get pods

要列出所有服务和部署(请注意逗号后面没有空格),请执行以下操作:

$ kubectl get services,deployments

要列出特定部署,请执行以下操作:

$ kubectl get deployment *<deployment-name>*

要列出所有资源,请执行以下操作:

$ kubectl get all

请注意,kubectl get是一个非常基本但极其有用的命令,可以快速查看集群中的情况——它本质上相当于 Unix 上的 ps 命令。

讨论

我们强烈建议启用自动完成功能,以避免记住所有 Kubernetes 资源名称。请参阅 Recipe 12.1 获取详细信息。

3.2 删除资源

问题

你不再需要资源并希望摆脱它们。

解决方案

使用kubectldelete动词,加上你想要删除的资源的类型和名称。

要删除命名空间my-app中的所有资源,以及命名空间本身,请执行以下操作:

$ kubectl get ns
NAME          STATUS    AGE
default       Active    2d
kube-public   Active    2d
kube-system   Active    2d
my-app        Active    20m

$ kubectl delete ns my-app
namespace "my-app" deleted

注意,在 Kubernetes 中你不能删除default命名空间。这也是创建自己命名空间的一个好理由,因为这样清理环境会更容易。话虽如此,你仍然可以通过以下命令删除default命名空间中的所有对象:

$ kubectl delete all --all -n *<namespace>*

如果想知道如何创建命名空间,请参阅 Recipe 7.3。

你还可以删除特定资源和/或影响它们被销毁的过程。要删除标记有app=niceone的服务和部署,请执行以下操作:

$ kubectl delete svc,deploy -l app=niceone

要强制删除名为hangingpod的 pod,请执行以下操作:

$ kubectl delete pod hangingpod --grace-period=0 --force

要删除命名空间test中的所有 pod,请执行以下操作:

$ kubectl delete pods --all --namespace test

讨论

不要删除由部署直接控制的监督对象,如 pod 或副本集。相反,终止它们的监督或使用专门的操作来摆脱托管资源。例如,如果将部署的副本数缩减至零(参见 Recipe 9.1),那么你有效地删除了它管理的所有 pod。

另一个需要考虑的方面是级联删除与直接删除的区别——例如,当你删除自定义资源定义(CRD)时,如 Recipe 15.4 中所示,其依赖对象也会被删除。要了解如何影响级联删除策略,请阅读 Kubernetes 文档中的 垃圾回收

3.3 使用 kubectl 监视资源变更

问题

你希望在终端中以交互方式观察 Kubernetes 对象的变更。

解决方案

kubectl 命令有一个 --watch 选项,可以实现这种行为。例如,要监视 Pods,请执行以下操作:

$ kubectl get pods --watch

请注意,这是一个阻塞和自动更新的命令,类似于 top

讨论

--watch 选项很有用,但有些人更喜欢来自 watch 命令 的输出格式,例如:

$ watch kubectl get pods

3.4 使用 kubectl 编辑对象

问题

你想要更新 Kubernetes 对象的属性。

解决方案

使用 kubectledit 命令以及对象类型:

$ kubectl run nginx --image=nginx
$ kubectl edit pod/nginx

现在在编辑器中编辑 nginx 的 Pod —— 例如,添加一个名为 mylabel 值为 true 的新标签。保存后,你会看到类似这样的输出:

pod/nginx edited

讨论

如果你的编辑器无法打开,或者你想指定使用哪个编辑器,请将 EDITORKUBE_EDITOR 环境变量设置为你希望使用的编辑器名称。例如:

$ export EDITOR=vi

同样要注意,并非所有更改都会触发对象更新。

有些触发器有快捷方式;例如,如果你想要更改 Deployment 使用的镜像版本,只需使用 kubectl set image,它会更新资源(适用于 Deployments、ReplicaSets/Replication Controllers、DaemonSets、Jobs 和简单 Pods)的现有容器镜像。

3.5 请求 kubectl 解释资源和字段

问题

你想更深入地了解某个资源,例如一个 Service,和/或者准确理解 Kubernetes 清单中某个字段的含义,包括默认值以及是否必填或可选。

解决方案

使用 kubectlexplain 命令:

$ kubectl explain svc
KIND:       Service
VERSION:    v1

DESCRIPTION:
Service is a named abstraction of software service (for example, mysql)
consisting of local port (for example 3306) that the proxy listens on, and the
selector that determines which pods will answer requests sent through the proxy.

FIELDS:
   status       <Object>
     Most recently observed status of the service. Populated by the system.
     Read-only. More info: https://git.k8s.io/community/contributors/devel/
     api-conventions.md#spec-and-status/

   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions
     .md#types-kinds

   metadata     <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

   spec <Object>
     Spec defines the behavior of a service. https://git.k8s.io/community/
     contributors/devel/api-conventions.md#spec-and-status/

$ kubectl explain svc.spec.externalIPs
KIND:       Service
VERSION:    v1

FIELD: externalIPs <[]string>

DESCRIPTION:
     externalIPs is a list of IP addresses for which nodes in the cluster will
     also accept traffic for this service.  These IPs are not managed by
     Kubernetes.  The user is responsible for ensuring that traffic arrives at a
     node with this IP.  A common example is external load-balancers that are not
     part of the Kubernetes system.

讨论

kubectl explain 命令从 API 服务器公开的 Swagger/OpenAPI 定义 中提取资源和字段的描述信息。

你可以把 kubectl explain 看作是描述 Kubernetes 资源结构的一种方式,而 kubectl describe 则是描述对象值的一种方式,它们是这些结构化资源的实例。

参见

第四章:创建和修改基础工作负载

在本章中,我们提供了显示如何管理基本 Kubernetes 工作负载类型(pod 和部署)的配方。我们展示了如何通过 CLI 命令和从 YAML 清单创建部署和 pod,并解释了如何扩展和更新部署。

4.1 使用 kubectl run 创建 pod

问题

您希望快速启动一个长时间运行的应用程序,例如 Web 服务器。

解决方案

使用 kubectl run 命令,它是一个即时创建 pod 的生成器。例如,要创建运行 NGINX 反向代理的 pod,请执行以下操作:

$ kubectl run nginx --image=nginx

$ kubectl get pod/nginx
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          3m55s

讨论

kubectl run 命令可以接受多个参数来配置 pod 的附加参数。例如,您可以执行以下操作:

  • 使用 --env 设置环境变量。

  • 使用 --port 定义容器端口。

  • 使用 --command 定义要运行的命令。

  • 使用 --expose 自动创建关联的服务。

  • 使用 --dry-run=client 运行测试而不实际运行任何内容。

典型用法如下。要在端口 2368 上启动提供 NGINX 服务并创建与之关联的服务,请输入以下内容:

$ kubectl run nginx --image=nginx --port=2368 --expose

要启动设置了 root 密码的 MySQL,请输入以下内容:

$ kubectl run mysql --image=mysql --env=MYSQL_ROOT_PASSWORD=root

要在启动时启动一个 busybox 容器并执行命令 sleep 3600,请输入以下内容:

$ kubectl run myshell --image=busybox:1.36 --command -- sh -c "sleep 3600"

参见 kubectl run --help 获取有关可用参数的更多详细信息。

4.2 使用 kubectl create 创建部署

问题

您希望快速启动一个长时间运行的应用程序,例如内容管理系统。

解决方案

使用 kubectl create deployment 即时创建部署清单。例如,要创建运行 WordPress 内容管理系统的部署,请执行以下操作:

$ kubectl create deployment wordpress --image wordpress:6.3.1

$ kubectl get deployments.apps/wordpress
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
wordpress   1/1     1            1           90s

讨论

kubectl create deployment 命令可以接受多个参数来配置部署的附加参数。例如,您可以执行以下操作:

  • 使用 --port 定义容器端口。

  • 使用 --replicas 定义副本数量。

  • 使用 --dry-run=client 运行测试而不实际运行任何内容。

  • 使用 --output yaml 提供创建的清单。

参见 kubectl create deployment --help 获取有关可用参数的更多详细信息。

4.3 从文件清单创建对象

问题

不要像使用 kubectl run 这样的生成器一样创建对象,你希望明确声明其属性,然后创建它。

解决方案

使用 kubectl apply 命令如下所示:

$ kubectl apply -f *<manifest>*

在 Recipe 7.3 中,你将看到如何使用 YAML 清单创建命名空间。这是一个非常简单的示例,因为清单非常简短。它可以用 YAML 或 JSON 编写,例如,使用 YAML 清单文件 myns.yaml 如下所示:

apiVersion: v1
kind: Namespace
metadata:
  name: myns

您可以使用以下命令创建此对象:

$ kubectl apply -f myns.yaml

使用以下命令检查命名空间是否已创建:

$ kubectl get namespaces

讨论

你可以将kubectl apply指向 URL 而不是本地文件系统中的文件名。例如,要为规范的 Guestbook 应用程序创建前端,请获取定义应用程序的原始 YAML 的 URL,并输入以下内容:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/
    master/guestbook/all-in-one/guestbook-all-in-one.yaml

检查通过此操作创建的资源,例如:

$ kubectl get all

4.4 从头编写 Pod 清单

问题

你希望从头开始编写 Pod 清单并以声明性方式应用它,而不是使用kubectl run这样的命令,后者是命令式的,不需要手动编辑清单。

解决方案

一个 Pod 是一个/api/v1对象,与任何其他 Kubernetes 对象一样,其清单文件包含以下字段:

  • apiVersion,指定 API 版本

  • kind,指示对象类型

  • metadata,提供了关于对象的一些元数据

  • spec,提供对象规范

Pod 清单包含容器数组和可选的卷数组(参见第八章)。在其最简单的形式中,具有单个容器且没有卷的情况下,看起来类似于这样:

apiVersion: v1
kind: Pod
metadata:
  name: oreilly
spec:
  containers:
  - name: oreilly
    image: nginx:1.25.2

将此 YAML 清单保存到名为oreilly.yaml的文件中,然后使用kubectl创建它:

$ kubectl apply -f oreilly.yaml

检查通过此操作创建的资源,例如:

$ kubectl get all

讨论

Pod 的 API 规范比解决方案中显示的要丰富得多,后者是最基本的运行 Pod。例如,一个 Pod 可以包含多个容器,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: oreilly
spec:
  containers:
  - name: oreilly
    image: nginx:1.25.2
  - name: safari
    image: redis:7.2.0

一个 Pod 还可以包含卷定义,以在容器中加载数据(参见 Recipe 8.1),以及用于检查容器化应用程序健康状况的探针(参见 Recipes 11.2 和 11.3)。

关于许多规范字段背后思想的描述以及完整 API 对象规范的链接都在文档中详细说明。

注意

除非有非常特定的原因,否则不要单独创建 Pod。使用Deployment对象(参见 Recipe 4.5)来监督 Pod——它将通过另一个称为ReplicaSet的对象监视 Pod。

4.5 使用清单启动部署

问题

你希望对(长期运行的)应用程序的启动和监控拥有完全的控制权。

解决方案

编写一个部署清单。关于基础知识,还请参见 Recipe 4.4。

假设你有一个名为fancyapp.yaml的清单文件,其内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fancyapp
spec:
  replicas: 5
  selector:
    matchLabels:
      app: fancy
  template:
    metadata:
      labels:
        app: fancy
        env: development
    spec:
      containers:
      - name: sise
        image: gcr.io/google-samples/hello-app:2.0
        ports:
        - containerPort: 8080
        env:
        - name: SIMPLE_SERVICE_VERSION
          value: "2.0"

如您所见,启动应用程序时可能希望显式执行几件事情:

  • 设置应该启动和监控的 Pod 数(replicas),或者说是相同的副本。

  • 标记它,例如使用env=development(另请参阅 Recipes 7.5 和 7.6)。

  • 设置环境变量,例如SIMPLE_SERVICE_VERSION

现在让我们看一下部署包含的内容:

$ kubectl apply -f fancyapp.yaml
deployment.apps/fancyapp created

$ kubectl get deployments
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
fancyapp   5/5     5            5           57s

$ kubectl get replicasets
NAME                  DESIRED   CURRENT   READY     AGE
fancyapp-1223770997   5         5         0         59s

$ kubectl get pods -l app=fancy
NAME                                 READY   STATUS    RESTARTS      AGE
fancyapp-74c6f7cfd7-98d97            1/1     Running   0             115s
fancyapp-74c6f7cfd7-9gm2l            1/1     Running   0             115s
fancyapp-74c6f7cfd7-kggsx            1/1     Running   0             115s
fancyapp-74c6f7cfd7-xfs6v            1/1     Running   0             115s
fancyapp-74c6f7cfd7-xntk2            1/1     Running   0             115s

警告

当您想要摆脱一个部署及其监督的复制集和 Pod 时,请执行类似kubectl delete deploy/fancyapp的命令。不要尝试删除单个 Pod,因为它们将由部署重新创建。这常常让初学者感到困惑。

部署允许您扩展应用程序(参见 Recipe 9.1),以及部署新版本或将ReplicaSet回滚到先前版本。总的来说,它们非常适合需要具有相同特性的无状态应用程序。

讨论

部署是 Pod 和复制集(RSs)的监督者,可以精细地控制何时以及如何将新的 Pod 版本部署或回滚到先前的状态。部署监督的 RSs 和 Pod 通常对您来说不感兴趣,除非例如需要调试一个 Pod(参见 Recipe 12.5)。图 4-1 展示了如何在部署修订之间来回切换。

部署修订

图 4-1. 部署修订

要生成部署的清单,可以使用kubectl create命令和--dry-run=client选项。这将允许您以 YAML 或 JSON 格式生成清单,并保存清单以供以后使用。例如,要使用容器镜像nginx创建名为fancy-app的部署清单,请执行以下命令:

$ kubectl create deployment fancyapp --image nginx:1.25.2 -o yaml \
    --dry-run=client
kind: Deployment
apiVersion: apps/v1
metadata:
  name: fancyapp
  creationTimestamp:
  labels:
    app: fancyapp
...

参见

4.6 更新部署

问题

您已经部署了一个应用,并希望部署一个新版本。

解决方案

更新您的部署,并让默认的更新策略RollingUpdate自动处理部署过程。

例如,如果您创建了一个新的容器镜像,并希望基于它更新部署,可以这样做:

$ kubectl create deployment myapp --image=gcr.io/google-samples/hello-app:1.0
deployment.apps/myapp created

$ kubectl set image deployment/myapp \
    hello-app=gcr.io/google-samples/hello-app:2.0
deployment.apps/myapp image updated

$ kubectl rollout status deployment myapp
deployment "myapp" successfully rolled out

$ kubectl rollout history deployment myapp
deployment.apps/myapp
REVISION        CHANGE-CAUSE
1               <none>
2               <none>

现在您已成功部署了新的版本,仅容器镜像发生了变化。部署的其他属性,如副本数量,保持不变。但是如果您想更新部署的其他方面,比如更改环境变量,您可以使用一些kubectl命令来进行更新。例如,要向当前部署添加端口定义,可以使用kubectl edit

$ kubectl edit deploy myapp

这个命令将在您的默认编辑器中打开当前部署,或者在设置和导出时,在由环境变量KUBE_EDITOR指定的编辑器中打开。

假设您想要添加以下端口定义(参见 Figure 4-2 以获取完整文件):

...
  ports:
  - containerPort: 9876
...

编辑过程的结果(在本例中,使用KUBE_EDITOR设置为vi)显示在 Figure 4-2 中。

保存并退出编辑器后,Kubernetes 将启动一个新的部署,现在带有定义的端口。让我们验证一下:

$ kubectl rollout history deployment myapp
deployments "sise"
REVISION        CHANGE-CAUSE
1               <none>
2               <none>
3               <none>

实际上,我们看到修订版本 3 已经根据我们使用 kubectl edit 引入的更改进行了推出。然而,CHANGE-CAUSE 列为空。您可以通过使用特殊的注解为修订版本指定更改原因。以下是为最近的修订版本设置更改原因的示例:

$ kubectl annotate deployment/myapp \
    kubernetes.io/change-cause="Added port definition."
deployment.apps/myapp annotate

编辑部署

图 4-2. 编辑部署

正如前面提到的,还有更多的 kubectl 命令可用于更新您的部署:

  • 使用 kubectl apply 从清单文件更新部署(或者如果不存在则创建它)—例如,kubectl apply -f simpleservice.yaml

  • 使用 kubectl replace 从清单文件替换部署—例如,kubectl replace -f simpleservice.yaml。请注意,与 apply 不同,要使用 replace,部署必须已经存在。

  • 使用 kubectl patch 更新特定键—例如:

    kubectl patch deployment myapp -p '{"spec": {"template":
    {"spec": {"containers":
    [{"name": "sise", "image": "gcr.io/google-samples/hello-app:2.0"}]}}}}'
    

如果在部署的新版本中出现错误或遇到问题怎么办?幸运的是,Kubernetes 通过 kubectl rollout undo 命令非常容易回滚到已知的良好状态。例如,假设最后一次编辑是错误的,您想要回滚到修订版本 2。您可以使用以下命令来执行此操作:

$ kubectl rollout undo deployment myapp ‐‐to‐revision 2

然后,您可以验证端口定义是否已通过 kubectl get deploy/myapp -o yaml 删除。

注意

部署的推出仅在更改了 pod 模板的部分(即 .spec.template 下的键),例如环境变量、端口或容器镜像时才会触发,而如副本数等部署的其他方面的更改则不会触发新的部署。

4.7 运行批处理作业

问题

您想要运行一个需要一定时间完成的过程,例如批量转换、备份操作或数据库架构升级。

解决方案

使用 Kubernetes Job 来启动和监督执行批处理过程的 pod(s)。

首先,在名为 counter-batch-job.yaml 的文件中为作业定义 Kubernetes 清单:

apiVersion: batch/v1
kind: Job
metadata:
  name: counter
spec:
  template:
    metadata:
      name: counter
    spec:
      containers:
      - name: counter
        image: busybox:1.36
        command:
         - "sh"
         - "-c"
         - "for i in 1 2 3 ; do echo $i ; done"
      restartPolicy: Never

然后启动作业并查看其状态:

$ kubectl apply -f counter-batch-job.yaml
job.batch/counter created

$ kubectl get jobs
NAME      COMPLETIONS   DURATION   AGE
counter   1/1           7s         12s

$ kubectl describe jobs/counter
Name:             counter
Namespace:        default
Selector:         controller-uid=2d21031e-7263-4ff1-becd-48406393edd5
Labels:           controller-uid=2d21031e-7263-4ff1-becd-48406393edd5
                  job-name=counter
Annotations:      batch.kubernetes.io/job-tracking:
Parallelism:      1
Completions:      1
Completion Mode:  NonIndexed
Start Time:       Mon, 03 Apr 2023 18:19:13 +0530
Completed At:     Mon, 03 Apr 2023 18:19:20 +0530
Duration:         7s
Pods Statuses:    0 Active (0 Ready) / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=2d21031e-7263-4ff1-becd-48406393edd5
           job-name=counter
  Containers:
   counter:
    Image:      busybox:1.36
    Port:       <none>
    Host Port:  <none>
    Command:
      sh
      -c
      for i in 1 2 3 ; do echo $i ; done
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  30s   job-controller  Created pod: counter-5c8s5
  Normal  Completed         23s   job-controller  Job completed

最后,您希望验证它是否确实执行了任务(从 1 到 3 进行计数):

$ kubectl logs jobs/counter
1
2
3

实际上,正如您所看到的,counter 作业按预期进行了计数。

讨论

作业成功执行后,由作业创建的 pod 将处于 Completed 状态。如果不再需要,可以删除作业,这将清理它创建的 pod:

$ kubectl delete jobs/counter 

您还可以暂时挂起作业的执行,并稍后恢复。挂起作业也将清理它创建的 pod:

$ kubectl patch jobs/counter --type=strategic --patch '{"spec":{"suspend":true}}'

要恢复作业,只需切换 suspend 标志:

$ kubectl patch jobs/counter --type=strategic \
    --patch '{"spec":{"suspend":false}}'

4.8 在 Kubernetes 管理的 pod 中按时间表运行任务

问题

您想要在由 Kubernetes 管理的 pod 内部按特定时间表运行任务。

解决方案

使用 Kubernetes CronJob 对象。CronJob 对象是更通用的 Job 对象的派生(参见 Recipe 4.7)。

您可以通过编写类似于此处显示的清单定期调度作业。在 spec 中,您可以看到一个遵循 crontab 格式的 schedule 部分。您还可以使用一些宏,例如 @hourly@daily@weekly@monthly@yearlytemplate 部分描述了将运行的 pod 和将执行的命令(此命令每小时将当前日期和时间打印到 stdout):

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hourly-date
spec:
  schedule: "0 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: date
            image: busybox:1.36
            command:
              - "sh"
              - "-c"
              - "date"
          restartPolicy: OnFailure

讨论

就像一个工作一样,cron job 也可以通过翻转 suspend 标志来暂停和恢复。例如:

$ kubectl patch cronjob.batch/hourly-date --type=strategic \
    --patch '{"spec":{"suspend":true}}'

如果您不再需要 cron job,请删除它以清理它创建的 pod:

$ kubectl delete cronjob.batch/hourly-date

参见

4.9 每个节点运行基础设施守护程序

问题

您希望启动基础设施守护程序—例如日志收集器或监控代理—以确保每个节点仅运行一个 pod。

解决方案

使用 DaemonSet 来启动和监督守护进程。例如,要在集群中的每个节点上启动一个 Fluentd 代理,创建一个名为 fluentd-daemonset.yaml 的文件,内容如下:

kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
      name: fluentd
    spec:
      containers:
      - name: fluentd
        image: gcr.io/google_containers/fluentd-elasticsearch:1.3
        env:
         - name: FLUENTD_ARGS
           value: -qq
        volumeMounts:
         - name: varlog
           mountPath: /varlog
         - name: containers
           mountPath: /var/lib/docker/containers
      volumes:
         - hostPath:
             path: /var/log
           name: varlog
         - hostPath:
             path: /var/lib/docker/containers
           name: containers

现在启动 DaemonSet,如下所示:

$ kubectl apply -f fluentd-daemonset.yaml
daemonset.apps/fluentd created

$ kubectl get ds
NAME     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd  1         1         1       1            1           <none>          60s

$ kubectl describe ds/fluentd
Name:           fluentd
Selector:       app=fluentd
Node-Selector:  <none>
Labels:         <none>
Annotations:    deprecated.daemonset.template.generation: 1
Desired Number of Nodes Scheduled: 1
Current Number of Nodes Scheduled: 1
Number of Nodes Scheduled with Up-to-date Pods: 1
Number of Nodes Scheduled with Available Pods: 1
Number of Nodes Misscheduled: 0
Pods Status:  1 Running / 0 Waiting / 0 Succeeded / 0 Failed
...

讨论

注意,在上述输出中,由于命令在 Minikube 上执行,您只会看到一个正在运行的 pod,因为此设置中只有一个节点。如果集群中有 15 个节点,则总共将有 15 个 pod,每个节点运行一个 pod。您还可以使用 DaemonSet 清单的 spec 中的 nodeSelector 部分将守护进程限制在某些节点上。

第五章:使用服务工作

在本章中,我们讨论了 Pod 在集群内部如何通信,应用程序如何发现彼此,以及如何暴露 Pod 以便从集群外部访问。

这里使用的资源称为 Kubernetes 服务,如图 5-1 所示。

服务概念

图 5-1. Kubernetes 服务概念

服务为一组 Pod 提供了稳定的虚拟 IP(VIP)地址。尽管 Pod 可能会动态变化,但服务允许客户端通过 VIP 可靠地发现和连接运行在 Pod 中的容器。VIP 中的“虚拟”意味着它不是连接到网络接口的实际 IP 地址;它的目的纯粹是将流量转发到一个或多个 Pod。保持 VIP 与 Pod 之间的映射信息更新是kube-proxy的工作,这是运行在集群每个节点上的一个进程。kube-proxy通过查询 API 服务器来了解集群中的新服务,并相应地更新节点的 iptables 规则,以提供必要的路由信息。

5.1 创建一个暴露应用程序的服务

问题

您希望为在集群内部发现和访问您的应用程序提供一种稳定和可靠的方式。

解决方案

为构成您的应用程序的 Pod 创建一个 Kubernetes 服务。

假设您使用kubectl create deployment nginx --image nginx:1.25.2创建了一个nginx部署,您可以使用kubectl expose命令自动创建一个Service对象,如下所示:

$ kubectl expose deploy/nginx --port 80
service "nginx" exposed

$ kubectl describe svc/nginx
Name:              nginx
Namespace:         default
Labels:            app=nginx
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.97.137.240
IPs:               10.97.137.240
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         172.17.0.3:80
Session Affinity:  None
Events:            <none>

当您列出Service时,您将看到对象出现:

$ kubectl get svc nginx
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx        ClusterIP   10.97.137.240   <none>        80/TCP    2s

讨论

要通过浏览器访问此服务,请在单独的终端中运行代理,如下所示:

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

然后使用以下命令在浏览器中打开:

$ open http://localhost:8001/api/v1/namespaces/default/services/nginx/proxy/

您应该看到 NGINX 的默认页面。

提示

如果您的服务似乎无法正常工作,请检查选择器中使用的标签,并验证使用kubectl get endpoints *<service-name>*是否填充了一组端点。如果没有,则很可能是您的选择器未找到任何匹配的 Pod。

如果您想手动为相同的nginx部署创建一个Service对象,您可以编写以下 YAML 文件:

apiVersion:  v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
  - port: 80

在这个 YAML 文件中需要注意的一点是选择器,它用于选择构成这个微服务抽象的所有 Pod。Kubernetes 使用Service对象动态配置所有节点上的 iptables,以便将网络流量发送到构成微服务的容器。选择是通过标签查询来完成的(参见 Recipe 7.6),并导致一个端点列表。

注意

Pod 的监管者,如DeploymentsReplicationSets,与Services的运作方式是正交的。虽然两者都通过标签找到它们管理的 Pod,但它们的任务不同:监管者监控并重新启动 Pod 的健康状态,而Services则以可靠的方式使它们可访问。

参见

5.2 验证服务的 DNS 条目

问题

您已经创建了一个服务(参见 Recipe 5.1),并希望验证您的域名系统(DNS)注册是否正常工作。

解决方案

默认情况下,Kubernetes 使用ClusterIP作为服务类型,并将服务暴露在集群内部 IP 上。如果 DNS 集群附加组件可用且正常工作,则可以通过形如$SERVICENAME.$NAMESPACE.svc.cluster.local的完全限定域名(FQDN)访问服务。

要验证这是否按预期工作,请在集群中的容器内获取交互式 shell。最简单的方法是使用kubectl run命令和busybox镜像,如下所示:

$ kubectl run busybox --rm -it --image busybox:1.36 -- /bin/sh
If you don't see a command prompt, try pressing enter.

/ # nslookup nginx
Server:		10.96.0.10
Address:	10.96.0.10:53

Name:	nginx.default.svc.cluster.local
Address: 10.100.34.223

返回的 IP 地址应对应于其集群 IP。

输入exit并按 Enter 键离开容器。

讨论

默认情况下,DNS 查询将限定在与发出请求的 Pod 相同的命名空间内。例如,在前面的示例中,如果你在运行nginx的命名空间与运行busybox的命名空间不同,那么默认情况下查找将不会返回任何结果。要指定正确的命名空间,请使用以下语法:*<service-name>*``.``*<namespace>*,例如,nginx.staging

5.3 更改服务类型

问题

如果您已经有一个现有的服务,比如类型为ClusterIP,如 Recipe 5.2 所述,并且您希望将其类型更改为NodePort或通过云提供商负载均衡器使用LoadBalancer服务类型来暴露应用程序。

解决方案

使用kubectl edit命令及您喜欢的编辑器更改服务类型。假设您有一个名为simple-nginx-svc.yaml的清单文件,内容如下:

kind: Service
apiVersion: v1
metadata:
  name: webserver
spec:
  ports:
  - port: 80
  selector:
    app: nginx

创建webserver服务并查询它:

$ kubectl apply -f simple-nginx-svc.yaml

$ kubectl get svc/webserver
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
webserver   ClusterIP   10.98.223.206   <none>        80/TCP    11s

接下来,将服务类型更改为NodePort,如下所示:

$ kubectl edit svc/webserver

这个命令将下载 API 服务器当前的服务规范,并在你的默认编辑器中打开它。请注意文末的粗体区域,在这里我们已经从ClusterIP类型更改为NodePort类型:

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this...
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name"...
  creationTimestamp: "2023-03-01T14:07:55Z"
  name: webserver
  namespace: default
  resourceVersion: "1128"
  uid: 48daed0e-a16f-4923-bd7e-1d879dc2221f
spec:
  clusterIP: 10.98.223.206
  clusterIPs:
  - 10.98.223.206
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - nodePort: 31275
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}

保存编辑(将type更改为NodePort)后,您可以验证更新的服务,如下所示:

$ kubectl get svc/webserver
NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
webserver   NodePort   10.98.223.206   <none>        80:31275/TCP   4m

$ kubectl get svc/webserver -o yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name"...
  creationTimestamp: "2023-03-01T14:07:55Z"
  name: webserver
  namespace: default
  resourceVersion: "1128"
  uid: 48daed0e-a16f-4923-bd7e-1d879dc2221f
spec:
  clusterIP: 10.98.223.206
  clusterIPs:
  - 10.98.223.206
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - nodePort: 31275
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}

讨论

请注意,您可以根据您的用例更改服务类型;但是,请注意某些类型(例如LoadBalancer)可能触发公共云基础设施组件的配置,如果在没有意识和/或监控的情况下使用可能会很昂贵。

参见

5.4 部署 Ingress 控制器

问题

您希望部署一个 Ingress 控制器来了解 Ingress 对象。您对 Ingress 对象感兴趣,因为您希望从 Kubernetes 集群外部访问正在运行的应用程序;但是,您不希望创建 NodePortLoadBalancer 类型的服务。

解决方案

Ingress 控制器充当反向代理和负载均衡器。它将外部流量路由到平台内运行的 Pod,并允许您在集群上部署多个应用程序,每个应用程序都可通过主机名和/或 URI 路径进行寻址。

要使 Ingress 对象(在 Recipe 5.5 中讨论)生效,并为外部集群到您的 Pod 提供路由,您需要部署一个 Ingress 控制器:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/
controller-v1.8.1/deploy/static/provider/cloud/deploy.yaml

提示

在 Minikube 上,您可以简单地启用 ingress 插件,如下所示:

$ minikube addons enable ingress

大约一分钟后,在新创建的 ingress-nginx 命名空间中将启动一个新的 Pod。

$ kubectl get pods -n ingress-nginx
NAME                                       READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-xpqbt       0/1     Completed   0          3m39s
ingress-nginx-admission-patch-r7cnf        0/1     Completed   1          3m39s
ingress-nginx-controller-6cc5ccb977-l9hvz  1/1     Running     0          3m39s

现在您可以创建 Ingress 对象了。

讨论

NGINX 是 Kubernetes 项目正式支持的 Ingress 控制器之一,但还有许多其他 开源和商业解决方案 支持 Ingress 规范,其中许多提供更广泛的 API 管理功能。

在撰写本文时,新的 Kubernetes Gateway API 规范 正在作为 Ingress 规范的未来替代品出现,并且已经得到许多第三方网关提供商的支持。如果您刚开始使用 Ingress,考虑将 Gateway API 作为更为未来证明的起点是值得的。

参见

5.5 使服务从集群外部访问

问题

您希望从集群外部通过 URI 路径访问 Kubernetes 服务。

解决方案

使用 Ingress 控制器(参见 Recipe 5.4),通过创建 Ingress 对象进行配置。

假设您想部署一个简单的服务,可以调用并返回“Hello, world!”,请从创建部署开始:

$ kubectl create deployment web --image=gcr.io/google-samples/hello-app:2.0

然后暴露服务:

$ kubectl expose deployment web --port=8080

验证所有这些资源是否已正确创建:

$ kubectl get all -l app=web
NAME                       READY   STATUS    RESTARTS   AGE
pod/web-79b7b8f988-95tjv   1/1     Running   0          47s

NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/web   ClusterIP   10.100.87.233   <none>        8080/TCP   8s

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   1/1     1            1           47s

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-79b7b8f988   1         1         1       47s

下面是配置 URI 路径 /webhello-app 服务的 Ingress 对象清单:

$ cat nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-public
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host:
    http:
      paths:
      - path: /web
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 8080

$ kubectl apply -f nginx-ingress.yaml

现在您可以在 Kubernetes 仪表板中看到为 NGINX 创建的 Ingress 对象(图 5-2)。

nginx ingress 对象的截图

图 5-2. NGINX Ingress 对象

从 Kubernetes 仪表板上,您可以看到 NGINX 将通过 IP 地址 192.168.49.2(您的可能会有所不同)可用。基于此信息,您可以按以下方式从集群外部访问 NGINX 的 /web URI 路径:

$ curl https://192.168.49.2/web
Hello, world!
Version: 1.0.0
Hostname: web-68487bc957-v9fj8

讨论

使用以下命令而不是使用仪表板查看您的服务 IP 地址的替代方法:

$ kubectl describe ingress

通常情况下,Ingress 的工作原理如 图 5-3 所示:Ingress 控制器监听 API 服务器的 /ingresses 端点,了解新规则。然后配置路由,使外部流量到达特定的(集群内部)服务,在所示示例中是端口 9876 上的 service1

Ingress 概念

图 5-3. Ingress 概念

另请参阅

第六章:管理应用程序清单

在本章中,我们将探讨如何通过使用工具如 Helm、kompose 和 kapp 简化 Kubernetes 上应用程序的管理。这些工具主要集中在管理你的 YAML 清单文件。Helm 是一个 YAML 模板化、打包和部署工具,而 Kompose 是一个帮助你将 Docker Compose 文件迁移到 Kubernetes 资源清单的工具。kapp 是一个相对较新的工具,允许你将一组 YAML 文件管理为一个应用程序,并以此方式管理它们的部署作为单个应用程序。

6.1 安装 Helm,Kubernetes 的包管理器

问题

你不想手工编写所有的 Kubernetes 清单。相反,你希望能够在仓库中搜索一个包,并使用命令行界面下载并安装它。

解决方案

使用 Helm。Helm 包括一个名为 helm 的客户端 CLI,用于在 Kubernetes 集群上搜索和部署图表。

你可以从 GitHub 的 release 页面 下载 Helm,并将 helm 二进制文件移到你的 $PATH 中。例如,在 macOS(Intel)上,对于 v3.12.3 版本,执行以下操作:

$ wget https://get.helm.sh/helm-v3.12.3-darwin-amd64.tar.gz
$ tar -xvf helm-v3.12.3-darwin-amd64.tar.gz
$ sudo mv darwin-amd64/helm /usr/local/bin

或者,你可以使用便捷的 安装脚本 安装最新版本的 Helm:

$ wget -O get_helm.sh https://raw.githubusercontent.com/helm/helm/main/
scripts/get-helm-3

$ chmod +x get_helm.sh
$ ./get_helm.sh

讨论

Helm 是 Kubernetes 的包管理器;它将 Kubernetes 包定义为一组清单和一些元数据。这些清单实际上是模板。当 Helm 实例化包时,模板中的值被填充。Helm 包被称为 chart,并且打包的图表在图表仓库中向用户提供。

在 Linux 或 macOS 上安装 Helm 的另一种方法是使用 Homebrew 包管理器:

$ brew install helm

6.2 将图表仓库添加到 Helm

问题

你已经安装了 helm 客户端(参见 Recipe 6.1),现在你想要找到并添加图表仓库到 Helm。

解决方案

图表仓库包含打包的图表和一些元数据,这使得 Helm 能够在仓库中搜索图表。在你可以使用 Helm 安装应用程序之前,你需要找到并添加提供图表的图表仓库。

正如 Figure 6-1 所示,Artifact Hub 是一个基于 Web 的服务,允许你从各种发布者那里搜索 超过 10,000 个图表 并将图表仓库添加到 Helm 中。

工件中心搜索 Redis 的 Helm 图表的截图

图 6-1. 工件中心,搜索 Redis 的 Helm 图表

讨论

helm 命令还与 Artifact Hub 集成,允许你直接从 helm 命令行搜索 Artifact Hub。

假设你想要搜索提供 Redis 图表的发布者。你可以使用 helm search hub 命令来找到一个:

$ helm search hub --list-repo-url redis
URL                      CHART VER... APP VER...  DESCRIPTION    REPO URL
https://art...s/redis    0.1.1        6.0.8.9     A Helm cha...  https://spy8...
https://art...s-arm/...  17.8.0       7.0.8       Redis(R) i...  https://libr...
https://art...ontain...  0.15.2       0.15.0      Provides e...  https://ot-c...
...

如果您想部署由Bitnami发布的图表(一个知名的发布者,拥有超过 100 个生产质量的图表),请使用以下命令添加图表仓库:

$ helm repo add bitnami https://charts.bitnami.com/bitnami

现在你已经准备好从仓库安装图表。

6.3 使用 Helm 安装应用程序

问题

您已将图表仓库添加到 Helm 中(参见 Recipe 6.2),现在您想要搜索图表并部署它们。

解决方案

假设你想要从Bitnami chart repository部署 Redis 图表。

在搜索图表仓库之前,更新图表仓库的本地缓存索引是一个良好的实践:

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

在 Bitnami 图表库中搜索redis

$ helm search repo bitnami/redis
NAME                  CHART VERSION APP VERSION DESCRIPTION
bitnami/redis         18.0.1        7.2.0       Redis(R) is an...
bitnami/redis-cluster 9.0.1         7.2.0       Redis(R) is an...

并使用helm install部署图表:

$ helm install redis bitnami/redis

Helm 将使用默认的图表配置并创建一个名为redis的 Helm 发布。Helm 发布是在图表中定义的所有 Kubernetes 对象的集合,您可以将其作为单个单元管理。

过一段时间后,您应该会看到redis的 Pod 正在运行:

$ kubectl get all -l app.kubernetes.io/name=redis
NAME                   READY   STATUS    RESTARTS   AGE
pod/redis-master-0     1/1     Running   0          114s
pod/redis-replicas-0   1/1     Running   0          114s
pod/redis-replicas-1   1/1     Running   0          66s
pod/redis-replicas-2   1/1     Running   0          38s

NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/redis-headless  ClusterIP   None            <none>        6379/TCP   114s
service/redis-master    ClusterIP   10.105.20.184   <none>        6379/TCP   114s
service/redis-replicas  ClusterIP   10.105.27.109   <none>        6379/TCP   114s

NAME                              READY   AGE
statefulset.apps/redis-master     1/1     114s
statefulset.apps/redis-replicas   3/3     114s

讨论

helm install命令的输出可能包含有关部署的重要信息,例如从秘密中检索的密码、已部署的服务等。您可以使用helm list命令列出现有的 Helm 安装,然后使用helm status *<name>*查看该安装的详细信息:

% helm status redis
NAME: redis
LAST DEPLOYED: Fri Nov 10 09:42:17 2023
NAMESPACE: default
STATUS: deployed
...

要了解有关 Helm 图表以及如何创建自己的图表的更多信息,请参阅 Recipe 6.8。

6.4 检查图表的可自定义参数

问题

您想了解图表的可自定义参数及其默认值。

解决方案

图表发布者公开了图表的各种参数,可以在安装图表时进行配置。这些参数的默认值配置在一个Values文件中,其内容可以使用helm show values命令查看,例如:

$ helm show values bitnami/redis
...
...
## @param architecture Redis® architecture. Allowed values: `standalone` or
`replication`
##
architecture: replication
...
...

讨论

出版者通常会在Values文件中记录图表参数的使用。然而,图表的Readme文件可以提供更详尽的文档,包括特定的使用说明。要查看图表的Readme文件,使用helm show readme命令。例如:

$ helm show readme bitnami/redis
...
...
### Redis® common configuration parameters

| Name                       | Description                     | Value         |
| -------------------------- | --------------------------------| ------------- |
| `architecture`             | Redis® architecture...          | `replication` |
| `auth.enabled`             | Enable password authentication  | `true`        |
...
...

值得注意的是,这个Readme与在Artifact Hub上呈现的图表相同。

6.5 覆盖图表参数

问题

您已经了解了图表的各种可自定义参数(参见 Recipe 6.4),现在您想要自定义图表的部署。

解决方案

安装图表时,可以通过传递--set *key*``=``*value*标志来覆盖 Helm 图表的默认参数。可以多次指定该标志,或者用逗号分隔键/值对,例如:key1=value1,key2=value2

例如,您可以覆盖bitnami/redis图表的部署配置,以使用standalone架构,如下所示:

$ helm install redis bitnami/redis --set architecture=standalone

讨论

当您需要覆盖许多图表参数时,可以提供 --values 标志以输入包含所有要覆盖参数的 YAML 格式文件。对于前面的示例,创建一个名为 values.yaml 的文件,其中包含以下内容:

architecture: standalone

然后将文件输入到 helm install 中:

$ helm install redis bitnami/redis --values values.yaml

bitnami/redis 图表的 standalone 配置生成较少的 pod 资源,适用于开发目的。让我们看一下:

$ kubectl get pods
NAME             READY   STATUS    RESTARTS   AGE
redis-master-0   1/1     Running   0          3m14s

6.6 获取 Helm 发布的用户提供的参数

问题

您的 Kubernetes 集群有一个 Helm 发布,您想知道安装图表时指定的用户提供的图表参数。

解决方案

helm list 命令获取集群中存在的 Helm 发布对象列表:

$ helm list
NAME  NAMESPACE REVISION  UPDATED              STATUS    CHART        APP VERSION
redis default   1         2022-12-30 14:02...  deployed  redis-17.4.0 7.0.7

您可以使用 helm get 命令获取有关 Helm 发布的扩展信息,例如用户提供的值:

$ helm get values redis
USER-SUPPLIED VALUES:
architecture: standalone

讨论

除了 values 之外,您还可以使用 helm get 命令检索配置图表中的 YAML 清单、部署后笔记和钩子。

6.7 使用 Helm 卸载应用程序

问题

您不再需要使用 Helm 安装的应用程序(参见 Recipe 6.3),希望将其移除。

解决方案

当您使用图表安装应用程序时,它会创建一个 Helm 发布,可以作为单个单元管理。要移除使用 Helm 安装的应用程序,只需使用 helm uninstall 命令移除 release 即可。

假设您要删除一个名为 redis 的 Helm 发布:

$ helm uninstall redis
release "redis" uninstalled

Helm 将删除与发布关联的所有 Kubernetes 对象,并释放与它们对象关联的集群资源。

6.8 使用 Helm 创建自己的图表以打包您的应用程序

问题

您已经编写了一个包含多个 Kubernetes 清单的应用程序,并希望将其打包为 Helm 图表。

解决方案

使用 helm createhelm package 命令。

使用 helm create 命令生成您的图表的框架。在终端中输入该命令,并指定您的图表名称。例如,要创建一个 oreilly 图表:

$ helm create oreilly
Creating oreilly

$ tree oreilly/
oreilly/
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

3 directories, 10 files

讨论

helm create 命令为典型的 Web 应用程序生成脚手架。您可以编辑生成的脚手架并适应您的应用程序,或者如果您已经编写了自己的清单,则可以删除 templates/ 目录中的内容,并将您现有的模板复制到其中。如果您希望对清单进行模板化,请在 values.yaml 文件中编写需要替换的值。编辑元数据文件 Chart.yaml,如果有任何依赖图表,请将它们放在 /charts 目录中。

您可以通过运行以下命令在本地测试您的图表:

$ helm install oreilly-app ./oreilly

最后,您可以使用 helm package oreilly/ 将其打包,生成一个可重新分发的图表压缩包。如果要将图表发布到图表存储库,请将其复制到存储库并使用命令 helm repo index . 生成一个新的 index.yaml。更新图表注册表完成后,假设您已将图表存储库添加到 Helm(参见 Recipe 6.2),helm search repo oreilly 应该返回您的图表:

$ helm search repo oreilly
NAME            	VERSION	DESCRIPTION
oreilly/oreilly 	0.1.0  	A Helm chart for Kubernetes

参见

6.9 安装 Kompose

问题

您已经开始使用 Docker 容器并编写了一些 Docker compose 文件来定义您的多容器应用程序。现在您想开始使用 Kubernetes,并想知道是否以及如何重用您的 Docker compose 文件。

解决方案

使用 Kompose。Kompose 是一个将 Docker compose 文件转换为 Kubernetes(或 OpenShift)清单的工具。

首先,从 GitHub 发布页面 下载 kompose 并将其移动到您的 $PATH,以便使用时更加方便。

例如,在 macOS 上,执行以下操作:

$ wget https://github.com/kubernetes/kompose/releases/download/v1.27.0/
kompose-darwin-amd64 -O kompose

$ sudo install -m 755 kompose /usr/local/bin/kompose
$ kompose version

或者,Linux 和 macOS 用户可以使用 Homebrew 软件包管理器安装 kompose 命令行工具:

$ brew install kompose

6.10 将 Docker Compose 文件转换为 Kubernetes Manifests

问题

您已经安装了 kompose 命令(参见 Recipe 6.9),现在您想将 Docker compose 文件转换为 Kubernetes manifests。

解决方案

假设您有以下启动 redis 容器的 Docker compose 文件:

version: '2'

services:
  redis:
    image: redis:7.2.0
    ports:
    - "6379:6379"

使用 Kompose,您可以使用以下命令自动将其转换为 Kubernetes manifests:

$ kompose convert

Kompose 将读取 Docker compose 文件的内容并在当前目录生成 Kubernetes manifests。然后您可以使用 kubectl apply 在集群中创建这些资源。

讨论

--stdout 参数添加到 kompose convert 命令中将生成 YAML,可以直接传输到 kubectl apply,如下所示:

$ kompose convert --stdout | kubectl apply -f -

一些 Docker compose 指令未转换为 Kubernetes。在这种情况下,kompose 会打印警告,告知您转换未成功。

虽然通常不会引起问题,但转换可能导致 Kubernetes 中的工作清单不起作用。这是预期的,因为这种转换类型无法完美。但是,它将使您接近一个可工作的 Kubernetes manifest。特别是,处理卷和网络隔离通常需要您手动进行自定义工作。

6.11 将 Docker Compose 文件转换为 Helm Chart

问题

您已经安装了 kompose 命令(参见 Recipe 6.9),现在您想从 Docker compose 文件创建一个 Helm chart。

解决方案

除了使用 Kompose 将 Docker compose 文件转换为 Kubernetes 清单(参见 Recipe 6.10)外,您还可以使用它为转换后的对象生成 Helm 图表。

使用 Kompose 从您的 Docker compose 文件生成 Helm 图表,如下所示:

$ kompose convert --chart

将在当前目录中生成一个新的 Helm 图表。可以使用 helm CLI 打包、部署和管理此图表(参见 Recipe 6.3)。

6.12 安装 kapp

问题

您已编写了部署应用程序到集群的 YAML 文件,并希望部署和管理部署的生命周期,但不想将其打包为 Helm 图表。

解决方案

使用 kapp,这是一个 CLI 工具,可以批量管理资源。与 Helm 不同,kapp 将 YAML 模板化视为其范围之外,并专注于管理应用程序部署。

要安装 kapp,请使用 下载脚本 从 GitHub 发布页面 下载适合您平台的最新版本:

$ mkdir local-bin/
$ wget https://carvel.dev/install.sh -qO - | \
    K14SIO_INSTALL_BIN_DIR=local-bin bash

$ sudo install -m 755 local-bin/kapp /usr/local/bin/kapp
$ kapp version

讨论

Linux 和 macOS 用户还可以使用 Homebrew 软件包管理器安装 kapp:

$ brew tap vmware-tanzu/carvel
$ brew install kapp
$ kapp version

6.13 使用 kapp 部署 YAML 清单

问题

您已安装了 kapp(参见 Recipe 6.12),现在您希望使用 kapp 部署和管理您的 YAML 清单。

解决方案

kapp 将一组具有相同标签的资源视为一个应用程序。假设您有一个名为 manifests/ 的文件夹,其中包含部署 NGINX 服务器的 YAML 文件。kapp 将所有这些清单视为单个应用程序:

$ cat manifests/deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.2
        ports:
        - containerPort: 80

$ cat manifests/svc.yaml
apiVersion:  v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
  - port: 80

要将这些清单部署为带有标签 nginx 的应用程序,请使用以下命令:

$ kapp deploy -a nginx -f manifests/
...
Namespace  Name   Kind        Age  Op      Op st.  Wait to    Rs  Ri
default    nginx  Deployment  -    create  -       reconcile  -   -
^          nginx  Service     -    create  -       reconcile  -   -
...
Continue? [yN]:

kapp 将提供将在集群上创建的资源的概述,并要求用户确认。要更新应用程序,您只需更新 manifests/ 文件夹中的 YAML 文件,并重新运行 deploy 命令。您可以添加 --diff-changes 选项以查看更新后 YAML 的差异。

讨论

使用 kapp 部署应用程序后,您还可以管理其生命周期。例如,要检查为应用程序部署创建的资源,请执行以下操作:

$ kapp inspect -a nginx
...
Name   Namespaces  Lcs   Lca
nginx  default     true  4s
...

要列出所有已部署的应用程序,请执行以下操作:

$ kapp ls
...
Name   Namespaces  Lcs   Lca
nginx  default     true  4s
...

要使用 kapp 删除已部署的应用程序,请执行以下操作:

$ kapp delete -a nginx

第七章:探索 Kubernetes API 和关键元数据

在本章中,我们提供了处理 Kubernetes 对象及其 API 基本交互的实例。每个 Kubernetes 中的对象,无论是像部署一样的有命名空间的对象,还是像节点一样的全局对象,都有一些可用的字段 — 例如 metadataspecstatusspec 描述了对象的期望状态(规范),而 status 则捕获了对象的实际状态,由 Kubernetes API 服务器管理。

7.1 探索 Kubernetes API 服务器的端点

问题

您希望发现 Kubernetes API 服务器上可用的各种 API 端点。

解决方案

在这里,我们假设您已经在本地启动了一个开发集群,如 kind 或 Minikube。您可以在单独的终端中运行 kubectl proxy。代理允许您使用诸如 curl 这样的 HTTP 客户端轻松访问 Kubernetes 服务器 API,而无需担心身份验证和证书。运行 kubectl proxy 后,您应该能够通过端口 8001 访问 API 服务器,如下所示:

$ curl http://localhost:8001/api/v1/
{
  "kind": "APIResourceList",
  "groupVersion": "v1",
  "resources": [
    {
      "name": "bindings",
      "singularName": "",
      "namespaced": true,
      "kind": "Binding",
      "verbs": [
        "create"
      ]
    },
    {
      "name": "componentstatuses",
      "singularName": "",
      "namespaced": false,
      ...

这列出了 Kubernetes API 暴露的所有对象。在列表顶部,您可以看到一个对象的示例,其 kind 设置为 Binding,以及此对象上允许的操作(此处为 create)。

注意,发现 API 端点的另一个便捷方式是使用 kubectl api-resources 命令。

讨论

您可以通过调用以下端点来发现所有的 API 组:

$ curl http://localhost:8001/apis/
{
  "kind": "APIGroupList",
  "apiVersion": "v1",
  "groups": [
    {
      "name": "apiregistration.k8s.io",
      "versions": [
        {
          "groupVersion": "apiregistration.k8s.io/v1",
          "version": "v1"
        }
      ],
      "preferredVersion": {
        "groupVersion": "apiregistration.k8s.io/v1",
        "version": "v1"
      }
    },
    {
      "name": "apps",
      "versions": [
      ...

从此列表中选择一些要探索的 API 组,例如以下内容:

  • /apis/apps

  • /apis/storage.k8s.io

  • /apis/flowcontrol.apiserver.k8s.io

  • /apis/autoscaling

每个这些端点对应一个 API 组。核心 API 对象在 /api/v1 组中可用,而其他更新的 API 对象在 /apis/ 端点下的命名组中可用,例如 storage.k8s.io/v1apps/v1。在一个组内,API 对象被版本化(例如 v1v2v1alphav1beta1)以指示对象的成熟度。例如,Pods、services、config maps 和 secrets 都是 /api/v1 API 组的一部分,而 /apis/autoscaling 组则有 v1v2 版本。

对象所属的组被称为对象规范中的 apiVersion,可通过 API 参考 获取。

参见

7.2 理解 Kubernetes 清单的结构

问题

尽管 Kubernetes 具有方便的生成器如 kubectl runkubectl create,您必须学会如何编写 Kubernetes 清单文件以支持 Kubernetes 对象规范的声明性特性。为此,您需要理解清单的一般结构。

解决方案

在 Recipe 7.1 中,您了解了各种 API 组以及如何发现特定对象所属的组。

所有 API 资源都是对象或列表。所有资源都有一个 kind 和一个 ap⁠i​Ve⁠rs⁠ion。此外,每个对象 kind 必须有 metadatametadata 包含对象的名称、它所在的命名空间(参见 Recipe 7.3)、以及可选的一些标签(参见 Recipe 7.6)和注释(参见 Recipe 7.7)。

例如,一个 pod 将是 kind PodapiVersion v1,并且以 YAML 编写的简单清单的开头看起来像这样:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
...

要完成一个清单,大多数对象都将有一个 spec,一旦创建,还会返回一个描述对象当前状态的 status

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  ...
status:
  ...

讨论

Kubernetes 清单可用于定义集群的期望状态。由于清单是文件,可以将其存储在像 Git 这样的版本控制系统中。这允许开发人员和运维人员进行分布式和异步的协作,并且还支持为持续集成和部署创建自动化流水线。这就是 GitOps 的基本理念,其中对系统的任何更改都是通过更改版本控制系统中的真实源来进行的。由于所有更改都记录在系统中,因此可以恢复到先前的状态或多次重现给定的状态。基础设施即代码(IaC)是一个经常用来描述基础设施状态的声明性真实源的术语(与应用程序相对)。

参见

7.3 创建命名空间以避免名称冲突

问题

您希望创建两个具有相同名称的对象,但希望避免名称冲突。

解决方案

创建两个命名空间,并在每个命名空间中创建一个对象。

如果您没有指定任何内容,则对象将在 default 命名空间中创建。尝试创建一个名为 my-app 的第二个命名空间,如下所示,并列出现有的命名空间。您将看到 default 命名空间,以及启动时创建的其他命名空间(kube-systemkube-publickube-node-lease),以及您刚刚创建的 my-app 命名空间:

$ kubectl create namespace my-app
namespace/my-app created

$ kubectl get ns
NAME                 STATUS   AGE
default              Active   5d20h
kube-node-lease      Active   5d20h
kube-public          Active   5d20h
kube-system          Active   5d20h
my-app               Active   13s

注意

或者,您可以编写一个清单来创建您的命名空间。如果您将以下清单保存为 app.yaml,然后可以使用 kubectl apply -f app.yaml 命令创建命名空间:

apiVersion: v1
kind: Namespace
metadata:
  name: my-app

讨论

尝试在同一命名空间中启动两个具有相同名称的对象(例如 default)会导致冲突,并且 Kubernetes API 服务器将返回错误。但是,如果您在不同的命名空间中启动第二个对象,API 服务器将会创建它:

$ kubectl run foobar --image=nginx:latest
pod/foobar created

$ kubectl run foobar --image=nginx:latest
Error from server (AlreadyExists): pods "foobar" already exists

$ kubectl run foobar --image=nginx:latest --namespace my-app
pod/foobar created

注意

kube-system 命名空间是保留给管理员的,而 kube-public 命名空间 则用于存储对集群中任何用户可用的公共对象。

7.4 在命名空间内设置配额

问题

您希望限制命名空间中可用的资源,例如命名空间中可以运行的 pod 总数。

解决方案

使用 ResourceQuota 对象来指定命名空间基础上的限制。

首先创建一个资源配额的清单,并将其保存在名为 resource-quota-pods.yaml 的文件中:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: podquota
spec:
  hard:
    pods: "10"

然后创建一个新的命名空间并对其应用配额:

$ kubectl create namespace my-app
namespace/my-app created

$ kubectl apply -f resource-quota-pods.yaml --namespace=my-app
resourcequota/podquota created

$ kubectl describe resourcequota podquota --namespace=my-app
Name:       podquota
Namespace:  my-app
Resource    Used  Hard
--------    ----  ----
pods        1     10

讨论

您可以在每个命名空间基础上设置多个配额,包括但不限于 pods、secrets 和 config maps。

参见

7.5 给对象打标签

问题

您希望为对象打上标签,以便稍后轻松找到它。标签可用于进一步的最终用户查询(参见 Recipe 7.6)或在系统自动化的上下文中使用。

解决方案

使用 kubectl label 命令。例如,要为名为 foobar 的 pod 打上键/值对 tier=frontend 的标签,请执行以下操作:

$ kubectl label pods foobar tier=frontend
pod/foobar labeled

提示

查看命令的完整帮助 (kubectl label --help)。您可以使用它来了解如何删除标签、覆盖现有标签,甚至为命名空间中的所有资源打上标签。

讨论

在 Kubernetes 中,您可以使用标签以灵活、非层次化的方式组织对象。标签是一个在 Kubernetes 中没有预定义含义的键/值对。换句话说,系统不会解释键/值对的内容。您可以使用标签来表示成员关系(例如,对象 X 属于部门 ABC)、环境(例如,此服务在生产环境运行)或任何需要组织对象的内容。您可以在 Kubernetes 文档 中了解一些常用的有用标签。请注意,标签在其 长度和允许值 方面有一些限制。然而,对于键的命名,有一个 社区指南

7.6 使用标签进行查询

问题

您希望高效查询对象。

解决方案

使用 kubectl get --selector 命令。例如,给定以下的 pods:

$ kubectl get pods --show-labels
NAME         READY   STATUS    RESTARTS        AGE     LABELS
foobar       1/1     Running   0               18m     run=foobar,tier=frontend
nginx1       1/1     Running   0               72s     app=nginx,run=nginx1
nginx2       1/1     Running   0               68s     app=nginx,run=nginx2
nginx3       1/1     Running   0               65s     app=nginx,run=nginx3

您可以选择属于 NGINX 应用程序 (app=nginx) 的 pods:

$ kubectl get pods --selector app=nginx
NAME     READY   STATUS    RESTARTS   AGE
nginx1   1/1     Running   0          3m45s
nginx2   1/1     Running   0          3m41s
nginx3   1/1     Running   0          3m38s

讨论

标签是对象元数据的一部分。在 Kubernetes 中,任何对象都可以被标记。标签也被 Kubernetes 本身用于通过 deployments(参见 Recipe 4.1)和 services(参见 Chapter 5)选择 pod。

标签可以通过 kubectl label 命令手动添加(参见 Recipe 7.5),或者您可以在对象清单中定义标签,如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: foobar
  labels:
    tier: frontend
...

一旦标签存在,您可以使用 kubectl get 列出它们,注意以下内容:

  • -l--selector 的缩写形式,将查询具有指定 *key*``=``*value* 对的对象。

  • --show-labels 将显示返回的每个对象的所有标签。

  • -L 将在返回的结果中添加一个列,该列的值为指定标签的值。

  • 许多对象类型支持基于集合的查询,这意味着您可以以“必须具有 X 和/或 Y 标签”的形式进行查询。例如,kubectl get pods -l 'env in (production, development)' 将给出在生产环境或开发环境中的 Pod。

当有两个运行中的 Pod,一个带有标签run=barfoo,另一个带有标签ru⁠n=​fo⁠oba⁠r时,您将会得到类似以下的输出:

$ kubectl get pods --show-labels
NAME                      READY   ...    LABELS
barfoo-76081199-h3gwx     1/1     ...    pod-template-hash=76081199,run=barfoo
foobar-1123019601-6x9w1   1/1     ...    pod-template-hash=1123019601,run=foobar

$ kubectl get pods -L run
NAME                      READY   ...    RUN
barfoo-76081199-h3gwx     1/1     ...    barfoo
foobar-1123019601-6x9w1   1/1     ...    foobar

$ kubectl get pods -l run=foobar
NAME                      READY   ...
foobar-1123019601-6x9w1   1/1     ...

参见

7.7 使用一个命令给资源加注释

问题

您希望使用一个通用的、非标识的键值对对资源进行注释,可能使用非人类可读的数据。

解决方案

使用kubectl annotate命令:

$ kubectl annotate pods foobar \
    description='something that you can use for automation'
pod/foobar annotated

讨论

注释通常用于增强 Kubernetes 的自动化。例如,当您使用kubectl create deployment命令创建一个部署时,您会注意到您的发布历史中的change-cause列(参见 Recipe 4.6)是空的。从 Kubernetes v1.6.0 开始,您可以使用kubernetes.io/change-cause键对其进行注释,以开始记录导致部署变更的命令。假设有一个名为 foobar 的部署,您可以使用以下内容对其进行注释:

$ kubectl annotate deployment foobar \
    kubernetes.io/change-cause="Reason for creating a new revision"

对部署的后续更改将被记录下来。

注释和标签之间的主要区别之一在于,标签可以用作过滤条件,而注释则不能。除非您计划将元数据用于过滤,否则通常更倾向于使用注释。

第八章:卷和配置数据

在 Kubernetes 中,volume 是一个对所有在 pod 中运行的容器可访问的目录,还保证数据跨单个容器重启时保持不变。

我们可以区分几种类型的卷:

  • 节点本地 临时卷,例如 emptyDir

  • 通用网络卷,例如 nfscephfs

  • 云服务提供商特定 卷,例如 AWS EBSAWS EFS

  • 特殊用途卷,例如 secretconfigMap

你选择哪种卷类型完全取决于你的用例。例如,对于临时的临时空间,emptyDir 可能足够了,但是当你需要确保数据在节点故障时能够存活时,你需要考虑更具弹性的替代方案或特定于云提供商的解决方案。

8.1 通过本地卷在容器之间交换数据

问题

你有两个或更多的容器在一个 pod 中运行,并希望能够通过文件系统操作交换数据。

解决方案

使用类型为 emptyDir 的本地卷。

起始点是以下 pod 清单 exchangedata.yaml,其中有两个容器(c1c2),它们每个都将本地卷 xchange 挂载到它们的文件系统中,使用不同的挂载点:

apiVersion: v1
kind: Pod
metadata:
  name: sharevol
spec:
  containers:
  - name: c1
    image: ubuntu:20.04
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"
    volumeMounts:
      - name: xchange
        mountPath: "/tmp/xchange"
  - name: c2
    image: ubuntu:20.04
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"
    volumeMounts:
      - name: xchange
        mountPath: "/tmp/data"
  volumes:
  - name: xchange
    emptyDir: {}

现在你可以启动 pod,exec 进入它,从一个容器中创建数据,然后从另一个容器中读取它:

$ kubectl apply -f exchangedata.yaml
pod/sharevol created

$ kubectl exec sharevol -c c1 -i -t -- bash
[root@sharevol /]# mount | grep xchange
/dev/vda1 on /tmp/xchange type ext4 (rw,relatime)
[root@sharevol /]# echo 'some data' > /tmp/xchange/data
[root@sharevol /]# exit

$ kubectl exec sharevol -c c2 -i -t -- bash
[root@sharevol /]# mount | grep /tmp/data
/dev/vda1 on /tmp/data type ext4 (rw,relatime)
[root@sharevol /]# cat /tmp/data/data
some data
[root@sharevol /]# exit

讨论

本地卷由运行 pod 及其容器的节点支持。如果节点宕机或需要对其进行维护(参见 Recipe 12.9),那么本地卷就会丢失,并且所有数据也会丢失。

有些用例中,本地卷是可以的——例如一些临时空间或者从其他地方(例如 S3 存储桶)获取规范状态时——但一般来说,你应该使用持久卷或由网络存储支持的卷(参见 Recipe 8.4)。

参见

  • Kubernetes 关于 volumes 的文档

8.2 使用秘密向 pod 传递 API 访问密钥

问题

作为管理员,你希望以安全的方式向你的开发者提供 API 访问密钥,即在 Kubernetes 清单中不明文共享它。

解决方案

使用类型为 secret 的本地卷。

假设你想要让你的开发者能够访问一个通过口令 open sesame 访问的外部服务。

首先,创建一个名为 passphrase 的文本文件,其中包含口令:

$ echo -n "open sesame" > ./passphrase

接下来,使用 passphrase 文件创建 secret

$ kubectl create secret generic pp --from-file=./passphrase
secret/pp created

$ kubectl describe secrets/pp
Name:           pp
Namespace:      default
Labels:         <none>
Annotations:    <none>

Type:   Opaque

Data
====
passphrase:     11 bytes

从管理员角度来看,你现在已经准备就绪,是开发者消费秘密的时候了。所以让我们换个角色,假设你是一个开发者,想要在 pod 内部使用口令。

例如,你可以将其作为一个卷挂载到你的 pod 中,然后像普通文件一样读取它。创建并保存以下名为 ppconsumer.yaml 的清单:

apiVersion: v1
kind: Pod
metadata:
  name: ppconsumer
spec:
  containers:
  - name: shell
    image: busybox:1.36
    command:
      - "sh"
      - "-c"
      - "mount | grep access  && sleep 3600"
    volumeMounts:
      - name: passphrase
        mountPath: "/tmp/access"
        readOnly: true
  volumes:
  - name: passphrase
    secret:
      secretName: pp

现在启动 Pod 并查看其日志,您期望看到ppconsumer密钥文件挂载为/tmp/access/passphrase

$ kubectl apply -f ppconsumer.yaml
pod/ppconsumer created

$ kubectl logs ppconsumer
tmpfs on /tmp/access type tmpfs (ro,relatime,size=7937656k)

要从运行中的容器中访问密码,只需读取/tmp/access中的passphrase文件,如下所示:

$ kubectl exec ppconsumer -i -t -- sh

/ # cat /tmp/access/passphrase
open sesame
/ # exit

讨论

秘密存在于命名空间的上下文中,因此在设置它们和/或使用它们时需要考虑到这一点。

您可以通过以下方法之一访问运行在 Pod 中的容器的秘密:

此外,请注意秘密的大小限制为 1 MiB。

小贴士

kubectl create secret处理三种类型的秘密,根据您的用例,您可能需要选择不同的秘密类型:

  • docker-registry类型用于与 Docker 注册表一起使用。

  • generic类型是我们在解决方案中使用的;它从本地文件、目录或文字值创建秘密(您需要自行进行 base64 编码)。

  • 使用tls,例如,您可以创建一个安全的 SSL 证书用于入口。

kubectl describe不会以纯文本显示秘密内容。这避免了“偷看”密码。但是,由于它只是 base64 编码而不是加密,您可以轻松地手动解码它:

$ kubectl get secret pp -o yaml | \
    grep passphrase | \
    cut -d":" -f 2 | \
    awk '{$1=$1};1' | \
    base64 --decode
open sesame

在此命令中,第一行检索秘密的 YAML 表示,第二行使用grep提取passphrase: b3BlbiBzZXNhbWU=行(请注意这里的前导空格)。然后,cut提取出密码的内容,awk命令去掉前导空格。最后,base64命令将其转换回原始数据。

小贴士

您可以使用--encryption-provider-config选项在启动kube-apiserver时选择加密秘密。

另请参阅

8.3 提供配置数据给应用程序

问题

您希望为应用程序提供配置数据,而不将其存储在容器映像中或硬编码到 Pod 规范中。

解决方案

使用配置映射。这些是 Kubernetes 的一流资源,您可以通过环境变量或文件向 Pod 提供配置数据。

假设您想创建一个带有键siseversion和值0.9的配置。只需这样简单:

$ kubectl create configmap nginxconfig \
    --from-literal=nginxgreeting="hello from nginx"
configmap/nginxconfig created

现在您可以在部署中使用配置映射,比如说,在一个具有以下内容的清单文件中:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.25.2
    env:
    - name: NGINX_GREETING
      valueFrom:
        configMapKeyRef:
          name: nginxconfig
          key: nginxgreeting

将此 YAML 清单保存为nginxpod.yaml,然后使用kubectl创建 Pod:

$ kubectl apply -f nginxpod.yaml
pod/nginx created

您可以使用以下命令列出 Pod 的容器环境变量:

$ kubectl exec nginx -- printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=nginx
NGINX_GREETING=hello from nginx
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
...

讨论

我们刚刚展示了如何将配置作为环境变量传递。但是,您也可以将其作为文件挂载到 Pod 中,使用一个卷。

假设您有以下配置文件 example.cfg

debug: true
home: ~/abc

您可以创建一个包含配置文件的配置映射,如下所示:

$ kubectl create configmap configfile --from-file=example.cfg
configmap/configfile created

现在,您可以像使用任何其他卷一样使用配置映射。以下是名为 oreilly 的 Pod 的清单文件;它使用 busybox 镜像并休眠 3,600 秒。在 volumes 部分,有一个名为 oreilly 的卷,它使用我们刚刚创建的配置映射 configfile。然后,在容器内部的路径 /oreilly 处挂载此卷。因此,文件将在 Pod 内可访问:

apiVersion: v1
kind: Pod
metadata:
  name: oreilly
spec:
  containers:
  - image: busybox:1.36
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /oreilly
      name: oreilly
    name: busybox
  volumes:
  - name: oreilly
    configMap:
      name: configfile

创建完 Pod 后,您可以验证 example.cfg 文件确实存在其中:

$ kubectl exec -ti oreilly -- ls -l oreilly
total 0
lrwxrwxrwx   1 root   root   18 Mar 31 09:39 example.cfg -> ..data/example.cfg

$ kubectl exec -ti oreilly -- cat oreilly/example.cfg
debug: true
home: ~/abc

关于如何从文件创建配置映射的完整示例,请参见 Recipe 11.7。

另请参阅

8.4 在 Minikube 中使用持久卷

问题

您不希望在容器使用的磁盘上丢失数据,即希望确保它在托管 Pod 重新启动后能够存活。

解决方案

使用持久卷(PV)。在 Minikube 的情况下,您可以创建一个 hostPath 类型的 PV,并像普通卷一样将其挂载到容器的文件系统中。

首先,在名为 hostpath-pv.yaml 的清单中定义 PV hostpathpv

apiVersion: v1
kind: PersistentVolume
metadata:
  name: hostpathpv
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  hostPath:
    path: "/tmp/pvdata"

但在创建 PV 之前,您需要准备节点上的目录 /tmp/pvdata,即 Minikube 实例本身。您可以使用 minikube ssh 进入运行 Kubernetes 集群的节点:

$ minikube ssh

$ mkdir /tmp/pvdata && \
    echo 'I am content served from a delicious persistent volume' > \
    /tmp/pvdata/index.html

$ cat /tmp/pvdata/index.html
I am content served from a delicious persistent volume

$ exit

现在,您已经在节点上准备好了目录,可以从清单文件 hostpath-pv.yaml 创建 PV:

$ kubectl apply -f hostpath-pv.yaml
persistentvolume/hostpathpv created

$ kubectl get pv
NAME        CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      ...   ...   ...
hostpathpv  1Gi        RWO           Retain          Available   ...   ...   ...

$ kubectl describe pv/hostpathpv
Name:            hostpathpv
Labels:          type=local
Annotations:     <none>
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    manual
Status:          Available
Claim:
Reclaim Policy:  Retain
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:
Source:
    Type:          HostPath (bare host directory volume)
    Path:          /tmp/pvdata
    HostPathType:
Events:            <none>

到目前为止,您将在管理员角色中执行这些步骤。您将定义 PV 并使其在 Kubernetes 集群中对开发者可用。

现在从开发者的角度来看,您可以在 Pod 中使用 PV。这是通过持久卷声明(PVC)完成的,因为您确实声明了一个 PV,满足某些特征,如大小或存储类。

创建一个名为 pvc.yaml 的清单文件,定义一个 PVC,请求 200 MB 的空间:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
spec:
  storageClassName: manual
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 200Mi

接下来,启动 PVC 并验证其状态:

$ kubectl apply -f pvc.yaml
persistentvolumeclaim/mypvc created

$ kubectl get pv
NAME        CAPACITY  ACCESSMODES  ...  STATUS  CLAIM          STORAGECLASS
hostpathpv  1Gi       RWO          ...  Bound   default/mypvc  manual

注意,PV hostpathpv 的状态已从 Available 变为 Bound

最后,是时候在容器中消耗 PV 中的数据了,这次是通过将其挂载到文件系统中的部署完成的。因此,请创建一个名为 nginx-using-pv.yaml 的文件,并包含以下内容:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-with-pv
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: webserver
        image: nginx:1.25.2
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: webservercontent
      volumes:
      - name: webservercontent
        persistentVolumeClaim:
          claimName: mypvc

并启动部署,如下所示:

$ kubectl apply -f nginx-using-pv.yaml
deployment.apps/nginx-with-pv created

$ kubectl get pvc
NAME   STATUS  VOLUME      CAPACITY  ACCESSMODES  STORAGECLASS  AGE
mypvc  Bound   hostpathpv  1Gi       RWO          manual        12m

正如您所看到的,PV 正通过您之前创建的 PVC 使用中。

要验证数据确实已到达,您现在可以创建一个服务(参见 Recipe 5.1)以及一个 Ingress 对象(参见 Recipe 5.5),然后像这样访问它:

$ curl -k -s https://192.168.99.100/web
I am content served from a delicious persistent volume

干得好!您(作为管理员)已经提供了持久卷,并(作为开发者)通过持久卷声明对其进行了声明,并通过将其挂载到容器文件系统中的 Pod 中使用。

讨论

在解决方案中,我们使用了 hostPath 类型的持久卷。在生产环境中,您不希望使用这个,而是要求您的集群管理员提供由 NFS 或 Amazon Elastic Block Store (EBS) 卷支持的网络化卷,以确保您的数据保持和在单节点故障时能够存活。

注意

请记住,PV 是集群范围的资源;即它们不是命名空间的。但是,PVC 是命名空间的。您可以使用命名空间 PVC 从特定命名空间声明 PV。

参见

8.5 在 Minikube 上理解数据持久性

问题

您希望使用 Minikube 在 Kubernetes 中部署一个有状态的应用程序。具体来说,您想部署一个 MySQL 数据库。

解决方案

在您的 pod 定义和/或数据库模板中使用 PersistentVolumeClaim 对象(参见 Recipe 8.4)。

首先,您需要请求特定数量的存储空间。以下的 data.yaml 清单请求了 1 GB 的存储空间:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

在 Minikube 上,创建此 PVC 并立即查看如何创建与此声明匹配的持久卷:

$ kubectl apply -f data.yaml
persistentvolumeclaim/data created

$ kubectl get pvc
NAME  STATUS  VOLUME                                    CAPACITY ...  ...  ...
data  Bound   pvc-da58c85c-e29a-11e7-ac0b-080027fcc0e7  1Gi      ...  ...  ...

$ kubectl get pv
NAME                                      CAPACITY  ...  ...  ...  ...  ...
pvc-da58c85c-e29a-11e7-ac0b-080027fcc0e7  1Gi       ...  ...  ...  ...  ...

您现在可以在您的 pod 中使用此声明。在 pod 清单的 volumes 部分,通过名称定义一个 PVC 类型的卷,并引用您刚刚创建的 PVC。

volumeMounts 字段中,您将在容器内的特定路径挂载此卷。对于 MySQL,您将其挂载在 /var/lib/mysql 下:

apiVersion: v1
kind: Pod
metadata:
  name: db
spec:
  containers:
  - image: mysql:8.1.0
    name: db
    volumeMounts:
    - mountPath: /var/lib/mysql
      name: data
    env:
      - name: MYSQL_ROOT_PASSWORD
        value: root
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: data

讨论

Minikube 默认配置了一个默认存储类别,定义了默认的持久卷供应者。这意味着当创建持久卷声明时,Kubernetes 将动态创建匹配的持久卷以填充该声明。

这就是解决方案中发生的事情。当您创建名为 data 的持久卷声明时,Kubernetes 自动创建了一个持久卷来匹配该声明。如果您稍微深入查看 Minikube 上的默认存储类别,您会看到供应商类型:

$ kubectl get storageclass
NAME                 PROVISIONER                ...
standard (default)   k8s.io/minikube-hostpath   ...

$ kubectl get storageclass standard -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
...
provisioner: k8s.io/minikube-hostpath
reclaimPolicy: Delete

此特定存储类别使用一个创建 hostPath 类型的存储供应者。您可以通过查看先前创建的匹配声明的 PV 的清单来查看这一点:

$ kubectl get pv
NAME                                       CAPACITY   ... CLAIM         ...
pvc-da58c85c-e29a-11e7-ac0b-080027fcc0e7   1Gi        ... default/data  ...

$ kubectl get pv pvc-da58c85c-e29a-11e7-ac0b-080027fcc0e7 -o yaml
apiVersion: v1
kind: PersistentVolume
...
  hostPath:
    path: /tmp/hostpath-provisioner/default/data
    type: ""
...

要验证所创建的主机卷是否包含数据库 data,您可以连接到 Minikube 并列出目录中的文件:

$ minikube ssh

$ ls -l /tmp/hostpath-provisioner/default/data
total 99688
...
drwxr-x--- 2 999 docker     4096 Mar 31 11:11  mysql
-rw-r----- 1 999 docker 31457280 Mar 31 11:11  mysql.ibd
lrwxrwxrwx 1 999 docker       27 Mar 31 11:11  mysql.sock -> /var/run/mysqld/...
drwxr-x--- 2 999 docker     4096 Mar 31 11:11  performance_schema
-rw------- 1 999 docker     1680 Mar 31 11:11  private_key.pem
-rw-r--r-- 1 999 docker      452 Mar 31 11:11  public_key.pem
...

确实,现在您拥有数据持久性。如果 pod 崩溃(或者您删除它),您的数据仍将可用。

通常,存储类别允许集群管理员定义他们可能提供的各种存储类型。对于开发人员来说,这抽象了存储类型,让他们可以使用 PVC 而不必担心存储提供者本身。

参见

8.6 在版本控制中存储加密的秘密

问题

您希望将所有 Kubernetes 清单存储在版本控制中并安全共享它们(甚至是公开的),包括秘密。

解决方案

使用sealed-secrets。Sealed-secrets 是一个 Kubernetes 控制器,用于解密单向加密的秘密并在集群中创建Secret对象(参见 Recipe 8.2)。

要开始,请从发布页面安装v0.23.1版本的 sealed-secrets 控制器:

$ kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/
releases/download/v0.23.1/controller.yaml

结果将是您在kube-system命名空间中运行一个新的自定义资源和一个新的 pod:

$ kubectl get customresourcedefinitions
NAME                        CREATED AT
sealedsecrets.bitnami.com   2023-01-18T09:23:33Z

$ kubectl get pods -n kube-system  -l name=sealed-secrets-controller
NAME                                         READY   STATUS    RESTARTS   AGE
sealed-secrets-controller-7ff6f47d47-dd76s   1/1     Running   0          2m22s

接下来,从发布页面下载相应版本的kubeseal二进制文件。这个工具将允许您加密您的秘密。

例如,在 macOS(amd64)上,执行以下操作:

$ wget https://github.com/bitnami-labs/sealed-secrets/releases/download/
v0.23.1/kubeseal-0.23.1-darwin-amd64.tar.gz

$ tar xf kubeseal-0.23.1-darwin-amd64.tar.gz

$ sudo install -m 755 kubeseal /usr/local/bin/kubeseal

$ kubeseal --version
kubeseal version: 0.23.1

您现在可以开始使用密封的秘密了。首先,生成一个通用的秘密清单:

$ kubectl create secret generic oreilly --from-literal=password=root -o json \
    --dry-run=client > secret.json

$ cat secret.json
{
    "kind": "Secret",
    "apiVersion": "v1",
    "metadata": {
        "name": "oreilly",
        "creationTimestamp": null
    },
    "data": {
        "password": "cm9vdA=="
    }
}

然后使用kubeseal命令生成新的自定义SealedSecret对象:

$ kubeseal < secret.json > sealedsecret.json

$ cat sealedsecret.json
{
  "kind": "SealedSecret",
  "apiVersion": "bitnami.com/v1alpha1",
  "metadata": {
    "name": "oreilly",
    "namespace": "default",
    "creationTimestamp": null
  },
  "spec": {
    "template": {
      "metadata": {
        "name": "oreilly",
        "namespace": "default",
        "creationTimestamp": null
      }
    },
    "encryptedData": {
      "password": "AgCyN4kBwl/eLt7aaaCDDNlFDp5s93QaQZZ/mm5BJ6SK1WoKyZ45hz..."
    }
  }
}

使用以下命令创建SealedSecret对象:

$ kubectl apply -f sealedsecret.json
sealedsecret.bitnami.com/oreilly created

您现在可以安全地将sealedsecret.json存储在版本控制中。

讨论

创建SealedSecret对象后,控制器将检测到它,解密它,并生成相应的秘密。

您的敏感信息被加密到一个SealedSecret对象中,这是一个自定义资源(参见 Recipe 15.4)。SealedSecret安全地存储在版本控制下并共享,甚至是公开的。一旦在 Kubernetes API 服务器上创建了SealedSecret,只有存储在密封秘密控制器中的私钥才能解密它并创建相应的Secret对象(仅为 base64 编码)。没有其他人,包括原作者,可以从Se⁠al⁠ed​Se⁠cr⁠et中解密原始Secret

虽然用户无法从SealedSecret中解密原始Secret,但他们可能能够从集群中访问未封存的Secret。您应该配置 RBAC 以禁止低权限用户从他们有限制访问权限的命名空间中读取Secret对象。

您可以使用以下命令列出当前命名空间中的SealedSecret对象:

$ kubectl get sealedsecret
NAME      AGE
oreilly   14s

参见

第九章:扩展

在 Kubernetes 中,扩展可能对不同的用户意味着不同的事情。我们区分两种情况:

集群扩展

有时被称为集群弹性,这指的是根据集群利用率自动添加或移除工作节点的过程。

应用程序级别的扩展

有时被称为Pod 扩展,这指的是根据各种指标调整 Pod 特性的(自动化)过程,从低级信号如 CPU 利用率到高级信号如每秒服务的 HTTP 请求,都适用于特定的 Pod。

存在两种类型的 Pod 级别扩展器:

水平 Pod 自动伸缩器(HPAs)

HPAs 根据特定的指标自动增加或减少 Pod 副本的数量。

垂直 Pod 自动伸缩器(VPAs)

VPAs 自动增加或减少运行在 Pod 中的容器的资源需求。

在本章中,我们首先检查 GKE、AKS 和 EKS 的集群弹性,然后讨论使用 HPAs 进行 Pod 扩展。

9.1 扩展部署

问题

你有一个部署并希望进行水平扩展。

解决方案

使用kubectl scale命令扩展部署。

让我们重用来自配方 4.5 的fancyapp部署,使用五个副本。如果还没有运行,请使用kubectl apply -f fancyapp.yaml进行创建。

现在假设负载已经减少,不再需要五个副本;三个就足够了。要将部署规模缩减到三个副本,请执行以下操作:

$ kubectl get deploy fancyapp
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
fancyapp   5/5     5            5           59s

$ kubectl scale deployment fancyapp --replicas=3
deployment "fancyapp" scaled

$ kubectl get deploy fancyapp
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
fancyapp   3/3     3            3           81s

你可以自动化这个过程,而不是手动调整部署的规模;参见配方 9.2 作为例子。

9.2 使用水平 Pod 自动伸缩

问题

你想要根据负载自动增加或减少部署中的 Pod 数量。

解决方案

如此描述,使用 HPA。

要使用 HPAs,必须可用 Kubernetes Metrics API。要安装 Kubernetes Metrics Server,请参见配方 2.7。

首先,创建一个应用程序——一个 PHP 环境和服务器,作为 HPA 的目标:

$ kubectl create deployment appserver --image=registry.k8s.io/hpa-example \
    --port 80
deployment.apps/appserver created
$ kubectl expose deployment appserver --port=80 --target-port=80
$ kubectl set resources deployment appserver -c=hpa-example --requests=cpu=200m

接下来,创建一个 HPA 并定义触发参数--cpu-percent=40,这意味着 CPU 利用率不应超过 40%:

$ kubectl autoscale deployment appserver --cpu-percent=40 --min=1 --max=5
horizontalpodautoscaler.autoscaling/appserver autoscaled

$ kubectl get hpa --watch
NAME        REFERENCE              TARGETS   MINPODS   MAXPODS  REPLICAS   AGE
appserver   Deployment/appserver   1%/40%    1         5        1          2m29s

在第二个终端会话中,监视部署的情况:

$ kubectl get deploy appserver --watch

最后,在第三个终端会话中,启动负载生成器:

$ kubectl run -i -t loadgen --rm --image=busybox:1.36 --restart=Never -- \
    /bin/sh -c "while sleep 0.01; do wget -q -O- http://appserver; done"

由于涉及三个并行的终端会话,因此在图 9-1 中提供了整体情况的概述。

设置 HPA 的终端会话的截图

图 9-1. 设置 HPA 的终端会话

在展示 Kubernetes 仪表板中的图 9-2 中,你可以看到 HPA 对appserver部署的影响。

显示 Kubernetes 仪表板上 HPA 效果的截图

图 9-2. Kubernetes 仪表板,显示 HPA 的效果

参见

9.3 在 GKE 中自动调整集群大小

问题

您希望 GKE 集群中的节点数量根据利用率自动增减。

解决方案

使用 GKE 集群自动缩放器。本文假定您已安装了gcloud命令,并设置了环境(即您已创建了一个项目并启用了计费)。

创建一个带有一个工作节点并启用集群自动缩放的集群:

$ gcloud container clusters create supersizeme --zone=us-west1-a \
    --machine-type=e2-small --num-nodes=1 \
    --min-nodes=1 --max-nodes=3 --enable-autoscaling
Creating cluster supersizeme in us-west1-a... Cluster is being health-checked
(master is healthy)...done.
Created [https://container.googleapis.com/v1/projects/k8s-cookbook/zones/
us-west1-a/clusters/supersizeme].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/
kubernetes/workload_/gcloud/us-west1-a/supersizeme?project=k8s-cookbook
kubeconfig entry generated for supersizeme.
NAME         LOCATION    ...  MACHINE_TYPE  NODE_VERSION     NUM_NODES  STATUS
supersizeme  us-west1-a  ...  e2-small      1.26.5-gke.1200  1          RUNNING

此时,在谷歌云控制台上查看时,您应该看到类似于图 9-3 所示的内容。

谷歌云控制台的截图,显示初始为一个节点的集群大小

图 9-3. 谷歌云控制台,显示初始为一个节点的集群大小

现在,使用一个部署启动三个 Pod,并请求集群资源以触发集群自动缩放:

$ kubectl create deployment gogs --image=gogs/gogs:0.13 --replicas=3
$ kubectl set resources deployment gogs -c=gogs --requests=cpu=200m,memory=256Mi

一段时间后,部署将会更新:

$ kubectl get deployment gogs
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
gogs   3/3     3            3           2m27s

现在您应该拥有两个节点的集群,如图 9-4 所示。

谷歌云控制台的截图,显示扩展到两个节点的集群结果

图 9-4. 谷歌云控制台,显示扩展到两个节点的集群结果

讨论

在创建后,可以在 GKE 集群上启用或更新集群自动缩放:

$ gcloud container clusters update supersizeme --zone=us-west1-a \
    --min-nodes=1 --max-nodes=3 --enable-autoscaling

选择用于集群节点的机器类型是需要考虑的重要因素,这取决于运行工作负载所需的资源。如果您的工作负载需要更多资源,则应考虑使用较大的机器类型。

与 Pod 的缩放不同,集群缩放会动态地向您的集群添加资源,这可能会显著增加您的云账单。确保适当配置 GKE 集群的最大节点数,以避免超出您的消费限制。

当您不再需要集群时,请删除它以避免因未使用的计算资源而被收费:

$ gcloud container clusters delete supersizeme

参见

9.4 在 Amazon EKS 集群中自动调整大小

问题

您希望 AWS EKS 集群中的节点数量根据利用率自动增减。

解决方案

使用集群自动缩放器,一个利用 AWS 自动扩展组的 Helm 包。按照 Recipe 6.1 安装 Helm 客户端,该客户端用于安装该包。

首先,创建一个带有一个工作节点的集群,并确保可以使用kubectl访问它:

$ eksctl create cluster --name supersizeme \
    --region eu-central-1 --instance-types t3.small \
    --nodes 1 --nodes-min 1 --nodes-max 3
2023-04-11 12:00:50 [i]  eksctl version 0.136.0-dev+3f5a7c5e0.2023-03-31T10...
2023-04-11 12:00:50 [i]  using region eu-central-1
...
2023-04-11 12:17:31 [i]  kubectl command should work with "/Users/sameersbn/
.kube/config", try 'kubectl get nodes'
2023-04-11 12:17:31 [✔]  EKS cluster "supersizeme" in "eu-central-1" region
is ready

$ aws eks update-kubeconfig --name supersizeme --region eu-central-1

接下来,部署 Cluster Autoscaler Helm 图表:

$ helm repo add autoscaler https://kubernetes.github.io/autoscaler
$ helm install autoscaler autoscaler/cluster-autoscaler \
    --set autoDiscovery.clusterName=supersizeme \
    --set awsRegion=eu-central-1 \
    --set awsAccessKeyID=<*YOUR AWS KEY ID>* \
    --set awsSecretAccessKey=<*YOUR AWS SECRET KEY>*

此时,集群只有一个节点:

$ kubectl get nodes
NAME                                 STATUS   ROLES    AGE   VERSION
ip...eu-central-1.compute.internal   Ready    <none>   31m   v1.25.9-eks-0a21954

现在,使用一个部署启动五个 Pod,并请求集群资源以触发集群自动缩放:

$ kubectl create deployment gogs --image=gogs/gogs:0.13 --replicas=5
$ kubectl set resources deployment gogs -c=gogs --requests=cpu=200m,memory=512Mi

一段时间后,部署将会更新:

$ kubectl get deployment gogs
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
gogs   5/5     5            5           2m7s

现在你的集群应该已经扩展以容纳所请求的资源:

$ kubectl get nodes
NAME                                 STATUS   ROLES    AGE   VERSION
ip...eu-central-1.compute.internal   Ready    <none>   92s   v1.25.9-eks-0a21954
ip...eu-central-1.compute.internal   Ready    <none>   93s   v1.25.9-eks-0a21954
ip...eu-central-1.compute.internal   Ready    <none>   36m   v1.25.9-eks-0a21954

要避免因未使用资源而收费,请在不再需要时删除集群:

$ eksctl delete cluster --name supersizeme --region eu-central-1

第十章:安全性

在 Kubernetes 中运行应用程序需要开发人员和运维人员共同承担责任,以确保最小化攻击向量、遵循最小权限原则,并清晰定义对资源的访问。在本章中,我们将介绍您可以使用的、也应该使用的配方,以确保集群和应用程序的安全运行。本章的配方涵盖以下内容:

  • 服务账户的角色和用法

  • 基于角色的访问控制(RBAC)

  • 定义 Pod 的安全上下文

10.1 为应用程序提供唯一标识

问题

想要以细粒度控制向应用程序授予对受限资源的访问权限。

解决方案

创建具有特定秘密访问权限的服务账户,并在 Pod 规范中引用它。

首先,为此和以下示例创建一个专用命名空间,称为sec

$ kubectl create namespace sec
namespace/sec created

然后,在该命名空间中创建一个名为myappsa的新服务账户,并仔细查看它:

$ kubectl create serviceaccount myappsa -n sec
serviceaccount/myappsa created

$ kubectl describe sa myappsa -n sec
Name:                myappsa
Namespace:           sec
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   <none>
Tokens:              <none>
Events:              <none>

您可以在 Pod 清单中引用此服务账户,我们将其称为serviceaccountpod.yaml,如下所示。请注意,我们还将此 Pod 放置在sec命名空间中:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  namespace: sec
spec:
  serviceAccountName: myappsa
  containers:
  - name: main
    image: busybox:1.36
    command:
      - "bin/sh"
      - "-c"
      - "sleep 10000"

创建 Pod:

$ kubectl apply -f serviceaccountpod.yaml
pod/myapp created

服务账户的 API 凭据将自动挂载到/var/run/secrets/kubernetes.io/serviceaccount/token位置:

$ kubectl exec myapp -n sec -- \
    cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsImtpZCI6IkdHeTRHOUUwNl ...

实际上,myappsa服务账户的令牌已经挂载到 Pod 中预期的位置,并且可以在后续使用。

尽管单独的服务账户并不是非常有用,但它构成了细粒度访问控制的基础;请参阅配方 10.2 了解更多信息。

讨论

能够识别实体是认证和授权的前提条件。从 API 服务器的角度看,有两种类型的实体:人类用户和应用程序。虽然用户身份(管理)不在 Kubernetes 的范围之内,但表示应用程序身份的一流资源是服务账户。

从技术上讲,应用程序的认证由位于/var/run/secrets/kubernetes.io/serviceaccount/token位置的文件中的令牌捕获,该文件通过秘密自动挂载。服务账户是命名空间资源,其表示如下:

system:serviceaccount:$NAMESPACE:$SERVICEACCOUNT

列出特定命名空间中的服务账户会得到类似以下的结果:

$ kubectl get sa -n sec
NAME      SECRETS   AGE
default   0         3m45s
myappsa   0         3m2s

注意这里的服务账户名为default。此账户是自动创建的;如果未显式为 Pod 设置服务账户(就像解决方案中所做的那样),它将被分配到其命名空间中的default服务账户。

参见

10.2 列出和查看访问控制信息

问题

您想了解您被允许做的操作——例如,更新部署或列出 secrets。

解决方案

下面的解决方案假设您使用 RBAC 作为授权模式。RBAC 是 Kubernetes 上默认的访问控制模式。

要检查特定用户是否允许对资源执行某个操作,请使用 kubectl auth can-i。例如,您可以执行此命令来检查之前在上一节中创建的名为 system:serviceaccount:sec:myappsa 的服务账户是否被允许在命名空间 sec 中列出 pods:

$ kubectl auth can-i list pods --as=system:serviceaccount:sec:myappsa -n=sec
no

您可以使用 Kubernetes 内置的 RBAC 系统将角色分配给服务账户。例如,您可以通过为该命名空间分配预定义的 view 集群角色,为服务账户授予查看所有资源的权限:

$ kubectl create rolebinding my-sa-view \
    --clusterrole=view \
    --serviceaccount=sec:myappsa \
    --namespace=sec
rolebinding.rbac.authorization.k8s.io/my-sa-view created

现在如果您运行相同的 can-i 命令,您会看到服务账户现在有权限在 sec 命名空间中读取 pods:

$ kubectl auth can-i list pods --as=system:serviceaccount:sec:myappsa -n=sec
yes

注意

对于 Minikube 上的此配方,根据您运行的版本,可能需要在启动 Minikube 集群时添加参数 --extra-config=apiserver.authorization-mode=Node,RBAC

要列出命名空间中可用的角色,请执行以下操作:

$ kubectl get roles -n=kube-system
extension-apiserver-authentication-reader        2023-04-14T15:06:36Z
kube-proxy                                       2023-04-14T15:06:38Z
kubeadm:kubelet-config                           2023-04-14T15:06:36Z
kubeadm:nodes-kubeadm-config                     2023-04-14T15:06:36Z
system::leader-locking-kube-controller-manager   2023-04-14T15:06:36Z
system::leader-locking-kube-scheduler            2023-04-14T15:06:36Z
system:controller:bootstrap-signer               2023-04-14T15:06:36Z
system:controller:cloud-provider                 2023-04-14T15:06:36Z
system:controller:token-cleaner                  2023-04-14T15:06:36Z
system:persistent-volume-provisioner             2023-04-14T15:06:39Z

$ kubectl get clusterroles
NAME                               CREATED AT
admin                              2023-04-14T15:06:36Z
cluster-admin                      2023-04-14T15:06:36Z
edit                               2023-04-14T15:06:36Z
kubeadm:get-nodes                  2023-04-14T15:06:37Z
system:aggregate-to-admin          2023-04-14T15:06:36Z
system:aggregate-to-edit           2023-04-14T15:06:36Z
system:aggregate-to-view           2023-04-14T15:06:36Z
system:auth-delegator              2023-04-14T15:06:36Z
...

输出显示了预定义的角色,您可以直接为用户和服务账户使用。

要进一步探索特定角色并了解允许的操作,请使用以下方法:

$ kubectl describe clusterroles/view
Name:           view
Labels:         kubernetes.io/bootstrapping=rbac-defaults
                rbac.authorization.k8s.io/aggregate-to-edit=true
Annotations:    rbac.authorization.kubernetes.io/autoupdate=true
PolicyRule:
  Resources                                     Non-Resource URLs     ...  ...
  ---------                                     -----------------     ---  ---
  bindings                                      []                    ...  ...
  configmaps                                    []                    ...  ...
  cronjobs.batch                                []                    ...  ...
  daemonsets.extensions                         []                    ...  ...
  deployments.apps                              []                    ...  ...
  deployments.extensions                        []                    ...  ...
  deployments.apps/scale                        []                    ...  ...
  deployments.extensions/scale                  []                    ...  ...
  endpoints                                     []                    ...  ...
  events                                        []                    ...  ...
  horizontalpodautoscalers.autoscaling          []                    ...  ...
  ingresses.extensions                          []                    ...  ...
  jobs.batch                                    []                    ...  ...
  limitranges                                   []                    ...  ...
  namespaces                                    []                    ...  ...
  namespaces/status                             []                    ...  ...
  persistentvolumeclaims                        []                    ...  ...
  pods                                          []                    ...  ...
  pods/log                                      []                    ...  ...
  pods/status                                   []                    ...  ...
  replicasets.extensions                        []                    ...  ...
  replicasets.extensions/scale                  []                    ...  ...
  ...

除了 kube-system 命名空间中定义的默认角色外,您还可以定义自己的角色;请参见第 10.3 节。

讨论

如您在图 10-1 中看到的,处理 RBAC 授权时有一些移动部件:

  • 一个实体——即组、用户或服务账户

  • 诸如 pod、service 或 secret 等资源

  • 一个角色,定义了对资源的操作规则

  • 一个角色绑定,将一个角色应用到一个实体上

RBAC 概念

图 10-1. RBAC 概念

角色在其规则中使用的资源操作称为动词:

  • getlistwatch

  • create

  • update/patch

  • delete

关于角色,我们区分两种类型:

全局集群范围内

集群角色及其相应的集群角色绑定。请注意,您也可以将集群角色附加到常规角色绑定上。

命名空间范围内

角色与角色绑定。

在第 10.3 节中,我们将进一步讨论如何创建并应用您自己的规则到用户和资源上。

另请参阅

10.3 控制对资源的访问

问题

对于特定用户或应用程序,您希望允许或拒绝某个操作,如查看 secrets 或更新部署。

解决方案

假设您希望限制应用程序仅能查看 pods——即列出 pods 并获取 pods 的详细信息。

我们将在名为 sec 的命名空间中操作,因此首先使用 kubectl create namespace sec 创建该命名空间。

然后,在 YAML 清单 pod-with-sa.yaml 中创建一个 pod 定义,使用专用服务账户 myappsa(详见 Recipe 10.1):

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  namespace: sec
spec:
  serviceAccountName: myappsa
  containers:
  - name: main
    image: busybox:1.36
    command:
      - "sh"
      - "-c"
      - "sleep 10000"

接下来,在清单 pod-reader.yaml 中定义一个角色——我们将其称为 podreader,定义在资源上的允许操作:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: podreader
  namespace: sec
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]

最后但同样重要的是,您需要将角色 podreader 应用到服务账户 myappsa,使用 pod-reader-binding.yaml 中的角色绑定:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: podreaderbinding
  namespace: sec
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: podreader
subjects:
- kind: ServiceAccount
  name: myappsa
  namespace: sec

在创建相应资源时,您可以直接使用 YAML 清单(假设服务账户已创建):

$ kubectl create -f pod-reader.yaml
$ kubectl create -f pod-reader-binding.yaml
$ kubectl create -f pod-with-sa.yaml

而不是为角色和角色绑定创建清单,您可以使用以下命令:

$ kubectl create role podreader \
    --verb=get --verb=list \
    --resource=pods -n=sec

$ kubectl create rolebinding podreaderbinding \
    --role=sec:podreader \
    --serviceaccount=sec:myappsa \
    --namespace=sec

注意,这是一个命名空间访问控制设置的案例,因为您正在使用角色和角色绑定。要进行集群范围的访问控制,您将使用相应的 create clusterrolecreate clusterrolebinding 命令。

讨论

有时候不明确是否应该使用角色还是集群角色和/或角色绑定,因此以下是一些您可能会发现有用的经验法则:

  • 如果您希望限制对特定命名空间中的命名空间资源(如服务或 Pod)的访问,请使用角色和角色绑定(就像我们在这个示例中所做的那样)。

  • 如果您想要在几个命名空间中重用一个角色,请使用带有角色绑定的集群角色。

  • 如果您希望限制对集群范围资源(如节点)或跨所有命名空间的命名空间资源的访问,请使用带有集群角色绑定的集群角色。

参见

10.4 保护 Pods

问题

您想要在 pod 级别定义应用程序的安全上下文。例如,您希望将应用程序作为非特权进程运行。

解决方案

要在 Kubernetes 的 pod 级别强制执行策略,请在 pod 规范中使用 securityContext 字段。

假设您想要一个作为非根用户运行的应用程序。为此,您将在容器级别使用安全上下文,如以下清单 securedpod.yaml 所示:

kind: Pod
apiVersion: v1
metadata:
  name: secpod
spec:
  containers:
  - name: shell
    image: ubuntu:20.04
    command:
      - "bin/bash"
      - "-c"
      - "sleep 10000"
    securityContext:
      runAsUser: 5000

现在创建 pod 并检查容器运行的用户:

$ kubectl apply -f securedpod.yaml
pod/secpod created

$ kubectl exec secpod -- ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
5000           1  0.0  0.0   2204   784 ?        Ss   15:56   0:00 sleep 10000
5000          13  0.0  0.0   6408  1652 ?        Rs   15:56   0:00 ps aux

如预期的那样,它以用户 ID 5000 运行。请注意,您也可以在 pod 级别而不是特定容器上使用 securityContext 字段。

讨论

在 pod 级别强制执行策略的更强大方法是使用 pod 安全入场。详见 Kubernetes 文档中的 “Pod Security Admission”

参见

第十一章:监控和日志

在本章中,我们专注于基础设施和应用程序级别的监控和日志记录食谱。在 Kubernetes 的上下文中,不同的角色通常有不同的范围:

管理员角色

管理员,如集群管理员、网络操作人员或命名空间级别的管理员,关注的是集群控制平面。他们可能会问自己一些例子性问题:节点是否健康?我们应该添加一个工作节点吗?集群的利用率如何?用户是否接近使用配额限制?

开发人员角色

开发人员主要在应用程序或数据平面的上下文中思考和行动,这在微服务时代可能是一个手到擒来的事情——有时候可能是一到十几个 pod。例如,开发人员可能会问:我分配给运行应用程序的资源足够吗?我应该将应用程序扩展到多少副本?我是否有访问正确卷的权限,它们的使用情况如何?我的某个应用程序是否失败了,如果失败了,原因是什么?

我们将首先通过利用 Kubernetes 存活和就绪探测来聚焦于集群内部监控的食谱,然后关注使用Metric ServerPrometheus进行监控,最后涵盖与日志相关的食谱。

11.1 访问容器的日志

问题

您希望访问运行在特定 pod 中某个容器内的应用程序的日志。

解决方案

使用kubectl logs命令。要查看各种选项,请检查用法,如下所示:

$ kubectl logs --help | more
Print the logs for a container in a pod or specified resource. If the pod has
only one container, the container name is optional.

Examples:
  # Return snapshot logs from pod nginx with only one container
  kubectl logs nginx
...

例如,给定由部署启动的 pod(参见食谱 4.1),您可以像这样检查日志:

$ kubectl get pods
NAME                             READY   STATUS    RESTARTS   AGE
nginx-with-pv-7d6877b8cf-mjx5m   1/1     Running   0          140m

$ kubectl logs nginx-with-pv-7d6877b8cf-mjx5m
...
2023/03/31 11:03:24 [notice] 1#1: using the "epoll" event method
2023/03/31 11:03:24 [notice] 1#1: nginx/1.23.4
2023/03/31 11:03:24 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/03/31 11:03:24 [notice] 1#1: OS: Linux 5.15.49-linuxkit
2023/03/31 11:03:24 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023/03/31 11:03:24 [notice] 1#1: start worker processes
...

提示

如果一个 pod 中有多个容器,您可以通过使用kubectl logs-c选项并指定容器的名称来获取任何一个容器的日志。

讨论

Stern是 Kubernetes 上查看 pod 日志的有用替代工具。它使得跨命名空间获取日志变得轻而易举,只需在查询中提供部分 pod 名称(而不是使用选择器,有时可能更繁琐)。

11.2 使用存活探测从故障状态恢复

问题

您希望确保,如果某些 pod 中运行的应用程序处于故障状态,Kubernetes 将自动重新启动这些 pod。

解决方案

使用存活探测。如果探测失败,kubelet将自动重新启动 pod。探测是 pod 规范的一部分,并添加到containers部分。每个 pod 中的容器都可以有一个存活探测。

一个探测可以有三种不同的类型:可以是在容器内执行的命令,是对容器内部 HTTP 服务器特定路由的 HTTP 或 gRPC 请求,或者是更通用的 TCP 探测。

在以下示例中,我们展示了一个基本的 HTTP 探测:

apiVersion: v1
kind: Pod
metadata:
  name: liveness-nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.25.2
    livenessProbe:
      httpGet:
        path: /
        port: 80

请参见食谱 11.5 以获取完整示例。

参见

11.3 使用就绪性探针控制到 Pod 的流量

问题

根据存活性探针(参见 Recipe 11.2),您的 Pod 正在运行,但只有在应用程序准备好提供服务时才希望将流量发送到它们。

解决方案

在您的 Pod 规范中添加就绪性探针。以下是使用 nginx 容器映像运行单个 Pod 的直接示例。就绪性探针向端口 80 发送 HTTP 请求:

apiVersion: v1
kind: Pod
metadata:
  name: readiness-nginx
spec:
  containers:
  - name: readiness
    image: nginx:1.25.2
    readinessProbe:
      httpGet:
        path: /
        port: 80

讨论

尽管此配方中显示的就绪性探针与 Recipe 11.2 中的存活性探针相同,但它们通常应该不同,因为两个探针的目的是提供关于应用程序不同方面的信息。存活性探针检查应用程序进程是否存活,但可能尚未准备好接受请求。就绪性探针检查应用程序是否正确服务请求。因此,只有在就绪性探针通过时,Pod 才会成为服务的一部分(参见 Recipe 5.1)。

参见

11.4 使用启动探针保护启动缓慢的容器

问题

您的 Pod 包含一个容器,在首次初始化时需要额外的启动时间,但您不希望使用存活性探针(参见 Recipe 11.2),因为这只是 Pod 第一次启动时的要求。

解决方案

根据您的 Pod 规范添加启动探针,设置 failureThresholdperiodSeconds 为足够覆盖 Pod 启动时间。类似于存活性探针,启动探针可以分为三种类型。以下是使用 nginx 容器映像运行单个 Pod 的直接示例。启动探针向端口 80 发送 HTTP 请求:

apiVersion: v1
kind: Pod
metadata:
  name: startup-nginx
spec:
  containers:
  - name: startup
    image: nginx:1.25.2
    startupProbe:
      httpGet:
        path: /
        port: 80
      failureThreshold: 30
      periodSeconds: 10

讨论

有时您必须处理需要很长时间才能启动的应用程序。例如,应用程序可能需要执行一些需要很长时间才能完成的数据库迁移。在这种情况下,设置存活性探针可能会有些棘手,而不会影响这种探针推动的快速响应死锁。为了解决这个问题,除了存活性探针之外,还可以设置一个与相同命令、HTTP 检查或 TCP 检查相同的启动探针,但 failureThreshold * periodSeconds 要足够长以覆盖最坏情况的启动时间。

如果配置了启动探针,则存活性和就绪性探针在成功之前不会启动,确保这些探针不会干扰应用程序的启动。此技术可安全实现对启动缓慢容器的存活性检查,避免它们在完全运行之前被 kubelet 杀死。

参见

11.5 向您的部署添加存活性和就绪性探针

问题

您希望能够自动检查您的应用程序是否健康,并让 Kubernetes 在这种情况下采取行动。

解决方案

要向 Kubernetes 发信号表明您的应用程序的状态,请按照此处描述的方式添加活力和就绪探针。

起始点是一个部署清单,webserver.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.2
        ports:
        - containerPort: 80

活力和就绪探针在 pod 规范的containers部分中定义。查看入门示例(Recipes 11.2 和 11.3)并将以下内容添加到您的部署的 pod 模板中的容器规范中:

...
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 2
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 2
          periodSeconds: 10
...

现在,您可以启动它并检查探针:

$ kubectl apply -f webserver.yaml
deployment.apps/webserver created

$ kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
webserver-4288715076-dk9c7   1/1       Running   0          2m

$ kubectl describe pod/webserver-4288715076-dk9c7
Name:           webserver-4288715076-dk9c7
Namespace:      default
Priority:       0

...
Status:         Running
IP:             10.32.0.2
...
Containers:
  nginx:
    ...
    Ready:           True
    Restart Count:   0
    Liveness:       http-get http://:80/ delay=2s timeout=1s period=10s #succe...
    Readiness:      http-get http://:80/ delay=2s timeout=1s period=10s #succe...
    ...
...

请注意,kubectl describe命令的输出已编辑为重要部分;还有更多信息可用,但在此问题中无关紧要。

讨论

要验证 pod 中的容器是否健康并准备好提供流量,Kubernetes 提供了一系列健康检查机制。健康检查或在 Kubernetes 中称为探针,在容器级别定义,并由两个不同的组件执行:

  • 每个工作节点上的kubelet使用规范中的livenessProbe指令来确定何时重新启动一个容器。这些活力探针可以帮助解决启动问题或死锁问题。

  • 一个负载均衡一组 pod 的服务使用readinessProbe指令来确定一个 pod 是否准备好,因此应该接收流量。如果不是这种情况,它将从服务的端点池中排除。请注意,当所有容器都准备就绪时,一个 pod 被视为准备就绪。

何时使用哪个探针?这实际上取决于容器的行为。如果您的容器在探针失败时可以且应该被杀死并重新启动,请使用活力探针和restartPolicyAlwaysOnFailure。如果只有当 pod 准备好时才想要发送流量,请使用就绪探针。请注意,在后一种情况下,就绪探针可以配置为使用与活力探针相同的探测声明端点(例如 URL)。

启动探针用于确定 pod 中的应用程序是否正常启动和运行。它们可以用来延迟活力和就绪探针的初始化,如果应用程序尚未正确启动,则这些探针可能会失败。

参见

11.6 在 CLI 中访问 Kubernetes 指标

问题

您已安装了 Kubernetes Metrics Server(参见 Recipe 2.7),并希望使用 Kubernetes CLI 访问指标。

解决方案

Kubernetes CLI 具有显示节点和 pod 资源使用情况的top命令:

$ kubectl top node
NAME       CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
minikube   338m         8%     1410Mi          17%

$ kubectl top pods --all-namespaces
NAMESPACE     NAME                               CPU(cores)   MEMORY(bytes)
default       db                                 15m          440Mi
default       liveness-nginx                     1m           5Mi
default       nginx-with-pv-7d6877b8cf-mjx5m     0m           3Mi
default       readiness-nginx                    1m           3Mi
default       webserver-f4f7cb455-rhxwt          1m           4Mi
kube-system   coredns-787d4945fb-jrp8j           4m           12Mi
kube-system   etcd-minikube                      48m          52Mi
kube-system   kube-apiserver-minikube            78m          266Mi
...

这些指标也可以在图形用户界面中查看,即 Kubernetes 仪表盘(参见 配方 2.5)。

提示

在启动 Metrics Server 后,可能需要几分钟才能使其可用。如果尚未准备就绪,则 top 命令可能会产生错误。

11.7 在 Minikube 上使用 Prometheus 和 Grafana

问题

您希望从一个中心位置查看和查询集群的系统和应用程序指标。

解决方案

在 Minikube 上部署 Prometheus 和 Grafana。我们将利用 kube-prometheus 项目,这是一个独立项目,可以轻松在任何 Kubernetes 集群上安装 Prometheus 和 Grafana。

运行以下命令启动一个新的 Minikube 实例,并正确配置以运行 kube-prometheus:

$ minikube delete && minikube start --kubernetes-version=v1.27.0 \
    --memory=6g --bootstrapper=kubeadm \
    --extra-config=kubelet.authentication-token-webhook=true \
    --extra-config=kubelet.authorization-mode=Webhook \
    --extra-config=scheduler.bind-address=0.0.0.0 \
    --extra-config=controller-manager.bind-address=0.0.0.0

确保在 Minikube 上禁用 metrics-server 插件:

$ minikube addons disable metrics-server

克隆 kube-prometheus 项目:

$ git clone https://github.com/prometheus-operator/kube-prometheus.git

切换到克隆的存储库,然后运行以下命令,它将创建一个名为 monitoring 的专用命名空间,并创建所需的自定义资源定义:

$ kubectl apply --server-side -f manifests/setup
$ kubectl wait \
    --for condition=Established \
    --all CustomResourceDefinition \
    --namespace=monitoring
$ kubectl apply -f manifests/

要打开 Prometheus 仪表盘,您可以使用如下的端口转发,或者您可以按照 配方 5.5 中定义的方式使用 ingress:

$ kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090

您随后可以在浏览器中打开 localhost:9090 上的 Prometheus。

您可以执行类似的操作以访问 Grafana 仪表盘:

$ kubectl --namespace monitoring port-forward svc/grafana 3000

然后在浏览器中打开本地的 Grafana 仪表盘,网址为 localhost:3000

使用默认凭据登录:用户名为 admin,密码为 admin。如果您只在本地 Minikube 实例上运行此配方,则可以跳过更改密码的步骤。

Kubernetes API 服务器有一个内置的仪表盘。要找到它,打开 URL http://localhost:3000/dashboards 或使用左侧菜单导航到仪表盘。找到名为“Kubernetes / API server”的仪表盘;打开它,您应该看到类似 图 11-1 所示的页面。

Grafana 截图

图 11-1. Grafana 中的 Kubernetes/API 服务器仪表盘

讨论

此处提供了一个很好的方法来开始尝试使用 Grafana 和 Prometheus,并展示如何使用内置的示例仪表盘快速启动。一旦开始部署自己的自定义工作负载和应用程序,您可以创建自己的自定义查询和仪表盘,以提供更符合您工作负载需求的指标。您可以在 Prometheus 查询参考文档 中了解更多关于 Prometheus 查询的信息,在 Grafana 文档 中了解更多关于 Grafana 仪表盘的信息。

另请参阅

第十二章:维护与故障排除

在本章中,您将找到处理应用程序级别和集群级别维护的各种配方。我们涵盖了各种故障排除方面,从调试 pod 和容器到测试服务连接性,解释资源状态以及维护节点。最后但并非最不重要的是,我们将讨论如何处理 Kubernetes 控制平面存储组件 etcd。这一章对集群管理员和应用程序开发人员都有意义。

12.1 启用 kubectl 的自动补全

问题

输入完整的kubectl CLI 命令和参数很麻烦,因此您希望为其添加自动补全功能。

解决方案

启用kubectl自动补全。

对于 bash shell,您可以使用以下命令在当前 shell 中启用kubectl自动补全:

$ source <(kubectl completion bash)

将此添加到您的~/.bashrc文件中,以便在所有 shell 会话中加载自动补全:

$ echo 'source <(kubectl completion bash)' >>~/.bashrc

请注意,bash 的自动补全依赖于已安装的bash-completion

对于 zsh shell,您可以使用以下命令启用kubectl自动补全:

$ source <(kubectl completion zsh)

而且您可以将同样的命令添加到您的~/.zshrc文件中,以便在所有 shell 会话中加载自动补全。

要使 zsh 中的自动补全功能正常工作,您可能需要在您的~/.zshrc文件的开头添加以下命令:

autoload -Uz compinit
compinit

对于其他操作系统和 shell,请查看文档

讨论

提升kubectl开发体验的另一个流行方法是将别名定义为缩短kubectl为字母k。您可以通过执行以下命令或将它们添加到您的 shell 启动脚本中来实现:

alias k=kubectl
complete -o default -F __start_kubectl k

然后,你可以简单地输入像k apply -f myobject.yaml这样的命令。这与自动补全结合使用使生活变得更加轻松。

参见

12.2 从服务中删除一个 Pod

问题

您拥有一个明确定义的服务(参见配方 5.1),支持多个 pod。但其中一个 pod 导致问题(例如崩溃或无响应),您希望将其从端点列表中移出以便稍后检查。

解决方案

使用--overwrite选项重新标记 pod——这将允许您更改 pod 上的run标签的值。通过覆盖此标签,您可以确保它不会被服务选择器(配方 5.1)选中,并将其从端点列表中移除。同时,监视您的 pod 的副本集将会注意到一个 pod 消失,并将启动一个新的副本。

要查看其工作原理,请从使用kubectl run生成的简单部署开始(参见配方 4.5):

$ kubectl create deployment nginx --image nginx:1.25.2 --replicas 4

当您列出带有键app的 pod 并显示值为nginx的标签时,您将看到四个带有值nginx的 pod(app=nginx是由kubectl create deployment命令自动生成的标签):

$ kubectl get pods -Lapp
NAME                      READY   STATUS    RESTARTS   AGE     APP
nginx-748c667d99-85zxr    1/1     Running   0          14m     nginx
nginx-748c667d99-jrhpc    1/1     Running   0          14m     nginx
nginx-748c667d99-rddww    1/1     Running   0          14m     nginx
nginx-748c667d99-x6h6h    1/1     Running   0          14m     nginx

然后你可以暴露这个部署与一个服务,并检查端点,这些端点对应于每个 Pod 的 IP 地址:

$ kubectl expose deployments nginx --port 80

$ kubectl get endpoints
NAME            ENDPOINTS                                                  AGE
kubernetes      192.168.49.2:8443                                          3h36m
nginx           10.244.0.10:80,10.244.0.11:80,10.244.0.13:80 + 1 more...   13m

假设列表中的第一个 Pod 正在出现问题,尽管它的状态是 Running

通过重新标记将第一个 Pod 移出服务池可以通过单个命令完成:

$ kubectl label pod nginx-748c667d99-85zxr app=notworking --overwrite

提示

要找到 Pod 的 IP 地址,可以使用 Go 模板格式化 Pod 信息并仅显示其 IP 地址:

$ kubectl get pod nginx-748c667d99-jrhpc \
    --template '{{.status.podIP}}' 
10.244.0.11

你将看到一个带有标签 app=nginx 的新 Pod 出现,你会看到你的不工作的 Pod 仍然存在,但不再出现在服务端点列表中:

$ kubectl get pods -Lapp
NAME                      READY   STATUS    RESTARTS   AGE     APP
nginx-748c667d99-85zxr    1/1     Running   0          14m     notworking
nginx-748c667d99-jrhpc    1/1     Running   0          14m     nginx
nginx-748c667d99-rddww    1/1     Running   0          14m     nginx
nginx-748c667d99-x6h6h    1/1     Running   0          14m     nginx
nginx-748c667d99-xfgqp    1/1     Running   0          2m17s   nginx

$ kubectl describe endpoints nginx
Name:         nginx
Namespace:    default
Labels:       app=nginx
Annotations:  endpoints.kubernetes.io/last-change-trigger-time: 2023-04-13T13...
Subsets:
  Addresses:          10.244.0.10,10.244.0.11,10.244.0.13,10.244.0.9
  NotReadyAddresses:  <none>
  Ports:
    Name     Port  Protocol
    ----     ----  --------
    <unset>  80    TCP

Events:  <none>

12.3 在集群外部访问 ClusterIP 服务

问题

你有一个内部服务正给你带来麻烦,你想在本地测试它是否正常工作,而不需要将服务暴露在外部。

解决方案

使用 kubectl proxy 对 Kubernetes API 服务器进行本地代理。

假设你已经创建了一个部署和一个服务,就像 Recipe 12.2 中描述的那样。当你列出服务时,你应该看到一个 nginx 服务:

$ kubectl get svc
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
nginx           ClusterIP   10.108.44.174   <none>        80/TCP     37m

这个服务在 Kubernetes 集群外部是无法访问的。但是,你可以在单独的终端中运行一个代理,然后在 localhost 上访问它。

首先,在单独的终端中运行代理:

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

提示

可以使用 --port 选项指定代理要运行的端口。

然后,在你的原始终端中,你可以使用浏览器或 curl 访问你的服务暴露的应用程序:

$ curl http://localhost:8001/api/v1/namespaces/default/services/nginx/proxy/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

注意服务的特定路径;它包含一个 /proxy 部分。如果没有这个部分,你将获得表示服务的 JSON 对象。

注意

注意,现在你也可以使用 curllocalhost 上访问整个 Kubernetes API。

讨论

此示例演示了一种适用于调试的方法,并不适用于生产环境中服务的常规访问。相反,请使用安全的 Recipe 5.5 处理生产场景。

12.4 理解和解析资源状态

问题

你希望监视一个对象,比如一个 Pod,并对对象状态的变化做出反应。有时这些状态变化会触发 CI/CD 管道中的事件。

解决方案

使用 kubectl get $KIND/$NAME -o json 并使用这里描述的两种方法之一解析 JSON 输出。

如果你已经安装了 JSON 查询工具 jq (已安装),你可以使用它来解析资源状态。假设你有一个名为 jump 的 Pod。你可以这样做来找出这个 Pod 属于什么 Quality of Service (QoS) class

$ kubectl run jump --image=nginx
pod/jump created

$ kubectl get po/jump -o json | jq --raw-output .status.qosClass
BestEffort

注意,jq--raw-output 参数将显示原始值,并且 .status.qosClass 是匹配相应子字段的表达式。

另一个状态查询可能围绕事件或状态转换。例如:

$ kubectl get po/jump -o json | jq .status.conditions
[
  {
    "lastProbeTime": null,
    "lastTransitionTime": "2023-04-13T14:00:13Z",
    "status": "True",
    "type": "Initialized"
  },
  {
    "lastProbeTime": null,
    "lastTransitionTime": "2023-04-13T14:00:18Z",
    "status": "True",
    "type": "Ready"
  },
  {
    "lastProbeTime": null,
    "lastTransitionTime": "2023-04-13T14:00:18Z",
    "status": "True",
    "type": "ContainersReady"
  },
  {
    "lastProbeTime": null,
    "lastTransitionTime": "2023-04-13T14:00:13Z",
    "status": "True",
    "type": "PodScheduled"
  }
]

当然,这些查询不仅限于 Pod —— 你可以将这种技术应用于任何资源。例如,你可以查询 Deployment 的修订版本:

$ kubectl create deployment wordpress --image wordpress:6.3.1
deployment.apps/wordpress created

$ kubectl get deploy/wordpress -o json | jq .metadata.annotations
{
  "deployment.kubernetes.io/revision": "1"
}

或者你可以列出组成一个服务的所有端点:

$ kubectl get ep/nginx -o json | jq '.subsets'
[
  {
    "addresses": [
      {
        "ip": "10.244.0.10",
        "nodeName": "minikube",
        "targetRef": {
          "kind": "Pod",
          "name": "nginx-748c667d99-x6h6h",
          "namespace": "default",
          "uid": "a0f3118f-32f5-4a65-8094-8e43979f7cec"
        }
      },
    ...
    ],
    "ports": [
      {
        "port": 80,
        "protocol": "TCP"
      }
    ]
  }
]

现在您已经看到jq的运行情况,让我们转向一种不需要外部工具的方法——即使用 Go 模板的内置功能。

编程语言 Go 定义了一个名为text/template的包,可以用于任何类型的文本或数据转换,而kubectl对其具有内置支持。例如,要列出当前命名空间中使用的所有容器镜像,请执行以下操作:

$ kubectl get pods -o go-template \
    --template="{{range .items}}{{range .spec.containers}}{{.image}} \
          {{end}}{{end}}"
fluent/fluentd:v1.16-1   nginx

讨论

您可能还想查看 JSONPath 作为解析kubectl生成的 JSON 的替代方法。它提供了一个更易读和更容易理解的语法。示例可在Kubernetes 文档中找到。

参见

12.5 调试 Pods

问题

您面临的情况是 Pod 未按预期达到或保持在运行状态,或者在一段时间后完全失败。

解决方案

为了系统地发现并修复问题的根本原因,请进入OODA 循环

  1. 观察。在容器日志中看到了什么?发生了什么事件?网络连接如何?

  2. 定位。制定一组可能的假设——保持开放的心态,不要轻易下结论。

  3. 决策。选择一个假设。

  4. 行动。测试假设。如果确认,则完成;否则,返回步骤 1 并继续。

让我们看一个具体的例子,一个 Pod 失败的情况。创建一个名为unhappy-pod.yaml的清单,其内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: unhappy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nevermind
  template:
    metadata:
      labels:
        app: nevermind
    spec:
      containers:
      - name: shell
        image: busybox:1.36
        command:
        - "sh"
        - "-c"
        - "echo I will just print something here and then exit"

现在当您启动该部署并查看它创建的 Pod 时,您会发现它很不开心:

$ kubectl apply -f unhappy-pod.yaml
deployment.apps/unhappy created

$ kubectl get pod -l app=nevermind
NAME                         READY   STATUS             RESTARTS      AGE
unhappy-576954b454-xtb2g     0/1     CrashLoopBackOff   2 (21s ago)   42s

$ kubectl describe pod -l app=nevermind
Name:             unhappy-576954b454-xtb2g
Namespace:        default
Priority:         0
Service Account:  default
Node:             minikube/192.168.49.2
Start Time:       Thu, 13 Apr 2023 22:31:28 +0200
Labels:           app=nevermind
                  pod-template-hash=576954b454
Annotations:      <none>
Status:           Running
IP:               10.244.0.16
IPs:
  IP:           10.244.0.16
Controlled By:  ReplicaSet/unhappy-576954b454
...
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
Volumes:
  kube-api-access-bff5c:
    Type:                    Projected (a volume that contains injected data...)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exist...
                             node.kubernetes.io/unreachable:NoExecute op=Exist...
Events:
  Type     Reason     ...   Message
  ----     ------     ---   -------
  Normal   Scheduled  ...   Successfully assigned default/unhappy-576954b454-x...
  Normal   Pulled     ...   Successfully pulled image "busybox" in 2.945704376...
  Normal   Pulled     ...   Successfully pulled image "busybox" in 1.075044917...
  Normal   Pulled     ...   Successfully pulled image "busybox" in 1.119703875...
  Normal   Pulling    ...   Pulling image "busybox"
  Normal   Created    ...   Created container shell
  Normal   Started    ...   Started container shell
  Normal   Pulled     ...   Successfully pulled image "busybox" in 1.055005126...
  Warning  BackOff    ...   Back-off restarting failed container shell in pod...

如你在描述底部看到的,Events部分,Kubernetes 认为此 Pod 未准备好为流量提供服务,因为“Back-off restarting failed…​.”

另一种观察方法是使用 Kubernetes 仪表板查看部署(图 12-1),以及受监控的副本集和 Pod(图 12-2)。通过 Minikube,您可以通过运行命令minikube dashboard轻松打开仪表板。

部署处于错误状态的屏幕截图

图 12-1. 部署处于错误状态

处于错误状态的 Pod 的屏幕截图

图 12-2. Pod 处于错误状态

讨论

问题,无论是 Pod 失败还是节点表现异常,都可能有许多不同的原因。在怀疑软件错误之前,以下是您要检查的一些事项:

  • 清单是否正确?使用诸如Kubeconform之类的工具进行检查。

  • 您能在 Kubernetes 之外本地运行容器吗?

  • Kubernetes 能否访问容器注册表并实际拉取容器镜像?

  • 节点之间能够互相通信吗?

  • 节点能够达到控制平面吗?

  • 集群中是否可用 DNS?

  • 节点上是否有足够的资源可用,例如 CPU、内存和磁盘空间?

  • 你是否限制了容器或命名空间的资源使用?

  • 对象描述中的事件说了什么?

另请参阅

12.6 影响 Pod 的启动行为

问题

为了让你的 Pod 正常运行,它依赖于其他一些服务的可用性。你希望影响 Pod 的启动行为,以便它只在依赖的 Pod 可用时才启动。

解决方案

使用 init containers 来影响 Pod 的启动行为。

假设你想要启动一个依赖于后端服务的 NGINX Web 服务器来提供内容。因此,你希望确保只有在后端服务已经启动并运行时,NGINX Pod 才启动。

首先,创建 Web 服务器依赖的后端服务:

$ kubectl create deployment backend --image=gcr.io/google-samples/hello-app:2.0
deployment.apps/backend created
$ kubectl expose deployment backend --port=80 --target-port=8080

然后,你可以使用以下清单 nginx-init-container.yaml 来启动 NGINX 实例,并确保它只在 backend 部署准备好接受连接时才启动:

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: webserver
        image: nginx:1.25.2
        ports:
        - containerPort: 80
      initContainers:
      - name: checkbackend
        image: busybox:1.36
        command: ['sh', '-c', 'until nc -w 5 backend.default.svc.cluster.local
                 80; do echo
                 "Waiting for backend to accept connections"; sleep 3; done; echo
                 "Backend is up, ready to launch web server"']

现在,你可以启动 nginx 部署,并通过查看它监督的 Pod 的日志来验证 init 容器是否已完成其工作:

$ kubectl apply -f nginx-init-container.yaml
deployment.apps/nginx created

$ kubectl get po
NAME                       READY   STATUS    RESTARTS   AGE
backend-8485c64ccb-99jdh   1/1     Running   0          4m33s
nginx-779d9fcdf6-2ntpn     1/1     Running   0          32s

$ kubectl logs nginx-779d9fcdf6-2ntpn -c checkbackend
Server:   10.96.0.10
Address:  10.96.0.10:53

Name: backend.default.svc.cluster.local
Address: 10.101.119.67

Backend is up, ready to launch web server

正如你所见,init 容器中的命令确实按计划运行。

讨论

Init 容器在等待服务可用时,防止应用程序发生崩溃循环非常有用。例如,如果你部署需要连接到数据库服务器的应用程序,可以配置一个 init 容器来检查并等待数据库服务器准备好,然后再尝试连接。

然而,重要的是要记住 Kubernetes 在成功启动 Pod 后也可以随时杀死它。因此,你需要确保你的应用程序具有足够的弹性,以应对其他依赖服务的故障。

12.7 获取集群状态的详细快照

问题

你想要获得整个集群状态的详细快照,以便进行定位、审计或故障排除。

解决方案

使用 kubectl cluster-info dump 命令。例如,要在子目录 cluster-state-2023-04-13 中创建集群状态的转储,请执行以下操作:

$ mkdir cluster-state-2023-04-13

$ kubectl cluster-info dump --all-namespaces \
    --output-directory=cluster-state-2023-04-13
Cluster info dumped to cluster-state-2023-04-13

$ tree ./cluster-state-2023-04-13
./cluster-state-2023-04-13
├── default
│   ├── daemonsets.json
│   ├── deployments.json
│   ├── es-598664765b-tpw59
│   │   └── logs.txt
│   ├── events.json
│   ├── fluentd-vw7d9
│   │   └── logs.txt
│   ├── jump
│   │   └── logs.txt
│   ├── kibana-5847789b45-bm6tn
│   │   └── logs.txt
    ...
├── ingress-nginx
│   ├── daemonsets.json
│   ├── deployments.json
│   ├── events.json
│   ├── ingress-nginx-admission-create-7qdjp
│   │   └── logs.txt
│   ├── ingress-nginx-admission-patch-cv6c6
│   │   └── logs.txt
│   ├── ingress-nginx-controller-77669ff58-rqdlq
│   │   └── logs.txt
│   ├── pods.json
│   ├── replicasets.json
│   ├── replication-controllers.json
│   └── services.json
├── kube-node-lease
│   ├── daemonsets.json
│   ├── deployments.json
│   ├── events.json
│   ├── pods.json
│   ├── replicasets.json
│   ├── replication-controllers.json
│   └── services.json
├── kube-public
│   ├── daemonsets.json
│   ├── deployments.json
│   ├── events.json
│   ├── pods.json
│   ├── replicasets.json
│   ├── replication-controllers.json
│   └── services.json
├── kube-system
│   ├── coredns-787d4945fb-9k8pn
│   │   └── logs.txt
│   ├── daemonsets.json
│   ├── deployments.json
│   ├── etcd-minikube
│   │   └── logs.txt
│   ├── events.json
│   ├── kube-apiserver-minikube
│   │   └── logs.txt
│   ├── kube-controller-manager-minikube
│   │   └── logs.txt
│   ├── kube-proxy-x6zdw
│   │   └── logs.txt
│   ├── kube-scheduler-minikube
│   │   └── logs.txt
│   ├── pods.json
│   ├── replicasets.json
│   ├── replication-controllers.json
│   ├── services.json
│   └── storage-provisioner
│       └── logs.txt
├── kubernetes-dashboard
│   ├── daemonsets.json
│   ├── dashboard-metrics-scraper-5c6664855-sztn5
│   │   └── logs.txt
│   ├── deployments.json
│   ├── events.json
│   ├── kubernetes-dashboard-55c4cbbc7c-ntjwk
│   │   └── logs.txt
│   ├── pods.json
│   ├── replicasets.json
│   ├── replication-controllers.json
│   └── services.json
└── nodes.json

30 directories, 66 files

12.8 添加 Kubernetes 工作节点

问题

你需要向你的 Kubernetes 集群添加一个工作节点,比如因为你想增加集群的容量。

解决方案

在您的环境中,根据需要配置新的机器(例如,在裸金属环境中,您可能需要在机架中物理安装新服务器,在公共云设置中,您需要创建新的虚拟机等),然后安装作为 Kubernetes 工作节点的三个组件作为守护程序:

kubelet

这是所有 Pod 的节点管理器和监督者,无论它们是由 API 服务器控制还是在本地运行,比如静态 Pod。请注意,kubelet 是决定哪些 Pod 可以在给定节点上运行的最终裁决者,并负责以下任务:

  • 向 API 服务器报告节点和 Pod 的状态

  • 定期执行存活探针

  • 挂载 Pod 卷和下载机密

  • 控制容器运行时(见下文)

容器运行时

这负责下载容器镜像并运行容器。Kubernetes 要求使用符合 容器运行时接口(CRI) 的运行时,如 cri-oDocker Enginecontainerd

kube-proxy

此过程动态配置节点上的 iptables 规则,以启用 Kubernetes 服务抽象(将 VIP 重定向到端点,一个或多个表示服务的 Pod)。

组件的实际安装严重依赖于您的环境和使用的安装方法(云、kubeadm 等)。有关可用选项的列表,请参阅 kubelet 参考文档kube-proxy 参考文档

讨论

与部署或服务等其他 Kubernetes 资源不同,工作节点并不直接由 Kubernetes 控制平面创建,而是仅由其管理。这意味着当 Kubernetes 创建节点时,实际上只创建一个表示工作节点的对象。它通过基于节点的 metadata.name 字段的健康检查来验证节点,如果节点有效——即所有必要的组件正在运行——则认为它是集群的一部分;否则,在节点变为有效之前,它将被忽略不参与任何集群活动。

参见

12.9 为维护而排空 Kubernetes 节点

问题

您需要对节点执行维护操作,例如应用安全补丁或升级操作系统。

解决方案

使用 kubectl drain 命令。例如,使用 kubectl get nodes 列出节点,然后执行对节点 123-worker 的维护:

$ kubectl drain 123-worker

当您准备好将节点重新投入服务时,请使用 kubectl uncordon 123-worker 命令,这将使节点再次可调度。

讨论

kubectl drain 命令首先将指定节点标记为不可调度,以阻止新的 Pod 进入(本质上是 kubectl cordon)。然后,如果 API 服务器支持驱逐,它会驱逐这些 Pod。否则,它将使用 kubectl delete 删除这些 Pod。Kubernetes 文档中有一个简洁的步骤序列图,详见图 12-3。

节点排空序列图

图 12-3. 节点排空序列图

kubectl drain 命令会驱逐或删除除镜像 Pod 外的所有 Pod(不能通过 API 服务器删除的镜像 Pod)。对于由 DaemonSet 管理的 Pod,如果不使用 --ignore-daemonsetsdrain 将不会继续进行;而且无论如何都不会删除任何由 DaemonSet 管理的 Pod——这些 Pod 将立即被 DaemonSet 控制器替换,后者会忽略不可调度标记。

警告

drain 等待优雅终止,因此在 kubectl drain 命令完成之前不应操作此节点。请注意,kubectl drain $NODE --force 还将驱逐不由 ReplicationControllerReplicaSetJobDaemonSetStatefulSet 管理的 Pod。

参见

第十三章:服务网格

本章重点介绍了使在 Kubernetes 上开发分布式、基于微服务的应用更加轻松的构建块之一:服务网格。像 Istio 和 Linkerd 这样的服务网格可以执行监控、服务发现、流量控制和安全等任务。通过将这些责任交给网格,应用开发者可以专注于提供附加值,而不是通过解决横向基础设施问题来重新发明轮子。

服务网格的一个主要优点是,它们可以在不需要服务(客户端和服务器)知道自己是服务网格的一部分的情况下,透明地对服务应用策略。

在本章中,我们将使用 Istio 和 Linkerd 分别进行基本示例。对于每个服务网格,我们将展示如何使用 Minikube 快速启动并实现网格内服务到服务的通信,同时使用简单但有说明性的服务网格策略。在这两个示例中,我们将部署一个基于 NGINX 的服务,并且我们的客户端调用服务将是一个 curl pod。这两者都将被添加到网格中,并且服务之间的交互将由网格管理。

13.1 安装 Istio 服务网格

问题

您的组织正在使用或计划使用微服务架构,并且希望通过卸载构建安全性、服务发现、遥测、部署策略等非功能性关注点的需要来减轻开发者的负担。

解决方案

在 Minikube 上安装 Istio。Istio 是目前最广泛采用的服务网格,可以减轻许多微服务开发者的责任,同时为运维人员提供集中式的安全与运维治理。

首先,您需要启动 Minikube,并为运行 Istio 分配足够的资源。确切的资源需求取决于您的平台,您可能需要调整资源分配。我们已经成功地使用了将近 8 GB 的内存和四个 CPU:

$ minikube start --memory=7851 --cpus=4

您可以使用 Minikube 隧道作为 Istio 的负载均衡器。要启动它,请在新的终端中运行以下命令(它将锁定终端以显示输出信息):

$ minikube tunnel

使用以下命令下载并解压最新版本的 Istio(适用于 Linux 和 macOS):

$ curl -L https://istio.io/downloadIstio | sh -

对于 Windows,你可以使用 choco 进行安装,或者直接从可下载的归档中提取 .exe。获取 Istio 的更多信息,请查阅 Istio 的入门指南

切换到 Istio 目录。您可能需要根据安装的 Istio 版本适应目录名称:

$ cd istio-1.18.0

istioctl 命令行工具旨在帮助调试和诊断您的服务网格,您将在其他示例中使用它来检查您的 Istio 配置。它位于 bin 目录中,请像这样将其添加到您的路径中:

$ export PATH=$PWD/bin:$PATH

现在你可以安装 Istio 了。以下 YAML 文件包含一个示例演示配置。它有意禁用了将 Istio 作为入口或出口网关使用,因为我们这里不会使用 Istio 作为入口。将此配置保存在名为 istio-demo-config.yaml 的文件中:

apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  profile: demo
  components:
    ingressGateways:
    - name: istio-ingressgateway
      enabled: false
    egressGateways:
    - name: istio-egressgateway
      enabled: false

现在使用 istioctl 将此配置应用到 Minikube:

$ istioctl install -f istio-demo-config.yaml -y
✔ Istio core installed
✔ Istiod installed
✔ Installation complete

最后,请确保 Istio 配置为自动注入 Envoy sidecar 代理到您部署的服务中。您可以使用以下命令为默认命名空间启用此功能:

$ kubectl label namespace default istio-injection=enabled
namespace/default labeled

讨论

本指南使用底层项目(如 Kubernetes 和 Istio)的默认(有时意味着最新)版本。

您可以根据您当前生产环境的版本自定义这些版本。例如,要设置您想要使用的 Istio 版本,请在下载 Istio 时使用 ISTIO_VERSIONTARGET_ARCH 参数。例如:

$ curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.18.0 \
    TARGET_ARCH=x86_64 sh -

参见

13.2 部署带有 Istio sidecar 的微服务

问题

您想将新服务部署到服务网格中,这意味着应自动向服务的 pod 注入 sidecar。sidecar 将拦截所有服务的进出流量,并允许实施路由、安全和监控策略(等等),而无需修改服务本身的实现。

解决方案

我们将使用 NGINX 作为一个简单的服务进行工作。首先创建一个 NGINX 的部署:

$ kubectl create deployment nginx --image nginx:1.25.2
deployment.apps/nginx created

然后将其暴露为 Kubernetes 服务:

$ kubectl expose deploy/nginx --port 80
service/nginx exposed

注意

Istio 不会在 Kubernetes 上创建新的 DNS 条目,而是依赖于 Kubernetes 或您可能正在使用的任何其他服务注册表中注册的现有服务。在本章后面,您将部署一个 curl pod,该 pod 调用 nginx 服务,并将 curl 主机设置为 nginx 以进行 DNS 解析,但随后 Istio 将通过拦截请求并允许您定义额外的流量控制策略来发挥其魔力。

现在列出默认命名空间中的 pods。服务的 pod 中应该有两个容器:

$ kubectl get po
NAME                          READY   STATUS    RESTARTS   AGE
nginx-77b4fdf86c-kzqvt        2/2     Running   0          27s

如果您调查此 pod 的详细信息,您会发现 Istio sidecar 容器(基于 Envoy 代理)已注入到 pod 中:

$ kubectl get pods -l app=nginx -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Pod
  metadata:
    labels:
      app: nginx
      ...
  spec:
    containers:
    - image: nginx:1.25.2
      imagePullPolicy: IfNotPresent
      name: nginx
      resources: {}

    ...
kind: List
metadata:
  resourceVersion: ""

讨论

本文假设您已经使用命名空间标记技术在命名空间中启用了自动 sidecar 注入,如 Recipe 13.1 所示。但是,并非必须将 sidecar 注入到命名空间中的每个 pod 中。在这种情况下,您可以手动选择应包含 sidecar 并因此添加到网格中的 pods。您可以在 官方 Istio 文档 中了解有关手动 sidecar 注入的更多信息。

参见

13.3 使用 Istio 虚拟服务进行流量路由

问题

你想在集群中部署另一个服务,该服务将调用之前部署的nginx服务,但你不想在服务本身中编写任何路由或安全逻辑。你还希望尽可能地解耦客户端和服务器。

解决方案

我们将通过部署一个将被添加到 mesh 中并调用nginx服务的curl pod 来模拟服务网格内的服务间通信。

为了将curl pod 与运行nginx的特定 pod 解耦,你将创建一个 Istio 虚拟服务。curl pod 只需要知道虚拟服务即可。Istio 及其 sidecars 将拦截并路由从客户端到服务的流量。

在名为virtualservice.yaml的文件中创建以下虚拟服务规范:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: nginx-vs
spec:
  hosts:
  - nginx
  http:
  - route:
    - destination:
        host: nginx

创建虚拟服务:

$ kubectl apply -f virtualservice.yaml

然后运行一个curl pod,你将用它来调用该服务。因为你已经在default命名空间中部署了curl pod,并且在该命名空间中激活了自动注入 sidecar(Recipe 13.1),curl pod 将自动获得一个 sidecar 并添加到 mesh 中:

$ kubectl run mycurlpod --image=curlimages/curl -i --tty -- sh

注意

如果你意外退出了curl pod 的 shell,你可以使用kubectl exec命令再次进入该 pod:

$ kubectl exec -i --tty mycurlpod -- sh

现在你可以从curl pod 中调用nginx虚拟服务:

$ curl -v nginx
*   Trying 10.152.183.90:80...
* Connected to nginx (10.152.183.90) port 80 (#0)
> GET / HTTP/1.1
> Host: nginx
> User-Agent: curl/8.1.2
> Accept: */*
>
> HTTP/1.1 200 OK
> server: envoy
...

你将看到来自nginx服务的响应,但请注意 HTTP 头server: envoy表明响应实际上来自运行在nginx pod 中的 Istio sidecar。

注意

为了从curl引用虚拟服务,我们使用引用 Kubernetes 服务名称(在本例中为nginx)的简短名称。在幕后,这些名称被转换为完全限定域名,如nginx.default.svc.cluster.local。正如你所见,完全限定名称包括命名空间名称(在本例中为default)。为了安全起见,在生产用例中建议你明确使用完全限定名称以避免配置错误。

讨论

本文重点介绍了服务网格内的服务间通信(也称为东西向通信),这是该技术的亮点。然而,Istio 和其他服务网格也能够执行网关职责(也称为入口南北向通信),例如在网格外(或 Kubernetes 集群外)运行的客户端与网格内运行的服务之间的交互。

在撰写本文时,Istio 的网关资源正在逐渐被新的Kubernetes Gateway API所取代。

参见

13.4 使用 Istio 虚拟服务重写 URL

问题

一个旧版客户端正在使用一个不再有效的服务 URL 和路径。您希望动态重写路径,以便正确调用该服务,而无需更改客户端。

您可以通过在curl容器中调用/legacypath路径来模拟此问题,这将产生 404 Not Found 响应:

$ curl -v nginx/legacypath
*   Trying 10.152.183.90:80...
* Connected to nginx (10.152.183.90) port 80 (#0)
> GET /legacypath HTTP/1.1
> Host: nginx
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 404 Not Found
< server: envoy
< date: Mon, 26 Jun 2023 09:37:43 GMT
< content-type: text/html
< content-length: 153
< x-envoy-upstream-service-time: 20
<
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.25.1</center>
</body>
</html>

解决方案

使用 Istio 重写旧路径,以便它达到服务的有效端点,在我们的示例中将是nginx服务的根目录。

更新虚拟服务以包含 HTTP 重写:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: nginx-vs
spec:
  hosts:
  - nginx
  http:
  - match:
    - uri:
        prefix: /legacypath
    rewrite:
      uri: /
    route:
    - destination:
        host: nginx
  - route:
    - destination:
        host: nginx

然后应用更改:

$ kubectl apply -f virtualservice.yaml

更新后的虚拟服务包含一个match属性,该属性将查找旧路径并将其重写为简单地指向根端点。

现在,从curl容器调用旧路径将不再产生 404 错误,而是产生 200 OK 响应:

$ curl -v nginx/legacypath
*   Trying 10.152.183.90:80...
* Connected to nginx (10.152.183.90) port 80 (#0)
> GET /legacypath HTTP/1.1
> Host: nginx
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK

讨论

虚拟服务的主要作用是定义从客户端到上游服务的路由。如需对上游服务的请求有额外的控制,请参考Istio 关于目标规则的文档

参见

13.5 安装 Linkerd 服务网格

问题

您的项目需要小型占地面积和/或不需要 Istio 提供的所有功能,例如对非 Kubernetes 工作负载的支持或对 egress 的本地支持。

解决方案

您可能有兴趣尝试 Linkerd,它定位为 Istio 的更轻量级替代品。

首先,如果您直接按照 Istio 的示例操作,您可以使用类似kubectl delete all --all的命令重置您的环境(请注意,这会从您的集群中删除所有内容!)。

您可以通过执行以下命令并按终端中的说明操作,手动安装 Linkerd:

$ curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh

前一个命令的输出将包括额外的步骤,包括更新您的PATH以及其他检查和安装命令,这些命令对完成安装 Linkerd 至关重要。在撰写本文时,以下片段显示了这些说明:

...
Add the linkerd CLI to your path with:

  export PATH=$PATH:/Users/jonathanmichaux/.linkerd2/bin

Now run:

  linkerd check --pre                     # validate that Linkerd can be inst...
  linkerd install --crds | kubectl apply -f - # install the Linkerd CRDs
  linkerd install | kubectl apply -f -    # install the control plane into the...
  linkerd check                           # validate everything worked!
...

当您运行这些install命令中的第二个时,您可能会收到错误消息,建议您使用额外的参数重新运行该命令,如下所示:

linkerd install --set proxyInit.runAsRoot=true | kubectl apply -f -

安装结束时,您将被要求运行一个命令来检查所有内容是否正常运行:

$ linkerd check
...
linkerd-control-plane-proxy
---------------------------
√ control plane proxies are healthy
√ control plane proxies are up-to-date
√ control plane proxies and cli versions match

Status check results are √

您还应该能够看到在linkerd命名空间中运行的 Linkerd pods:

$ kubectl get pods -n linkerd
NAME                                     READY   STATUS    RESTARTS   AGE
linkerd-destination-6b8c559b89-rx8f7     4/4     Running   0          9m23s
linkerd-identity-6dd765fb74-52plg        2/2     Running   0          9m23s
linkerd-proxy-injector-f54b7f688-lhjg6   2/2     Running   0          9m22s

确保 Linkerd 配置为自动注入 Linkerd 代理到您部署的服务中。您可以通过以下命令为默认命名空间启用此功能:

$ kubectl annotate namespace default linkerd.io/inject=enabled
namespace/default annotate

讨论

威廉·摩根,Buoyant Inc.的联合创始人兼 CEO,于 2016 年首次提出了服务网格一词。自那时起,Buoyant 的 Linkerd 社区一直专注于提供一个功能齐全、性能卓越的产品。

正如问题陈述中提到的,在撰写本文时,Linkerd 的主要限制之一是它只能网格化运行在 Kubernetes 上的服务。

另请参阅

13.6 将服务部署到 Linkerd 网格中

问题

您想将一个服务部署到 Linkerd 网格中,并向其 pod 注入一个 sidecar。

解决方案

让我们部署与 Istio 相同的nginx服务,它会响应其根端点上的 HTTP GET 请求,并在其他请求上返回 404 响应。

首先创建一个 NGINX 部署:

$ kubectl create deployment nginx --image nginx:1.25.2
deployment.apps/nginx created

然后将其公开为 Kubernetes 服务:

$ kubectl expose deploy/nginx --port 80
service/nginx exposed

现在列出默认命名空间中的 pod。在nginx服务的 pod 中应该有两个容器:

$ kubectl get po
NAME                     READY   STATUS    RESTARTS   AGE
nginx-748c667d99-fjjm4   2/2     Running   0          13s

如果您调查此 pod 的详细信息,您会发现已注入两个 Linkerd 容器到该 pod 中。其中一个是初始化容器,在路由 TCP 流量到 pod 以及在其他 pod 启动之前终止时起作用。另一个容器是 Linkerd 代理本身:

$ kubectl describe pod -l app=nginx | grep Image:
    Image:          cr.l5d.io/linkerd/proxy-init:v2.2.1
    Image:          cr.l5d.io/linkerd/proxy:stable-2.13.5
    Image:          nginx

讨论

与 Istio 类似,Linkerd 依赖于一个 sidecar 代理,也被称为大使容器,它被注入到 pod 中并为其运行的服务提供附加功能。

Linkerd CLI 提供了linkerd inject命令作为一种有用的替代方法,用于决定何时以及何地将 Linkerd 代理容器注入到应用程序 pod 中,而无需自行操纵标签。您可以在Linkerd 文档中了解更多信息。

另请参阅

13.7 将流量路由到 Linkerd 中的服务

问题

您想将一个服务部署到网格中,该服务将调用您在上一个示例中部署的nginx服务,并验证 Linkerd 及其 sidecar 是否拦截和路由流量。

解决方案

我们将通过部署一个curl pod 来模拟服务网格内的服务间通信,该 pod 将被添加到网格中并调用nginx服务。正如您在本示例中所见,Linkerd 中的路由策略定义方式不同。

首先运行一个curl pod,您将用它来调用服务。因为您在默认命名空间中启动了curl pod,并且在此命名空间中启用了自动注入 sidecar(Recipe 13.5),curl pod 将自动获取一个 sidecar 并添加到网格中:

$ kubectl run mycurlpod --image=curlimages/curl -i --tty -- sh
Defaulted container "linkerd-proxy" out of: linkerd-proxy, mycurlpod,
linkerd-init (init)
error: Unable to use a TTY - container linkerd-proxy did not allocate one
If you don't see a command prompt, try pressing enter.

注意

由于 Linkerd 修改了网格化 pod 中默认的容器排序,之前的run命令将失败,因为它试图进入 Linkerd 代理而不是我们的curl容器。

要绕过此问题,您可以使用 CTRL-C 解除终端阻塞,然后使用-c标志运行命令以连接到正确的容器:

$ kubectl attach mycurlpod -c mycurlpod -i -t

现在您可以从curl pod 中调用nginx服务:

$ curl -v nginx
*   Trying 10.111.17.127:80...
* Connected to nginx (10.111.17.127) port 80 (#0)
> GET / HTTP/1.1
> Host: nginx
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< server: nginx/1.25.1
...
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

注意

您将看到nginx服务的响应,但与 Istio 不同的是,目前还没有明确的指示表明 Linkerd 已成功拦截此请求。

要开始向nginx服务添加 Linkerd 路由策略,请在名为linkerd-server.yaml的文件中定义 Linkerd 的Server资源,如下所示:

apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  port: 80

然后创建服务器:

$ kubectl apply -f linkerd-server.yaml
server.policy.linkerd.io/nginx created

现在,如果您从curl pod 再次调用服务,您将收到确认信息表明 Linkerd 正在拦截此请求,因为默认情况下它将拒绝未关联授权策略的服务器的请求:

$ curl -v nginx
*   Trying 10.111.17.127:80...
* Connected to nginx (10.111.17.127) port 80 (#0)
> GET / HTTP/1.1
> Host: nginx
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< l5d-proxy-error: client 10.244.0.24:53274: server: 10.244.0.23:80:
unauthorized request on route
< date: Wed, 05 Jul 2023 20:33:24 GMT
< content-length: 0
<

讨论

正如您所看到的,Linkerd 使用 pod 选择器标签来确定哪些 pod 受到 mesh 策略的管理。相比之下,Istio 的VirtualService资源直接通过名称引用服务。

13.8 针对 Linkerd 服务器的流量授权

问题

您已经将类似nginx的服务添加到了 mesh 中,并声明其为 Linkerd 服务器,但现在由于默认情况下 mesh 要求所有声明的服务器进行授权,因此您正在收到 403 Forbidden 响应。

解决方案

Linkerd 提供了不同的策略来定义哪些客户端可以联系哪些服务器。在这个例子中,我们将使用 Linkerd 的AuthorizationPolicy来指定哪些服务账户可以调用nginx服务。

在您的开发环境中,curl pod 正在使用default服务账户,除非另有指定。在生产环境中,您的服务将具有专用的服务账户。

首先创建一个名为linkerd-auth-policy.yaml的文件,如下所示:

apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
  name: nginx-policy
spec:
  targetRef:
    group: policy.linkerd.io
    kind: Server
    name: nginx
  requiredAuthenticationRefs:
    - name: default
      kind: ServiceAccount

此策略声明任何使用default服务账户的客户端将能够访问您在前一篇中创建的名为nginx的 Linkerd 服务器。

应用此策略:

$ kubectl apply -f linkerd-auth-policy.yaml
authorizationpolicy.policy.linkerd.io/nginx-policy created

现在您可以从curl pod 调用nginx服务并获得 200 OK:

$ curl -v nginx
*   Trying 10.111.17.127:80...
* Connected to nginx (10.111.17.127) port 80 (#0)
> GET / HTTP/1.1
> Host: nginx
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
...
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

讨论

控制对服务器访问的另一种方法包括基于 TLS 身份的策略、基于 IP 的策略、通过使用 pod 选择器特定引用客户端,以及这些方法的任意组合。

此外,可以应用默认策略,限制访问未经 Linkerd Server资源正式引用的服务。

参见

第十四章:无服务器和事件驱动应用程序

无服务器代表一种云原生开发范式,赋予开发者创建和部署应用程序的能力,而无需管理服务器的负担。虽然服务器仍然是方程式的一部分,但该平台将其从应用程序开发的复杂性中抽象出来。

在本章中,您将找到展示如何使用 Knative 栈在 Kubernetes 上部署无服务器工作负载的示例。

14.1 安装 Knative Operator

问题

您希望将 Knative 平台部署到您的集群中。

解决方案

使用 Knative Operator,您可以轻松地将 Knative 栈组件部署到您的集群中。该操作员定义了自定义资源 (CRs),使您能够轻松配置、安装、升级和管理 Knative 栈的生命周期。

要从 发布页面 安装 Knative Operator 的 1.11.4 版本,请执行以下操作:

$ kubectl apply -f https://github.com/knative/operator/releases/download/
knative-v1.11.4/operator.yaml

验证操作员是否在运行:

$ kubectl get deployment knative-operator
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
knative-operator   1/1     1            1           13s

讨论

Knative 是一个开源项目,旨在为 Kubernetes 上的无服务器、云原生应用程序开发、部署和管理组件。该平台由两个主要组件组成,即 ServingEventing

虽然 Knative Operator 是部署和配置 Knative 组件的首选方法,但这些组件也可以使用其各自发布页面上提供的 YAML 文件部署。

14.2 安装 Knative Serving 组件

问题

您已安装了 Knative Operator(参见 Recipe 14.1),现在您想要部署 Knative Serving 组件来运行无服务器应用程序。

解决方案

使用 Knative Operator 提供的 KnativeServing 自定义资源安装 Knative 的 Serving 组件。

Knative Serving 应安装在名为 knative-serving 的命名空间中:

$ kubectl create ns knative-serving
namespace/knative-serving created

您必须创建一个 KnativeServing CR,添加一个网络层,并配置 DNS。对于网络层,我们将使用 Kourier,这是 Knative Serving 的轻量级 Ingress 对象。对于 DNS,我们将使用 sslip.io DNS 服务。

创建名为 serving.yaml 的文件,并包含以下内容:

apiVersion: operator.knative.dev/v1beta1
kind: KnativeServing
metadata:
  name: knative-serving
  namespace: knative-serving
spec:
  ingress:
    kourier:
      enabled: true
  config:
    network:
      ingress-class: "kourier.ingress.networking.knative.dev"

现在使用 kubectl 应用此配置:

$ kubectl apply -f serving.yaml
knativeserving.operator.knative.dev/knative-serving created

Knative Serving 组件成功部署需要几分钟时间。您可以使用以下命令来查看其部署状态:

$ kubectl -n knative-serving get KnativeServing knative-serving -w
NAME              VERSION   READY   REASON
knative-serving   1.11.0    False   NotReady
knative-serving   1.11.0    False   NotReady
knative-serving   1.11.0    True

或者,您可以使用 YAML 文件安装 Knative Serving:

$ kubectl apply -f https://github.com/knative/serving/releases/download/
knative-v1.11.0/serving-crds.yaml
$ kubectl apply -f https://github.com/knative/serving/releases/download/
knative-v1.11.0/serving-core.yaml

检查 kourier 服务是否已分配外部 IP 地址或 CNAME:

$ kubectl -n knative-serving get service kourier
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)         AGE
kourier   LoadBalancer   10.99.62.226   10.99.62.226   80:30227/T...   118s

提示

在 Minikube 集群上,在终端中运行命令 minikube tunnel,以便为 kourier 服务分配外部 IP 地址。

最后,配置 Knative Serving 使用 sslip.io 作为 DNS 后缀:

$ kubectl apply -f https://github.com/knative/serving/releases/download/
knative-v1.11.0/serving-default-domain.yaml
job.batch/default-domain created
service/default-domain-service created

讨论

Knative Serving 组件启用了 Serving API。它提供了一个高级抽象,用于部署、管理和自动缩放无状态、请求驱动的无服务器工作负载应用程序。其主要目标是简化以无服务器方式部署和管理容器化应用程序的过程,使开发人员能够专注于编写代码,而无需管理基础设施问题。

sslip.io 是一个 DNS 服务,允许您通过域名轻松访问部署在 Knative 上的应用程序,无需管理 DNS 记录。服务的 URL 将带有 sslip.io 后缀,当使用包含嵌入 IP 地址的主机名查询时,服务将返回该 IP 地址。

在生产环境中,强烈建议为部署在 Knative 上的工作负载配置一个真实的 DNS

另请参阅

14.3 安装 Knative CLI

问题

您已安装了 Knative Operator(Recipe 14.1),现在希望有一种简便的方法来管理 Knative 资源,而不必处理 YAML 文件。

解决方案

使用 kn,即 Knative CLI。

从 GitHub 的发布页面安装 kn 二进制文件,并将其移动到您的 $PATH。例如,在 macOS(Intel)上安装 kn v1.8.2,请执行以下操作:

$ wget https://github.com/knative/client/releases/download/knative-v1.11.0/
kn-darwin-amd64

$ sudo install -m 755 kn-darwin-amd64 /usr/local/bin/kn

或者,Linux 和 macOS 用户可以使用 Homebrew 包管理器安装 Knative CLI:

$ brew install knative/client/kn

kn 的安装在项目页面有详细文档。

讨论

kn 提供了一种快速简便的方式来创建 Knative 资源,比如服务和事件源,而无需直接处理 YAML 文件。kn 工具提供了多个命令来管理 Knative 资源。

要查看可用命令的概述,请执行以下操作:

$ kn help
kn is the command line interface for managing Knative Serving and Eventing

Find more information about Knative at: https://knative.dev

Serving Commands:
  service      Manage Knative services
  revision     Manage service revisions
  ...

Eventing Commands:
  source       Manage event sources
  broker       Manage message brokers
  ...

Other Commands:
  plugin       Manage kn plugins
  completion   Output shell completion code
  version      Show the version of this client

您将在本章的其余部分找到 kn 的使用示例场景。

14.4 创建一个 Knative Service

问题

您已安装了 Knative Serving(请参阅 Recipe 14.2),现在希望在 Kubernetes 上部署一个应用程序,当不使用时将释放集群资源。

解决方案

使用 Knative Serving API 创建一个 Knative Service,在不使用时自动缩减至零。

例如,让我们部署应用程序 functions/nodeinfo,它提供有关其运行的 Kubernetes 节点的信息。创建一个名为 nodeinfo.yaml 的文件,以将该应用程序部署为一个 Knative Service

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: nodeinfo
spec:
  template:
    spec:
      containers:
        - image: functions/nodeinfo:latest

需要注意的是,这种类型的服务与 Figure 5-1 中描述的 Service 对象不同;相反,此 Service 对象是从Knative Serving API 实例化的。

使用以下命令部署应用程序:

$ kubectl apply -f nodeinfo.yaml
service.serving.knative.dev/nodeinfo created

使用以下命令检查服务的状态:

$ kubectl get ksvc nodeinfo
NAME       URL                          LATESTCREATED    LATESTREADY      READY
nodeinfo   http://nodeinfo...sslip.io   nodeinfo-00001   nodeinfo-00001   True

服务成功启动后,在浏览器中打开 URL 以查看节点信息。

现在,查看为服务创建的 pods:

$ kubectl get po -l serving.knative.dev/service=nodeinfo -w
NAME                       READY   STATUS    RESTARTS   AGE
nodeinfo-00001-deploy...   0/2     Pending   0          0s
nodeinfo-00001-deploy...   0/2     Pending   0          0s
nodeinfo-00001-deploy...   0/2     ContainerCreating   0          0s
nodeinfo-00001-deploy...   1/2     Running             0          2s
nodeinfo-00001-deploy...   2/2     Running             0          2s

关闭浏览器窗口,大约两分钟后,您应该注意到nodeinfo pods 会自动缩减为零:

$ kubectl get po -l serving.knative.dev/service=nodeinfo
No resources found in default namespace.

现在,如果在浏览器中打开 URL,将自动启动一个新的Pod对象来处理传入的请求。由于创建新的Pod来处理此请求,您应该注意到页面渲染的延迟。

讨论

使用kn客户端(参见 Recipe 14.3),您可以创建服务而无需编写 YAML 文件:

$ kn service create nodeinfo --image functions/nodeinfo:latest --port 8080
Creating service 'nodeinfo' in namespace 'default':

  0.054s The Route is still working to reflect the latest desired specification.
  0.068s Configuration "nodeinfo" is waiting for a Revision to become ready.
  3.345s ...
  3.399s Ingress has not yet been reconciled.
  3.481s Waiting for load balancer to be ready
  3.668s Ready to serve.

Service 'nodeinfo' created to latest revision 'nodeinfo-00001' is available at
URL: http://nodeinfo.default.10.96.170.166.sslip.io

14.5 安装 Knative Eventing 组件

问题

您已安装 Knative Operator(参见 Recipe 14.1),现在您想部署Knative Eventing组件来构建基于事件驱动的应用程序。

解决方案

使用 Knative Operator 提供的KnativeEventing自定义资源安装 Knative 的 Eventing 组件。

Knative Eventing 应安装在名为knative-eventing的命名空间中:

$ kubectl create ns knative-eventing
namespace/knative-eventing created

创建一个名为eventing.yaml的文件,并包含以下内容:

apiVersion: operator.knative.dev/v1beta1
kind: KnativeEventing
metadata:
  name: knative-eventing
  namespace: knative-eventing

现在使用kubectl应用此配置:

$ kubectl apply -f eventing.yaml
knativeeventing.operator.knative.dev/knative-eventing created

Knative Eventing组件成功部署需要几分钟时间。您可以使用以下命令观察其部署状态:

$ kubectl --namespace knative-eventing get KnativeEventing knative-eventing -w
NAME               VERSION   READY   REASON
knative-eventing   1.11.1    False   NotReady
knative-eventing   1.11.1    False   NotReady
knative-eventing   1.11.1    False   NotReady
knative-eventing   1.11.1    True

或者,通过 YAML 文件安装 Knative Eventing,请执行以下操作:

$ kubectl apply -f https://github.com/knative/eventing/releases/download/
knative-v1.11.1/eventing-crds.yaml
$ kubectl apply -f https://github.com/knative/eventing/releases/download/
knative-v1.11.1/eventing-core.yaml

然后安装内存通道和MTChannelBasedBroker

$ kubectl apply -f https://github.com/knative/eventing/releases/download/
knative-v1.11.1/in-memory-channel.yaml
$ kubectl apply -f https://github.com/knative/eventing/releases/download/
knative-v1.11.1/mt-channel-broker.yaml

讨论

Knative Eventing 组件使得 Eventing API 生效。它提供了在云原生环境中管理和处理事件的框架。这里的事件指系统内发生或变化的事件,例如创建新资源、更新现有资源或外部触发器。此组件使开发人员能够构建响应实时变化和触发器的反应式和灵活应用程序,跨云原生生态系统响应这些变化和触发器。

14.6 部署 Knative Eventing 源

问题

您已安装 Knative Eventing(参见 Recipe 14.5),现在您想部署一个生成事件的源,以便可以使用这些事件来触发 Knative 中的工作流。

解决方案

事件源是一个 Kubernetes 自定义资源,充当事件生产者和事件接收器之间的链接。要检查当前可用的事件源,请执行以下操作:

$ kubectl api-resources --api-group='sources.knative.dev'
NAME              SHORTNAMES  APIVERSION           NAMESPACED   KIND
apiserversources              sources.kn...dev/v1  true         ApiServerSource
containersources              sources.kn...dev/v1  true         ContainerSource
pingsources                   sources.kn...dev/v1  true         PingSource
sinkbindings                  sources.kn...dev/v1  true         SinkBinding

PingSource是一个事件源,定期按照 cron 调度定义的固定载荷生成事件。让我们部署一个PingSource并将其连接到名为sockeyeSink

首先创建sockeye服务:

$ kubectl apply -f https://github.com/n3wscott/sockeye/releases/download/
v0.7.0/release.yaml
service.serving.knative.dev/sockeye created

验证sockeye服务是否成功创建:

$ kubectl get ksvc sockeye
NAME      URL                         LATESTCREATED   LATESTREADY     READY
sockeye   http://sockeye...sslip.io   sockeye-00001   sockeye-00001   True

创建一个名为pingsource.yaml的文件,用来创建PingSource并将其与sockeye应用程序连接起来:

apiVersion: sources.knative.dev/v1
kind: PingSource
metadata:
  name: ping-source
spec:
  schedule: "* * * * *"
  contentType: "application/json"
  data: '{ "message": "Hello, world!" }'
  sink:
    ref:
      apiVersion: serving.knative.dev/v1
      kind: Service
      name: sockeye

应用此清单:

$ kubectl apply -f pingsource.yaml
pingsource.sources.knative.dev/ping-source created

验证PingSource是否成功创建:

$ kubectl get pingsource ping-source -w
NAME          ...   AGE   READY   REASON
ping-source   ...   52s   False   MinimumReplicasUnavailable
ping-source   ...   59s   True

使用以下命令获取sockeye服务的 URL:

$ kubectl get ksvc sockeye  -o jsonpath={.status.url}
http://sockeye.default.10.99.62.226.sslip.io

在浏览器中打开 URL 后,您应该可以看到每分钟出现新事件,如 Figure 14-1 所示。

Sockeye 中显示的事件截图

图 14-1. Sockeye 中显示的事件

讨论

如果您不想编写 YAML 文件,可以使用kn客户端(参见 Recipe 14.3)。

使用以下命令创建sockeye服务:

$ kn service create sockeye --image docker.io/n3wscott/sockeye:v0.7.0

接下来,创建PingSource

$ kn source ping create ping-source --data '{ "message": "Hello, world!" }' \
    --schedule '* * * * *' --sink sockeye

14.7 启用 Knative Eventing 事件源

问题

您已安装了 Knative Eventing 组件(参见 Recipe 14.5),并且希望启用默认未启用的 Knative 事件源。

解决方案

其他事件源由 Knative 社区开发,例如 GitHub、GitLab、Apache Kafka 等,可以在 Knative Eventing 自定义资源中配置。例如,要安装GitHub 事件源,请按照 Recipe 14.5 更新eventing.yaml文件:

apiVersion: operator.knative.dev/v1beta1
kind: KnativeEventing
metadata:
  name: knative-eventing
  namespace: knative-eventing
spec:
  source:
    github:
      enabled: true

使用以下命令应用更改:

$ kubectl apply -f eventing.yaml
knativeeventing.operator.knative.dev/knative-eventing configured

查看更新的状态:

$ kubectl -n knative-eventing get KnativeEventing knative-eventing -w
NAME               VERSION   READY   REASON
knative-eventing   1.11.1    False   NotReady
knative-eventing   1.11.1    True

现在,如果您检查可用的事件源,应该会看到GitHubSource事件源:

$ kubectl api-resources --api-group='sources.knative.dev'
NAME                 APIVERSION                  NAMESPACED    KIND
apiserversources     sources.kn..dev/v1          true          ApiServerSource
containersources     sources.kn..dev/v1          true          ContainerSource
githubsources        sources.kn..dev/v1alpha1    true          GitHubSource
pingsources          sources.kn..dev/v1          true          PingSource
sinkbindings         sources.kn..dev/v1          true          SinkBinding

讨论

GitHubSource事件源注册以便在指定的 GitHub 组织或仓库上监听事件,并对所选的 GitHub 事件类型触发新的事件。

还可以获取 GitLab、Apache Kafka、RabbitMQ 等开源事件源。

14.8 安装 TriggerMesh 的事件源

问题

您已安装了 Knative Eventing(参见 Recipe 14.5),现在您希望安装 TriggerMesh 提供的事件源,以便您可以访问各种平台和服务的事件源。

解决方案

要安装 TriggerMesh 的 v1.26.0 版本,请执行以下操作:

$ kubectl apply -f https://github.com/triggermesh/triggermesh/releases/
download/v1.26.0/triggermesh-crds.yaml
...k8s.io/awscloudwatchlogssources.sources.triggermesh.io created
...k8s.io/awscloudwatchsources.sources.triggermesh.io created
...k8s.io/awscodecommitsources.sources.triggermesh.io create
...

$ kubectl apply -f https://github.com/triggermesh/triggermesh/releases/
download/v1.26.0/triggermesh.yaml
namespace/triggermesh created
clusterrole.rbac.authorization.k8s.io/triggermesh-namespaced-admin created
clusterrole.rbac.authorization.k8s.io/triggermesh-namespaced-edit created
clusterrole.rbac.authorization.k8s.io/triggermesh-namespaced-view created
...

您可以使用以下命令检查 TriggerMesh API 提供的事件源:

$ kubectl api-resources --api-group='sources.triggermesh.io'
NAME                     APIVERSION       NAMESPACED  KIND
awscloudwatchlog...      sources.tri...   true        AWSCloudWatchLogsSource
awscloudwatchsou...      sources.tri...   true        AWSCloudWatchSource
awscodecommitsou...      sources.tri...   true        AWSCodeCommitSource
...

类似地,您可以使用以下命令列出 TriggerMesh API 提供的所有接收器。

$ kubectl api-resources --api-group='targets.triggermesh.io'
NAME                 SHORT...  APIVERSION       NAMESPACED  KIND
awscomprehendtar...            targets.tri...   true        AWSComprehendTarget
awsdynamodbtarge...            targets.tri...   true        AWSDynamoDBTarget
awseventbridgeta...            targets.tri...   true        AWSEventBridgeTarget
...

讨论

TriggerMesh是一款免费开源软件,可让您轻松构建事件驱动应用程序。TriggerMesh 为 AWS、Google Cloud、Azure、Salesforce、Zendesk 等各种平台和服务提供事件源。除事件源外,TriggerMesh 还提供组件,使您能够转换云事件。

转到TriggerMesh 文档了解更多信息。

参见

第十五章:扩展 Kubernetes

现在你已经看到了如何安装、交互和使用 Kubernetes 来部署和管理应用程序,本章节我们将重点放在如何将 Kubernetes 调整为符合你需求的地方。在本章的示例中,你需要安装 Go 并且可以访问托管在 GitHub 上的 Kubernetes 源代码。我们展示如何编译整个 Kubernetes,以及如何编译像客户端 kubectl 这样的特定组件。我们还演示了如何使用 Python 与 Kubernetes API 服务器通信,并展示了如何通过自定义资源定义来扩展 Kubernetes。

15.1 编译源码

问题

你想要从源码构建自己的 Kubernetes 二进制文件,而不是下载官方发布的二进制文件(参见 Recipe 2.9)或第三方构件。

解决方案

克隆 Kubernetes Git 仓库并从源码构建。

如果你的开发机器已经安装了 Docker Engine,你可以使用根目录 Makefilequick-release 目标,如下所示:

$ git clone https://github.com/kubernetes/kubernetes.git
$ cd kubernetes
$ make quick-release

提示

这种基于 Docker 的构建需要至少 8 GB 的 RAM 才能完成。确保你的 Docker 守护进程可以访问到足够的内存。在 macOS 上,访问 Docker for Mac 首选项并增加分配的 RAM。

二进制文件将位于 _output/release-stage 目录中,完整的捆绑包将位于 _output/release-tars 目录中。

或者,如果你已经正确设置了 Golang 环境,可以使用根目录 Makefilerelease 目标:

$ git clone https://github.com/kubernetes/kubernetes.git
$ cd kubernetes
$ make

二进制文件将位于 _output/bin 目录中。

参见

15.2 编译特定组件

问题

你想要从源码中构建 Kubernetes 的某个特定组件。例如,你只想要构建客户端 kubectl

解决方案

而不是使用 make quick-release 或简单地使用 make,如 Recipe 15.1 中所示,使用 make kubectl

在根目录的 Makefile 中有目标可以构建各个单独的组件。例如,要编译 kubectlkubeadmkubelet,请执行以下操作:

$ make kubectl kubeadm kubelet

二进制文件将位于 _output/bin 目录中。

提示

要获取 Makefile 的完整构建目标列表,请运行 make help

15.3 使用 Python 客户端与 Kubernetes API 交互

问题

作为开发者,你想要使用 Python 编写脚本来使用 Kubernetes API。

解决方案

安装 Python 的 kubernetes 模块。这个 模块 是 Kubernetes 的官方 Python 客户端库。你可以从源码或者 Python Package Index (PyPi) 网站 安装这个模块:

$ pip install kubernetes

使用默认的 kubectl 上下文可以访问 Kubernetes 集群后,你现在可以准备使用这个 Python 模块与 Kubernetes API 交互了。例如,下面的 Python 脚本列出所有的 Pod 并打印它们的名称:

from kubernetes import client, config

config.load_kube_config()

v1 = client.CoreV1Api()
res = v1.list_pod_for_all_namespaces(watch=False)
for pod in res.items:
    print(pod.metadata.name)

此脚本中的config.load_kube_config()调用将从您的kubectl配置文件中加载 Kubernetes 凭据和终端。默认情况下,它将为当前上下文加载集群终端和凭据。

讨论

使用 OpenAPI 规范构建的 Python 客户端。它是最新的并且是自动生成的。所有 API 都可以通过此客户端访问。

每个 API 组对应于一个特定的类,因此要在属于/api/v1 API 组的 API 对象上调用方法,您需要实例化CoreV1Api类。要使用部署,您需要实例化extensionsV1beta1Api类。可以在自动生成的README中找到所有方法和相应的 API 组实例。

参见

15.4 扩展 API 使用自定义资源定义

问题

您有一个自定义工作负载,而现有的资源(如DeploymentJobStatefulSet)都不太适合。因此,您希望通过新资源扩展 Kubernetes API,表示您的工作负载,同时继续以通常的方式使用kubectl

解决方案

使用自定义资源定义(CRD)

假设您想要定义一种名为Function的自定义资源。这代表了一种类似于 AWS Lambda 提供的短期运行Job的资源,即函数即服务(FaaS,有时误称为“无服务器函数”)。

注意

对于在 Kubernetes 上运行的生产就绪 FaaS 解决方案,请参见第十四章。

首先,在名为functions-crd.yaml的清单文件中定义 CRD:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: functions.example.com
spec:
  group: example.com
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              code:
                type: string
              ram:
                type: string
  scope:              Namespaced
  names:
    plural: functions
    singular: function
    kind: Function

然后让 API 服务器知道您的新 CRD(注册可能需要几分钟):

$ kubectl apply -f functions-crd.yaml
customresourcedefinition.apiextensions.k8s.io/functions.example.com created

现在您已经定义了Function的自定义资源,并且 API 服务器知道它,您可以使用名为myfaas.yaml的清单来实例化它,内容如下:

apiVersion: example.com/v1
kind: Function
metadata:
  name: myfaas
spec:
  code: "http://src.example.com/myfaas.js"
  ram: 100Mi

然后按照通常的方式创建myfaas类型为Function的资源:

$ kubectl apply -f myfaas.yaml
function.example.com/myfaas created

$ kubectl get crd functions.example.com -o yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  creationTimestamp: "2023-05-02T12:12:03Z"
  generation: 1
  name: functions.example.com
  resourceVersion: "2494492251"
  uid: 5e0128b3-95d9-412b-b84d-b3fac030be75
spec:
  conversion:
    strategy: None
  group: example.com
  names:
    kind: Function
    listKind: FunctionList
    plural: functions
    shortNames:
    - fn
    singular: function
  scope: Namespaced
  versions:
  - name: v1
    schema:
      openAPIV3Schema:
        properties:
          spec:
            properties:
              code:
                type: string
              ram:
                type: string
            type: object
        type: object
    served: true
    storage: true
status:
  acceptedNames:
    kind: Function
    listKind: FunctionList
    plural: functions
    shortNames:
    - fn
    singular: function
  conditions:
  - lastTransitionTime: "2023-05-02T12:12:03Z"
    message: no conflicts found
    reason: NoConflicts
    status: "True"
    type: NamesAccepted
  - lastTransitionTime: "2023-05-02T12:12:03Z"
    message: the initial names have been accepted
    reason: InitialNamesAccepted
    status: "True"
    type: Established
  storedVersions:
  - v1

$ kubectl describe functions.example.com/myfaas
Name:         myfaas
Namespace:    triggermesh
Labels:       <none>
Annotations:  <none>
API Version:  example.com/v1
Kind:         Function
Metadata:
  Creation Timestamp:  2023-05-02T12:13:07Z
  Generation:          1
  Resource Version:    2494494012
  UID:                 bed83736-6c40-4039-97fb-2730c7a4447a
Spec:
  Code:  http://src.example.com/myfaas.js
  Ram:   100Mi
Events:  <none>

要发现 CRD,只需访问 API 服务器。例如,使用kubectl proxy,您可以在本地访问 API 服务器并查询键空间(在我们的情况下是example.com/v1):

$ curl 127.0.0.1:8001/apis/example.com/v1/ | jq .
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "example.com/v1",
  "resources": [
    {
      "name": "functions",
      "singularName": "function",
      "namespaced": true,
      "kind": "Function",
      "verbs": [
        "delete",
        "deletecollection",
        "get",
        "list",
        "patch",
        "create",
        "update",
        "watch"
      ],
      "shortNames": [
        "fn"
      ],
      "storageVersionHash": "FLWxvcx1j74="
    }
  ]
}

在这里,你可以看到资源以及允许的动词。

当您想要摆脱自定义资源实例时,只需删除它:

$ kubectl delete functions.example.com/myfaas
function.example.com "myfaas" deleted

讨论

正如您所见,创建 CRD 非常简单。从最终用户的角度来看,CRD 提供了一致的 API,并且在某种程度上与像 Pod 或 Job 这样的本机资源难以区分。所有通常的命令,如kubectl getkubectl delete,都能正常工作。

创建一个 CRD 实际上只是完全扩展 Kubernetes API 所需工作的不到一半。单独来看,CRD 只允许您通过 etcd 中的 API 服务器存储和检索自定义数据。您还需要编写一个自定义控制器,该控制器解释用户意图表达的自定义数据,建立一个控制循环,比较当前状态与声明状态,并尝试协调两者。

参见

附录:资源

一般

教程和示例

posted @ 2025-11-16 08:57  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报