这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

文档

1 - Kubernetes 文档

Kubernetes 是一个开源的容器编排引擎,用来对容器化应用进行自动部署、 扩缩和管理。此开源项目由云原生计算基金会(CNCF)托管。

1.1 - Kubernetes 文档支持的版本

本网站包含当前版本和之前四个版本的 Kubernetes 文档。

Kubernetes 版本的文档可用性与当前是否支持该版本是分开的。 阅读支持期限,了解官方支持 Kubernetes 的哪些版本,以及支持多长时间。

2 - 入门

本节列出了设置和运行 Kubernetes 的不同方法。

安装 Kubernetes 时,请根据以下条件选择安装类型:易于维护、安全性、可控制性、可用资源以及操作和管理 Kubernetes 集群所需的专业知识。

你可以下载 Kubernetes,在本地机器、云或你自己的数据中心上部署 Kubernetes 集群。

某些 Kubernetes 组件, 比如 kube-apiserverkube-proxy 等, 可以在集群中以容器镜像部署。

建议尽可能将 Kubernetes 组件作为容器镜像运行,并且让 Kubernetes 管理这些组件。 但是运行容器的相关组件 —— 尤其是 kubelet,不在此列。

如果你不想自己管理 Kubernetes 集群,则可以选择托管服务,包括经过认证的平台。 在各种云和裸机环境中,还有其他标准化和定制的解决方案。

学习环境

如果正打算学习 Kubernetes,请使用 Kubernetes 社区支持 或生态系统中的工具在本地计算机上设置 Kubernetes 集群。 请参阅安装工具

生产环境

在评估生产环境的解决方案时, 请考虑要自己管理 Kubernetes 集群(或相关抽象)的哪些方面,将哪些托付给提供商。

对于你自己管理的集群,官方支持的用于部署 Kubernetes 的工具是 kubeadm

接下来

Kubernetes 的设计是让其控制平面在 Linux 上运行的。 在集群中,你可以在 Linux 或其他操作系统(包括 Windows)上运行应用程序。

2.1 - 学习环境

2.2 - 生产环境

生产质量的 Kubernetes 集群需要规划和准备。 如果你的 Kubernetes 集群是用来运行关键负载的,该集群必须被配置为弹性的(Resilient)。 本页面阐述你在安装生产就绪的集群或将现有集群升级为生产用途时可以遵循的步骤。 如果你已经熟悉生产环境安装,因此只关注一些链接,则可以跳到接下来节。

生产环境考量

通常,一个生产用 Kubernetes 集群环境与个人学习、开发或测试环境所使用的 Kubernetes 相比有更多的需求。 生产环境可能需要被很多用户安全地访问,需要提供一致的可用性,以及能够与需求变化相适配的资源。

在你决定在何处运行你的生产用 Kubernetes 环境(在本地或者在云端), 以及你希望承担或交由他人承担的管理工作量时, 需要考察以下因素如何影响你对 Kubernetes 集群的需求:

  • 可用性:一个单机的 Kubernetes 学习环境 具有单点失效特点。创建高可用的集群则意味着需要考虑:
    • 将控制面与工作节点分开
    • 在多个节点上提供控制面组件的副本
    • 为针对集群的 API 服务器 的流量提供负载均衡
    • 随着负载的合理需要,提供足够的可用的(或者能够迅速变为可用的)工作节点
  • 规模:如果你预期你的生产用 Kubernetes 环境要承受固定量的请求, 你可能可以针对所需要的容量来一次性完成安装。 不过,如果你预期服务请求会随着时间增长,或者因为类似季节或者特殊事件的原因而发生剧烈变化, 你就需要规划如何处理请求上升时对控制面和工作节点的压力,或者如何缩减集群规模以减少未使用资源的消耗。
  • 安全性与访问管理:在你自己的学习环境 Kubernetes 集群上,你拥有完全的管理员特权。 但是针对运行着重要工作负载的共享集群,用户账户不止一两个时, 就需要更细粒度的方案来确定谁或者哪些主体可以访问集群资源。 你可以使用基于角色的访问控制(RBAC) 和其他安全机制来确保用户和负载能够访问到所需要的资源, 同时确保工作负载及集群自身仍然是安全的。 你可以通过管理策略容器资源 来针对用户和工作负载所可访问的资源设置约束。

在自行构建 Kubernetes 生产环境之前, 请考虑将这一任务的部分或者全部交给云方案承包服务提供商或者其他 Kubernetes 合作伙伴。选项有:

  • 无服务:仅是在第三方设备上运行负载,完全不必管理集群本身。 你需要为 CPU 用量、内存和磁盘请求等付费。
  • 托管控制面:让供应商决定集群控制面的规模和可用性,并负责打补丁和升级等操作。
  • 托管工作节点:配置一个节点池来满足你的需要,由供应商来确保节点始终可用,并在需要的时候完成升级。
  • 集成:有一些供应商能够将 Kubernetes 与一些你可能需要的其他服务集成, 这类服务包括存储、容器镜像仓库、身份认证方法以及开发工具等。

无论你是自行构造一个生产用 Kubernetes 集群还是与合作伙伴一起协作, 请审阅下面章节以评估你的需求,因为这关系到你的集群的控制面工作节点用户访问以及负载资源

生产用集群安装

在生产质量的 Kubernetes 集群中,控制面用不同的方式来管理集群和可以分布到多个计算机上的服务。 每个工作节点则代表的是一个可配置来运行 Kubernetes Pod 的实体。

生产用控制面

最简单的 Kubernetes 集群中,整个控制面和工作节点服务都运行在同一台机器上。 你可以通过添加工作节点来提升环境运算能力,正如 Kubernetes 组件示意图所示。 如果只需要集群在很短的一段时间内可用,或者可以在某些事物出现严重问题时直接丢弃, 这种配置可能符合你的需要。

如果你需要一个更为持久的、高可用的集群,那么就需要考虑扩展控制面的方式。 根据设计,运行在一台机器上的单机控制面服务不是高可用的。 如果你认为保持集群的正常运行并需要确保它在出错时可以被修复是很重要的, 可以考虑以下步骤:

  • 选择部署工具:你可以使用类似 kubeadm、kops 和 kubespray 这类工具来部署控制面。 参阅使用部署工具安装 Kubernetes 以了解使用这类部署方法来完成生产就绪部署的技巧。 存在不同的容器运行时 可供你的部署采用。
  • 管理证书:控制面服务之间的安全通信是通过证书来完成的。 证书是在部署期间自动生成的,或者你也可以使用自己的证书机构来生成它们。 参阅 PKI 证书和需求了解细节。
  • 为 API 服务器配置负载均衡:配置负载均衡器来将外部的 API 请求散布给运行在不同节点上的 API 服务实例。 参阅创建外部负载均衡器了解细节。
  • 分离并备份 etcd 服务:etcd 服务可以运行于其他控制面服务所在的机器上, 也可以运行在不同的机器上以获得更好的安全性和可用性。 因为 etcd 存储着集群的配置数据,应该经常性地对 etcd 数据库进行备份, 以确保在需要的时候你可以修复该数据库。与配置和使用 etcd 相关的细节可参阅 etcd FAQ。 更多的细节可参阅为 Kubernetes 运维 etcd 集群使用 kubeadm 配置高可用的 etcd 集群
  • 创建多控制面系统:为了实现高可用性,控制面不应被限制在一台机器上。 如果控制面服务是使用某 init 服务(例如 systemd)来运行的,每个服务应该至少运行在三台机器上。 不过,将控制面作为服务运行在 Kubernetes Pod 中可以确保你所请求的个数的服务始终保持可用。 调度器应该是可容错的,但不是高可用的。 某些部署工具会安装 Raft 票选算法来对 Kubernetes 服务执行领导者选举。 如果主节点消失,另一个服务会被选中并接手相应服务。
  • 跨多个可用区:如果保持你的集群一直可用这点非常重要,可以考虑创建一个跨多个数据中心的集群; 在云环境中,这些数据中心被视为可用区。若干个可用区在一起可构成地理区域。 通过将集群分散到同一区域中的多个可用区内,即使某个可用区不可用,整个集群能够继续工作的机会也大大增加。 更多的细节可参阅跨多个可用区运行
  • 管理演进中的特性:如果你计划长时间保留你的集群,就需要执行一些维护其健康和安全的任务。 例如,如果你采用 kubeadm 安装的集群, 则有一些可以帮助你完成 证书管理升级 kubeadm 集群 的指令。 参见管理集群了解一个 Kubernetes 管理任务的较长列表。

如要了解运行控制面服务时可使用的选项,可参阅 kube-apiserverkube-controller-managerkube-scheduler 组件参考页面。 如要了解高可用控制面的例子,可参阅高可用拓扑结构选项使用 kubeadm 创建高可用集群 以及为 Kubernetes 运维 etcd 集群。 关于制定 etcd 备份计划,可参阅对 etcd 集群执行备份

生产用工作节点

生产质量的工作负载需要是弹性的;它们所依赖的其他组件(例如 CoreDNS)也需要是弹性的。 无论你是自行管理控制面还是让云供应商来管理,你都需要考虑如何管理工作节点 (有时也简称为节点)。

  • 配置节点:节点可以是物理机或者虚拟机。如果你希望自行创建和管理节点, 你可以安装一个受支持的操作系统,之后添加并运行合适的节点服务。考虑:
    • 在安装节点时要通过配置适当的内存、CPU 和磁盘读写速率、存储容量来满足你的负载的需求。
    • 是否通用的计算机系统即足够,还是你有负载需要使用 GPU 处理器、Windows 节点或者 VM 隔离。
  • 验证节点:参阅验证节点配置以了解如何确保节点满足加入到 Kubernetes 集群的需求。
  • 添加节点到集群中:如果你自行管理你的集群,你可以通过安装配置你的机器, 之后或者手动加入集群,或者让它们自动注册到集群的 API 服务器。 参阅节点节,了解如何配置 Kubernetes 以便以这些方式来添加节点。
  • 扩缩节点:制定一个扩充集群容量的规划,你的集群最终会需要这一能力。 参阅大规模集群考察事项 以确定你所需要的节点数; 这一规模是基于你要运行的 Pod 和容器个数来确定的。 如果你自行管理集群节点,这可能意味着要购买和安装你自己的物理设备。
  • 节点自动扩缩容:大多数云供应商支持 集群自动扩缩器(Cluster Autoscaler) 以便替换不健康的节点、根据需求来增加或缩减节点个数。 参阅常见问题 了解自动扩缩器的工作方式,并参阅 Deployment 了解不同云供应商是如何实现集群自动扩缩器的。 对于本地集群,有一些虚拟化平台可以通过脚本来控制按需启动新节点。
  • 安装节点健康检查:对于重要的工作负载,你会希望确保节点以及在节点上运行的 Pod 处于健康状态。 通过使用 Node Problem Detector, 你可以确保你的节点是健康的。

生产级用户环境

在生产环境中,情况可能不再是你或者一小组人在访问集群,而是几十上百人需要访问集群。 在学习环境或者平台原型环境中,你可能具有一个可以执行任何操作的管理账号。 在生产环境中,你会需要对不同名字空间具有不同访问权限级别的很多账号。

建立一个生产级别的集群意味着你需要决定如何有选择地允许其他用户访问集群。 具体而言,你需要选择验证尝试访问集群的人的身份标识(身份认证), 并确定他们是否被许可执行他们所请求的操作(鉴权):

  • 认证(Authentication):API 服务器可以使用客户端证书、持有者令牌、 身份认证代理或者 HTTP 基本认证机制来完成身份认证操作。 你可以选择你要使用的认证方法。通过使用插件, API 服务器可以充分利用你所在组织的现有身份认证方法, 例如 LDAP 或者 Kerberos。 关于认证 Kubernetes 用户身份的不同方法的描述, 可参阅身份认证
  • 鉴权(Authorization):当你准备为一般用户执行权限判定时, 你可能会需要在 RBAC 和 ABAC 鉴权机制之间做出选择。 参阅鉴权概述, 了解对用户账户(以及访问你的集群的服务账户)执行鉴权的不同模式。
    • 基于角色的访问控制RBAC): 让你通过为通过身份认证的用户授权特定的许可集合来控制集群访问。 访问许可可以针对某特定名字空间(Role)或者针对整个集群(ClusterRole)。 通过使用 RoleBinding 和 ClusterRoleBinding 对象,这些访问许可可以被关联到特定的用户身上。
    • 基于属性的访问控制ABAC): 让你能够基于集群中资源的属性来创建访问控制策略,基于对应的属性来决定允许还是拒绝访问。 策略文件的每一行都给出版本属性(apiVersion 和 kind)以及一个规约属性的映射, 用来匹配主体(用户或组)、资源属性、非资源属性(/version 或 /apis)和只读属性。 参阅示例以了解细节。

作为在你的生产用 Kubernetes 集群中安装身份认证和鉴权机制的负责人,要考虑的事情如下:

  • 设置鉴权模式:当 Kubernetes API 服务器(kube-apiserver)启动时, 所支持的鉴权模式必须使用 --authorization-mode 标志配置。 例如,kube-apiserver.yaml(位于 /etc/kubernetes/manifests 下)中对应的标志可以设置为 Node,RBAC。 这样就会针对已完成身份认证的请求执行 Node 和 RBAC 鉴权。
  • 创建用户证书和角色绑定(RBAC):如果你在使用 RBAC 鉴权,用户可以创建由集群 CA 签名的 CertificateSigningRequest(CSR)。接下来你就可以将 Role 和 ClusterRole 绑定到每个用户身上。 参阅证书签名请求了解细节。
  • 创建组合属性的策略(ABAC):如果你在使用 ABAC 鉴权, 你可以设置属性组合以构造策略对所选用户或用户组执行鉴权, 判定他们是否可访问特定的资源(例如 Pod)、名字空间或者 apiGroup。 进一步的详细信息可参阅示例
  • 考虑准入控制器:针对指向 API 服务器的请求的其他鉴权形式还包括 Webhook 令牌认证。 Webhook 和其他特殊的鉴权类型需要通过向 API 服务器添加准入控制器来启用。

为负载资源设置约束

生产环境负载的需求可能对 Kubernetes 的控制面内外造成压力。 在针对你的集群的负载执行配置时,要考虑以下条目:

  • 为 DNS 请求做准备:如果你希望工作负载能够完成大规模扩展,你的 DNS 服务也必须能够扩大规模。 参阅自动扩缩集群中 DNS 服务
  • 创建额外的服务账户:用户账户决定用户可以在集群上执行的操作,服务账号则定义的是在特定名字空间中 Pod 的访问权限。默认情况下,Pod 使用所在名字空间中的 default 服务账号。 参阅管理服务账号以了解如何创建新的服务账号。 例如,你可能需要:

接下来

2.2.1 - 容器运行时

你需要在集群内每个节点上安装一个 容器运行时 以使 Pod 可以运行在上面。本文概述了所涉及的内容并描述了与节点设置相关的任务。

Kubernetes 1.24 要求你使用符合容器运行时接口(CRI)的运行时。

有关详细信息,请参阅 CRI 版本支持。 本页简要介绍在 Kubernetes 中几个常见的容器运行时的用法。

安装和配置先决条件

以下步骤将通用设置应用于 Linux 上的 Kubernetes 节点。

如果你确定不需要某个特定设置,则可以跳过它。

有关更多信息,请参阅网络插件要求 或特定容器运行时的文档。

转发 IPv4 并让 iptables 看到桥接流量

通过运行 lsmod | grep br_netfilter 来验证 br_netfilter 模块是否已加载。

若要显式加载此模块,请运行 sudo modprobe br_netfilter

为了让 Linux 节点的 iptables 能够正确查看桥接流量,请确认 sysctl 配置中的 net.bridge.bridge-nf-call-iptables 设置为 1。例如:

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

sudo modprobe overlay
sudo modprobe br_netfilter

# 设置所需的 sysctl 参数,参数在重新启动后保持不变
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

# 应用 sysctl 参数而不重新启动
sudo sysctl --system

Cgroup 驱动程序

在 Linux 上,控制组(CGroup) 用于限制分配给进程的资源。

当某个 Linux 系统发行版使用 systemd 作为其初始化系统时,初始化进程会生成并使用一个 root 控制组(cgroup),并充当 cgroup 管理器。 Systemd 与 cgroup 集成紧密,并将为每个 systemd 单元分配一个 cgroup。 你也可以配置容器运行时和 kubelet 使用 cgroupfs。 连同 systemd 一起使用 cgroupfs 意味着将有两个不同的 cgroup 管理器。

单个 cgroup 管理器将简化分配资源的视图,并且默认情况下将对可用资源和使用 中的资源具有更一致的视图。 当有两个管理器共存于一个系统中时,最终将对这些资源产生两种视图。 在此领域人们已经报告过一些案例,某些节点配置让 kubelet 和 docker 使用 cgroupfs,而节点上运行的其余进程则使用 systemd; 这类节点在资源压力下 会变得不稳定。

更改设置,令容器运行时和 kubelet 使用 systemd 作为 cgroup 驱动,以此使系统更为稳定。 对于 Docker,要设置 native.cgroupdriver=systemd 选项。

Cgroup v2

Cgroup v2 是 cgroup Linux API 的下一个版本。与 cgroup v1 不同的是, Cgroup v2 只有一个层次结构,而不是每个控制器有一个不同的层次结构。

新版本对 cgroup v1 进行了多项改进,其中一些改进是:

  • 更简洁、更易于使用的 API
  • 可将安全子树委派给容器
  • 更新的功能,如压力失速信息(Pressure Stall Information)

尽管内核支持混合配置,即其中一些控制器由 cgroup v1 管理,另一些由 cgroup v2 管理, Kubernetes 仅支持使用同一 cgroup 版本来管理所有控制器。

如果 systemd 默认不使用 cgroup v2,你可以通过在内核命令行中添加 systemd.unified_cgroup_hierarchy=1 来配置系统去使用它。

# 此示例适用于使用 DNF 包管理器的 Linux 操作系统
# 你的系统可能使用不同的方法来设置 Linux 内核使用的命令行。
sudo dnf install -y grubby && \
  sudo grubby \
  --update-kernel=ALL \
  --args="systemd.unified_cgroup_hierarchy=1"

如果更改内核的命令行,则必须重新启动节点才能使更改生效。

切换到 cgroup v2 时,用户体验不应有任何明显差异, 除非用户直接在节点上或在容器内访问 cgroup 文件系统。 为了使用它,CRI 运行时也必须支持 cgroup v2。

将 kubeadm 托管的集群迁移到 systemd 驱动

如果你希望将现有的由 kubeadm 管理的集群迁移到 systemd cgroup 驱动程序, 请按照配置 cgroup 驱动程序操作。

CRI 版本支持

你的容器运行时必须至少支持容器运行时接口的 v1alpha2。

Kubernetes 1.24 默认使用 v1 的 CRI API。如果容器运行时不支持 v1 API, 则 kubelet 会回退到使用(已弃用的)v1alpha2 API。

容器运行时

containerd

本节概述了使用 containerd 作为 CRI 运行时的必要步骤。

使用以下命令在系统上安装 Containerd:

按照开始使用 containerd 的说明进行操作。 创建有效的配置文件 config.toml 后返回此步骤。

你可以在路径 /etc/containerd/config.toml 下找到此文件。

你可以在路径 C:\Program Files\containerd\config.toml 下找到此文件。

在 Linux 上,containerd 的默认 CRI 套接字是 /run/containerd/containerd.sock。 在 Windows 上,默认 CRI 端点是 npipe://./pipe/containerd-containerd

配置 systemd cgroup 驱动程序

结合 runc 使用 systemd cgroup 驱动,在 /etc/containerd/config.toml 中设置

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  ...
  [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
    SystemdCgroup = true

如果你应用此更改,请确保重新启动 containerd:

sudo systemctl restart containerd

当使用 kubeadm 时,请手动配置 kubelet 的 cgroup 驱动

重载沙箱(pause)镜像

在你的 containerd 配置中, 你可以通过设置以下选项重载沙箱镜像:

[plugins."io.containerd.grpc.v1.cri"]
  sandbox_image = "k8s.gcr.io/pause:3.2"

一旦你更新了这个配置文件,可能就同样需要重启 containerdsystemctl restart containerd

CRI-O

本节包含安装 CRI-O 作为容器运行时的必要步骤。

要安装 CRI-O,请按照 CRI-O 安装说明执行操作。

cgroup 驱动程序

CRI-O 默认使用 systemd cgroup 驱动程序,这对你来说可能工作得很好。 要切换到 cgroupfs cgroup 驱动程序,请编辑 /etc/crio/crio.conf 或在 /etc/crio/crio.conf.d/02-cgroup-manager.conf 中放置一个插入式配置,例如:

[crio.runtime]
conmon_cgroup = "pod"
cgroup_manager = "cgroupfs"

你还应该注意到 conmon_cgroup 被更改,当使用 CRI-O 和 cgroupfs 时,必须将其设置为值 pod。 通常需要保持 kubelet 的 cgroup 驱动配置(通常通过 kubeadm 完成)和 CRI-O 同步。

对于 CRI-O,CRI 套接字默认为 /var/run/crio/crio.sock

重载沙箱(pause)镜像

在你的 CRI-O 配置中, 你可以设置以下配置值:

[crio.image]
pause_image="registry.k8s.io/pause:3.6"

这一设置选项支持动态配置重加载来应用所做变更:systemctl reload crio。 也可以通过向 crio 进程发送 SIGHUP 信号来实现。

Docker Engine

  1. 在你的每个节点上,遵循安装 Docker Engine 指南为你的 Linux 发行版安装 Docker。
  1. 按照源代码仓库中的说明安装 cri-dockerd

对于 cri-dockerd,默认情况下,CRI 套接字是 /run/cri-dockerd.sock

Mirantis 容器运行时

Mirantis Container Runtime (MCR) 是一种商用容器运行时,以前称为 Docker 企业版。 你可以使用 MCR 中包含的开源 cri-dockerd 组件将 Mirantis Container Runtime 与 Kubernetes 一起使用。

要了解有关如何安装 Mirantis Container Runtime 的更多信息, 请访问 MCR 部署指南

检查名为 cri-docker.socket 的 systemd 单元以找出 CRI 套接字的路径。

重载沙箱(pause)镜像

cri-dockerd 适配器能够接受一个命令行参数是,设置用哪个容器镜像作为 Pod 的基础设施容器(“pause 镜像”)。 要使用的命令行参数是 --pod-infra-container-image

接下来

除了容器运行时,你的集群还需要有效的网络插件

2.2.2 - Turnkey 云解决方案

本页列示 Kubernetes 认证解决方案供应商。 在每一个供应商分页,你可以学习如何安装和设置生产就绪的集群。

2.2.3 - 使用部署工具安装 Kubernetes

2.2.3.1 - 使用 kubeadm 引导集群

2.2.3.1.1 - 安装 kubeadm

本页面显示如何安装 kubeadm 工具箱。 有关在执行此安装过程后如何使用 kubeadm 创建集群的信息,请参见 使用 kubeadm 创建集群页面。

准备开始

  • 一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux 发行版以及一些不提供包管理器的发行版提供通用的指令
  • 每台机器 2 GB 或更多的 RAM(如果少于这个数字将会影响你应用的运行内存)
  • 2 CPU 核或更多
  • 集群中的所有机器的网络彼此均能相互连接(公网和内网都可以)
  • 节点之中不可以有重复的主机名、MAC 地址或 product_uuid。请参见这里了解更多详细信息。
  • 开启机器上的某些端口。请参见这里了解更多详细信息。
  • 禁用交换分区。为了保证 kubelet 正常工作,你 必须 禁用交换分区。

确保每个节点上 MAC 地址和 product_uuid 的唯一性

  • 你可以使用命令 ip linkifconfig -a 来获取网络接口的 MAC 地址
  • 可以使用 sudo cat /sys/class/dmi/id/product_uuid 命令对 product_uuid 校验

一般来讲,硬件设备会拥有唯一的地址,但是有些虚拟机的地址可能会重复。 Kubernetes 使用这些值来唯一确定集群中的节点。 如果这些值在每个节点上不唯一,可能会导致安装失败

检查网络适配器

如果你有一个以上的网络适配器,同时你的 Kubernetes 组件通过默认路由不可达,我们建议你预先添加 IP 路由规则, 这样 Kubernetes 集群就可以通过对应的适配器完成连接。

允许 iptables 检查桥接流量

确保 br_netfilter 模块被加载。这一操作可以通过运行 lsmod | grep br_netfilter 来完成。若要显式加载该模块,可执行 sudo modprobe br_netfilter

为了让你的 Linux 节点上的 iptables 能够正确地查看桥接流量,你需要确保在 sysctl 配置中将 net.bridge.bridge-nf-call-iptables 设置为 1。例如:

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

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

更多的相关细节可查看网络插件需求页面。

检查所需端口

启用这些必要的端口后才能使 Kubernetes 的各组件相互通信。 可以使用 netcat 之类的工具来检查端口是否启用,例如:

nc 127.0.0.1 6443

你使用的 Pod 网络插件 (详见后续章节) 也可能需要开启某些特定端口。 由于各个 Pod 网络插件的功能都有所不同,请参阅他们各自文档中对端口的要求。

安装容器运行时

为了在 Pod 中运行容器,Kubernetes 使用 容器运行时(Container Runtime)

默认情况下,Kubernetes 使用 容器运行时接口(Container Runtime Interface,CRI) 来与你所选择的容器运行时交互。

如果你不指定运行时,kubeadm 会自动尝试通过扫描已知的端点列表来检测已安装的容器运行时。

如果检测到有多个或者没有容器运行时,kubeadm 将抛出一个错误并要求你指定一个想要使用的运行时。

参阅容器运行时 以了解更多信息。

下面的表格包括被支持的操作系统的已知端点。

运行时Unix 域套接字
containerdunix:///var/run/containerd/containerd.sock
CRI-Ounix:///var/run/crio/crio.sock
Docker Engine (使用 cri-dockerd)unix:///var/run/cri-dockerd.sock

运行时Windows 命名管道路径
containerdnpipe:////./pipe/containerd-containerd
Docker Engine (使用 cri-dockerd)npipe:////./pipe/cri-dockerd

安装 kubeadm、kubelet 和 kubectl

你需要在每台机器上安装以下的软件包:

  • kubeadm:用来初始化集群的指令。

  • kubelet:在集群中的每个节点上用来启动 Pod 和容器等。

  • kubectl:用来与集群通信的命令行工具。

kubeadm 不能帮你安装或者管理 kubeletkubectl, 所以你需要确保它们与通过 kubeadm 安装的控制平面的版本相匹配。 如果不这样做,则存在发生版本偏差的风险,可能会导致一些预料之外的错误和问题。 然而,控制平面与 kubelet 之间可以存在一个次要版本的偏差,但 kubelet 的版本不可以超过 API 服务器的版本。 例如,1.7.0 版本的 kubelet 可以完全兼容 1.8.0 版本的 API 服务器,反之则不可以。

有关安装 kubectl 的信息,请参阅安装和设置 kubectl 文档。

关于版本偏差的更多信息,请参阅以下文档:

  1. 更新 apt 包索引并安装使用 Kubernetes apt 仓库所需要的包:

    sudo apt-get update
    sudo apt-get install -y apt-transport-https ca-certificates curl
    
  1. 下载 Google Cloud 公开签名秘钥:

    sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
    
  1. 添加 Kubernetes apt 仓库:

    echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
    
  1. 更新 apt 包索引,安装 kubelet、kubeadm 和 kubectl,并锁定其版本:

    sudo apt-get update
    sudo apt-get install -y kubelet kubeadm kubectl
    sudo apt-mark hold kubelet kubeadm kubectl
    

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF

# 将 SELinux 设置为 permissive 模式(相当于将其禁用)
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

sudo systemctl enable --now kubelet

请注意:

  • 通过运行命令 setenforce 0sed ... 将 SELinux 设置为 permissive 模式 可以有效地将其禁用。 这是允许容器访问主机文件系统所必需的,而这些操作是为了例如 Pod 网络工作正常。

    你必须这么做,直到 kubelet 做出对 SELinux 的支持进行升级为止。

  • 如果你知道如何配置 SELinux 则可以将其保持启用状态,但可能需要设定 kubeadm 不支持的部分配置

  • 如果由于该 Red Hat 的发行版无法解析 basearch 导致获取 baseurl 失败,请将 \$basearch 替换为你计算机的架构。 输入 uname -m 以查看该值。 例如,x86_64baseurl URL 可以是:https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64

安装 CNI 插件(大多数 Pod 网络都需要):

CNI_VERSION="v0.8.2"
ARCH="amd64"
sudo mkdir -p /opt/cni/bin
curl -L "https://github.com/containernetworking/plugins/releases/download/${CNI_VERSION}/cni-plugins-linux-${ARCH}-${CNI_VERSION}.tgz" | sudo tar -C /opt/cni/bin -xz

定义要下载命令文件的目录。

DOWNLOAD_DIR=/usr/local/bin
sudo mkdir -p $DOWNLOAD_DIR

安装 crictl(kubeadm/kubelet 容器运行时接口(CRI)所需)

CRICTL_VERSION="v1.22.0"
ARCH="amd64"
curl -L "https://github.com/kubernetes-sigs/cri-tools/releases/download/${CRICTL_VERSION}/crictl-${CRICTL_VERSION}-linux-${ARCH}.tar.gz" | sudo tar -C $DOWNLOAD_DIR -xz

安装 kubeadmkubeletkubectl 并添加 kubelet 系统服务:

RELEASE="$(curl -sSL https://dl.k8s.io/release/stable.txt)"
ARCH="amd64"
cd $DOWNLOAD_DIR
sudo curl -L --remote-name-all https://storage.googleapis.com/kubernetes-release/release/${RELEASE}/bin/linux/${ARCH}/{kubeadm,kubelet,kubectl}
sudo chmod +x {kubeadm,kubelet,kubectl}

RELEASE_VERSION="v0.4.0"
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/${RELEASE_VERSION}/cmd/kubepkg/templates/latest/deb/kubelet/lib/systemd/system/kubelet.service" | sed "s:/usr/bin:${DOWNLOAD_DIR}:g" | sudo tee /etc/systemd/system/kubelet.service
sudo mkdir -p /etc/systemd/system/kubelet.service.d
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/${RELEASE_VERSION}/cmd/kubepkg/templates/latest/deb/kubeadm/10-kubeadm.conf" | sed "s:/usr/bin:${DOWNLOAD_DIR}:g" | sudo tee /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

激活并启动 kubelet

systemctl enable --now kubelet

kubelet 现在每隔几秒就会重启,因为它陷入了一个等待 kubeadm 指令的死循环。

配置 cgroup 驱动程序

容器运行时和 kubelet 都具有名字为 "cgroup driver" 的属性,该属性对于在 Linux 机器上管理 CGroups 而言非常重要。

故障排查

如果你在使用 kubeadm 时遇到困难,请参阅我们的 故障排查文档

接下来

2.2.3.1.2 - 对 kubeadm 进行故障排查

与任何程序一样,你可能会在安装或者运行 kubeadm 时遇到错误。 本文列举了一些常见的故障场景,并提供可帮助你理解和解决这些问题的步骤。

如果你的问题未在下面列出,请执行以下步骤:

  • 如果你认为问题是 kubeadm 的错误:

  • 如果你对 kubeadm 的工作方式有疑问,可以在 Slack 上的 #kubeadm 频道提问, 或者在 StackOverflow 上提问。 请加入相关标签,例如 #kubernetes#kubeadm,这样其他人可以帮助你。

由于缺少 RBAC,无法将 v1.18 Node 加入 v1.17 集群

自从 v1.18 后,如果集群中已存在同名 Node,kubeadm 将禁止 Node 加入集群。 这需要为 bootstrap-token 用户添加 RBAC 才能 GET Node 对象。

但这会导致一个问题,v1.18 的 kubeadm join 无法加入由 kubeadm v1.17 创建的集群。

要解决此问题,你有两种选择:

使用 kubeadm v1.18 在控制平面节点上执行 kubeadm init phase bootstrap-token。 请注意,这也会启用 bootstrap-token 的其余权限。

或者,也可以使用 kubectl apply -f ... 手动应用以下 RBAC:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kubeadm:get-nodes
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubeadm:get-nodes
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: kubeadm:get-nodes
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: Group
  name: system:bootstrappers:kubeadm:default-node-token

在安装过程中没有找到 ebtables 或者其他类似的可执行文件

如果在运行 kubeadm init 命令时,遇到以下的警告

[preflight] WARNING: ebtables not found in system path
[preflight] WARNING: ethtool not found in system path

那么或许在你的节点上缺失 ebtablesethtool 或者类似的可执行文件。 你可以使用以下命令安装它们:

  • 对于 Ubuntu/Debian 用户,运行 apt install ebtables ethtool 命令。
  • 对于 CentOS/Fedora 用户,运行 yum install ebtables ethtool 命令。

在安装过程中,kubeadm 一直等待控制平面就绪

如果你注意到 kubeadm init 在打印以下行后挂起:

[apiclient] Created API client, waiting for the control plane to become ready

这可能是由许多问题引起的。最常见的是:

  • 网络连接问题。在继续之前,请检查你的计算机是否具有全部联通的网络连接。
  • 容器运行时的 cgroup 驱动不同于 kubelet 使用的 cgroup 驱动。要了解如何正确配置 cgroup 驱动, 请参阅配置 cgroup 驱动
  • 控制平面上的 Docker 容器持续进入崩溃状态或(因其他原因)挂起。你可以运行 docker ps 命令来检查以及 docker logs 命令来检视每个容器的运行日志。 对于其他容器运行时,请参阅使用 crictl 对 Kubernetes 节点进行调试

当删除托管容器时 kubeadm 阻塞

如果容器运行时停止并且未删除 Kubernetes 所管理的容器,可能发生以下情况:

sudo kubeadm reset
[preflight] Running pre-flight checks
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Removing kubernetes-managed containers
(block)

一个可行的解决方案是重新启动 Docker 服务,然后重新运行 kubeadm reset: 你也可以使用 crictl 来调试容器运行时的状态。 参见使用 CRICTL 调试 Kubernetes 节点

Pod 处于 RunContainerErrorCrashLoopBackOff 或者 Error 状态

kubeadm init 命令运行后,系统中不应该有 Pod 处于这类状态。

  • kubeadm init 命令执行完后,如果有 Pod 处于这些状态之一,请在 kubeadm 仓库提起一个 issue。coredns (或者 kube-dns) 应该处于 Pending 状态, 直到你部署了网络插件为止。

  • 如果在部署完网络插件之后,有 Pod 处于 RunContainerErrorCrashLoopBackOffError 状态之一,并且 coredns (或者 kube-dns)仍处于 Pending 状态, 那很可能是你安装的网络插件由于某种原因无法工作。你或许需要授予它更多的 RBAC 特权或使用较新的版本。请在 Pod Network 提供商的问题跟踪器中提交问题, 然后在此处分类问题。

  • 如果你安装的 Docker 版本早于 1.12.1,请在使用 systemd 来启动 dockerd 和重启 docker 时, 删除 MountFlags=slave 选项。 你可以在 /usr/lib/systemd/system/docker.service 中看到 MountFlags。 MountFlags 可能会干扰 Kubernetes 挂载的卷,并使 Pod 处于 CrashLoopBackOff 状态。 当 Kubernetes 不能找到 var/run/secrets/kubernetes.io/serviceaccount 文件时会发生错误。

coredns 停滞在 Pending 状态

这一行为是 预期之中 的,因为系统就是这么设计的。 kubeadm 的网络供应商是中立的,因此管理员应该选择 安装 Pod 的网络插件。 你必须完成 Pod 的网络配置,然后才能完全部署 CoreDNS。 在网络被配置好之前,DNS 组件会一直处于 Pending 状态。

HostPort 服务无法工作

HostPortHostIP 功能是否可用取决于你的 Pod 网络配置。请联系 Pod 网络插件的作者, 以确认 HostPortHostIP 功能是否可用。

已验证 Calico、Canal 和 Flannel CNI 驱动程序支持 HostPort。

有关更多信息,请参考 CNI portmap 文档.

如果你的网络提供商不支持 portmap CNI 插件,你或许需要使用 NodePort 服务的功能 或者使用 HostNetwork=true

无法通过其服务 IP 访问 Pod

  • 许多网络附加组件尚未启用 hairpin 模式 该模式允许 Pod 通过其服务 IP 进行访问。这是与 CNI 有关的问题。 请与网络附加组件提供商联系,以获取他们所提供的 hairpin 模式的最新状态。

  • 如果你正在使用 VirtualBox (直接使用或者通过 Vagrant 使用),你需要 确保 hostname -i 返回一个可路由的 IP 地址。默认情况下,第一个接口连接不能路由的仅主机网络。 解决方法是修改 /etc/hosts,请参考示例 Vagrantfile

TLS 证书错误

以下错误说明证书可能不匹配。

# kubectl get pods
Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")
  • 验证 $HOME/.kube/config 文件是否包含有效证书, 并在必要时重新生成证书。在 kubeconfig 文件中的证书是 base64 编码的。 该 base64 --decode 命令可以用来解码证书,openssl x509 -text -noout 命令可以用于查看证书信息。

  • 使用如下方法取消设置 KUBECONFIG 环境变量的值:

    unset KUBECONFIG
    

    或者将其设置为默认的 KUBECONFIG 位置:

    export KUBECONFIG=/etc/kubernetes/admin.conf
    
  • 另一个方法是覆盖 kubeconfig 的现有用户 "管理员":

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

Kubelet 客户端证书轮换失败

默认情况下,kubeadm 使用 /etc/kubernetes/kubelet.conf 中指定的 /var/lib/kubelet/pki/kubelet-client-current.pem 符号链接 来配置 kubelet 自动轮换客户端证书。如果此轮换过程失败,你可能会在 kube-apiserver 日志中看到诸如 x509: certificate has expired or is not yet valid 之类的错误。要解决此问题,你必须执行以下步骤:

  1. 从故障节点备份和删除 /etc/kubernetes/kubelet.conf/var/lib/kubelet/pki/kubelet-client*
  2. 在集群中具有 /etc/kubernetes/pki/ca.key 的、正常工作的控制平面节点上 执行 kubeadm kubeconfig user --org system:nodes --client-name system:node:$NODE > kubelet.conf$NODE 必须设置为集群中现有故障节点的名称。 手动修改生成的 kubelet.conf 以调整集群名称和服务器端点, 或传递 kubeconfig user --config(此命令接受 InitConfiguration)。 如果你的集群没有 ca.key,你必须在外部对 kubelet.conf 中的嵌入式证书进行签名。
  1. 将得到的 kubelet.conf 文件复制到故障节点上,作为 /etc/kubernetes/kubelet.conf
  2. 在故障节点上重启 kubelet(systemctl restart kubelet),等待 /var/lib/kubelet/pki/kubelet-client-current.pem 重新创建。
  1. 手动编辑 kubelet.conf 指向轮换的 kubelet 客户端证书,方法是将 client-certificate-dataclient-key-data 替换为:

    client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
    client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
    
  1. 重新启动 kubelet。
  2. 确保节点状况变为 Ready

在 Vagrant 中使用 flannel 作为 Pod 网络时的默认 NIC

以下错误可能表明 Pod 网络中出现问题:

Error from server (NotFound): the server could not find the requested resource
  • 如果你正在 Vagrant 中使用 flannel 作为 Pod 网络,则必须指定 flannel 的默认接口名称。

    Vagrant 通常为所有 VM 分配两个接口。第一个为所有主机分配了 IP 地址 10.0.2.15,用于获得 NATed 的外部流量。

    这可能会导致 flannel 出现问题,它默认为主机上的第一个接口。这导致所有主机认为它们具有 相同的公共 IP 地址。为防止这种情况,传递 --iface eth1 标志给 flannel 以便选择第二个接口。

容器使用的非公共 IP

在某些情况下 kubectl logskubectl run 命令或许会返回以下错误,即便除此之外集群一切功能正常:

Error from server: Get https://10.19.0.41:10250/containerLogs/default/mysql-ddc65b868-glc5m/mysql: dial tcp 10.19.0.41:10250: getsockopt: no route to host
  • 这或许是由于 Kubernetes 使用的 IP 无法与看似相同的子网上的其他 IP 进行通信的缘故, 可能是由机器提供商的政策所导致的。

  • DigitalOcean 既分配一个共有 IP 给 eth0,也分配一个私有 IP 在内部用作其浮动 IP 功能的锚点, 然而 kubelet 将选择后者作为节点的 InternalIP 而不是公共 IP。

    使用 ip addr show 命令代替 ifconfig 命令去检查这种情况,因为 ifconfig 命令 不会显示有问题的别名 IP 地址。或者指定的 DigitalOcean 的 API 端口允许从 droplet 中 查询 anchor IP:

    curl http://169.254.169.254/metadata/v1/interfaces/public/0/anchor_ipv4/address
    

    解决方法是通知 kubelet 使用哪个 --node-ip。当使用 DigitalOcean 时,可以是公网IP(分配给 eth0 的), 或者是私网IP(分配给 eth1 的)。私网 IP 是可选的。 kubadm NodeRegistrationOptions 结构KubeletExtraArgs 部分被用来处理这种情况。

    然后重启 kubelet

    systemctl daemon-reload
    systemctl restart kubelet
    

coredns Pod 有 CrashLoopBackOff 或者 Error 状态

如果有些节点运行的是旧版本的 Docker,同时启用了 SELinux,你或许会遇到 coredns Pod 无法启动的情况。 要解决此问题,你可以尝试以下选项之一:

kubectl -n kube-system get deployment coredns -o yaml | \
  sed 's/allowPrivilegeEscalation: false/allowPrivilegeEscalation: true/g' | \
  kubectl apply -f -

CoreDNS 处于 CrashLoopBackOff 时的另一个原因是当 Kubernetes 中部署的 CoreDNS Pod 检测 到环路时。有许多解决方法 可以避免在每次 CoreDNS 监测到循环并退出时,Kubernetes 尝试重启 CoreDNS Pod 的情况。

etcd Pod 持续重启

如果你遇到以下错误:

rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:247: starting container process caused "process_linux.go:110: decoding init error from pipe caused \"read parent: connection reset by peer\""

如果你使用 Docker 1.13.1.84 运行 CentOS 7 就会出现这种问题。 此版本的 Docker 会阻止 kubelet 在 etcd 容器中执行。

为解决此问题,请选择以下选项之一:

  • 回滚到早期版本的 Docker,例如 1.13.1-75

    yum downgrade docker-1.13.1-75.git8633870.el7.centos.x86_64 docker-client-1.13.1-75.git8633870.el7.centos.x86_64 docker-common-1.13.1-75.git8633870.el7.centos.x86_64
    
  • 安装较新的推荐版本之一,例如 18.06:

    sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    yum install docker-ce-18.06.1.ce-3.el7.x86_64
    

无法将以逗号分隔的值列表传递给 --component-extra-args 标志内的参数

kubeadm init 标志例如 --component-extra-args 允许你将自定义参数传递给像 kube-apiserver 这样的控制平面组件。然而,由于解析 (mapStringString) 的基础类型值,此机制将受到限制。

如果你决定传递一个支持多个逗号分隔值(例如 --apiserver-extra-args "enable-admission-plugins=LimitRanger,NamespaceExists")参数, 将出现 flag: malformed pair, expect string=string 错误。 发生这种问题是因为参数列表 --apiserver-extra-args 预期的是 key=value 形式, 而这里的 NamespacesExists 被误认为是缺少取值的键名。

一种解决方法是尝试分离 key=value 对,像这样: --apiserver-extra-args "enable-admission-plugins=LimitRanger,enable-admission-plugins=NamespaceExists" 但这将导致键 enable-admission-plugins 仅有值 NamespaceExists

已知的解决方法是使用 kubeadm 配置文件

在节点被云控制管理器初始化之前,kube-proxy 就被调度了

在云环境场景中,可能出现在云控制管理器完成节点地址初始化之前,kube-proxy 就被调度到新节点了。 这会导致 kube-proxy 无法正确获取节点的 IP 地址,并对管理负载平衡器的代理功能产生连锁反应。

在 kube-proxy Pod 中可以看到以下错误:

server.go:610] Failed to retrieve node IP: host IP unknown; known addresses: []
proxier.go:340] invalid nodeIP, initializing kube-proxy with 127.0.0.1 as nodeIP

一种已知的解决方案是修补 kube-proxy DaemonSet,以允许在控制平面节点上调度它, 而不管它们的条件如何,将其与其他节点保持隔离,直到它们的初始保护条件消除:

kubectl -n kube-system patch ds kube-proxy -p='{ "spec": { "template": { "spec": { "tolerations": [ { "key": "CriticalAddonsOnly", "operator": "Exists" }, { "effect": "NoSchedule", "key": "node-role.kubernetes.io/master" }, { "effect": "NoSchedule", "key": "node-role.kubernetes.io/control-plane" } ] } } } }'

此问题的跟踪在这里

节点上的 /usr 被以只读方式挂载

在类似 Fedora CoreOS 或者 Flatcar Container Linux 这类 Linux 发行版本中, 目录 /usr 是以只读文件系统的形式挂载的。 在支持 FlexVolume时, 类似 kubelet 和 kube-controller-manager 这类 Kubernetes 组件使用默认路径 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/, 而 FlexVolume 的目录 必须是可写入的,该功能特性才能正常工作。 (注意:FlexVolume 在 Kubernetes v1.23 版本中已被弃用)

为了解决这个问题,你可以使用 kubeadm 的配置文件 来配置 FlexVolume 的目录。

在(使用 kubeadm init 创建的)主控制节点上,使用 --config 参数传入如下文件:

apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
  kubeletExtraArgs:
    volume-plugin-dir: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
controllerManager:
  extraArgs:
    flex-volume-plugin-dir: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"

在加入到集群中的节点上,使用下面的文件:

apiVersion: kubeadm.k8s.io/v1beta3
kind: JoinConfiguration
nodeRegistration:
  kubeletExtraArgs:
    volume-plugin-dir: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"

或者,你要可以更改 /etc/fstab 使得 /usr 目录能够以可写入的方式挂载, 不过请注意这样做本质上是在更改 Linux 发行版的某种设计原则。

kubeadm upgrade plan 输出错误信息 context deadline exceeded

在使用 kubeadm 来升级某运行外部 etcd 的 Kubernetes 集群时可能显示这一错误信息。 这并不是一个非常严重的一个缺陷,之所以出现此错误信息,原因是老的 kubeadm 版本会对外部 etcd 集群执行版本检查。你可以继续执行 kubeadm upgrade apply ...

这一问题已经在 1.19 版本中得到修复。

kubeadm reset 会卸载 /var/lib/kubelet

如果已经挂载了 /var/lib/kubelet 目录,执行 kubeadm reset 操作的时候会将其卸载。

要解决这一问题,可以在执行了 kubeadm reset 操作之后重新挂载 /var/lib/kubelet 目录。

这是一个在 1.15 中引入的故障,已经在 1.20 版本中修复。

无法在 kubeadm 集群中安全地使用 metrics-server

在 kubeadm 集群中可以通过为 metrics-server 设置 --kubelet-insecure-tls 来以不安全的形式使用该服务。 建议不要在生产环境集群中这样使用。

如果你需要在 metrics-server 和 kubelet 之间使用 TLS,会有一个问题, kubeadm 为 kubelet 部署的是自签名的服务证书。这可能会导致 metrics-server 端报告下面的错误信息:

x509: certificate signed by unknown authority
x509: certificate is valid for IP-foo not IP-bar

参见为 kubelet 启用签名的服务证书 以进一步了解如何在 kubeadm 集群中配置 kubelet 使用正确签名了的服务证书。

另请参阅 How to run the metrics-server securely

2.2.3.1.3 - 使用 kubeadm 创建集群

使用 kubeadm,你能创建一个符合最佳实践的最小化 Kubernetes 集群。 事实上,你可以使用 kubeadm 配置一个通过 Kubernetes 一致性测试的集群。 kubeadm 还支持其他集群生命周期功能, 例如启动引导令牌和集群升级。

kubeadm 工具很棒,如果你需要:

  • 一个尝试 Kubernetes 的简单方法。
  • 一个现有用户可以自动设置集群并测试其应用程序的途径。
  • 其他具有更大范围的生态系统和/或安装工具中的构建模块。

你可以在各种机器上安装和使用 kubeadm:笔记本电脑, 一组云服务器,Raspberry Pi 等。无论是部署到云还是本地, 你都可以将 kubeadm 集成到预配置系统中,例如 Ansible 或 Terraform。

准备开始

要遵循本指南,你需要:

  • 一台或多台运行兼容 deb/rpm 的 Linux 操作系统的计算机;例如:Ubuntu 或 CentOS。
  • 每台机器 2 GB 以上的内存,内存不足时应用会受限制。
  • 用作控制平面节点的计算机上至少有 2 个 CPU。
  • 集群中所有计算机之间具有完全的网络连接。你可以使用公共网络或专用网络。

你还需要使用可以在新集群中部署特定 Kubernetes 版本对应的 kubeadm

Kubernetes 版本及版本偏差策略适用于 kubeadm 以及整个 Kubernetes。 查阅该策略以了解支持哪些版本的 Kubernetes 和 kubeadm。 该页面是为 Kubernetes v1.24 编写的。

kubeadm 工具的整体功能状态为一般可用性(GA)。一些子功能仍在积极开发中。 随着工具的发展,创建集群的实现可能会略有变化,但总体实现应相当稳定。

目标

  • 安装单个控制平面的 Kubernetes 集群
  • 在集群上安装 Pod 网络,以便你的 Pod 可以相互连通

操作指南

主机准备

在所有主机上安装 容器运行时 和 kubeadm。 详细说明和其他前提条件,请参见安装 kubeadm

准备所需的容器镜像

这个步骤是可选的,只适用于你希望 kubeadm initkubeadm join 不去下载存放在 k8s.gcr.io 上的默认的容器镜像的情况。

当你在离线的节点上创建一个集群的时候,Kubeadm 有一些命令可以帮助你预拉取所需的镜像。 阅读离线运行 kubeadm 获取更多的详情。

Kubeadm 允许你给所需要的镜像指定一个自定义的镜像仓库。 阅读使用自定义镜像 获取更多的详情。

初始化控制平面节点

控制平面节点是运行控制平面组件的机器, 包括 etcd (集群数据库) 和 API Server (命令行工具 kubectl 与之通信)。

  1. (推荐)如果计划将单个控制平面 kubeadm 集群升级成高可用, 你应该指定 --control-plane-endpoint 为所有控制平面节点设置共享端点。 端点可以是负载均衡器的 DNS 名称或 IP 地址。
  2. 选择一个 Pod 网络插件,并验证是否需要为 kubeadm init 传递参数。 根据你选择的第三方网络插件,你可能需要设置 --pod-network-cidr 的值。 请参阅安装 Pod 网络附加组件
  1. (可选)kubeadm 试图通过使用已知的端点列表来检测容器运行时。 使用不同的容器运行时或在预配置的节点上安装了多个容器运行时,请为 kubeadm init 指定 --cri-socket 参数。 请参阅安装运行时
  2. (可选)除非另有说明,否则 kubeadm 使用与默认网关关联的网络接口来设置此控制平面节点 API server 的广播地址。 要使用其他网络接口,请为 kubeadm init 设置 --apiserver-advertise-address=<ip-address> 参数。 要部署使用 IPv6 地址的 Kubernetes 集群, 必须指定一个 IPv6 地址,例如 --apiserver-advertise-address=fd00::101

要初始化控制平面节点,请运行:

kubeadm init <args>

关于 apiserver-advertise-address 和 ControlPlaneEndpoint 的注意事项

--apiserver-advertise-address 可用于为控制平面节点的 API server 设置广播地址, --control-plane-endpoint 可用于为所有控制平面节点设置共享端点。

--control-plane-endpoint 允许 IP 地址和可以映射到 IP 地址的 DNS 名称。 请与你的网络管理员联系,以评估有关此类映射的可能解决方案。

这是一个示例映射:

192.168.0.102 cluster-endpoint

其中 192.168.0.102 是此节点的 IP 地址,cluster-endpoint 是映射到该 IP 的自定义 DNS 名称。 这将允许你将 --control-plane-endpoint=cluster-endpoint 传递给 kubeadm init,并将相同的 DNS 名称传递给 kubeadm join。 稍后你可以修改 cluster-endpoint 以指向高可用性方案中的负载均衡器的地址。

kubeadm 不支持将没有 --control-plane-endpoint 参数的单个控制平面集群转换为高可用性集群。

更多信息

有关 kubeadm init 参数的更多信息,请参见 kubeadm 参考指南

要使用配置文件配置 kubeadm init 命令, 请参见带配置文件使用 kubeadm init

要自定义控制平面组件,包括可选的对控制平面组件和 etcd 服务器的活动探针提供 IPv6 支持, 请参阅自定义参数

要重新配置一个已经创建的集群,请参见 重新配置一个 kubeadm 集群

要再次运行 kubeadm init,你必须首先卸载集群

如果将具有不同架构的节点加入集群, 请确保已部署的 DaemonSet 对这种体系结构具有容器镜像支持。

kubeadm init 首先运行一系列预检查以确保机器 准备运行 Kubernetes。这些预检查会显示警告并在错误时退出。然后 kubeadm init 下载并安装集群控制平面组件。这可能会需要几分钟。 完成之后你应该看到:

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

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

You can now join any number of machines by running the following on each node
as root:

  kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>

要使非 root 用户可以运行 kubectl,请运行以下命令, 它们也是 kubeadm init 输出的一部分:

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

或者,如果你是 root 用户,则可以运行:

export KUBECONFIG=/etc/kubernetes/admin.conf

记录 kubeadm init 输出的 kubeadm join 命令。 你需要此命令将节点加入集群

令牌用于控制平面节点和加入节点之间的相互身份验证。 这里包含的令牌是密钥。确保它的安全, 因为拥有此令牌的任何人都可以将经过身份验证的节点添加到你的集群中。 可以使用 kubeadm token 命令列出,创建和删除这些令牌。 请参阅 kubeadm 参考指南

安装 Pod 网络附加组件

一些外部项目为 Kubernetes 提供使用 CNI 的 Pod 网络,其中一些还支持网络策略

请参阅实现 Kubernetes 网络模型的附加组件列表。

你可以使用以下命令在控制平面节点或具有 kubeconfig 凭据的节点上安装 Pod 网络附加组件:

kubectl apply -f <add-on.yaml>

每个集群只能安装一个 Pod 网络。

安装 Pod 网络后,你可以通过在 kubectl get pods --all-namespaces 输出中检查 CoreDNS Pod 是否 Running 来确认其是否正常运行。 一旦 CoreDNS Pod 启用并运行,你就可以继续加入节点。

如果你的网络无法正常工作或 CoreDNS 不在“运行中”状态,请查看 kubeadm故障排除指南

托管节点标签

默认情况下,kubeadm 启用 NodeRestriction 准入控制器来限制 kubelets 在节点注册时可以应用哪些标签。准入控制器文档描述 kubelet --node-labels 选项允许使用哪些标签。 其中 node-role.kubernetes.io/control-plane 标签就是这样一个受限制的标签, kubeadm 在节点创建后使用特权客户端手动应用此标签。 你可以使用一个有特权的 kubeconfig,比如由 kubeadm 管理的 /etc/kubernetes/admin.conf, 通过执行 kubectl label 来手动完成操作。

控制平面节点隔离

默认情况下,出于安全原因,你的集群不会在控制平面节点上调度 Pod。 如果你希望能够在控制平面节点上调度 Pod,例如单机 Kubernetes 集群,请运行:

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

输出看起来像:

node "test-01" untainted

这将从任何拥有 node-role.kubernetes.io/control-planenode-role.kubernetes.io/master 污点的节点上移除该污点。

包括控制平面节点,这意味着调度程序将能够在任何地方调度 Pods。

加入节点

节点是你的工作负载(容器和 Pod 等)运行的地方。要将新节点添加到集群,请对每台计算机执行以下操作:

  • SSH 到机器

  • 成为 root (例如 sudo su -

  • 必要时安装一个运行时

  • 运行 kubeadm init 输出的命令,例如:

    kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>
    

如果没有令牌,可以通过在控制平面节点上运行以下命令来获取令牌:

kubeadm token list

输出类似于以下内容:

TOKEN                    TTL  EXPIRES              USAGES           DESCRIPTION            EXTRA GROUPS
8ewj1p.9r9hcjoqgajrj4gi  23h  2018-06-12T02:51:28Z authentication,  The default bootstrap  system:
                                                   signing          token generated by     bootstrappers:
                                                                    'kubeadm init'.        kubeadm:
                                                                                           default-node-token

默认情况下,令牌会在 24 小时后过期。如果要在当前令牌过期后将节点加入集群, 则可以通过在控制平面节点上运行以下命令来创建新令牌:

kubeadm token create

输出类似于以下内容:

5didvk.d09sbcov8ph2amjw

如果你没有 --discovery-token-ca-cert-hash 的值,则可以通过在控制平面节点上执行以下命令链来获取它:

openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
   openssl dgst -sha256 -hex | sed 's/^.* //'

输出类似于以下内容:

8cb2de97839780a412b93877f8507ad6c94f73add17d5d7058e91741c9d5ec78

输出应类似于:

[preflight] Running pre-flight checks

... (log output of join workflow) ...

Node join complete:
* Certificate signing request sent to control-plane and response
  received.
* Kubelet informed of new secure connection details.

Run 'kubectl get nodes' on control-plane to see this machine join.

几秒钟后,当你在控制平面节点上执行 kubectl get nodes,你会注意到该节点出现在输出中。

(可选)从控制平面节点以外的计算机控制集群

为了使 kubectl 在其他计算机(例如笔记本电脑)上与你的集群通信, 你需要将管理员 kubeconfig 文件从控制平面节点复制到工作站,如下所示:

scp root@<control-plane-host>:/etc/kubernetes/admin.conf .
kubectl --kubeconfig ./admin.conf get nodes

(可选)将 API 服务器代理到本地主机

如果要从集群外部连接到 API 服务器,则可以使用 kubectl proxy

scp root@<control-plane-host>:/etc/kubernetes/admin.conf .
kubectl --kubeconfig ./admin.conf proxy

你现在可以在本地访问 API 服务器 http://localhost:8001/api/v1

清理

如果你在集群中使用了一次性服务器进行测试,则可以关闭这些服务器,而无需进一步清理。你可以使用 kubectl config delete-cluster 删除对集群的本地引用。

但是,如果要更干净地取消配置集群, 则应首先清空节点并确保该节点为空, 然后取消配置该节点。

删除节点

使用适当的凭证与控制平面节点通信,运行:

kubectl drain <node name> --delete-emptydir-data --force --ignore-daemonsets

在删除节点之前,请重置 kubeadm 安装的状态:

kubeadm reset

重置过程不会重置或清除 iptables 规则或 IPVS 表。如果你希望重置 iptables,则必须手动进行:

iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X

如果要重置 IPVS 表,则必须运行以下命令:

ipvsadm -C

现在删除节点:

kubectl delete node <node name>

如果你想重新开始,只需运行 kubeadm initkubeadm join 并加上适当的参数。

清理控制平面

你可以在控制平面主机上使用 kubeadm reset 来触发尽力而为的清理。

有关此子命令及其选项的更多信息,请参见 kubeadm reset 参考文档。

下一步

反馈

版本偏差策略

虽然 kubeadm 允许所管理的组件有一定程度的版本偏差, 但是建议你将 kubeadm 的版本与控制平面组件、kube-proxy 和 kubelet 的版本相匹配。

kubeadm 中的 Kubernetes 版本偏差

kubeadm 可以与 Kubernetes 组件一起使用,这些组件的版本与 kubeadm 相同,或者比它大一个版本。 Kubernetes 版本可以通过使用 --kubeadm init--kubernetes-version 标志或使用 --config 时的 ClusterConfiguration.kubernetesVersion 字段指定给 kubeadm。 这个选项将控制 kube-apiserver、kube-controller-manager、kube-scheduler 和 kube-proxy 的版本。

例子:

  • kubeadm 的版本为 1.24。
  • kubernetesVersion 必须为 1.24 或者 1.23。

kubeadm 中 kubelet 的版本偏差

与 Kubernetes 版本类似,kubeadm 可以使用与 kubeadm 相同版本的 kubelet, 或者比 kubeadm 老一个版本的 kubelet。

例子:

  • kubeadm 的版本为 1.24。
  • 主机上的 kubelet 必须为 1.24 或者 1.23。

kubeadm 支持的 kubeadm 的版本偏差

kubeadm 命令在现有节点或由 kubeadm 管理的整个集群上的操作有一定限制。

如果新的节点加入到集群中,用于 kubeadm join 的 kubeadm 二进制文件必须与用 kubeadm init 创建集群或用 kubeadm upgrade 升级同一节点时所用的 kubeadm 版本一致。 类似的规则适用于除了 kubeadm upgrade 以外的其他 kubeadm 命令。

kubeadm join 的例子:

  • 使用 kubeadm init 创建集群时使用版本为 1.24 的 kubeadm。
  • 添加节点所用的 kubeadm 可执行文件为版本 。

对于正在升级的节点,所使用的的 kubeadm 必须与管理该节点的 kubeadm 具有相同的 MINOR 版本或比后者新一个 MINOR 版本。

kubeadm upgrade 的例子:

  • 用于创建或升级节点的 kubeadm 版本为 1.23。
  • 用于升级节点的 kubeadm 版本必须为 1.23 或 1.24。

要了解更多关于不同 Kubernetes 组件之间的版本偏差,请参见 版本偏差策略

局限性

集群弹性

此处创建的集群具有单个控制平面节点,运行单个 etcd 数据库。 这意味着如果控制平面节点发生故障,你的集群可能会丢失数据并且可能需要从头开始重新创建。

解决方法:

  • 定期备份 etcd。 kubeadm 配置的 etcd 数据目录位于控制平面节点上的 /var/lib/etcd 中。

平台兼容性

kubeadm deb/rpm 软件包和二进制文件是为 amd64、arm (32-bit)、arm64、ppc64le 和 s390x 构建的遵循多平台提案

从 v1.12 开始还支持用于控制平面和附加组件的多平台容器镜像。

只有一些网络提供商为所有平台提供解决方案。 请查阅上方的网络提供商清单或每个提供商的文档以确定提供商是否支持你选择的平台。

故障排除

如果你在使用 kubeadm 时遇到困难, 请查阅我们的故障排除文档

2.2.3.1.4 - 使用 kubeadm API 定制组件

本页面介绍了如何自定义 kubeadm 部署的组件。 你可以使用 ClusterConfiguration 结构中定义的参数,或者在每个节点上应用补丁来定制控制平面组件。 你可以使用 KubeletConfigurationKubeProxyConfiguration 结构分别定制 kubelet 和 kube-proxy 组件。

所有这些选项都可以通过 kubeadm 配置 API 实现。 有关配置中的每个字段的详细信息,你可以导航到我们的 API 参考页面

使用 ClusterConfiguration 中的标志自定义控制平面

kubeadm ClusterConfiguration 对象为用户提供了一种方法, 用以覆盖传递给控制平面组件(如 APIServer、ControllerManager、Scheduler 和 Etcd)的默认参数。 各组件配置使用如下字段定义:

  • apiServer
  • controllerManager
  • scheduler
  • etcd

这些结构包含一个通用的 extraArgs 字段,该字段由 key: value 组成。 要覆盖控制平面组件的参数:

  1. 将适当的字段 extraArgs 添加到配置中。
  2. 向字段 extraArgs 添加要覆盖的参数值。
  3. --config <YOUR CONFIG YAML> 运行 kubeadm init

APIServer 参数

有关详细信息,请参阅 kube-apiserver 参考文档

使用示例:

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.16.0
apiServer:
  extraArgs:
    anonymous-auth: "false"
    enable-admission-plugins: AlwaysPullImages,DefaultStorageClass
    audit-log-path: /home/johndoe/audit.log

ControllerManager 参数

有关详细信息,请参阅 kube-controller-manager 参考文档

使用示例:

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.16.0
controllerManager:
  extraArgs:
    cluster-signing-key-file: /home/johndoe/keys/ca.key
    deployment-controller-sync-period: "50"

Scheduler 参数

有关详细信息,请参阅 kube-scheduler 参考文档

使用示例:

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.16.0
scheduler:
  extraArgs:
    config: /etc/kubernetes/scheduler-config.yaml
  extraVolumes:
    - name: schedulerconfig
      hostPath: /home/johndoe/schedconfig.yaml
      mountPath: /etc/kubernetes/scheduler-config.yaml
      readOnly: true
      pathType: "File"

Etcd 参数

有关详细信息,请参阅 etcd 服务文档.

使用示例:

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
etcd:
  local:
    extraArgs:
      election-timeout: 1000

使用补丁定制控制平面

特性状态: Kubernetes v1.22 [beta]

Kubeadm 允许将包含补丁文件的目录传递给各个节点上的 InitConfigurationJoinConfiguration。 这些补丁可被用作控制平面组件清单写入磁盘之前的最后一个自定义步骤。

可以使用 --config <你的 YAML 格式控制文件> 将配置文件传递给 kubeadm init

apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
patches:
  directory: /home/user/somedir

你可以使用 --config <你的 YAML 格式配置文件> 将配置文件传递给 kubeadm join

apiVersion: kubeadm.k8s.io/v1beta3
kind: JoinConfiguration
patches:
  directory: /home/user/somedir

补丁目录必须包含名为 target[suffix][+patchtype].extension 的文件。 例如,kube-apiserver0+merge.yaml 或只是 etcd.json

  • target 可以是 kube-apiserverkube-controller-managerkube-scheduleretcd 之一。
  • patchtype 可以是 strategymergejson 之一,并且这些必须匹配 kubectl 支持 的补丁格式。 默认补丁类型是 strategic 的。
  • extension 必须是 jsonyaml
  • suffix 是一个可选字符串,可用于确定首先按字母数字应用哪些补丁。

自定义 kubelet

要自定义 kubelet,你可以在同一配置文件中的 ClusterConfigurationInitConfiguration 之外添加一个 KubeletConfiguration,用 --- 分隔。 然后可以将此文件传递给 kubeadm init

更多详情,请参阅使用 kubeadm 配置集群中的每个 kubelet

自定义 kube-proxy

要自定义 kube-proxy,你可以在 ClusterConfigurationInitConfiguration 之外添加一个由 --- 分隔的 KubeProxyConfiguration, 传递给 kubeadm init

可以导航到 API 参考页面查看更多详情,

2.2.3.1.5 - 高可用拓扑选项

本页面介绍了配置高可用(HA)Kubernetes 集群拓扑的两个选项。

你可以设置 HA 集群:

  • 使用堆叠(stacked)控制平面节点,其中 etcd 节点与控制平面节点共存
  • 使用外部 etcd 节点,其中 etcd 在与控制平面不同的节点上运行

在设置 HA 集群之前,你应该仔细考虑每种拓扑的优缺点。

堆叠(Stacked)etcd 拓扑

堆叠(Stacked)HA 集群是一种这样的拓扑, 其中 etcd 分布式数据存储集群堆叠在 kubeadm 管理的控制平面节点上,作为控制平面的一个组件运行。

每个控制平面节点运行 kube-apiserverkube-schedulerkube-controller-manager 实例。 kube-apiserver 使用负载均衡器暴露给工作节点。

每个控制平面节点创建一个本地 etcd 成员(member),这个 etcd 成员只与该节点的 kube-apiserver 通信。 这同样适用于本地 kube-controller-managerkube-scheduler 实例。

这种拓扑将控制平面和 etcd 成员耦合在同一节点上。相对使用外部 etcd 集群, 设置起来更简单,而且更易于副本管理。

然而,堆叠集群存在耦合失败的风险。如果一个节点发生故障,则 etcd 成员和控制平面实例都将丢失, 并且冗余会受到影响。你可以通过添加更多控制平面节点来降低此风险。

因此,你应该为 HA 集群运行至少三个堆叠的控制平面节点。

这是 kubeadm 中的默认拓扑。当使用 kubeadm initkubeadm join --control-plane 时, 在控制平面节点上会自动创建本地 etcd 成员。

堆叠的 etcd 拓扑

外部 etcd 拓扑

具有外部 etcd 的 HA 集群是一种这样的拓扑, 其中 etcd 分布式数据存储集群在独立于控制平面节点的其他节点上运行。

就像堆叠的 etcd 拓扑一样,外部 etcd 拓扑中的每个控制平面节点都会运行 kube-apiserverkube-schedulerkube-controller-manager 实例。 同样,kube-apiserver 使用负载均衡器暴露给工作节点。但是 etcd 成员在不同的主机上运行, 每个 etcd 主机与每个控制平面节点的 kube-apiserver 通信。

这种拓扑结构解耦了控制平面和 etcd 成员。因此它提供了一种 HA 设置, 其中失去控制平面实例或者 etcd 成员的影响较小,并且不会像堆叠的 HA 拓扑那样影响集群冗余。

但此拓扑需要两倍于堆叠 HA 拓扑的主机数量。 具有此拓扑的 HA 集群至少需要三个用于控制平面节点的主机和三个用于 etcd 节点的主机。

外部 etcd 拓扑

接下来

2.2.3.1.6 - 利用 kubeadm 创建高可用集群

本文讲述了使用 kubeadm 设置一个高可用的 Kubernetes 集群的两种不同方式:

  • 使用具有堆叠的控制平面节点。这种方法所需基础设施较少。etcd 成员和控制平面节点位于同一位置。
  • 使用外部集群。这种方法所需基础设施较多。控制平面的节点和 etcd 成员是分开的。

在下一步之前,你应该仔细考虑哪种方法更好地满足你的应用程序和环境的需求。 高可用拓扑选项 讲述了每种方法的优缺点。

如果你在安装 HA 集群时遇到问题,请在 kubeadm 问题跟踪里向我们提供反馈。

你也可以阅读升级文档

准备开始

根据集群控制平面所选择的拓扑结构不同,准备工作也有所差异:

需要准备:

  • 配置满足 kubeadm 的最低要求 的三台机器作为控制面节点。奇数台控制平面节点有利于机器故障或者网络分区时进行重新选主。
  • 配置满足 kubeadm 的最低要求 的三台机器作为工作节点
  • 在集群中,确保所有计算机之间存在全网络连接(公网或私网)
  • 在所有机器上具有 sudo 权限
    • 可以使用其他工具;本教程以 sudo 举例
  • 从某台设备通过 SSH 访问系统中所有节点的能力
  • 所有机器上已经安装 kubeadmkubelet

拓扑详情请参考堆叠(Stacked)etcd 拓扑

需要准备:

  • 配置满足 kubeadm 的最低要求 的三台机器作为控制面节点。奇数台控制平面节点有利于机器故障或者网络分区时进行重新选主。
  • 配置满足 kubeadm 的最低要求 的三台机器作为工作节点
  • 在集群中,确保所有计算机之间存在全网络连接(公网或私网)
  • 在所有机器上具有 sudo 权限
    • 可以使用其他工具;本教程以 sudo 举例
  • 从某台设备通过 SSH 访问系统中所有节点的能力
  • 所有机器上已经安装 kubeadmkubelet

还需要准备:

  • 给 etcd 集群使用的另外三台及以上机器。为了分布式一致性算法达到更好的投票效果,集群必须由奇数个节点组成。
    • 机器上已经安装 kubeadmkubelet
    • 机器上同样需要安装好容器运行时,并能正常运行。

拓扑详情请参考外部 etcd 拓扑

容器镜像

每台主机需要能够从 Kubernetes 容器镜像仓库(k8s.gcr.io)读取和拉取镜像。 想要在无法拉取 Kubernetes 仓库镜像的机器上部署高可用集群也是可行的。通过其他的手段保证主机上已经有对应的容器镜像即可。

命令行

一旦集群创建成功,需要在 PC 上安装 kubectl 用于管理 Kubernetes。 为了方便故障排查,也可以在每个控制平面节点上安装 kubectl

这两种方法的第一步

为 kube-apiserver 创建负载均衡器

  1. 创建一个名为 kube-apiserver 的负载均衡器解析 DNS。

    • 在云环境中,应该将控制平面节点放置在 TCP 转发负载平衡后面。 该负载均衡器将流量分配给目标列表中所有运行状况良好的控制平面节点。 API 服务器的健康检查是在 kube-apiserver 的监听端口(默认值 :6443) 上进行的一个 TCP 检查。

    • 不建议在云环境中直接使用 IP 地址。

    • 负载均衡器必须能够在 API 服务器端口上与所有控制平面节点通信。 它还必须允许其监听端口的入站流量。

    • 确保负载均衡器的地址始终匹配 kubeadm 的 ControlPlaneEndpoint 地址。

    • 阅读软件负载平衡选项指南 以获取更多详细信息。

  1. 添加第一个控制平面节点到负载均衡器并测试连接:

    nc -v LOAD_BALANCER_IP PORT
    

    由于 API 服务器尚未运行,预期会出现一个连接拒绝错误。 然而超时意味着负载均衡器不能和控制平面节点通信。 如果发生超时,请重新配置负载均衡器与控制平面节点进行通信。

  2. 将其余控制平面节点添加到负载均衡器目标组。

使用堆控制平面和 etcd 节点

控制平面节点的第一步

  1. 初始化控制平面:

    sudo kubeadm init --control-plane-endpoint "LOAD_BALANCER_DNS:LOAD_BALANCER_PORT" --upload-certs
    
    • 你可以使用 --kubernetes-version 标志来设置要使用的 Kubernetes 版本。 建议将 kubeadm、kebelet、kubectl 和 Kubernetes 的版本匹配。
    • 这个 --control-plane-endpoint 标志应该被设置成负载均衡器的地址或 DNS 和端口。
    • 这个 --upload-certs 标志用来将在所有控制平面实例之间的共享证书上传到集群。 如果正好相反,你更喜欢手动地通过控制平面节点或者使用自动化工具复制证书, 请删除此标志并参考如下部分证书分配手册
    • 输出类似于:
    ...
    You can now join any number of control-plane node by running the following command on each as a root:
    kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07
    
    Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
    As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use kubeadm init phase upload-certs to reload certs afterward.
    
    Then you can join any number of worker nodes by running the following on each as root:
      kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
    
    • 将此输出复制到文本文件。 稍后你将需要它来将控制平面节点和工作节点加入集群。

    • 当使用 --upload-certs 调用 kubeadm init 时,主控制平面的证书被加密并上传到 kubeadm-certs Secret 中。

    • 要重新上传证书并生成新的解密密钥,请在已加入集群节点的控制平面上使用以下命令:

      sudo kubeadm init phase upload-certs --upload-certs
      
    • 你还可以在 init 期间指定自定义的 --certificate-key,以后可以由 join 使用。 要生成这样的密钥,可以使用以下命令:
    kubeadm certs certificate-key
    
  1. 应用你所选择的 CNI 插件: 请遵循以下指示 安装 CNI 驱动。如果适用,请确保配置与 kubeadm 配置文件中指定的 Pod CIDR 相对应。

  1. 输入以下内容,并查看控制平面组件的 Pod 启动:

    kubectl get pod -n kube-system -w
    

其余控制平面节点的步骤

对于每个其他控制平面节点,你应该:

  1. 执行先前由第一个节点上的 kubeadm init 输出提供给你的 join 命令。 它看起来应该像这样:

    sudo kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07
    
    • 这个 --control-plane 标志通知 kubeadm join 创建一个新的控制平面。
    • --certificate-key ... 将导致从集群中的 kubeadm-certs Secret 下载控制平面证书并使用给定的密钥进行解密。

外部 etcd 节点

使用外部 etcd 节点设置集群类似于用于堆叠 etcd 的过程, 不同之处在于你应该首先设置 etcd,并在 kubeadm 配置文件中传递 etcd 信息。

设置 ectd 集群

  1. 按照这些指示 去设置 etcd 集群。

  2. 根据这里 的描述配置 SSH。

  3. 将以下文件从集群中的任何 etcd 节点复制到第一个控制平面节点:

    export CONTROL_PLANE="ubuntu@10.0.0.7"
    scp /etc/kubernetes/pki/etcd/ca.crt "${CONTROL_PLANE}":
    scp /etc/kubernetes/pki/apiserver-etcd-client.crt "${CONTROL_PLANE}":
    scp /etc/kubernetes/pki/apiserver-etcd-client.key "${CONTROL_PLANE}":
    
    • 用第一台控制平面节点的 user@host 替换 CONTROL_PLANE 的值。

设置第一个控制平面节点

  1. 用以下内容创建一个名为 kubeadm-config.yaml 的文件:

    ---
    apiVersion: kubeadm.k8s.io/v1beta3
    kind: ClusterConfiguration
    kubernetesVersion: stable
    controlPlaneEndpoint: "LOAD_BALANCER_DNS:LOAD_BALANCER_PORT" # change this (see below)
    etcd:
      external:
        endpoints:
          - https://ETCD_0_IP:2379 # 适当地更改 ETCD_0_IP
          - https://ETCD_1_IP:2379 # 适当地更改 ETCD_1_IP
          - https://ETCD_2_IP:2379 # 适当地更改 ETCD_2_IP
        caFile: /etc/kubernetes/pki/etcd/ca.crt
        certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
        keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
    
    • 在你的集群中,将配置模板中的以下变量替换为适当值:

    • LOAD_BALANCER_DNS

    • LOAD_BALANCER_PORT

    • ETCD_0_IP

    • ETCD_1_IP

    • ETCD_2_IP

以下的步骤与设置内置 etcd 的集群是相似的:

  1. 在节点上运行 sudo kubeadm init --config kubeadm-config.yaml --upload-certs 命令。

  2. 记下输出的 join 命令,这些命令将在以后使用。

  3. 应用你选择的 CNI 插件。

其他控制平面节点的步骤

步骤与设置内置 etcd 相同:

  • 确保第一个控制平面节点已完全初始化。
  • 使用保存到文本文件的 join 命令将每个控制平面节点连接在一起。 建议一次加入一个控制平面节点。
  • 不要忘记默认情况下,--certificate-key 中的解密秘钥会在两个小时后过期。

列举控制平面之后的常见任务

安装工作节点

你可以使用之前存储的 kubeadm init 命令的输出将工作节点加入集群中:

sudo kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866

手动证书分发

如果你选择不将 kubeadm init--upload-certs 命令一起使用, 则意味着你将必须手动将证书从主控制平面节点复制到将要加入的控制平面节点上。

有许多方法可以实现这种操作。下面的例子使用了 sshscp

如果要在单独的一台计算机控制所有节点,则需要 SSH。

  1. 在你的主设备上启用 ssh-agent,要求该设备能访问系统中的所有其他节点:

    eval $(ssh-agent)
    
  1. 将 SSH 身份添加到会话中:

    ssh-add ~/.ssh/path_to_private_key
    
  1. 检查节点间的 SSH 以确保连接是正常运行的

    • SSH 到任何节点时,请确保添加 -A 标志。 此标志允许你通过 SSH 登录到节点后从该节点上访问你自己 PC 上的 SSH 代理。 如果你不完全信任该节点上的用户会话安全,可以考虑使用其他替代方法。
    ssh -A 10.0.0.7
    
    • 当在任何节点上使用 sudo 时,请确保保持环境变量设置,以便 SSH 转发能够正常工作:
    sudo -E -s
    
  1. 在所有节点上配置 SSH 之后,你应该在运行过 kubeadm init 命令的第一个控制平面节点上运行以下脚本。 该脚本会将证书从第一个控制平面节点复制到另一个控制平面节点:

    在以下示例中,用其他控制平面节点的 IP 地址替换 CONTROL_PLANE_IPS

    USER=ubuntu # 可定制
    CONTROL_PLANE_IPS="10.0.0.7 10.0.0.8"
    for host in ${CONTROL_PLANE_IPS}; do
        scp /etc/kubernetes/pki/ca.crt "${USER}"@$host:
        scp /etc/kubernetes/pki/ca.key "${USER}"@$host:
        scp /etc/kubernetes/pki/sa.key "${USER}"@$host:
        scp /etc/kubernetes/pki/sa.pub "${USER}"@$host:
        scp /etc/kubernetes/pki/front-proxy-ca.crt "${USER}"@$host:
        scp /etc/kubernetes/pki/front-proxy-ca.key "${USER}"@$host:
        scp /etc/kubernetes/pki/etcd/ca.crt "${USER}"@$host:etcd-ca.crt
        # 如果你正使用外部 etcd,忽略下一行
        scp /etc/kubernetes/pki/etcd/ca.key "${USER}"@$host:etcd-ca.key
    done
    
  1. 然后,在每个即将加入集群的控制平面节点上,你必须先运行以下脚本,然后再运行 kubeadm join。 该脚本会将先前复制的证书从主目录移动到 /etc/kubernetes/pki

    USER=ubuntu # 可定制
    mkdir -p /etc/kubernetes/pki/etcd
    mv /home/${USER}/ca.crt /etc/kubernetes/pki/
    mv /home/${USER}/ca.key /etc/kubernetes/pki/
    mv /home/${USER}/sa.pub /etc/kubernetes/pki/
    mv /home/${USER}/sa.key /etc/kubernetes/pki/
    mv /home/${USER}/front-proxy-ca.crt /etc/kubernetes/pki/
    mv /home/${USER}/front-proxy-ca.key /etc/kubernetes/pki/
    mv /home/${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
    # 如果你正使用外部 etcd,忽略下一行
    mv /home/${USER}/etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
    

2.2.3.1.7 - 使用 kubeadm 创建一个高可用 etcd 集群

默认情况下,kubeadm 在每个控制平面节点上运行一个本地 etcd 实例。也可以使用外部的 etcd 集群,并在不同的主机上提供 etcd 实例。 这两种方法的区别在 高可用拓扑的选项 页面中阐述。

这个任务将指导你创建一个由三个成员组成的高可用外部 etcd 集群,该集群在创建过程中可被 kubeadm 使用。

准备开始

  • 三个可以通过 2379 和 2380 端口相互通信的主机。本文档使用这些作为默认端口。不过,它们可以通过 kubeadm 的配置文件进行自定义。
  • 一些可以用来在主机间复制文件的基础设施。例如 sshscp 就可以满足需求。

建立集群

一般来说,是在一个节点上生成所有证书并且只分发这些必要的文件到其它节点上。

  1. 将 kubelet 配置为 etcd 的服务管理器。

    由于 etcd 是首先创建的,因此你必须通过创建具有更高优先级的新文件来覆盖 kubeadm 提供的 kubelet 单元文件。

    cat << EOF > /etc/systemd/system/kubelet.service.d/20-etcd-service-manager.conf
    [Service]
    ExecStart=
    # 将下面的 "systemd" 替换为你的容器运行时所使用的 cgroup 驱动。
    # kubelet 的默认值为 "cgroupfs"。
    # 如果需要的话,将 "--container-runtime-endpoint " 的值替换为一个不同的容器运行时。
    ExecStart=/usr/bin/kubelet --address=127.0.0.1 --pod-manifest-path=/etc/kubernetes/manifests --cgroup-driver=systemd
    Restart=always
    EOF
    
    systemctl daemon-reload
    systemctl restart kubelet
    

    检查 kubelet 的状态以确保其处于运行状态:

    systemctl status kubelet
    
  1. 为 kubeadm 创建配置文件。

    使用以下脚本为每个将要运行 etcd 成员的主机生成一个 kubeadm 配置文件。

    # 使用你的主机 IP 替换 HOST0、HOST1 和 HOST2 的 IP 地址
    export HOST0=10.0.0.6
    export HOST1=10.0.0.7
    export HOST2=10.0.0.8
    
    # 使用你的主机名更新 NAME0、NAME1 和 NAME2
    export NAME0="infra0"
    export NAME1="infra1"
    export NAME2="infra2"
    
    # 创建临时目录来存储将被分发到其它主机上的文件
    mkdir -p /tmp/${HOST0}/ /tmp/${HOST1}/ /tmp/${HOST2}/
    
    HOSTS=(${HOST0} ${HOST1} ${HOST2})
    NAMES=(${NAME0} ${NAME1} ${NAME2})
    
    for i in "${!HOSTS[@]}"; do
    HOST=${HOSTS[$i]}
    NAME=${NAMES[$i]}
    cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml
    ---
    apiVersion: "kubeadm.k8s.io/v1beta3"
    kind: InitConfiguration
    nodeRegistration:
        name: ${NAME}
    localAPIEndpoint:
        advertiseAddress: ${HOST}
    ---
    apiVersion: "kubeadm.k8s.io/v1beta3"
    kind: ClusterConfiguration
    etcd:
        local:
            serverCertSANs:
            - "${HOST}"
            peerCertSANs:
            - "${HOST}"
            extraArgs:
                initial-cluster: ${NAMES[0]}=https://${HOSTS[0]}:2380,${NAMES[1]}=https://${HOSTS[1]}:2380,${NAMES[2]}=https://${HOSTS[2]}:2380
                initial-cluster-state: new
                name: ${NAME}
                listen-peer-urls: https://${HOST}:2380
                listen-client-urls: https://${HOST}:2379
                advertise-client-urls: https://${HOST}:2379
                initial-advertise-peer-urls: https://${HOST}:2380
    EOF
    done
    
  1. 生成证书颁发机构

    如果你已经拥有 CA,那么唯一的操作是复制 CA 的 crtkey 文件到 etc/kubernetes/pki/etcd/ca.crt/etc/kubernetes/pki/etcd/ca.key。 复制完这些文件后继续下一步,“为每个成员创建证书”。

    如果你还没有 CA,则在 $HOST0(你为 kubeadm 生成配置文件的位置)上运行此命令。

    kubeadm init phase certs etcd-ca
    

    这一操作创建如下两个文件:

    • /etc/kubernetes/pki/etcd/ca.crt
    • /etc/kubernetes/pki/etcd/ca.key
  1. 为每个成员创建证书

    kubeadm init phase certs etcd-server --config=/tmp/${HOST2}/kubeadmcfg.yaml
    kubeadm init phase certs etcd-peer --config=/tmp/${HOST2}/kubeadmcfg.yaml
    kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST2}/kubeadmcfg.yaml
    kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST2}/kubeadmcfg.yaml
    cp -R /etc/kubernetes/pki /tmp/${HOST2}/
    # 清理不可重复使用的证书
    find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
    
    kubeadm init phase certs etcd-server --config=/tmp/${HOST1}/kubeadmcfg.yaml
    kubeadm init phase certs etcd-peer --config=/tmp/${HOST1}/kubeadmcfg.yaml
    kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST1}/kubeadmcfg.yaml
    kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST1}/kubeadmcfg.yaml
    cp -R /etc/kubernetes/pki /tmp/${HOST1}/
    find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
    
    kubeadm init phase certs etcd-server --config=/tmp/${HOST0}/kubeadmcfg.yaml
    kubeadm init phase certs etcd-peer --config=/tmp/${HOST0}/kubeadmcfg.yaml
    kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST0}/kubeadmcfg.yaml
    kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST0}/kubeadmcfg.yaml
    # 不需要移动 certs 因为它们是给 HOST0 使用的
    
    # 清理不应从此主机复制的证书
    find /tmp/${HOST2} -name ca.key -type f -delete
    find /tmp/${HOST1} -name ca.key -type f -delete
    
  1. 复制证书和 kubeadm 配置

    证书已生成,现在必须将它们移动到对应的主机。

    USER=ubuntu
    HOST=${HOST1}
    scp -r /tmp/${HOST}/* ${USER}@${HOST}:
    ssh ${USER}@${HOST}
    USER@HOST $ sudo -Es
    root@HOST $ chown -R root:root pki
    root@HOST $ mv pki /etc/kubernetes/
    
  1. 确保已经所有预期的文件都存在

    $HOST0 所需文件的完整列表如下:

    /tmp/${HOST0}
    └── kubeadmcfg.yaml
    ---
    /etc/kubernetes/pki
    ├── apiserver-etcd-client.crt
    ├── apiserver-etcd-client.key
    └── etcd
        ├── ca.crt
        ├── ca.key
        ├── healthcheck-client.crt
        ├── healthcheck-client.key
        ├── peer.crt
        ├── peer.key
        ├── server.crt
        └── server.key
    

    $HOST1 上:

    $HOME
    └── kubeadmcfg.yaml
    ---
    /etc/kubernetes/pki
    ├── apiserver-etcd-client.crt
    ├── apiserver-etcd-client.key
    └── etcd
        ├── ca.crt
        ├── healthcheck-client.crt
        ├── healthcheck-client.key
        ├── peer.crt
        ├── peer.key
        ├── server.crt
        └── server.key
    

    $HOST2 上:

    $HOME
    └── kubeadmcfg.yaml
    ---
    /etc/kubernetes/pki
    ├── apiserver-etcd-client.crt
    ├── apiserver-etcd-client.key
    └── etcd
        ├── ca.crt
        ├── healthcheck-client.crt
        ├── healthcheck-client.key
        ├── peer.crt
        ├── peer.key
        ├── server.crt
        └── server.key
    
  1. 创建静态 Pod 清单

    既然证书和配置已经就绪,是时候去创建清单了。 在每台主机上运行 kubeadm 命令来生成 etcd 使用的静态清单。

     root@HOST0 $ kubeadm init phase etcd local --config=/tmp/${HOST0}/kubeadmcfg.yaml
     root@HOST1 $ kubeadm init phase etcd local --config=$HOME/kubeadmcfg.yaml
     root@HOST2 $ kubeadm init phase etcd local --config=$HOME/kubeadmcfg.yaml
    
  1. 可选:检查集群运行状况

    docker run --rm -it \
    --net host \
    -v /etc/kubernetes:/etc/kubernetes k8s.gcr.io/etcd:${ETCD_TAG} etcdctl \
    --cert /etc/kubernetes/pki/etcd/peer.crt \
    --key /etc/kubernetes/pki/etcd/peer.key \
    --cacert /etc/kubernetes/pki/etcd/ca.crt \
    --endpoints https://${HOST0}:2379 endpoint health --cluster
    ...
    https://[HOST0 IP]:2379 is healthy: successfully committed proposal: took = 16.283339ms
    https://[HOST1 IP]:2379 is healthy: successfully committed proposal: took = 19.44402ms
    https://[HOST2 IP]:2379 is healthy: successfully committed proposal: took = 35.926451ms
    
    • ${ETCD_TAG} 设置为你的 etcd 镜像的版本标签,例如 3.4.3-0。 要查看 kubeadm 使用的 etcd 镜像和标签,请执行 kubeadm config images list --kubernetes-version ${K8S_VERSION}, 例如,其中的 ${K8S_VERSION} 可以是 v1.17.0
    • ${HOST0} 设置为要测试的主机的 IP 地址。

接下来

一旦拥有了一个正常工作的 3 成员的 etcd 集群,你就可以基于 使用 kubeadm 外部 etcd 的方法, 继续部署一个高可用的控制平面。

2.2.3.1.8 - 使用 kubeadm 配置集群中的每个 kubelet

特性状态: Kubernetes v1.11 [stable]

kubeadm CLI 工具的生命周期与 kubelet 解耦;kubelet 是一个守护程序,在 Kubernetes 集群中的每个节点上运行。 当 Kubernetes 初始化或升级时,kubeadm CLI 工具由用户执行,而 kubelet 始终在后台运行。

由于kubelet是守护程序,因此需要通过某种初始化系统或服务管理器进行维护。 当使用 DEB 或 RPM 安装 kubelet 时,配置系统去管理 kubelet。 你可以改用其他服务管理器,但需要手动地配置。

集群中涉及的所有 kubelet 的一些配置细节都必须相同, 而其他配置方面则需要基于每个 kubelet 进行设置,以适应给定机器的不同特性(例如操作系统、存储和网络)。 你可以手动地管理 kubelet 的配置,但是 kubeadm 现在提供一种 KubeletConfiguration API 类型 用于集中管理 kubelet 的配置

Kubelet 配置模式

以下各节讲述了通过使用 kubeadm 简化 kubelet 配置模式,而不是在每个节点上手动地管理 kubelet 配置。

将集群级配置传播到每个 kubelet 中

你可以通过 kubeadm initkubeadm join 命令为 kubelet 提供默认值。 有趣的示例包括使用其他容器运行时或通过服务器设置不同的默认子网。

如果你想使用子网 10.96.0.0/12 作为服务的默认网段,你可以给 kubeadm 传递 --service-cidr 参数:

kubeadm init --service-cidr 10.96.0.0/12

现在,可以从该子网分配服务的虚拟 IP。 你还需要通过 kubelet 使用 --cluster-dns 标志设置 DNS 地址。 在集群中的每个管理器和节点上的 kubelet 的设置需要相同。 kubelet 提供了一个版本化的结构化 API 对象,该对象可以配置 kubelet 中的大多数参数,并将此配置推送到集群中正在运行的每个 kubelet 上。 此对象被称为 KubeletConfigurationKubeletConfiguration 允许用户指定标志,例如用骆峰值代表集群的 DNS IP 地址,如下所示:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
clusterDNS:
- 10.96.0.10

有关 KubeletConfiguration 的更多详细信息,请参阅本节

提供特定于某实例的配置细节

由于硬件、操作系统、网络或者其他主机特定参数的差异。某些主机需要特定的 kubelet 配置。 以下列表提供了一些示例。

  • 由 kubelet 配置标志 --resolv-conf 指定的 DNS 解析文件的路径在操作系统之间可能有所不同, 它取决于你是否使用 systemd-resolved。 如果此路径错误,则在其 kubelet 配置错误的节点上 DNS 解析也将失败。

  • 除非你使用云驱动,否则默认情况下 Node API 对象的 .metadata.name 会被设置为计算机的主机名。 如果你需要指定一个与机器的主机名不同的节点名称,你可以使用 --hostname-override 标志覆盖默认值。

  • 当前,kubelet 无法自动检测容器运行时使用的 cgroup 驱动程序, 但是值 --cgroup-driver 必须与容器运行时使用的 cgroup 驱动程序匹配,以确保 kubelet 的健康运行状况。

  • 要指定容器运行时,你必须用 --container-runtime-endpoint=<path> 标志来指定端点。

你可以在服务管理器(例如 systemd)中设定某个 kubelet 的配置来指定这些参数。

使用 kubeadm 配置 kubelet

如果自定义的 KubeletConfiguration API 对象使用像 kubeadm ... --config some-config-file.yaml 这样的配置文件进行传递,则可以配置 kubeadm 启动的 kubelet。

通过调用 kubeadm config print init-defaults --component-configs KubeletConfiguration, 你可以看到此结构中的所有默认值。

也可以阅读 KubeletConfiguration 参考 来获取有关各个字段的更多信息。

使用 kubeadm init 时的工作流程

当调用 kubeadm init 时,kubelet 的配置会被写入磁盘 /var/lib/kubelet/config.yaml, 并上传到集群 kube-system 命名空间的 kubelet-config ConfigMap。 kubelet 配置信息也被写入 /etc/kubernetes/kubelet.conf,其中包含集群内所有 kubelet 的基线配置。 此配置文件指向允许 kubelet 与 API 服务器通信的客户端证书。 这解决了将集群级配置传播到每个 kubelet 的需求。

针对为特定实例提供配置细节的第二种模式, kubeadm 的解决方法是将环境文件写入 /var/lib/kubelet/kubeadm-flags.env,其中包含了一个标志列表, 当 kubelet 启动时,该标志列表会传递给 kubelet 标志在文件中的显示方式如下:

KUBELET_KUBEADM_ARGS="--flag1=value1 --flag2=value2 ..."

除了启动 kubelet 时所使用的标志外,该文件还包含动态参数,例如 cgroup 驱动程序以及是否使用其他容器运行时套接字(--cri-socket)。

将这两个文件编组到磁盘后,如果使用 systemd,则 kubeadm 尝试运行以下两个命令:

systemctl daemon-reload && systemctl restart kubelet

如果重新加载和重新启动成功,则正常的 kubeadm init 工作流程将继续。

使用 kubeadm join 时的工作流程

当运行 kubeadm join 时,kubeadm 使用 Bootstrap Token 证书执行 TLS 引导,该引导会获取一份证书, 该证书需要下载 kubelet-config ConfigMap 并把它写入 /var/lib/kubelet/config.yaml 中。 动态环境文件的生成方式恰好与 kubeadm init 完全相同。

接下来,kubeadm 运行以下两个命令将新配置加载到 kubelet 中:

systemctl daemon-reload && systemctl restart kubelet

在 kubelet 加载新配置后,kubeadm 将写入 /etc/kubernetes/bootstrap-kubelet.conf KubeConfig 文件中, 该文件包含 CA 证书和引导程序令牌。 kubelet 使用这些证书执行 TLS 引导程序并获取唯一的凭据,该凭据被存储在 /etc/kubernetes/kubelet.conf 中。

/etc/kubernetes/kubelet.conf 文件被写入后,kubelet 就完成了 TLS 引导过程。 Kubeadm 在完成 TLS 引导过程后将删除 /etc/kubernetes/bootstrap-kubelet.conf 文件。

kubelet 的 systemd drop-in 文件

kubeadm 中附带了有关系统如何运行 kubelet 的 systemd 配置文件。 请注意 kubeadm CLI 命令不会修改此文件。

通过 kubeadm DEB 包 或者 RPM 包 安装的配置文件被写入 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 并由 systemd 使用。 它对原来的 RPM 版本 kubelet.service 或者 DEB 版本 kubelet.service 作了增强:

[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"
# 这是 "kubeadm init" 和 "kubeadm join" 运行时生成的文件,动态地填充 KUBELET_KUBEADM_ARGS 变量
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# 这是一个文件,用户在不得已下可以将其用作替代 kubelet args。
# 用户最好使用 .NodeRegistration.KubeletExtraArgs 对象在配置文件中替代。
# KUBELET_EXTRA_ARGS 应该从此文件中获取。
EnvironmentFile=-/etc/default/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS

此文件指定由 kubeadm 为 kubelet 管理的所有文件的默认位置。

  • 用于 TLS 引导程序的 KubeConfig 文件为 /etc/kubernetes/bootstrap-kubelet.conf, 但仅当 /etc/kubernetes/kubelet.conf 不存在时才能使用。
  • 具有唯一 kubelet 标识的 KubeConfig 文件为 /etc/kubernetes/kubelet.conf
  • 包含 kubelet 的组件配置的文件为 /var/lib/kubelet/config.yaml
  • 包含的动态环境的文件 KUBELET_KUBEADM_ARGS 是来源于 /var/lib/kubelet/kubeadm-flags.env
  • 包含用户指定标志替代的文件 KUBELET_EXTRA_ARGS 是来源于 /etc/default/kubelet(对于 DEB),或者 /etc/sysconfig/kubelet(对于 RPM)。 KUBELET_EXTRA_ARGS 在标志链中排在最后,并且在设置冲突时具有最高优先级。

Kubernetes 可执行文件和软件包内容

Kubernetes 版本对应的 DEB 和 RPM 软件包是:

软件包名称描述
kubeadm给 kubelet 安装 /usr/bin/kubeadm CLI 工具和 kubelet 的 systemd drop-in 文件
kubelet安装 /usr/bin/kubelet 可执行文件。
kubectl安装 /usr/bin/kubectl 可执行文件。
cri-toolscri-tools git 仓库中安装 /usr/bin/crictl 可执行文件。
kubernetes-cniplugins git 仓库中安装 /opt/cni/bin 可执行文件。

2.2.3.1.9 - 使用 kubeadm 支持双协议栈

特性状态: Kubernetes v1.23 [stable]

你的集群包含双协议栈组网支持, 这意味着集群网络允许你在两种地址族间任选其一。在集群中,控制面可以为同一个 Pod 或者 Service 同时赋予 IPv4 和 IPv6 地址。

准备开始

你需要已经遵从安装 kubeadm 中所给的步骤安装了 kubeadm 工具。

针对你要作为节点使用的每台服务器, 确保其允许 IPv6 转发。在 Linux 节点上,你可以通过以 root 用户在每台服务器上运行 sysctl -w net.ipv6.conf.all.forwarding=1 来完成设置。

你需要一个可以使用的 IPv4 和 IPv6 地址范围。集群操作人员通常为 IPv4 使用 私有地址范围。对于 IPv6,集群操作人员通常会基于分配给该操作人员的地址范围, 从 2000::/3 中选择一个全局的单播地址块。你不需要将集群的 IP 地址范围路由 到公众互联网。

创建双协议栈集群

要使用 kubeadm init 创建一个双协议栈集群,你可以传递与下面的例子类似的命令行参数:

# 这里的地址范围仅作示例使用
kubeadm init --pod-network-cidr=10.244.0.0/16,2001:db8:42:0::/56 --service-cidr=10.96.0.0/16,2001:db8:42:1::/112

为了更便于理解,参看下面的名为 kubeadm-config.yaml 的 kubeadm 配置文件, 该文件用于双协议栈控制面的主控制节点。

---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
networking:
  podSubnet: 10.244.0.0/16,2001:db8:42:0::/56
  serviceSubnet: 10.96.0.0/16,2001:db8:42:1::/112
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
  advertiseAddress: "10.100.0.1"
  bindPort: 6443
nodeRegistration:
  kubeletExtraArgs:
    node-ip: 10.100.0.2,fd00:1:2:3::2

InitConfiguration 中的 advertiseAddress 给出 API 服务器将公告自身要监听的 IP 地址。advertiseAddress 的取值与 kubeadm init 的标志 --apiserver-advertise-address 的取值相同。

运行 kubeadm 来实例化双协议栈控制面节点:

kubeadm init --config=kubeadm-config.yaml

kube-controller-manager 标志 --node-cidr-mask-size-ipv4|--node-cidr-mask-size-ipv6 是使用默认值来设置的。参见配置 IPv4/IPv6 双协议栈

向双协议栈集群添加节点

在添加节点之前,请确保该节点具有 IPv6 可路由的网络接口并且启用了 IPv6 转发。

下面的名为 kubeadm-config.yaml 的 kubeadm 配置文件 示例用于向集群中添加工作节点。

apiVersion: kubeadm.k8s.io/v1beta3
kind: JoinConfiguration
discovery:
  bootstrapToken:
    apiServerEndpoint: 10.100.0.1:6443
    token: "clvldh.vjjwg16ucnhp94qr"
    caCertHashes:
    - "sha256:a4863cde706cfc580a439f842cc65d5ef112b7b2be31628513a9881cf0d9fe0e"
    # 请更改上面的认证信息,使之与你的集群中实际使用的令牌和 CA 证书匹配
nodeRegistration:
  kubeletExtraArgs:
    node-ip: 10.100.0.3,fd00:1:2:3::3

下面的名为 kubeadm-config.yaml 的 kubeadm 配置文件 示例用于向集群中添加另一个控制面节点。

apiVersion: kubeadm.k8s.io/v1beta3
kind: JoinConfiguration
controlPlane:
  localAPIEndpoint:
    advertiseAddress: "10.100.0.2"
    bindPort: 6443
discovery:
  bootstrapToken:
    apiServerEndpoint: 10.100.0.1:6443
    token: "clvldh.vjjwg16ucnhp94qr"
    caCertHashes:
    - "sha256:a4863cde706cfc580a439f842cc65d5ef112b7b2be31628513a9881cf0d9fe0e"
    # 请更改上面的认证信息,使之与你的集群中实际使用的令牌和 CA 证书匹配
nodeRegistration:
  kubeletExtraArgs:
    node-ip: 10.100.0.4,fd00:1:2:3::4

JoinConfiguration.controlPlane 中的 advertiseAddress 设定 API 服务器将公告自身要监听的 IP 地址。advertiseAddress 的取值与 kubeadm join 的标志 --apiserver-advertise-address 的取值相同。

kubeadm join --config=kubeadm-config.yaml

创建单协议栈集群

为了更便于理解,参看下面的名为 kubeadm-config.yaml 的 kubeadm 配置文件示例, 该文件用于单协议栈控制面节点。

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
networking:
  podSubnet: 10.244.0.0/16
  serviceSubnet: 10.96.0.0/16

接下来

2.2.3.2 - 使用 Kops 安装 Kubernetes

本篇快速入门介绍了如何在 AWS 上轻松安装 Kubernetes 集群。 本篇使用了一个名为 kops 的工具。

kops 是一个自动化的制备系统:

  • 全自动安装流程
  • 使用 DNS 识别集群
  • 自我修复:一切都在自动扩缩组中运行
  • 支持多种操作系统(如 Debian、Ubuntu 16.04、CentOS、RHEL、Amazon Linux 和 CoreOS) - 参考 images.md
  • 支持高可用 - 参考 high_availability.md
  • 可以直接提供或者生成 terraform 清单 - 参考 terraform.md

准备开始

创建集群

(1/5) 安装 kops

安装

下载页面下载 kops (从源代码构建也很方便):

使用下面的命令下载最新发布版本:

curl -LO https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-darwin-amd64

要下载特定版本,使用特定的 kops 版本替换下面命令中的部分:

$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)

例如,要下载 kops v1.20.0,输入:

curl -LO https://github.com/kubernetes/kops/releases/download/v1.20.0/kops-darwin-amd64

令 kops 二进制文件可执行:

chmod +x kops-darwin-amd64

将 kops 二进制文件移到你的 PATH 下:

sudo mv kops-darwin-amd64 /usr/local/bin/kops

你也可以使用 Homebrew 安装 kops:

brew update && brew install kops

使用命令下载最新发布版本:

curl -LO https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-linux-amd64

要下载 kops 的特定版本,用特定的 kops 版本替换下面命令中的部分:

$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)

例如,要下载 kops v1.20 版本,输入:

curl -LO https://github.com/kubernetes/kops/releases/download/v1.20.0/kops-linux-amd64

令 kops 二进制文件可执行:

chmod +x kops-linux-amd64

将 kops 二进制文件移到 PATH 下:

sudo mv kops-linux-amd64 /usr/local/bin/kops

你也可以使用 Homebrew 来安装 kops:

brew update && brew install kops

(2/5) 为你的集群创建一个 route53 域名

kops 在集群内部和外部都使用 DNS 进行发现操作,这样你可以从客户端访问 kubernetes API 服务器。

kops 对集群名称有明显的要求:它应该是有效的 DNS 名称。这样一来,你就不会再使集群混乱, 可以与同事明确共享集群,并且无需依赖记住 IP 地址即可访问集群。

你可以,或许应该使用子域名来划分集群。作为示例,我们将使用域名 useast1.dev.example.com。 这样,API 服务器端点域名将为 api.useast1.dev.example.com

Route53 托管区域可以服务子域名。你的托管区域可能是 useast1.dev.example.com,还有 dev.example.com 甚至 example.com。 kops 可以与以上任何一种配合使用,因此通常你出于组织原因选择不同的托管区域。 例如,允许你在 dev.example.com 下创建记录,但不能在 example.com 下创建记录。

假设你使用 dev.example.com 作为托管区域。你可以使用 正常流程 或者使用诸如 aws route53 create-hosted-zone --name dev.example.com --caller-reference 1 之类的命令来创建该托管区域。

然后,你必须在父域名中设置你的 DNS 记录,以便该域名中的记录可以被解析。 在这里,你将在 example.com 中为 dev 创建 DNS 记录。 如果它是根域名,则可以在域名注册机构配置 DNS 记录。 例如,你需要在购买 example.com 的地方配置 example.com

检查你的 route53 域已经被正确设置(这是导致问题的最常见原因!)。 如果你安装了 dig 工具,则可以通过运行以下步骤再次检查集群是否配置正确:

dig DNS dev.example.com

你应该看到 Route53 分配了你的托管区域的 4 条 DNS 记录。

(3/5) 创建一个 S3 存储桶来存储集群状态

kops 使你即使在安装后也可以管理集群。为此,它必须跟踪已创建的集群及其配置、所使用的密钥等。 此信息存储在 S3 存储桶中。S3 权限用于控制对存储桶的访问。

多个集群可以使用同一 S3 存储桶,并且你可以在管理同一集群的同事之间共享一个 S3 存储桶 - 这比传递 kubecfg 文件容易得多。 但是有权访问 S3 存储桶的任何人都将拥有对所有集群的管理访问权限, 因此你不想在运营团队之外共享它。

因此,通常每个运维团队都有一个 S3 存储桶(而且名称通常对应于上面托管区域的名称!)

在我们的示例中,我们选择 dev.example.com 作为托管区域,因此我们选择 clusters.dev.example.com 作为 S3 存储桶名称。

  • 导出 AWS_PROFILE 文件(如果你需要选择一个配置文件用来使 AWS CLI 正常工作)
  • 使用 aws s3 mb s3://clusters.dev.example.com 创建 S3 存储桶
  • 你可以进行 export KOPS_STATE_STORE=s3://clusters.dev.example.com 操作, 然后 kops 将默认使用此位置。 我们建议将其放入你的 bash profile 文件或类似文件中。

(4/5) 建立你的集群配置

运行 kops create cluster 以创建你的集群配置:

kops create cluster --zones=us-east-1c useast1.dev.example.com

kops 将为你的集群创建配置。请注意,它_仅_创建配置,实际上并没有创建云资源 - 你将在下一步中使用 kops update cluster 进行配置。 这使你有机会查看配置或进行更改。

它打印出可用于进一步探索的命令:

  • 使用以下命令列出集群:kops get cluster
  • 使用以下命令编辑该集群:kops edit cluster useast1.dev.example.com
  • 使用以下命令编辑你的节点实例组:kops edit ig --name = useast1.dev.example.com nodes
  • 使用以下命令编辑你的主实例组:kops edit ig --name = useast1.dev.example.com master-us-east-1c

如果这是你第一次使用 kops,请花几分钟尝试一下!实例组是一组实例,将被注册为 kubernetes 节点。 在 AWS 上,这是通过 auto-scaling-groups 实现的。你可以有多个实例组。 例如,如果你想要的是混合实例和按需实例的节点,或者 GPU 和非 GPU 实例。

(5/5) 在 AWS 中创建集群

运行 kops update cluster 以在 AWS 中创建集群:

kops update cluster useast1.dev.example.com --yes

这需要几秒钟的时间才能运行,但实际上集群可能需要几分钟才能准备就绪。 每当更改集群配置时,都会使用 kops update cluster 工具。 它将对配置进行的更改应用于你的集群 - 根据需要重新配置 AWS 或者 kubernetes。

例如,在你运行 kops edit ig nodes 之后,然后运行 kops update cluster --yes 应用你的配置,有时你还必须运行 kops rolling-update cluster 立即回滚更新配置。

如果没有 --yes 参数,kops update cluster 操作将向你显示其操作的预览效果。这对于生产集群很方便!

探索其他附加组件

请参阅附加组件列表探索其他附加组件, 包括用于 Kubernetes 集群的日志记录、监视、网络策略、可视化和控制的工具。

清理

  • 删除集群:kops delete cluster useast1.dev.example.com --yes

接下来

  • 了解有关 Kubernetes 的概念kubectl 的更多信息。
  • 参阅 kops 进阶用法获取教程、最佳实践和进阶配置选项。
  • 通过 Slack:社区讨论 参与 kops 社区讨论。
  • 通过解决或提出一个 GitHub Issue 来为 kops 做贡献。

2.2.3.3 - 使用 Kubespray 安装 Kubernetes

此快速入门有助于使用 Kubespray 安装在 GCE、Azure、OpenStack、AWS、vSphere、Equinix Metal(曾用名 Packet)、Oracle Cloud Infrastructure(实验性)或 Baremetal 上托管的 Kubernetes 集群。

Kubespray 是一个由 Ansible playbooks、 清单(inventory)、 制备工具和通用 OS/Kubernetes 集群配置管理任务的领域知识组成的。

Kubespray 提供:

  • 高可用性集群
  • 可组合属性(例如可选择网络插件)
  • 支持大多数流行的 Linux 发行版
    • Kinvolk 的 Flatcar Container Linux
    • Debian Bullseye、Buster、Jessie、Stretch
    • Ubuntu 16.04、18.04、20.04, 22.04
    • CentOS/RHEL 7、8
    • Fedora 34、35
    • Fedora CoreOS
    • openSUSE Leap 15.x/Tumbleweed
    • Oracle Linux 7、8
    • Alma Linux 8
    • Rocky Linux 8
    • Amazon Linux 2
  • 持续集成测试

要选择最适合你的用例的工具,请阅读 kubeadmkops 之间的 这份比较

创建集群

(1/5)满足下层设施要求

按以下要求来配置服务器:

  • Kubernetes 的最低版本要求为 V1.22
  • 在将运行 Ansible 命令的计算机上安装 Ansible v2.11(或更高版本)、Jinja 2.11(或更高版本)和 python-netaddr
  • 目标服务器必须能够访问 Internet 才能拉取 Docker 镜像。否则, 需要其他配置(请参见离线环境
  • 目标服务器配置为允许 IPv4 转发
  • 如果针对 Pod 和 Service 使用 IPv6,则目标服务器配置为允许 IPv6 转发
  • 防火墙不是由 kubespray 管理的。你需要根据需求设置适当的规则策略。为了避免部署过程中出现问题,可以禁用防火墙。
  • 如果从非 root 用户帐户运行 kubespray,则应在目标服务器中配置正确的特权升级方法并指定 ansible_become 标志或命令参数 --become-b

Kubespray 提供以下实用程序来帮助你设置环境:

(2/5)编写清单文件

设置服务器后,请创建一个 Ansible 的清单文件。 你可以手动执行此操作,也可以通过动态清单脚本执行此操作。有关更多信息,请参阅 “建立你自己的清单”。

(3/5)规划集群部署

Kubespray 能够自定义部署的许多方面:

  • 选择部署模式: kubeadm 或非 kubeadm
  • CNI(网络)插件
  • DNS 配置
  • 控制平面的选择:本机/可执行文件或容器化
  • 组件版本
  • Calico 路由反射器
  • 组件运行时选项
  • 证书生成方式

可以修改变量文件 以进行 Kubespray 定制。 如果你刚刚开始使用 Kubespray,请考虑使用 Kubespray 默认设置来部署你的集群并探索 Kubernetes。

(4/5)部署集群

接下来,部署你的集群:

使用 ansible-playbook 进行集群部署。

ansible-playbook -i your/inventory/inventory.ini cluster.yml -b -v \
  --private-key=~/.ssh/private_key

大型部署(超过 100 个节点)可能需要 特定的调整, 以获得最佳效果。

(5/5)验证部署

Kubespray 提供了一种使用 Netchecker 验证 Pod 间连接和 DNS 解析的方法。 Netchecker 确保 netchecker-agents Pods 可以解析 DNS 请求, 并在默认命名空间内对每个请求执行 ping 操作。 这些 Pod 模仿其他工作负载类似的行为,并用作集群运行状况指示器。

集群操作

Kubespray 提供了其他 Playbooks 来管理集群: scaleupgrade

扩展集群

你可以通过运行 scale playbook 向集群中添加工作节点。有关更多信息, 请参见 “添加节点”。 你可以通过运行 remove-node playbook 来从集群中删除工作节点。有关更多信息, 请参见 “删除节点”。

升级集群

你可以通过运行 upgrade-cluster Playbook 来升级集群。有关更多信息,请参见 “升级”。

清理

你可以通过 reset playbook 重置节点并清除所有与 Kubespray 一起安装的组件。

反馈

接下来

  • 查看有关 Kubespray 的 路线图的计划工作。
  • 查阅有关 Kubespray 的更多信息。

2.3 - 最佳实践

2.3.1 - 运行于多可用区环境

本页描述如何跨多个区(Zone)中运行集群。

背景

Kubernetes 从设计上允许同一个 Kubernetes 集群跨多个失效区来运行, 通常这些区位于某个称作 区域(region) 逻辑分组中。 主要的云提供商都将区域定义为一组失效区的集合(也称作 可用区(Availability Zones)), 能够提供一组一致的功能特性:每个区域内,各个可用区提供相同的 API 和服务。

典型的云体系结构都会尝试降低某个区中的失效影响到其他区中服务的概率。

控制面行为

所有的控制面组件 都支持以一组可相互替换的资源池的形式来运行,每个组件都有多个副本。

当你部署集群控制面时,应将控制面组件的副本跨多个失效区来部署。 如果可用性是一个很重要的指标,应该选择至少三个失效区,并将每个 控制面组件(API 服务器、调度器、etcd、控制器管理器)复制多个副本, 跨至少三个失效区来部署。如果你在运行云控制器管理器,则也应该将 该组件跨所选的三个失效区来部署。

节点行为

Kubernetes 自动为负载资源(如DeploymentStatefulSet)) 跨集群中不同节点来部署其 Pods。 这种分布逻辑有助于降低失效带来的影响。

节点启动时,每个节点上的 kubelet 会向 Kubernetes API 中代表该 kubelet 的 Node 对象 添加 标签。 这些标签可能包含区信息

如果你的集群跨了多个可用区或者地理区域,你可以使用节点标签,结合 Pod 拓扑分布约束 来控制如何在你的集群中多个失效域之间分布 Pods。这里的失效域可以是 地理区域、可用区甚至是特定节点。 这些提示信息使得调度器 能够更好地分布 Pods,以实现更好的可用性,降低因为某种失效给整个工作负载 带来的风险。

例如,你可以设置一种约束,确保某个 StatefulSet 中的三个副本都运行在 不同的可用区中,只要其他条件允许。你可以通过声明的方式来定义这种约束, 而不需要显式指定每个工作负载使用哪些可用区。

跨多个区分布节点

Kubernetes 的核心逻辑并不会帮你创建节点,你需要自行完成此操作,或者使用 类似 Cluster API 这类工具来替你管理节点。

使用类似 Cluster API 这类工具,你可以跨多个失效域来定义一组用做你的集群 工作节点的机器,以及当整个区的服务出现中断时如何自动治愈集群的策略。

为 Pods 手动指定区

你可以应用节点选择算符约束 到你所创建的 Pods 上,或者为 Deployment、StatefulSet 或 Job 这类工作负载资源 中的 Pod 模板设置此类约束。

跨区的存储访问

当创建持久卷时,PersistentVolumeLabel 准入控制器 会自动向那些链接到特定区的 PersistentVolume 添加区标签。 调度器通过其 NoVolumeZoneConflict 断言确保申领给定 PersistentVolume 的 Pods 只会 被调度到该卷所在的可用区。

你可以为 PersistentVolumeClaim 指定StorageClass 以设置该类中的存储可以使用的失效域(区)。 要了解如何配置能够感知失效域或区的 StorageClass,请参阅 可用的拓扑逻辑

网络

Kubernetes 自身不提供与可用区相关的联网配置。 你可以使用网络插件 来配置集群的联网,该网络解决方案可能拥有一些与可用区相关的元素。 例如,如果你的云提供商支持 type=LoadBalancer 的 Service,则负载均衡器 可能仅会将请求流量发送到运行在负责处理给定连接的负载均衡器组件所在的区。 请查阅云提供商的文档了解详细信息。

对于自定义的或本地集群部署,也可以考虑这些因素 Service Ingress 的行为, 包括处理不同失效区的方法,在很大程度上取决于你的集群是如何搭建的。

失效恢复

在搭建集群时,你可能需要考虑当某区域中的所有失效区都同时掉线时,是否以及如何 恢复服务。例如,你是否要求在某个区中至少有一个节点能够运行 Pod? 请确保任何对集群很关键的修复工作都不要指望集群中至少有一个健康节点。 例如:当所有节点都不健康时,你可能需要运行某个修复性的 Job, 该 Job 要设置特定的容忍度 以便修复操作能够至少将一个节点恢复为可用状态。

Kubernetes 对这类问题没有现成的解决方案;不过这也是要考虑的因素之一。

接下来

要了解调度器如何在集群中放置 Pods 并遵从所配置的约束,可参阅 调度与驱逐

2.3.2 - 大规模集群的注意事项

集群是运行 Kubernetes 代理的、 由控制平面管理的一组 节点(物理机或虚拟机)。 Kubernetes v1.24 支持的最大节点数为 5000。 更具体地说,Kubernetes旨在适应满足以下所有标准的配置:

  • 每个节点的 Pod 数量不超过 110
  • 节点数不超过 5000
  • Pod 总数不超过 150000
  • 容器总数不超过 300000

你可以通过添加或删除节点来扩展集群。集群扩缩的方式取决于集群的部署方式。

云供应商资源配额

为避免遇到云供应商配额问题,在创建具有大规模节点的集群时,请考虑以下事项:

  • 请求增加云资源的配额,例如:
    • 计算实例
    • CPUs
    • 存储卷
    • 使用中的 IP 地址
    • 数据包过滤规则集
    • 负载均衡数量
    • 网络子网
    • 日志流
  • 由于某些云供应商限制了创建新实例的速度,因此通过分批启动新节点来控制集群扩展操作,并在各批之间有一个暂停。

控制面组件

对于大型集群,你需要一个具有足够计算能力和其他资源的控制平面。

通常,你将在每个故障区域运行一个或两个控制平面实例, 先垂直缩放这些实例,然后在到达下降点(垂直)后再水平缩放。

你应该在每个故障区域至少应运行一个实例,以提供容错能力。 Kubernetes 节点不会自动将流量引向相同故障区域中的控制平面端点。 但是,你的云供应商可能有自己的机制来执行此操作。

例如,使用托管的负载均衡器时,你可以配置负载均衡器发送源自故障区域 A 中的 kubelet 和 Pod 的流量, 并将该流量仅定向到也位于区域 A 中的控制平面主机。 如果单个控制平面主机或端点故障区域 A 脱机,则意味着区域 A 中的节点的所有控制平面流量现在都在区域之间发送。 在每个区域中运行多个控制平面主机能降低出现这种结果的可能性。

etcd 存储

为了提高大规模集群的性能,你可以将事件对象存储在单独的专用 etcd 实例中。

在创建集群时,你可以(使用自定义工具):

  • 启动并配置额外的 etcd 实例
  • 配置 API 服务器,将它用于存储事件

有关为大型集群配置和管理 etcd 的详细信息,请参阅 为 Kubernetes 运行 etcd 集群 和使用 kubeadm 创建一个高可用 etcd 集群

插件资源

Kubernetes 资源限制 有助于最大程度地减少内存泄漏的影响以及 Pod 和容器可能对其他组件的其他方式的影响。 这些资源限制适用于插件资源, 就像它们适用于应用程序工作负载一样。

例如,你可以对日志组件设置 CPU 和内存限制

  ...
  containers:
  - name: fluentd-cloud-logging
    image: fluent/fluentd-kubernetes-daemonset:v1
    resources:
      limits:
        cpu: 100m
        memory: 200Mi

插件的默认限制通常基于从中小规模 Kubernetes 集群上运行每个插件的经验收集的数据。 插件在大规模集群上运行时,某些资源消耗常常比其默认限制更多。 如果在不调整这些值的情况下部署了大规模集群,则插件可能会不断被杀死,因为它们不断达到内存限制。 或者,插件可能会运行,但由于 CPU 时间片的限制而导致性能不佳。

为避免遇到集群插件资源问题,在创建大规模集群时,请考虑以下事项:

  • 部分垂直扩展插件 —— 总有一个插件副本服务于整个集群或服务于整个故障区域。 对于这些附加组件,请在扩大集群时加大资源请求和资源限制。
  • 许多水平扩展插件 —— 你可以通过运行更多的 Pod 来增加容量——但是在大规模集群下, 可能还需要稍微提高 CPU 或内存限制。 VerticalPodAutoscaler 可以在 recommender 模式下运行, 以提供有关请求和限制的建议数字。
  • 一些插件在每个节点上运行一个副本,并由 DaemonSet 控制: 例如,节点级日志聚合器。与水平扩展插件的情况类似, 你可能还需要稍微提高 CPU 或内存限制。

接下来

VerticalPodAutoscaler 是一种自定义资源,你可以将其部署到集群中,帮助你管理资源请求和 Pod 的限制。 访问 Vertical Pod Autoscaler 以了解有关 VerticalPodAutoscaler 的更多信息, 以及如何使用它来扩展集群组件(包括对集群至关重要的插件)的信息。

集群自动扩缩器 与许多云供应商集成在一起,帮助你在你的集群中,按照资源需求级别运行正确数量的节点。

addon resizer 可帮助你在集群规模变化时自动调整插件的大小。

2.3.3 - 校验节点设置

节点一致性测试

节点一致性测试 是一个容器化的测试框架,提供了针对节点的系统验证和功能测试。 测试验证节点是否满足 Kubernetes 的最低要求;通过测试的节点有资格加入 Kubernetes 集群。

该测试主要检测节点是否满足 Kubernetes 的最低要求,通过检测的节点有资格加入 Kubernetes 集群。

节点的前提条件

要运行节点一致性测试,节点必须满足与标准 Kubernetes 节点相同的前提条件。节点至少应安装以下守护程序:

  • 容器运行时 (Docker)
  • Kubelet

运行节点一致性测试

要运行节点一致性测试,请执行以下步骤:

  1. 得出 kubelet 的 --kubeconfig 的值;例如:--kubeconfig=/var/lib/kubelet/config.yaml。 由于测试框架启动了本地控制平面来测试 kubelet,因此使用 http://localhost:8080 作为API 服务器的 URL。 一些其他的 kubelet 命令行参数可能会被用到:
    • --cloud-provider:如果使用 --cloud-provider=gce,需要移除这个参数来运行测试。
  1. 使用以下命令运行节点一致性测试:

    # $CONFIG_DIR 是你 Kubelet 的 pod manifest 路径。
    # $LOG_DIR 是测试的输出路径。
    sudo docker run -it --rm --privileged --net=host \
      -v /:/rootfs -v $CONFIG_DIR:$CONFIG_DIR -v $LOG_DIR:/var/result \
      k8s.gcr.io/node-test:0.2
    

针对其他硬件体系结构运行节点一致性测试

Kubernetes 也为其他硬件体系结构的系统提供了节点一致性测试的 Docker 镜像:

架构镜像
amd64node-test-amd64
armnode-test-arm
arm64node-test-arm64

运行特定的测试

要运行特定测试,请使用你希望运行的测试的特定表达式覆盖环境变量 FOCUS

sudo docker run -it --rm --privileged --net=host \
  -v /:/rootfs:ro -v $CONFIG_DIR:$CONFIG_DIR -v $LOG_DIR:/var/result \
  -e FOCUS=MirrorPod \ # Only run MirrorPod test
  k8s.gcr.io/node-test:0.2

要跳过特定的测试,请使用你希望跳过的测试的常规表达式覆盖环境变量 SKIP

sudo docker run -it --rm --privileged --net=host \
  -v /:/rootfs:ro -v $CONFIG_DIR:$CONFIG_DIR -v $LOG_DIR:/var/result \
  -e SKIP=MirrorPod \ # 运行除 MirrorPod 测试外的所有一致性测试内容
  k8s.gcr.io/node-test:0.2

节点一致性测试是节点端到端测试的容器化版本。

默认情况下,它会运行所有一致性测试。

理论上,只要合理地配置容器和挂载所需的卷,就可以运行任何的节点端到端测试用例。但是这里强烈建议只运行一致性测试,因为运行非一致性测试需要很多复杂的配置。

注意事项

  • 测试会在节点上遗留一些 Docker 镜像,包括节点一致性测试本身的镜像和功能测试相关的镜像。
  • 测试会在节点上遗留一些死的容器。这些容器是在功能测试的过程中创建的。

2.3.4 - PKI 证书和要求

Kubernetes 需要 PKI 证书才能进行基于 TLS 的身份验证。如果你是使用 kubeadm 安装的 Kubernetes, 则会自动生成集群所需的证书。你还可以生成自己的证书。 例如,不将私钥存储在 API 服务器上,可以让私钥更加安全。此页面说明了集群必需的证书。

集群是如何使用证书的

Kubernetes 需要 PKI 才能执行以下操作:

  • Kubelet 的客户端证书,用于 API 服务器身份验证
  • Kubelet 服务端证书, 用于 API 服务器与 Kubelet 的会话
  • API 服务器端点的证书
  • 集群管理员的客户端证书,用于 API 服务器身份认证
  • API 服务器的客户端证书,用于和 Kubelet 的会话
  • API 服务器的客户端证书,用于和 etcd 的会话
  • 控制器管理器的客户端证书/kubeconfig,用于和 API 服务器的会话
  • 调度器的客户端证书/kubeconfig,用于和 API 服务器的会话
  • 前端代理 的客户端及服务端证书

etcd 还实现了双向 TLS 来对客户端和对其他对等节点进行身份验证。

证书存放的位置

假如通过 kubeadm 安装 Kubernetes,大多数证书都存储在 /etc/kubernetes/pki。 本文档中的所有路径都是相对于该目录的,但用户账户证书除外,kubeadm 将其放在 /etc/kubernetes 中。

手动配置证书

如果你不想通过 kubeadm 生成这些必需的证书,你可以使用一个单一的根 CA 来创建这些证书或者直接提供所有证书。 参见证书以进一步了解创建自己的证书机构。 关于管理证书的更多信息,请参见使用 kubeadm 进行证书管理

单根 CA

你可以创建由管理员控制的单根 CA。该根 CA 可以创建多个中间 CA,并将所有进一步的创建委托给 Kubernetes。

需要这些 CA:

路径默认 CN描述
ca.crt,keykubernetes-caKubernetes 通用 CA
etcd/ca.crt,keyetcd-ca与 etcd 相关的所有功能
front-proxy-ca.crt,keykubernetes-front-proxy-ca用于 前端代理

上面的 CA 之外,还需要获取用于服务账户管理的密钥对,也就是 sa.keysa.pub

下面的例子说明了上表中所示的 CA 密钥和证书文件。

/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/ca.key
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/etcd/ca.key
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-ca.key

所有的证书

如果你不想将 CA 的私钥拷贝至你的集群中,你也可以自己生成全部的证书。

需要这些证书:

默认 CN父级 CAO (位于 Subject 中)类型主机 (SAN)
kube-etcdetcd-caserver, client<hostname>, <Host_IP>, localhost, 127.0.0.1
kube-etcd-peeretcd-caserver, client<hostname>, <Host_IP>, localhost, 127.0.0.1
kube-etcd-healthcheck-clientetcd-caclient
kube-apiserver-etcd-clientetcd-casystem:mastersclient
kube-apiserverkubernetes-caserver<hostname>, <Host_IP>, <advertise_IP>, [1]
kube-apiserver-kubelet-clientkubernetes-casystem:mastersclient
front-proxy-clientkubernetes-front-proxy-caclient

[1]: 用来连接到集群的不同 IP 或 DNS 名 (就像 kubeadm 为负载均衡所使用的固定 IP 或 DNS 名,kuberneteskubernetes.defaultkubernetes.default.svckubernetes.default.svc.clusterkubernetes.default.svc.cluster.local)。

其中,kind 对应一种或多种类型的 x509 密钥用途

kind密钥用途
server数字签名、密钥加密、服务端认证
client数字签名、密钥加密、客户端认证

证书路径

证书应放置在建议的路径中(以便 kubeadm 使用)。无论使用什么位置,都应使用给定的参数指定路径。

默认 CN建议的密钥路径建议的证书路径命令密钥参数证书参数
etcd-caetcd/ca.keyetcd/ca.crtkube-apiserver--etcd-cafile
kube-apiserver-etcd-clientapiserver-etcd-client.keyapiserver-etcd-client.crtkube-apiserver--etcd-keyfile--etcd-certfile
kubernetes-caca.keyca.crtkube-apiserver--client-ca-file
kubernetes-caca.keyca.crtkube-controller-manager--cluster-signing-key-file--client-ca-file, --root-ca-file, --cluster-signing-cert-file
kube-apiserverapiserver.keyapiserver.crtkube-apiserver--tls-private-key-file--tls-cert-file
kube-apiserver-kubelet-clientapiserver-kubelet-client.keyapiserver-kubelet-client.crtkube-apiserver--kubelet-client-key--kubelet-client-certificate
front-proxy-cafront-proxy-ca.keyfront-proxy-ca.crtkube-apiserver--requestheader-client-ca-file
front-proxy-cafront-proxy-ca.keyfront-proxy-ca.crtkube-controller-manager--requestheader-client-ca-file
front-proxy-clientfront-proxy-client.keyfront-proxy-client.crtkube-apiserver--proxy-client-key-file--proxy-client-cert-file
etcd-caetcd/ca.keyetcd/ca.crtetcd--trusted-ca-file, --peer-trusted-ca-file
kube-etcdetcd/server.keyetcd/server.crtetcd--key-file--cert-file
kube-etcd-peeretcd/peer.keyetcd/peer.crtetcd--peer-key-file--peer-cert-file
etcd-caetcd/ca.crtetcdctl--cacert
kube-etcd-healthcheck-clientetcd/healthcheck-client.keyetcd/healthcheck-client.crtetcdctl--key--cert

注意事项同样适用于服务帐户密钥对:

私钥路径公钥路径命令参数
sa.keykube-controller-manager--service-account-private-key-file
sa.pubkube-apiserver--service-account-key-file

下面的例子展示了自行生成所有密钥和证书时所需要提供的文件路径。 这些路径基于前面的表格

/etc/kubernetes/pki/etcd/ca.key
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/apiserver-etcd-client.key
/etc/kubernetes/pki/apiserver-etcd-client.crt
/etc/kubernetes/pki/ca.key
/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/apiserver.key
/etc/kubernetes/pki/apiserver.crt
/etc/kubernetes/pki/apiserver-kubelet-client.key
/etc/kubernetes/pki/apiserver-kubelet-client.crt
/etc/kubernetes/pki/front-proxy-ca.key
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-client.key
/etc/kubernetes/pki/front-proxy-client.crt
/etc/kubernetes/pki/etcd/server.key
/etc/kubernetes/pki/etcd/server.crt
/etc/kubernetes/pki/etcd/peer.key
/etc/kubernetes/pki/etcd/peer.crt
/etc/kubernetes/pki/etcd/healthcheck-client.key
/etc/kubernetes/pki/etcd/healthcheck-client.crt
/etc/kubernetes/pki/sa.key
/etc/kubernetes/pki/sa.pub

为用户帐户配置证书

你必须手动配置以下管理员帐户和服务帐户:

文件名凭据名称默认 CNO (位于 Subject 中)
admin.confdefault-adminkubernetes-adminsystem:masters
kubelet.confdefault-authsystem:node:<nodeName> (参阅注释)system:nodes
controller-manager.confdefault-controller-managersystem:kube-controller-manager
scheduler.confdefault-schedulersystem:kube-scheduler
  1. 对于每个配置,请都使用给定的 CN 和 O 生成 x509 证书/密钥偶对。

  2. 为每个配置运行下面的 kubectl 命令:

KUBECONFIG=<filename> kubectl config set-cluster default-cluster --server=https://<host ip>:6443 --certificate-authority <path-to-kubernetes-ca> --embed-certs
KUBECONFIG=<filename> kubectl config set-credentials <credential-name> --client-key <path-to-key>.pem --client-certificate <path-to-cert>.pem --embed-certs
KUBECONFIG=<filename> kubectl config set-context default-system --cluster default-cluster --user <credential-name>
KUBECONFIG=<filename> kubectl config use-context default-system

这些文件用途如下:

文件名命令说明
admin.confkubectl配置集群的管理员
kubelet.confkubelet集群中的每个节点都需要一份
controller-manager.confkube-controller-manager必需添加到 manifests/kube-controller-manager.yaml 清单中
scheduler.confkube-scheduler必需添加到 manifests/kube-scheduler.yaml 清单中

下面是前表中所列文件的完整路径。

/etc/kubernetes/admin.conf
/etc/kubernetes/kubelet.conf
/etc/kubernetes/controller-manager.conf
/etc/kubernetes/scheduler.conf

2.3.5 - 强制实施 Pod 安全性标准

本页提供实施 Pod 安全标准(Pod Security Standards) 时的一些最佳实践。

使用内置的 Pod 安全性准入控制器

特性状态: Kubernetes v1.23 [beta]

Pod 安全性准入控制器 尝试替换已被废弃的 PodSecurityPolicies。

配置所有集群名字空间

完全未经配置的名字空间应该被视为集群安全模型中的重大缺陷。 我们建议花一些时间来分析在每个名字空间中执行的负载的类型, 并通过引用 Pod 安全性标准来确定每个负载的合适级别。 未设置标签的名字空间应该视为尚未被评估。

针对所有名字空间中的所有负载都具有相同的安全性需求的场景, 我们提供了一个示例 用来展示如何批量应用 Pod 安全性标签。

拥抱最小特权原则

在一个理想环境中,每个名字空间中的每个 Pod 都会满足 restricted 策略的需求。 不过,这既不可能也不现实,某些负载会因为合理的原因而需要特权上的提升。

  • 允许 privileged 负载的名字空间需要建立并实施适当的访问控制机制。
  • 对于运行在特权宽松的名字空间中的负载,需要维护其独特安全性需求的文档。 如果可能的话,要考虑如何进一步约束这些需求。

采用多种模式的策略

Pod 安全性标准准入控制器的 auditwarn 模式(mode) 能够在不影响现有负载的前提下,让该控制器更方便地收集关于 Pod 的重要的安全信息。

针对所有名字空间启用这些模式是一种好的实践,将它们设置为你最终打算 enforce期望的 级别和版本。这一阶段中所生成的警告和审计注解信息可以帮助你到达这一状态。 如果你期望负载的作者能够作出变更以便适应期望的级别,可以启用 warn 模式。 如果你希望使用审计日志了监控和驱动变更,以便负载能够适应期望的级别,可以启用 audit 模式。

当你将 enforce 模式设置为期望的取值时,这些模式在不同的场合下仍然是有用的:

  • 通过将 warn 设置为 enforce 相同的级别,客户可以在尝试创建无法通过合法检查的 Pod (或者包含 Pod 模板的资源)时收到警告信息。这些信息会帮助于更新资源使其合规。
  • 在将 enforce 锁定到特定的非最新版本的名字空间中,将 auditwarn 模式设置为 enforce 一样的级别而非 latest 版本, 这样可以方便看到之前版本所允许但当前最佳实践中被禁止的设置。

第三方替代方案

Kubernetes 生态系统中也有一些其他强制实施安全设置的替代方案处于开发状态中:

采用 内置的 方案(例如 PodSecurity 准入控制器)还是第三方工具, 这一决策完全取决于你自己的情况。在评估任何解决方案时,对供应链的信任都是至关重要的。 最终,使用前述方案中的 任何 一种都好过放任自流。

3 - 概念

概念部分可以帮助你了解 Kubernetes 的各个组成部分以及 Kubernetes 用来表示集群的一些抽象概念,并帮助你更加深入的理解 Kubernetes 是如何工作的。

3.1 - 概述

了解 Kubernetes 及其构件的高层次概要。

3.1.1 - Kubernetes 是什么?

Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,方便进行声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统,其服务、支持和工具的使用范围广泛。

此页面是 Kubernetes 的概述。

Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具的使用范围相当广泛。

Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。k8s 这个缩写是因为 k 和 s 之间有八个字符的关系。 Google 在 2014 年开源了 Kubernetes 项目。 Kubernetes 建立在Google 大规模运行生产工作负载十几年经验的基础上, 结合了社区中最优秀的想法和实践。

时光回溯

让我们回顾一下为何 Kubernetes 能够裨益四方。

部署演进

传统部署时代:

早期,各机构是在物理服务器上运行应用程序。 由于无法限制在物理服务器中运行的应用程序资源使用,因此会导致资源分配问题。 例如,如果在物理服务器上运行多个应用程序, 则可能会出现一个应用程序占用大部分资源的情况,而导致其他应用程序的性能下降。 一种解决方案是将每个应用程序都运行在不同的物理服务器上, 但是当某个应用程式资源利用率不高时,剩余资源无法被分配给其他应用程式, 而且维护许多物理服务器的成本很高。

虚拟化部署时代:

因此,虚拟化技术被引入了。虚拟化技术允许你在单个物理服务器的 CPU 上运行多台虚拟机(VM)。 虚拟化能使应用程序在不同 VM 之间被彼此隔离,且能提供一定程度的安全性, 因为一个应用程序的信息不能被另一应用程序随意访问。

虚拟化技术能够更好地利用物理服务器的资源,并且因为可轻松地添加或更新应用程序, 而因此可以具有更高的可伸缩性,以及降低硬件成本等等的好处。

每个 VM 是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统(OS)。

容器部署时代:

容器类似于 VM,但是更宽松的隔离特性,使容器之间可以共享操作系统(OS)。 因此,容器比起 VM 被认为是更轻量级的。且与 VM 类似,每个容器都具有自己的文件系统、CPU、内存、进程空间等。 由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。

容器因具有许多优势而变得流行起来。下面列出的是容器的一些好处:

  • 敏捷应用程序的创建和部署:与使用 VM 镜像相比,提高了容器镜像创建的简便性和效率。
  • 持续开发、集成和部署:通过快速简单的回滚(由于镜像不可变性), 提供可靠且频繁的容器镜像构建和部署。
  • 关注开发与运维的分离:在构建、发布时创建应用程序容器镜像,而不是在部署时, 从而将应用程序与基础架构分离。
  • 可观察性:不仅可以显示 OS 级别的信息和指标,还可以显示应用程序的运行状况和其他指标信号。
  • 跨开发、测试和生产的环境一致性:在笔记本计算机上也可以和在云中运行一样的应用程序。
  • 跨云和操作系统发行版本的可移植性:可在 Ubuntu、RHEL、CoreOS、本地、 Google Kubernetes Engine 和其他任何地方运行。
  • 以应用程序为中心的管理:提高抽象级别,从在虚拟硬件上运行 OS 到使用逻辑资源在 OS 上运行应用程序。
  • 松散耦合、分布式、弹性、解放的微服务:应用程序被分解成较小的独立部分, 并且可以动态部署和管理 - 而不是在一台大型单机上整体运行。
  • 资源隔离:可预测的应用程序性能。
  • 资源利用:高效率和高密度。

为什么需要 Kubernetes,它能做什么?

容器是打包和运行应用程序的好方式。在生产环境中, 你需要管理运行着应用程序的容器,并确保服务不会下线。 例如,如果一个容器发生故障,则你需要启动另一个容器。 如果此行为交由给系统处理,是不是会更容易一些?

这就是 Kubernetes 要来做的事情! Kubernetes 为你提供了一个可弹性运行分布式系统的框架。 Kubernetes 会满足你的扩展要求、故障转移、部署模式等。 例如,Kubernetes 可以轻松管理系统的 Canary 部署。

Kubernetes 为你提供:

  • 服务发现和负载均衡

    Kubernetes 可以使用 DNS 名称或自己的 IP 地址来曝露容器。 如果进入容器的流量很大, Kubernetes 可以负载均衡并分配网络流量,从而使部署稳定。

  • 存储编排

    Kubernetes 允许你自动挂载你选择的存储系统,例如本地存储、公共云提供商等。

  • 自动部署和回滚

    你可以使用 Kubernetes 描述已部署容器的所需状态, 它可以以受控的速率将实际状态更改为期望状态。 例如,你可以自动化 Kubernetes 来为你的部署创建新容器, 删除现有容器并将它们的所有资源用于新容器。

  • 自动完成装箱计算

    Kubernetes 允许你指定每个容器所需 CPU 和内存(RAM)。 当容器指定了资源请求时,Kubernetes 可以做出更好的决策来为容器分配资源。

  • 自我修复

    Kubernetes 将重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器, 并且在准备好服务之前不将其通告给客户端。

  • 密钥与配置管理

    Kubernetes 允许你存储和管理敏感信息,例如密码、OAuth 令牌和 ssh 密钥。 你可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥。

Kubernetes 不是什么

Kubernetes 不是传统的、包罗万象的 PaaS(平台即服务)系统。 由于 Kubernetes 是在容器级别运行,而非在硬件级别, 它提供了 PaaS 产品共有的一些普遍适用的功能, 例如部署、扩展、负载均衡、日志记录和监视。 但是,Kubernetes 不是单体式(monolithic)系统,那些默认解决方案都是可选、可插拔的。 Kubernetes 为构建开发人员平台提供了基础,但是在重要的地方保留了用户选择权,能有更高的灵活性。

Kubernetes:

  • 不限制支持的应用程序类型。 Kubernetes 旨在支持极其多种多样的工作负载,包括无状态、有状态和数据处理工作负载。 如果应用程序可以在容器中运行,那么它应该可以在 Kubernetes 上很好地运行。
  • 不部署源代码,也不构建你的应用程序。 持续集成(CI)、交付和部署(CI/CD)工作流取决于组织的文化和偏好以及技术要求。
  • 不提供应用程序级别的服务作为内置服务,例如中间件(例如消息中间件)、 数据处理框架(例如 Spark)、数据库(例如 MySQL)、缓存、集群存储系统 (例如 Ceph)。这样的组件可以在 Kubernetes 上运行,并且/或者可以由运行在 Kubernetes 上的应用程序通过可移植机制 (例如开放服务代理)来访问。
  • 不是日志记录、监视或警报的解决方案。 它集成了一些功能作为概念证明,并提供了收集和导出指标的机制。
  • 不提供也不要求配置用的语言、系统(例如 jsonnet),它提供了声明性 API, 该声明性 API 可以由任意形式的声明性规范所构成。
  • 不提供也不采用任何全面的机器配置、维护、管理或自我修复系统。
  • 此外,Kubernetes 不仅仅是一个编排系统,实际上它消除了编排的需要。 编排的技术定义是执行已定义的工作流程:首先执行 A,然后执行 B,再执行 C。 而 Kubernetes 包含了一组独立可组合的控制过程, 可以连续地将当前状态驱动到所提供的预期状态。 你不需要在乎如何从 A 移动到 C,也不需要集中控制,这使得系统更易于使用 且功能更强大、系统更健壮,更为弹性和可扩展。

接下来

3.1.2 - Kubernetes 组件

Kubernetes 集群由代表控制平面的组件和一组称为节点的机器组成。

当你部署完 Kubernetes,便拥有了一个完整的集群。

一个 Kubernetes 集群是由一组被称作节点(node)的机器组成, 这些节点上会运行由 Kubernetes 所管理的容器化应用。 且每个集群至少有一个工作节点。

工作节点会托管所谓的 Pods,而 Pod 就是作为应用负载的组件。 控制平面管理集群中的工作节点和 Pods。 为集群提供故障转移和高可用性, 这些控制平面一般跨多主机运行,而集群也会跨多个节点运行。

本文档概述了一个正常运行的 Kubernetes 集群所需的各种组件。

Kubernetes 的组件

Kubernetes 集群的组件

控制平面组件(Control Plane Components)

控制平面组件会为集群做出全局决策,比如资源的调度。 以及检测和响应集群事件,例如当不满足部署的 replicas 字段时, 要启动新的 pod)。

控制平面组件可以在集群中的任何节点上运行。 然而,为了简单起见,设置脚本通常会在同一个计算机上启动所有控制平面组件, 并且不会在此计算机上运行用户容器。 请参阅使用 kubeadm 构建高可用性集群 中关于跨多机器控制平面设置的示例。

kube-apiserver

API 服务器是 Kubernetes 控制平面的组件, 该组件负责公开了 Kubernetes API,负责处理接受请求的工作。 API 服务器是 Kubernetes 控制平面的前端。

Kubernetes API 服务器的主要实现是 kube-apiserverkube-apiserver 设计上考虑了水平扩缩,也就是说,它可通过部署多个实例来进行扩缩。 你可以运行 kube-apiserver 的多个实例,并在这些实例之间平衡流量。

etcd

etcd 是兼顾一致性与高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。

你的 Kubernetes 集群的 etcd 数据库通常需要有个备份计划。

如果想要更深入的了解 etcd,请参考 etcd 文档

kube-scheduler

kube-scheduler控制平面的组件, 负责监视新创建的、未指定运行节点(node)Pods, 并选择节点来让 Pod 在上面运行。

调度决策考虑的因素包括单个 Pod 及 Pods 集合的资源需求、软硬件及策略约束、 亲和性及反亲和性规范、数据位置、工作负载间的干扰及最后时限。

kube-controller-manager

kube-controller-manager控制平面的组件, 负责运行控制器进程。

从逻辑上讲, 每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在同一个进程中运行。

这些控制器包括:

  • 节点控制器(Node Controller):负责在节点出现故障时进行通知和响应
  • 任务控制器(Job Controller):监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成
  • 端点控制器(Endpoints Controller):填充端点(Endpoints)对象(即加入 Service 与 Pod)
  • 服务帐户和令牌控制器(Service Account & Token Controllers):为新的命名空间创建默认帐户和 API 访问令牌

cloud-controller-manager

cloud-controller-manager 是指嵌入特定云的控制逻辑之 控制平面组件。 cloud-controller-manager 允许你将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。

cloud-controller-manager 仅运行特定于云平台的控制器。 因此如果你在自己的环境中运行 Kubernetes,或者在本地计算机中运行学习环境, 所部署的集群不需要有云控制器管理器。

kube-controller-manager 类似,cloud-controller-manager 将若干逻辑上独立的控制回路组合到同一个可执行文件中, 供你以同一进程的方式运行。 你可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。

下面的控制器都包含对云平台驱动的依赖:

  • 节点控制器(Node Controller):用于在节点终止响应后检查云提供商以确定节点是否已被删除
  • 路由控制器(Route Controller):用于在底层云基础架构中设置路由
  • 服务控制器(Service Controller):用于创建、更新和删除云提供商负载均衡器

Node 组件

节点组件会在每个节点上运行,负责维护运行的 Pod 并提供 Kubernetes 运行环境。

kubelet

kubelet 会在集群中每个节点(node)上运行。 它保证容器(containers)都运行在 Pod 中。

kubelet 接收一组通过各类机制提供给它的 PodSpecs, 确保这些 PodSpecs 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。

kube-proxy

kube-proxy 是集群中每个节点(node)所上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。

kube-proxy 维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。

如果操作系统提供了可用的数据包过滤层,则 kube-proxy 会通过它来实现网络规则。 否则,kube-proxy 仅做流量转发。

容器运行时(Container Runtime)

容器运行环境是负责运行容器的软件。

Kubernetes 支持许多容器运行环境,例如 DockercontainerdCRI-O 以及 Kubernetes CRI (容器运行环境接口) 的其他任何实现。

插件(Addons)

插件使用 Kubernetes 资源(DaemonSetDeployment 等)实现集群功能。 因为这些插件提供集群级别的功能,插件中命名空间域的资源属于 kube-system 命名空间。

下面描述众多插件中的几种。有关可用插件的完整列表,请参见 插件(Addons)

DNS

尽管其他插件都并非严格意义上的必需组件,但几乎所有 Kubernetes 集群都应该 有集群 DNS, 因为很多示例都需要 DNS 服务。

集群 DNS 是一个 DNS 服务器,和环境中的其他 DNS 服务器一起工作,它为 Kubernetes 服务提供 DNS 记录。

Kubernetes 启动的容器自动将此 DNS 服务器包含在其 DNS 搜索列表中。

Web 界面(仪表盘)

Dashboard 是 Kubernetes 集群的通用的、基于 Web 的用户界面。 它使用户可以管理集群中运行的应用程序以及集群本身, 并进行故障排除。

容器资源监控

容器资源监控 将关于容器的一些常见的时间序列度量值保存到一个集中的数据库中, 并提供浏览这些数据的界面。

集群层面日志

集群层面日志 机制负责将容器的日志数据保存到一个集中的日志存储中, 这种集中日志存储提供搜索和浏览接口。

接下来

3.1.3 - Kubernetes API

Kubernetes API 使你可以查询和操纵 Kubernetes 中对象的状态。 Kubernetes 控制平面的核心是 API 服务器和它暴露的 HTTP API。 用户、集群的不同部分以及外部组件都通过 API 服务器相互通信。

Kubernetes 控制面 的核心是 API 服务器。 API 服务器负责提供 HTTP API,以供用户、集群中的不同部分和集群外部组件相互通信。

Kubernetes API 使你可以查询和操纵 Kubernetes API 中对象(例如:Pod、Namespace、ConfigMap 和 Event)的状态。

大部分操作都可以通过 kubectl 命令行接口或 类似 kubeadm 这类命令行工具来执行, 这些工具在背后也是调用 API。不过,你也可以使用 REST 调用来访问这些 API。

如果你正在编写程序来访问 Kubernetes API,可以考虑使用 客户端库之一。

OpenAPI 规范

完整的 API 细节是用 OpenAPI 来表述的。

OpenAPI V2

Kubernetes API 服务器通过 /openapi/v2 端点提供聚合的 OpenAPI v2 规范。 你可以按照下表所给的请求头部,指定响应的格式:

头部可选值说明
Accept-Encodinggzip不指定此头部也是可以的
Acceptapplication/com.github.proto-openapi.spec.v2@v1.0+protobuf主要用于集群内部
application/json默认值
*提供application/json
OpenAPI v2 查询请求的合法头部值

Kubernetes 为 API 实现了一种基于 Protobuf 的序列化格式,主要用于集群内部通信。 关于此格式的详细信息,可参考 Kubernetes Protobuf 序列化 设计提案。每种模式对应的接口描述语言(IDL)位于定义 API 对象的 Go 包中。

OpenAPI V3

特性状态: Kubernetes v1.24 [beta]

Kubernetes v1.24 提供将其 API 以 OpenAPI v3 形式发布的 beta 支持; 这一功能特性处于 beta 状态,默认被开启。 你可以通过为 kube-apiserver 组件关闭 OpenAPIV3 特性门控来禁用此 beta 特性。

发现端点 /openapi/v3 被提供用来查看可用的所有组、版本列表。 此列表仅返回 JSON。这些组、版本以下面的格式提供:

{
    "paths": {
        ...
        "api/v1": {
            "serverRelativeURL": "/openapi/v3/api/v1?hash=CC0E9BFD992D8C59AEC98A1E2336F899E8318D3CF4C68944C3DEC640AF5AB52D864AC50DAA8D145B3494F75FA3CFF939FCBDDA431DAD3CA79738B297795818CF"
        },
        "apis/admissionregistration.k8s.io/v1": {
            "serverRelativeURL": "/openapi/v3/apis/admissionregistration.k8s.io/v1?hash=E19CC93A116982CE5422FC42B590A8AFAD92CDE9AE4D59B5CAAD568F083AD07946E6CB5817531680BCE6E215C16973CD39003B0425F3477CFD854E89A9DB6597"
        },
        ...
}

为了改进客户端缓存,相对的 URL 会指向不可变的 OpenAPI 描述。 为了此目的,API 服务器也会设置正确的 HTTP 缓存标头 (Expires 为未来 1 年,和 Cache-Controlimmutable)。 当一个过时的 URL 被使用时,API 服务器会返回一个指向最新 URL 的重定向。

Kubernetes API 服务器会在端点 /openapi/v3/apis/<group>/<version>?hash=<hash> 发布一个 Kubernetes 组版本的 OpenAPI v3 规范。

请参阅下表了解可接受的请求头部。

OpenAPI v3 查询的合法请求头部值
头部可选值说明
Accept-Encodinggzip不提供此头部也是可接受的
Acceptapplication/com.github.proto-openapi.spec.v3@v1.0+protobuf主要用于集群内部使用
application/json默认
* application/json 形式返回

API 变更

任何成功的系统都要随着新的使用案例的出现和现有案例的变化来成长和变化。 为此,Kubernetes 的功能特性设计考虑了让 Kubernetes API 能够持续变更和成长的因素。 Kubernetes 项目的目标是 不要 引发现有客户端的兼容性问题,并在一定的时期内 维持这种兼容性,以便其他项目有机会作出适应性变更。

一般而言,新的 API 资源和新的资源字段可以被频繁地添加进来。 删除资源或者字段则要遵从 API 废弃策略

Kubernetes 对维护达到正式发布(GA)阶段的官方 API 的兼容性有着很强的承诺, 通常这一 API 版本为 v1。此外,Kubernetes 在可能的时候还会保持 Beta API 版本的兼容性:如果你采用了 Beta API,你可以继续在集群上使用该 API, 即使该功能特性已进入稳定期也是如此。

关于 API 版本分级的定义细节,请参阅 API 版本参考页面。

API 扩展

有两种途径来扩展 Kubernetes API:

  1. 你可以使用自定义资源 来以声明式方式定义 API 服务器如何提供你所选择的资源 API。
  2. 你也可以选择实现自己的 聚合层 来扩展 Kubernetes API。

接下来

3.1.4 - 使用 Kubernetes 对象

Kubernetes 对象是 Kubernetes 系统中的持久性实体。Kubernetes 使用这些实体表示你的集群状态。 了解 Kubernetes 对象模型以及如何使用这些对象。

3.1.4.1 - 理解 Kubernetes 对象

本页说明了在 Kubernetes API 中是如何表示 Kubernetes 对象的, 以及使用 .yaml 格式的文件表示 Kubernetes 对象。

理解 Kubernetes 对象

在 Kubernetes 系统中,Kubernetes 对象 是持久化的实体。 Kubernetes 使用这些实体去表示整个集群的状态。 比较特别地是,它们描述了如下信息:

  • 哪些容器化应用正在运行(以及在哪些节点上运行)
  • 可以被应用使用的资源
  • 关于应用运行时表现的策略,比如重启策略、升级策略以及容错策略

Kubernetes 对象是“目标性记录” —— 一旦创建该对象,Kubernetes 系统将不断工作以确保该对象存在。 通过创建对象,你就是在告知 Kubernetes 系统,你想要的集群工作负载状态看起来应是什么样子的, 这就是 Kubernetes 集群所谓的 期望状态(Desired State)

操作 Kubernetes 对象 —— 无论是创建、修改或者删除 —— 需要使用 Kubernetes API。 比如,当使用 kubectl 命令行接口(CLI)时,CLI 会调用必要的 Kubernetes API; 也可以在程序中使用客户端库, 来直接调用 Kubernetes API。

对象规约(Spec)与状态(Status)

几乎每个 Kubernetes 对象包含两个嵌套的对象字段,它们负责管理对象的配置: 对象 spec(规约) 和 对象 status(状态)。 对于具有 spec 的对象,你必须在创建对象时设置其内容,描述你希望对象所具有的特征: 期望状态(Desired State)

status 描述了对象的当前状态(Current State),它是由 Kubernetes 系统和组件设置并更新的。 在任何时刻,Kubernetes 控制平面 都一直都在积极地管理着对象的实际状态,以使之达成期望状态。

例如,Kubernetes 中的 Deployment 对象能够表示运行在集群中的应用。 当创建 Deployment 时,可能会去设置 Deployment 的 spec,以指定该应用要有 3 个副本运行。 Kubernetes 系统读取 Deployment 的 spec, 并启动我们所期望的应用的 3 个实例 —— 更新状态以与规约相匹配。 如果这些实例中有的失败了(一种状态变更),Kubernetes 系统会通过执行修正操作来响应 spec 和状态间的不一致 —— 意味着它会启动一个新的实例来替换。

关于对象 spec、status 和 metadata 的更多信息,可参阅 Kubernetes API 约定

描述 Kubernetes 对象

创建 Kubernetes 对象时,必须提供对象的 spec,用来描述该对象的期望状态, 以及关于对象的一些基本信息(例如名称)。 当使用 Kubernetes API 创建对象时(直接创建,或经由 kubectl), API 请求必须在请求本体中包含 JSON 格式的信息。 大多数情况下,你需要提供 .yaml 文件为 kubectl 提供这些信息kubectl 在发起 API 请求时,将这些信息转换成 JSON 格式。

这里有一个 .yaml 示例文件,展示了 Kubernetes Deployment 的必需字段和对象 spec

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # 告知 Deployment 运行 2 个与该模板匹配的 Pod
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

相较于上面使用 .yaml 文件来创建 Deployment,另一种类似的方式是使用 kubectl 命令行接口(CLI)中的 kubectl apply 命令, 将 .yaml 文件作为参数。下面是一个示例:

kubectl apply -f https://k8s.io/examples/application/deployment.yaml

输出类似下面这样:

deployment.apps/nginx-deployment created

必需字段

在想要创建的 Kubernetes 对象所对应的 .yaml 文件中,需要配置的字段如下:

  • apiVersion - 创建该对象所使用的 Kubernetes API 的版本
  • kind - 想要创建的对象的类别
  • metadata - 帮助唯一标识对象的一些数据,包括一个 name 字符串、UID 和可选的 namespace
  • spec - 你所期望的该对象的状态

对每个 Kubernetes 对象而言,其 spec 之精确格式都是不同的,包含了特定于该对象的嵌套字段。 我们能在 Kubernetes API 参考 找到我们想要在 Kubernetes 上创建的任何对象的规约格式。

例如,参阅 Pod API 参考文档中 spec 字段。 对于每个 Pod,其 .spec 字段设置了 Pod 及其期望状态(例如 Pod 中每个容器的容器镜像名称)。 另一个对象规约的例子是 StatefulSet API 中的 spec 字段。 对于 StatefulSet 而言,其 .spec 字段设置了 StatefulSet 及其期望状态。 在 StatefulSet 的 .spec 内,有一个为 Pod 对象提供的模板。 该模板描述了 StatefulSet 控制器为了满足 StatefulSet 规约而要创建的 Pod。 不同类型的对象可以由不同的 .status 信息。API 参考页面给出了 .status 字段的详细结构, 以及针对不同类型 API 对象的具体内容。

接下来

3.1.4.2 - Kubernetes 对象管理

kubectl 命令行工具支持多种不同的方式来创建和管理 Kubernetes 对象。 本文档概述了不同的方法。 阅读 Kubectl book 来了解 kubectl 管理对象的详细信息。

管理技巧

管理技术作用于建议的环境支持的写者学习难度
指令式命令活跃对象开发项目1+最低
指令式对象配置单个文件生产项目1中等
声明式对象配置文件目录生产项目1+最高

指令式命令

使用指令式命令时,用户可以在集群中的活动对象上进行操作。用户将操作传给 kubectl 命令作为参数或标志。

这是开始或者在集群中运行一次性任务的推荐方法。因为这个技术直接在活跃对象 上操作,所以它不提供以前配置的历史记录。

例子

通过创建 Deployment 对象来运行 nginx 容器的实例:

kubectl create deployment nginx --image nginx

权衡

与对象配置相比的优点:

  • 命令简单,易学且易于记忆。
  • 命令仅需一步即可对集群进行更改。

与对象配置相比的缺点:

  • 命令不与变更审查流程集成。
  • 命令不提供与更改关联的审核跟踪。
  • 除了实时内容外,命令不提供记录源。
  • 命令不提供用于创建新对象的模板。

指令式对象配置

在指令式对象配置中,kubectl 命令指定操作(创建,替换等),可选标志和 至少一个文件名。指定的文件必须包含 YAML 或 JSON 格式的对象的完整定义。

有关对象定义的详细信息,请查看 API 参考

例子

创建配置文件中定义的对象:

kubectl create -f nginx.yaml

删除两个配置文件中定义的对象:

kubectl delete -f nginx.yaml -f redis.yaml

通过覆盖活动配置来更新配置文件中定义的对象:

kubectl replace -f nginx.yaml

权衡

与指令式命令相比的优点:

  • 对象配置可以存储在源控制系统中,比如 Git。
  • 对象配置可以与流程集成,例如在推送和审计之前检查更新。
  • 对象配置提供了用于创建新对象的模板。

与指令式命令相比的缺点:

  • 对象配置需要对对象架构有基本的了解。
  • 对象配置需要额外的步骤来编写 YAML 文件。

与声明式对象配置相比的优点:

  • 指令式对象配置行为更加简单易懂。
  • 从 Kubernetes 1.5 版本开始,指令对象配置更加成熟。

与声明式对象配置相比的缺点:

  • 指令式对象配置更适合文件,而非目录。
  • 对活动对象的更新必须反映在配置文件中,否则会在下一次替换时丢失。

声明式对象配置

使用声明式对象配置时,用户对本地存储的对象配置文件进行操作,但是用户 未定义要对该文件执行的操作。 kubectl 会自动检测每个文件的创建、更新和删除操作。 这使得配置可以在目录上工作,根据目录中配置文件对不同的对象执行不同的操作。

例子

处理 configs 目录中的所有对象配置文件,创建并更新活跃对象。 可以首先使用 diff 子命令查看将要进行的更改,然后在进行应用:

kubectl diff -f configs/
kubectl apply -f configs/

递归处理目录:

kubectl diff -R -f configs/
kubectl apply -R -f configs/

权衡

与指令式对象配置相比的优点:

  • 对活动对象所做的更改即使未合并到配置文件中,也会被保留下来。
  • 声明性对象配置更好地支持对目录进行操作并自动检测每个文件的操作类型(创建,修补,删除)。

与指令式对象配置相比的缺点:

  • 声明式对象配置难于调试并且出现异常时结果难以理解。
  • 使用 diff 产生的部分更新会创建复杂的合并和补丁操作。

接下来

3.1.4.3 - 对象名称和 IDs

集群中的每一个对象都有一个[名称](#names)来标识在同类资源中的唯一性。

每个 Kubernetes 对象也有一个 [UID](#uids)来标识在整个集群中的唯一性。

比如,在同一个名字空间 中有一个名为 myapp-1234 的 Pod,但是可以命名一个 Pod 和一个 Deployment 同为 myapp-1234

对于用户提供的非唯一性的属性,Kubernetes 提供了 标签(Labels)注解(Annotation)机制。

名称

客户端提供的字符串,引用资源 url 中的对象,如/api/v1/pods/some name

某一时刻,只能有一个给定类型的对象具有给定的名称。但是,如果删除该对象,则可以创建同名的新对象。

以下是比较常见的四种资源命名约束。

DNS 子域名

很多资源类型需要可以用作 DNS 子域名的名称。 DNS 子域名的定义可参见 RFC 1123。 这一要求意味着名称必须满足如下规则:

  • 不能超过 253 个字符
  • 只能包含小写字母、数字,以及 '-' 和 '.'
  • 必须以字母数字开头
  • 必须以字母数字结尾

RFC 1123 标签名

某些资源类型需要其名称遵循 RFC 1123 所定义的 DNS 标签标准。也就是命名必须满足如下规则:

  • 最多 63 个字符
  • 只能包含小写字母、数字,以及 '-'
  • 必须以字母数字开头
  • 必须以字母数字结尾

RFC 1035 标签名

某些资源类型需要其名称遵循 RFC 1035 所定义的 DNS 标签标准。也就是命名必须满足如下规则:

  • 最多 63 个字符
  • 只能包含小写字母、数字,以及 '-'
  • 必须以字母开头
  • 必须以字母数字结尾

路径分段名称

某些资源类型要求名称能被安全地用作路径中的片段。 换句话说,其名称不能是 ...,也不可以包含 /% 这些字符。

下面是一个名为 nginx-demo 的 Pod 的配置清单:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-demo
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

UIDs

Kubernetes 系统生成的字符串,唯一标识对象。

在 Kubernetes 集群的整个生命周期中创建的每个对象都有一个不同的 uid,它旨在区分类似实体的历史事件。

Kubernetes UID 是全局唯一标识符(也叫 UUIDs)。 UUIDs 是标准化的,见 ISO/IEC 9834-8 和 ITU-T X.667。

接下来

3.1.4.4 - 名字空间

在 Kubernetes 中,名字空间(Namespace) 提供一种机制,将同一集群中的资源划分为相互隔离的组。 同一名字空间内的资源名称要唯一,但跨名字空间时没有这个要求。 名字空间作用域仅针对带有名字空间的对象,例如 Deployment、Service 等, 这种作用域对集群访问的对象不适用,例如 StorageClass、Node、PersistentVolume 等。

何时使用多个名字空间

名字空间适用于存在很多跨多个团队或项目的用户的场景。对于只有几到几十个用户的集群,根本不需要创建或考虑名字空间。当需要名字空间提供的功能时,请开始使用它们。

名字空间为名称提供了一个范围。资源的名称需要在名字空间内是唯一的,但不能跨名字空间。 名字空间不能相互嵌套,每个 Kubernetes 资源只能在一个名字空间中。

名字空间是在多个用户之间划分集群资源的一种方法(通过资源配额)。

不必使用多个名字空间来分隔仅仅轻微不同的资源,例如同一软件的不同版本: 应该使用标签 来区分同一名字空间中的不同资源。

使用名字空间

名字空间的创建和删除在名字空间的管理指南文档描述。

查看名字空间

你可以使用以下命令列出集群中现存的名字空间:

kubectl get namespace
NAME              STATUS   AGE
default           Active   1d
kube-node-lease   Active   1d
kube-public       Active   1d
kube-system       Active   1d

Kubernetes 会创建四个初始名字空间:

  • default 没有指明使用其它名字空间的对象所使用的默认名字空间
  • kube-system Kubernetes 系统创建对象所使用的名字空间
  • kube-public 这个名字空间是自动创建的,所有用户(包括未经过身份验证的用户)都可以读取它。 这个名字空间主要用于集群使用,以防某些资源在整个集群中应该是可见和可读的。 这个名字空间的公共方面只是一种约定,而不是要求。
  • kube-node-lease 此名字空间用于与各个节点相关的 租约(Lease)对象。 节点租期允许 kubelet 发送心跳,由此控制面能够检测到节点故障。

为请求设置名字空间

要为当前请求设置名字空间,请使用 --namespace 参数。

例如:

kubectl run nginx --image=nginx --namespace=<名字空间名称>
kubectl get pods --namespace=<名字空间名称>

设置名字空间偏好

你可以永久保存名字空间,以用于对应上下文中所有后续 kubectl 命令。

kubectl config set-context --current --namespace=<名字空间名称>
# 验证
kubectl config view --minify | grep namespace:

名字空间和 DNS

当你创建一个服务时, Kubernetes 会创建一个相应的 DNS 条目

该条目的形式是 <服务名称>.<名字空间名称>.svc.cluster.local,这意味着如果容器只使用 <服务名称>,它将被解析到本地名字空间的服务。这对于跨多个名字空间(如开发、测试和生产) 使用相同的配置非常有用。如果你希望跨名字空间访问,则需要使用完全限定域名(FQDN)。

因此,所有的名字空间名称都必须是合法的 RFC 1123 DNS 标签

并非所有对象都在名字空间中

大多数 kubernetes 资源(例如 Pod、Service、副本控制器等)都位于某些名字空间中。 但是名字空间资源本身并不在名字空间中。而且底层资源, 例如节点和持久化卷不属于任何名字空间。

查看哪些 Kubernetes 资源在名字空间中,哪些不在名字空间中:

# 位于名字空间中的资源
kubectl api-resources --namespaced=true

# 不在名字空间中的资源
kubectl api-resources --namespaced=false

自动打标签

特性状态: Kubernetes 1.21 [beta]

Kubernetes 控制面会为所有名字空间设置一个不可变更的 标签 kubernetes.io/metadata.name,只要 NamespaceDefaultLabelName 这一特性门控被启用。 标签的值是名字空间的名称。

接下来

3.1.4.5 - 标签和选择算符

**标签(Labels)**是附加到 Kubernetes 对象(比如 Pods)上的键值对。 标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。 标签可以用于组织和选择对象的子集。标签可以在创建时附加到对象,随后可以随时添加和修改。 每个对象都可以定义一组键/值标签。每个键对于给定对象必须是唯一的。

"metadata": {
  "labels": {
    "key1" : "value1",
    "key2" : "value2"
  }
}

标签能够支持高效的查询和监听操作,对于用户界面和命令行是很理想的。 应使用注解记录非识别信息。

动机

标签使用户能够以松散耦合的方式将他们自己的组织结构映射到系统对象,而无需客户端存储这些映射。

服务部署和批处理流水线通常是多维实体(例如,多个分区或部署、多个发行序列、多个层,每层多个微服务)。 管理通常需要交叉操作,这打破了严格的层次表示的封装,特别是由基础设施而不是用户确定的严格的层次结构。

示例标签:

  • "release" : "stable", "release" : "canary"
  • "environment" : "dev", "environment" : "qa", "environment" : "production"
  • "tier" : "frontend", "tier" : "backend", "tier" : "cache"
  • "partition" : "customerA", "partition" : "customerB"
  • "track" : "daily", "track" : "weekly"

有一些常用标签的例子;你可以任意制定自己的约定。 请记住,标签的 Key 对于给定对象必须是唯一的。

语法和字符集

标签是键值对。有效的标签键有两个段:可选的前缀和名称,用斜杠(/)分隔。 名称段是必需的,必须小于等于 63 个字符,以字母数字字符([a-z0-9A-Z])开头和结尾, 带有破折号(-),下划线(_),点( .)和之间的字母数字。 前缀是可选的。如果指定,前缀必须是 DNS 子域:由点(.)分隔的一系列 DNS 标签,总共不超过 253 个字符, 后跟斜杠(/)。

如果省略前缀,则假定标签键对用户是私有的。 向最终用户对象添加标签的自动系统组件(例如 kube-schedulerkube-controller-managerkube-apiserverkubectl 或其他第三方自动化工具)必须指定前缀。

kubernetes.io/k8s.io/ 前缀是为 Kubernetes 核心组件保留的

有效标签值:

  • 必须为 63 个字符或更少(可以为空)
  • 除非标签值为空,必须以字母数字字符([a-z0-9A-Z])开头和结尾
  • 包含破折号(-)、下划线(_)、点(.)和字母或数字

例如,这是一个有 environment: productionapp: nginx 标签的 Pod 配置文件:


apiVersion: v1
kind: Pod
metadata:
  name: label-demo
  labels:
    environment: production
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

标签选择算符

名称和 UID 不同, 标签不支持唯一性。通常,我们希望许多对象携带相同的标签。

通过标签选择算符,客户端/用户可以识别一组对象。标签选择算符是 Kubernetes 中的核心分组原语。

API 目前支持两种类型的选择算符:基于等值的基于集合的。 标签选择算符可以由逗号分隔的多个需求组成。 在多个需求的情况下,必须满足所有要求,因此逗号分隔符充当逻辑&&)运算符。

空标签选择算符或者未指定的选择算符的语义取决于上下文, 支持使用选择算符的 API 类别应该将算符的合法性和含义用文档记录下来。

基于等值的需求

基于等值基于不等值的需求允许按标签键和值进行过滤。 匹配对象必须满足所有指定的标签约束,尽管它们也可能具有其他标签。 可接受的运算符有 ===!= 三种。 前两个表示相等(并且是同义词),而后者表示不相等。例如:

environment = production
tier != frontend

前者选择所有资源,其键名等于 environment,值等于 production。 后者选择所有资源,其键名等于 tier,值不同于 frontend,所有资源都没有带有 tier 键的标签。 可以使用逗号运算符来过滤 production 环境中的非 frontend 层资源:environment=production,tier!=frontend

基于等值的标签要求的一种使用场景是 Pod 要指定节点选择标准。 例如,下面的示例 Pod 选择带有标签 "accelerator=nvidia-tesla-p100"。

apiVersion: v1
kind: Pod
metadata:
  name: cuda-test
spec:
  containers:
    - name: cuda-test
      image: "k8s.gcr.io/cuda-vector-add:v0.1"
      resources:
        limits:
          nvidia.com/gpu: 1
  nodeSelector:
    accelerator: nvidia-tesla-p100

基于集合的需求

基于集合的标签需求允许你通过一组值来过滤键。 支持三种操作符:innotinexists(只可以用在键标识符上)。例如:

environment in (production, qa)
tier notin (frontend, backend)
partition
!partition
  • 第一个示例选择了所有键等于 environment 并且值等于 production 或者 qa 的资源。
  • 第二个示例选择了所有键等于 tier 并且值不等于 frontend 或者 backend 的资源,以及所有没有 tier 键标签的资源。
  • 第三个示例选择了所有包含了有 partition 标签的资源;没有校验它的值。
  • 第四个示例选择了所有没有 partition 标签的资源;没有校验它的值。

类似地,逗号分隔符充当 运算符。因此,使用 partition 键(无论为何值)和 environment 不同于 qa 来过滤资源可以使用 partition, environment notin (qa) 来实现。

基于集合的标签选择算符是相等标签选择算符的一般形式,因为 environment=production 等同于 environment in (production)!=notin 也是类似的。

基于集合的要求可以与基于相等的要求混合使用。例如:partition in (customerA, customerB),environment!=qa

API

LIST 和 WATCH 过滤

LIST 和 WATCH 操作可以使用查询参数指定标签选择算符过滤一组对象。 两种需求都是允许的。(这里显示的是它们出现在 URL 查询字符串中)

  • 基于等值的需求:?labelSelector=environment%3Dproduction,tier%3Dfrontend
  • 基于集合的需求:?labelSelector=environment+in+%28production%2Cqa%29%2Ctier+in+%28frontend%29

两种标签选择算符都可以通过 REST 客户端用于 list 或者 watch 资源。 例如,使用 kubectl 定位 apiserver,可以使用基于等值的标签选择算符可以这么写:

kubectl get pods -l environment=production,tier=frontend

或者使用基于集合的需求:

kubectl get pods -l 'environment in (production),tier in (frontend)'

正如刚才提到的,基于集合的需求更具有表达力。例如,它们可以实现值的操作:

kubectl get pods -l 'environment in (production, qa)'

或者通过exists运算符限制不匹配:

kubectl get pods -l 'environment,environment notin (frontend)'

在 API 对象中设置引用

一些 Kubernetes 对象,例如 servicesreplicationcontrollers, 也使用了标签选择算符去指定了其他资源的集合,例如 pods

Service 和 ReplicationController

一个 Service 指向的一组 Pods 是由标签选择算符定义的。同样,一个 ReplicationController 应该管理的 pods 的数量也是由标签选择算符定义的。

两个对象的标签选择算符都是在 json 或者 yaml 文件中使用映射定义的,并且只支持 基于等值需求的选择算符:

"selector": {
    "component" : "redis",
}

或者

selector:
    component: redis

这个选择算符(分别在 json 或者 yaml 格式中)等价于 component=rediscomponent in (redis)

支持基于集合需求的资源

比较新的资源,例如 JobDeploymentReplicaSetDaemonSet, 也支持基于集合的需求。

selector:
  matchLabels:
    component: redis
  matchExpressions:
    - {key: tier, operator: In, values: [cache]}
    - {key: environment, operator: NotIn, values: [dev]}

matchLabels 是由 {key,value} 对组成的映射。 matchLabels 映射中的单个 {key,value} 等同于 matchExpressions 的元素, 其 key 字段为 "key",operator 为 "In",而 values 数组仅包含 "value"。 matchExpressions 是 Pod 选择算符需求的列表。 有效的运算符包括 InNotInExistsDoesNotExist。 在 InNotIn 的情况下,设置的值必须是非空的。 来自 matchLabelsmatchExpressions 的所有要求都按逻辑与的关系组合到一起 -- 它们必须都满足才能匹配。

选择节点集

通过标签进行选择的一个用例是确定节点集,方便 Pod 调度。 有关更多信息,请参阅选择节点文档。

3.1.4.6 - 注解

你可以使用 Kubernetes 注解为对象附加任意的非标识的元数据。客户端程序(例如工具和库)能够获取这些元数据信息。

为对象附加元数据

你可以使用标签或注解将元数据附加到 Kubernetes 对象。 标签可以用来选择对象和查找满足某些条件的对象集合。 相反,注解不用于标识和选择对象。 注解中的元数据,可以很小,也可以很大,可以是结构化的,也可以是非结构化的,能够包含标签不允许的字符。

注解和标签一样,是键/值对:

"metadata": {
  "annotations": {
    "key1" : "value1",
    "key2" : "value2"
  }
}

以下是一些例子,用来说明哪些信息可以使用注解来记录:

  • 由声明性配置所管理的字段。 将这些字段附加为注解,能够将它们与客户端或服务端设置的默认值、 自动生成的字段以及通过自动调整大小或自动伸缩系统设置的字段区分开来。
  • 构建、发布或镜像信息(如时间戳、发布 ID、Git 分支、PR 数量、镜像哈希、仓库地址)。
  • 指向日志记录、监控、分析或审计仓库的指针。
  • 可用于调试目的的客户端库或工具信息:例如,名称、版本和构建信息。

  • 用户或者工具/系统的来源信息,例如来自其他生态系统组件的相关对象的 URL。

  • 轻量级上线工具的元数据信息:例如,配置或检查点。

  • 负责人员的电话或呼机号码,或指定在何处可以找到该信息的目录条目,如团队网站。

  • 从用户到最终运行的指令,以修改行为或使用非标准功能。

你可以将这类信息存储在外部数据库或目录中而不使用注解, 但这样做就使得开发人员很难生成用于部署、管理、自检的客户端共享库和工具。

语法和字符集

注解(Annotations) 存储的形式是键/值对。有效的注解键分为两部分: 可选的前缀和名称,以斜杠(/)分隔。 名称段是必需项,并且必须在 63 个字符以内,以字母数字字符([a-z0-9A-Z])开头和结尾, 并允许使用破折号(-),下划线(_),点(.)和字母数字。 前缀是可选的。如果指定,则前缀必须是 DNS 子域:一系列由点(.)分隔的 DNS 标签, 总计不超过 253 个字符,后跟斜杠(/)。 如果省略前缀,则假定注解键对用户是私有的。 由系统组件添加的注解 (例如,kube-schedulerkube-controller-managerkube-apiserverkubectl 或其他第三方组件),必须为终端用户添加注解前缀。

kubernetes.io/k8s.io/ 前缀是为 Kubernetes 核心组件保留的。

例如,下面是一个 Pod 的配置文件,其注解中包含 imageregistry: https://hub.docker.com/

apiVersion: v1
kind: Pod
metadata:
  name: annotations-demo
  annotations:
    imageregistry: "https://hub.docker.com/"
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80

接下来

3.1.4.7 - Finalizers

Finalizer 是带有命名空间的键,告诉 Kubernetes 等到特定的条件被满足后, 再完全删除被标记为删除的资源。 Finalizer 提醒控制器清理被删除的对象拥有的资源。

当你告诉 Kubernetes 删除一个指定了 Finalizer 的对象时, Kubernetes API 通过填充 .metadata.deletionTimestamp 来标记要删除的对象, 并返回202状态码 (HTTP "已接受") 使其进入只读状态。 此时控制平面或其他组件会采取 Finalizer 所定义的行动, 而目标对象仍然处于终止中(Terminating)的状态。 这些行动完成后,控制器会删除目标对象相关的 Finalizer。 当 metadata.finalizers 字段为空时,Kubernetes 认为删除已完成。

你可以使用 Finalizer 控制资源的垃圾收集。 例如,你可以定义一个 Finalizer,在删除目标资源前清理相关资源或基础设施。

你可以通过使用 Finalizers 提醒控制器 在删除目标资源前执行特定的清理任务, 来控制资源的垃圾收集

Finalizers 通常不指定要执行的代码。 相反,它们通常是特定资源上的键的列表,类似于注解。 Kubernetes 自动指定了一些 Finalizers,但你也可以指定你自己的。

Finalizers 如何工作

当你使用清单文件创建资源时,你可以在 metadata.finalizers 字段指定 Finalizers。 当你试图删除该资源时,处理删除请求的 API 服务器会注意到 finalizers 字段中的值, 并进行以下操作:

  • 修改对象,将你开始执行删除的时间添加到 metadata.deletionTimestamp 字段。
  • 禁止对象被删除,直到其 metadata.finalizers 字段为空。
  • 返回 202 状态码(HTTP "Accepted")。

管理 finalizer 的控制器注意到对象上发生的更新操作,对象的 metadata.deletionTimestamp 被设置,意味着已经请求删除该对象。然后,控制器会试图满足资源的 Finalizers 的条件。 每当一个 Finalizer 的条件被满足时,控制器就会从资源的 finalizers 字段中删除该键。 当 finalizers 字段为空时,deletionTimestamp 字段被设置的对象会被自动删除。 你也可以使用 Finalizers 来阻止删除未被管理的资源。

一个常见的 Finalizer 的例子是 kubernetes.io/pv-protection, 它用来防止意外删除 PersistentVolume 对象。 当一个 PersistentVolume 对象被 Pod 使用时, Kubernetes 会添加 pv-protection Finalizer。 如果你试图删除 PersistentVolume,它将进入 Terminating 状态, 但是控制器因为该 Finalizer 存在而无法删除该资源。 当 Pod 停止使用 PersistentVolume 时, Kubernetes 清除 pv-protection Finalizer,控制器就会删除该卷。

属主引用、标签和 Finalizers

标签类似, 属主引用 描述了 Kubernetes 中对象之间的关系,但它们作用不同。 当一个控制器 管理类似于 Pod 的对象时,它使用标签来跟踪相关对象组的变化。 例如,当 Job 创建一个或多个 Pod 时, Job 控制器会给这些 Pod 应用上标签,并跟踪集群中的具有相同标签的 Pod 的变化。

Job 控制器还为这些 Pod 添加了“属主引用”,指向创建 Pod 的 Job。 如果你在这些 Pod 运行的时候删除了 Job, Kubernetes 会使用属主引用(而不是标签)来确定集群中哪些 Pod 需要清理。

当 Kubernetes 识别到要删除的资源上的属主引用时,它也会处理 Finalizers。

在某些情况下,Finalizers 会阻止依赖对象的删除, 这可能导致目标属主对象被保留的时间比预期的长,而没有被完全删除。 在这些情况下,你应该检查目标属主和附属对象上的 Finalizers 和属主引用,来排查原因。

接下来

3.1.4.8 - 属主与附属

在 Kubernetes 中,一些对象是其他对象的“属主(Owner)”。 例如,ReplicaSet 是一组 Pod 的属主。 具有属主的对象是属主的“附属(Dependent)”。

属主关系不同于一些资源使用的标签和选择算符机制。 例如,有一个创建 EndpointSlice 对象的 Service, 该 Service 使用标签来让控制平面确定,哪些 EndpointSlice 对象属于该 Service。 除开标签,每个代表 Service 所管理的 EndpointSlice 都有一个属主引用。 属主引用避免 Kubernetes 的不同部分干扰到不受它们控制的对象。

对象规约中的属主引用

附属对象有一个 metadata.ownerReferences 字段,用于引用其属主对象。 一个有效的属主引用,包含与附属对象同在一个命名空间下的对象名称和一个 UID。 Kubernetes 自动为一些对象的附属资源设置属主引用的值, 这些对象包含 ReplicaSet、DaemonSet、Deployment、Job、CronJob、ReplicationController 等。 你也可以通过改变这个字段的值,来手动配置这些关系。 然而,通常不需要这么做,你可以让 Kubernetes 自动管理附属关系。

附属对象还有一个 ownerReferences.blockOwnerDeletion 字段,该字段使用布尔值, 用于控制特定的附属对象是否可以阻止垃圾收集删除其属主对象。 如果控制器(例如 Deployment 控制器) 设置了 metadata.ownerReferences 字段的值,Kubernetes 会自动设置 blockOwnerDeletion 的值为 true。 你也可以手动设置 blockOwnerDeletion 字段的值,以控制哪些附属对象会阻止垃圾收集。

Kubernetes 准入控制器根据属主的删除权限控制用户访问,以便为附属资源更改此字段。 这种控制机制可防止未经授权的用户延迟属主对象的删除。

属主关系与 Finalizer

当你告诉 Kubernetes 删除一个资源,API 服务器允许管理控制器处理该资源的任何 Finalizer 规则Finalizer 防止意外删除你的集群所依赖的、用于正常运作的资源。 例如,如果你试图删除一个仍被 Pod 使用的 PersistentVolume,该资源不会被立即删除, 因为 PersistentVolumekubernetes.io/pv-protection Finalizer。 相反,它将进入 Terminating 状态,直到 Kubernetes 清除这个 Finalizer, 而这种情况只会发生在 PersistentVolume 不再被挂载到 Pod 上时。

当你使用前台或孤立级联删除时, Kubernetes 也会向属主资源添加 Finalizer。 在前台删除中,会添加 foreground Finalizer,这样控制器必须在删除了拥有 ownerReferences.blockOwnerDeletion=true 的附属资源后,才能删除属主对象。 如果你指定了孤立删除策略,Kubernetes 会添加 orphan Finalizer, 这样控制器在删除属主对象后,会忽略附属资源。

接下来

3.1.4.9 - 字段选择器

“字段选择器(Field selectors)”允许你根据一个或多个资源字段的值 筛选 Kubernetes 资源。 下面是一些使用字段选择器查询的例子:

  • metadata.name=my-service
  • metadata.namespace!=default
  • status.phase=Pending

下面这个 kubectl 命令将筛选出 status.phase 字段值为 Running 的所有 Pod:

kubectl get pods --field-selector status.phase=Running

支持的字段

不同的 Kubernetes 资源类型支持不同的字段选择器。 所有资源类型都支持 metadata.namemetadata.namespace 字段。 使用不被支持的字段选择器会产生错误。例如:

kubectl get ingress --field-selector foo.bar=baz
Error from server (BadRequest): Unable to find "ingresses" that match label selector "", field selector "foo.bar=baz": "foo.bar" is not a known field selector: only "metadata.name", "metadata.namespace"

支持的操作符

你可在字段选择器中使用 ===!==== 的意义是相同的)操作符。 例如,下面这个 kubectl 命令将筛选所有不属于 default 命名空间的 Kubernetes 服务:

kubectl get services  --all-namespaces --field-selector metadata.namespace!=default

链式选择器

标签和其他选择器一样, 字段选择器可以通过使用逗号分隔的列表组成一个选择链。 下面这个 kubectl 命令将筛选 status.phase 字段不等于 Running 同时 spec.restartPolicy 字段等于 Always 的所有 Pod:

kubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always

多种资源类型

你能够跨多种资源类型来使用字段选择器。 下面这个 kubectl 命令将筛选出所有不在 default 命名空间中的 StatefulSet 和 Service:

kubectl get statefulsets,services --all-namespaces --field-selector metadata.namespace!=default

3.1.4.10 - 推荐使用的标签

除了 kubectl 和 dashboard 之外,你可以使用其他工具来可视化和管理 Kubernetes 对象。 一组通用的标签可以让多个工具之间相互操作,用所有工具都能理解的通用方式描述对象。

除了支持工具外,推荐的标签还以一种可以查询的方式描述了应用程序。

元数据围绕 应用(application) 的概念进行组织。Kubernetes 不是 平台即服务(PaaS),没有或强制执行正式的应用程序概念。 相反,应用程序是非正式的,并使用元数据进行描述。应用程序包含的定义是松散的。

共享标签和注解都使用同一个前缀:app.kubernetes.io。没有前缀的标签是用户私有的。共享前缀可以确保共享标签不会干扰用户自定义的标签。

标签

为了充分利用这些标签,应该在每个资源对象上都使用它们。

描述示例类型
app.kubernetes.io/name应用程序的名称mysql字符串
app.kubernetes.io/instance用于唯一确定应用实例的名称mysql-abcxzy字符串
app.kubernetes.io/version应用程序的当前版本(例如,语义版本,修订版哈希等)5.7.21字符串
app.kubernetes.io/component架构中的组件database字符串
app.kubernetes.io/part-of此级别的更高级别应用程序的名称wordpress字符串
app.kubernetes.io/managed-by用于管理应用程序的工具helm字符串
app.kubernetes.io/created-by创建该资源的控制器或者用户controller-manager字符串

为说明这些标签的实际使用情况,请看下面的 StatefulSet 对象:

# 这是一段节选
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app.kubernetes.io/name: mysql
    app.kubernetes.io/instance: mysql-abcxzy
    app.kubernetes.io/version: "5.7.21"
    app.kubernetes.io/component: database
    app.kubernetes.io/part-of: wordpress
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/created-by: controller-manager

应用和应用实例

应用可以在 Kubernetes 集群中安装一次或多次。在某些情况下,可以安装在同一命名空间中。例如,可以不止一次地为不同的站点安装不同的 WordPress。

应用的名称和实例的名称是分别记录的。例如,WordPress 应用的 app.kubernetes.io/namewordpress,而其实例名称 app.kubernetes.io/instancewordpress-abcxzy。 这使得应用和应用的实例均可被识别,应用的每个实例都必须具有唯一的名称。

示例

为了说明使用这些标签的不同方式,以下示例具有不同的复杂性。

一个简单的无状态服务

考虑使用 DeploymentService 对象部署的简单无状态服务的情况。以下两个代码段表示如何以最简单的形式使用标签。

下面的 Deployment 用于监督运行应用本身的 pods。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: myservice
    app.kubernetes.io/instance: myservice-abcxzy
...

下面的 Service 用于暴露应用。

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: myservice
    app.kubernetes.io/instance: myservice-abcxzy
...

带有一个数据库的 Web 应用程序

考虑一个稍微复杂的应用:一个使用 Helm 安装的 Web 应用(WordPress),其中 使用了数据库(MySQL)。以下代码片段说明用于部署此应用程序的对象的开始。

以下 Deployment 的开头用于 WordPress:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: wordpress
    app.kubernetes.io/instance: wordpress-abcxzy
    app.kubernetes.io/version: "4.9.4"
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: server
    app.kubernetes.io/part-of: wordpress
...

这个 Service 用于暴露 WordPress:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: wordpress
    app.kubernetes.io/instance: wordpress-abcxzy
    app.kubernetes.io/version: "4.9.4"
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: server
    app.kubernetes.io/part-of: wordpress
...

MySQL 作为一个 StatefulSet 暴露,包含它和它所属的较大应用程序的元数据:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app.kubernetes.io/name: mysql
    app.kubernetes.io/instance: mysql-abcxzy
    app.kubernetes.io/version: "5.7.21"
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: database
    app.kubernetes.io/part-of: wordpress
...

Service 用于将 MySQL 作为 WordPress 的一部分暴露:

apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: mysql
    app.kubernetes.io/instance: mysql-abcxzy
    app.kubernetes.io/version: "5.7.21"
    app.kubernetes.io/managed-by: helm
    app.kubernetes.io/component: database
    app.kubernetes.io/part-of: wordpress
...

使用 MySQL StatefulSetService,你会注意到有关 MySQL 和 Wordpress 的信息,包括更广泛的应用程序。

3.2 - Kubernetes 架构

Kubernetes 背后的架构概念。

3.2.1 - 节点

Kubernetes 通过将容器放入在节点(Node)上运行的 Pod 中来执行你的工作负载。 节点可以是一个虚拟机或者物理机器,取决于所在的集群配置。 每个节点包含运行 Pod 所需的服务; 这些节点由 控制面 负责管理。

通常集群中会有若干个节点;而在一个学习所用或者资源受限的环境中,你的集群中也可能只有一个节点。

节点上的组件包括 kubelet容器运行时以及 kube-proxy

管理

API 服务器添加节点的方式主要有两种:

  1. 节点上的 kubelet 向控制面执行自注册;
  2. 你(或者别的什么人)手动添加一个 Node 对象。

在你创建了 Node 对象或者节点上的 kubelet 执行了自注册操作之后,控制面会检查新的 Node 对象是否合法。 例如,如果你尝试使用下面的 JSON 对象来创建 Node 对象:

{
  "kind": "Node",
  "apiVersion": "v1",
  "metadata": {
    "name": "10.240.79.157",
    "labels": {
      "name": "my-first-k8s-node"
    }
  }
}

Kubernetes 会在内部创建一个 Node 对象作为节点的表示。Kubernetes 检查 kubelet 向 API 服务器注册节点时使用的 metadata.name 字段是否匹配。 如果节点是健康的(即所有必要的服务都在运行中),则该节点可以用来运行 Pod。 否则,直到该节点变为健康之前,所有的集群活动都会忽略该节点。

Node 对象的名称必须是合法的 DNS 子域名

节点名称唯一性

节点的名称用来标识 Node 对象。 没有两个 Node 可以同时使用相同的名称。 Kubernetes 还假定名字相同的资源是同一个对象。 就 Node 而言,隐式假定使用相同名称的实例会具有相同的状态(例如网络配置、根磁盘内容) 和类似节点标签这类属性。这可能在节点被更改但其名称未变时导致系统状态不一致。 如果某个 Node 需要被替换或者大量变更,需要从 API 服务器移除现有的 Node 对象, 之后再在更新之后重新将其加入。

节点自注册

当 kubelet 标志 --register-node 为 true(默认)时,它会尝试向 API 服务注册自己。 这是首选模式,被绝大多数发行版选用。

对于自注册模式,kubelet 使用下列参数启动:

  • --kubeconfig - 用于向 API 服务器执行身份认证所用的凭据的路径。
  • --cloud-provider - 与某云驱动 进行通信以读取与自身相关的元数据的方式。
  • --register-node - 自动向 API 服务注册。
  • --register-with-taints - 使用所给的污点列表 (逗号分隔的 <key>=<value>:<effect>)注册节点。当 register-node 为 false 时无效。
  • --node-ip - 节点 IP 地址。
  • --node-labels - 在集群中注册节点时要添加的标签。 (参见 NodeRestriction 准入控制插件所实施的标签限制)。
  • --node-status-update-frequency - 指定 kubelet 向控制面发送状态的频率。

启用 Node 鉴权模式NodeRestriction 准入插件时, 仅授权 kubelet 创建或修改其自己的节点资源。

手动节点管理

你可以使用 kubectl 来创建和修改 Node 对象。

如果你希望手动创建节点对象时,请设置 kubelet 标志 --register-node=false

你可以修改 Node 对象(忽略 --register-node 设置)。 例如,你可以修改节点上的标签或并标记其为不可调度。

你可以结合使用 Node 上的标签和 Pod 上的选择算符来控制调度。 例如,你可以限制某 Pod 只能在符合要求的节点子集上运行。

如果标记节点为不可调度(unschedulable),将阻止新 Pod 调度到该 Node 之上, 但不会影响任何已经在其上的 Pod。 这是重启节点或者执行其他维护操作之前的一个有用的准备步骤。

要标记一个 Node 为不可调度,执行以下命令:

kubectl cordon $NODENAME

更多细节参考安全地腾空节点

节点状态

一个节点的状态包含以下信息:

你可以使用 kubectl 来查看节点状态和其他细节信息:

kubectl describe node <节点名称>

下面对每个部分进行详细描述。

地址

这些字段的用法取决于你的云服务商或者物理机配置。

  • HostName:由节点的内核报告。可以通过 kubelet 的 --hostname-override 参数覆盖。
  • ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。
  • InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。

状况

conditions 字段描述了所有 Running 节点的状况。状况的示例包括:

节点状况及每种状况适用场景的描述
节点状况描述
Ready如节点是健康的并已经准备好接收 Pod 则为 TrueFalse 表示节点不健康而且不能接收 Pod;Unknown 表示节点控制器在最近 node-monitor-grace-period 期间(默认 40 秒)没有收到节点的消息
DiskPressureTrue 表示节点存在磁盘空间压力,即磁盘可用量低, 否则为 False
MemoryPressureTrue 表示节点存在内存压力,即节点内存可用量低,否则为 False
PIDPressureTrue 表示节点存在进程压力,即节点上进程过多;否则为 False
NetworkUnavailableTrue 表示节点网络配置不正确;否则为 False

在 Kubernetes API 中,节点的状况表示节点资源中.status 的一部分。 例如,以下 JSON 结构描述了一个健康节点:

"conditions": [
  {
    "type": "Ready",
    "status": "True",
    "reason": "KubeletReady",
    "message": "kubelet is posting ready status",
    "lastHeartbeatTime": "2019-06-05T18:38:35Z",
    "lastTransitionTime": "2019-06-05T11:41:27Z"
  }
]

如果 Ready 状况的 status 处于 Unknown 或者 False 状态的时间超过了 pod-eviction-timeout 值(一个传递给 kube-controller-manager 的参数),节点控制器会对节点上的所有 Pod 触发 API 发起的驱逐。 默认的逐出超时时长为 5 分钟

某些情况下,当节点不可达时,API 服务器不能和其上的 kubelet 通信。 删除 Pod 的决定不能传达给 kubelet,直到它重新建立和 API 服务器的连接为止。 与此同时,被计划删除的 Pod 可能会继续在游离的节点上运行。

节点控制器在确认 Pod 在集群中已经停止运行前,不会强制删除它们。 你可以看到可能在这些无法访问的节点上运行的 Pod 处于 Terminating 或者 Unknown 状态。 如果 kubernetes 不能基于下层基础设施推断出某节点是否已经永久离开了集群, 集群管理员可能需要手动删除该节点对象。 从 Kubernetes 删除节点对象将导致 API 服务器删除节点上所有运行的 Pod 对象并释放它们的名字。

当节点上出现问题时,Kubernetes 控制面会自动创建与影响节点的状况对应的 污点。 调度器在将 Pod 指派到某 Node 时会考虑 Node 上的污点设置。 Pod 也可以设置容忍度, 以便能够在设置了特定污点的 Node 上运行。

进一步的细节可参阅根据状况为节点设置污点

容量(Capacity)与可分配(Allocatable)

这两个值描述节点上的可用资源:CPU、内存和可以调度到节点上的 Pod 的个数上限。

capacity 块中的字段标示节点拥有的资源总量。 allocatable 块指示节点上可供普通 Pod 消耗的资源量。

可以在学习如何在节点上预留计算资源 的时候了解有关容量和可分配资源的更多信息。

信息(Info)

Info 指的是节点的一般信息,如内核版本、Kubernetes 版本(kubeletkube-proxy 版本)、 容器运行时详细信息,以及节点使用的操作系统。 kubelet 从节点收集这些信息并将其发布到 Kubernetes API。

心跳

Kubernetes 节点发送的心跳帮助你的集群确定每个节点的可用性,并在检测到故障时采取行动。

对于节点,有两种形式的心跳:

与 Node 的 .status 更新相比,Lease 是一种轻量级资源。 使用 Lease 来表达心跳在大型集群中可以减少这些更新对性能的影响。

kubelet 负责创建和更新节点的 .status,以及更新它们对应的 Lease。

  • 当节点状态发生变化时,或者在配置的时间间隔内没有更新事件时,kubelet 会更新 .status.status 更新的默认间隔为 5 分钟(比节点不可达事件的 40 秒默认超时时间长很多)。
  • kubelet 会创建并每 10 秒(默认更新间隔时间)更新 Lease 对象。 Lease 的更新独立于 Node 的 .status 更新而发生。 如果 Lease 的更新操作失败,kubelet 会采用指数回退机制,从 200 毫秒开始重试, 最长重试间隔为 7 秒钟。

节点控制器

节点控制器是 Kubernetes 控制面组件, 管理节点的方方面面。

节点控制器在节点的生命周期中扮演多个角色。 第一个是当节点注册时为它分配一个 CIDR 区段(如果启用了 CIDR 分配)。

第二个是保持节点控制器内的节点列表与云服务商所提供的可用机器列表同步。 如果在云环境下运行,只要某节点不健康,节点控制器就会询问云服务是否节点的虚拟机仍可用。 如果不可用,节点控制器会将该节点从它的节点列表删除。

第三个是监控节点的健康状况。节点控制器负责:

  • 在节点不可达的情况下,在 Node 的 .status 中更新 Ready 状况。 在这种情况下,节点控制器将 NodeReady 状况更新为 Unknown
  • 如果节点仍然无法访问:对于不可达节点上的所有 Pod 触发 API 发起的逐出操作。 默认情况下,节点控制器在将节点标记为 Unknown 后等待 5 分钟提交第一个驱逐请求。

默认情况下,节点控制器每 5 秒检查一次节点状态,可以使用 kube-controller-manager 组件上的 --node-monitor-period 参数来配置周期。

逐出速率限制

大部分情况下,节点控制器把逐出速率限制在每秒 --node-eviction-rate 个(默认为 0.1)。 这表示它每 10 秒钟内至多从一个节点驱逐 Pod。

当一个可用区域(Availability Zone)中的节点变为不健康时,节点的驱逐行为将发生改变。 节点控制器会同时检查可用区域中不健康(Ready 状况为 UnknownFalse) 的节点的百分比:

  • 如果不健康节点的比例超过 --unhealthy-zone-threshold (默认为 0.55), 驱逐速率将会降低。
  • 如果集群较小(意即小于等于 --large-cluster-size-threshold 个节点 - 默认为 50), 驱逐操作将会停止。
  • 否则驱逐速率将降为每秒 --secondary-node-eviction-rate 个(默认为 0.01)。

在逐个可用区域中实施这些策略的原因是, 当一个可用区域可能从控制面脱离时其它可用区域可能仍然保持连接。 如果你的集群没有跨越云服务商的多个可用区域,那(整个集群)就只有一个可用区域。

跨多个可用区域部署你的节点的一个关键原因是当某个可用区域整体出现故障时, 工作负载可以转移到健康的可用区域。 因此,如果一个可用区域中的所有节点都不健康时,节点控制器会以正常的速率 --node-eviction-rate 进行驱逐操作。 在所有的可用区域都不健康(也即集群中没有健康节点)的极端情况下, 节点控制器将假设控制面与节点间的连接出了某些问题,它将停止所有驱逐动作 (如果故障后部分节点重新连接,节点控制器会从剩下不健康或者不可达节点中驱逐 Pod)。

节点控制器还负责驱逐运行在拥有 NoExecute 污点的节点上的 Pod, 除非这些 Pod 能够容忍此污点。 节点控制器还负责根据节点故障(例如节点不可访问或没有就绪) 为其添加污点。 这意味着调度器不会将 Pod 调度到不健康的节点上。

资源容量跟踪

Node 对象会跟踪节点上资源的容量(例如可用内存和 CPU 数量)。 通过自注册机制生成的 Node 对象会在注册期间报告自身容量。 如果你手动添加了 Node, 你就需要在添加节点时手动设置节点容量。

Kubernetes 调度器 保证节点上有足够的资源供其上的所有 Pod 使用。 它会检查节点上所有容器的请求的总和不会超过节点的容量。 总的请求包括由 kubelet 启动的所有容器,但不包括由容器运行时直接启动的容器, 也不包括不受 kubelet 控制的其他进程。

节点拓扑

特性状态: Kubernetes v1.18 [beta]

如果启用了 TopologyManager 特性门控kubelet 可以在作出资源分配决策时使用拓扑提示。 参考控制节点上拓扑管理策略了解详细信息。

节点体面关闭

特性状态: Kubernetes v1.21 [beta]

kubelet 会尝试检测节点系统关闭事件并终止在节点上运行的所有 Pod。

在节点终止期间,kubelet 保证 Pod 遵从常规的 Pod 终止流程

节点体面关闭特性依赖于 systemd,因为它要利用 systemd 抑制器锁机制, 在给定的期限内延迟节点关闭。

节点体面关闭特性受 GracefulNodeShutdown 特性门控控制, 在 1.21 版本中是默认启用的。

注意,默认情况下,下面描述的两个配置选项,shutdownGracePeriodshutdownGracePeriodCriticalPods 都是被设置为 0 的,因此不会激活节点体面关闭功能。 要激活此功能特性,这两个 kubelet 配置选项要适当配置,并设置为非零值。

在体面关闭节点过程中,kubelet 分两个阶段来终止 Pod:

  1. 终止在节点上运行的常规 Pod。
  2. 终止在节点上运行的关键 Pod

节点体面关闭的特性对应两个 KubeletConfiguration 选项:

  • shutdownGracePeriod
    • 指定节点应延迟关闭的总持续时间。此时间是 Pod 体面终止的时间总和,不区分常规 Pod 还是关键 Pod
  • shutdownGracePeriodCriticalPods
    • 在节点关闭期间指定用于终止关键 Pod 的持续时间。该值应小于 shutdownGracePeriod

例如,如果设置了 shutdownGracePeriod=30sshutdownGracePeriodCriticalPods=10s, 则 kubelet 将延迟 30 秒关闭节点。 在关闭期间,将保留前 20(30 - 10)秒用于体面终止常规 Pod, 而保留最后 10 秒用于终止关键 Pod

节点非体面关闭

特性状态: Kubernetes v1.24 [alpha]

节点关闭的操作可能无法被 kubelet 的节点关闭管理器检测到, 是因为该命令不会触发 kubelet 所使用的抑制锁定机制,或者是因为用户错误的原因, 即 ShutdownGracePeriod 和 ShutdownGracePeriodCriticalPod 配置不正确。 请参考以上节点体面关闭部分了解更多详细信息。

当某节点关闭但 kubelet 的节点关闭管理器未检测到这一事件时, 在那个已关闭节点上、属于 StatefulSet 的 Pod 将停滞于终止状态,并且不能移动到新的运行节点上。 这是因为已关闭节点上的 kubelet 已不存在,亦无法删除 Pod, 因此 StatefulSet 无法创建同名的新 Pod。 如果 Pod 使用了卷,则 VolumeAttachments 不会从原来的已关闭节点上删除, 因此这些 Pod 所使用的卷也无法挂接到新的运行节点上。 所以,那些以 StatefulSet 形式运行的应用无法正常工作。 如果原来的已关闭节点被恢复,kubelet 将删除 Pod,新的 Pod 将被在不同的运行节点上创建。 如果原来的已关闭节点没有被恢复,那些在已关闭节点上的 Pod 将永远滞留在终止状态。

为了缓解上述情况,用户可以手动将具有 NoExecuteNoSchedule 效果的 node kubernetes.io/out-of-service 污点添加到节点上,标记其无法提供服务。 如果在 kube-controller-manager 上启用了 NodeOutOfServiceVolumeDetach 特性门控, 并且节点被通过污点标记为无法提供服务,如果节点 Pod 上没有设置对应的容忍度, 那么这样的 Pod 将被强制删除,并且该在节点上被终止的 Pod 将立即进行卷分离操作。 这样就允许那些在无法提供服务节点上的 Pod 能在其他节点上快速恢复。

在非体面关闭期间,Pod 分两个阶段终止:

  1. 强制删除没有匹配的 out-of-service 容忍度的 Pod。
  2. 立即对此类 Pod 执行分离卷操作。

基于 Pod 优先级的节点体面关闭

特性状态: Kubernetes v1.23 [alpha]

为了在节点体面关闭期间提供更多的灵活性,尤其是处理关闭期间的 Pod 排序问题, 节点体面关闭机制能够关注 Pod 的 PriorityClass 设置,前提是你已经在集群中启用了此功能特性。 此功能特性允许集群管理员基于 Pod 的优先级类(Priority Class) 显式地定义节点体面关闭期间 Pod 的处理顺序。

前文所述的节点体面关闭特性能够分两个阶段关闭 Pod, 首先关闭的是非关键的 Pod,之后再处理关键 Pod。 如果需要显式地以更细粒度定义关闭期间 Pod 的处理顺序,需要一定的灵活度, 这时可以使用基于 Pod 优先级的体面关闭机制。

当节点体面关闭能够处理 Pod 优先级时,节点体面关闭的处理可以分为多个阶段, 每个阶段关闭特定优先级类的 Pod。kubelet 可以被配置为按确切的阶段处理 Pod, 且每个阶段可以独立设置关闭时间。

假设集群中存在以下自定义的 Pod 优先级类

Pod 优先级类名称Pod 优先级类数值
custom-class-a100000
custom-class-b10000
custom-class-c1000
regular/unset0

kubelet 配置中, shutdownGracePeriodByPodPriority 可能看起来是这样:

Pod 优先级类数值关闭期限
10000010 秒
10000180 秒
1000120 秒
060 秒

对应的 kubelet 配置 YAML 将会是:

shutdownGracePeriodByPodPriority:
  - priority: 100000
    shutdownGracePeriodSeconds: 10
  - priority: 10000
    shutdownGracePeriodSeconds: 180
  - priority: 1000
    shutdownGracePeriodSeconds: 120
  - priority: 0
    shutdownGracePeriodSeconds: 60

上面的表格表明,所有 priority 值大于等于 100000 的 Pod 会得到 10 秒钟期限停止, 所有 priority 值介于 10000 和 100000 之间的 Pod 会得到 180 秒钟期限停止, 所有 priority 值介于 1000 和 10000 之间的 Pod 会得到 120 秒钟期限停止, 所有其他 Pod 将获得 60 秒的时间停止。

用户不需要为所有的优先级类都设置数值。例如,你也可以使用下面这种配置:

Pod 优先级类数值关闭期限
100000300 秒
1000120 秒
060 秒

在上面这个场景中,优先级类为 custom-class-b 的 Pod 会与优先级类为 custom-class-c 的 Pod 在关闭时按相同期限处理。

如果在特定的范围内不存在 Pod,则 kubelet 不会等待对应优先级范围的 Pod。 kubelet 会直接跳到下一个优先级数值范围进行处理。

如果此功能特性被启用,但没有提供配置数据,则不会出现排序操作。

使用此功能特性需要启用 GracefulNodeShutdownBasedOnPodPriority 特性门控, 并将 kubelet 配置 中的 shutdownGracePeriodByPodPriority 设置为期望的配置, 其中包含 Pod 的优先级类数值以及对应的关闭期限。

kubelet 子系统中会生成 graceful_shutdown_start_time_secondsgraceful_shutdown_end_time_seconds 度量指标以便监视节点关闭行为。

交换内存管理

特性状态: Kubernetes v1.22 [alpha]

在 Kubernetes 1.22 之前,节点不支持使用交换内存,并且默认情况下, 如果在节点上检测到交换内存配置,kubelet 将无法启动。 在 1.22 以后,可以逐个节点地启用交换内存支持。

要在节点上启用交换内存,必须启用kubelet 的 NodeSwap 特性门控, 同时使用 --fail-swap-on 命令行参数或者将 failSwapOn 配置设置为 false。

用户还可以选择配置 memorySwap.swapBehavior 以指定节点使用交换内存的方式。例如:

memorySwap:
  swapBehavior: LimitedSwap

可用的 swapBehavior 的配置选项有:

  • LimitedSwap:Kubernetes 工作负载的交换内存会受限制。 不受 Kubernetes 管理的节点上的工作负载仍然可以交换。
  • UnlimitedSwap:Kubernetes 工作负载可以使用尽可能多的交换内存请求, 一直到达到系统限制为止。

如果启用了特性门控但是未指定 memorySwap 的配置,默认情况下 kubelet 将使用 LimitedSwap 设置。

LimitedSwap 这种设置的行为取决于节点运行的是 v1 还是 v2 的控制组(也就是 cgroups):

  • cgroupsv1: Kubernetes 工作负载可以使用内存和交换,上限为 Pod 的内存限制值(如果设置了的话)。
  • cgroupsv2: Kubernetes 工作负载不能使用交换内存。

如需更多信息以及协助测试和提供反馈,请参见 KEP-2400 及其设计提案

接下来

3.2.2 - 节点与控制面之间的通信

本文列举控制面节点(确切说是 API 服务器)和 Kubernetes 集群之间的通信路径。 目的是为了让用户能够自定义他们的安装,以实现对网络配置的加固, 使得集群能够在不可信的网络上(或者在一个云服务商完全公开的 IP 上)运行。

节点到控制面

Kubernetes 采用的是中心辐射型(Hub-and-Spoke)API 模式。 所有从节点(或运行于其上的 Pod)发出的 API 调用都终止于 API 服务器。 其它控制面组件都没有被设计为可暴露远程服务。 API 服务器被配置为在一个安全的 HTTPS 端口(通常为 443)上监听远程连接请求, 并启用一种或多种形式的客户端身份认证机制。 一种或多种客户端鉴权机制应该被启用, 特别是在允许使用匿名请求服务账户令牌的时候。

应该使用集群的公共根证书开通节点,这样它们就能够基于有效的客户端凭据安全地连接 API 服务器。 一种好的方法是以客户端证书的形式将客户端凭据提供给 kubelet。 请查看 kubelet TLS 启动引导 以了解如何自动提供 kubelet 客户端证书。

想要连接到 API 服务器的 Pod 可以使用服务账号安全地进行连接。 当 Pod 被实例化时,Kubernetes 自动把公共根证书和一个有效的持有者令牌注入到 Pod 里。 kubernetes 服务(位于 default 名字空间中)配置了一个虚拟 IP 地址, 用于(通过 kube-proxy)转发请求到 API 服务器的 HTTPS 末端。

控制面组件也通过安全端口与集群的 API 服务器通信。

这样,从集群节点和节点上运行的 Pod 到控制面的连接的缺省操作模式即是安全的, 能够在不可信的网络或公网上运行。

控制面到节点

从控制面(API 服务器)到节点有两种主要的通信路径。 第一种是从 API 服务器到集群中每个节点上运行的 kubelet 进程。 第二种是从 API 服务器通过它的代理功能连接到任何节点、Pod 或者服务。

API 服务器到 kubelet

从 API 服务器到 kubelet 的连接用于:

  • 获取 Pod 日志
  • 挂接(通过 kubectl)到运行中的 Pod
  • 提供 kubelet 的端口转发功能。

这些连接终止于 kubelet 的 HTTPS 末端。 默认情况下,API 服务器不检查 kubelet 的服务证书。这使得此类连接容易受到中间人攻击, 在非受信网络或公开网络上运行也是 不安全的

为了对这个连接进行认证,使用 --kubelet-certificate-authority 标志给 API 服务器提供一个根证书包,用于 kubelet 的服务证书。

如果无法实现这点,又要求避免在非受信网络或公共网络上进行连接,可在 API 服务器和 kubelet 之间使用 SSH 隧道

最后,应该启用 Kubelet 认证/鉴权 来保护 kubelet API。

API 服务器到节点、Pod 和服务

从 API 服务器到节点、Pod 或服务的连接默认为纯 HTTP 方式,因此既没有认证,也没有加密。 这些连接可通过给 API URL 中的节点、Pod 或服务名称添加前缀 https: 来运行在安全的 HTTPS 连接上。 不过这些连接既不会验证 HTTPS 末端提供的证书,也不会提供客户端证书。 因此,虽然连接是加密的,仍无法提供任何完整性保证。 这些连接 目前还不能安全地 在非受信网络或公共网络上运行。

SSH 隧道

Kubernetes 支持使用 SSH 隧道来保护从控制面到节点的通信路径。在这种配置下, API 服务器建立一个到集群中各节点的 SSH 隧道(连接到在 22 端口监听的 SSH 服务器) 并通过这个隧道传输所有到 kubelet、节点、Pod 或服务的请求。 这一隧道保证通信不会被暴露到集群节点所运行的网络之外。

Konnectivity 服务

特性状态: Kubernetes v1.18 [beta]

作为 SSH 隧道的替代方案,Konnectivity 服务提供 TCP 层的代理,以便支持从控制面到集群的通信。 Konnectivity 服务包含两个部分:Konnectivity 服务器和 Konnectivity 代理, 分别运行在控制面网络和节点网络中。 Konnectivity 代理建立并维持到 Konnectivity 服务器的网络连接。 启用 Konnectivity 服务之后,所有控制面到节点的通信都通过这些连接传输。

请浏览 Konnectivity 服务任务 在你的集群中配置 Konnectivity 服务。

3.2.3 - 控制器

在机器人技术和自动化领域,控制回路(Control Loop)是一个非终止回路,用于调节系统状态。

这是一个控制环的例子:房间里的温度自动调节器。

当你设置了温度,告诉了温度自动调节器你的期望状态(Desired State)。 房间的实际温度是当前状态(Current State)。 通过对设备的开关控制,温度自动调节器让其当前状态接近期望状态。

在 Kubernetes 中,控制器通过监控集群 的公共状态,并致力于将当前状态转变为期望的状态。

控制器模式

一个控制器至少追踪一种类型的 Kubernetes 资源。这些 对象 有一个代表期望状态的 spec 字段。 该资源的控制器负责确保其当前状态接近期望状态。

控制器可能会自行执行操作;在 Kubernetes 中更常见的是一个控制器会发送信息给 API 服务器,这会有副作用。 具体可参看后文的例子。

通过 API 服务器来控制

Job 控制器是一个 Kubernetes 内置控制器的例子。 内置控制器通过和集群 API 服务器交互来管理状态。

Job 是一种 Kubernetes 资源,它运行一个或者多个 Pod, 来执行一个任务然后停止。 (一旦被调度了,对 kubelet 来说 Pod 对象就会变成了期望状态的一部分)。

在集群中,当 Job 控制器拿到新任务时,它会保证一组 Node 节点上的 kubelet 可以运行正确数量的 Pod 来完成工作。 Job 控制器不会自己运行任何的 Pod 或者容器。Job 控制器是通知 API 服务器来创建或者移除 Pod。 控制面中的其它组件 根据新的消息作出反应(调度并运行新 Pod)并且最终完成工作。

创建新 Job 后,所期望的状态就是完成这个 Job。Job 控制器会让 Job 的当前状态不断接近期望状态:创建为 Job 要完成工作所需要的 Pod,使 Job 的状态接近完成。

控制器也会更新配置对象。例如:一旦 Job 的工作完成了,Job 控制器会更新 Job 对象的状态为 Finished

(这有点像温度自动调节器关闭了一个灯,以此来告诉你房间的温度现在到你设定的值了)。

直接控制

相比 Job 控制器,有些控制器需要对集群外的一些东西进行修改。

例如,如果你使用一个控制回路来保证集群中有足够的 节点,那么控制器就需要当前集群外的 一些服务在需要时创建新节点。

和外部状态交互的控制器从 API 服务器获取到它想要的状态,然后直接和外部系统进行通信 并使当前状态更接近期望状态。

(实际上有一个控制器 可以水平地扩展集群中的节点。)

这里,很重要的一点是,控制器做出了一些变更以使得事物更接近你的期望状态, 之后将当前状态报告给集群的 API 服务器。 其他控制回路可以观测到所汇报的数据的这种变化并采取其各自的行动。

在温度计的例子中,如果房间很冷,那么某个控制器可能还会启动一个防冻加热器。 就 Kubernetes 集群而言,控制面间接地与 IP 地址管理工具、存储服务、云驱动 APIs 以及其他服务协作,通过扩展 Kubernetes 来实现这点。

期望状态与当前状态

Kubernetes 采用了系统的云原生视图,并且可以处理持续的变化。

在任务执行时,集群随时都可能被修改,并且控制回路会自动修复故障。 这意味着很可能集群永远不会达到稳定状态。

只要集群中的控制器在运行并且进行有效的修改,整体状态的稳定与否是无关紧要的。

设计

作为设计原则之一,Kubernetes 使用了很多控制器,每个控制器管理集群状态的一个特定方面。 最常见的一个特定的控制器使用一种类型的资源作为它的期望状态, 控制器管理控制另外一种类型的资源向它的期望状态演化。 例如,Job 的控制器跟踪 Job 对象(以发现新的任务)和 Pod 对象(以运行 Job,然后查看任务何时完成)。 在这种情况下,新任务会创建 Job,而 Job 控制器会创建 Pod。

使用简单的控制器而不是一组相互连接的单体控制回路是很有用的。 控制器会失败,所以 Kubernetes 的设计正是考虑到了这一点。

运行控制器的方式

Kubernetes 内置一组控制器,运行在 kube-controller-manager 内。 这些内置的控制器提供了重要的核心功能。

Deployment 控制器和 Job 控制器是 Kubernetes 内置控制器的典型例子。 Kubernetes 允许你运行一个稳定的控制平面,这样即使某些内置控制器失败了, 控制平面的其他部分会接替它们的工作。

你会遇到某些控制器运行在控制面之外,用以扩展 Kubernetes。 或者,如果你愿意,你也可以自己编写新控制器。 你可以以一组 Pod 来运行你的控制器,或者运行在 Kubernetes 之外。 最合适的方案取决于控制器所要执行的功能是什么。

接下来

3.2.4 - 云控制器管理器

特性状态: Kubernetes v1.11 [beta]

使用云基础设施技术,你可以在公有云、私有云或者混合云环境中运行 Kubernetes。 Kubernetes 的信条是基于自动化的、API 驱动的基础设施,同时避免组件间紧密耦合。

组件 cloud-controller-manager 是指云控制器管理器, cloud-controller-manager 是指嵌入特定云的控制逻辑之 控制平面组件。 cloud-controller-manager 允许你将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。

通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑, cloud-controller-manager 组件使云提供商能够以不同于 Kubernetes 主项目的 步调发布新特征。

cloud-controller-manager 组件是基于一种插件机制来构造的, 这种机制使得不同的云厂商都能将其平台与 Kubernetes 集成。

设计

Kubernetes 组件

云控制器管理器以一组多副本的进程集合的形式运行在控制面中,通常表现为 Pod 中的容器。每个 cloud-controller-manager 在同一进程中实现多个 控制器

云控制器管理器的功能

云控制器管理器中的控制器包括:

节点控制器

节点控制器负责在云基础设施中创建了新服务器时为之 更新 节点(Node)对象。 节点控制器从云提供商获取当前租户中主机的信息。节点控制器执行以下功能:

  1. 使用从云平台 API 获取的对应服务器的唯一标识符更新 Node 对象;
  2. 利用特定云平台的信息为 Node 对象添加注解和标签,例如节点所在的区域 (Region)和所具有的资源(CPU、内存等等);
  3. 获取节点的网络地址和主机名;
  4. 检查节点的健康状况。如果节点无响应,控制器通过云平台 API 查看该节点是否已从云中禁用、删除或终止。如果节点已从云中删除, 则控制器从 Kubernetes 集群中删除 Node 对象。

某些云驱动实现中,这些任务被划分到一个节点控制器和一个节点生命周期控制器中。

路由控制器

Route 控制器负责适当地配置云平台中的路由,以便 Kubernetes 集群中不同节点上的容器之间可以相互通信。

取决于云驱动本身,路由控制器可能也会为 Pod 网络分配 IP 地址块。

服务控制器

服务(Service)与受控的负载均衡器、 IP 地址、网络包过滤、目标健康检查等云基础设施组件集成。 服务控制器与云驱动的 API 交互,以配置负载均衡器和其他基础设施组件。 你所创建的 Service 资源会需要这些组件服务。

鉴权

本节分别讲述云控制器管理器为了完成自身工作而产生的对各类 API 对象的访问需求。

节点控制器

节点控制器只操作 Node 对象。它需要读取和修改 Node 对象的完全访问权限。

v1/Node:

  • Get
  • List
  • Create
  • Update
  • Patch
  • Watch
  • Delete

路由控制器

路由控制器会监听 Node 对象的创建事件,并据此配置路由设施。 它需要读取 Node 对象的 Get 权限。

v1/Node:

  • Get

服务控制器

服务控制器监测 Service 对象的 Create、Update 和 Delete 事件, 并配置对应服务的 Endpoints 对象。

为了访问 Service 对象,它需要 List 和 Watch 访问权限。 为了更新 Service 对象,它需要 Patch 和 Update 访问权限。

为了能够配置 Service 对应的 Endpoints 资源, 它需要 Create、List、Get、Watch 和 Update 等访问权限。

v1/Service:

  • List
  • Get
  • Watch
  • Patch
  • Update

其他

在云控制器管理器的实现中,其核心部分需要创建 Event 对象的访问权限, 并创建 ServiceAccount 资源以保证操作安全性的权限。

v1/Event:

  • Create
  • Patch
  • Update

v1/ServiceAccount:

  • Create

用于云控制器管理器 RBAC 的 ClusterRole 如下例所示:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cloud-controller-manager
rules:
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
  - update
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - '*'
- apiGroups:
  - ""
  resources:
  - nodes/status
  verbs:
  - patch
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - list
  - patch
  - update
  - watch
- apiGroups:
  - ""
  resources:
  - serviceaccounts
  verbs:
  - create
- apiGroups:
  - ""
  resources:
  - persistentvolumes
  verbs:
  - get
  - list
  - update
  - watch
- apiGroups:
  - ""
  resources:
  - endpoints
  verbs:
  - create
  - get
  - list
  - watch
  - update

接下来

云控制器管理器的管理 给出了运行和管理云控制器管理器的指南。

要升级 HA 控制平面以使用云控制器管理器,请参见将复制的控制平面迁移以使用云控制器管理器

想要了解如何实现自己的云控制器管理器,或者对现有项目进行扩展么?

云控制器管理器使用 Go 语言的接口,从而使得针对各种云平台的具体实现都可以接入。 其中使用了在 kubernetes/cloud-provider 项目中 cloud.go 文件所定义的 CloudProvider 接口。

本文中列举的共享控制器(节点控制器、路由控制器和服务控制器等)的实现以及其他一些生成具有 CloudProvider 接口的框架的代码,都是 Kubernetes 的核心代码。 特定于云驱动的实现虽不是 Kubernetes 核心成分,仍要实现 CloudProvider 接口。

关于如何开发插件的详细信息,可参考 开发云控制器管理器 文档。

3.2.5 - 垃圾收集

垃圾收集是 Kubernetes 用于清理集群资源的各种机制的统称。 垃圾收集允许系统清理如下资源:

属主与依赖

Kubernetes 中很多对象通过属主引用 链接到彼此。属主引用(Owner Reference)可以告诉控制面哪些对象依赖于其他对象。 Kubernetes 使用属主引用来为控制面以及其他 API 客户端在删除某对象时提供一个清理关联资源的机会。 在大多数场合,Kubernetes 都是自动管理属主引用的。

属主关系与某些资源所使用的标签和选择算符不同。 例如,考虑一个创建 EndpointSlice 对象的 Service 对象。Service 对象使用标签来允许控制面确定哪些 EndpointSlice 对象被该 Service 使用。除了标签,每个被 Service 托管的 EndpointSlice 对象还有一个属主引用属性。 属主引用可以帮助 Kubernetes 中的不同组件避免干预并非由它们控制的对象。

级联删除

Kubernetes 会检查并删除那些不再拥有属主引用的对象,例如在你删除了 ReplicaSet 之后留下来的 Pod。当你删除某个对象时,你可以控制 Kubernetes 是否去自动删除该对象的依赖对象, 这个过程称为 级联删除(Cascading Deletion)。 级联删除有两种类型,分别如下:

  • 前台级联删除
  • 后台级联删除

你也可以使用 Kubernetes Finalizers 来控制垃圾收集机制如何以及何时删除包含属主引用的资源。

前台级联删除

在前台级联删除中,正在被你删除的属主对象首先进入 deletion in progress 状态。 在这种状态下,针对属主对象会发生以下事情:

  • Kubernetes API 服务器将对象的 metadata.deletionTimestamp 字段设置为对象被标记为要删除的时间点。
  • Kubernetes API 服务器也会将 metadata.finalizers 字段设置为 foregroundDeletion
  • 在删除过程完成之前,通过 Kubernetes API 仍然可以看到该对象。

当属主对象进入删除过程中状态后,控制器删除其依赖对象。控制器在删除完所有依赖对象之后, 删除属主对象。这时,通过 Kubernetes API 就无法再看到该对象。

在前台级联删除过程中,唯一可能阻止属主对象被删除的是那些带有 ownerReference.blockOwnerDeletion=true 字段的依赖对象。 参阅使用前台级联删除 以了解进一步的细节。

后台级联删除

在后台级联删除过程中,Kubernetes 服务器立即删除属主对象,控制器在后台清理所有依赖对象。 默认情况下,Kubernetes 使用后台级联删除方案,除非你手动设置了要使用前台删除, 或者选择遗弃依赖对象。

参阅使用后台级联删除 以了解进一步的细节。

被遗弃的依赖对象

当 Kubernetes 删除某个属主对象时,被留下来的依赖对象被称作被遗弃的(Orphaned)对象。 默认情况下,Kubernetes 会删除依赖对象。要了解如何重载这种默认行为,可参阅 删除属主对象和遗弃依赖对象

未使用容器和镜像的垃圾收集

kubelet 会每五分钟对未使用的镜像执行一次垃圾收集, 每分钟对未使用的容器执行一次垃圾收集。 你应该避免使用外部的垃圾收集工具,因为外部工具可能会破坏 kubelet 的行为,移除应该保留的容器。

要配置对未使用容器和镜像的垃圾收集选项,可以使用一个 配置文件,基于 KubeletConfiguration 资源类型来调整与垃圾搜集相关的 kubelet 行为。

容器镜像生命期

Kubernetes 通过其镜像管理器(Image Manager)来管理所有镜像的生命周期, 该管理器是 kubelet 的一部分,工作时与 cadvisor 协同。 kubelet 在作出垃圾收集决定时会考虑如下磁盘用量约束:

  • HighThresholdPercent
  • LowThresholdPercent

磁盘用量超出所配置的 HighThresholdPercent 值时会触发垃圾收集, 垃圾收集器会基于镜像上次被使用的时间来按顺序删除它们,首先删除的是最老的镜像。 kubelet 会持续删除镜像,直到磁盘用量到达 LowThresholdPercent 值为止。

容器垃圾收集

kubelet 会基于如下变量对所有未使用的容器执行垃圾收集操作,这些变量都是你可以定义的:

  • MinAge:kubelet 可以垃圾回收某个容器时该容器的最小年龄。设置为 0 表示禁止使用此规则。
  • MaxPerPodContainer:每个 Pod 可以包含的已死亡的容器个数上限。设置为小于 0 的值表示禁止使用此规则。
  • MaxContainers:集群中可以存在的已死亡的容器个数上限。设置为小于 0 的值意味着禁止应用此规则。

除以上变量之外,kubelet 还会垃圾收集除无标识的以及已删除的容器,通常从最老的容器开始。

当保持每个 Pod 的最大数量的容器(MaxPerPodContainer)会使得全局的已死亡容器个数超出上限 (MaxContainers)时,MaxPerPodContainerMaxContainers 之间可能会出现冲突。 在这种情况下,kubelet 会调整 MaxPerPodContainer 来解决这一冲突。 最坏的情形是将 MaxPerPodContainer 降格为 1,并驱逐最老的容器。 此外,当隶属于某已被删除的 Pod 的容器的年龄超过 MinAge 时,它们也会被删除。

配置垃圾收集

你可以通过配置特定于管理资源的控制器来调整资源的垃圾收集行为。 下面的页面为你展示如何配置垃圾收集:

接下来

3.2.6 - 容器运行时接口(CRI)

CRI 是一个插件接口,它使 kubelet 能够使用各种容器运行时,无需重新编译集群组件。

你需要在集群中的每个节点上都有一个可以正常工作的 容器运行时, 这样 kubelet 能启动 Pod 及其容器。

容器运行时接口(CRI)是 kubelet 和容器运行时之间通信的主要协议。

Kubernetes 容器运行时接口(CRI)定义了主要 gRPC 协议, 用于集群组件 kubelet容器运行时

API

特性状态: Kubernetes v1.23 [stable]

当通过 gRPC 连接到容器运行时时,kubelet 充当客户端。 运行时和镜像服务端点必须在容器运行时中可用,可以使用 命令行标志--image-service-endpoint--container-runtime-endpoint 在 kubelet 中单独配置。

对 Kubernetes v1.24,kubelet 偏向于使用 CRI v1 版本。 如果容器运行时不支持 CRI 的 v1 版本,那么 kubelet 会尝试协商任何旧的其他支持版本。 如果 kubelet 无法协商支持的 CRI 版本,则 kubelet 放弃并且不会注册为节点。

升级

升级 Kubernetes 时,kubelet 会尝试在组件重启时自动选择最新的 CRI 版本。 如果失败,则将如上所述进行回退。如果由于容器运行时已升级而需要 gRPC 重拨, 则容器运行时还必须支持最初选择的版本,否则重拨预计会失败。 这需要重新启动 kubelet。

接下来

3.3 - 容器

打包应用及其运行依赖环境的技术。

每个运行的容器都是可重复的; 包含依赖环境在内的标准,意味着无论你在哪里运行它都会得到相同的行为。

容器将应用程序从底层的主机设施中解耦。 这使得在不同的云或 OS 环境中部署更加容易。

容器镜像

容器镜像是一个随时可以运行的软件包, 包含运行应用程序所需的一切:代码和它需要的所有运行时、应用程序和系统库,以及一些基本设置的默认值。

根据设计,容器是不可变的:你不能更改已经运行的容器的代码。 如果有一个容器化的应用程序需要修改,则需要构建包含更改的新镜像,然后再基于新构建的镜像重新运行容器。

容器运行时

容器运行环境是负责运行容器的软件。

Kubernetes 支持许多容器运行环境,例如 DockercontainerdCRI-O 以及 Kubernetes CRI (容器运行环境接口) 的其他任何实现。

接下来

3.3.1 - 镜像

容器镜像(Image)所承载的是封装了应用程序及其所有软件依赖的二进制数据。 容器镜像是可执行的软件包,可以单独运行;该软件包对所处的运行时环境具有 良定(Well Defined)的假定。

你通常会创建应用的容器镜像并将其推送到某仓库(Registry),然后在 Pod 中引用它。

本页概要介绍容器镜像的概念。

镜像名称

容器镜像通常会被赋予 pauseexample/mycontainer 或者 kube-apiserver 这类的名称。 镜像名称也可以包含所在仓库的主机名。例如:fictional.registry.example/imagename。 还可以包含仓库的端口号,例如:fictional.registry.example:10443/imagename

如果你不指定仓库的主机名,Kubernetes 认为你在使用 Docker 公共仓库。

在镜像名称之后,你可以添加一个标签(Tag)(与使用 dockerpodman 等命令时的方式相同)。 使用标签能让你辨识同一镜像序列中的不同版本。

镜像标签可以包含小写字母、大写字母、数字、下划线(_)、句点(.)和连字符(-)。 关于在镜像标签中何处可以使用分隔字符(_-.)还有一些额外的规则。 如果你不指定标签,Kubernetes 认为你想使用标签 latest

更新镜像

当你最初创建一个 DeploymentStatefulSet、Pod 或者其他包含 Pod 模板的对象时,如果没有显式设定的话,Pod 中所有容器的默认镜像 拉取策略是 IfNotPresent。这一策略会使得 kubelet 在镜像已经存在的情况下直接略过拉取镜像的操作。

镜像拉取策略

容器的 imagePullPolicy 和镜像的标签会影响 kubelet 尝试拉取(下载)指定的镜像。

以下列表包含了 imagePullPolicy 可以设置的值,以及这些值的效果:

IfNotPresent
只有当镜像在本地不存在时才会拉取。
Always
每当 kubelet 启动一个容器时,kubelet 会查询容器的镜像仓库, 将名称解析为一个镜像摘要。 如果 kubelet 有一个容器镜像,并且对应的摘要已在本地缓存,kubelet 就会使用其缓存的镜像; 否则,kubelet 就会使用解析后的摘要拉取镜像,并使用该镜像来启动容器。
Never
Kubelet 不会尝试获取镜像。如果镜像已经以某种方式存在本地, kubelet 会尝试启动容器;否则,会启动失败。 更多细节见提前拉取镜像

只要能够可靠地访问镜像仓库,底层镜像提供者的缓存语义甚至可以使 imagePullPolicy: Always 高效。 你的容器运行时可以注意到节点上已经存在的镜像层,这样就不需要再次下载。

为了确保 Pod 总是使用相同版本的容器镜像,你可以指定镜像的摘要; 将 <image-name>:<tag> 替换为 <image-name>@<digest>,例如 image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

当使用镜像标签时,如果镜像仓库修改了代码所对应的镜像标签,可能会出现新旧代码混杂在 Pod 中运行的情况。 镜像摘要唯一标识了镜像的特定版本,因此 Kubernetes 每次启动具有指定镜像名称和摘要的容器时,都会运行相同的代码。 通过摘要指定镜像可固定你运行的代码,这样镜像仓库的变化就不会导致版本的混杂。

有一些第三方的准入控制器 在创建 Pod(和 Pod 模板)时产生变更,这样运行的工作负载就是根据镜像摘要,而不是标签来定义的。 无论镜像仓库上的标签发生什么变化,你都想确保你所有的工作负载都运行相同的代码,那么指定镜像摘要会很有用。

默认镜像拉取策略

当你(或控制器)向 API 服务器提交一个新的 Pod 时,你的集群会在满足特定条件时设置 imagePullPolicy 字段:

  • 如果你省略了 imagePullPolicy 字段,并且容器镜像的标签是 :latestimagePullPolicy 会自动设置为 Always
  • 如果你省略了 imagePullPolicy 字段,并且没有指定容器镜像的标签, imagePullPolicy 会自动设置为 Always
  • 如果你省略了 imagePullPolicy 字段,并且为容器镜像指定了非 :latest 的标签, imagePullPolicy 就会自动设置为 IfNotPresent

必要的镜像拉取

如果你想总是强制执行拉取,你可以使用下述的一中方式:

  • 设置容器的 imagePullPolicyAlways
  • 省略 imagePullPolicy,并使用 :latest 作为镜像标签; 当你提交 Pod 时,Kubernetes 会将策略设置为 Always
  • 省略 imagePullPolicy 和镜像的标签; 当你提交 Pod 时,Kubernetes 会将策略设置为 Always
  • 启用准入控制器 AlwaysPullImages

ImagePullBackOff

当 kubelet 使用容器运行时创建 Pod 时,容器可能因为 ImagePullBackOff 导致状态为 Waiting

ImagePullBackOff 状态意味着容器无法启动, 因为 Kubernetes 无法拉取容器镜像(原因包括无效的镜像名称,或从私有仓库拉取而没有 imagePullSecret)。 BackOff 部分表示 Kubernetes 将继续尝试拉取镜像,并增加回退延迟。

Kubernetes 会增加每次尝试之间的延迟,直到达到编译限制,即 300 秒(5 分钟)。

带镜像索引的多架构镜像

除了提供二进制的镜像之外,容器仓库也可以提供 容器镜像索引。 镜像索引可以根据特定于体系结构版本的容器指向镜像的多个 镜像清单。 这背后的理念是让你可以为镜像命名(例如:pauseexample/mycontainerkube-apiserver) 的同时,允许不同的系统基于它们所使用的机器体系结构取回正确的二进制镜像。

Kubernetes 自身通常在命名容器镜像时添加后缀 -$(ARCH)。 为了向前兼容,请在生成较老的镜像时也提供后缀。 这里的理念是为某镜像(如 pause)生成针对所有平台都适用的清单时, 生成 pause-amd64 这类镜像,以便较老的配置文件或者将镜像后缀硬编码到其中的 YAML 文件也能兼容。

使用私有仓库

从私有仓库读取镜像时可能需要密钥。 凭证可以用以下方式提供:

  • 配置节点向私有仓库进行身份验证
    • 所有 Pod 均可读取任何已配置的私有仓库
    • 需要集群管理员配置节点
  • 预拉镜像
    • 所有 Pod 都可以使用节点上缓存的所有镜像
    • 需要所有节点的 root 访问权限才能进行设置
  • 在 Pod 中设置 ImagePullSecrets
    • 只有提供自己密钥的 Pod 才能访问私有仓库
  • 特定于厂商的扩展或者本地扩展
    • 如果你在使用定制的节点配置,你(或者云平台提供商)可以实现让节点 向容器仓库认证的机制

下面将详细描述每一项。

配置 Node 对私有仓库认证

设置凭据的具体说明取决于你选择使用的容器运行时和仓库。 你应该参考解决方案的文档来获取最准确的信息。

有关配置私有容器镜像仓库的示例,请参阅任务 从私有镜像库中拉取镜像。 该示例使用 Docker Hub 中的私有注册表。

config.json 说明

对于 config.json 的解释在原始 Docker 实现和 Kubernetes 的解释之间有所不同。 在 Docker 中,auths 键只能指定根 URL ,而 Kubernetes 允许 glob URLs 以及 前缀匹配的路径。这意味着,像这样的 config.json 是有效的:

{
    "auths": {
        "*my-registry.io/images": {
            "auth": "…"
        }
    }
}

使用以下语法匹配根 URL (*my-registry.io):

pattern:
    { term }

term:
    '*'         匹配任何无分隔符字符序列
    '?'         匹配任意单个非分隔符
    '[' [ '^' ] 字符范围
                  字符集(必须非空)
    c           匹配字符 c (c 不为 '*','?','\\','[')
    '\\' c      匹配字符 c

字符范围: 
    c           匹配字符 c (c 不为 '\\','?','-',']')
    '\\' c      匹配字符 c
    lo '-' hi   匹配字符范围在 lo 到 hi 之间字符

现在镜像拉取操作会将每种有效模式的凭据都传递给 CRI 容器运行时。例如下面的容器镜像名称会匹配成功:

  • my-registry.io/images
  • my-registry.io/images/my-image
  • my-registry.io/images/another-image
  • sub.my-registry.io/images/my-image
  • a.sub.my-registry.io/images/my-image

kubelet 为每个找到的凭证的镜像按顺序拉取。 这意味着在 config.json 中可能有多项:

{
    "auths": {
        "my-registry.io/images": {
            "auth": "…"
        },
        "my-registry.io/images/subpath": {
            "auth": "…"
        }
    }
}

如果一个容器指定了要拉取的镜像 my-registry.io/images/subpath/my-image, 并且其中一个失败,kubelet 将尝试从另一个身份验证源下载镜像。

提前拉取镜像

默认情况下,kubelet 会尝试从指定的仓库拉取每个镜像。 但是,如果容器属性 imagePullPolicy 设置为 IfNotPresent 或者 Never, 则会优先使用(对应 IfNotPresent)或者一定使用(对应 Never)本地镜像。

如果你希望使用提前拉取镜像的方法代替仓库认证,就必须保证集群中所有节点提前拉取的镜像是相同的。

这一方案可以用来提前载入指定的镜像以提高速度,或者作为向私有仓库执行身份认证的一种替代方案。

所有的 Pod 都可以使用节点上提前拉取的镜像。

在 Pod 上指定 ImagePullSecrets

Kubernetes 支持在 Pod 中设置容器镜像仓库的密钥。

使用 Docker Config 创建 Secret

你需要知道用于向仓库进行身份验证的用户名、密码和客户端电子邮件地址,以及它的主机名。 运行以下命令,注意替换适当的大写值:

kubectl create secret docker-registry <name> --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL

如果你已经有 Docker 凭据文件,则可以将凭据文件导入为 Kubernetes Secret, 而不是执行上面的命令。 基于已有的 Docker 凭据创建 Secret 解释了如何完成这一操作。

如果你在使用多个私有容器仓库,这种技术将特别有用。 原因是 kubectl create secret docker-registry 创建的是仅适用于某个私有仓库的 Secret。

在 Pod 中引用 ImagePullSecrets

现在,在创建 Pod 时,可以在 Pod 定义中增加 imagePullSecrets 部分来引用该 Secret。

例如:

cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: foo
  namespace: awesomeapps
spec:
  containers:
    - name: foo
      image: janedoe/awesomeapp:v1
  imagePullSecrets:
    - name: myregistrykey
EOF

cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF

你需要对使用私有仓库的每个 Pod 执行以上操作。 不过,设置该字段的过程也可以通过为 服务账号 资源设置 imagePullSecrets 来自动完成。 有关详细指令可参见 将 ImagePullSecrets 添加到服务账号

你也可以将此方法与节点级别的 .docker/config.json 配置结合使用。 来自不同来源的凭据会被合并。

使用案例

配置私有仓库有多种方案,以下是一些常用场景和建议的解决方案。

  1. 集群运行非专有镜像(例如,开源镜像)。镜像不需要隐藏。
    • 使用 Docker hub 上的公开镜像
      • 无需配置
      • 某些云厂商会自动为公开镜像提供高速缓存,以便提升可用性并缩短拉取镜像所需时间
  1. 集群运行一些专有镜像,这些镜像需要对公司外部隐藏,对所有集群用户可见
    • 使用托管的私有 Docker 仓库
      • 可以托管在 Docker Hub 或者其他地方
      • 按照上面的描述,在每个节点上手动配置 .docker/config.json 文件
    • 或者,在防火墙内运行一个组织内部的私有仓库,并开放读取权限
      • 不需要配置 Kubenretes
    • 使用控制镜像访问的托管容器镜像仓库服务
      • 与手动配置节点相比,这种方案能更好地处理集群自动扩缩容
    • 或者,在不方便更改节点配置的集群中,使用 imagePullSecrets
  1. 集群使用专有镜像,且有些镜像需要更严格的访问控制
    • 确保 AlwaysPullImages 准入控制器被启用。否则,所有 Pod 都可以使用所有镜像。
    • 确保将敏感数据存储在 Secret 资源中,而不是将其打包在镜像里
  1. 集群是多租户的并且每个租户需要自己的私有仓库
    • 确保 AlwaysPullImages 准入控制器。否则,所有租户的所有的 Pod 都可以使用所有镜像。
    • 为私有仓库启用鉴权
    • 为每个租户生成访问仓库的凭据,放置在 Secret 中,并将 Secrert 发布到各租户的命名空间下。
    • 租户将 Secret 添加到每个名字空间中的 imagePullSecrets

如果你需要访问多个仓库,可以为每个仓库创建一个 Secret。 kubelet 将所有 imagePullSecrets 合并为一个虚拟的 .docker/config.json 文件。

接下来

3.3.2 - 容器环境

本页描述了在容器环境里容器可用的资源。

容器环境

Kubernetes 的容器环境给容器提供了几个重要的资源:

  • 文件系统,其中包含一个镜像 和一个或多个的
  • 容器自身的信息
  • 集群中其他对象的信息

容器信息

一个容器的 hostname 是该容器运行所在的 Pod 的名称。通过 hostname 命令或者调用 libc 中的 gethostname 函数可以获取该名称。

Pod 名称和命名空间可以通过 下行 API 转换为环境变量。

Pod 定义中的用户所定义的环境变量也可在容器中使用,就像在 container 镜像中静态指定的任何环境变量一样。

集群信息

创建容器时正在运行的所有服务都可用作该容器的环境变量。 这里的服务仅限于新容器的 Pod 所在的名字空间中的服务,以及 Kubernetes 控制面的服务。

对于名为 foo 的服务,当映射到名为 bar 的容器时,定义了以下变量:

FOO_SERVICE_HOST=<其上服务正运行的主机>
FOO_SERVICE_PORT=<其上服务正运行的端口>

服务具有专用的 IP 地址。如果启用了 DNS 插件, 可以在容器中通过 DNS 来访问服务。

接下来

3.3.3 - 容器运行时类(Runtime Class)

特性状态: Kubernetes v1.20 [stable]

本页面描述了 RuntimeClass 资源和运行时的选择机制。

RuntimeClass 是一个用于选择容器运行时配置的特性,容器运行时配置用于运行 Pod 中的容器。

动机

你可以在不同的 Pod 设置不同的 RuntimeClass,以提供性能与安全性之间的平衡。 例如,如果你的部分工作负载需要高级别的信息安全保证,你可以决定在调度这些 Pod 时尽量使它们在使用硬件虚拟化的容器运行时中运行。 这样,你将从这些不同运行时所提供的额外隔离中获益,代价是一些额外的开销。

你还可以使用 RuntimeClass 运行具有相同容器运行时但具有不同设置的 Pod。

设置

  1. 在节点上配置 CRI 的实现(取决于所选用的运行时)
  2. 创建相应的 RuntimeClass 资源

1. 在节点上配置 CRI 实现

RuntimeClass 的配置依赖于 运行时接口(CRI)的实现。 根据你使用的 CRI 实现,查阅相关的文档(下方)来了解如何配置。

所有这些配置都具有相应的 handler 名,并被 RuntimeClass 引用。 handler 必须是有效的 DNS 标签名

2. 创建相应的 RuntimeClass 资源

在上面步骤 1 中,每个配置都需要有一个用于标识配置的 handler。 针对每个 handler 需要创建一个 RuntimeClass 对象。

RuntimeClass 资源当前只有两个重要的字段:RuntimeClass 名 (metadata.name) 和 handler (handler)。 对象定义如下所示:

# RuntimeClass 定义于 node.k8s.io API 组
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  # 用来引用 RuntimeClass 的名字
  # RuntimeClass 是一个集群层面的资源
  name: myclass
# 对应的 CRI 配置的名称
handler: myconfiguration

RuntimeClass 对象的名称必须是有效的 DNS 子域名

使用说明

一旦完成集群中 RuntimeClasses 的配置, 你可以在 Pod spec 中指定 runtimeClassName 来使用它。例如:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  runtimeClassName: myclass
  # ...

这一设置会告诉 kubelet 使用所指的 RuntimeClass 来运行该 pod。 如果所指的 RuntimeClass 不存在或者 CRI 无法运行相应的 handler, 那么 pod 将会进入 Failed 终止阶段。 你可以查看相应的事件, 获取执行过程中的错误信息。

如果未指定 runtimeClassName,则将使用默认的 RuntimeHandler,相当于禁用 RuntimeClass 功能特性。

CRI 配置

关于如何安装 CRI 运行时,请查阅 CRI 安装

containerd

通过 containerd 的 /etc/containerd/config.toml 配置文件来配置运行时 handler。 handler 需要配置在 runtimes 块中:

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.${HANDLER_NAME}]

更详细信息,请查阅 containerd 的配置指南

CRI-O

通过 CRI-O 的 /etc/crio/crio.conf 配置文件来配置运行时 handler。 handler 需要配置在 crio.runtime 表之下:

[crio.runtime.runtimes.${HANDLER_NAME}]
  runtime_path = "${PATH_TO_BINARY}"

更详细信息,请查阅 CRI-O 配置文档

调度

特性状态: Kubernetes v1.16 [beta]

通过为 RuntimeClass 指定 scheduling 字段, 你可以通过设置约束,确保运行该 RuntimeClass 的 Pod 被调度到支持该 RuntimeClass 的节点上。 如果未设置 scheduling,则假定所有节点均支持此 RuntimeClass。

为了确保 pod 会被调度到支持指定运行时的 node 上,每个 node 需要设置一个通用的 label 用于被 runtimeclass.scheduling.nodeSelector 挑选。在 admission 阶段,RuntimeClass 的 nodeSelector 将会与 pod 的 nodeSelector 合并,取二者的交集。如果有冲突,pod 将会被拒绝。

如果 node 需要阻止某些需要特定 RuntimeClass 的 pod,可以在 tolerations 中指定。 与 nodeSelector 一样,tolerations 也在 admission 阶段与 pod 的 tolerations 合并,取二者的并集。

更多有关 node selector 和 tolerations 的配置信息,请查阅 将 Pod 分派到节点

Pod 开销

特性状态: Kubernetes v1.24 [stable]

你可以指定与运行 Pod 相关的 开销 资源。声明开销即允许集群(包括调度器)在决策 Pod 和资源时将其考虑在内。

Pod 开销通过 RuntimeClass 的 overhead 字段定义。 通过使用这个字段,你可以指定使用该 RuntimeClass 运行 Pod 时的开销并确保 Kubernetes 将这些开销计算在内。

接下来

3.3.4 - 容器生命周期回调

这个页面描述了 kubelet 管理的容器如何使用容器生命周期回调框架, 藉由其管理生命周期中的事件触发,运行指定代码。

概述

类似于许多具有生命周期回调组件的编程语言框架,例如 Angular、Kubernetes 为容器提供了生命周期回调。 回调使容器能够了解其管理生命周期中的事件,并在执行相应的生命周期回调时运行在处理程序中实现的代码。

容器回调

有两个回调暴露给容器:

PostStart

这个回调在容器被创建之后立即被执行。 但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。 没有参数传递给处理程序。

PreStop

在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等) 而被终止之前,此回调会被调用。 如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。 在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。 Pod 的终止宽限周期在 PreStop 回调被执行之前即开始计数, 所以无论回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。 没有参数会被传递给处理程序。

有关终止行为的更详细描述,请参见 终止 Pod

回调处理程序的实现

容器可以通过实现和注册该回调的处理程序来访问该回调。 针对容器,有两种类型的回调处理程序可供实现:

  • Exec - 在容器的 cgroups 和名字空间中执行特定的命令(例如 pre-stop.sh)。 命令所消耗的资源计入容器的资源消耗。
  • HTTP - 对容器上的特定端点执行 HTTP 请求。

回调处理程序执行

当调用容器生命周期管理回调时,Kubernetes 管理系统根据回调动作执行其处理程序, httpGettcpSocket 在 kubelet 进程执行,而 exec 则由容器内执行。

回调处理程序调用在包含容器的 Pod 上下文中是同步的。 这意味着对于 PostStart 回调,容器入口点和回调异步触发。 但是,如果回调运行或挂起的时间太长,则容器无法达到 running 状态。

PreStop 回调并不会与停止容器的信号处理程序异步执行;回调必须在可以发送信号之前完成执行。 如果 PreStop 回调在执行期间停滞不前,Pod 的阶段会变成 Terminating并且一直处于该状态, 直到其 terminationGracePeriodSeconds 耗尽为止,这时 Pod 会被杀死。 这一宽限期是针对 PreStop 回调的执行时间及容器正常停止时间的总和而言的。 例如,如果 terminationGracePeriodSeconds 是 60,回调函数花了 55 秒钟完成执行, 而容器在收到信号之后花了 10 秒钟来正常结束,那么容器会在其能够正常结束之前即被杀死, 因为 terminationGracePeriodSeconds 的值小于后面两件事情所花费的总时间(55+10)。

如果 PostStartPreStop 回调失败,它会杀死容器。

用户应该使他们的回调处理程序尽可能的轻量级。 但也需要考虑长时间运行的命令也很有用的情况,比如在停止容器之前保存状态。

回调递送保证

回调的递送应该是至少一次,这意味着对于任何给定的事件, 例如 PostStartPreStop,回调可以被调用多次。 如何正确处理被多次调用的情况,是回调实现所要考虑的问题。

通常情况下,只会进行单次递送。 例如,如果 HTTP 回调接收器宕机,无法接收流量,则不会尝试重新发送。 然而,偶尔也会发生重复递送的可能。 例如,如果 kubelet 在发送回调的过程中重新启动,回调可能会在 kubelet 恢复后重新发送。

调试回调处理程序

回调处理程序的日志不会在 Pod 事件中公开。 如果处理程序由于某种原因失败,它将播放一个事件。 对于 PostStart,这是 FailedPostStartHook 事件,对于 PreStop,这是 FailedPreStopHook 事件。 要自己生成失败的 FailedPreStopHook 事件,请修改 lifecycle-events.yaml 文件将 postStart 命令更改为 “badcommand” 并应用它。 以下是通过运行 kubectl describe pod lifecycle-demo 后你看到的一些结果事件的示例输出:

Events:
  Type     Reason               Age              From               Message
  ----     ------               ----             ----               -------
  Normal   Scheduled            7s               default-scheduler  Successfully assigned default/lifecycle-demo to ip-XXX-XXX-XX-XX.us-east-2...
  Normal   Pulled               6s               kubelet            Successfully pulled image "nginx" in 229.604315ms
  Normal   Pulling              4s (x2 over 6s)  kubelet            Pulling image "nginx"
  Normal   Created              4s (x2 over 5s)  kubelet            Created container lifecycle-demo-container
  Normal   Started              4s (x2 over 5s)  kubelet            Started container lifecycle-demo-container
  Warning  FailedPostStartHook  4s (x2 over 5s)  kubelet            Exec lifecycle hook ([badcommand]) for Container "lifecycle-demo-container" in Pod "lifecycle-demo_default(30229739-9651-4e5a-9a32-a8f1688862db)" failed - error: command 'badcommand' exited with 126: , message: "OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: \"badcommand\": executable file not found in $PATH: unknown\r\n"
  Normal   Killing              4s (x2 over 5s)  kubelet            FailedPostStartHook
  Normal   Pulled               4s               kubelet            Successfully pulled image "nginx" in 215.66395ms
  Warning  BackOff              2s (x2 over 3s)  kubelet            Back-off restarting failed container

接下来

3.4 - Kubernetes 中的 Windows

3.4.1 - Kubernetes 中的 Windows 容器

在许多组织中,所运行的很大一部分服务和应用是 Windows 应用。 Windows 容器提供了一种封装进程和包依赖项的方式, 从而简化了 DevOps 实践,令 Windows 应用程序同样遵从云原生模式。

对于同时投入基于 Windows 应用和 Linux 应用的组织而言,他们不必寻找不同的编排系统来管理其工作负载, 使其跨部署的运营效率得以大幅提升,而不必关心所用的操作系统。

Kubernetes 中的 Windows 节点

若要在 Kubernetes 中启用对 Windows 容器的编排,可以在现有的 Linux 集群中包含 Windows 节点。 在 Kubernetes 上调度 Pod 中的 Windows 容器与调度基于 Linux 的容器类似。

为了运行 Windows 容器,你的 Kubernetes 集群必须包含多个操作系统。 尽管你只能在 Linux 上运行控制平面, 你可以部署运行 Windows 或 Linux 的工作节点。

支持 Windows 节点的前提是操作系统为 Windows Server 2019。

本文使用术语 Windows 容器表示具有进程隔离能力的 Windows 容器。 Kubernetes 不支持使用 Hyper-V 隔离能力来运行 Windows 容器。

兼容性与局限性

某些节点层面的功能特性仅在使用特定容器运行时时才可用; 另外一些特性则在 Windows 节点上不可用,包括:

  • 巨页(HugePages):Windows 容器当前不支持。
  • 特权容器:Windows 容器当前不支持。 HostProcess 容器提供类似功能。
  • TerminationGracePeriod:需要 containerD。

Windows 节点并不支持共享命名空间的所有功能特性。 有关更多详细信息,请参考 API 兼容性

有关 Kubernetes 测试时所使用的 Windows 版本的详细信息,请参考 Windows 操作系统版本兼容性

从 API 和 kubectl 的角度来看,Windows 容器的行为与基于 Linux 的容器非常相似。 然而,在本节所概述的一些关键功能上,二者存在一些显著差异。

与 Linux 比较

Kubernetes 关键组件在 Windows 上的工作方式与在 Linux 上相同。 本节介绍几个关键的工作负载抽象及其如何映射到 Windows。

  • Pod

    Pod 是 Kubernetes 的基本构建块,是可以创建或部署的最小和最简单的单元。 你不可以在同一个 Pod 中部署 Windows 和 Linux 容器。 Pod 中的所有容器都调度到同一 Node 上,每个 Node 代表一个特定的平台和体系结构。 Windows 容器支持以下 Pod 能力、属性和事件:

    • 每个 Pod 有一个或多个容器,具有进程隔离和卷共享能力

    • Pod status 字段

    • 就绪、存活和启动探针

    • postStart 和 preStop 容器生命周期回调

    • ConfigMap 和 Secret:作为环境变量或卷

    • emptyDir

    • 命名管道形式的主机挂载

    • 资源限制

    • 操作系统字段:

      .spec.os.name 字段应设置为 windows 以表明当前 Pod 使用 Windows 容器。 需要启用 IdentifyPodOS 特性门控才能让这个字段被识别。

      如果 IdentifyPodOS 特性门控已启用并且你将 .spec.os.name 字段设置为 windows, 则你不得在对应 Pod 的 .spec 中设置以下字段:

      • spec.hostPID
      • spec.hostIPC
      • spec.securityContext.seLinuxOptions
      • spec.securityContext.seccompProfile
      • spec.securityContext.fsGroup
      • spec.securityContext.fsGroupChangePolicy
      • spec.securityContext.sysctls
      • spec.shareProcessNamespace
      • spec.securityContext.runAsUser
      • spec.securityContext.runAsGroup
      • spec.securityContext.supplementalGroups
      • spec.containers[*].securityContext.seLinuxOptions
      • spec.containers[*].securityContext.seccompProfile
      • spec.containers[*].securityContext.capabilities
      • spec.containers[*].securityContext.readOnlyRootFilesystem
      • spec.containers[*].securityContext.privileged
      • spec.containers[*].securityContext.allowPrivilegeEscalation
      • spec.containers[*].securityContext.procMount
      • spec.containers[*].securityContext.runAsUser
      • spec.containers[*].securityContext.runAsGroup

      在上述列表中,通配符(*)表示列表中的所有项。 例如,spec.containers[*].securityContext 指代所有容器的 SecurityContext 对象。 如果指定了这些字段中的任意一个,则 API 服务器不会接受此 Pod。

Pod、工作负载资源和 Service 是在 Kubernetes 上管理 Windows 工作负载的关键元素。 然而,它们本身还不足以在动态的云原生环境中对 Windows 工作负载进行恰当的生命周期管理。 Kubernetes 还支持:

kubelet 的命令行选项

某些 kubelet 命令行选项在 Windows 上的行为不同,如下所述:

  • --windows-priorityclass 允许你设置 kubelet 进程的调度优先级 (参考 CPU 资源管理)。
  • --kubelet-reserve--system-reserve--eviction-hard 标志更新 NodeAllocatable
  • 未实现使用 --enforce-node-allocable 驱逐。
  • 未实现使用 --eviction-hard--eviction-soft 驱逐。
  • 在 Windows 节点上运行时,kubelet 没有内存或 CPU 限制。 --kube-reserved--system-reserved 仅从 NodeAllocatable 中减去,并且不保证为工作负载提供的资源。 有关更多信息,请参考 Windows 节点的资源管理
  • 未实现 MemoryPressure 条件。
  • kubelet 不会执行 OOM 驱逐操作。

API 兼容性

由于操作系统和容器运行时的缘故,Kubernetes API 在 Windows 上的工作方式存在细微差异。 某些工作负载属性是为 Linux 设计的,无法在 Windows 上运行。

从较高的层面来看,以下操作系统概念是不同的:

  • 身份 - Linux 使用 userID(UID)和 groupID(GID),表示为整数类型。 用户名和组名是不规范的,它们只是 /etc/groups/etc/passwd 中的别名, 作为 UID+GID 的后备标识。 Windows 使用更大的二进制安全标识符(SID), 存放在 Windows 安全访问管理器(Security Access Manager,SAM)数据库中。 此数据库在主机和容器之间或容器之间不共享。
  • 文件权限 - Windows 使用基于 SID 的访问控制列表, 而像 Linux 使用基于对象权限和 UID+GID 的位掩码(POSIX 系统)以及可选的访问控制列表。
  • 文件路径 - Windows 上的约定是使用 \ 而不是 /。 Go IO 库通常接受两者,能让其正常工作,但当你设置要在容器内解读的路径或命令行时, 可能需要用 \
  • 信号 - Windows 交互式应用处理终止的方式不同,可以实现以下一种或多种:
    • UI 线程处理包括 WM_CLOSE 在内准确定义的消息。
    • 控制台应用使用控制处理程序(Control Handler)处理 Ctrl-C 或 Ctrl-Break。
    • 服务会注册可接受 SERVICE_CONTROL_STOP 控制码的服务控制处理程序(Service Control Handler)函数。

容器退出码遵循相同的约定,其中 0 表示成功,非零表示失败。 具体的错误码在 Windows 和 Linux 中可能不同。 但是,从 Kubernetes 组件(kubelet、kube-proxy)传递的退出码保持不变。

容器规范的字段兼容性

以下列表记录了 Pod 容器规范在 Windows 和 Linux 之间的工作方式差异:

  • 巨页(Huge page)在 Windows 容器运行时中未实现,且不可用。 巨页需要不可为容器配置的用户特权生效
  • requests.cpurequests.memory - 从节点可用资源中减去请求,因此请求可用于避免一个节点过量供应。 但是,请求不能用于保证已过量供应的节点中的资源。 如果运营商想要完全避免过量供应,则应将设置请求作为最佳实践应用到所有容器。
  • securityContext.allowPrivilegeEscalation - 不能在 Windows 上使用;所有权能字都无法生效。
  • securityContext.capabilities - POSIX 权能未在 Windows 上实现。
  • securityContext.privileged - Windows 不支持特权容器。
  • securityContext.procMount - Windows 没有 /proc 文件系统。
  • securityContext.readOnlyRootFilesystem - 不能在 Windows 上使用;对于容器内运行的注册表和系统进程,写入权限是必需的。
  • securityContext.runAsGroup - 不能在 Windows 上使用,因为不支持 GID。
  • securityContext.runAsNonRoot - 此设置将阻止以 ContainerAdministrator 身份运行容器,这是 Windows 上与 root 用户最接近的身份。
  • securityContext.runAsUser - 改用 runAsUserName
  • securityContext.seLinuxOptions - 不能在 Windows 上使用,因为 SELinux 特定于 Linux。
  • terminationMessagePath - 这个字段有一些限制,因为 Windows 不支持映射单个文件。 默认值为 /dev/termination-log,因为默认情况下它在 Windows 上不存在,所以能生效。
Pod 规范的字段兼容性

以下列表记录了 Pod 规范在 Windows 和 Linux 之间的工作方式差异:

  • hostIPChostpid - 不能在 Windows 上共享主机命名空间。
  • hostNetwork - Windows 操作系统不支持共享主机网络。
  • dnsPolicy - Windows 不支持将 Pod dnsPolicy 设为 ClusterFirstWithHostNet, 因为未提供主机网络。Pod 始终用容器网络运行。
  • podSecurityContext(参见下文)
  • shareProcessNamespace - 这是一个 beta 版功能特性,依赖于 Windows 上未实现的 Linux 命名空间。 Windows 无法共享进程命名空间或容器的根文件系统(root filesystem)。 只能共享网络。
  • terminationGracePeriodSeconds - 这在 Windows 上的 Docker 中没有完全实现, 请参考 GitHub issue。 目前的行为是通过 CTRL_SHUTDOWN_EVENT 发送 ENTRYPOINT 进程,然后 Windows 默认等待 5 秒, 最后使用正常的 Windows 关机行为终止所有进程。 5 秒默认值实际上位于容器内的 Windows 注册表中, 因此在构建容器时可以覆盖这个值。
  • volumeDevices - 这是一个 beta 版功能特性,未在 Windows 上实现。 Windows 无法将原始块设备挂接到 Pod。
  • volumes
    • 如果你定义一个 emptyDir 卷,则你无法将卷源设为 memory
  • 你无法为卷挂载启用 mountPropagation,因为这在 Windows 上不支持。
Pod 安全上下文的字段兼容性

Pod 的所有 securityContext 字段都无法在 Windows 上生效。

节点问题检测器

节点问题检测器(参考节点健康监测)初步支持 Windows。 有关更多信息,请访问该项目的 GitHub 页面

Pause 容器

在 Kubernetes Pod 中,首先创建一个基础容器或 “pause” 容器来承载容器。 在 Linux 中,构成 Pod 的 cgroup 和命名空间维持持续存在需要一个进程; 而 pause 进程就提供了这个功能。 属于同一 Pod 的容器(包括基础容器和工作容器)共享一个公共网络端点 (相同的 IPv4 和/或 IPv6 地址,相同的网络端口空间)。 Kubernetes 使用 pause 容器以允许工作容器崩溃或重启,而不会丢失任何网络配置。

Kubernetes 维护一个多体系结构的镜像,包括对 Windows 的支持。 对于 Kubernetes v1.24,推荐的 pause 镜像为 k8s.gcr.io/pause:3.6。 可在 GitHub 上获得源代码

Microsoft 维护一个不同的多体系结构镜像,支持 Linux 和 Windows amd64, 你可以找到的镜像类似 mcr.microsoft.com/oss/kubernetes/pause:3.6。 此镜像的构建与 Kubernetes 维护的镜像同源,但所有 Windows 可执行文件均由 Microsoft 进行了验证码签名。 如果你正部署到一个需要签名可执行文件的生产或类生产环境, Kubernetes 项目建议使用 Microsoft 维护的镜像。

容器运行时

你需要将容器运行时安装到集群中的每个节点, 这样 Pod 才能在这些节点上运行。

以下容器运行时适用于 Windows:

ContainerD

特性状态: Kubernetes v1.20 [stable]

对于运行 Windows 的 Kubernetes 节点,你可以使用 ContainerD 1.4.0+ 作为容器运行时。

学习如何在 Windows 上安装 ContainerD

Mirantis 容器运行时

Mirantis 容器运行时(MCR) 可作为所有 Windows Server 2019 和更高版本的容器运行时。

有关更多信息,请参考在 Windows Server 上安装 MCR

Windows 操作系统版本兼容性

在 Windows 节点上,如果主机操作系统版本必须与容器基础镜像操作系统版本匹配, 则会应用严格的兼容性规则。 仅 Windows Server 2019 作为容器操作系统时,才能完全支持 Windows 容器。

对于 Kubernetes v1.24,Windows 节点(和 Pod)的操作系统兼容性如下:

Windows Server LTSC release
Windows Server 2019
Windows Server 2022
Windows Server SAC release
Windows Server version 20H2

也适用 Kubernetes 版本偏差策略

获取帮助和故障排查

对 Kubernetes 集群进行故障排查的主要帮助来源应始于故障排查页面。

本节包括了一些其他特定于 Windows 的故障排查帮助。 日志是解决 Kubernetes 中问题的重要元素。 确保在任何时候向其他贡献者寻求故障排查协助时随附了日志信息。 遵照 SIG Windows 日志收集贡献指南中的指示说明。

报告问题和功能请求

如果你发现疑似 bug,或者你想提出功能请求,请按照 SIG Windows 贡献指南 新建一个 Issue。 你应该先搜索 issue 列表,以防之前报告过这个问题,凭你对该问题的经验添加评论, 并随附日志信息。 Kubernetes Slack 上的 SIG Windows 频道也是一个很好的途径, 可以在创建工单之前获得一些初始支持和故障排查思路。

接下来

部署工具

kubeadm 工具帮助你部署 Kubernetes 集群,提供管理集群的控制平面以及运行工作负载的节点。 添加 Windows 节点阐述了如何使用 kubeadm 将 Windows 节点部署到你的集群。

Kubernetes 集群 API 项目也提供了自动部署 Windows 节点的方式。

Windows 分发渠道

有关 Windows 分发渠道的详细阐述,请参考 Microsoft 文档

有关支持模型在内的不同 Windows Server 服务渠道的信息,请参考 Windows Server 服务渠道

3.4.2 - Kubernetes 中的 Windows 容器调度指南

在许多组织中运行的服务和应用程序中,Windows 应用程序构成了很大一部分。 本指南将引导你完成在 Kubernetes 中配置和部署 Windows 容器的步骤。

目标

  • 配置 Deployment 样例以在 Windows 节点上运行 Windows 容器
  • 在 Kubernetes 中突出 Windows 特定的功能

在你开始之前

  • 创建一个 Kubernetes 集群,其中包含一个控制平面和一个运行 Windows Server 的工作节点
  • 务必请注意,在 Kubernetes 上创建和部署服务和工作负载的行为方式与 Linux 和 Windows 容器的行为方式大致相同。 与集群交互的 kubectl 命令是一致的。 下一小节的示例旨在帮助你快速开始使用 Windows 容器。

快速开始:部署 Windows 容器

以下示例 YAML 文件部署了一个在 Windows 容器内运行的简单 Web 服务器的应用程序。

创建一个名为 win-webserver.yaml 的 Service 规约,其内容如下:

apiVersion: v1
kind: Service
metadata:
  name: win-webserver
  labels:
    app: win-webserver
spec:
  ports:
    # 此 Service 服务的端口
    - port: 80
      targetPort: 80
  selector:
    app: win-webserver
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: win-webserver
  name: win-webserver
spec:
  replicas: 2
  selector:
    matchLabels:
      app: win-webserver
  template:
    metadata:
      labels:
        app: win-webserver
      name: win-webserver
    spec:
     containers:
      - name: windowswebserver
        image: mcr.microsoft.com/windows/servercore:ltsc2019
        command:
        - powershell.exe
        - -command
        - "<#code used from https://gist.github.com/19WAS85/5424431#> ; $$listener = New-Object System.Net.HttpListener ; $$listener.Prefixes.Add('http://*:80/') ; $$listener.Start() ; $$callerCounts = @{} ; Write-Host('Listening at http://*:80/') ; while ($$listener.IsListening) { ;$$context = $$listener.GetContext() ;$$requestUrl = $$context.Request.Url ;$$clientIP = $$context.Request.RemoteEndPoint.Address ;$$response = $$context.Response ;Write-Host '' ;Write-Host('> {0}' -f $$requestUrl) ;  ;$$count = 1 ;$$k=$$callerCounts.Get_Item($$clientIP) ;if ($$k -ne $$null) { $$count += $$k } ;$$callerCounts.Set_Item($$clientIP, $$count) ;$$ip=(Get-NetAdapter | Get-NetIpAddress); $$header='<html><body><H1>Windows Container Web Server</H1>' ;$$callerCountsString='' ;$$callerCounts.Keys | % { $$callerCountsString+='<p>IP {0} callerCount {1} ' -f $$ip[1].IPAddress,$$callerCounts.Item($$_) } ;$$footer='</body></html>' ;$$content='{0}{1}{2}' -f $$header,$$callerCountsString,$$footer ;Write-Output $$content ;$$buffer = [System.Text.Encoding]::UTF8.GetBytes($$content) ;$$response.ContentLength64 = $$buffer.Length ;$$response.OutputStream.Write($$buffer, 0, $$buffer.Length) ;$$response.Close() ;$$responseStatus = $$response.StatusCode ;Write-Host('< {0}' -f $$responseStatus)  } ; "
     nodeSelector:
      kubernetes.io/os: windows
  1. 检查所有节点是否健康

    kubectl get nodes
    
  1. 部署 Service 并监视 Pod 更新:

    kubectl apply -f win-webserver.yaml
    kubectl get pods -o wide -w
    

    当 Service 被正确部署时,两个 Pod 都被标记为就绪(Ready)。要退出 watch 命令,请按 Ctrl+C。

  1. 检查部署是否成功。请验证:

    • 当执行 kubectl get pods 命令时,能够从 Linux 控制平面所在的节点上列出两个 Pod。
    • 跨网络的节点到 Pod 通信,从 Linux 控制平面所在的节点上执行 curl 命令来访问 Pod IP 的 80 端口以检查 Web 服务器响应。
    • Pod 间通信,使用 docker execkubectl exec 命令进入容器,并在 Pod 之间(以及跨主机,如果你有多个 Windows 节点)相互进行 ping 操作。
    • Service 到 Pod 的通信,在 Linux 控制平面所在的节点以及独立的 Pod 中执行 curl 命令来访问虚拟的服务 IP(在 kubectl get services 命令下查看)。
    • 服务发现,执行 curl 命令来访问带有 Kubernetes 默认 DNS 后缀的服务名称。
    • 入站连接,在 Linux 控制平面所在的节点上或集群外的机器上执行 curl 命令来访问 NodePort 服务。
    • 出站连接,使用 kubectl exec,从 Pod 内部执行 curl 访问外部 IP。

可观察性

捕捉来自工作负载的日志

日志是可观察性的重要元素;它们使用户能够深入了解工作负载的运行情况,并且是解决问题的关键因素。 由于 Windows 容器和 Windows 容器中的工作负载与 Linux 容器的行为不同,因此用户很难收集日志,从而限制了操作可见性。 例如,Windows 工作负载通常配置为记录到 ETW(Windows 事件跟踪)或向应用程序事件日志推送条目。 LogMonitor 是一个微软开源的工具,是监视 Windows 容器内所配置的日志源的推荐方法。 LogMonitor 支持监视事件日志、ETW 提供程序和自定义应用程序日志,将它们传送到 STDOUT 以供 kubectl logs <pod> 使用。

按照 LogMonitor GitHub 页面中的说明,将其二进制文件和配置文件复制到所有容器, 并为 LogMonitor 添加必要的入口点以将日志推送到标准输出(STDOUT)。

配置容器用户

使用可配置的容器用户名

Windows 容器可以配置为使用不同于镜像默认值的用户名来运行其入口点和进程。 在这里了解更多信息。

使用组托管服务帐户(GMSA)管理工作负载身份

Windows 容器工作负载可以配置为使用组托管服务帐户(Group Managed Service Accounts,GMSA)。 组托管服务帐户是一种特定类型的活动目录(Active Directory)帐户,可提供自动密码管理、 简化的服务主体名称(Service Principal Name,SPN)管理,以及将管理委派给多个服务器上的其他管理员的能力。 配置了 GMSA 的容器可以携带使用 GMSA 配置的身份访问外部活动目录域资源。 在此处了解有关为 Windows 容器配置和使用 GMSA 的更多信息。

污点和容忍度

用户需要使用某种污点(Taint)和节点选择器的组合,以便将 Linux 和 Windows 工作负载各自调度到特定操作系统的节点。 下面概述了推荐的方法,其主要目标之一是该方法不应破坏现有 Linux 工作负载的兼容性。

如果启用了 IdentifyPodOS 特性门控, 你可以(并且应该)将 Pod 的 .spec.os.name 设置为该 Pod 中的容器设计所用于的操作系统。 对于运行 Linux 容器的 Pod,将 .spec.os.name 设置为 linux。 对于运行 Windows 容器的 Pod,将 .spec.os.name 设置为 Windows

调度器在将 Pod 分配到节点时并不使用 .spec.os.name 的值。 你应该使用正常的 Kubernetes 机制将 Pod 分配给节点, 以确保集群的控制平面将 Pod 放置到运行适当操作系统的节点上。

.spec.os.name 值对 Windows Pod 的调度没有影响, 因此仍然需要污点和容忍以及节点选择器来确保 Windows Pod 落在适当的 Windows 节点。

确保特定于操作系统的工作负载落到合适的容器主机上

用户可以使用污点(Taint)和容忍度(Toleration)确保将 Windows 容器调度至合适的主机上。 现在,所有的 Kubernetes 节点都有以下默认标签:

  • kubernetes.io/os = [windows|linux]
  • kubernetes.io/arch = [amd64|arm64|...]

如果 Pod 规约没有指定像 "kubernetes.io/os": windows 这样的 nodeSelector, 则 Pod 可以被调度到任何主机上,Windows 或 Linux。 这可能会有问题,因为 Windows 容器只能在 Windows 上运行,而 Linux 容器只能在 Linux 上运行。 最佳实践是使用 nodeSelector。

但是,我们了解到,在许多情况下,用户已经预先存在大量 Linux 容器部署, 以及现成配置的生态系统,例如社区中的 Helm Chart 包和程序化的 Pod 生成案例,例如 Operator。 在这些情况下,你可能不愿更改配置来添加节点选择器。 另一种方法是使用污点。因为 kubelet 可以在注册过程中设置污点, 所以可以很容易地修改为,当只能在 Windows 上运行时,自动添加污点。

例如:--register-with-taints='os=windows:NoSchedule'

通过向所有 Windows 节点添加污点,任何负载都不会被调度到这些节点上(包括现有的 Linux Pod)。 为了在 Windows 节点上调度 Windows Pod,它需要 nodeSelector 和匹配合适的容忍度来选择 Windows。

nodeSelector:
    kubernetes.io/os: windows
    node.kubernetes.io/windows-build: '10.0.17763'
tolerations:
    - key: "os"
      operator: "Equal"
      value: "windows"
      effect: "NoSchedule"

处理同一集群中的多个 Windows 版本

每个 Pod 使用的 Windows Server 版本必须与节点的版本匹配。 如果要在同一个集群中使用多个 Windows Server 版本,则应设置额外的节点标签和节点选择器。

Kubernetes 1.17 自动添加了一个新标签 node.kubernetes.io/windows-build 来简化这一点。 如果你运行的是旧版本,则建议手动将此标签添加到 Windows 节点。

此标签反映了需要匹配以实现兼容性的 Windows 主要、次要和内部版本号。 以下是目前用于每个 Windows Server 版本的值。

产品名称构建号
Windows Server 201910.0.17763
Windows Server, Version 20H210.0.19042
Windows Server 202210.0.20348

使用 RuntimeClass 进行简化

RuntimeClass 可用于简化使用污点和容忍度的流程。 集群管理员可以创建一个用于封装这些污点和容忍度的 RuntimeClass 对象。

  1. 将此文件保存到 runtimeClasses.yml。它包括针对 Windows 操作系统、架构和版本的 nodeSelector
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: windows-2019
handler: 'docker'
scheduling:
  nodeSelector:
    kubernetes.io/os: 'windows'
    kubernetes.io/arch: 'amd64'
    node.kubernetes.io/windows-build: '10.0.17763'
  tolerations:
  - effect: NoSchedule
    key: os
    operator: Equal
    value: "windows"
  1. 以集群管理员身份运行 kubectl create -f runtimeClasses.yml
  2. 根据情况,向 Pod 规约中添加 runtimeClassName: windows-2019

例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: iis-2019
  labels:
    app: iis-2019
spec:
  replicas: 1
  template:
    metadata:
      name: iis-2019
      labels:
        app: iis-2019
    spec:
      runtimeClassName: windows-2019
      containers:
      - name: iis
        image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
        resources:
          limits:
            cpu: 1
            memory: 800Mi
          requests:
            cpu: .1
            memory: 300Mi
        ports:
          - containerPort: 80
 selector:
    matchLabels:
      app: iis-2019
---
apiVersion: v1
kind: Service
metadata:
  name: iis
spec:
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
  selector:
    app: iis-2019

3.5 - 工作负载

理解 Pods,Kubernetes 中可部署的最小计算对象,以及辅助它运行它们的高层抽象对象。
工作负载是在 Kubernetes 上运行的应用程序。

在 Kubernetes 中,无论你的负载是由单个组件还是由多个一同工作的组件构成, 你都可以在一组 Pod 中运行它。 在 Kubernetes 中,Pod 代表的是集群上处于运行状态的一组 容器 的集合。

Kubernetes Pods 遵循预定义的生命周期。 例如,当在你的集群中运行了某个 Pod,但是 Pod 所在的 节点 出现致命错误时, 所有该节点上的 Pods 的状态都会变成失败。Kubernetes 将这类失败视为最终状态: 即使该节点后来恢复正常运行,你也需要创建新的 Pod 以恢复应用。

不过,为了减轻用户的使用负担,通常不需要用户直接管理每个 Pod。 而是使用负载资源来替用户管理一组 Pod。 这些负载资源通过配置 控制器 来确保正确类型的、处于运行状态的 Pod 个数是正确的,与用户所指定的状态相一致。

Kubernetes 提供若干种内置的工作负载资源:

  • DeploymentReplicaSet (替换原来的资源 ReplicationController)。 Deployment 很适合用来管理你的集群上的无状态应用,Deployment 中的所有 Pod 都是相互等价的,并且在需要的时候被替换。
  • StatefulSet 让你能够运行一个或者多个以某种方式跟踪应用状态的 Pods。 例如,如果你的负载会将数据作持久存储,你可以运行一个 StatefulSet,将每个 Pod 与某个 PersistentVolume 对应起来。你在 StatefulSet 中各个 Pod 内运行的代码可以将数据复制到同一 StatefulSet 中的其它 Pod 中以提高整体的服务可靠性。
  • DaemonSet 定义提供节点本地支撑设施的 Pods。这些 Pods 可能对于你的集群的运维是 非常重要的,例如作为网络链接的辅助工具或者作为网络 插件 的一部分等等。每次你向集群中添加一个新节点时,如果该节点与某 DaemonSet 的规约匹配,则控制面会为该 DaemonSet 调度一个 Pod 到该新节点上运行。
  • JobCronJob。 定义一些一直运行到结束并停止的任务。Job 用来表达的是一次性的任务,而 CronJob 会根据其时间规划反复运行。

在庞大的 Kubernetes 生态系统中,你还可以找到一些提供额外操作的第三方工作负载相关的资源。 通过使用定制资源定义(CRD), 你可以添加第三方工作负载资源,以完成原本不是 Kubernetes 核心功能的工作。 例如,如果你希望运行一组 Pods,但要求所有 Pods 都可用时才执行操作 (比如针对某种高吞吐量的分布式任务),你可以基于定制资源实现一个能够满足这一需求的扩展, 并将其安装到集群中运行。

接下来

除了阅读了解每类资源外,你还可以了解与这些资源相关的任务:

要了解 Kubernetes 将代码与配置分离的实现机制,可参阅配置部分

关于 Kubernetes 如何为应用管理 Pods,还有两个支撑概念能够提供相关背景信息:

  • 垃圾收集机制负责在 对象的 属主资源 被删除时在集群中清理这些对象。
  • Time-to-Live 控制器会在 Job 结束之后的指定时间间隔之后删除它们。

一旦你的应用处于运行状态,你就可能想要以 Service 的形式使之可在互联网上访问;或者对于 Web 应用而言,使用 Ingress 资源将其暴露到互联网上。

3.5.1 - Pod

Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。

Pod(就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个) 容器; 这些容器共享存储、网络、以及怎样运行这些容器的声明。 Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。 Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器, 这些容器相对紧密地耦合在一起。 在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。

除了应用容器,Pod 还可以包含在 Pod 启动期间运行的 Init 容器。 你也可以在集群中支持临时性容器 的情况下,为调试的目的注入临时性容器。

什么是 Pod?

Pod 的共享上下文包括一组 Linux 名字空间、控制组(cgroup)和可能一些其他的隔离方面, 即用来隔离 Docker 容器的技术。 在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。

就 Docker 概念的术语而言,Pod 类似于共享名字空间和文件系统卷的一组 Docker 容器。

使用 Pod

下面是一个 Pod 示例,它由一个运行镜像 nginx:1.14.2 的容器组成。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

要创建上面显示的 Pod,请运行以下命令:

kubectl apply -f https://k8s.io/examples/pods/simple-pod.yaml

Pod 通常不是直接创建的,而是使用工作负载资源创建的。 有关如何将 Pod 用于工作负载资源的更多信息,请参阅使用 Pod

用于管理 pod 的工作负载资源

通常你不需要直接创建 Pod,甚至单实例 Pod。 相反,你会使用诸如 DeploymentJob 这类工作负载资源来创建 Pod。 如果 Pod 需要跟踪状态,可以考虑 StatefulSet 资源。

Kubernetes 集群中的 Pod 主要有两种用法:

  • 运行单个容器的 Pod。"每个 Pod 一个容器" 模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。

  • 运行多个协同工作的容器的 Pod。 Pod 可能封装由多个紧密耦合且需要共享资源的共处容器组成的应用程序。 这些位于同一位置的容器可能形成单个内聚的服务单元 —— 一个容器将文件从共享卷提供给公众, 而另一个单独的 “边车”(sidecar)容器则刷新或更新这些文件。 Pod 将这些容器和存储资源打包为一个可管理的实体。

每个 Pod 都旨在运行给定应用程序的单个实例。如果希望横向扩展应用程序 (例如,运行多个实例以提供更多的资源),则应该使用多个 Pod,每个实例使用一个 Pod。 在 Kubernetes 中,这通常被称为副本(Replication)。 通常使用一种工作负载资源及其控制器 来创建和管理一组 Pod 副本。

参见 Pod 和控制器以了解 Kubernetes 如何使用工作负载资源及其控制器以实现应用的扩缩和自动修复。

Pod 怎样管理多个容器

Pod 被设计成支持形成内聚服务单元的多个协作过程(形式为容器)。 Pod 中的容器被自动安排到集群中的同一物理机或虚拟机上,并可以一起进行调度。 容器之间可以共享资源和依赖、彼此通信、协调何时以及何种方式终止自身。

例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的 "边车 (sidercar)" 容器负责从远端更新这些文件,如下图所示:

Pod 创建示意图

有些 Pod 具有 Init 容器应用容器。 Init 容器会在启动应用容器之前运行并完成。

Pod 天生地为其成员容器提供了两种共享资源:网络存储

使用 Pod

你很少在 Kubernetes 中直接创建一个个的 Pod,甚至是单实例(Singleton)的 Pod。 这是因为 Pod 被设计成了相对临时性的、用后即抛的一次性实体。 当 Pod 由你或者间接地由控制器 创建时,它被调度在集群中的节点上运行。 Pod 会保持在该节点上运行,直到 Pod 结束执行、Pod 对象被删除、Pod 因资源不足而被驱逐或者节点失效为止。

当你为 Pod 对象创建清单时,要确保所指定的 Pod 名称是合法的 DNS 子域名

Pod 和控制器

你可以使用工作负载资源来创建和管理多个 Pod。 资源的控制器能够处理副本的管理、上线,并在 Pod 失效时提供自愈能力。 例如,如果一个节点失败,控制器注意到该节点上的 Pod 已经停止工作, 就可以创建替换性的 Pod。调度器会将替身 Pod 调度到一个健康的节点执行。

下面是一些管理一个或者多个 Pod 的工作负载资源的示例:

Pod 模板

负载资源的控制器通常使用 **Pod 模板(Pod Template)**来替你创建 Pod 并管理它们。

Pod 模板是包含在工作负载对象中的规范,用来创建 Pod。这类负载资源包括 DeploymentJobDaemonSets 等。

工作负载的控制器会使用负载对象中的 PodTemplate 来生成实际的 Pod。 PodTemplate 是你用来运行应用时指定的负载资源的目标状态的一部分。

下面的示例是一个简单的 Job 的清单,其中的 template 指示启动一个容器。 该 Pod 中的容器会打印一条消息之后暂停。

apiVersion: batch/v1
kind: Job
metadata:
  name: hello
spec:
  template:
    # 这里是 Pod 模板
    spec:
      containers:
      - name: hello
        image: busybox:1.28
        command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
      restartPolicy: OnFailure
    # 以上为 Pod 模板

修改 Pod 模板或者切换到新的 Pod 模板都不会对已经存在的 Pod 起作用。 Pod 不会直接收到模板的更新。相反, 新的 Pod 会被创建出来,与更改后的 Pod 模板匹配。

例如,Deployment 控制器针对每个 Deployment 对象确保运行中的 Pod 与当前的 Pod 模板匹配。如果模板被更新,则 Deployment 必须删除现有的 Pod,基于更新后的模板创建新的 Pod。 每个工作负载资源都实现了自己的规则,用来处理对 Pod 模板的更新。

在节点上,kubelet 并不直接监测 或管理与 Pod 模板相关的细节或模板的更新,这些细节都被抽象出来。 这种抽象和关注点分离简化了整个系统的语义, 并且使得用户可以在不改变现有代码的前提下就能扩展集群的行为。

Pod 更新与替换

正如前面章节所述,当某工作负载的 Pod 模板被改变时, 控制器会基于更新的模板创建新的 Pod 对象而不是对现有 Pod 执行更新或者修补操作。

Kubernetes 并不禁止你直接管理 Pod。对运行中的 Pod 的某些字段执行就地更新操作 还是可能的。不过,类似 patchreplace 这类更新操作有一些限制:

  • Pod 的绝大多数元数据都是不可变的。例如,你不可以改变其 namespacenameuid 或者 creationTimestamp 字段;generation 字段是比较特别的, 如果更新该字段,只能增加字段取值而不能减少。

  • 如果 metadata.deletionTimestamp 已经被设置,则不可以向 metadata.finalizers 列表中添加新的条目。

  • Pod 更新不可以改变除 spec.containers[*].imagespec.initContainers[*].imagespec.activeDeadlineSecondsspec.tolerations 之外的字段。 对于 spec.tolerations,你只被允许添加新的条目到其中。

  • 在更新spec.activeDeadlineSeconds 字段时,以下两种更新操作是被允许的:

    1. 如果该字段尚未设置,可以将其设置为一个正数;
    2. 如果该字段已经设置为一个正数,可以将其设置为一个更小的、非负的整数。

资源共享和通信

Pod 使它的成员容器间能够进行数据共享和通信。

Pod 中的存储

一个 Pod 可以设置一组共享的存储。 Pod 中的所有容器都可以访问该共享卷,从而允许这些容器共享数据。 卷还允许 Pod 中的持久数据保留下来,即使其中的容器需要重新启动。 有关 Kubernetes 如何在 Pod 中实现共享存储并将其提供给 Pod 的更多信息, 请参考

Pod 联网

每个 Pod 都在每个地址族中获得一个唯一的 IP 地址。 Pod 中的每个容器共享网络名字空间,包括 IP 地址和网络端口。 Pod 内的容器可以使用 localhost 互相通信。 当 Pod 中的容器与 Pod 之外的实体通信时,它们必须协调如何使用共享的网络资源(例如端口)。

在同一个 Pod 内,所有容器共享一个 IP 地址和端口空间,并且可以通过 localhost 发现对方。 他们也能通过如 SystemV 信号量或 POSIX 共享内存这类标准的进程间通信方式互相通信。 不同 Pod 中的容器的 IP 地址互不相同,没有特殊配置,无法通过 OS 级 IPC 进行通信就不能使用 IPC 进行通信。 如果某容器希望与运行于其他 Pod 中的容器通信,可以通过 IP 联网的方式实现。

Pod 中的容器所看到的系统主机名与为 Pod 配置的 name 属性值相同。 网络部分提供了更多有关此内容的信息。

容器的特权模式

在 Linux 中,Pod 中的任何容器都可以使用容器规约中的 安全性上下文中的 privileged(Linux)参数启用特权模式。 这对于想要使用操作系统管理权能(Capabilities,如操纵网络堆栈和访问设备)的容器很有用。

如果你的集群启用了 WindowsHostProcessContainers 特性,你可以使用 Pod 规约中安全上下文的 windowsOptions.hostProcess 参数来创建 Windows HostProcess Pod。 这些 Pod 中的所有容器都必须以 Windows HostProcess 容器方式运行。 HostProcess Pod 可以直接运行在主机上,它也能像 Linux 特权容器一样,用于执行管理任务。

静态 Pod

**静态 Pod(Static Pod)**直接由特定节点上的 kubelet 守护进程管理, 不需要 API 服务器看到它们。 尽管大多数 Pod 都是通过控制面(例如,Deployment) 来管理的,对于静态 Pod 而言,kubelet 直接监控每个 Pod,并在其失效时重启之。

静态 Pod 通常绑定到某个节点上的 kubelet。 其主要用途是运行自托管的控制面。 在自托管场景中,使用 kubelet 来管理各个独立的 控制面组件

kubelet 自动尝试为每个静态 Pod 在 Kubernetes API 服务器上创建一个 镜像 Pod。 这意味着在节点上运行的 Pod 在 API 服务器上是可见的,但不可以通过 API 服务器来控制。

容器探针

Probe 是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 可以执行三种动作:

  • ExecAction(借助容器运行时执行)
  • TCPSocketAction(由 kubelet 直接检测)
  • HTTPGetAction(由 kubelet 直接检测)

你可以参阅 Pod 的生命周期文档中的探针部分。

接下来

要了解为什么 Kubernetes 会在其他资源 (如 StatefulSetDeployment) 封装通用的 Pod API,相关的背景信息可以在前人的研究中找到。具体包括:

3.5.1.1 - Pod 的生命周期

本页面讲述 Pod 的生命周期。 Pod 遵循一个预定义的生命周期,起始于 Pending 阶段,如果至少 其中有一个主要容器正常启动,则进入 Running,之后取决于 Pod 中是否有容器以 失败状态结束而进入 Succeeded 或者 Failed 阶段。

在 Pod 运行期间,kubelet 能够重启容器以处理一些失效场景。 在 Pod 内部,Kubernetes 跟踪不同容器的状态 并确定使 Pod 重新变得健康所需要采取的动作。

在 Kubernetes API 中,Pod 包含规约部分和实际状态部分。 Pod 对象的状态包含了一组 Pod 状况(Conditions)。 如果应用需要的话,你也可以向其中注入自定义的就绪性信息

Pod 在其生命周期中只会被调度一次。 一旦 Pod 被调度(分派)到某个节点,Pod 会一直在该节点运行,直到 Pod 停止或者 被终止

Pod 生命期

和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。 Pod 会被创建、赋予一个唯一的 ID(UID), 并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。

如果一个节点死掉了,调度到该节点 的 Pod 也被计划在给定超时期限结束后删除

Pod 自身不具有自愈能力。如果 Pod 被调度到某节点 而该节点之后失效,Pod 会被删除;类似地,Pod 无法在因节点资源 耗尽或者节点维护而被驱逐期间继续存活。Kubernetes 使用一种高级抽象 来管理这些相对而言可随时丢弃的 Pod 实例,称作 控制器

任何给定的 Pod (由 UID 定义)从不会被“重新调度(rescheduled)”到不同的节点; 相反,这一 Pod 可以被一个新的、几乎完全相同的 Pod 替换掉。 如果需要,新 Pod 的名字可以不变,但是其 UID 会不同。

如果某物声称其生命期与某 Pod 相同,例如存储, 这就意味着该对象在此 Pod (UID 亦相同)存在期间也一直存在。 如果 Pod 因为任何原因被删除,甚至某完全相同的替代 Pod 被创建时, 这个相关的对象(例如这里的卷)也会被删除并重建。

Pod 结构图例

一个包含多个容器的 Pod 中包含一个用来拉取文件的程序和一个 Web 服务器, 均使用持久卷作为容器间共享的存储。

Pod 阶段

Pod 的 status 字段是一个 PodStatus 对象,其中包含一个 phase 字段。

Pod 的阶段(Phase)是 Pod 在其生命周期中所处位置的简单宏观概述。 该阶段并不是对容器或 Pod 状态的综合汇总,也不是为了成为完整的状态机。

Pod 阶段的数量和含义是严格定义的。 除了本文档中列举的内容外,不应该再假定 Pod 有其他的 phase 值。

下面是 phase 可能的值:

取值描述
Pending(悬决)Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。
Running(运行中)Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。
Succeeded(成功)Pod 中的所有容器都已成功终止,并且不会再重启。
Failed(失败)Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止。
Unknown(未知)因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。

如果某节点死掉或者与集群中其他节点失联,Kubernetes 会实施一种策略,将失去的节点上运行的所有 Pod 的 phase 设置为 Failed

容器状态

Kubernetes 会跟踪 Pod 中每个容器的状态,就像它跟踪 Pod 总体上的阶段一样。 你可以使用容器生命周期回调 来在容器生命周期中的特定时间点触发事件。

一旦调度器将 Pod 分派给某个节点,kubelet 就通过 容器运行时 开始为 Pod 创建容器。 容器的状态有三种:Waiting(等待)、Running(运行中)和 Terminated(已终止)。

要检查 Pod 中容器的状态,你可以使用 kubectl describe pod <pod 名称>。 其输出中包含 Pod 中每个容器的状态。

每种状态都有特定的含义:

Waiting (等待)

如果容器并不处在 RunningTerminated 状态之一,它就处在 Waiting 状态。 处于 Waiting 状态的容器仍在运行它完成启动所需要的操作:例如,从某个容器镜像 仓库拉取容器镜像,或者向容器应用 Secret 数据等等。 当你使用 kubectl 来查询包含 Waiting 状态的容器的 Pod 时,你也会看到一个 Reason 字段,其中给出了容器处于等待状态的原因。

Running(运行中)

Running 状态表明容器正在执行状态并且没有问题发生。 如果配置了 postStart 回调,那么该回调已经执行且已完成。 如果你使用 kubectl 来查询包含 Running 状态的容器的 Pod 时,你也会看到 关于容器进入 Running 状态的信息。

Terminated(已终止)

处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。 如果你使用 kubectl 来查询包含 Terminated 状态的容器的 Pod 时,你会看到 容器进入此状态的原因、退出代码以及容器执行期间的起止时间。

如果容器配置了 preStop 回调,则该回调会在容器进入 Terminated 状态之前执行。

容器重启策略

Pod 的 spec 中包含一个 restartPolicy 字段,其可能取值包括 Always、OnFailure 和 Never。默认值是 Always。

restartPolicy 适用于 Pod 中的所有容器。restartPolicy 仅针对同一节点上 kubelet 的容器重启动作。当 Pod 中的容器退出时,kubelet 会按指数回退 方式计算重启的延迟(10s、20s、40s、...),其最长延迟为 5 分钟。 一旦某容器执行了 10 分钟并且没有出现问题,kubelet 对该容器的重启回退计时器执行 重置操作。

Pod 状况

Pod 有一个 PodStatus 对象,其中包含一个 PodConditions 数组。Pod 可能通过也可能未通过其中的一些状况测试。

  • PodScheduled:Pod 已经被调度到某节点;
  • ContainersReady:Pod 中所有容器都已就绪;
  • Initialized:所有的 Init 容器 都已成功完成;
  • Ready:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。
字段名称描述
typePod 状况的名称
status表明该状况是否适用,可能的取值有 "True", "False" 或 "Unknown"
lastProbeTime上次探测 Pod 状况时的时间戳
lastTransitionTimePod 上次从一种状态转换到另一种状态时的时间戳
reason机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因
message人类可读的消息,给出上次状态转换的详细信息

Pod 就绪态

特性状态: Kubernetes v1.14 [stable]

你的应用可以向 PodStatus 中注入额外的反馈或者信号:Pod Readiness(Pod 就绪态)。 要使用这一特性,可以设置 Pod 规约中的 readinessGates 列表,为 kubelet 提供一组额外的状况供其评估 Pod 就绪态时使用。

就绪态门控基于 Pod 的 status.conditions 字段的当前值来做决定。 如果 Kubernetes 无法在 status.conditions 字段中找到某状况,则该状况的 状态值默认为 "False"。

这里是一个例子:

kind: Pod
...
spec:
  readinessGates:
    - conditionType: "www.example.com/feature-1"
status:
  conditions:
    - type: Ready                              # 内置的 Pod 状况
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
    - type: "www.example.com/feature-1"        # 额外的 Pod 状况
      status: "False"
      lastProbeTime: null
      lastTransitionTime: 2018-01-01T00:00:00Z
  containerStatuses:
    - containerID: docker://abcd...
      ready: true
...

你所添加的 Pod 状况名称必须满足 Kubernetes 标签键名格式

Pod 就绪态的状态

命令 kubectl patch 不支持修改对象的状态。 如果需要设置 Pod 的 status.conditions,应用或者 Operators 需要使用 PATCH 操作。 你可以使用 Kubernetes 客户端库 之一来编写代码,针对 Pod 就绪态设置定制的 Pod 状况。

对于使用定制状况的 Pod 而言,只有当下面的陈述都适用时,该 Pod 才会被评估为就绪:

  • Pod 中所有容器都已就绪;
  • readinessGates 中的所有状况都为 True 值。

当 Pod 的容器都已就绪,但至少一个定制状况没有取值或者取值为 Falsekubelet 将 Pod 的状况设置为 ContainersReady

容器探针

probe 是由 kubelet 对容器执行的定期诊断。 要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。

检查机制

使用探针来检查容器有四种不同的方法。 每个探针都必须准确定义为这四种机制中的一种:

exec
在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
grpc
使用 gRPC 执行一个远程过程调用。 目标应该实现 gRPC健康检查。 如果响应的状态是 "SERVING",则认为诊断成功。 gRPC 探针是一个 alpha 特性,只有在你启用了 "GRPCContainerProbe" 特性门控时才能使用。
httpGet
对容器的 IP 地址上指定端口和路径执行 HTTP GET 请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。
tcpSocket
对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。 如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。

探测结果

每次探测都将获得以下三种结果之一:

Success(成功)
容器通过了诊断。
Failure(失败)
容器未通过诊断。
Unknown(未知)
诊断失败,因此不会采取任何行动。

探测类型

针对运行中的容器,kubelet 可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:

livenessProbe
指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器, 并且容器将根据其重启策略决定未来。如果容器不提供存活探针, 则默认状态为 Success
readinessProbe
指示容器是否准备好为请求提供服务。如果就绪态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。 初始延迟之前的就绪态的状态值默认为 Failure。 如果容器不提供就绪态探针,则默认状态为 Success
startupProbe
指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被 禁用,直到此探针成功为止。如果启动探测失败,kubelet 将杀死容器,而容器依其 重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success

如欲了解如何设置存活态、就绪态和启动探针的进一步细节,可以参阅 配置存活态、就绪态和启动探针

何时该使用存活态探针?

特性状态: Kubernetes v1.0 [stable]

如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针; kubelet 将根据 Pod 的 restartPolicy 自动执行修复操作。

如果你希望容器在探测失败时被杀死并重新启动,那么请指定一个存活态探针, 并指定 restartPolicy 为 "Always" 或 "OnFailure"。

何时该使用就绪态探针?

特性状态: Kubernetes v1.0 [stable]

如果要仅在探测成功时才开始向 Pod 发送请求流量,请指定就绪态探针。 在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着 Pod 将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。

如果你希望容器能够自行进入维护状态,也可以指定一个就绪态探针,检查某个特定于 就绪态的因此不同于存活态探测的端点。

如果你的应用程序对后端服务有严格的依赖性,你可以同时实现存活态和就绪态探针。 当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。 这可以帮助你避免将流量导向只能返回错误信息的 Pod。

如果你的容器需要在启动期间加载大型数据、配置文件或执行迁移,你可以使用 启动探针。 然而,如果你想区分已经失败的应用和仍在处理其启动数据的应用,你可能更倾向于使用就绪探针。

何时该使用启动探针?

特性状态: Kubernetes v1.18 [beta]

对于所包含的容器需要较长时间才能启动就绪的 Pod 而言,启动探针是有用的。 你不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定, 对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。

如果你的容器启动时间通常超出 initialDelaySeconds + failureThreshold × periodSeconds 总值,你应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。 periodSeconds 的默认值是 10 秒。你应该将其 failureThreshold 设置得足够高, 以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。 这一设置有助于减少死锁状况的发生。

Pod 的终止

由于 Pod 所代表的是在集群中节点上运行的进程,当不再需要这些进程时允许其体面地 终止是很重要的。一般不应武断地使用 KILL 信号终止它们,导致这些进程没有机会 完成清理操作。

设计的目标是令你能够请求删除进程,并且知道进程何时被终止,同时也能够确保删除 操作终将完成。当你请求删除某个 Pod 时,集群会记录并跟踪 Pod 的体面终止周期, 而不是直接强制地杀死 Pod。在存在强制关闭设施的前提下, kubelet 会尝试体面地终止 Pod。

通常情况下,容器运行时会发送一个 TERM 信号到每个容器中的主进程。 很多容器运行时都能够注意到容器镜像中 STOPSIGNAL 的值,并发送该信号而不是 TERM。 一旦超出了体面终止限期,容器运行时会向所有剩余进程发送 KILL 信号,之后 Pod 就会被从 API 服务器 上移除。如果 kubelet 或者容器运行时的管理服务在等待进程终止期间被重启, 集群会从头开始重试,赋予 Pod 完整的体面终止限期。

下面是一个例子:

  1. 你使用 kubectl 工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期是默认值(30 秒)。

  2. API 服务器中的 Pod 对象被更新,记录涵盖体面终止限期在内 Pod 的最终死期,超出所计算时间点则认为 Pod 已死(dead)。 如果你使用 kubectl describe 来查验你正在删除的 Pod,该 Pod 会显示为 "Terminating" (正在终止)。 在 Pod 运行所在的节点上:kubelet 一旦看到 Pod 被标记为正在终止(已经设置了体面终止限期),kubelet 即开始本地的 Pod 关闭过程。

    1. 如果 Pod 中的容器之一定义了 preStop 回调kubelet 开始在容器内运行该回调逻辑。如果超出体面终止限期时,preStop 回调逻辑 仍在运行,kubelet 会请求给予该 Pod 的宽限期一次性增加 2 秒钟。
    1. kubelet 接下来触发容器运行时发送 TERM 信号给每个容器中的进程 1。

  1. 与此同时,kubelet 启动体面关闭逻辑,控制面会将 Pod 从对应的端点列表(以及端点切片列表, 如果启用了的话)中移除,过滤条件是 Pod 被对应的 服务以某 选择算符选定。 ReplicaSets和其他工作负载资源 不再将关闭进程中的 Pod 视为合法的、能够提供服务的副本。关闭动作很慢的 Pod 也无法继续处理请求数据,因为负载均衡器(例如服务代理)已经在终止宽限期开始的时候 将其从端点列表中移除。
  1. 超出终止宽限期限时,kubelet 会触发强制关闭过程。容器运行时会向 Pod 中所有容器内 仍在运行的进程发送 SIGKILL 信号。 kubelet 也会清理隐藏的 pause 容器,如果容器运行时使用了这种容器的话。

  2. kubelet 触发强制从 API 服务器上删除 Pod 对象的逻辑,并将体面终止限期设置为 0 (这意味着马上删除)。

  3. API 服务器删除 Pod 的 API 对象,从任何客户端都无法再看到该对象。

强制终止 Pod

默认情况下,所有的删除操作都会附有 30 秒钟的宽限期限。 kubectl delete 命令支持 --grace-period=<seconds> 选项,允许你重载默认值, 设定自己希望的期限值。

将宽限期限强制设置为 0 意味着立即从 API 服务器删除 Pod。 如果 Pod 仍然运行于某节点上,强制删除操作会触发 kubelet 立即执行清理操作。

执行强制删除操作时,API 服务器不再等待来自 kubelet 的、关于 Pod 已经在原来运行的节点上终止执行的确认消息。 API 服务器直接删除 Pod 对象,这样新的与之同名的 Pod 即可以被创建。 在节点侧,被设置为立即终止的 Pod 仍然会在被强行杀死之前获得一点点的宽限时间。

如果你需要强制删除 StatefulSet 的 Pod,请参阅 从 StatefulSet 中删除 Pod 的任务文档。

已终止 Pod 的垃圾收集

对于已失败的 Pod 而言,对应的 API 对象仍然会保留在集群的 API 服务器上,直到 用户或者控制器进程显式地 将其删除。

控制面组件会在 Pod 个数超出所配置的阈值 (根据 kube-controller-managerterminated-pod-gc-threshold 设置)时 删除已终止的 Pod(阶段值为 SucceededFailed)。 这一行为会避免随着时间演进不断创建和终止 Pod 而引起的资源泄露问题。

接下来

3.5.1.2 - Init 容器

本页提供了 Init 容器的概览。Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。

你可以在 Pod 的规约中与用来描述应用容器的 containers 数组平行的位置指定 Init 容器。

理解 Init 容器

每个 Pod 中可以包含多个容器, 应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。

Init 容器与普通的容器非常像,除了如下两点:

  • 它们总是运行到完成。
  • 每个都必须在下一个启动之前成功完成。

如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。 然而,如果 Pod 对应的 restartPolicy 值为 "Never",并且 Pod 的 Init 容器失败, 则 Kubernetes 会将整个 Pod 状态设置为失败。

为 Pod 设置 Init 容器需要在 Pod 规约 中添加 initContainers 字段, 该字段以 Container 类型对象数组的形式组织,和应用的 containers 数组同级相邻。 参阅 API 参考的容器章节了解详情。

Init 容器的状态在 status.initContainerStatuses 字段中以容器状态数组的格式返回 (类似 status.containerStatuses 字段)。

与普通容器的不同之处

Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。 然而,Init 容器对资源请求和限制的处理稍有不同,在下面资源节有说明。

同时 Init 容器不支持 lifecyclelivenessProbereadinessProbestartupProbe, 因为它们必须在 Pod 就绪之前运行完成。

如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。 每个 Init 容器必须运行成功,下一个才能够运行。当所有的 Init 容器运行完成时, Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。

使用 Init 容器

因为 Init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:

  • Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。 例如,没有必要仅为了在安装过程中使用类似 sedawkpythondig 这样的工具而去 FROM 一个镜像来生成一个新的镜像。

  • Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。

  • 应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。

  • Init 容器能以不同于 Pod 内应用容器的文件系统视图运行。因此,Init 容器可以访问 应用容器不能访问的 Secret 的权限。

  • 由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。 一旦前置条件满足,Pod 内的所有的应用容器会并行启动。

示例

下面是一些如何使用 Init 容器的想法:

  • 等待一个 Service 完成创建,通过类似如下 Shell 命令:

    for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
    
  • 注册这个 Pod 到远程服务器,通过在命令中调用 API,类似如下:

    curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
    
  • 在启动应用容器之前等一段时间,使用类似命令:

    sleep 60
    
  • 克隆 Git 仓库到中。

  • 将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。 例如,在配置文件中存放 POD_IP 值,并使用 Jinja 生成主应用配置文件。

使用 Init 容器的情况

下面的例子定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动, 第二个等待 mydb 启动。 一旦这两个 Init容器 都启动完成,Pod 将启动 spec 节中的应用容器。

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app.kubernetes.io/name: MyApp
spec:
  containers:
  - name: myapp-container
    image: busybox:1.28
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']
  initContainers:
  - name: init-myservice
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
  - name: init-mydb
    image: busybox:1.28
    command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]

你通过运行下面的命令启动 Pod:

kubectl apply -f myapp.yaml

输出类似于:

pod/myapp-pod created

使用下面的命令检查其状态:

kubectl get -f myapp.yaml

输出类似于:

NAME        READY     STATUS     RESTARTS   AGE
myapp-pod   0/1       Init:0/2   0          6m

或者查看更多详细信息:

kubectl describe -f myapp.yaml

输出类似于:

Name:          myapp-pod
Namespace:     default
[...]
Labels:        app.kubernetes.io/name=MyApp
Status:        Pending
[...]
Init Containers:
  init-myservice:
[...]
    State:         Running
[...]
  init-mydb:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Containers:
  myapp-container:
[...]
    State:         Waiting
      Reason:      PodInitializing
    Ready:         False
[...]
Events:
  FirstSeen    LastSeen    Count    From                      SubObjectPath                           Type          Reason        Message
  ---------    --------    -----    ----                      -------------                           --------      ------        -------
  16s          16s         1        {default-scheduler }                                              Normal        Scheduled     Successfully assigned myapp-pod to 172.17.4.201
  16s          16s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulling       pulling image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Pulled        Successfully pulled image "busybox"
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Created       Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634

如需查看 Pod 内 Init 容器的日志,请执行:

kubectl logs myapp-pod -c init-myservice # 查看第一个 Init 容器
kubectl logs myapp-pod -c init-mydb      # 查看第二个 Init 容器

在这一刻,Init 容器将会等待至发现名称为 mydbmyservice 的 Service。

如下为创建这些 Service 的配置文件:

---
apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
  name: mydb
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9377

创建 mydbmyservice 服务的命令:

kubectl create -f services.yaml

输出类似于:

service "myservice" created
service "mydb" created

这样你将能看到这些 Init 容器执行完毕,随后 my-app 的 Pod 进入 Running 状态:

kubectl get -f myapp.yaml

输出类似于:

NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

这个简单例子应该能为你创建自己的 Init 容器提供一些启发。 接下来节提供了更详细例子的链接。

具体行为

在 Pod 启动过程中,每个 Init 容器会在网络和数据卷初始化之后按顺序启动。 kubelet 运行依据 Init 容器在 Pod 规约中的出现顺序依次运行之。

每个 Init 容器成功退出后才会启动下一个 Init 容器。 如果某容器因为容器运行时的原因无法启动,或以错误状态退出,kubelet 会根据 Pod 的 restartPolicy 策略进行重试。 然而,如果 Pod 的 restartPolicy 设置为 "Always",Init 容器失败时会使用 restartPolicy 的 "OnFailure" 策略。

在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready 状态。 Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending 状态, 但会将状况 Initializing 设置为 false。

如果 Pod 重启,所有 Init 容器必须重新执行。

对 Init 容器规约的修改仅限于容器的 image 字段。 更改 Init 容器的 image 字段,等同于重启该 Pod。

因为 Init 容器可能会被重启、重试或者重新执行,所以 Init 容器的代码应该是幂等的。 特别地,基于 emptyDirs 写文件的代码,应该对输出文件可能已经存在做好准备。

Init 容器具有应用容器的所有字段。然而 Kubernetes 禁止使用 readinessProbe, 因为 Init 容器不能定义不同于完成态(Completion)的就绪态(Readiness)。 Kubernetes 会在校验时强制执行此检查。

在 Pod 上使用 activeDeadlineSeconds 和在容器上使用 livenessProbe 可以避免 Init 容器一直重复失败。 activeDeadlineSeconds 时间包含了 Init 容器启动的时间。 但建议仅在团队将其应用程序部署为 Job 时才使用 activeDeadlineSeconds, 因为 activeDeadlineSeconds 在 Init 容器结束后仍有效果。 如果你设置了 activeDeadlineSeconds,已经在正常运行的 Pod 会被杀死。

在 Pod 中的每个应用容器和 Init 容器的名称必须唯一; 与任何其它容器共享同一个名称,会在校验时抛出错误。

资源

在给定的 Init 容器执行顺序下,资源使用适用于如下规则:

  • 所有 Init 容器上定义的任何特定资源的 limit 或 request 的最大值,作为 Pod 有效初始 request/limit。 如果任何资源没有指定资源限制,这被视为最高限制。
  • Pod 对资源的 有效 limit/request 是如下两者中的较大者:
    • 所有应用容器对某个资源的 limit/request 之和
    • 对某个资源的有效初始 limit/request
  • 基于有效 limit/request 完成调度,这意味着 Init 容器能够为初始化过程预留资源, 这些资源在 Pod 生命周期过程中并没有被使用。
  • Pod 的 有效 QoS 层 ,与 Init 容器和应用容器的一样。

配额和限制适用于有效 Pod 的请求和限制值。 Pod 级别的 cgroups 是基于有效 Pod 的请求和限制值,和调度器相同。

Pod 重启的原因

Pod 重启会导致 Init 容器重新执行,主要有如下几个原因:

  • Pod 的基础设施容器 (译者注:如 pause 容器) 被重启。这种情况不多见, 必须由具备 root 权限访问节点的人员来完成。

  • restartPolicy 设置为 Always,Pod 中所有容器会终止而强制重启。 由于垃圾收集机制的原因,Init 容器的完成记录将会丢失。

当 Init 容器的镜像发生改变或者 Init 容器的完成记录因为垃圾收集等原因被丢失时, Pod 不会被重启。这一行为适用于 Kubernetes v1.20 及更新版本。 如果你在使用较早版本的 Kubernetes,可查阅你所使用的版本对应的文档。

接下来

3.5.1.3 - 干扰(Disruptions)

本指南针对的是希望构建高可用性应用程序的应用所有者,他们有必要了解可能发生在 Pod 上的干扰类型。

文档同样适用于想要执行自动化集群操作(例如升级和自动扩展集群)的集群管理员。

自愿干扰和非自愿干扰

Pod 不会消失,除非有人(用户或控制器)将其销毁,或者出现了不可避免的硬件或软件系统错误。

我们把这些不可避免的情况称为应用的非自愿干扰(Involuntary Disruptions)。例如:

  • 节点下层物理机的硬件故障
  • 集群管理员错误地删除虚拟机(实例)
  • 云提供商或虚拟机管理程序中的故障导致的虚拟机消失
  • 内核错误
  • 节点由于集群网络隔离从集群中消失
  • 由于节点资源不足导致 pod 被驱逐。

除了资源不足的情况,大多数用户应该都熟悉这些情况;它们不是特定于 Kubernetes 的。

我们称其他情况为自愿干扰(Voluntary Disruptions)。 包括由应用程序所有者发起的操作和由集群管理员发起的操作。典型的应用程序所有者的操 作包括:

  • 删除 Deployment 或其他管理 Pod 的控制器
  • 更新了 Deployment 的 Pod 模板导致 Pod 重启
  • 直接删除 Pod(例如,因为误操作)

集群管理员操作包括:

这些操作可能由集群管理员直接执行,也可能由集群管理员所使用的自动化工具执行,或者由集群托管提供商自动执行。

咨询集群管理员或联系云提供商,或者查询发布文档,以确定是否为集群启用了任何资源干扰源。 如果没有启用,可以不用创建 Pod Disruption Budgets(Pod 干扰预算)

处理干扰

以下是减轻非自愿干扰的一些方法:

  • 确保 Pod 在请求中给出所需资源
  • 如果需要更高的可用性,请复制应用程序。 (了解有关运行多副本的无状态有状态应用程序的信息。)
  • 为了在运行复制应用程序时获得更高的可用性,请跨机架(使用 反亲和性 或跨区域(如果使用多区域集群)扩展应用程序。

自愿干扰的频率各不相同。在一个基本的 Kubernetes 集群中,没有自愿干扰(只有用户触发的干扰)。 然而,集群管理员或托管提供商可能运行一些可能导致自愿干扰的额外服务。例如,节点软 更新可能导致自愿干扰。另外,集群(节点)自动缩放的某些 实现可能导致碎片整理和紧缩节点的自愿干扰。集群 管理员或托管提供商应该已经记录了各级别的自愿干扰(如果有的话)。 有些配置选项,例如在 pod spec 中 使用 PriorityClasses 也会产生自愿(和非自愿)的干扰。

Kubernetes 提供特性来满足在出现频繁自愿干扰的同时运行高可用的应用程序。我们称这些特性为 干扰预算(Disruption Budget)

干扰预算

特性状态: Kubernetes v1.21 [stable]

即使你会经常引入自愿性干扰,Kubernetes 也能够支持你运行高度可用的应用。

应用程序所有者可以为每个应用程序创建 PodDisruptionBudget 对象(PDB)。 PDB 将限制在同一时间因自愿干扰导致的复制应用程序中宕机的 pod 数量。 例如,基于票选机制的应用程序希望确保运行的副本数永远不会低于仲裁所需的数量。 Web 前端可能希望确保提供负载的副本数量永远不会低于总数的某个百分比。

集群管理员和托管提供商应该使用遵循 PodDisruptionBudgets 的接口 (通过调用Eviction API), 而不是直接删除 Pod 或 Deployment。

例如,kubectl drain 命令可以用来标记某个节点即将停止服务。 运行 kubectl drain 命令时,工具会尝试驱逐机器上的所有 Pod。 kubectl 所提交的驱逐请求可能会暂时被拒绝,所以该工具会定时重试失败的请求, 直到所有的 Pod 都被终止,或者达到配置的超时时间。

PDB 指定应用程序可以容忍的副本数量(相当于应该有多少副本)。 例如,具有 .spec.replicas: 5 的 Deployment 在任何时间都应该有 5 个 Pod。 如果 PDB 允许其在某一时刻有 4 个副本,那么驱逐 API 将允许同一时刻仅有一个而不是两个 Pod 自愿干扰。

使用标签选择器来指定构成应用程序的一组 Pod,这与应用程序的控制器(Deployment,StatefulSet 等) 选择 Pod 的逻辑一样。

Pod 控制器的 .spec.replicas 计算“预期的” Pod 数量。 根据 Pod 对象的 .metadata.ownerReferences 字段来发现控制器。

PDB 无法防止非自愿干扰; 但它们确实计入预算。

由于应用程序的滚动升级而被删除或不可用的 Pod 确实会计入干扰预算, 但是控制器(如 Deployment 和 StatefulSet)在进行滚动升级时不受 PDB 的限制。应用程序更新期间的故障处理方式是在对应的工作负载资源的 spec 中配置的。

当使用驱逐 API 驱逐 Pod 时,Pod 会被体面地 终止,期间会 参考 PodSpec 中的 terminationGracePeriodSeconds 配置值。

PDB 例子

假设集群有 3 个节点,node-1node-3。集群上运行了一些应用。 其中一个应用有 3 个副本,分别是 pod-apod-bpod-c。 另外,还有一个不带 PDB 的无关 pod pod-x 也同样显示出来。 最初,所有的 Pod 分布如下:

node-1node-2node-3
pod-a availablepod-b availablepod-c available
pod-x available

3 个 Pod 都是 deployment 的一部分,并且共同拥有同一个 PDB,要求 3 个 Pod 中至少有 2 个 Pod 始终处于可用状态。

例如,假设集群管理员想要重启系统,升级内核版本来修复内核中的缺陷。 集群管理员首先使用 kubectl drain 命令尝试排空 node-1 节点。 命令尝试驱逐 pod-apod-x。操作立即就成功了。 两个 Pod 同时进入 terminating 状态。这时的集群处于下面的状态:

node-1 drainingnode-2node-3
pod-a terminatingpod-b availablepod-c available
pod-x terminating

Deployment 控制器观察到其中一个 Pod 正在终止,因此它创建了一个替代 Pod pod-d。 由于 node-1 被封锁(cordon),pod-d 落在另一个节点上。 同样其他控制器也创建了 pod-y 作为 pod-x 的替代品。

(注意:对于 StatefulSet 来说,pod-a(也称为 pod-0)需要在替换 Pod 创建之前完全终止, 替代它的也称为 pod-0,但是具有不同的 UID。除此之外,此示例也适用于 StatefulSet。)

当前集群的状态如下:

node-1 drainingnode-2node-3
pod-a terminatingpod-b availablepod-c available
pod-x terminatingpod-d startingpod-y

在某一时刻,Pod 被终止,集群如下所示:

node-1 drainednode-2node-3
pod-b availablepod-c available
pod-d startingpod-y

此时,如果一个急躁的集群管理员试图排空(drain)node-2node-3,drain 命令将被阻塞, 因为对于 Deployment 来说只有 2 个可用的 Pod,并且它的 PDB 至少需要 2 个。 经过一段时间,pod-d 变得可用。

集群状态如下所示:

node-1 drainednode-2node-3
pod-b availablepod-c available
pod-d availablepod-y

现在,集群管理员试图排空(drain)node-2。 drain 命令将尝试按照某种顺序驱逐两个 Pod,假设先是 pod-b,然后是 pod-d。 命令成功驱逐 pod-b,但是当它尝试驱逐 pod-d时将被拒绝,因为对于 Deployment 来说只剩一个可用的 Pod 了。

Deployment 创建 pod-b 的替代 Pod pod-e。 因为集群中没有足够的资源来调度 pod-e,drain 命令再次阻塞。集群最终将是下面这种状态:

node-1 drainednode-2node-3no node
pod-b terminatingpod-c availablepod-e pending
pod-d availablepod-y

此时,集群管理员需要增加一个节点到集群中以继续升级操作。

可以看到 Kubernetes 如何改变干扰发生的速率,根据:

  • 应用程序需要多少个副本
  • 优雅关闭应用实例需要多长时间
  • 启动应用新实例需要多长时间
  • 控制器的类型
  • 集群的资源能力

分离集群所有者和应用所有者角色

通常,将集群管理者和应用所有者视为彼此了解有限的独立角色是很有用的。这种责任分离在下面这些场景下是有意义的:

  • 当有许多应用程序团队共用一个 Kubernetes 集群,并且有自然的专业角色
  • 当第三方工具或服务用于集群自动化管理

Pod 干扰预算通过在角色之间提供接口来支持这种分离。

如果你的组织中没有这样的责任分离,则可能不需要使用 Pod 干扰预算。

如何在集群上执行干扰性操作

如果你是集群管理员,并且需要对集群中的所有节点执行干扰操作,例如节点或系统软件升级,则可以使用以下选项

  • 接受升级期间的停机时间。
  • 故障转移到另一个完整的副本集群。
    • 没有停机时间,但是对于重复的节点和人工协调成本可能是昂贵的。
  • 编写可容忍干扰的应用程序和使用 PDB。
    • 不停机。
    • 最小的资源重复。
    • 允许更多的集群管理自动化。
    • 编写可容忍干扰的应用程序是棘手的,但对于支持容忍自愿干扰所做的工作,和支持自动扩缩和容忍非 自愿干扰所做工作相比,有大量的重叠

接下来

3.5.1.4 - 临时容器

特性状态: Kubernetes v1.23 [beta]

本页面概述了临时容器:一种特殊的容器,该容器在现有 Pod 中临时运行,以便完成用户发起的操作,例如故障排查。 你会使用临时容器来检查服务,而不是用它来构建应用程序。

了解临时容器

Pod 是 Kubernetes 应用程序的基本构建块。 由于 Pod 是一次性且可替换的,因此一旦 Pod 创建,就无法将容器加入到 Pod 中。 取而代之的是,通常使用 Deployment 以受控的方式来删除并替换 Pod。

有时有必要检查现有 Pod 的状态。例如,对于难以复现的故障进行排查。 在这些场景中,可以在现有 Pod 中运行临时容器来检查其状态并运行任意命令。

什么是临时容器?

临时容器与其他容器的不同之处在于,它们缺少对资源或执行的保证,并且永远不会自动重启, 因此不适用于构建应用程序。 临时容器使用与常规容器相同的 ContainerSpec 节来描述,但许多字段是不兼容和不允许的。

  • 临时容器没有端口配置,因此像 portslivenessProbereadinessProbe 这样的字段是不允许的。
  • Pod 资源分配是不可变的,因此 resources 配置是不允许的。
  • 有关允许字段的完整列表,请参见 EphemeralContainer 参考文档

临时容器是使用 API 中的一种特殊的 ephemeralcontainers 处理器进行创建的, 而不是直接添加到 pod.spec 段,因此无法使用 kubectl edit 来添加一个临时容器。

与常规容器一样,将临时容器添加到 Pod 后,将不能更改或删除临时容器。

临时容器的用途

当由于容器崩溃或容器镜像不包含调试工具而导致 kubectl exec 无用时, 临时容器对于交互式故障排查很有用。

尤其是,Distroless 镜像 允许用户部署最小的容器镜像,从而减少攻击面并减少故障和漏洞的暴露。 由于 distroless 镜像不包含 Shell 或任何的调试工具,因此很难单独使用 kubectl exec 命令进行故障排查。

使用临时容器时,启用 进程名字空间共享 很有帮助,可以查看其他容器中的进程。

接下来

3.5.1.5 - Downward API

有两种方法可以将 Pod 和容器字段暴露给运行中的容器:环境变量和由特殊卷类型承载的文件。 这两种暴露 Pod 和容器字段的方法统称为 Downward API。

对于容器来说,在不与 Kubernetes 过度耦合的情况下,拥有关于自身的信息有时是很有用的。 Downward API 允许容器在不使用 Kubernetes 客户端或 API 服务器的情况下获得自己或集群的信息。

例如,现有应用程序假设某特定的周知的环境变量是存在的,其中包含唯一标识符。 一种方法是对应用程序进行封装,但这很繁琐且容易出错,并且违背了低耦合的目标。 更好的选择是使用 Pod 名称作为标识符,并将 Pod 名称注入到周知的环境变量中。

在 Kubernetes 中,有两种方法可以将 Pod 和容器字段暴露给运行中的容器:

这两种暴露 Pod 和容器字段的方式统称为 Downward API

可用字段

只有部分 Kubernetes API 字段可以通过 Downward API 使用。本节列出了你可以使用的字段。

你可以使用 fieldRef 传递来自可用的 Pod 级字段的信息。在 API 层面,一个 Pod 的 spec 总是定义了至少一个 Container。 你可以使用 resourceFieldRef 传递来自可用的 Container 级字段的信息。

可通过 fieldRef 获得的信息

对于大多数 Pod 级别的字段,你可以将它们作为环境变量或使用 downwardAPI 卷提供给容器。 通过这两种机制可用的字段有:

metadata.name
Pod 的名称
metadata.namespace
Pod 的命名空间
metadata.uid
Pod 的唯一 ID
metadata.annotations['<KEY>']
Pod 的注解 <KEY> 的值(例如:metadata.annotations['myannotation']
metadata.labels['<KEY>']
Pod 的标签 <KEY> 的值(例如:metadata.labels['mylabel']
spec.serviceAccountName
Pod 的服务账号名称
spec.nodeName
Pod 运行时所处的节点名称
status.hostIP
Pod 所在节点的主 IP 地址
status.podIP
Pod 的主 IP 地址(通常是其 IPv4 地址)

此外,以下信息可以通过 downwardAPIfieldRef 获得,但不能作为环境变量获得:

metadata.labels
Pod 的所有标签,格式为 标签键名="转义后的标签值",每行一个标签
metadata.annotations
Pod 的全部注解,格式为 注解键名="转义后的注解值",每行一个注解

可通过 resourceFieldRef 获得的信息

resource: limits.cpu
容器的 CPU 限制值
resource: requests.cpu
容器的 CPU 请求值
resource: limits.memory
容器的内存限制值
resource: requests.memory
容器的内存请求值
resource: limits.hugepages-*
容器的巨页限制值(前提是启用了 DownwardAPIHugePages 特性门控
resource: requests.hugepages-*
容器的巨页请求值(前提是启用了 DownwardAPIHugePages 特性门控
resource: limits.ephemeral-storage
容器的临时存储的限制值
resource: requests.ephemeral-storage
容器的临时存储的请求值

资源限制的后备信息

如果没有为容器指定 CPU 和内存限制时尝试使用 Downward API 暴露该信息,那么 kubelet 默认会根据 节点可分配资源 计算并暴露 CPU 和内存的最大可分配值。

接下来

你可以阅读有关 downwardAPI的内容。

你可以尝试使用 Downward API 暴露容器或 Pod 级别的信息:

3.5.2 - 工作负载资源

3.5.2.1 - Deployments

一个 Deployment 为 PodReplicaSet 提供声明式的更新能力。

你负责描述 Deployment 中的 目标状态,而 Deployment 控制器(Controller) 以受控速率更改实际状态, 使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 收养其资源。

用例

以下是 Deployments 的典型用例:

  • 创建 Deployment 以将 ReplicaSet 上线。 ReplicaSet 在后台创建 Pods。 检查 ReplicaSet 的上线状态,查看其是否成功。
  • 通过更新 Deployment 的 PodTemplateSpec,声明 Pod 的新状态 。 新的 ReplicaSet 会被创建,Deployment 以受控速率将 Pod 从旧 ReplicaSet 迁移到新 ReplicaSet。 每个新的 ReplicaSet 都会更新 Deployment 的修订版本。

创建 Deployment

下面是一个 Deployment 示例。其中创建了一个 ReplicaSet,负责启动三个 nginx Pods:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

在该例中:

  • 创建名为 nginx-deployment(由 .metadata.name 字段标明)的 Deployment。
  • 该 Deployment 创建三个(由 replicas 字段标明)Pod 副本。
  • selector 字段定义 Deployment 如何查找要管理的 Pods。 在这里,你选择在 Pod 模板中定义的标签(app: nginx)。 不过,更复杂的选择规则是也可能的,只要 Pod 模板本身满足所给规则即可。

  • template 字段包含以下子字段:
    • Pod 被使用 .metadata.labels 字段打上 app: nginx 标签。
    • Pod 模板规约(即 .template.spec 字段)指示 Pods 运行一个 nginx 容器, 该容器运行版本为 1.14.2 的 nginx Docker Hub 镜像。
    • 创建一个容器并使用 .spec.template.spec.containers[0].name 字段将其命名为 nginx

开始之前,请确保的 Kubernetes 集群已启动并运行。 按照以下步骤创建上述 Deployment :

  1. 通过运行以下命令创建 Deployment :

    kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
    
  1. 运行 kubectl get deployments 检查 Deployment 是否已创建。 如果仍在创建 Deployment,则输出类似于:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   0/3     0            0           1s
    

    在检查集群中的 Deployment 时,所显示的字段有:

    • NAME 列出了集群中 Deployment 的名称。
    • READY 显示应用程序的可用的“副本”数。显示的模式是“就绪个数/期望个数”。
    • UP-TO-DATE 显示为了达到期望状态已经更新的副本数。
    • AVAILABLE 显示应用可供用户使用的副本数。
    • AGE 显示应用程序运行的时间。

    请注意期望副本数是根据 .spec.replicas 字段设置 3。

  1. 要查看 Deployment 上线状态,运行 kubectl rollout status deployment/nginx-deployment

    输出类似于:

    Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
    deployment "nginx-deployment" successfully rolled out
    
  1. 几秒钟后再次运行 kubectl get deployments。输出类似于:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           18s
    

    注意 Deployment 已创建全部三个副本,并且所有副本都是最新的(它们包含最新的 Pod 模板) 并且可用。

  1. 要查看 Deployment 创建的 ReplicaSet(rs),运行 kubectl get rs。 输出类似于:

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-75675f5897   3         3         3       18s
    

    ReplicaSet 输出中包含以下字段:

    • NAME 列出名字空间中 ReplicaSet 的名称;
    • DESIRED 显示应用的期望副本个数,即在创建 Deployment 时所定义的值。 此为期望状态;
    • CURRENT 显示当前运行状态中的副本个数;
    • READY 显示应用中有多少副本可以为用户提供服务;
    • AGE 显示应用已经运行的时间长度。

    注意 ReplicaSet 的名称始终被格式化为[Deployment名称]-[随机字符串]。 其中的随机字符串是使用 pod-template-hash 作为种子随机生成的。

  1. 要查看每个 Pod 自动生成的标签,运行 kubectl get pods --show-labels。返回以下输出:

    NAME                                READY     STATUS    RESTARTS   AGE       LABELS
    nginx-deployment-75675f5897-7ci7o   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    nginx-deployment-75675f5897-kzszj   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    nginx-deployment-75675f5897-qqcnn   1/1       Running   0          18s       app=nginx,pod-template-hash=3123191453
    

    所创建的 ReplicaSet 确保总是存在三个 nginx Pod。

Pod-template-hash 标签

Deployment 控制器将 pod-template-hash 标签添加到 Deployment 所创建或收留的每个 ReplicaSet 。

此标签可确保 Deployment 的子 ReplicaSets 不重叠。 标签是通过对 ReplicaSet 的 PodTemplate 进行哈希处理。 所生成的哈希值被添加到 ReplicaSet 选择算符、Pod 模板标签,并存在于在 ReplicaSet 可能拥有的任何现有 Pod 中。

更新 Deployment

按照以下步骤更新 Deployment:

  1. 先来更新 nginx Pod 以使用 nginx:1.16.1 镜像,而不是 nginx:1.14.2 镜像。

    kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.16.1
    

    或者使用下面的命令:

    kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    

    输出类似于:

    deployment/nginx-deployment image updated
    

    或者,可以对 Deployment 执行 edit 操作并将 .spec.template.spec.containers[0].imagenginx:1.14.2 更改至 nginx:1.16.1

    kubectl edit deployment/nginx-deployment
    

    输出类似于:

    deployment/nginx-deployment edited
    
  1. 要查看上线状态,运行:

    kubectl rollout status deployment/nginx-deployment
    

    输出类似于:

    Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
    

    或者

    deployment "nginx-deployment" successfully rolled out
    

获取关于已更新的 Deployment 的更多信息:

  • 在上线成功后,可以通过运行 kubectl get deployments 来查看 Deployment: 输出类似于:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           36s
    
  • 运行 kubectl get rs 以查看 Deployment 通过创建新的 ReplicaSet 并将其扩容到 3 个副本并将旧 ReplicaSet 缩容到 0 个副本完成了 Pod 的更新操作:

    kubectl get rs
    

    输出类似于:

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-1564180365   3         3         3       6s
    nginx-deployment-2035384211   0         0         0       36s
    
  • 现在运行 get pods 应仅显示新的 Pods:

    kubectl get pods
    

    输出类似于:

    NAME                                READY     STATUS    RESTARTS   AGE
    nginx-deployment-1564180365-khku8   1/1       Running   0          14s
    nginx-deployment-1564180365-nacti   1/1       Running   0          14s
    nginx-deployment-1564180365-z9gth   1/1       Running   0          14s
    

    下次要更新这些 Pods 时,只需再次更新 Deployment Pod 模板即可。

    Deployment 可确保在更新时仅关闭一定数量的 Pod。默认情况下,它确保至少所需 Pods 75% 处于运行状态(最大不可用比例为 25%)。

    Deployment 还确保仅所创建 Pod 数量只可能比期望 Pods 数高一点点。 默认情况下,它可确保启动的 Pod 个数比期望个数最多多出 25%(最大峰值 25%)。

    例如,如果仔细查看上述 Deployment ,将看到它首先创建了一个新的 Pod,然后删除了一些旧的 Pods, 并创建了新的 Pods。它不会杀死老 Pods,直到有足够的数量新的 Pods 已经出现。 在足够数量的旧 Pods 被杀死前并没有创建新 Pods。它确保至少 2 个 Pod 可用, 同时最多总共 4 个 Pod 可用。 当 Deployment 设置为 4 个副本时,Pod 的个数会介于 3 和 5 之间。

  • 获取 Deployment 的更多信息

    kubectl describe deployments
    

    输出类似于:

    Name:                   nginx-deployment
    Namespace:              default
    CreationTimestamp:      Thu, 30 Nov 2017 10:56:25 +0000
    Labels:                 app=nginx
    Annotations:            deployment.kubernetes.io/revision=2
    Selector:               app=nginx
    Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
    StrategyType:           RollingUpdate
    MinReadySeconds:        0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
       Containers:
        nginx:
          Image:        nginx:1.16.1
          Port:         80/TCP
          Environment:  <none>
          Mounts:       <none>
        Volumes:        <none>
      Conditions:
        Type           Status  Reason
        ----           ------  ------
        Available      True    MinimumReplicasAvailable
        Progressing    True    NewReplicaSetAvailable
      OldReplicaSets:  <none>
      NewReplicaSet:   nginx-deployment-1564180365 (3/3 replicas created)
      Events:
        Type    Reason             Age   From                   Message
        ----    ------             ----  ----                   -------
        Normal  ScalingReplicaSet  2m    deployment-controller  Scaled up replica set nginx-deployment-2035384211 to 3
        Normal  ScalingReplicaSet  24s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 1
        Normal  ScalingReplicaSet  22s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 2
        Normal  ScalingReplicaSet  22s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 2
        Normal  ScalingReplicaSet  19s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 1
        Normal  ScalingReplicaSet  19s   deployment-controller  Scaled up replica set nginx-deployment-1564180365 to 3
        Normal  ScalingReplicaSet  14s   deployment-controller  Scaled down replica set nginx-deployment-2035384211 to 0
    

    可以看到,当第一次创建 Deployment 时,它创建了一个 ReplicaSet(nginx-deployment-2035384211) 并将其直接扩容至 3 个副本。更新 Deployment 时,它创建了一个新的 ReplicaSet (nginx-deployment-1564180365),并将其扩容为 1,等待其就绪;然后将旧 ReplicaSet 缩容到 2, 将新的 ReplicaSet 扩容到 2 以便至少有 3 个 Pod 可用且最多创建 4 个 Pod。 然后,它使用相同的滚动更新策略继续对新的 ReplicaSet 扩容并对旧的 ReplicaSet 缩容。 最后,你将有 3 个可用的副本在新的 ReplicaSet 中,旧 ReplicaSet 将缩容到 0。

翻转(多 Deployment 动态更新)

Deployment 控制器每次注意到新的 Deployment 时,都会创建一个 ReplicaSet 以启动所需的 Pods。 如果更新了 Deployment,则控制标签匹配 .spec.selector 但模板不匹配 .spec.template 的 Pods 的现有 ReplicaSet 被缩容。最终,新的 ReplicaSet 缩放为 .spec.replicas 个副本, 所有旧 ReplicaSets 缩放为 0 个副本。

当 Deployment 正在上线时被更新,Deployment 会针对更新创建一个新的 ReplicaSet 并开始对其扩容,之前正在被扩容的 ReplicaSet 会被翻转,添加到旧 ReplicaSets 列表 并开始缩容。

例如,假定你在创建一个 Deployment 以生成 nginx:1.14.2 的 5 个副本,但接下来 更新 Deployment 以创建 5 个 nginx:1.16.1 的副本,而此时只有 3 个 nginx:1.14.2 副本已创建。在这种情况下,Deployment 会立即开始杀死 3 个 nginx:1.14.2 Pods, 并开始创建 nginx:1.16.1 Pods。它不会等待 nginx:1.14.2 的 5 个副本都创建完成后才开始执行变更动作。

更改标签选择算符

通常不鼓励更新标签选择算符。建议你提前规划选择算符。 在任何情况下,如果需要更新标签选择算符,请格外小心, 并确保自己了解这背后可能发生的所有事情。

  • 添加选择算符时要求使用新标签更新 Deployment 规约中的 Pod 模板标签,否则将返回验证错误。 此更改是非重叠的,也就是说新的选择算符不会选择使用旧选择算符所创建的 ReplicaSet 和 Pod, 这会导致创建新的 ReplicaSet 时所有旧 ReplicaSet 都会被孤立。
  • 选择算符的更新如果更改了某个算符的键名,这会导致与添加算符时相同的行为。
  • 删除选择算符的操作会删除从 Deployment 选择算符中删除现有算符。 此操作不需要更改 Pod 模板标签。现有 ReplicaSet 不会被孤立,也不会因此创建新的 ReplicaSet, 但请注意已删除的标签仍然存在于现有的 Pod 和 ReplicaSet 中。

回滚 Deployment

有时,你可能想要回滚 Deployment;例如,当 Deployment 不稳定时(例如进入反复崩溃状态)。 默认情况下,Deployment 的所有上线记录都保留在系统中,以便可以随时回滚 (你可以通过修改修订历史记录限制来更改这一约束)。

  • 假设你在更新 Deployment 时犯了一个拼写错误,将镜像名称命名设置为 nginx:1.161 而不是 nginx:1.16.1

    kubectl set image deployment/nginx-deployment nginx=nginx:1.161 --record=true
    

    输出类似于:

    deployment/nginx-deployment image updated
    
  • 此上线进程会出现停滞。你可以通过检查上线状态来验证:

    kubectl rollout status deployment/nginx-deployment
    

    输出类似于:

    Waiting for rollout to finish: 1 out of 3 new replicas have been updated...
    
  • 按 Ctrl-C 停止上述上线状态观测。有关上线停滞的详细信息,参考这里
  • 你可以看到旧的副本有两个(nginx-deployment-1564180365nginx-deployment-2035384211), 新的副本有 1 个(nginx-deployment-3066724191):

    kubectl get rs
    

    输出类似于:

    NAME                          DESIRED   CURRENT   READY   AGE
    nginx-deployment-1564180365   3         3         3       25s
    nginx-deployment-2035384211   0         0         0       36s
    nginx-deployment-3066724191   1         1         0       6s
    
  • 查看所创建的 Pod,你会注意到新 ReplicaSet 所创建的 1 个 Pod 卡顿在镜像拉取循环中。

    kubectl get pods
    

    输出类似于:

    NAME                                READY     STATUS             RESTARTS   AGE
    nginx-deployment-1564180365-70iae   1/1       Running            0          25s
    nginx-deployment-1564180365-jbqqo   1/1       Running            0          25s
    nginx-deployment-1564180365-hysrc   1/1       Running            0          25s
    nginx-deployment-3066724191-08mng   0/1       ImagePullBackOff   0          6s
    
  • 获取 Deployment 描述信息:

    kubectl describe deployment
    

    输出类似于:

    Name:           nginx-deployment
    Namespace:      default
    CreationTimestamp:  Tue, 15 Mar 2016 14:48:04 -0700
    Labels:         app=nginx
    Selector:       app=nginx
    Replicas:       3 desired | 1 updated | 4 total | 3 available | 1 unavailable
    StrategyType:       RollingUpdate
    MinReadySeconds:    0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
      Containers:
       nginx:
        Image:        nginx:1.161
        Port:         80/TCP
        Host Port:    0/TCP
        Environment:  <none>
        Mounts:       <none>
      Volumes:        <none>
    Conditions:
      Type           Status  Reason
      ----           ------  ------
      Available      True    MinimumReplicasAvailable
      Progressing    True    ReplicaSetUpdated
    OldReplicaSets:     nginx-deployment-1564180365 (3/3 replicas created)
    NewReplicaSet:      nginx-deployment-3066724191 (1/1 replicas created)
    Events:
      FirstSeen LastSeen    Count   From                    SubobjectPath   Type        Reason              Message
      --------- --------    -----   ----                    -------------   --------    ------              -------
      1m        1m          1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-2035384211 to 3
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 1
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 2
      22s       22s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 2
      21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 1
      21s       21s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-1564180365 to 3
      13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled down replica set nginx-deployment-2035384211 to 0
      13s       13s         1       {deployment-controller }                Normal      ScalingReplicaSet   Scaled up replica set nginx-deployment-3066724191 to 1
    

    要解决此问题,需要回滚到以前稳定的 Deployment 版本。

检查 Deployment 上线历史

按照如下步骤检查回滚历史:

  1. 首先,检查 Deployment 修订历史:

    kubectl rollout history deployment/nginx-deployment
    

    输出类似于:

    deployments "nginx-deployment"
    REVISION    CHANGE-CAUSE
    1           kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml
    2           kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    3           kubectl set image deployment/nginx-deployment nginx=nginx:1.161
    

    CHANGE-CAUSE 的内容是从 Deployment 的 kubernetes.io/change-cause 注解复制过来的。 复制动作发生在修订版本创建时。你可以通过以下方式设置 CHANGE-CAUSE 消息:

    • 使用 kubectl annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.16.1" 为 Deployment 添加注解。
    • 手动编辑资源的清单。
  1. 要查看修订历史的详细信息,运行:

    kubectl rollout history deployment/nginx-deployment --revision=2
    

    输出类似于:

    deployments "nginx-deployment" revision 2
      Labels:       app=nginx
              pod-template-hash=1159050644
      Annotations:  kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
      Containers:
       nginx:
        Image:      nginx:1.16.1
        Port:       80/TCP
         QoS Tier:
            cpu:      BestEffort
            memory:   BestEffort
        Environment Variables:      <none>
      No volumes.
    

回滚到之前的修订版本

按照下面给出的步骤将 Deployment 从当前版本回滚到以前的版本(即版本 2)。

  1. 假定现在你已决定撤消当前上线并回滚到以前的修订版本:

    kubectl rollout undo deployment/nginx-deployment
    

    输出类似于:

    deployment.apps/nginx-deployment
    

    或者,你也可以通过使用 --to-revision 来回滚到特定修订版本:

    kubectl rollout undo deployment/nginx-deployment --to-revision=2
    

    输出类似于:

    deployment.apps/nginx-deployment
    

    与回滚相关的指令的更详细信息,请参考 kubectl rollout

    现在,Deployment 正在回滚到以前的稳定版本。正如你所看到的,Deployment 控制器生成了回滚到修订版本 2 的 DeploymentRollback 事件。

  1. 检查回滚是否成功以及 Deployment 是否正在运行,运行:

    kubectl get deployment nginx-deployment
    

    输出类似于:

    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment   3/3     3            3           30m
    
  1. 获取 Deployment 描述信息:

    kubectl describe deployment nginx-deployment
    

    输出类似于:

    Name:                   nginx-deployment
    Namespace:              default
    CreationTimestamp:      Sun, 02 Sep 2018 18:17:55 -0500
    Labels:                 app=nginx
    Annotations:            deployment.kubernetes.io/revision=4
                            kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    Selector:               app=nginx
    Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
    StrategyType:           RollingUpdate
    MinReadySeconds:        0
    RollingUpdateStrategy:  25% max unavailable, 25% max surge
    Pod Template:
      Labels:  app=nginx
      Containers:
       nginx:
        Image:        nginx:1.16.1
        Port:         80/TCP
        Host Port:    0/TCP
        Environment:  <none>
        Mounts:       <none>
      Volumes:        <none>
    Conditions:
      Type           Status  Reason
      ----           ------  ------
      Available      True    MinimumReplicasAvailable
      Progressing    True    NewReplicaSetAvailable
    OldReplicaSets:  <none>
    NewReplicaSet:   nginx-deployment-c4747d96c (3/3 replicas created)
    Events:
      Type    Reason              Age   From                   Message
      ----    ------              ----  ----                   -------
      Normal  ScalingReplicaSet   12m   deployment-controller  Scaled up replica set nginx-deployment-75675f5897 to 3
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 1
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 2
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 2
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 1
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-c4747d96c to 3
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled down replica set nginx-deployment-75675f5897 to 0
      Normal  ScalingReplicaSet   11m   deployment-controller  Scaled up replica set nginx-deployment-595696685f to 1
      Normal  DeploymentRollback  15s   deployment-controller  Rolled back deployment "nginx-deployment" to revision 2
      Normal  ScalingReplicaSet   15s   deployment-controller  Scaled down replica set nginx-deployment-595696685f to 0
    

缩放 Deployment

你可以使用如下指令缩放 Deployment:

kubectl scale deployment/nginx-deployment --replicas=10

输出类似于:

deployment.apps/nginx-deployment scaled

假设集群启用了Pod 的水平自动缩放, 你可以为 Deployment 设置自动缩放器,并基于现有 Pod 的 CPU 利用率选择要运行的 Pod 个数下限和上限。

kubectl autoscale deployment/nginx-deployment --min=10 --max=15 --cpu-percent=80

输出类似于:

deployment.apps/nginx-deployment scaled

比例缩放

RollingUpdate 的 Deployment 支持同时运行应用程序的多个版本。 当自动缩放器缩放处于上线进程(仍在进行中或暂停)中的 RollingUpdate Deployment 时, Deployment 控制器会平衡现有的活跃状态的 ReplicaSets(含 Pods 的 ReplicaSets)中的额外副本, 以降低风险。这称为 比例缩放(Proportional Scaling)

例如,你正在运行一个 10 个副本的 Deployment,其 maxSurge=3,maxUnavailable=2。

  • 确保 Deployment 的这 10 个副本都在运行。

    kubectl get deploy
    

    输出类似于:

    NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    nginx-deployment     10        10        10           10          50s
    
  • 更新 Deployment 使用新镜像,碰巧该镜像无法从集群内部解析。

    kubectl set image deployment/nginx-deployment nginx=nginx:sometag
    

    输出类似于:

    deployment.apps/nginx-deployment image updated
    
  • 镜像更新使用 ReplicaSet nginx-deployment-1989198191 启动新的上线过程, 但由于上面提到的 maxUnavailable 要求,该进程被阻塞了。检查上线状态:

    kubectl get rs
    

    输出类似于:

    NAME                          DESIRED   CURRENT   READY     AGE
    nginx-deployment-1989198191   5         5         0         9s
    nginx-deployment-618515232    8         8         8         1m
    
  • 然后,出现了新的 Deployment 扩缩请求。自动缩放器将 Deployment 副本增加到 15。 Deployment 控制器需要决定在何处添加 5 个新副本。如果未使用比例缩放,所有 5 个副本 都将添加到新的 ReplicaSet 中。使用比例缩放时,可以将额外的副本分布到所有 ReplicaSet。 较大比例的副本会被添加到拥有最多副本的 ReplicaSet,而较低比例的副本会进入到 副本较少的 ReplicaSet。所有剩下的副本都会添加到副本最多的 ReplicaSet。 具有零副本的 ReplicaSets 不会被扩容。

在上面的示例中,3 个副本被添加到旧 ReplicaSet 中,2 个副本被添加到新 ReplicaSet。 假定新的副本都很健康,上线过程最终应将所有副本迁移到新的 ReplicaSet 中。 要确认这一点,请运行:

kubectl get deploy

输出类似于:

NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     15        18        7            8           7m

上线状态确认了副本是如何被添加到每个 ReplicaSet 的。

kubectl get rs

输出类似于:

NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

暂停、恢复 Deployment 的上线过程

在你更新一个 Deployment 的时候,或者计划更新它的时候, 你可以在触发一个或多个更新之前暂停 Deployment 的上线过程。 当你准备应用这些变更时,你可以重新恢复 Deployment 上线过程。 这样做使得你能够在暂停和恢复执行之间应用多个修补程序,而不会触发不必要的上线操作。

  • 例如,对于一个刚刚创建的 Deployment:

    获取该 Deployment 信息:

    kubectl get deploy
    

    输出类似于:

    NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    nginx     3         3         3            3           1m
    

    获取上线状态:

    kubectl get rs
    

    输出类似于:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   3         3         3         1m
    
  • 使用如下指令暂停上线:

    kubectl rollout pause deployment/nginx-deployment
    

    输出类似于:

    deployment.apps/nginx-deployment paused
    
  • 接下来更新 Deployment 镜像:

    kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
    

    输出类似于:

    deployment.apps/nginx-deployment image updated
    
  • 注意没有新的上线被触发:

    kubectl rollout history deployment/nginx-deployment
    

    输出类似于:

    deployments "nginx"
    REVISION  CHANGE-CAUSE
    1   <none>
    
  • 获取上线状态验证现有的 ReplicaSet 没有被更改:

    kubectl get rs
    

    输出类似于:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   3         3         3         2m
    
  • 你可以根据需要执行很多更新操作,例如,可以要使用的资源:

    kubectl set resources deployment/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
    

    输出类似于:

    deployment.apps/nginx-deployment resource requirements updated
    

    暂停 Deployment 上线之前的初始状态将继续发挥作用,但新的更新在 Deployment 上线被暂停期间不会产生任何效果。

  • 最终,恢复 Deployment 上线并观察新的 ReplicaSet 的创建过程,其中包含了所应用的所有更新:

    kubectl rollout resume deployment/nginx-deployment
    

    输出类似于这样:

    deployment.apps/nginx-deployment resumed
    
  • 观察上线的状态,直到完成。

    kubectl get rs -w
    

    输出类似于:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   2         2         2         2m
    nginx-3926361531   2         2         0         6s
    nginx-3926361531   2         2         1         18s
    nginx-2142116321   1         2         2         2m
    nginx-2142116321   1         2         2         2m
    nginx-3926361531   3         2         1         18s
    nginx-3926361531   3         2         1         18s
    nginx-2142116321   1         1         1         2m
    nginx-3926361531   3         3         1         18s
    nginx-3926361531   3         3         2         19s
    nginx-2142116321   0         1         1         2m
    nginx-2142116321   0         1         1         2m
    nginx-2142116321   0         0         0         2m
    nginx-3926361531   3         3         3         20s
    
  • 获取最近上线的状态:

    kubectl get rs
    

    输出类似于:

    NAME               DESIRED   CURRENT   READY     AGE
    nginx-2142116321   0         0         0         2m
    nginx-3926361531   3         3         3         28s
    

Deployment 状态

Deployment 的生命周期中会有许多状态。上线新的 ReplicaSet 期间可能处于 Progressing(进行中),可能是 Complete(已完成),也可能是 Failed(失败)以至于无法继续进行。

进行中的 Deployment

执行下面的任务期间,Kubernetes 标记 Deployment 为进行中(Progressing)_:

  • Deployment 创建新的 ReplicaSet
  • Deployment 正在为其最新的 ReplicaSet 扩容
  • Deployment 正在为其旧有的 ReplicaSet(s) 缩容
  • 新的 Pods 已经就绪或者可用(就绪至少持续了 MinReadySeconds 秒)。

当上线过程进入“Progressing”状态时,Deployment 控制器会向 Deployment 的 .status.conditions 中添加包含下面属性的状况条目:

  • type: Progressing
  • status: "True"
  • reason: NewReplicaSetCreated | reason: FoundNewReplicaSet | reason: ReplicaSetUpdated

你可以使用 kubectl rollout status 监视 Deployment 的进度。

完成的 Deployment

当 Deployment 具有以下特征时,Kubernetes 将其标记为完成(Complete);

  • 与 Deployment 关联的所有副本都已更新到指定的最新版本,这意味着之前请求的所有更新都已完成。
  • 与 Deployment 关联的所有副本都可用。
  • 未运行 Deployment 的旧副本。

当上线过程进入“Complete”状态时,Deployment 控制器会向 Deployment 的 .status.conditions 中添加包含下面属性的状况条目:

  • type: Progressing
  • status: "True"
  • reason: NewReplicaSetAvailable

这一 Progressing 状况的状态值会持续为 "True",直至新的上线动作被触发。 即使副本的可用状态发生变化(进而影响 Available 状况),Progressing 状况的值也不会变化。

你可以使用 kubectl rollout status 检查 Deployment 是否已完成。 如果上线成功完成,kubectl rollout status 返回退出代码 0。

kubectl rollout status deployment/nginx-deployment

输出类似于:

Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out

kubectl rollout 命令获得的返回状态为 0(成功):

$ echo $?
0

失败的 Deployment

你的 Deployment 可能会在尝试部署其最新的 ReplicaSet 受挫,一直处于未完成状态。 造成此情况一些可能因素如下:

  • 配额(Quota)不足
  • 就绪探测(Readiness Probe)失败
  • 镜像拉取错误
  • 权限不足
  • 限制范围(Limit Ranges)问题
  • 应用程序运行时的配置错误

检测此状况的一种方法是在 Deployment 规约中指定截止时间参数: (.spec.progressDeadlineSeconds)。 .spec.progressDeadlineSeconds 给出的是一个秒数值,Deployment 控制器在(通过 Deployment 状态) 标示 Deployment 进展停滞之前,需要等待所给的时长。

以下 kubectl 命令设置规约中的 progressDeadlineSeconds,从而告知控制器 在 10 分钟后报告 Deployment 没有进展:

kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'

输出类似于:

deployment.apps/nginx-deployment patched

超过截止时间后,Deployment 控制器将添加具有以下属性的 Deployment 状况到 Deployment 的 .status.conditions 中:

  • Type=Progressing
  • Status=False
  • Reason=ProgressDeadlineExceeded

这一状况也可能会比较早地失败,因而其状态值被设置为 "False", 其原因为 ReplicaSetCreateError。 一旦 Deployment 上线完成,就不再考虑其期限。

参考 Kubernetes API Conventions 获取更多状态状况相关的信息。

Deployment 可能会出现瞬时性的错误,可能因为设置的超时时间过短, 也可能因为其他可认为是临时性的问题。例如,假定所遇到的问题是配额不足。 如果描述 Deployment,你将会注意到以下部分:

kubectl describe deployment nginx-deployment

输出类似于:

<...>
Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     True    ReplicaSetUpdated
  ReplicaFailure  True    FailedCreate
<...>

如果运行 kubectl get deployment nginx-deployment -o yaml,Deployment 状态输出 将类似于这样:

status:
  availableReplicas: 2
  conditions:
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: Replica set "nginx-deployment-4262182780" is progressing.
    reason: ReplicaSetUpdated
    status: "True"
    type: Progressing
  - lastTransitionTime: 2016-10-04T12:25:42Z
    lastUpdateTime: 2016-10-04T12:25:42Z
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: 2016-10-04T12:25:39Z
    lastUpdateTime: 2016-10-04T12:25:39Z
    message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
      object-counts, requested: pods=1, used: pods=3, limited: pods=2'
    reason: FailedCreate
    status: "True"
    type: ReplicaFailure
  observedGeneration: 3
  replicas: 2
  unavailableReplicas: 2

最终,一旦超过 Deployment 进度限期,Kubernetes 将更新状态和进度状况的原因:

Conditions:
  Type            Status  Reason
  ----            ------  ------
  Available       True    MinimumReplicasAvailable
  Progressing     False   ProgressDeadlineExceeded
  ReplicaFailure  True    FailedCreate

可以通过缩容 Deployment 或者缩容其他运行状态的控制器,或者直接在命名空间中增加配额 来解决配额不足的问题。如果配额条件满足,Deployment 控制器完成了 Deployment 上线操作, Deployment 状态会更新为成功状况(Status=True and Reason=NewReplicaSetAvailable)。

Conditions:
  Type          Status  Reason
  ----          ------  ------
  Available     True    MinimumReplicasAvailable
  Progressing   True    NewReplicaSetAvailable

type: Available 加上 status: True 意味着 Deployment 具有最低可用性。 最低可用性由 Deployment 策略中的参数指定。 type: Progressing 加上 status: True 表示 Deployment 处于上线过程中,并且正在运行, 或者已成功完成进度,最小所需新副本处于可用。 请参阅对应状况的 Reason 了解相关细节。 在我们的案例中 reason: NewReplicaSetAvailable 表示 Deployment 已完成。

你可以使用 kubectl rollout status 检查 Deployment 是否未能取得进展。 如果 Deployment 已超过进度限期,kubectl rollout status 返回非零退出代码。

kubectl rollout status deployment/nginx-deployment

输出类似于:

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline

kubectl rollout 命令的退出状态为 1(表明发生了错误):

$ echo $?
1

对失败 Deployment 的操作

可应用于已完成的 Deployment 的所有操作也适用于失败的 Deployment。 你可以对其执行扩缩容、回滚到以前的修订版本等操作,或者在需要对 Deployment 的 Pod 模板应用多项调整时,将 Deployment 暂停。

清理策略

你可以在 Deployment 中设置 .spec.revisionHistoryLimit 字段以指定保留此 Deployment 的多少个旧有 ReplicaSet。其余的 ReplicaSet 将在后台被垃圾回收。 默认情况下,此值为 10。

金丝雀部署

如果要使用 Deployment 向用户子集或服务器子集上线版本, 则可以遵循资源管理所描述的金丝雀模式, 创建多个 Deployment,每个版本一个。

编写 Deployment 规约

同其他 Kubernetes 配置一样, Deployment 需要 apiVersionkindmetadata 字段。 有关配置文件的其他信息,请参考部署 Deployment、 配置容器和使用 kubectl 管理资源等相关文档。

Deployment 对象的名称必须是合法的 DNS 子域名。 Deployment 还需要 .spec 部分

Pod 模板

.spec 中只有 .spec.template.spec.selector 是必需的字段。

.spec.template 是一个 Pod 模板。 它和 Pod 的语法规则完全相同。 只是这里它是嵌套的,因此不需要 apiVersionkind

除了 Pod 的必填字段外,Deployment 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。 对于标签,请确保不要与其他控制器重叠。请参考选择算符

只有 .spec.template.spec.restartPolicy 等于 Always 才是被允许的,这也是在没有指定时的默认设置。

副本

.spec.replicas 是指定所需 Pod 的可选字段。它的默认值是1。

如果你对某个 Deployment 执行了手动扩缩操作(例如,通过 kubectl scale deployment deployment --replicas=X), 之后基于清单对 Deployment 执行了更新操作(例如通过运行 kubectl apply -f deployment.yaml),那么通过应用清单而完成的更新会覆盖之前手动扩缩所作的变更。

如果一个 HorizontalPodAutoscaler (或者其他执行水平扩缩操作的类似 API)在管理 Deployment 的扩缩, 则不要设置 .spec.replicas

恰恰相反,应该允许 Kubernetes 控制面来自动管理 .spec.replicas 字段。

选择算符

.spec.selector 是指定本 Deployment 的 Pod 标签选择算符的必需字段。

.spec.selector 必须匹配 .spec.template.metadata.labels,否则请求会被 API 拒绝。

在 API apps/v1版本中,.spec.selector.metadata.labels 如果没有设置的话, 不会被默认设置为 .spec.template.metadata.labels,所以需要明确进行设置。 同时在 apps/v1版本中,Deployment 创建后 .spec.selector 是不可变的。

当 Pod 的标签和选择算符匹配,但其模板和 .spec.template 不同时,或者此类 Pod 的总数超过 .spec.replicas 的设置时,Deployment 会终结之。 如果 Pods 总数未达到期望值,Deployment 会基于 .spec.template 创建新的 Pod。

如果有多个控制器的选择算符发生重叠,则控制器之间会因冲突而无法正常工作。

策略

.spec.strategy 策略指定用于用新 Pods 替换旧 Pods 的策略。 .spec.strategy.type 可以是 “Recreate” 或 “RollingUpdate”。“RollingUpdate” 是默认值。

重新创建 Deployment

如果 .spec.strategy.type==Recreate,在创建新 Pods 之前,所有现有的 Pods 会被杀死。

滚动更新 Deployment

Deployment 会在 .spec.strategy.type==RollingUpdate时,采取 滚动更新的方式更新 Pods。你可以指定 maxUnavailablemaxSurge 来控制滚动更新 过程。

最大不可用

.spec.strategy.rollingUpdate.maxUnavailable 是一个可选字段,用来指定 更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5),也可以是所需 Pods 的百分比(例如,10%)。百分比值会转换成绝对数并去除小数部分。 如果 .spec.strategy.rollingUpdate.maxSurge 为 0,则此值不能为 0。 默认值为 25%。

例如,当此值设置为 30% 时,滚动更新开始时会立即将旧 ReplicaSet 缩容到期望 Pod 个数的70%。 新 Pod 准备就绪后,可以继续缩容旧有的 ReplicaSet,然后对新的 ReplicaSet 扩容, 确保在更新期间可用的 Pods 总数在任何时候都至少为所需的 Pod 个数的 70%。

最大峰值

.spec.strategy.rollingUpdate.maxSurge 是一个可选字段,用来指定可以创建的超出期望 Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pods 的百分比(例如,10%)。 如果 MaxUnavailable 为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。 此字段的默认值为 25%。

例如,当此值为 30% 时,启动滚动更新后,会立即对新的 ReplicaSet 扩容,同时保证新旧 Pod 的总数不超过所需 Pod 总数的 130%。一旦旧 Pods 被杀死,新的 ReplicaSet 可以进一步扩容, 同时确保更新期间的任何时候运行中的 Pods 总数最多为所需 Pods 总数的 130%。

进度期限秒数

.spec.progressDeadlineSeconds 是一个可选字段,用于指定系统在报告 Deployment 进展失败 之前等待 Deployment 取得进展的秒数。 这类报告会在资源状态中体现为 type: Progressingstatus: Falsereason: ProgressDeadlineExceeded。Deployment 控制器将持续重试 Deployment。 将来,一旦实现了自动回滚,Deployment 控制器将在探测到这样的条件时立即回滚 Deployment。

如果指定,则此字段值需要大于 .spec.minReadySeconds 取值。

最短就绪时间

.spec.minReadySeconds 是一个可选字段,用于指定新创建的 Pod 在没有任意容器崩溃情况下的最小就绪时间, 只有超出这个时间 Pod 才被视为可用。默认值为 0(Pod 在准备就绪后立即将被视为可用)。 要了解何时 Pod 被视为就绪, 可参考容器探针

修订历史限制

Deployment 的修订历史记录存储在它所控制的 ReplicaSets 中。

.spec.revisionHistoryLimit 是一个可选字段,用来设定出于会滚目的所要保留的旧 ReplicaSet 数量。 这些旧 ReplicaSet 会消耗 etcd 中的资源,并占用 kubectl get rs 的输出。 每个 Deployment 修订版本的配置都存储在其 ReplicaSets 中;因此,一旦删除了旧的 ReplicaSet, 将失去回滚到 Deployment 的对应修订版本的能力。 默认情况下,系统保留 10 个旧 ReplicaSet,但其理想值取决于新 Deployment 的频率和稳定性。

更具体地说,将此字段设置为 0 意味着将清理所有具有 0 个副本的旧 ReplicaSet。 在这种情况下,无法撤消新的 Deployment 上线,因为它的修订历史被清除了。

paused(暂停的)

.spec.paused 是用于暂停和恢复 Deployment 的可选布尔字段。 暂停的 Deployment 和未暂停的 Deployment 的唯一区别是,Deployment 处于暂停状态时, PodTemplateSpec 的任何修改都不会触发新的上线。 Deployment 在创建时是默认不会处于暂停状态。

接下来

3.5.2.2 - ReplicaSet

ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。 因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。

ReplicaSet 的工作原理

ReplicaSet 是通过一组字段来定义的,包括一个用来识别可获得的 Pod 的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新 Pod 以满足副本个数条件时要使用的 Pod 模板等等。 每个 ReplicaSet 都通过根据需要创建和删除 Pod 以使得副本个数达到期望值, 进而实现其存在价值。当 ReplicaSet 需要创建新的 Pod 时,会使用所提供的 Pod 模板。

ReplicaSet 通过 Pod 上的 metadata.ownerReferences 字段连接到附属 Pod,该字段给出当前对象的属主资源。 ReplicaSet 所获得的 Pod 都在其 ownerReferences 字段中包含了属主 ReplicaSet 的标识信息。正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态, 并据此计划其操作行为。

ReplicaSet 使用其选择算符来辨识要获得的 Pod 集合。如果某个 Pod 没有 OwnerReference 或者其 OwnerReference 不是一个控制器, 且其匹配到某 ReplicaSet 的选择算符,则该 Pod 立即被此 ReplicaSet 获得。

何时使用 ReplicaSet

ReplicaSet 确保任何时间都有指定数量的 Pod 副本在运行。 然而,Deployment 是一个更高级的概念,它管理 ReplicaSet,并向 Pod 提供声明式的更新以及许多其他有用的功能。 因此,我们建议使用 Deployment 而不是直接使用 ReplicaSet, 除非你需要自定义更新业务流程或根本不需要更新。

这实际上意味着,你可能永远不需要操作 ReplicaSet 对象:而是使用 Deployment,并在 spec 部分定义你的应用。

示例

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # 按你的实际情况修改副本数
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

将此清单保存到 frontend.yaml 中,并将其提交到 Kubernetes 集群, 就能创建 yaml 文件所定义的 ReplicaSet 及其管理的 Pod。

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

你可以看到当前被部署的 ReplicaSet:

kubectl get rs

并看到你所创建的前端:

NAME       DESIRED   CURRENT   READY   AGE
frontend   3         3         3       6s

你也可以查看 ReplicaSet 的状态:

kubectl describe rs/frontend

你会看到类似如下的输出:

Name:         frontend
Namespace:    default
Selector:     tier=frontend
Labels:       app=guestbook
              tier=frontend
Annotations:  kubectl.kubernetes.io/last-applied-configuration:
                {"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"labels":{"app":"guestbook","tier":"frontend"},"name":"frontend",...
Replicas:     3 current / 3 desired
Pods Status:  3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  tier=frontend
  Containers:
   php-redis:
    Image:        gcr.io/google_samples/gb-frontend:v3
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  117s  replicaset-controller  Created pod: frontend-wtsmm
  Normal  SuccessfulCreate  116s  replicaset-controller  Created pod: frontend-b2zdv
  Normal  SuccessfulCreate  116s  replicaset-controller  Created pod: frontend-vcmts

最后可以查看启动了的 Pod 集合:

kubectl get pods

你会看到类似如下的 Pod 信息:

NAME             READY   STATUS    RESTARTS   AGE
frontend-b2zdv   1/1     Running   0          6m36s
frontend-vcmts   1/1     Running   0          6m36s
frontend-wtsmm   1/1     Running   0          6m36s

你也可以查看 Pod 的属主引用被设置为前端的 ReplicaSet。 要实现这点,可取回运行中的某个 Pod 的 YAML:

kubectl get pods frontend-b2zdv -o yaml

输出将类似这样,frontend ReplicaSet 的信息被设置在 metadata 的 ownerReferences 字段中:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2020-02-12T07:06:16Z"
  generateName: frontend-
  labels:
    tier: frontend
  name: frontend-b2zdv
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: frontend
    uid: f391f6db-bb9b-4c09-ae74-6a1f77f3d5cf
...

非模板 Pod 的获得

尽管你完全可以直接创建裸的 Pod,强烈建议你确保这些裸的 Pod 并不包含可能与你的某个 ReplicaSet 的选择算符相匹配的标签。原因在于 ReplicaSet 并不仅限于拥有在其模板中设置的 Pod,它还可以像前面小节中所描述的那样获得其他 Pod。

apiVersion: v1
kind: Pod
metadata:
  name: pod1
  labels:
    tier: frontend
spec:
  containers:
  - name: hello1
    image: gcr.io/google-samples/hello-app:2.0

---

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  labels:
    tier: frontend
spec:
  containers:
  - name: hello2
    image: gcr.io/google-samples/hello-app:1.0

由于这些 Pod 没有控制器(Controller,或其他对象)作为其属主引用, 并且其标签与 frontend ReplicaSet 的选择算符匹配,它们会立即被该 ReplicaSet 获取。

假定你在 frontend ReplicaSet 已经被部署之后创建 Pod,并且你已经在 ReplicaSet 中设置了其初始的 Pod 副本数以满足其副本计数需要:

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

新的 Pod 会被该 ReplicaSet 获取,并立即被 ReplicaSet 终止, 因为它们的存在会使得 ReplicaSet 中 Pod 个数超出其期望值。

取回 Pod:

kubectl get pods

输出显示新的 Pod 或者已经被终止,或者处于终止过程中:

NAME             READY   STATUS        RESTARTS   AGE
frontend-b2zdv   1/1     Running       0          10m
frontend-vcmts   1/1     Running       0          10m
frontend-wtsmm   1/1     Running       0          10m
pod1             0/1     Terminating   0          1s
pod2             0/1     Terminating   0          1s

如果你先行创建 Pod:

kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml

之后再创建 ReplicaSet:

kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml

你会看到 ReplicaSet 已经获得了该 Pod,并仅根据其规约创建新的 Pod, 直到新的 Pod 和原来的 Pod 的总数达到其预期个数。 这时取回 Pod 列表:

kubectl get pods

将会生成下面的输出:

NAME             READY   STATUS    RESTARTS   AGE
frontend-hmmj2   1/1     Running   0          9s
pod1             1/1     Running   0          36s
pod2             1/1     Running   0          36s

采用这种方式,一个 ReplicaSet 中可以包含异质的 Pod 集合。

编写 ReplicaSet 的清单

与所有其他 Kubernetes API 对象一样,ReplicaSet 也需要 apiVersionkind、和 metadata 字段。 对于 ReplicaSet 而言,其 kind 始终是 ReplicaSet。

ReplicaSet 对象的名称必须是合法的 DNS 子域名

ReplicaSet 也需要 .spec 部分。

Pod 模板

.spec.template 是一个 Pod 模板, 要求设置标签。在 frontend.yaml 示例中,我们指定了标签 tier: frontend。 注意不要将标签与其他控制器的选择算符重叠,否则那些控制器会尝试收养此 Pod。

对于模板的重启策略 字段,.spec.template.spec.restartPolicy,唯一允许的取值是 Always,这也是默认值.

Pod 选择算符

.spec.selector 字段是一个标签选择算符。 如前文中所讨论的,这些是用来标识要被获取的 Pod 的标签。在签名的 frontend.yaml 示例中,选择算符为:

matchLabels:
  tier: frontend

在 ReplicaSet 中,.spec.template.metadata.labels 的值必须与 spec.selector 值相匹配,否则该配置会被 API 拒绝。

Replicas

你可以通过设置 .spec.replicas 来指定要同时运行的 Pod 个数。 ReplicaSet 创建、删除 Pod 以与此值匹配。

如果你没有指定 .spec.replicas,那么默认值为 1。

使用 ReplicaSet

删除 ReplicaSet 和它的 Pod

要删除 ReplicaSet 和它的所有 Pod,使用 kubectl delete 命令。 默认情况下,垃圾收集器 自动删除所有依赖的 Pod。

当使用 REST API 或 client-go 库时,你必须在 -d 选项中将 propagationPolicy 设置为 BackgroundForeground。例如:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
  -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
  -H "Content-Type: application/json"

只删除 ReplicaSet

你可以只删除 ReplicaSet 而不影响它的各个 Pod,方法是使用 kubectl delete 命令并设置 --cascade=orphan 选项。

当使用 REST API 或 client-go 库时,你必须将 propagationPolicy 设置为 Orphan。 例如:

kubectl proxy --port=8080
curl -X DELETE  'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
  -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
  -H "Content-Type: application/json"

一旦删除了原来的 ReplicaSet,就可以创建一个新的来替换它。 由于新旧 ReplicaSet 的 .spec.selector 是相同的,新的 ReplicaSet 将接管老的 Pod。 但是,它不会努力使现有的 Pod 与新的、不同的 Pod 模板匹配。 若想要以可控的方式更新 Pod 的规约,可以使用 Deployment 资源,因为 ReplicaSet 并不直接支持滚动更新。

将 Pod 从 ReplicaSet 中隔离

可以通过改变标签来从 ReplicaSet 中移除 Pod。 这种技术可以用来从服务中去除 Pod,以便进行排错、数据恢复等。 以这种方式移除的 Pod 将被自动替换(假设副本的数量没有改变)。

扩缩 RepliaSet

通过更新 .spec.replicas 字段,ReplicaSet 可以被轻松地进行扩缩。ReplicaSet 控制器能确保匹配标签选择器的数量的 Pod 是可用的和可操作的。

在降低集合规模时,ReplicaSet 控制器通过对可用的所有 Pod 进行排序来优先选择要被删除的那些 Pod。 其一般性算法如下:

  1. 首先选择剔除悬决(Pending,且不可调度)的各个 Pod
  2. 如果设置了 controller.kubernetes.io/pod-deletion-cost 注解,则注解值较小的优先被裁减掉
  3. 所处节点上副本个数较多的 Pod 优先于所处节点上副本较少者
  4. 如果 Pod 的创建时间不同,最近创建的 Pod 优先于早前创建的 Pod 被裁减。 (当 LogarithmicScaleDown 这一特性门控 被启用时,创建时间是按整数幂级来分组的)。

如果以上比较结果都相同,则随机选择。

Pod 删除开销

特性状态: Kubernetes v1.22 [beta]

通过使用 controller.kubernetes.io/pod-deletion-cost 注解,用户可以对 ReplicaSet 缩容时要先删除哪些 Pod 设置偏好。

此注解要设置到 Pod 上,取值范围为 [-2147483647, 2147483647]。 所代表的是删除同一 ReplicaSet 中其他 Pod 相比较而言的开销。 删除开销较小的 Pod 比删除开销较高的 Pod 更容易被删除。

Pod 如果未设置此注解,则隐含的设置值为 0。负值也是可接受的。 如果注解值非法,API 服务器会拒绝对应的 Pod。

此功能特性处于 Beta 阶段,默认被禁用。你可以通过为 kube-apiserver 和 kube-controller-manager 设置特性门控 PodDeletionCost 来启用此功能。

使用场景示例

同一应用的不同 Pod 可能其利用率是不同的。在对应用执行缩容操作时, 可能希望移除利用率较低的 Pod。为了避免频繁更新 Pod,应用应该在执行缩容操作之前更新一次 controller.kubernetes.io/pod-deletion-cost 注解值 (将注解值设置为一个与其 Pod 利用率对应的值)。 如果应用自身控制器缩容操作时(例如 Spark 部署的驱动 Pod),这种机制是可以起作用的。

ReplicaSet 作为水平的 Pod 自动扩缩器目标

ReplicaSet 也可以作为水平的 Pod 扩缩器 (HPA) 的目标。也就是说,ReplicaSet 可以被 HPA 自动扩缩。 以下是 HPA 以我们在前一个示例中创建的副本集为目标的示例。

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: frontend-scaler
spec:
  scaleTargetRef:
    kind: ReplicaSet
    name: frontend
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

将这个列表保存到 hpa-rs.yaml 并提交到 Kubernetes 集群,就能创建它所定义的 HPA,进而就能根据复制的 Pod 的 CPU 利用率对目标 ReplicaSet 进行自动扩缩。

kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml

或者,可以使用 kubectl autoscale 命令完成相同的操作(而且它更简单!)

kubectl autoscale rs frontend --max=10 --min=3 --cpu-percent=50

ReplicaSet 的替代方案

Deployment 是一个可以拥有 ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。 尽管 ReplicaSet 可以独立使用,目前它们的主要用途是提供给 Deployment 作为编排 Pod 创建、删除和更新的一种机制。当使用 Deployment 时,你不必关心如何管理它所创建的 ReplicaSet,Deployment 拥有并管理其 ReplicaSet。 因此,建议你在需要 ReplicaSet 时使用 Deployment。

裸 Pod

与用户直接创建 Pod 的情况不同,ReplicaSet 会替换那些由于某些原因被删除或被终止的 Pod,例如在节点故障或破坏性的节点维护(如内核升级)的情况下。 因为这个原因,我们建议你使用 ReplicaSet,即使应用程序只需要一个 Pod。 想像一下,ReplicaSet 类似于进程监视器,只不过它在多个节点上监视多个 Pod, 而不是在单个节点上监视单个进程。 ReplicaSet 将本地容器重启的任务委托给了节点上的某个代理(例如,Kubelet)去完成。

Job

使用Job 代替 ReplicaSet, 可以用于那些期望自行终止的 Pod。

DaemonSet

对于管理那些提供主机级别功能(如主机监控和主机日志)的容器, 就要用 DaemonSet 而不用 ReplicaSet。 这些 Pod 的寿命与主机寿命有关:这些 Pod 需要先于主机上的其他 Pod 运行, 并且在机器准备重新启动/关闭时安全地终止。

ReplicationController

ReplicaSet 是 ReplicationController 的后继者。二者目的相同且行为类似,只是 ReplicationController 不支持 标签用户指南 中讨论的基于集合的选择算符需求。 因此,相比于 ReplicationController,应优先考虑 ReplicaSet。

接下来

3.5.2.3 - StatefulSet

StatefulSet 是用来管理有状态应用的工作负载 API 对象。

StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。

Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。

如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。 尽管 StatefulSet 中的单个 Pod 仍可能出现故障, 但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。

使用 StatefulSet

StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:

  • 稳定的、唯一的网络标识符。
  • 稳定的、持久的存储。
  • 有序的、优雅的部署和扩缩。
  • 有序的、自动的滚动更新。

在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。 如果应用程序不需要任何稳定的标识符或有序的部署、删除或扩缩, 则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如 Deployment 或者 ReplicaSet 可能更适用于你的无状态应用部署需要。

限制

  • 给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供,或者由管理员预先提供。
  • 删除或者扩缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
  • StatefulSet 当前需要无头服务 来负责 Pod 的网络标识。你需要负责创建此服务。
  • 当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序且体面地终止,可以在删除之前将 StatefulSet 缩容到 0。
  • 在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新,可能进入需要人工干预 才能修复的损坏状态。

组件

下面的示例演示了 StatefulSet 的组件。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # 必须匹配 .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # 默认值是 1
  minReadySeconds: 10 # 默认值是 0
  template:
    metadata:
      labels:
        app: nginx # 必须匹配 .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

上述例子中:

  • 名为 nginx 的 Headless Service 用来控制网络域名。
  • 名为 web 的 StatefulSet 有一个 Spec,它表明将在独立的 3 个 Pod 副本中启动 nginx 容器。
  • volumeClaimTemplates 将通过 PersistentVolumes 驱动提供的 PersistentVolumes 来提供稳定的存储。

StatefulSet 的命名需要遵循 DNS 子域名规范。

Pod 选择算符

你必须设置 StatefulSet 的 .spec.selector 字段,使之匹配其在 .spec.template.metadata.labels 中设置的标签。 未指定匹配的 Pod 选择算符将在创建 StatefulSet 期间导致验证错误。

卷申领模板

你可以设置 .spec.volumeClaimTemplates, 它可以使用 PersistentVolume 制备程序所准备的 PersistentVolumes 来提供稳定的存储。

最短就绪秒数

特性状态: Kubernetes v1.23 [beta]

.spec.minReadySeconds 是一个可选字段, 它指定新创建的 Pod 应该准备好且其任何容器不崩溃的最小秒数,以使其被视为可用。 请注意,此功能是测试版,默认启用。如果你不希望启用此功能, 请通过取消设置 StatefulSetMinReadySeconds 标志来选择退出。 该字段默认为 0(Pod 准备就绪后将被视为可用)。 要了解有关何时认为 Pod 准备就绪的更多信息, 请参阅容器探针

Pod 标识

StatefulSet Pod 具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。 该标识和 Pod 是绑定的,与该 Pod 调度到哪个节点上无关。

有序索引

对于具有 N 个副本的 StatefulSet,该 StatefulSet 中的每个 Pod 将被分配一个从 0 到 N-1 的整数序号,该序号在此 StatefulSet 上是唯一的。

稳定的网络 ID

StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。 组合主机名的格式为$(StatefulSet 名称)-$(序号)。 上例将会创建三个名称分别为 web-0、web-1、web-2 的 Pod。 StatefulSet 可以使用 无头服务 控制它的 Pod 的网络域。管理域的这个服务的格式为: $(服务名称).$(名字空间).svc.cluster.local,其中 cluster.local 是集群域。 一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为: $(pod 名称).$(所属服务的 DNS 域名),其中所属服务由 StatefulSet 的 serviceName 域来设定。

取决于集群域内部 DNS 的配置,有可能无法查询一个刚刚启动的 Pod 的 DNS 命名。 当集群内其他客户端在 Pod 创建完成前发出 Pod 主机名查询时,就会发生这种情况。 负缓存 (在 DNS 中较为常见) 意味着之前失败的查询结果会被记录和重用至少若干秒钟, 即使 Pod 已经正常运行了也是如此。

如果需要在 Pod 被创建之后及时发现它们,可使用以下选项:

  • 直接查询 Kubernetes API(比如,利用 watch 机制)而不是依赖于 DNS 查询
  • 缩短 Kubernetes DNS 驱动的缓存时长(通常这意味着修改 CoreDNS 的 ConfigMap,目前缓存时长为 30 秒)

正如限制中所述,你需要负责创建无头服务 以便为 Pod 提供网络标识。

下面给出一些选择集群域、服务名、StatefulSet 名、及其怎样影响 StatefulSet 的 Pod 上的 DNS 名称的示例:

集群域名服务(名字空间/名字)StatefulSet(名字空间/名字)StatefulSet 域名Pod DNSPod 主机名
cluster.localdefault/nginxdefault/webnginx.default.svc.cluster.localweb-{0..N-1}.nginx.default.svc.cluster.localweb-{0..N-1}
cluster.localfoo/nginxfoo/webnginx.foo.svc.cluster.localweb-{0..N-1}.nginx.foo.svc.cluster.localweb-{0..N-1}
kube.localfoo/nginxfoo/webnginx.foo.svc.kube.localweb-{0..N-1}.nginx.foo.svc.kube.localweb-{0..N-1}

稳定的存储

对于 StatefulSet 中定义的每个 VolumeClaimTemplate,每个 Pod 接收到一个 PersistentVolumeClaim。在上面的 nginx 示例中,每个 Pod 将会得到基于 StorageClass my-storage-class 提供的 1 Gib 的 PersistentVolume。 如果没有声明 StorageClass,就会使用默认的 StorageClass。 当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts 会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。 请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的 PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。

Pod 名称标签

当 StatefulSet 控制器(Controller) 创建 Pod 时, 它会添加一个标签 statefulset.kubernetes.io/pod-name,该标签值设置为 Pod 名称。 这个标签允许你给 StatefulSet 中的特定 Pod 绑定一个 Service。

部署和扩缩保证

  • 对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为 0..N-1
  • 当删除 Pod 时,它们是逆序终止的,顺序为 N-1..0
  • 在将扩缩操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。
  • 在一个 Pod 终止之前,所有的继任者必须完全关闭。

StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds 设置为 0。 这种做法是不安全的,要强烈阻止。更多的解释请参考 强制删除 StatefulSet Pod

在上面的 nginx 示例被创建后,会按照 web-0、web-1、web-2 的顺序部署三个 Pod。 在 web-0 进入 Running 和 Ready 状态前不会部署 web-1。在 web-1 进入 Running 和 Ready 状态前不会部署 web-2。 如果 web-1 已经处于 Running 和 Ready 状态,而 web-2 尚未部署,在此期间发生了 web-0 运行失败,那么 web-2 将不会被部署,要等到 web-0 部署完成并进入 Running 和 Ready 状态后,才会部署 web-2。

如果用户想将示例中的 StatefulSet 缩放为 replicas=1,首先被终止的是 web-2。 在 web-2 没有被完全停止和删除前,web-1 不会被终止。 当 web-2 已被终止和删除、web-1 尚未被终止,如果在此期间发生 web-0 运行失败, 那么就不会终止 web-1,必须等到 web-0 进入 Running 和 Ready 状态后才会终止 web-1。

Pod 管理策略

StatefulSet 允许你放宽其排序保证, 同时通过它的 .spec.podManagementPolicy 域保持其唯一性和身份保证。

OrderedReady Pod 管理

OrderedReady Pod 管理是 StatefulSet 的默认设置。它实现了 上面描述的功能。

并行 Pod 管理

Parallel Pod 管理让 StatefulSet 控制器并行的启动或终止所有的 Pod, 启动或者终止其他 Pod 前,无需等待 Pod 进入 Running 和 ready 或者完全停止状态。 这个选项只会影响扩缩操作的行为,更新则不会被影响。

更新策略

StatefulSet 的 .spec.updateStrategy 字段让 你可以配置和禁用掉自动滚动更新 Pod 的容器、标签、资源请求或限制、以及注解。 有两个允许的值:

OnDelete
当 StatefulSet 的 .spec.updateStrategy.type 设置为 OnDelete 时, 它的控制器将不会自动更新 StatefulSet 中的 Pod。 用户必须手动删除 Pod 以便让控制器创建新的 Pod,以此来对 StatefulSet 的 .spec.template 的变动作出反应。
RollingUpdate
RollingUpdate 更新策略对 StatefulSet 中的 Pod 执行自动的滚动更新。这是默认的更新策略。

滚动更新

当 StatefulSet 的 .spec.updateStrategy.type 被设置为 RollingUpdate 时, StatefulSet 控制器会删除和重建 StatefulSet 中的每个 Pod。 它将按照与 Pod 终止相同的顺序(从最大序号到最小序号)进行,每次更新一个 Pod。

Kubernetes 控制平面会等到被更新的 Pod 进入 Running 和 Ready 状态,然后再更新其前身。 如果你设置了 .spec.minReadySeconds(查看最短就绪秒数), 控制平面在 Pod 就绪后会额外等待一定的时间再执行下一步。

分区滚动更新

通过声明 .spec.updateStrategy.rollingUpdate.partition 的方式,RollingUpdate 更新策略可以实现分区。 如果声明了一个分区,当 StatefulSet 的 .spec.template 被更新时, 所有序号大于等于该分区序号的 Pod 都会被更新。 所有序号小于该分区序号的 Pod 都不会被更新,并且,即使它们被删除也会依据之前的版本进行重建。 如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition 大于它的 .spec.replicas,则对它的 .spec.template 的更新将不会传递到它的 Pod。 在大多数情况下,你不需要使用分区,但如果你希望进行阶段更新、执行金丝雀或执行 分阶段上线,则这些分区会非常有用。

最大不可用 Pod

特性状态: Kubernetes v1.24 [alpha]

你可以通过指定 .spec.updateStrategy.rollingUpdate.maxUnavailable 字段来控制更新期间不可用的 Pod 的最大数量。 该值可以是绝对值(例如,“5”)或者是期望 Pod 个数的百分比(例如,10%)。 绝对值是根据百分比值四舍五入计算的。 该字段不能为 0。默认设置为 1。

该字段适用于 0replicas - 1 范围内的所有 Pod。 如果在 0replicas - 1 范围内存在不可用 Pod,这类 Pod 将被计入 maxUnavailable 值。

强制回滚

在默认 Pod 管理策略(OrderedReady) 下使用 滚动更新,可能进入需要人工干预才能修复的损坏状态。

如果更新后 Pod 模板配置进入无法运行或就绪的状态(例如, 由于错误的二进制文件或应用程序级配置错误),StatefulSet 将停止回滚并等待。

在这种状态下,仅将 Pod 模板还原为正确的配置是不够的。由于 已知问题,StatefulSet 将继续等待损坏状态的 Pod 准备就绪(永远不会发生),然后再尝试将其恢复为正常工作配置。

恢复模板后,还必须删除 StatefulSet 尝试使用错误的配置来运行的 Pod。这样, StatefulSet 才会开始使用被还原的模板来重新创建 Pod。

PersistentVolumeClaim 保留

特性状态: Kubernetes v1.23 [alpha]

在 StatefulSet 的生命周期中,可选字段 .spec.persistentVolumeClaimRetentionPolicy 控制是否删除以及如何删除 PVC。 使用该字段,你必须启用 StatefulSetAutoDeletePVC 特性门控。 启用后,你可以为每个 StatefulSet 配置两个策略:

whenDeleted
配置删除 StatefulSet 时应用的卷保留行为。
whenScaled
配置当 StatefulSet 的副本数减少时应用的卷保留行为;例如,缩小集合时。

对于你可以配置的每个策略,你可以将值设置为 DeleteRetain

Delete
对于受策略影响的每个 Pod,基于 StatefulSet 的 volumeClaimTemplate 字段创建的 PVC 都会被删除。 使用 whenDeleted 策略,所有来自 volumeClaimTemplate 的 PVC 在其 Pod 被删除后都会被删除。 使用 whenScaled 策略,只有与被缩减的 Pod 副本对应的 PVC 在其 Pod 被删除后才会被删除。
Retain(默认)
来自 volumeClaimTemplate 的 PVC 在 Pod 被删除时不受影响。这是此新功能之前的行为。

请记住,这些策略适用于由于 StatefulSet 被删除或被缩小而被删除的 Pod。 例如,如果与 StatefulSet 关联的 Pod 由于节点故障而失败, 并且控制平面创建了替换 Pod,则 StatefulSet 保留现有的 PVC。 现有卷不受影响,集群会将其附加到新 Pod 即将启动的节点上。

策略的默认值为 Retain,与此新功能之前的 StatefulSet 行为相匹配。

这是一个示例策略。

apiVersion: apps/v1
kind: StatefulSet
...
spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain
    whenScaled: Delete
...

StatefulSet 控制器为其 PVC 添加了 属主引用, 这些 PVC 在 Pod 终止后被垃圾回收器删除。 这使 Pod 能够在删除 PVC 之前(以及在删除后备 PV 和卷之前,取决于保留策略)干净地卸载所有卷。 当你设置 whenDeleted 删除策略,对 StatefulSet 实例的属主引用放置在与该 StatefulSet 关联的所有 PVC 上。

whenScaled 策略必须仅在 Pod 缩减时删除 PVC,而不是在 Pod 因其他原因被删除时删除。 执行协调操作时,StatefulSet 控制器将其所需的副本数与集群上实际存在的 Pod 进行比较。 对于 StatefulSet 中的所有 Pod 而言,如果其 ID 大于副本数,则将被废弃并标记为需要删除。 如果 whenScaled 策略是 Delete,则在删除 Pod 之前, 首先将已销毁的 Pod 设置为与 StatefulSet 模板对应的 PVC 的属主。 这会导致 PVC 仅在已废弃的 Pod 终止后被垃圾收集。

这意味着如果控制器崩溃并重新启动,在其属主引用更新到适合策略的 Pod 之前,不会删除任何 Pod。 如果在控制器关闭时强制删除了已废弃的 Pod,则属主引用可能已被设置,也可能未被设置,具体取决于控制器何时崩溃。 更新属主引用可能需要几个协调循环,因此一些已废弃的 Pod 可能已经被设置了属主引用,而其他可能没有。 出于这个原因,我们建议等待控制器恢复,控制器将在终止 Pod 之前验证属主引用。 如果这不可行,则操作员应验证 PVC 上的属主引用,以确保在强制删除 Pod 时删除预期的对象。

副本数

.spec.replicas 是一个可选字段,用于指定所需 Pod 的数量。它的默认值为 1。

如果你手动扩缩已部署的负载,例如通过 kubectl scale statefulset statefulset --replicas=X, 然后根据清单更新 StatefulSet(例如:通过运行 kubectl apply -f statefulset.yaml), 那么应用该清单的操作会覆盖你之前所做的手动扩缩。

如果 HorizontalPodAutoscaler (或任何类似的水平缩放 API)正在管理 StatefulSet 的缩放, 请不要设置 .spec.replicas。 相反,允许 Kubernetes 控制平面自动管理 .spec.replicas 字段。

接下来

3.5.2.4 - DaemonSet

DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。

DaemonSet 的一些典型用法:

  • 在每个节点上运行集群守护进程
  • 在每个节点上运行日志收集守护进程
  • 在每个节点上运行监控守护进程

一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。 一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。

编写 DaemonSet Spec

创建 DaemonSet

你可以在 YAML 文件中描述 DaemonSet。 例如,下面的 daemonset.yaml 文件描述了一个运行 fluentd-elasticsearch Docker 镜像的 DaemonSet:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      # 这些容忍度设置是为了让该守护进程集在控制平面节点上运行
      # 如果你不希望自己的控制平面节点运行 Pod,可以删除它们
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

基于 YAML 文件创建 DaemonSet:

kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml

必需字段

和所有其他 Kubernetes 配置一样,DaemonSet 需要 apiVersionkindmetadata 字段。 有关配置文件的基本信息,参见 部署应用配置容器使用 kubectl 进行对象管理 文档。

DaemonSet 对象的名称必须是一个合法的 DNS 子域名

DaemonSet 也需要一个 .spec 配置段。

Pod 模板

.spec 中唯一必需的字段是 .spec.template

.spec.template 是一个 Pod 模板。 除了它是嵌套的,因而不具有 apiVersionkind 字段之外,它与 Pod 具有相同的 schema。

除了 Pod 必需字段外,在 DaemonSet 中的 Pod 模板必须指定合理的标签(查看 Pod 选择算符)。

在 DaemonSet 中的 Pod 模板必须具有一个值为 AlwaysRestartPolicy。 当该值未指定时,默认是 Always

Pod 选择算符

.spec.selector 字段表示 Pod 选择算符,它与 Job.spec.selector 的作用是相同的。

你必须指定与 .spec.template 的标签匹配的 Pod 选择算符。 此外,一旦创建了 DaemonSet,它的 .spec.selector 就不能修改。 修改 Pod 选择算符可能导致 Pod 意外悬浮,并且这对用户来说是费解的。

spec.selector 是一个对象,如下两个字段组成:

  • matchLabels - 与 ReplicationController.spec.selector 的作用相同。
  • matchExpressions - 允许构建更加复杂的选择器,可以通过指定 key、value 列表以及将 key 和 value 列表关联起来的 operator。

当上述两个字段都指定时,结果会按逻辑与(AND)操作处理。

.spec.selector 必须与 .spec.template.metadata.labels 相匹配。 如果配置中这两个字段不匹配,则会被 API 拒绝。

仅在某些节点上运行 Pod

如果指定了 .spec.template.spec.nodeSelector,DaemonSet 控制器将在能够与 Node 选择算符 匹配的节点上创建 Pod。 类似这种情况,可以指定 .spec.template.spec.affinity,之后 DaemonSet 控制器 将在能够与节点亲和性 匹配的节点上创建 Pod。 如果根本就没有指定,则 DaemonSet Controller 将在所有节点上创建 Pod。

Daemon Pods 是如何被调度的

通过默认调度器调度

特性状态: Kubernetes 1.17 [stable]

DaemonSet 确保所有符合条件的节点都运行该 Pod 的一个副本。 通常,运行 Pod 的节点由 Kubernetes 调度器选择。 不过,DaemonSet Pods 由 DaemonSet 控制器创建和调度。这就带来了以下问题:

  • Pod 行为的不一致性:正常 Pod 在被创建后等待调度时处于 Pending 状态, DaemonSet Pods 创建后不会处于 Pending 状态下。这使用户感到困惑。
  • Pod 抢占 由默认调度器处理。启用抢占后,DaemonSet 控制器将在不考虑 Pod 优先级和抢占 的情况下制定调度决策。

ScheduleDaemonSetPods 允许你使用默认调度器而不是 DaemonSet 控制器来调度 DaemonSets, 方法是将 NodeAffinity 条件而不是 .spec.nodeName 条件添加到 DaemonSet Pods。 默认调度器接下来将 Pod 绑定到目标主机。 如果 DaemonSet Pod 的节点亲和性配置已存在,则被替换 (原始的节点亲和性配置在选择目标主机之前被考虑)。 DaemonSet 控制器仅在创建或修改 DaemonSet Pod 时执行这些操作, 并且不会更改 DaemonSet 的 spec.template

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchFields:
      - key: metadata.name
        operator: In
        values:
        - target-host-name

此外,系统会自动添加 node.kubernetes.io/unschedulable:NoSchedule 容忍度到 DaemonSet Pods。在调度 DaemonSet Pod 时,默认调度器会忽略 unschedulable 节点。

污点和容忍度

尽管 Daemon Pods 遵循污点和容忍度 规则,根据相关特性,控制器会自动将以下容忍度添加到 DaemonSet Pod:

容忍度键名效果版本描述
node.kubernetes.io/not-readyNoExecute1.13+当出现类似网络断开的情况导致节点问题时,DaemonSet Pod 不会被逐出。
node.kubernetes.io/unreachableNoExecute1.13+当出现类似于网络断开的情况导致节点问题时,DaemonSet Pod 不会被逐出。
node.kubernetes.io/disk-pressureNoSchedule1.8+DaemonSet Pod 被默认调度器调度时能够容忍磁盘压力属性。
node.kubernetes.io/memory-pressureNoSchedule1.8+DaemonSet Pod 被默认调度器调度时能够容忍内存压力属性。
node.kubernetes.io/unschedulableNoSchedule1.12+DaemonSet Pod 能够容忍默认调度器所设置的 unschedulable 属性.
node.kubernetes.io/network-unavailableNoSchedule1.12+DaemonSet 在使用宿主网络时,能够容忍默认调度器所设置的 network-unavailable 属性。

与 Daemon Pods 通信

与 DaemonSet 中的 Pod 进行通信的几种可能模式如下:

  • 推送(Push):配置 DaemonSet 中的 Pod,将更新发送到另一个服务,例如统计数据库。 这些服务没有客户端。

  • NodeIP 和已知端口:DaemonSet 中的 Pod 可以使用 hostPort,从而可以通过节点 IP 访问到 Pod。客户端能通过某种方法获取节点 IP 列表,并且基于此也可以获取到相应的端口。

  • DNS:创建具有相同 Pod 选择算符的 无头服务, 通过使用 endpoints 资源或从 DNS 中检索到多个 A 记录来发现 DaemonSet。

  • Service:创建具有相同 Pod 选择算符的服务,并使用该服务随机访问到某个节点上的 守护进程(没有办法访问到特定节点)。

更新 DaemonSet

如果节点的标签被修改,DaemonSet 将立刻向新匹配上的节点添加 Pod, 同时删除不匹配的节点上的 Pod。

你可以修改 DaemonSet 创建的 Pod。不过并非 Pod 的所有字段都可更新。 下次当某节点(即使具有相同的名称)被创建时,DaemonSet 控制器还会使用最初的模板。

你可以删除一个 DaemonSet。如果使用 kubectl 并指定 --cascade=orphan 选项, 则 Pod 将被保留在节点上。接下来如果创建使用相同选择算符的新 DaemonSet, 新的 DaemonSet 会收养已有的 Pod。 如果有 Pod 需要被替换,DaemonSet 会根据其 updateStrategy 来替换。

你可以对 DaemonSet 执行滚动更新操作。

DaemonSet 的替代方案

init 脚本

直接在节点上启动守护进程(例如使用 initupstartdsystemd)的做法当然是可行的。 不过,基于 DaemonSet 来运行这些进程有如下一些好处:

  • 像所运行的其他应用一样,DaemonSet 具备为守护进程提供监控和日志管理的能力。

  • 为守护进程和应用所使用的配置语言和工具(如 Pod 模板、kubectl)是相同的。

  • 在资源受限的容器中运行守护进程能够增加守护进程和应用容器的隔离性。 然而,这一点也可以通过在容器中运行守护进程但却不在 Pod 中运行之来实现。 例如,直接基于 Docker 启动。

裸 Pod

直接创建 Pod并指定其运行在特定的节点上也是可以的。 然而,DaemonSet 能够替换由于任何原因(例如节点失败、例行节点维护、内核升级) 而被删除或终止的 Pod。 由于这个原因,你应该使用 DaemonSet 而不是单独创建 Pod。

静态 Pod

通过在一个指定的、受 kubelet 监视的目录下编写文件来创建 Pod 也是可行的。 这类 Pod 被称为静态 Pod。 不像 DaemonSet,静态 Pod 不受 kubectl 和其它 Kubernetes API 客户端管理。 静态 Pod 不依赖于 API 服务器,这使得它们在启动引导新集群的情况下非常有用。 此外,静态 Pod 在将来可能会被废弃。

Deployments

DaemonSet 与 Deployments 非常类似, 它们都能创建 Pod,并且 Pod 中的进程都不希望被终止(例如,Web 服务器、存储服务器)。

建议为无状态的服务使用 Deployments,比如前端服务。 对这些服务而言,对副本的数量进行扩缩容、平滑升级,比精确控制 Pod 运行在某个主机上要重要得多。 当需要 Pod 副本总是运行在全部或特定主机上,并且当该 DaemonSet 提供了节点级别的功能(允许其他 Pod 在该特定节点上正确运行)时, 应该使用 DaemonSet。

例如,网络插件通常包含一个以 DaemonSet 运行的组件。 这个 DaemonSet 组件确保它所在的节点的集群网络正常工作。

接下来

3.5.2.5 - Job

Job 会创建一个或者多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。 随着 Pod 成功结束,Job 跟踪记录成功完成的 Pod 个数。 当数量达到指定的成功个数阈值时,任务(即 Job)结束。 删除 Job 的操作会清除所创建的全部 Pod。 挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。

一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。 当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job 对象会启动一个新的 Pod。

你也可以使用 Job 以并行的方式运行多个 Pod。

如果你想按某种排期表(Schedule)运行 Job(单个任务或多个并行任务),请参阅 CronJob

运行示例 Job

下面是一个 Job 配置示例。它负责计算 π 到小数点后 2000 位,并将结果打印出来。 此计算大约需要 10 秒钟完成。

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

你可以使用下面的命令来运行此示例:

kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml

输出类似于:

job.batch/pi created

使用 kubectl 来检查 Job 的状态:

kubectl describe jobs/pi

输出类似于:

Name:           pi
Namespace:      default
Selector:       controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels:         controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
                job-name=pi
Annotations:    kubectl.kubernetes.io/last-applied-configuration:
                  {"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pi","namespace":"default"},"spec":{"backoffLimit":4,"template":...
Parallelism:    1
Completions:    1
Start Time:     Mon, 02 Dec 2019 15:20:11 +0200
Completed At:   Mon, 02 Dec 2019 15:21:16 +0200
Duration:       65s
Pods Statuses:  0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
           job-name=pi
  Containers:
   pi:
    Image:      perl:5.34.0
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  14m   job-controller  Created pod: pi-5rwd7

要查看 Job 对应的已完成的 Pod,可以执行 kubectl get pods

要以机器可读的方式列举隶属于某 Job 的全部 Pod,你可以使用类似下面这条命令:

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods

输出类似于:

pi-5rwd7

这里,选择算符与 Job 的选择算符相同。--output=jsonpath 选项给出了一个表达式, 用来从返回的列表中提取每个 Pod 的 name 字段。

查看其中一个 Pod 的标准输出:

kubectl logs $pods

输出类似于:

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

编写 Job 规约

与 Kubernetes 中其他资源的配置类似,Job 也需要 apiVersionkindmetadata 字段。 Job 的名字必须是合法的 DNS 子域名

Job 配置还需要一个 .spec

Pod 模板

Job 的 .spec 中只有 .spec.template 是必需的字段。

字段 .spec.template 的值是一个 Pod 模板。 其定义规范与 Pod 完全相同,只是其中不再需要 apiVersionkind 字段。

除了作为 Pod 所必需的字段之外,Job 中的 Pod 模板必须设置合适的标签 (参见 Pod 选择算符)和合适的重启策略。

Job 中 Pod 的 RestartPolicy 只能设置为 NeverOnFailure 之一。

Pod 选择算符

字段 .spec.selector 是可选的。在绝大多数场合,你都不需要为其赋值。 参阅设置自己的 Pod 选择算符.

Job 的并行执行

适合以 Job 形式来运行的任务主要有三种:

  1. 非并行 Job:
    • 通常只启动一个 Pod,除非该 Pod 失败。
    • 当 Pod 成功终止时,立即视 Job 为完成状态。
  2. 具有确定完成计数的并行 Job:
    • .spec.completions 字段设置为非 0 的正数值。
    • Job 用来代表整个任务,当成功的 Pod 个数达到 .spec.completions 时,Job 被视为完成。
    • 当使用 .spec.completionMode="Indexed" 时,每个 Pod 都会获得一个不同的 索引值,介于 0 和 .spec.completions-1 之间。
  3. 工作队列的并行 Job:
    • 不设置 spec.completions,默认值为 .spec.parallelism
    • 多个 Pod 之间必须相互协调,或者借助外部服务确定每个 Pod 要处理哪个工作条目。 例如,任一 Pod 都可以从工作队列中取走最多 N 个工作条目。
    • 每个 Pod 都可以独立确定是否其它 Pod 都已完成,进而确定 Job 是否完成。
    • 当 Job 中任何 Pod 成功终止,不再创建新 Pod。
    • 一旦至少 1 个 Pod 成功完成,并且所有 Pod 都已终止,即可宣告 Job 成功完成。
    • 一旦任何 Pod 成功退出,任何其它 Pod 都不应再对此任务执行任何操作或生成任何输出。 所有 Pod 都应启动退出过程。

对于非并行的 Job,你可以不设置 spec.completionsspec.parallelism。 这两个属性都不设置时,均取默认值 1。

对于确定完成计数类型的 Job,你应该设置 .spec.completions 为所需要的完成个数。 你可以设置 .spec.parallelism,也可以不设置。其默认值为 1。

对于一个工作队列 Job,你不可以设置 .spec.completions,但要将.spec.parallelism 设置为一个非负整数。

关于如何利用不同类型的 Job 的更多信息,请参见 Job 模式一节。

控制并行性

并行性请求(.spec.parallelism)可以设置为任何非负整数。 如果未设置,则默认为 1。 如果设置为 0,则 Job 相当于启动之后便被暂停,直到此值被增加。

实际并行性(在任意时刻运行状态的 Pod 个数)可能比并行性请求略大或略小, 原因如下:

  • 对于确定完成计数 Job,实际上并行执行的 Pod 个数不会超出剩余的完成数。 如果 .spec.parallelism 值较高,会被忽略。
  • 对于工作队列 Job,有任何 Job 成功结束之后,不会有新的 Pod 启动。 不过,剩下的 Pod 允许执行完毕。
  • 如果 Job 控制器 没有来得及作出响应,或者
  • 如果 Job 控制器因为任何原因(例如,缺少 ResourceQuota 或者没有权限)无法创建 Pod。 Pod 个数可能比请求的数目小。
  • Job 控制器可能会因为之前同一 Job 中 Pod 失效次数过多而压制新 Pod 的创建。
  • 当 Pod 处于体面终止进程中,需要一定时间才能停止。

完成模式

特性状态: Kubernetes v1.24 [stable]

带有确定完成计数的 Job,即 .spec.completions 不为 null 的 Job, 都可以在其 .spec.completionMode 中设置完成模式:

  • NonIndexed(默认值):当成功完成的 Pod 个数达到 .spec.completions 所 设值时认为 Job 已经完成。换言之,每个 Job 完成事件都是独立无关且同质的。 要注意的是,当 .spec.completions 取值为 null 时,Job 被隐式处理为 NonIndexed

  • Indexed:Job 的 Pod 会获得对应的完成索引,取值为 0 到 .spec.completions-1。 该索引可以通过三种方式获取:

    • Pod 注解 batch.kubernetes.io/job-completion-index
    • 作为 Pod 主机名的一部分,遵循模式 $(job-name)-$(index)。 当你同时使用带索引的 Job(Indexed Job)与 服务(Service), Job 中的 Pod 可以通过 DNS 使用确切的主机名互相寻址。
    • 对于容器化的任务,在环境变量 JOB_COMPLETION_INDEX 中。

    当每个索引都对应一个完成完成的 Pod 时,Job 被认为是已完成的。 关于如何使用这种模式的更多信息,可参阅 用带索引的 Job 执行基于静态任务分配的并行处理。 需要注意的是,对同一索引值可能被启动的 Pod 不止一个,尽管这种情况很少发生。 这时,只有一个会被记入完成计数中。

处理 Pod 和容器失效

Pod 中的容器可能因为多种不同原因失效,例如因为其中的进程退出时返回值非零, 或者容器因为超出内存约束而被杀死等等。 如果发生这类事件,并且 .spec.template.spec.restartPolicy = "OnFailure", Pod 则继续留在当前节点,但容器会被重新运行。 因此,你的程序需要能够处理在本地被重启的情况,或者要设置 .spec.template.spec.restartPolicy = "Never"。 关于 restartPolicy 的更多信息,可参阅 Pod 生命周期

整个 Pod 也可能会失败,且原因各不相同。 例如,当 Pod 启动时,节点失效(被升级、被重启、被删除等)或者其中的容器失败而 .spec.template.spec.restartPolicy = "Never"。 当 Pod 失败时,Job 控制器会启动一个新的 Pod。 这意味着,你的应用需要处理在一个新 Pod 中被重启的情况。 尤其是应用需要处理之前运行所产生的临时文件、锁、不完整的输出等问题。

注意,即使你将 .spec.parallelism 设置为 1,且将 .spec.completions 设置为 1,并且 .spec.template.spec.restartPolicy 设置为 "Never",同一程序仍然有可能被启动两次。

如果你确实将 .spec.parallelism.spec.completions 都设置为比 1 大的值, 那就有可能同时出现多个 Pod 运行的情况。 为此,你的 Pod 也必须能够处理并发性问题。

Pod 回退失效策略

在有些情形下,你可能希望 Job 在经历若干次重试之后直接进入失败状态, 因为这很可能意味着遇到了配置错误。 为了实现这点,可以将 .spec.backoffLimit 设置为视 Job 为失败之前的重试次数。 失效回退的限制值默认为 6。 与 Job 相关的失效的 Pod 会被 Job 控制器重建,回退重试时间将会按指数增长 (从 10 秒、20 秒到 40 秒)最多至 6 分钟。

计算重试次数有以下两种方法:

  • 计算 .status.phase = "Failed" 的 Pod 数量。
  • 当 Pod 的 restartPolicy = "OnFailure" 时,针对 .status.phase 等于 PendingRunning 的 Pod,计算其中所有容器的重试次数。

如果两种方式其中一个的值达到 .spec.backoffLimit,则 Job 被判定为失败。

JobTrackingWithFinalizers 特性被禁用时, 失败的 Pod 数目仅基于 API 中仍然存在的 Pod。

Job 终止与清理

Job 完成时不会再创建新的 Pod,不过已有的 Pod 通常也不会被删除。 保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告或者其它诊断性输出。 Job 完成时 Job 对象也一样被保留下来,这样你就可以查看它的状态。 在查看了 Job 状态之后删除老的 Job 的操作留给了用户自己。 你可以使用 kubectl 来删除 Job(例如,kubectl delete jobs/pi 或者 kubectl delete -f ./job.yaml)。 当使用 kubectl 来删除 Job 时,该 Job 所创建的 Pod 也会被删除。

默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never) 或者某个容器出错退出(restartPolicy=OnFailure)。 这时,Job 基于前述的 spec.backoffLimit 来决定是否以及如何重试。 一旦重试次数到达 .spec.backoffLimit 所设的上限,Job 会被标记为失败, 其中运行的 Pod 都会被终止。

终止 Job 的另一种方式是设置一个活跃期限。 你可以为 Job 的 .spec.activeDeadlineSeconds 设置一个秒数值。 该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。 一旦 Job 运行时间达到 activeDeadlineSeconds 秒,其所有运行中的 Pod 都会被终止, 并且 Job 的状态更新为 type: Failedreason: DeadlineExceeded

注意 Job 的 .spec.activeDeadlineSeconds 优先级高于其 .spec.backoffLimit 设置。 因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达 activeDeadlineSeconds 所设的时限即不再部署额外的 Pod, 即使其重试次数还未达到 backoffLimit 所设的限制。

例如:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-timeout
spec:
  backoffLimit: 5
  activeDeadlineSeconds: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

注意 Job 规约和 Job 中的 Pod 模板规约 都有 activeDeadlineSeconds 字段。 请确保你在合适的层次设置正确的字段。

还要注意的是,restartPolicy 对应的是 Pod,而不是 Job 本身: 一旦 Job 状态变为 type: Failed,就不会再发生 Job 重启的动作。 换言之,由 .spec.activeDeadlineSeconds.spec.backoffLimit 所触发的 Job 终结机制都会导致 Job 永久性的失败,而这类状态都需要手工干预才能解决。

自动清理完成的 Job

完成的 Job 通常不需要留存在系统中。在系统中一直保留它们会给 API 服务器带来额外的压力。 如果 Job 由某种更高级别的控制器来管理,例如 CronJob, 则 Job 可以被 CronJob 基于特定的根据容量裁定的清理策略清理掉。

已完成 Job 的 TTL 机制

特性状态: Kubernetes v1.23 [stable]

自动清理已完成 Job (状态为 CompleteFailed)的另一种方式是使用由 TTL 控制器所提供的 TTL 机制。 通过设置 Job 的 .spec.ttlSecondsAfterFinished 字段,可以让该控制器清理掉已结束的资源。

TTL 控制器清理 Job 时,会级联式地删除 Job 对象。 换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。 注意,当 Job 被删除时,系统会考虑其生命周期保障,例如其 Finalizers。

例如:

apiVersion: batch/v1
kind: Job
metadata:
  name: pi-with-ttl
spec:
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never

Job pi-with-ttl 在结束 100 秒之后,可以成为被自动删除的对象。

如果该字段设置为 0,Job 在结束之后立即成为可被自动删除的对象。 如果该字段没有设置,Job 不会在结束之后被 TTL 控制器自动清除。

Job 模式

Job 对象可以用来支持多个 Pod 的可靠的并发执行。 Job 对象不是设计用来支持相互通信的并行进程的,后者一般在科学计算中应用较多。 Job 的确能够支持对一组相互独立而又有所关联的工作条目的并行处理。 这类工作条目可能是要发送的电子邮件、要渲染的视频帧、要编解码的文件、NoSQL 数据库中要扫描的主键范围等等。

在一个复杂系统中,可能存在多个不同的工作条目集合。 这里我们仅考虑用户希望一起管理的工作条目集合之一:批处理作业

并行计算的模式有好多种,每种都有自己的强项和弱点。这里要权衡的因素有:

  • 每个工作条目对应一个 Job 或者所有工作条目对应同一 Job 对象。 后者更适合处理大量工作条目的场景; 前者会给用户带来一些额外的负担,而且需要系统管理大量的 Job 对象。
  • 创建与工作条目相等的 Pod 或者令每个 Pod 可以处理多个工作条目。 前者通常不需要对现有代码和容器做较大改