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

返回本页常规视图.

管理集群

了解管理集群的常见任务。

1 - 用 kubeadm 进行管理

1.1 - 使用 kubeadm 进行证书管理

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

kubeadm 生成的客户端证书在 1 年后到期。 本页说明如何使用 kubeadm 管理证书续订,同时也涵盖其他与 kubeadm 证书管理相关的说明。

准备开始

你应该熟悉 Kubernetes 中的 PKI 证书和要求

使用自定义的证书

默认情况下,kubeadm 会生成运行一个集群所需的全部证书。 你可以通过提供你自己的证书来改变这个行为策略。

如果要这样做,你必须将证书文件放置在通过 --cert-dir 命令行参数或者 kubeadm 配置中的 certificatesDir 配置项指明的目录中。默认的值是 /etc/kubernetes/pki

如果在运行 kubeadm init 之前存在给定的证书和私钥对,kubeadm 将不会重写它们。 例如,这意味着你可以将现有的 CA 复制到 /etc/kubernetes/pki/ca.crt/etc/kubernetes/pki/ca.key 中,而 kubeadm 将使用此 CA 对其余证书进行签名。

外部 CA 模式

只提供了 ca.crt 文件但是不提供 ca.key 文件也是可以的 (这只对 CA 根证书可用,其它证书不可用)。 如果所有的其它证书和 kubeconfig 文件已就绪,kubeadm 检测到满足以上条件就会激活 "外部 CA" 模式。kubeadm 将会在没有 CA 密钥文件的情况下继续执行。

否则,kubeadm 将独立运行 controller-manager,附加一个 --controllers=csrsigner 的参数,并且指明 CA 证书和密钥。

PKI 证书和要求包括集群使用外部 CA 的设置指南。

检查证书是否过期

你可以使用 check-expiration 子命令来检查证书何时过期

kubeadm certs check-expiration

输出类似于以下内容:

CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Dec 30, 2020 23:36 UTC   364d                                    no
apiserver                  Dec 30, 2020 23:36 UTC   364d            ca                      no
apiserver-etcd-client      Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
apiserver-kubelet-client   Dec 30, 2020 23:36 UTC   364d            ca                      no
controller-manager.conf    Dec 30, 2020 23:36 UTC   364d                                    no
etcd-healthcheck-client    Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
etcd-peer                  Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
etcd-server                Dec 30, 2020 23:36 UTC   364d            etcd-ca                 no
front-proxy-client         Dec 30, 2020 23:36 UTC   364d            front-proxy-ca          no
scheduler.conf             Dec 30, 2020 23:36 UTC   364d                                    no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Dec 28, 2029 23:36 UTC   9y              no
etcd-ca                 Dec 28, 2029 23:36 UTC   9y              no
front-proxy-ca          Dec 28, 2029 23:36 UTC   9y              no

该命令显示 /etc/kubernetes/pki 文件夹中的客户端证书以及 kubeadm(admin.confcontroller-manager.confscheduler.conf) 使用的 kubeconfig 文件中嵌入的客户端证书的到期时间/剩余时间。

另外,kubeadm 会通知用户证书是否由外部管理; 在这种情况下,用户应该小心的手动/使用其他工具来管理证书更新。

自动更新证书

kubeadm 会在控制面升级的时候更新所有证书。

这个功能旨在解决最简单的用例;如果你对此类证书的更新没有特殊要求, 并且定期执行 Kubernetes 版本升级(每次升级之间的间隔时间少于 1 年), 则 kubeadm 将确保你的集群保持最新状态并保持合理的安全性。

如果你对证书更新有更复杂的需求,则可通过将 --certificate-renewal=false 传递给 kubeadm upgrade apply 或者 kubeadm upgrade node,从而选择不采用默认行为。

手动更新证书

你能随时通过 kubeadm certs renew 命令手动更新你的证书,只需带上合适的命令行选项。

此命令用 CA(或者 front-proxy-CA )证书和存储在 /etc/kubernetes/pki 中的密钥执行更新。

执行完此命令之后你需要重启控制面 Pod。因为动态证书重载目前还不被所有组件和证书支持,所有这项操作是必须的。 静态 Pod 是被本地 kubelet 而不是 API 服务器管理,所以 kubectl 不能用来删除或重启他们。 要重启静态 Pod 你可以临时将清单文件从 /etc/kubernetes/manifests/ 移除并等待 20 秒 (参考 KubeletConfiguration 结构中的 fileCheckFrequency 值)。如果 Pod 不在清单目录里,kubelet 将会终止它。 在另一个 fileCheckFrequency 周期之后你可以将文件移回去,kubelet 可以完成 Pod 的重建,而组件的证书更新操作也得以完成。

kubeadm certs renew 可以更新任何特定的证书,或者使用子命令 all 更新所有的证书,如下所示:

kubeadm certs renew all

用 Kubernetes 证书 API 更新证书

本节提供有关如何使用 Kubernetes 证书 API 执行手动证书更新的更多详细信息。

设置一个签名者(Signer)

Kubernetes 证书颁发机构不是开箱即用。你可以配置外部签名者,例如 cert-manager, 也可以使用内置签名者。

内置签名者是 kube-controller-manager 的一部分。

要激活内置签名者,请传递 --cluster-signing-cert-file--cluster-signing-key-file 参数。

如果你正在创建一个新的集群,你可以使用 kubeadm 的配置文件

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
controllerManager:
  extraArgs:
    cluster-signing-cert-file: /etc/kubernetes/pki/ca.crt
    cluster-signing-key-file: /etc/kubernetes/pki/ca.key

创建证书签名请求 (CSR)

有关使用 Kubernetes API 创建 CSR 的信息, 请参见创建 CertificateSigningRequest

通过外部 CA 更新证书

本节提供有关如何使用外部 CA 执行手动更新证书的更多详细信息。

为了更好的与外部 CA 集成,kubeadm 还可以生成证书签名请求(CSR)。 CSR 表示向 CA 请求客户的签名证书。 在 kubeadm 术语中,通常由磁盘 CA 签名的任何证书都可以作为 CSR 生成。但是,CA 不能作为 CSR 生成。

创建证书签名请求 (CSR)

你可以通过 kubeadm certs renew --csr-only 命令创建证书签名请求。

CSR 和随附的私钥都在输出中给出。 你可以传入一个带有 --csr-dir 的目录,将 CSR 输出到指定位置。 如果未指定 --csr-dir,则使用默认证书目录(/etc/kubernetes/pki)。

证书可以通过 kubeadm certs renew --csr-only 来续订。 和 kubeadm init 一样,可以使用 --csr-dir 标志指定一个输出目录。

CSR 中包含一个证书的名字,域和 IP,但是未指定用法。 颁发证书时,CA 有责任指定正确的证书用法

使用首选方法对证书签名后,必须将证书和私钥复制到 PKI 目录(默认为 /etc/kubernetes/pki)。

证书机构(CA)轮换

kubeadm 并不直接支持对 CA 证书的轮换或者替换。

关于手动轮换或者置换 CA 的更多信息, 可参阅手动轮换 CA 证书

启用已签名的 kubelet 服务证书

默认情况下,kubeadm 所部署的 kubelet 服务证书是自签名(Self-Signed)。 这意味着从 metrics-server 这类外部服务发起向 kubelet 的链接时无法使用 TLS 来完成保护。

要在新的 kubeadm 集群中配置 kubelet 以使用被正确签名的服务证书, 你必须向 kubeadm init 传递如下最小配置数据:

apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
serverTLSBootstrap: true

如果你已经创建了集群,你必须通过执行下面的操作来完成适配:

  • 找到 kube-system 名字空间中名为 kubelet-config-1.30 的 ConfigMap 并编辑之。 在该 ConfigMap 中,kubelet 键下面有一个 KubeletConfiguration 文档作为其取值。编辑该 KubeletConfiguration 文档以设置 serverTLSBootstrap: true
  • 在每个节点上,在 /var/lib/kubelet/config.yaml 文件中添加 serverTLSBootstrap: true 字段,并使用 systemctl restart kubelet 来重启 kubelet。

字段 serverTLSBootstrap 将允许启动引导 kubelet 的服务证书,方式是从 certificates.k8s.io API 处读取。这种方式的一种局限在于这些证书的 CSR(证书签名请求)不能被 kube-controller-manager 中默认的签名组件 kubernetes.io/kubelet-serving 批准。需要用户或者第三方控制器来执行此操作。

可以使用下面的命令来查看 CSR:

kubectl get csr
NAME        AGE     SIGNERNAME                        REQUESTOR                      CONDITION
csr-9wvgt   112s    kubernetes.io/kubelet-serving     system:node:worker-1           Pending
csr-lz97v   1m58s   kubernetes.io/kubelet-serving     system:node:control-plane-1    Pending

你可以执行下面的操作来批准这些请求:

kubectl certificate approve <CSR-名称>

默认情况下,这些服务证书会在一年后过期。 kubeadm 将 KubeletConfigurationrotateCertificates 字段设置为 true;这意味着证书快要过期时,会生成一组针对服务证书的新的 CSR,而这些 CSR 也要被批准才能完成证书轮换。要进一步了解这里的细节, 可参阅证书轮换文档。

如果你在寻找一种能够自动批准这些 CSR 的解决方案,建议你与你的云提供商 联系,询问他们是否有 CSR 签名组件,用来以带外(out-of-band)的方式检查 节点的标识符。

也可以使用第三方定制的控制器:

除非既能够验证 CSR 中的 CommonName,也能检查请求的 IP 和域名, 这类控制器还算不得安全的机制。 只有完成彻底的检查,才有可能避免有恶意的、能够访问 kubelet 客户端证书的第三方为任何 IP 或域名请求服务证书。

为其他用户生成 kubeconfig 文件

在集群创建过程中,kubeadm 对 admin.conf 中的证书进行签名时,将其配置为 Subject: O = system:masters, CN = kubernetes-adminsystem:masters 是一个例外的超级用户组,可以绕过鉴权层(例如 RBAC)。 强烈建议不要将 admin.conf 文件与任何人共享。

你要使用 kubeadm kubeconfig user 命令为其他用户生成 kubeconfig 文件,这个命令支持命令行参数和 kubeadm 配置结构。 以上命令会将 kubeconfig 打印到终端上,也可以使用 kubeadm kubeconfig user ... > somefile.conf 输出到一个文件中。

如下 kubeadm 可以在 --config 后加的配置文件示例:

# example.yaml
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
# kubernetes 将作为 kubeconfig 中集群名称
clusterName: "kubernetes"
# some-dns-address:6443 将作为集群 kubeconfig 文件中服务地址(IP 或者 DNS 名称)
controlPlaneEndpoint: "some-dns-address:6443"
# 从本地挂载集群的 CA 秘钥和 CA 证书
certificatesDir: "/etc/kubernetes/pki"

确保这些设置与所需的目标集群设置相匹配。可以使用以下命令查看现有集群的设置:

kubectl get cm kubeadm-config -n kube-system -o=jsonpath="{.data.ClusterConfiguration}"

以下示例将为在 appdevs 组的 johndoe 用户创建一个有效期为 24 小时的 kubeconfig 文件:

kubeadm kubeconfig user --config example.yaml --org appdevs --client-name johndoe --validity-period 24h

以下示例将为管理员创建一个有效期有一周的 kubeconfig 文件:

kubeadm kubeconfig user --config example.yaml --client-name admin --validity-period 168h

1.2 - 配置 cgroup 驱动

本页阐述如何配置 kubelet 的 cgroup 驱动以匹配 kubeadm 集群中的容器运行时的 cgroup 驱动。

准备开始

你应该熟悉 Kubernetes 的容器运行时需求

配置容器运行时 cgroup 驱动

容器运行时页面提到, 由于 kubeadm 把 kubelet 视为一个 系统服务来管理, 所以对基于 kubeadm 的安装, 我们推荐使用 systemd 驱动, 不推荐 kubelet 默认cgroupfs 驱动。

此页还详述了如何安装若干不同的容器运行时,并将 systemd 设为其默认驱动。

配置 kubelet 的 cgroup 驱动

kubeadm 支持在执行 kubeadm init 时,传递一个 KubeletConfiguration 结构体。 KubeletConfiguration 包含 cgroupDriver 字段,可用于控制 kubelet 的 cgroup 驱动。

这是一个最小化的示例,其中显式的配置了此字段:

# kubeadm-config.yaml
kind: ClusterConfiguration
apiVersion: kubeadm.k8s.io/v1beta3
kubernetesVersion: v1.21.0
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd

这样一个配置文件就可以传递给 kubeadm 命令了:

kubeadm init --config kubeadm-config.yaml

使用 cgroupfs 驱动

如仍需使用 cgroupfs 且要防止 kubeadm upgrade 修改现有系统中 KubeletConfiguration 的 cgroup 驱动,你必须显式声明它的值。 此方法应对的场景为:在将来某个版本的 kubeadm 中,你不想使用默认的 systemd 驱动。

参阅以下章节“修改 kubelet 的 ConfigMap ”,了解显式设置该值的方法。

如果你希望配置容器运行时来使用 cgroupfs 驱动, 则必须参考所选容器运行时的文档。

迁移到 systemd 驱动

要将现有 kubeadm 集群的 cgroup 驱动从 cgroupfs 就地升级为 systemd, 需要执行一个与 kubelet 升级类似的过程。 该过程必须包含下面两个步骤:

修改 kubelet 的 ConfigMap

  • 运行 kubectl edit cm kubelet-config -n kube-system

  • 修改现有 cgroupDriver 的值,或者新增如下式样的字段:

    cgroupDriver: systemd
    

    该字段必须出现在 ConfigMap 的 kubelet: 小节下。

更新所有节点的 cgroup 驱动

对于集群中的每一个节点:

  • 执行命令 kubectl drain <node-name> --ignore-daemonsets,以 腾空节点
  • 执行命令 systemctl stop kubelet,以停止 kubelet
  • 停止容器运行时
  • 修改容器运行时 cgroup 驱动为 systemd
  • 在文件 /var/lib/kubelet/config.yaml 中添加设置 cgroupDriver: systemd
  • 启动容器运行时
  • 执行命令 systemctl start kubelet,以启动 kubelet
  • 执行命令 kubectl uncordon <node-name>,以 取消节点隔离

在节点上依次执行上述步骤,确保工作负载有充足的时间被调度到其他节点。

流程完成后,确认所有节点和工作负载均健康如常。

1.3 - 重新配置 kubeadm 集群

kubeadm 不支持自动重新配置部署在托管节点上的组件的方式。 一种自动化的方法是使用自定义的 operator

要修改组件配置,你必须手动编辑磁盘上关联的集群对象和文件。 本指南展示了实现 kubeadm 集群重新配置所需执行的正确步骤顺序。

准备开始

  • 你需要一个使用 kubeadm 部署的集群
  • 拥有管理员凭据(/etc/kubernetes/admin.conf) 和从安装了 kubectl 的主机到集群中正在运行的 kube-apiserver 的网络连接
  • 在所有主机上安装文本编辑器

重新配置集群

kubeadm 在 ConfigMap 和其他对象中写入了一组集群范围的组件配置选项。 这些对象必须手动编辑,可以使用命令 kubectl edit

kubectl edit 命令将打开一个文本编辑器,你可以在其中直接编辑和保存对象。 你可以使用环境变量 KUBECONFIGKUBE_EDITOR 来指定 kubectl 使用的 kubeconfig 文件和首选文本编辑器的位置。

例如:

KUBECONFIG=/etc/kubernetes/admin.conf KUBE_EDITOR=nano kubectl edit <parameters>

应用集群配置更改

更新 ClusterConfiguration

在集群创建和升级期间,kubeadm 将其 ClusterConfiguration 写入 kube-system 命名空间中名为 kubeadm-config 的 ConfigMap。

要更改 ClusterConfiguration 中的特定选项,你可以使用以下命令编辑 ConfigMap:

kubectl edit cm -n kube-system kubeadm-config

配置位于 data.ClusterConfiguration 键下。

在控制平面节点上反映 ClusterConfiguration 更改

kubeadm 将控制平面组件作为位于 /etc/kubernetes/manifests 目录中的静态 Pod 清单进行管理。 对 apiServercontrollerManagerscheduleretcd键下的 ClusterConfiguration 的任何更改都必须反映在控制平面节点上清单目录中的关联文件中。

此类更改可能包括:

  • extraArgs - 需要更新传递给组件容器的标志列表
  • extraMounts - 需要更新组件容器的卷挂载
  • *SANs - 需要使用更新的主题备用名称编写新证书

在继续进行这些更改之前,请确保你已备份目录 /etc/kubernetes/

要编写新证书,你可以使用:

kubeadm init phase certs <component-name> --config <config-file>

要在 /etc/kubernetes/manifests 中编写新的清单文件,你可以使用以下命令:

# Kubernetes 控制平面组件
kubeadm init phase control-plane <component-name> --config <config-file>
# 本地 etcd
kubeadm init phase etcd local --config <config-file>

<config-file> 内容必须与更新后的 ClusterConfiguration 匹配。 <component-name> 值必须是一个控制平面组件(apiservercontroller-managerscheduler)的名称。

应用 kubelet 配置更改

更新 KubeletConfiguration

在集群创建和升级期间,kubeadm 将其 KubeletConfiguration 写入 kube-system 命名空间中名为 kubelet-config 的 ConfigMap。 你可以使用以下命令编辑 ConfigMap:

kubectl edit cm -n kube-system kubelet-config

配置位于 data.kubelet 键下。

反映 kubelet 的更改

要反映 kubeadm 节点上的更改,你必须执行以下操作:

  • 登录到 kubeadm 节点
  • 运行 kubeadm upgrade node phase kubelet-config 下载最新的 kubelet-config ConfigMap 内容到本地文件 /var/lib/kubelet/config.yaml
  • 编辑文件 /var/lib/kubelet/kubeadm-flags.env 以使用标志来应用额外的配置
  • 使用 systemctl restart kubelet 重启 kubelet 服务

应用 kube-proxy 配置更改

更新 KubeProxyConfiguration

在集群创建和升级期间,kubeadm 将其写入 KubeProxyConfiguration 在名为 kube-proxykube-system 命名空间中的 ConfigMap 中。

此 ConfigMap 由 kube-system 命名空间中的 kube-proxy DaemonSet 使用。

要更改 KubeProxyConfiguration 中的特定选项,你可以使用以下命令编辑 ConfigMap:

kubectl edit cm -n kube-system kube-proxy

配置位于 data.config.conf 键下。

反映 kube-proxy 的更改

更新 kube-proxy ConfigMap 后,你可以重新启动所有 kube-proxy Pod:

获取 Pod 名称:

kubectl get po -n kube-system | grep kube-proxy

使用以下命令删除 Pod:

kubectl delete po -n kube-system <pod-name>

将创建使用更新的 ConfigMap 的新 Pod。

应用 CoreDNS 配置更改

更新 CoreDNS 的 Deployment 和 Service

kubeadm 将 CoreDNS 部署为名为 coredns 的 Deployment,并使用 Service kube-dns, 两者都在 kube-system 命名空间中。

要更新任何 CoreDNS 设置,你可以编辑 Deployment 和 Service:

kubectl edit deployment -n kube-system coredns
kubectl edit service -n kube-system kube-dns

反映 CoreDNS 的更改

应用 CoreDNS 更改后,你可以删除 CoreDNS Pod。

获取 Pod 名称:

kubectl get po -n kube-system | grep coredns

使用以下命令删除 Pod:

kubectl delete po -n kube-system <pod-name>

将创建具有更新的 CoreDNS 配置的新 Pod。

持久化重新配置

在受管节点上执行 kubeadm upgrade 期间,kubeadm 可能会覆盖在创建集群(重新配置)后应用的配置。

持久化 Node 对象重新配置

kubeadm 在特定 Kubernetes 节点的 Node 对象上写入标签、污点、CRI 套接字和其他信息。要更改此 Node 对象的任何内容,你可以使用:

kubectl edit no <node-name>

kubeadm upgrade 期间,此类节点的内容可能会被覆盖。 如果你想在升级后保留对 Node 对象的修改,你可以准备一个 kubectl patch 并将其应用到 Node 对象:

kubectl patch no <node-name> --patch-file <patch-file>

持久化控制平面组件重新配置

控制平面配置的主要来源是存储在集群中的 ClusterConfiguration 对象。 要扩展静态 Pod 清单配置,可以使用 patches

这些补丁文件必须作为文件保留在控制平面节点上,以确保它们可以被 kubeadm upgrade ... --patches <directory> 使用。

如果对 ClusterConfiguration 和磁盘上的静态 Pod 清单进行了重新配置,则必须相应地更新节点特定补丁集。

持久化 kubelet 重新配置

对存储在 /var/lib/kubelet/config.yaml 中的 KubeletConfiguration 所做的任何更改都将在 kubeadm upgrade 时因为下载集群范围内的 kubelet-config ConfigMap 的内容而被覆盖。 要持久保存 kubelet 节点特定的配置,文件 /var/lib/kubelet/config.yaml 必须在升级后手动更新,或者文件 /var/lib/kubelet/kubeadm-flags.env 可以包含标志。 kubelet 标志会覆盖相关的 KubeletConfiguration 选项,但请注意,有些标志已被弃用。

更改 /var/lib/kubelet/config.yaml/var/lib/kubelet/kubeadm-flags.env 后需要重启 kubelet。

接下来

1.4 - 升级 kubeadm 集群

本页介绍如何将 kubeadm 创建的 Kubernetes 集群从 1.29.x 版本 升级到 1.30.x 版本以及从 1.30.x 升级到 1.30.y(其中 y > x)。略过次版本号的升级是 不被支持的。更多详情请访问版本偏差策略

要查看 kubeadm 创建的有关旧版本集群升级的信息,请参考以下页面:

升级工作的基本流程如下:

  1. 升级主控制平面节点
  2. 升级其他控制平面节点
  3. 升级工作节点

准备开始

  • 务必仔细认真阅读发行说明
  • 集群应使用静态的控制平面和 etcd Pod 或者外部 etcd。
  • 务必备份所有重要组件,例如存储在数据库中应用层面的状态。 kubeadm upgrade 不会影响你的工作负载,只会涉及 Kubernetes 内部的组件,但备份终究是好的。
  • 必须禁用交换分区

附加信息

  • 下述说明了在升级过程中何时腾空每个节点。如果你正在对任何 kubelet 进行小版本升级, 你需要先腾空待升级的节点(或多个节点)。对于控制面节点,其上可能运行着 CoreDNS Pod 或者其它非常重要的负载。更多信息见腾空节点
  • Kubernetes 项目推荐你使用版本匹配的 kubelet 和 kubeadm。 但你也可以使用比 kubeadm 版本更低的 kubelet 版本,前提是该版本仍处于支持的版本范围内。 欲了解更多信息,请访问 kubeadm 与 kubelet 的版本差异
  • 升级后,因为容器规约的哈希值已更改,所有容器都会被重新启动。
  • 要验证 kubelet 服务在升级后是否成功重启,可以执行 systemctl status kubeletjournalctl -xeu kubelet 查看服务日志。
  • 不建议使用 kubeadm upgrade--config 参数和 kubeadm 配置 API 类型来重新配置集群, 这样会产生意想不到的结果。 请按照重新配置 kubeadm 集群中的步骤来进行。

更改软件包仓库

如果你正在使用社区版的软件包仓库(pkgs.k8s.io), 你需要启用所需的 Kubernetes 小版本的软件包仓库。 这一点在更改 Kubernetes 软件包仓库文档中有详细说明。

确定要升级到哪个版本

使用操作系统的包管理器找到最新的补丁版本 Kubernetes 1.30:

# 在列表中查找最新的 1.30 版本
# 它看起来应该是 1.30.x-*,其中 x 是最新的补丁版本
sudo apt update
sudo apt-cache madison kubeadm

# 在列表中查找最新的 1.30 版本
# 它看起来应该是 1.30.x-*,其中 x 是最新的补丁版本
sudo yum list --showduplicates kubeadm --disableexcludes=kubernetes

升级控制平面节点

控制面节点上的升级过程应该每次处理一个节点。 首先选择一个要先行升级的控制面节点。该节点上必须拥有 /etc/kubernetes/admin.conf 文件。

执行 “kubeadm upgrade”

对于第一个控制面节点

  1. 升级 kubeadm:

    # 用最新的补丁版本号替换 1.30.x-* 中的 x
    sudo apt-mark unhold kubeadm && \
    sudo apt-get update && sudo apt-get install -y kubeadm='1.30.x-*' && \
    sudo apt-mark hold kubeadm
    

    # 用最新的补丁版本号替换 1.30.x-* 中的 x
    sudo yum install -y kubeadm-'1.30.x-*' --disableexcludes=kubernetes
    
  1. 验证下载操作正常,并且 kubeadm 版本正确:

    kubeadm version
    
  1. 验证升级计划:

    sudo kubeadm upgrade plan
    

    此命令检查你的集群是否可被升级,并取回你要升级的目标版本。 命令也会显示一个包含组件配置版本状态的表格。

  1. 选择要升级到的目标版本,运行合适的命令。例如:

    # 将 x 替换为你为此次升级所选择的补丁版本号
    sudo kubeadm upgrade apply v1.30.x
    

    一旦该命令结束,你应该会看到:

    [upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.30.x". Enjoy!
    
    [upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.
    
  1. 手动升级你的 CNI 驱动插件。

    你的容器网络接口(CNI)驱动应该提供了程序自身的升级说明。 参阅插件页面查找你的 CNI 驱动, 并查看是否需要其他升级步骤。

    如果 CNI 驱动作为 DaemonSet 运行,则在其他控制平面节点上不需要此步骤。

对于其它控制面节点

与第一个控制面节点相同,但是使用:

sudo kubeadm upgrade node

而不是:

sudo kubeadm upgrade apply

此外,不需要执行 kubeadm upgrade plan 和更新 CNI 驱动插件的操作。

腾空节点

将节点标记为不可调度并驱逐所有负载,准备节点的维护:

# 将 <node-to-drain> 替换为你要腾空的控制面节点名称
kubectl drain <node-to-drain> --ignore-daemonsets

升级 kubelet 和 kubectl

  1. 升级 kubelet 和 kubectl:

    # 用最新的补丁版本替换 1.30.x-* 中的 x
    sudo apt-mark unhold kubelet kubectl && \
    sudo apt-get update && sudo apt-get install -y kubelet='1.30.x-*' kubectl='1.30.x-*' && \
    sudo apt-mark hold kubelet kubectl
    

    # 用最新的补丁版本号替换 1.30.x-* 中的 x
    sudo yum install -y kubelet-'1.30.x-*' kubectl-'1.30.x-*' --disableexcludes=kubernetes
    
  1. 重启 kubelet:

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet
    

解除节点的保护

通过将节点标记为可调度,让其重新上线:

# 将 <node-to-uncordon> 替换为你的节点名称
kubectl uncordon <node-to-uncordon>

升级工作节点

工作节点上的升级过程应该一次执行一个节点,或者一次执行几个节点, 以不影响运行工作负载所需的最小容量。

以下内容演示如何升级 Linux 和 Windows 工作节点:

验证集群的状态

在所有节点上升级 kubelet 后,通过从 kubectl 可以访问集群的任何位置运行以下命令, 验证所有节点是否再次可用:

kubectl get nodes

STATUS 应显示所有节点为 Ready 状态,并且版本号已经被更新。

从故障状态恢复

如果 kubeadm upgrade 失败并且没有回滚,例如由于执行期间节点意外关闭, 你可以再次运行 kubeadm upgrade。 此命令是幂等的,并最终确保实际状态是你声明的期望状态。

要从故障状态恢复,你还可以运行 sudo kubeadm upgrade apply --force 而无需更改集群正在运行的版本。

在升级期间,kubeadm 向 /etc/kubernetes/tmp 目录下的如下备份文件夹写入数据:

  • kubeadm-backup-etcd-<date>-<time>
  • kubeadm-backup-manifests-<date>-<time>

kubeadm-backup-etcd 包含当前控制面节点本地 etcd 成员数据的备份。 如果 etcd 升级失败并且自动回滚也无法修复,则可以将此文件夹中的内容复制到 /var/lib/etcd 进行手工修复。如果使用的是外部的 etcd,则此备份文件夹为空。

kubeadm-backup-manifests 包含当前控制面节点的静态 Pod 清单文件的备份版本。 如果升级失败并且无法自动回滚,则此文件夹中的内容可以复制到 /etc/kubernetes/manifests 目录实现手工恢复。 如果由于某些原因,在升级前后某个组件的清单未发生变化,则 kubeadm 也不会为之生成备份版本。

工作原理

kubeadm upgrade apply 做了以下工作:

  • 检查你的集群是否处于可升级状态:
    • API 服务器是可访问的
    • 所有节点处于 Ready 状态
    • 控制面是健康的
  • 强制执行版本偏差策略。
  • 确保控制面的镜像是可用的或可拉取到服务器上。
  • 如果组件配置要求版本升级,则生成替代配置与/或使用用户提供的覆盖版本配置。
  • 升级控制面组件或回滚(如果其中任何一个组件无法启动)。
  • 应用新的 CoreDNSkube-proxy 清单,并强制创建所有必需的 RBAC 规则。
  • 如果旧文件在 180 天后过期,将创建 API 服务器的新证书和密钥文件并备份旧文件。

kubeadm upgrade node 在其他控制平节点上执行以下操作:

  • 从集群中获取 kubeadm ClusterConfiguration
  • (可选操作)备份 kube-apiserver 证书。
  • 升级控制平面组件的静态 Pod 清单。
  • 为本节点升级 kubelet 配置

kubeadm upgrade node 在工作节点上完成以下工作:

  • 从集群取回 kubeadm ClusterConfiguration
  • 为本节点升级 kubelet 配置。

1.5 - 升级 Linux 节点

本页讲述了如何升级用 kubeadm 创建的 Linux 工作节点。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

更改软件包仓库

如果你正在使用社区自治的软件包仓库(pkgs.k8s.io), 你需要启用所需的 Kubernetes 小版本的软件包仓库。 这一点在更改 Kubernetes 软件包仓库文档中有详细说明。

升级工作节点

升级 kubeadm

升级 kubeadm:

# 将 1.30.x-* 中的 x 替换为最新的补丁版本
sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.30.x-*' && \
sudo apt-mark hold kubeadm

# 将 1.30.x-* 中的 x 替换为最新的补丁版本
sudo yum install -y kubeadm-'1.30.x-*' --disableexcludes=kubernetes

执行 "kubeadm upgrade"

对于工作节点,下面的命令会升级本地的 kubelet 配置:

sudo kubeadm upgrade node

腾空节点

将节点标记为不可调度并驱逐所有负载,准备节点的维护:

# 在控制平面节点上执行此命令
# 将 <node-to-drain> 替换为你正腾空的节点的名称
kubectl drain <node-to-drain> --ignore-daemonsets

升级 kubelet 和 kubectl

  1. 升级 kubelet 和 kubectl:

    # 将 1.30.x-* 中的 x 替换为最新的补丁版本
    sudo apt-mark unhold kubelet kubectl && \
    sudo apt-get update && sudo apt-get install -y kubelet='1.30.x-*' kubectl='1.30.x-*' && \
    sudo apt-mark hold kubelet kubectl
    

    # 将 1.30.x-* 中的 x 替换为最新的补丁版本
    sudo yum install -y kubelet-'1.30.x-*' kubectl-'1.30.x-*' --disableexcludes=kubernetes
    
  1. 重启 kubelet:

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet
    

取消对节点的保护

通过将节点标记为可调度,让节点重新上线:

# 在控制平面节点上执行此命令
# 将 <node-to-uncordon> 替换为你的节点名称
kubectl uncordon <node-to-uncordon>

接下来

1.6 - 升级 Windows 节点

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

本页解释如何升级用 kubeadm 创建的 Windows 节点。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 1.17. 要获知版本信息,请输入 kubectl version.

升级工作节点

升级 kubeadm

  1. 在 Windows 节点上升级 kubeadm:

    # 将 %!f(string=1.).0 替换为你希望的版本
    curl.exe -Lo <kubeadm.exe 路径>  "https://dl.k8s.io/v%!f(string=1.).0/bin/windows/amd64/kubeadm.exe"
    

腾空节点

  1. 在一个能访问到 Kubernetes API 的机器上,将 Windows 节点标记为不可调度并 驱逐其上的所有负载,以便准备节点维护操作:

    # 将 <要腾空的节点> 替换为你要腾空的节点的名称
    kubectl drain <要腾空的节点> --ignore-daemonsets
    

    你应该会看到类似下面的输出:

    node/ip-172-31-85-18 cordoned
    node/ip-172-31-85-18 drained
    

升级 kubelet 配置

  1. 在 Windows 节点上,执行下面的命令来同步新的 kubelet 配置:

    kubeadm upgrade node
    

升级 kubelet 和 kube-proxy

  1. 在 Windows 节点上升级并重启 kubelet:

    stop-service kubelet
    curl.exe -Lo <kubelet.exe 路径> "https://dl.k8s.io/v%!f(string=1.).0/bin/windows/amd64/kubelet.exe"
    restart-service kubelet
    
  1. 在 Windows 节点上升级并重启 kube-proxy:

    stop-service kube-proxy
    curl.exe -Lo <kube-proxy.exe 路径> "https://dl.k8s.io/v%!f(string=1.).0/bin/windows/amd64/kube-proxy.exe"
    restart-service kube-proxy
    

对节点执行 uncordon 操作

  1. 从一台能够访问到 Kubernetes API 的机器上,通过将节点标记为可调度,使之 重新上线:

    # 将 <要腾空的节点> 替换为你的节点名称
    kubectl uncordon <要腾空的节点>
    

接下来

1.7 - 更改 Kubernetes 软件包仓库

本页介绍了如何在升级集群时启用包含 Kubernetes 次要版本的软件包仓库。 这仅适用于使用托管在 pkgs.k8s.io 上社区自治软件包仓库的用户。 启用新的 Kubernetes 小版本的软件包仓库。与传统的软件包仓库不同, 社区自治的软件包仓库所采用的结构为每个 Kubernetes 小版本都有一个专门的软件包仓库。

准备开始

本文假设你已经在使用社区自治的软件包仓库(pkgs.k8s.io)。如果不是这种情况, 强烈建议按照官方公告中所述, 迁移到社区自治的软件包仓库。

验证是否正在使用 Kubernetes 软件包仓库

如果你不确定自己是在使用社区自治的软件包仓库还是在使用老旧的软件包仓库, 可以执行以下步骤进行验证:

打印定义 Kubernetes apt 仓库的文件的内容:

# 在你的系统上,此配置文件可能具有不同的名称
pager /etc/apt/sources.list.d/kubernetes.list

如果你看到类似以下的一行:

deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /

你正在使用 Kubernetes 软件包仓库,本指南适用于你。 否则,强烈建议按照官方公告中所述, 迁移到 Kubernetes 软件包仓库。

打印定义 Kubernetes yum 仓库的文件的内容:

# 在你的系统上,此配置文件可能具有不同的名称
cat /etc/yum.repos.d/kubernetes.repo

如果你看到的 baseurl 类似以下输出中的 baseurl

[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl

你正在使用 Kubernetes 软件包仓库,本指南适用于你。 否则,强烈建议按照官方公告中所述, 迁移到 Kubernetes 软件包仓库。

打印定义 Kubernetes zypper 仓库的文件的内容:

# 在你的系统上,此配置文件可能具有不同的名称
cat /etc/zypp/repos.d/kubernetes.repo

如果你看到的 baseurl 类似以下输出中的 baseurl

[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl

你正在使用 Kubernetes 软件包仓库,本指南适用于你。 否则,强烈建议按照官方公告中所述, 迁移到 Kubernetes 软件包仓库。

切换到其他 Kubernetes 软件包仓库

在从一个 Kubernetes 小版本升级到另一个版本时,应执行此步骤以获取所需 Kubernetes 小版本的软件包访问权限。

  1. 使用你所选择的文本编辑器打开定义 Kubernetes apt 仓库的文件:

    nano /etc/apt/sources.list.d/kubernetes.list
    

    你应该看到一行包含当前 Kubernetes 小版本的 URL。 例如,如果你正在使用 v1.29,你应该看到类似以下的输出:

    deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /
    
  1. 将 URL 中的版本更改为下一个可用的小版本,例如:

    deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /
    
  1. 保存文件并退出文本编辑器。继续按照相关的升级说明进行操作。

  1. 使用你所选择的文本编辑器打开定义 Kubernetes yum 仓库的文件:

    nano /etc/yum.repos.d/kubernetes.repo
    

    你应该看到一个文件包含当前 Kubernetes 小版本的两个 URL。 例如,如果你正在使用 v1.29,你应该看到类似以下的输出:

    [kubernetes]
    name=Kubernetes
    baseurl=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/
    enabled=1
    gpgcheck=1
    gpgkey=https://pkgs.k8s.io/core:/stable:/v1.29/rpm/repodata/repomd.xml.key
    exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
    
  1. 将这些 URL 中的版本更改为下一个可用的小版本,例如:

    [kubernetes]
    name=Kubernetes
    baseurl=https://pkgs.k8s.io/core:/stable:/v1.30/rpm/
    enabled=1
    gpgcheck=1
    gpgkey=https://pkgs.k8s.io/core:/stable:/v1.30/rpm/repodata/repomd.xml.key
    exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
    
  1. 保存文件并退出文本编辑器。继续按照相关的升级说明进行操作。

接下来

2 - 从 dockershim 迁移

本节提供从 dockershim 迁移到其他容器运行时的必备知识。

自从 Kubernetes 1.20 宣布 弃用 dockershim, 各类疑问随之而来:这对各类工作负载和 Kubernetes 部署会产生什么影响。 我们的弃用 Dockershim 常见问题可以帮助你更好地理解这个问题。

Dockershim 在 Kubernetes v1.24 版本已经被移除。 如果你集群内是通过 dockershim 使用 Docker Engine 作为容器运行时,并希望 Kubernetes 升级到 v1.24, 建议你迁移到其他容器运行时或使用其他方法以获得 Docker 引擎支持。

请参阅容器运行时 一节以了解可用的备选项。

带 dockershim 的 Kubernetes 版本 (1.23) 已不再支持, v1.24 很快也将不再支持。

当在迁移过程中遇到麻烦,请上报问题。 那么问题就可以及时修复,你的集群也可以进入移除 dockershim 前的就绪状态。 在 v1.24 支持结束后,如果出现影响集群的严重问题, 你需要联系你的 Kubernetes 供应商以获得支持或一次升级多个版本。

你的集群中可以有不止一种类型的节点,尽管这不是常见的情况。

下面这些任务可以帮助你完成迁移:

接下来

  • 查看容器运行时了解可选的容器运行时。
  • 如果你发现与 dockershim 迁移相关的缺陷或其他技术问题, 可以在 Kubernetes 项目报告问题

2.1 - 将节点上的容器运行时从 Docker Engine 改为 containerd

本任务给出将容器运行时从 Docker 改为 containerd 所需的步骤。 此任务适用于运行 1.23 或更早版本 Kubernetes 的集群操作人员。 同时,此任务也涉及从 dockershim 迁移到 containerd 的示例场景。 有关其他备选的容器运行时,可查阅 此页面进行拣选。

准备开始

安装 containerd。进一步的信息可参见 containerd 的安装文档。 关于一些特定的环境准备工作,请遵循 containerd 指南

腾空节点

kubectl drain <node-to-drain> --ignore-daemonsets

<node-to-drain> 替换为你所要腾空的节点的名称。

停止 Docker 守护进程

systemctl stop kubelet
systemctl disable docker.service --now

安装 Containerd

遵循此指南 了解安装 containerd 的详细步骤。

  1. 从官方的 Docker 仓库安装 containerd.io 包。关于为你所使用的 Linux 发行版来设置 Docker 仓库,以及安装 containerd.io 包的详细说明, 可参见开始使用 containerd
  1. 配置 containerd:

    sudo mkdir -p /etc/containerd
    containerd config default | sudo tee /etc/containerd/config.toml
    
  1. 重启 containerd:

    sudo systemctl restart containerd
    

启动一个 Powershell 会话,将 $Version 设置为期望的版本(例如:$Version="1.4.3"), 之后运行下面的命令:

  1. 下载 containerd:

    curl.exe -L https://github.com/containerd/containerd/releases/download/v$Version/containerd-$Version-windows-amd64.tar.gz -o containerd-windows-amd64.tar.gz
    tar.exe xvf .\containerd-windows-amd64.tar.gz
    
  1. 解压缩并执行配置:

    Copy-Item -Path ".\bin\" -Destination "$Env:ProgramFiles\containerd" -Recurse -Force
    cd $Env:ProgramFiles\containerd\
    .\containerd.exe config default | Out-File config.toml -Encoding ascii
    
    # 请审查配置信息。取决于你的安装环境,你可能需要调整:
    # - sandbox_image (Kubernetes pause 镜像)
    # - CNI 的 bin_dir 和 conf_dir 的位置
    Get-Content config.toml
    
    # (可选步骤,但强烈建议执行)将 containerd 排除在 Windows Defender 扫描之外
    Add-MpPreference -ExclusionProcess "$Env:ProgramFiles\containerd\containerd.exe"
    
  1. 启动 containerd:

    .\containerd.exe --register-service
    Start-Service containerd
    

配置 kubelet 使用 containerd 作为其容器运行时

编辑文件 /var/lib/kubelet/kubeadm-flags.env,将 containerd 运行时添加到标志中; --container-runtime-endpoint=unix:///run/containerd/containerd.sock

使用 kubeadm 的用户应该知道,kubeadm 工具将每个主机的 CRI 套接字保存在该主机对应的 Node 对象的注解中。 要更改这一注解信息,你可以在一台包含 kubeadm /etc/kubernetes/admin.conf 文件的机器上执行以下命令:

kubectl edit no <node-name>

这一命令会打开一个文本编辑器,供你在其中编辑 Node 对象。 要选择不同的文本编辑器,你可以设置 KUBE_EDITOR 环境变量。

  • 更改 kubeadm.alpha.kubernetes.io/cri-socket 值,将其从 /var/run/dockershim.sock 改为你所选择的 CRI 套接字路径 (例如:unix:///run/containerd/containerd.sock)。

    注意新的 CRI 套接字路径必须带有 unix:// 前缀。

  • 保存文本编辑器中所作的修改,这会更新 Node 对象。

重启 kubelet

systemctl start kubelet

验证节点处于健康状态

运行 kubectl get nodes -o wide,containerd 会显示为我们所更改的节点上的运行时。

移除 Docker Engine

如果节点显示正常,删除 Docker。

sudo yum remove docker-ce docker-ce-cli

sudo apt-get purge docker-ce docker-ce-cli

sudo dnf remove docker-ce docker-ce-cli

sudo apt-get purge docker-ce docker-ce-cli

上面的命令不会移除你的主机上的镜像、容器、卷或者定制的配置文件。 要删除这些内容,参阅 Docker 的指令来卸载 Docker Engine

uncordon 节点

kubectl uncordon <node-to-uncordon>

<node-to-uncordon> 替换为你之前腾空的节点的名称。

2.2 - 将 Docker Engine 节点从 dockershim 迁移到 cri-dockerd

本页面为你展示如何迁移你的 Docker Engine 节点,使之使用 cri-dockerd 而不是 dockershim。 在以下场景中,你可以遵从这里的步骤执行操作:

  • 你期望不再使用 dockershim,但仍然使用 Docker Engine 来在 Kubernetes 中运行容器。
  • 你希望升级到 Kubernetes v1.30 且你的现有集群依赖于 dockershim, 因此你必须放弃 dockershim,而 cri-dockerd 是你的一种选项。

要进一步了解 dockershim 的移除,请阅读 FAQ 页面

cri-dockerd 是什么?

在 Kubernetes v1.24 及更早版本中,你可以在 Kubernetes 中使用 Docker Engine, 依赖于一个称作 dockershim 的内置 Kubernetes 组件。 dockershim 组件在 Kubernetes v1.24 发行版本中已被移除;不过,一种来自第三方的替代品, cri-dockerd 是可供使用的。cri-dockerd 适配器允许你通过 容器运行时接口(Container Runtime Interface,CRI) 来使用 Docker Engine。

如果你想要迁移到 cri-dockerd 以便继续使用 Docker Engine 作为你的容器运行时, 你需要在所有被影响的节点上执行以下操作:

  1. 安装 cri-dockerd
  2. 隔离(Cordon)并腾空(Drain)该节点;
  3. 配置 kubelet 使用 cri-dockerd
  4. 重新启动 kubelet;
  5. 验证节点处于健康状态。

首先在非关键节点上测试这一迁移过程。

你应该针对所有希望迁移到 cri-dockerd 的节点执行以下步骤。

准备开始

隔离并腾空节点

  1. 隔离节点,阻止新的 Pod 被调度到节点上:

    kubectl cordon <NODE_NAME>
    

    <NODE_NAME> 替换为节点名称。

  1. 腾空节点以安全地逐出所有运行中的 Pod:

    kubectl drain <NODE_NAME> --ignore-daemonsets
    

配置 kubelet 使用 cri-dockerd

下面的步骤适用于用 kubeadm 工具安装的集群。如果你使用不同的工具, 你需要使用针对该工具的配置指令来修改 kubelet。

  1. 在每个被影响的节点上,打开 /var/lib/kubelet/kubeadm-flags.env 文件;
  2. --container-runtime-endpoint 标志,将其设置为 unix:///var/run/cri-dockerd.sock
  3. --container-runtime 标志修改为 remote(在 Kubernetes v1.27 及更高版本中不可用)。

kubeadm 工具将节点上的套接字存储为控制面上 Node 对象的注解。 要为每个被影响的节点更改此套接字:

  1. 编辑 Node 对象的 YAML 表示:

    KUBECONFIG=/path/to/admin.conf kubectl edit no <NODE_NAME>
    

    根据下面的说明执行替换:

    • /path/to/admin.conf:指向 kubectl 配置文件 admin.conf 的路径;
    • <NODE_NAME>:你要修改的节点的名称。
  2. kubeadm.alpha.kubernetes.io/cri-socket 标志从 /var/run/dockershim.sock 更改为 unix:///var/run/cri-dockerd.sock

  3. 保存所作更改。保存时,Node 对象被更新。

重启 kubelet

systemctl restart kubelet

验证节点处于健康状态

要检查节点是否在使用 cri-dockerd 端点, 按照找出你所使用的运行时页面所给的指令操作。 kubelet 的 --container-runtime-endpoint 标志取值应该是 unix:///var/run/cri-dockerd.sock

解除节点隔离

kubectl uncordon <NODE_NAME>

接下来

2.3 - 查明节点上所使用的容器运行时

本页面描述查明集群中节点所使用的容器运行时 的步骤。

取决于你运行集群的方式,节点所使用的容器运行时可能是事先配置好的, 也可能需要你来配置。如果你在使用托管的 Kubernetes 服务, 可能存在特定于厂商的方法来检查节点上配置的容器运行时。 本页描述的方法应该在能够执行 kubectl 的场合下都可以工作。

准备开始

安装并配置 kubectl。参见安装工具 节了解详情。

查明节点所使用的容器运行时

使用 kubectl 来读取并显示节点信息:

kubectl get nodes -o wide

输出如下面所示。CONTAINER-RUNTIME 列给出容器运行时及其版本。

对于 Docker Engine,输出类似于:

NAME         STATUS   VERSION    CONTAINER-RUNTIME
node-1       Ready    v1.16.15   docker://19.3.1
node-2       Ready    v1.16.15   docker://19.3.1
node-3       Ready    v1.16.15   docker://19.3.1

如果你的容器运行时显示为 Docker Engine,你仍然可能不会被 v1.24 中 dockershim 的移除所影响。 通过检查运行时端点,可以查看你是否在使用 dockershim。 如果你没有使用 dockershim,你就不会被影响。

对于 containerd,输出类似于这样:

# For containerd
NAME         STATUS   VERSION   CONTAINER-RUNTIME
node-1       Ready    v1.19.6   containerd://1.4.1
node-2       Ready    v1.19.6   containerd://1.4.1
node-3       Ready    v1.19.6   containerd://1.4.1

你可以在容器运行时 页面找到与容器运行时相关的更多信息。

检查当前使用的运行时端点

容器运行时使用 Unix Socket 与 kubelet 通信,这一通信使用基于 gRPC 框架的 CRI 协议。kubelet 扮演客户端,运行时扮演服务器端。 在某些情况下,你可能想知道你的节点使用的是哪个 socket。 如若集群是 Kubernetes v1.24 及以后的版本, 或许你想知道当前运行时是否是使用 dockershim 的 Docker Engine。

可以通过检查 kubelet 的参数得知当前使用的是哪个 socket。

  1. 查看 kubelet 进程的启动命令

     tr \\0 ' ' < /proc/"$(pgrep kubelet)"/cmdline
    

    如有节点上没有 tr 或者 pgrep,就需要手动检查 kubelet 的启动命令

  1. 在命令的输出中,查找 --container-runtime--container-runtime-endpoint 标志。

    • 如果你的节点使用 Kubernetes v1.23 或更早的版本,这两个参数不存在, 或者 --container-runtime 标志值不是 remote,则你在通过 dockershim 套接字使用 Docker Engine。 在 Kubernetes v1.27 及以后的版本中,--container-runtime 命令行参数不再可用。
    • 如果设置了 --container-runtime-endpoint 参数,查看套接字名称即可得知当前使用的运行时。 如若套接字 unix:///run/containerd/containerd.sock 是 containerd 的端点。

如果你将节点上的容器运行时从 Docker Engine 改变为 containerd,可在 迁移到不同的运行时 找到更多信息。或者,如果你想在 Kubernetes v1.24 及以后的版本仍使用 Docker Engine, 可以安装 CRI 兼容的适配器实现,如 cri-dockerd

2.4 - 排查 CNI 插件相关的错误

为了避免 CNI 插件相关的错误,需要验证你正在使用或升级到一个经过测试的容器运行时, 该容器运行时能够在你的 Kubernetes 版本上正常工作。

关于 "Incompatible CNI versions" 和 "Failed to destroy network for sandbox" 错误

在 containerd v1.6.0-v1.6.3 中,当配置或清除 Pod CNI 网络时,如果 CNI 插件没有升级和/或 CNI 配置文件中没有声明 CNI 配置版本时,会出现服务问题。containerd 团队报告说: “这些问题在 containerd v1.6.4 中得到了解决。”

在使用 containerd v1.6.0-v1.6.3 时,如果你不升级 CNI 插件和/或声明 CNI 配置版本, 你可能会遇到以下 "Incompatible CNI versions" 或 "Failed to destroy network for sandbox" 错误状况。

Incompatible CNI versions 错误

如果因为配置版本比插件版本新,导致你的 CNI 插件版本与配置中的插件版本无法正确匹配时, 在启动 Pod 时,containerd 日志可能会显示类似的错误信息:

incompatible CNI versions; config is \"1.0.0\", plugin supports [\"0.1.0\" \"0.2.0\" \"0.3.0\" \"0.3.1\" \"0.4.0\"]"

为了解决这个问题,需要更新你的 CNI 插件和 CNI 配置文件

Failed to destroy network for sandbox 错误

如果 CNI 插件配置中未给出插件的版本, Pod 可能可以运行。但是,停止 Pod 时会产生类似于以下错误:

ERROR[2022-04-26T00:43:24.518165483Z] StopPodSandbox for "b" failed
error="failed to destroy network for sandbox \"bbc85f891eaf060c5a879e27bba9b6b06450210161dfdecfbb2732959fb6500a\": invalid version \"\": the version is empty"

此错误使 Pod 处于未就绪状态,且仍然挂接到某网络名字空间上。 为修复这一问题,编辑 CNI 配置文件以添加缺失的版本信息。 下一次尝试停止 Pod 应该会成功。

更新你的 CNI 插件和 CNI 配置文件

如果你使用 containerd v1.6.0-v1.6.3 并遇到 "Incompatible CNI versions" 或者 "Failed to destroy network for sandbox" 错误,考虑更新你的 CNI 插件并编辑 CNI 配置文件。

以下是针对各节点要执行的典型步骤的概述:

  1. 安全地腾空并隔离节点
  1. 停止容器运行时和 kubelet 服务后,执行以下升级操作:
  • 如果你正在运行 CNI 插件,请将它们升级到最新版本。
  • 如果你使用的是非 CNI 插件,请将它们替换为 CNI 插件,并使用最新版本的插件。
  • 更新插件配置文件以指定或匹配 CNI 规范支持的插件版本, 如后文 "containerd 配置文件示例"章节所示。
  • 对于 containerd,请确保你已安装 CNI loopback 插件的最新版本(v1.0.0 或更高版本)。
  • 将节点组件(例如 kubelet)升级到 Kubernetes v1.24
  • 升级到或安装最新版本的容器运行时。
  1. 通过重新启动容器运行时和 kubelet 将节点重新加入到集群。取消节点隔离(kubectl uncordon <nodename>)。

containerd 配置文件示例

以下示例显示了 containerd 运行时 v1.6.x 的配置, 它支持最新版本的 CNI 规范(v1.0.0)。 请参阅你的插件和网络提供商的文档,以获取有关你系统配置的进一步说明。

在 Kubernetes 中,作为其默认行为,containerd 运行时为 Pod 添加一个本地回路接口:lo。 containerd 运行时通过 CNI 插件 loopback 配置本地回路接口。
loopback 插件作为 containerd 发布包的一部分,扮演 cni 角色。 containerd v1.6.0 及更高版本包括与 CNI v1.0.0 兼容的 loopback 插件以及其他默认 CNI 插件。 loopback 插件的配置由 containerd 内部完成,并被设置为使用 CNI v1.0.0。 这也意味着当这个更新版本的 containerd 启动时,loopback 插件的版本必然是 v1.0.0 或更高版本。

以下 Bash 命令生成一个 CNI 配置示例。这里,cniVersion 字段被设置为配置版本值 1.0.0, 以供 containerd 调用 CNI 桥接插件时使用。

cat << EOF | tee /etc/cni/net.d/10-containerd-net.conflist
{
 "cniVersion": "1.0.0",
 "name": "containerd-net",
 "plugins": [
   {
     "type": "bridge",
     "bridge": "cni0",
     "isGateway": true,
     "ipMasq": true,
     "promiscMode": true,
     "ipam": {
       "type": "host-local",
       "ranges": [
         [{
           "subnet": "10.88.0.0/16"
         }],
         [{
           "subnet": "2001:db8:4860::/64"
         }]
       ],
       "routes": [
         { "dst": "0.0.0.0/0" },
         { "dst": "::/0" }
       ]
     }
   },
   {
     "type": "portmap",
     "capabilities": {"portMappings": true},
     "externalSetMarkChain": "KUBE-MARK-MASQ"
   }
 ]
}
EOF

基于你的用例和网络地址规划,将前面示例中的 IP 地址范围更新为合适的值。

2.5 - 检查移除 Dockershim 是否对你有影响

Kubernetes 的 dockershim 组件使得你可以把 Docker 用作 Kubernetes 的 容器运行时。 在 Kubernetes v1.24 版本中,内建组件 dockershim 被移除。

本页讲解你的集群把 Docker 用作容器运行时的运作机制, 并提供使用 dockershim 时,它所扮演角色的详细信息, 继而展示了一组操作,可用来检查移除 dockershim 对你的工作负载是否有影响。

检查你的应用是否依赖于 Docker

即使你是通过 Docker 创建的应用容器,也不妨碍你在其他任何容器运行时上运行这些容器。 这种使用 Docker 的方式并不构成对 Docker 作为一个容器运行时的依赖。

当用了别的容器运行时之后,Docker 命令可能不工作,或者产生意外的输出。 下面是判定你是否依赖于 Docker 的方法。

  1. 确认没有特权 Pod 执行 Docker 命令(如 docker ps)、重新启动 Docker 服务(如 systemctl restart docker.service)或修改 Docker 配置文件 /etc/docker/daemon.json
  2. 检查 Docker 配置文件(如 /etc/docker/daemon.json)中容器镜像仓库的镜像(mirror)站点设置。 这些配置通常需要针对不同容器运行时来重新设置。
  3. 检查确保在 Kubernetes 基础设施之外的节点上运行的脚本和应用程序没有执行 Docker 命令。 可能的情况有:
    • SSH 到节点排查故障;
    • 节点启动脚本;
    • 直接安装在节点上的监控和安全代理。
  1. 检查执行上述特权操作的第三方工具。 详细操作请参考从 dockershim 迁移遥测和安全代理
  2. 确认没有对 dockershim 行为的间接依赖。这是一种极端情况,不太可能影响你的应用。 一些工具很可能被配置为使用了 Docker 特性,比如,基于特定指标发警报, 或者在故障排查指令的一个环节中搜索特定的日志信息。 如果你有此类配置的工具,需要在迁移之前,在测试集群上测试这类行为。

Docker 依赖详解

容器运行时是一个软件, 用来运行组成 Kubernetes Pod 的容器。 Kubernetes 负责编排和调度 Pod;在每一个节点上,kubelet 使用抽象的容器运行时接口,所以你可以任意选用兼容的容器运行时。

在早期版本中,Kubernetes 提供的兼容性支持一个容器运行时:Docker。 在 Kubernetes 后来的发展历史中,集群运营人员希望采用别的容器运行时。 于是 CRI 被设计出来满足这类灵活性需求 - 而 kubelet 亦开始支持 CRI。 然而,因为 Docker 在 CRI 规范创建之前就已经存在,Kubernetes 就创建了一个适配器组件 dockershim。 dockershim 适配器允许 kubelet 与 Docker 交互,就好像 Docker 是一个 CRI 兼容的运行时一样。

你可以阅读博文 Kubernetes 正式支持集成 Containerd

Dockershim 和 Containerd CRI 的实现对比图

切换到 Containerd 容器运行时可以消除掉中间环节。 所有相同的容器都可由 Containerd 这类容器运行时来运行。 但是现在,由于直接用容器运行时调度容器,它们对 Docker 是不可见的。 因此,你以前用来检查这些容器的 Docker 工具或漂亮的 UI 都不再可用。

你不能再使用 docker psdocker inspect 命令来获取容器信息。 由于你不能列出容器,因此你不能获取日志、停止容器,甚至不能通过 docker exec 在容器中执行命令。

你仍然可以下载镜像,或者用 docker build 命令创建它们。 但用 Docker 创建、下载的镜像,对于容器运行时和 Kubernetes,均不可见。 为了在 Kubernetes 中使用,需要把镜像推送(push)到某镜像仓库。

已知问题

一些文件系统指标缺失并且指标格式不同

Kubelet /metrics/cadvisor 端点提供 Prometheus 指标, 如 Kubernetes 系统组件指标 中所述。 如果你安装了一个依赖该端点的指标收集器,你可能会看到以下问题:

  • Docker 节点上的指标格式为 k8s_<container-name>_<pod-name>_<namespace>_<pod-uid>_<restart-count>, 但其他运行时的格式不同。例如,在 containerd 节点上它是 <container-id>

  • 一些文件系统指标缺失,如下所示:

    container_fs_inodes_free
    container_fs_inodes_total
    container_fs_io_current
    container_fs_io_time_seconds_total
    container_fs_io_time_weighted_seconds_total
    container_fs_limit_bytes
    container_fs_read_seconds_total
    container_fs_reads_merged_total
    container_fs_sector_reads_total
    container_fs_sector_writes_total
    container_fs_usage_bytes
    container_fs_write_seconds_total
    container_fs_writes_merged_total
    

解决方法

你可以通过使用 cAdvisor 作为一个独立的守护程序来缓解这个问题。

  1. 找到名称格式为 vX.Y.Z-containerd-cri 的最新 cAdvisor 版本(例如 v0.42.0-containerd-cri)。
  2. 按照 cAdvisor Kubernetes Daemonset 中的步骤来创建守护进程。
  3. 将已安装的指标收集器指向使用 cAdvisor 的 /metrics 端点。 该端点提供了全套的 Prometheus 容器指标

替代方案:

  • 使用替代的第三方指标收集解决方案。
  • 从 Kubelet 摘要 API 收集指标,该 API 在 /stats/summary 提供服务。

接下来

2.6 - 从 dockershim 迁移遥测和安全代理

Kubernetes 对与 Docker Engine 直接集成的支持已被弃用且已经被删除。 大多数应用程序不直接依赖于托管容器的运行时。但是,仍然有大量的遥测和监控代理依赖 docker 来收集容器元数据、日志和指标。 本文汇总了一些信息和链接:信息用于阐述如何探查这些依赖,链接用于解释如何迁移这些代理去使用通用的工具或其他容器运行。

遥测和安全代理

在 Kubernetes 集群中,有几种不同的方式来运行遥测或安全代理。 一些代理在以 DaemonSet 的形式运行或直接在节点上运行时,直接依赖于 Docker Engine。

为什么有些遥测代理会与 Docker Engine 通信?

从历史上看,Kubernetes 是专门为与 Docker Engine 一起工作而编写的。 Kubernetes 负责网络和调度,依靠 Docker Engine 在节点上启动并运行容器(在 Pod 内)。一些与遥测相关的信息,例如 pod 名称, 只能从 Kubernetes 组件中获得。其他数据,例如容器指标,不是容器运行时的责任。 早期遥测代理需要查询容器运行时 Kubernetes 以报告准确的信息。 随着时间的推移,Kubernetes 获得了支持多种运行时的能力, 现在支持任何兼容容器运行时接口的运行时。

一些代理和 Docker 工具紧密绑定。比如代理会用到 docker psdocker top 这类命令来列出容器和进程,用 docker logs 订阅 Docker 的日志。 如果现有集群中的节点使用 Docker Engine,在你切换到其它容器运行时的时候, 这些命令将不再起作用。

识别依赖于 Docker Engine 的 DaemonSet

如果某 Pod 想调用运行在节点上的 dockerd,该 Pod 必须满足以下两个条件之一:

  • 将包含 Docker 守护进程特权套接字的文件系统挂载为一个;或
  • 直接以卷的形式挂载 Docker 守护进程特权套接字的特定路径。

举例来说:在 COS 镜像中,Docker 通过 /var/run/docker.sock 开放其 Unix 域套接字。 这意味着 Pod 的规约中需要包含 hostPath 卷以挂载 /var/run/docker.sock

下面是一个 shell 示例脚本,用于查找包含直接映射 Docker 套接字的挂载点的 Pod。 你也可以删掉 grep '/var/run/docker.sock' 这一代码片段以查看其它挂载信息。

kubectl get pods --all-namespaces \
-o=jsonpath='{range .items[*]}{"\n"}{.metadata.namespace}{":\t"}{.metadata.name}{":\t"}{range .spec.volumes[*]}{.hostPath.path}{", "}{end}{end}' \
| sort \
| grep '/var/run/docker.sock'

检测节点代理对 Docker 的依赖性

在你的集群节点被定制、且在各个节点上均安装了额外的安全和遥测代理的场景下, 一定要和代理的供应商确认:该代理是否依赖于 Docker。

遥测和安全代理的供应商

本节旨在汇总有关可能依赖于容器运行时的各种遥测和安全代理的信息。

我们通过 谷歌文档 提供了为各类遥测和安全代理供应商准备的持续更新的迁移指导。 请与供应商联系,获取从 dockershim 迁移的最新说明。

从 dockershim 迁移

Aqua

无需更改:在运行时变更时可以无缝切换运行。

Datadog

如何迁移: Kubernetes 中对于 Docker 的弃用 名字中包含以下字符串的 Pod 可能访问 Docker Engine:

  • datadog-agent
  • datadog
  • dd-agent

Dynatrace

如何迁移: 在 Dynatrace 上从 Docker-only 迁移到通用容器指标

Containerd 支持公告:在基于 containerd 的 Kubernetes 环境的获取容器的自动化全栈可见性 CRI-O 支持公告:在基于 CRI-O 的 Kubernetes 环境获取容器的自动化全栈可见性(测试版)

名字中包含以下字符串的 Pod 可能访问 Docker:

  • dynatrace-oneagent

Falco

如何迁移: 迁移 Falco 从 dockershim Falco 支持任何与 CRI 兼容的运行时(默认配置中使用 containerd);该文档解释了所有细节。

名字中包含以下字符串的 Pod 可能访问 Docker:

  • falco

Prisma Cloud Compute

在依赖于 CRI(非 Docker)的集群上安装 Prisma Cloud 时,查看 Prisma Cloud 提供的文档

名字中包含以下字符串的 Pod 可能访问 Docker:

  • twistlock-defender-ds

SignalFx (Splunk)

SignalFx Smart Agent(已弃用)在 Kubernetes 集群上使用了多种不同的监视器, 包括 kubernetes-clusterkubelet-stats/kubelet-metricsdocker-container-statskubelet-stats 监视器此前已被供应商所弃用,现支持 kubelet-metricsdocker-container-stats 监视器受 dockershim 移除的影响。 不要为 docker-container-stats 监视器使用 Docker Engine 之外的运行时。

如何从依赖 dockershim 的代理迁移:

  1. 所配置的监视器中移除 docker-container-stats。 注意,若节点上已经安装了 Docker,在非 dockershim 环境中启用此监视器后会导致报告错误的指标; 如果节点未安装 Docker,则无法获得指标。
  2. 启用和配置 kubelet-metrics 监视器。

名字中包含以下字符串的 Pod 可能访问 Docker:

  • signalfx-agent

Yahoo Kubectl Flame

Flame 不支持 Docker 以外的容器运行时,具体可见 https://github.com/yahoo/kubectl-flame/issues/51

3 - 手动生成证书

在使用客户端证书认证的场景下,你可以通过 easyrsaopensslcfssl 等工具以手工方式生成证书。

easyrsa

easyrsa 支持以手工方式为你的集群生成证书。

  1. 下载、解压、初始化打过补丁的 easyrsa3

    curl -LO https://dl.k8s.io/easy-rsa/easy-rsa.tar.gz
    tar xzf easy-rsa.tar.gz
    cd easy-rsa-master/easyrsa3
    ./easyrsa init-pki
    
  1. 生成新的证书颁发机构(CA)。参数 --batch 用于设置自动模式; 参数 --req-cn 用于设置新的根证书的通用名称(CN)。

    ./easyrsa --batch "--req-cn=${MASTER_IP}@`date +%s`" build-ca nopass
    
  1. 生成服务器证书和秘钥。

    参数 --subject-alt-name 设置 API 服务器的 IP 和 DNS 名称。 MASTER_CLUSTER_IP 用于 API 服务器和控制器管理器,通常取 CIDR 的第一个 IP, 由 --service-cluster-ip-range 的参数提供。 参数 --days 用于设置证书的过期时间。 下面的示例假定你的默认 DNS 域名为 cluster.local

    ./easyrsa --subject-alt-name="IP:${MASTER_IP},"\
    "IP:${MASTER_CLUSTER_IP},"\
    "DNS:kubernetes,"\
    "DNS:kubernetes.default,"\
    "DNS:kubernetes.default.svc,"\
    "DNS:kubernetes.default.svc.cluster,"\
    "DNS:kubernetes.default.svc.cluster.local" \
    --days=10000 \
    build-server-full server nopass
    
  1. 拷贝文件 pki/ca.crtpki/issued/server.crtpki/private/server.key 到你的目录中。
  1. 在 API 服务器的启动参数中添加以下参数:

    --client-ca-file=/yourdirectory/ca.crt
    --tls-cert-file=/yourdirectory/server.crt
    --tls-private-key-file=/yourdirectory/server.key
    

openssl

openssl 支持以手工方式为你的集群生成证书。

  1. 生成一个 2048 位的 ca.key 文件

    openssl genrsa -out ca.key 2048
    
  1. 在 ca.key 文件的基础上,生成 ca.crt 文件(用参数 -days 设置证书有效期)

    openssl req -x509 -new -nodes -key ca.key -subj "/CN=${MASTER_IP}" -days 10000 -out ca.crt
    
  1. 生成一个 2048 位的 server.key 文件:

    openssl genrsa -out server.key 2048
    
  1. 创建一个用于生成证书签名请求(CSR)的配置文件。 保存文件(例如:csr.conf)前,记得用真实值替换掉尖括号中的值(例如:<MASTER_IP>)。 注意:MASTER_CLUSTER_IP 就像前一小节所述,它的值是 API 服务器的服务集群 IP。 下面的例子假定你的默认 DNS 域名为 cluster.local

    [ req ]
    default_bits = 2048
    prompt = no
    default_md = sha256
    req_extensions = req_ext
    distinguished_name = dn
    
    [ dn ]
    C = <country>
    ST = <state>
    L = <city>
    O = <organization>
    OU = <organization unit>
    CN = <MASTER_IP>
    
    [ req_ext ]
    subjectAltName = @alt_names
    
    [ alt_names ]
    DNS.1 = kubernetes
    DNS.2 = kubernetes.default
    DNS.3 = kubernetes.default.svc
    DNS.4 = kubernetes.default.svc.cluster
    DNS.5 = kubernetes.default.svc.cluster.local
    IP.1 = <MASTER_IP>
    IP.2 = <MASTER_CLUSTER_IP>
    
    [ v3_ext ]
    authorityKeyIdentifier=keyid,issuer:always
    basicConstraints=CA:FALSE
    keyUsage=keyEncipherment,dataEncipherment
    extendedKeyUsage=serverAuth,clientAuth
    subjectAltName=@alt_names
    
  1. 基于上面的配置文件生成证书签名请求:

    openssl req -new -key server.key -out server.csr -config csr.conf
    
  1. 基于 ca.key、ca.crt 和 server.csr 等三个文件生成服务端证书:

    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
        -CAcreateserial -out server.crt -days 10000 \
        -extensions v3_ext -extfile csr.conf -sha256
    
  1. 查看证书签名请求:

    openssl req  -noout -text -in ./server.csr
    
  1. 查看证书:

    openssl x509  -noout -text -in ./server.crt
    

最后,为 API 服务器添加相同的启动参数。

cfssl

cfssl 是另一个用于生成证书的工具。

  1. 下载、解压并准备如下所示的命令行工具。

    注意:你可能需要根据所用的硬件体系架构和 cfssl 版本调整示例命令。

    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl_1.5.0_linux_amd64 -o cfssl
    chmod +x cfssl
    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssljson_1.5.0_linux_amd64 -o cfssljson
    chmod +x cfssljson
    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl-certinfo_1.5.0_linux_amd64 -o cfssl-certinfo
    chmod +x cfssl-certinfo
    
  1. 创建一个目录,用它保存所生成的构件和初始化 cfssl:

    mkdir cert
    cd cert
    ../cfssl print-defaults config > config.json
    ../cfssl print-defaults csr > csr.json
    
  1. 创建一个 JSON 配置文件来生成 CA 文件,例如:ca-config.json

    {
      "signing": {
        "default": {
          "expiry": "8760h"
        },
        "profiles": {
          "kubernetes": {
            "usages": [
              "signing",
              "key encipherment",
              "server auth",
              "client auth"
            ],
            "expiry": "8760h"
          }
        }
      }
    }
    
  1. 创建一个 JSON 配置文件,用于 CA 证书签名请求(CSR),例如:ca-csr.json。 确认用你需要的值替换掉尖括号中的值。

    {
      "CN": "kubernetes",
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names":[{
        "C": "<country>",
        "ST": "<state>",
        "L": "<city>",
        "O": "<organization>",
        "OU": "<organization unit>"
      }]
    }
    
  1. 生成 CA 秘钥文件(ca-key.pem)和证书文件(ca.pem):

    ../cfssl gencert -initca ca-csr.json | ../cfssljson -bare ca
    
  1. 创建一个 JSON 配置文件,用来为 API 服务器生成秘钥和证书,例如:server-csr.json。 确认用你需要的值替换掉尖括号中的值。MASTER_CLUSTER_IP 是为 API 服务器 指定的服务集群 IP,就像前面小节描述的那样。 以下示例假定你的默认 DNS 域名为cluster.local

    {
      "CN": "kubernetes",
      "hosts": [
        "127.0.0.1",
        "<MASTER_IP>",
        "<MASTER_CLUSTER_IP>",
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster",
        "kubernetes.default.svc.cluster.local"
      ],
      "key": {
        "algo": "rsa",
        "size": 2048
      },
      "names": [{
        "C": "<country>",
        "ST": "<state>",
        "L": "<city>",
        "O": "<organization>",
        "OU": "<organization unit>"
      }]
    }
    
  1. 为 API 服务器生成秘钥和证书,默认会分别存储为server-key.pemserver.pem 两个文件。

    ../cfssl gencert -ca=ca.pem -ca-key=ca-key.pem \
         --config=ca-config.json -profile=kubernetes \
         server-csr.json | ../cfssljson -bare server
    

分发自签名的 CA 证书

客户端节点可能不认可自签名 CA 证书的有效性。 对于非生产环境,或者运行在公司防火墙后的环境,你可以分发自签名的 CA 证书到所有客户节点,并刷新本地列表以使证书生效。

在每一个客户节点,执行以下操作:

sudo cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt
sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....
done.

证书 API

你可以通过 certificates.k8s.io API 提供 x509 证书,用来做身份验证, 如管理集群中的 TLS 认证文档所述。

4 - 管理内存、CPU 和 API 资源

4.1 - 为命名空间配置默认的内存请求和限制

为命名空间定义默认的内存资源限制,这样在该命名空间中每个新建的 Pod 都会被配置上内存资源限制。

本章介绍如何为命名空间配置默认的内存请求和限制。

一个 Kubernetes 集群可被划分为多个命名空间。 如果你在具有默认内存限制 的命名空间内尝试创建一个 Pod,并且这个 Pod 中的容器没有声明自己的内存资源限制, 那么控制面会为该容器设定默认的内存限制。

Kubernetes 还为某些情况指定了默认的内存请求,本章后面会进行介绍。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

在你的集群里你必须要有创建命名空间的权限。

你的集群中的每个节点必须至少有 2 GiB 的内存。

创建命名空间

创建一个命名空间,以便本练习中所建的资源与集群的其余资源相隔离。

kubectl create namespace default-mem-example

创建 LimitRange 和 Pod

以下为 LimitRange 的示例清单。 清单中声明了默认的内存请求和默认的内存限制。

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
spec:
  limits:
  - default:
      memory: 512Mi
    defaultRequest:
      memory: 256Mi
    type: Container

在 default-mem-example 命名空间创建限制范围:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults.yaml --namespace=default-mem-example

现在如果你在 default-mem-example 命名空间中创建一个 Pod, 并且该 Pod 中所有容器都没有声明自己的内存请求和内存限制, 控制面 会将内存的默认请求值 256MiB 和默认限制值 512MiB 应用到 Pod 上。

以下为只包含一个容器的 Pod 的清单。该容器没有声明内存请求和限制。

apiVersion: v1
kind: Pod
metadata:
  name: default-mem-demo
spec:
  containers:
  - name: default-mem-demo-ctr
    image: nginx

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod.yaml --namespace=default-mem-example

查看 Pod 的详情:

kubectl get pod default-mem-demo --output=yaml --namespace=default-mem-example

输出内容显示该 Pod 的容器有 256 MiB 的内存请求和 512 MiB 的内存限制。 这些都是 LimitRange 设置的默认值。

containers:
- image: nginx
  imagePullPolicy: Always
  name: default-mem-demo-ctr
  resources:
    limits:
      memory: 512Mi
    requests:
      memory: 256Mi

删除你的 Pod:

kubectl delete pod default-mem-demo --namespace=default-mem-example

声明容器的限制而不声明它的请求会怎么样?

以下为只包含一个容器的 Pod 的清单。该容器声明了内存限制,而没有声明内存请求。

apiVersion: v1
kind: Pod
metadata:
  name: default-mem-demo-2
spec:
  containers:
  - name: default-mem-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "1Gi"

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod-2.yaml --namespace=default-mem-example

查看 Pod 的详情:

kubectl get pod default-mem-demo-2 --output=yaml --namespace=default-mem-example

输出结果显示容器的内存请求被设置为它的内存限制相同的值。注意该容器没有被指定默认的内存请求值 256MiB。

resources:
  limits:
    memory: 1Gi
  requests:
    memory: 1Gi

声明容器的内存请求而不声明内存限制会怎么样?

以下为只包含一个容器的 Pod 的清单。该容器声明了内存请求,但没有内存限制:

apiVersion: v1
kind: Pod
metadata:
  name: default-mem-demo-3
spec:
  containers:
  - name: default-mem-demo-3-ctr
    image: nginx
    resources:
      requests:
        memory: "128Mi"

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod-3.yaml --namespace=default-mem-example

查看 Pod 声明:

kubectl get pod default-mem-demo-3 --output=yaml --namespace=default-mem-example

输出结果显示所创建的 Pod 中,容器的内存请求为 Pod 清单中声明的值。 然而同一容器的内存限制被设置为 512MiB,此值是该命名空间的默认内存限制值。

resources:
  limits:
    memory: 512Mi
  requests:
    memory: 128Mi

设置默认内存限制和请求的动机

如果你的命名空间设置了内存 资源配额, 那么为内存限制设置一个默认值会很有帮助。 以下是内存资源配额对命名空间的施加的三条限制:

  • 命名空间中运行的每个 Pod 中的容器都必须有内存限制。 (如果为 Pod 中的每个容器声明了内存限制, Kubernetes 可以通过将其容器的内存限制相加推断出 Pod 级别的内存限制)。

  • 内存限制用来在 Pod 被调度到的节点上执行资源预留。 预留给命名空间中所有 Pod 使用的内存总量不能超过规定的限制。

  • 命名空间中所有 Pod 实际使用的内存总量也不能超过规定的限制。

当你添加 LimitRange 时:

如果该命名空间中的任何 Pod 的容器未指定内存限制, 控制面将默认内存限制应用于该容器, 这样 Pod 可以在受到内存 ResourceQuota 限制的命名空间中运行。

清理

删除你的命名空间:

kubectl delete namespace default-mem-example

接下来

集群管理员参考

应用开发者参考

4.2 - 为命名空间配置默认的 CPU 请求和限制

为命名空间定义默认的 CPU 资源限制,在该命名空间中每个新建的 Pod 都会被配置上 CPU 资源限制。

本章介绍如何为命名空间配置默认的 CPU 请求和限制。

一个 Kubernetes 集群可被划分为多个命名空间。 如果你在具有默认 CPU 限制 的命名空间内创建一个 Pod,并且这个 Pod 中任何容器都没有声明自己的 CPU 限制, 那么控制面会为容器设定默认的 CPU 限制。

Kubernetes 在一些特定情况还可以设置默认的 CPU 请求,本文后续章节将会对其进行解释。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

在你的集群里你必须要有创建命名空间的权限。

如果你还不熟悉 Kubernetes 中 1.0 CPU 的含义, 请阅读 CPU 的含义

创建命名空间

创建一个命名空间,以便本练习中创建的资源和集群的其余部分相隔离。

kubectl create namespace default-cpu-example

创建 LimitRange 和 Pod

以下为 LimitRange 的示例清单。 清单中声明了默认 CPU 请求和默认 CPU 限制。

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-limit-range
spec:
  limits:
  - default:
      cpu: 1
    defaultRequest:
      cpu: 0.5
    type: Container

在命名空间 default-cpu-example 中创建 LimitRange 对象:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults.yaml --namespace=default-cpu-example

现在如果你在 default-cpu-example 命名空间中创建一个 Pod, 并且该 Pod 中所有容器都没有声明自己的 CPU 请求和 CPU 限制, 控制面会将 CPU 的默认请求值 0.5 和默认限制值 1 应用到 Pod 上。

以下为只包含一个容器的 Pod 的清单。该容器没有声明 CPU 请求和限制。

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo
spec:
  containers:
  - name: default-cpu-demo-ctr
    image: nginx

创建 Pod。

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod.yaml --namespace=default-cpu-example

查看该 Pod 的声明:

kubectl get pod default-cpu-demo --output=yaml --namespace=default-cpu-example

输出显示该 Pod 的唯一的容器有 500m cpu 的 CPU 请求和 1 cpu 的 CPU 限制。 这些是 LimitRange 声明的默认值。

containers:
- image: nginx
  imagePullPolicy: Always
  name: default-cpu-demo-ctr
  resources:
    limits:
      cpu: "1"
    requests:
      cpu: 500m

你只声明容器的限制,而不声明请求会怎么样?

以下为只包含一个容器的 Pod 的清单。该容器声明了 CPU 限制,而没有声明 CPU 请求。

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo-2
spec:
  containers:
  - name: default-cpu-demo-2-ctr
    image: nginx
    resources:
      limits:
        cpu: "1"

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod-2.yaml --namespace=default-cpu-example

查看你所创建的 Pod 的规约

kubectl get pod default-cpu-demo-2 --output=yaml --namespace=default-cpu-example

输出显示该容器的 CPU 请求和 CPU 限制设置相同。注意该容器没有被指定默认的 CPU 请求值 0.5 cpu

resources:
  limits:
    cpu: "1"
  requests:
    cpu: "1"

你只声明容器的请求,而不声明它的限制会怎么样?

这里给出了包含一个容器的 Pod 的示例清单。该容器声明了 CPU 请求,而没有声明 CPU 限制。

apiVersion: v1
kind: Pod
metadata:
  name: default-cpu-demo-3
spec:
  containers:
  - name: default-cpu-demo-3-ctr
    image: nginx
    resources:
      requests:
        cpu: "0.75"

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod-3.yaml --namespace=default-cpu-example

查看你所创建的 Pod 的规约:

kubectl get pod default-cpu-demo-3 --output=yaml --namespace=default-cpu-example

输出显示你所创建的 Pod 中,容器的 CPU 请求为 Pod 清单中声明的值。 然而同一容器的 CPU 限制被设置为 1 cpu,此值是该命名空间的默认 CPU 限制值。

resources:
  limits:
    cpu: "1"
  requests:
    cpu: 750m

默认 CPU 限制和请求的动机

如果你的命名空间设置了 CPU 资源配额, 为 CPU 限制设置一个默认值会很有帮助。 以下是 CPU 资源配额对命名空间的施加的两条限制:

  • 命名空间中运行的每个 Pod 中的容器都必须有 CPU 限制。

  • CPU 限制用来在 Pod 被调度到的节点上执行资源预留。

预留给命名空间中所有 Pod 使用的 CPU 总量不能超过规定的限制。

当你添加 LimitRange 时:

如果该命名空间中的任何 Pod 的容器未指定 CPU 限制, 控制面将默认 CPU 限制应用于该容器, 这样 Pod 可以在受到 CPU ResourceQuota 限制的命名空间中运行。

清理

删除你的命名空间:

kubectl delete namespace default-cpu-example

接下来

集群管理员参考

应用开发者参考

4.3 - 配置命名空间的最小和最大内存约束

为命名空间定义一个有效的内存资源限制范围,在该命名空间中每个新创建 Pod 的内存资源是在设置的范围内。

本页介绍如何设置在名字空间 中运行的容器所使用的内存的最小值和最大值。你可以在 LimitRange 对象中指定最小和最大内存值。如果 Pod 不满足 LimitRange 施加的约束, 则无法在名字空间中创建它。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

在你的集群里你必须要有创建命名空间的权限。

集群中的每个节点都必须至少有 1 GiB 的内存可供 Pod 使用。

创建命名空间

创建一个命名空间,以便在此练习中创建的资源与集群的其余资源隔离。

kubectl create namespace constraints-mem-example

创建 LimitRange 和 Pod

下面是 LimitRange 的示例清单:

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-min-max-demo-lr
spec:
  limits:
  - max:
      memory: 1Gi
    min:
      memory: 500Mi
    type: Container

创建 LimitRange:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints.yaml --namespace=constraints-mem-example

查看 LimitRange 的详情:

kubectl get limitrange mem-min-max-demo-lr --namespace=constraints-mem-example --output=yaml

输出显示预期的最小和最大内存约束。 但请注意,即使你没有在 LimitRange 的配置文件中指定默认值,默认值也会自动生成。

  limits:
  - default:
      memory: 1Gi
    defaultRequest:
      memory: 1Gi
    max:
      memory: 1Gi
    min:
      memory: 500Mi
    type: Container

现在,每当在 constraints-mem-example 命名空间中创建 Pod 时,Kubernetes 就会执行下面的步骤:

  • 如果 Pod 中的任何容器未声明自己的内存请求和限制,控制面将为该容器设置默认的内存请求和限制。
  • 确保该 Pod 中的每个容器的内存请求至少 500 MiB。
  • 确保该 Pod 中每个容器内存请求不大于 1 GiB。

以下为包含一个容器的 Pod 清单。该容器声明了 600 MiB 的内存请求和 800 MiB 的内存限制, 这些满足了 LimitRange 施加的最小和最大内存约束。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo
spec:
  containers:
  - name: constraints-mem-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "800Mi"
      requests:
        memory: "600Mi"

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod.yaml --namespace=constraints-mem-example

确认 Pod 正在运行,并且其容器处于健康状态:

kubectl get pod constraints-mem-demo --namespace=constraints-mem-example

查看 Pod 详情:

kubectl get pod constraints-mem-demo --output=yaml --namespace=constraints-mem-example

输出结果显示该 Pod 的容器的内存请求为 600 MiB,内存限制为 800 MiB。 这些满足这个命名空间中 LimitRange 设定的限制范围。

resources:
  limits:
     memory: 800Mi
  requests:
    memory: 600Mi

删除你创建的 Pod:

kubectl delete pod constraints-mem-demo --namespace=constraints-mem-example

尝试创建一个超过最大内存限制的 Pod

以下为包含一个容器的 Pod 的清单。这个容器声明了 800 MiB 的内存请求和 1.5 GiB 的内存限制。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo-2
spec:
  containers:
  - name: constraints-mem-demo-2-ctr
    image: nginx
    resources:
      limits:
        memory: "1.5Gi"
      requests:
        memory: "800Mi"

尝试创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-2.yaml --namespace=constraints-mem-example

输出结果显示 Pod 没有创建成功,因为它定义了一个容器的内存请求超过了允许的值。

Error from server (Forbidden): error when creating "examples/admin/resource/memory-constraints-pod-2.yaml":
pods "constraints-mem-demo-2" is forbidden: maximum memory usage per Container is 1Gi, but limit is 1536Mi.

尝试创建一个不满足最小内存请求的 Pod

以下为只有一个容器的 Pod 的清单。这个容器声明了 100 MiB 的内存请求和 800 MiB 的内存限制。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo-3
spec:
  containers:
  - name: constraints-mem-demo-3-ctr
    image: nginx
    resources:
      limits:
        memory: "800Mi"
      requests:
        memory: "100Mi"

尝试创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-3.yaml --namespace=constraints-mem-example

输出结果显示 Pod 没有创建成功,因为它定义了一个容器的内存请求小于强制要求的最小值:

Error from server (Forbidden): error when creating "examples/admin/resource/memory-constraints-pod-3.yaml":
pods "constraints-mem-demo-3" is forbidden: minimum memory usage per Container is 500Mi, but request is 100Mi.

创建一个没有声明内存请求和限制的 Pod

以下为只有一个容器的 Pod 清单。该容器没有声明内存请求,也没有声明内存限制。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-mem-demo-4
spec:
  containers:
  - name: constraints-mem-demo-4-ctr
    image: nginx

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-4.yaml --namespace=constraints-mem-example

查看 Pod 详情:

kubectl get pod constraints-mem-demo-4 --namespace=constraints-mem-example --output=yaml

输出结果显示 Pod 的唯一容器内存请求为 1 GiB,内存限制为 1 GiB。容器怎样获得那些数值呢?

resources:
  limits:
    memory: 1Gi
  requests:
    memory: 1Gi

因为你的 Pod 没有为容器声明任何内存请求和限制,集群会从 LimitRange 获取默认的内存请求和限制。 应用于容器。

这意味着 Pod 的定义会显示这些值。你可以通过 kubectl describe 查看:

# 查看输出结果中的 "Requests:" 的值
kubectl describe pod constraints-mem-demo-4 --namespace=constraints-mem-example

此时,你的 Pod 可能已经运行起来也可能没有运行起来。 回想一下我们本次任务的先决条件是你的每个节点都至少有 1 GiB 的内存。 如果你的每个节点都只有 1 GiB 的内存,那将没有一个节点拥有足够的可分配内存来满足 1 GiB 的内存请求。

删除你的 Pod:

kubectl delete pod constraints-mem-demo-4 --namespace=constraints-mem-example

强制执行内存最小和最大限制

LimitRange 为命名空间设定的最小和最大内存限制只有在 Pod 创建和更新时才会强制执行。 如果你更新 LimitRange,它不会影响此前创建的 Pod。

设置内存最小和最大限制的动因

作为集群管理员,你可能想规定 Pod 可以使用的内存总量限制。例如:

  • 集群的每个节点有 2 GiB 内存。你不想接受任何请求超过 2 GiB 的 Pod,因为集群中没有节点可以满足。
  • 集群由生产部门和开发部门共享。你希望允许产品部门的负载最多耗用 8 GiB 内存, 但是开发部门的负载最多可使用 512 MiB。 这时,你可以为产品部门和开发部门分别创建名字空间,并为各个名字空间设置内存约束。

清理

删除你的命名空间:

kubectl delete namespace constraints-mem-example

接下来

集群管理员参考

应用开发者参考

4.4 - 为命名空间配置 CPU 最小和最大约束

为命名空间定义一个有效的 CPU 资源限制范围,使得在该命名空间中所有新建 Pod 的 CPU 资源是在你所设置的范围内。

本页介绍如何为命名空间中的容器和 Pod 设置其所使用的 CPU 资源的最小和最大值。你可以通过 LimitRange 对象声明 CPU 的最小和最大值. 如果 Pod 不能满足 LimitRange 的限制,就无法在该命名空间中被创建。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

在你的集群里你必须要有创建命名空间的权限。

集群中的每个节点都必须至少有 1.0 个 CPU 可供 Pod 使用。

请阅读 CPU 的含义 理解 "1 CPU" 在 Kubernetes 中的含义。

创建命名空间

创建一个命名空间,以便本练习中创建的资源和集群的其余资源相隔离。

kubectl create namespace constraints-cpu-example

创建 LimitRange 和 Pod

以下为 LimitRange 的示例清单:

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-min-max-demo-lr
spec:
  limits:
  - max:
      cpu: "800m"
    min:
      cpu: "200m"
    type: Container

创建 LimitRange:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints.yaml --namespace=constraints-cpu-example

查看 LimitRange 详情:

kubectl get limitrange cpu-min-max-demo-lr --output=yaml --namespace=constraints-cpu-example

输出结果显示 CPU 的最小和最大限制符合预期。但需要注意的是,尽管你在 LimitRange 的配置文件中你没有声明默认值,默认值也会被自动创建。

limits:
- default:
    cpu: 800m
  defaultRequest:
    cpu: 800m
  max:
    cpu: 800m
  min:
    cpu: 200m
  type: Container

现在,每当你在 constraints-cpu-example 命名空间中创建 Pod 时,或者某些其他的 Kubernetes API 客户端创建了等价的 Pod 时,Kubernetes 就会执行下面的步骤:

  • 如果 Pod 中的任何容器未声明自己的 CPU 请求和限制,控制面将为该容器设置默认的 CPU 请求和限制。

  • 确保该 Pod 中的每个容器的 CPU 请求至少 200 millicpu。

  • 确保该 Pod 中每个容器 CPU 请求不大于 800 millicpu。

以下为某个仅包含一个容器的 Pod 的清单。 该容器声明了 CPU 请求 500 millicpu 和 CPU 限制 800 millicpu。 这些参数满足了 LimitRange 对象为此名字空间规定的 CPU 最小和最大限制。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo
spec:
  containers:
  - name: constraints-cpu-demo-ctr
    image: nginx
    resources:
      limits:
        cpu: "800m"
      requests:
        cpu: "500m"

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod.yaml --namespace=constraints-cpu-example

确认 Pod 正在运行,并且其容器处于健康状态:

kubectl get pod constraints-cpu-demo --namespace=constraints-cpu-example

查看 Pod 的详情:

kubectl get pod constraints-cpu-demo --output=yaml --namespace=constraints-cpu-example

输出结果显示该 Pod 的容器的 CPU 请求为 500 millicpu,CPU 限制为 800 millicpu。 这些参数满足 LimitRange 规定的限制范围。

resources:
  limits:
    cpu: 800m
  requests:
    cpu: 500m

删除 Pod

kubectl delete pod constraints-cpu-demo --namespace=constraints-cpu-example

尝试创建一个超过最大 CPU 限制的 Pod

这里给出了包含一个容器的 Pod 清单。容器声明了 500 millicpu 的 CPU 请求和 1.5 CPU 的 CPU 限制。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo-2
spec:
  containers:
  - name: constraints-cpu-demo-2-ctr
    image: nginx
    resources:
      limits:
        cpu: "1.5"
      requests:
        cpu: "500m"

尝试创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-2.yaml --namespace=constraints-cpu-example

输出结果表明 Pod 没有创建成功,因为其中定义了一个无法被接受的容器。 该容器之所以无法被接受是因为其中设定了过高的 CPU 限制值:

Error from server (Forbidden): error when creating "examples/admin/resource/cpu-constraints-pod-2.yaml":
pods "constraints-cpu-demo-2" is forbidden: maximum cpu usage per Container is 800m, but limit is 1500m.

尝试创建一个不满足最小 CPU 请求的 Pod

以下为某个只有一个容器的 Pod 的清单。该容器声明了 CPU 请求 100 millicpu 和 CPU 限制 800 millicpu。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo-3
spec:
  containers:
  - name: constraints-cpu-demo-3-ctr
    image: nginx
    resources:
      limits:
        cpu: "800m"
      requests:
        cpu: "100m"

尝试创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-3.yaml --namespace=constraints-cpu-example

输出结果显示 Pod 没有创建成功,因为其中定义了一个无法被接受的容器。 该容器无法被接受的原因是其中所设置的 CPU 请求小于最小值的限制:

Error from server (Forbidden): error when creating "examples/admin/resource/cpu-constraints-pod-3.yaml":
pods "constraints-cpu-demo-3" is forbidden: minimum cpu usage per Container is 200m, but request is 100m.

创建一个没有声明 CPU 请求和 CPU 限制的 Pod

以下为一个只有一个容器的 Pod 的清单。该容器没有声明 CPU 请求,也没有声明 CPU 限制。

apiVersion: v1
kind: Pod
metadata:
  name: constraints-cpu-demo-4
spec:
  containers:
  - name: constraints-cpu-demo-4-ctr
    image: vish/stress

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-4.yaml --namespace=constraints-cpu-example

查看 Pod 的详情:

kubectl get pod constraints-cpu-demo-4 --namespace=constraints-cpu-example --output=yaml

输出结果显示 Pod 的唯一容器的 CPU 请求为 800 millicpu,CPU 限制为 800 millicpu。

容器是怎样获得这些数值的呢?

resources:
  limits:
    cpu: 800m
  requests:
    cpu: 800m

因为这一容器没有声明自己的 CPU 请求和限制, 控制面会根据命名空间中配置 LimitRange 设置默认的 CPU 请求和限制

此时,你的 Pod 可能已经运行起来也可能没有运行起来。 回想一下我们本次任务的先决条件是你的每个节点都至少有 1 CPU。 如果你的每个节点都只有 1 CPU,那将没有一个节点拥有足够的可分配 CPU 来满足 800 millicpu 的请求。 如果你在用的节点恰好有 2 CPU,那么有可能有足够的 CPU 来满足 800 millicpu 的请求。

删除你的 Pod:

kubectl delete pod constraints-cpu-demo-4 --namespace=constraints-cpu-example

CPU 最小和最大限制的强制执行

只有当 Pod 创建或者更新时,LimitRange 为命名空间规定的 CPU 最小和最大限制才会被强制执行。 如果你对 LimitRange 进行修改,那不会影响此前创建的 Pod。

最小和最大 CPU 限制范围的动机

作为集群管理员,你可能想设定 Pod 可以使用的 CPU 资源限制。例如:

  • 集群中的每个节点有两个 CPU。你不想接受任何请求超过 2 个 CPU 的 Pod, 因为集群中没有节点可以支持这种请求。
  • 你的生产和开发部门共享一个集群。你想允许生产工作负载消耗 3 个 CPU, 而开发部门工作负载的消耗限制为 1 个 CPU。 你可以为生产和开发创建不同的命名空间,并且为每个命名空间都应用 CPU 限制。

清理

删除你的命名空间:

kubectl delete namespace constraints-cpu-example

接下来

集群管理员参考

应用开发者参考

4.5 - 为命名空间配置内存和 CPU 配额

为命名空间定义总的 CPU 和内存资源限制。

本文介绍如何为命名空间下运行的所有 Pod 设置总的内存和 CPU 配额。你可以通过使用 ResourceQuota 对象设置配额.

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

在你的集群里你必须要有创建命名空间的权限。

集群中每个节点至少有 1 GiB 的内存。

创建命名空间

创建一个命名空间,以便本练习中创建的资源和集群的其余部分相隔离。

kubectl create namespace quota-mem-cpu-example

创建 ResourceQuota

下面是 ResourceQuota 的示例清单:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-demo
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi

创建 ResourceQuota:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu.yaml --namespace=quota-mem-cpu-example

查看 ResourceQuota 详情:

kubectl get resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example --output=yaml

ResourceQuota 在 quota-mem-cpu-example 命名空间中设置了如下要求:

  • 在该命名空间中的每个 Pod 的所有容器都必须要有内存请求和限制,以及 CPU 请求和限制。
  • 在该命名空间中所有 Pod 的内存请求总和不能超过 1 GiB。
  • 在该命名空间中所有 Pod 的内存限制总和不能超过 2 GiB。
  • 在该命名空间中所有 Pod 的 CPU 请求总和不能超过 1 cpu。
  • 在该命名空间中所有 Pod 的 CPU 限制总和不能超过 2 cpu。

请阅读 CPU 的含义 理解 "1 CPU" 在 Kubernetes 中的含义。

创建 Pod

以下是 Pod 的示例清单:

apiVersion: v1
kind: Pod
metadata:
  name: quota-mem-cpu-demo
spec:
  containers:
  - name: quota-mem-cpu-demo-ctr
    image: nginx
    resources:
      limits:
        memory: "800Mi"
        cpu: "800m"
      requests:
        memory: "600Mi"
        cpu: "400m"

创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu-pod.yaml --namespace=quota-mem-cpu-example

确认 Pod 正在运行,并且其容器处于健康状态:

kubectl get pod quota-mem-cpu-demo --namespace=quota-mem-cpu-example

再查看 ResourceQuota 的详情:

kubectl get resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example --output=yaml

输出结果显示了配额以及有多少配额已经被使用。你可以看到 Pod 的内存和 CPU 请求值及限制值没有超过配额。

status:
  hard:
    limits.cpu: "2"
    limits.memory: 2Gi
    requests.cpu: "1"
    requests.memory: 1Gi
  used:
    limits.cpu: 800m
    limits.memory: 800Mi
    requests.cpu: 400m
    requests.memory: 600Mi

如果有 jq 工具的话,你可以通过(使用 JSONPath) 直接查询 used 字段的值,并且输出整齐的 JSON 格式。

kubectl get resourcequota mem-cpu-demo --namespace=quota-mem-cpu-example -o jsonpath='{ .status.used }' | jq .

尝试创建第二个 Pod

以下为第二个 Pod 的清单:

apiVersion: v1
kind: Pod
metadata:
  name: quota-mem-cpu-demo-2
spec:
  containers:
  - name: quota-mem-cpu-demo-2-ctr
    image: redis
    resources:
      limits:
        memory: "1Gi"
        cpu: "800m"
      requests:
        memory: "700Mi"
        cpu: "400m"

在清单中,你可以看到 Pod 的内存请求为 700 MiB。 请注意新的内存请求与已经使用的内存请求之和超过了内存请求的配额: 600 MiB + 700 MiB > 1 GiB。

尝试创建 Pod:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu-pod-2.yaml --namespace=quota-mem-cpu-example

第二个 Pod 不能被创建成功。输出结果显示创建第二个 Pod 会导致内存请求总量超过内存请求配额。

Error from server (Forbidden): error when creating "examples/admin/resource/quota-mem-cpu-pod-2.yaml":
pods "quota-mem-cpu-demo-2" is forbidden: exceeded quota: mem-cpu-demo,
requested: requests.memory=700Mi,used: requests.memory=600Mi, limited: requests.memory=1Gi

讨论

如你在本练习中所见,你可以用 ResourceQuota 限制命名空间中所有 Pod 的内存请求总量。 同样你也可以限制内存限制总量、CPU 请求总量、CPU 限制总量。

除了可以管理命名空间资源使用的总和,如果你想限制单个 Pod,或者限制这些 Pod 中的容器资源, 可以使用 LimitRange 实现这类的功能。

清理

删除你的命名空间:

kubectl delete namespace quota-mem-cpu-example

接下来

集群管理员参考

应用开发者参考

4.6 - 配置命名空间下 Pod 配额

限制在命名空间中创建的 Pod 数量。

本文主要介绍如何在命名空间中设置可运行 Pod 总数的配额。 你可以通过使用 ResourceQuota 对象来配置配额。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

在你的集群里你必须要有创建命名空间的权限。

创建一个命名空间

首先创建一个命名空间,这样可以将本次操作中创建的资源与集群其他资源隔离开来。

kubectl create namespace quota-pod-example

创建 ResourceQuota

下面是 ResourceQuota 的示例清单:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: pod-demo
spec:
  hard:
    pods: "2"

创建 ResourceQuota:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-pod.yaml --namespace=quota-pod-example

查看资源配额的详细信息:

kubectl get resourcequota pod-demo --namespace=quota-pod-example --output=yaml

从输出的信息我们可以看到,该命名空间下 Pod 的配额是 2 个,目前创建的 Pod 数为 0, 配额使用率为 0。

spec:
  hard:
    pods: "2"
status:
  hard:
    pods: "2"
  used:
    pods: "0"

下面是一个 Deployment 的示例清单:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-quota-demo
spec:
  selector:
    matchLabels:
      purpose: quota-demo
  replicas: 3
  template:
    metadata:
      labels:
        purpose: quota-demo
    spec:
      containers:
      - name: pod-quota-demo
        image: nginx

在清单中,replicas: 3 告诉 Kubernetes 尝试创建三个新的 Pod, 且运行相同的应用。

创建这个 Deployment:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-pod-deployment.yaml --namespace=quota-pod-example

查看 Deployment 的详细信息:

kubectl get deployment pod-quota-demo --namespace=quota-pod-example --output=yaml

从输出的信息显示,即使 Deployment 指定了三个副本, 也只有两个 Pod 被创建,原因是之前已经定义了配额:

spec:
  ...
  replicas: 3
...
status:
  availableReplicas: 2
...
lastUpdateTime: 2021-04-02T20:57:05Z
    message: 'unable to create pods: pods "pod-quota-demo-1650323038-" is forbidden:
      exceeded quota: pod-demo, requested: pods=1, used: pods=2, limited: pods=2'

资源的选择

在此任务中,你定义了一个限制 Pod 总数的 ResourceQuota, 你也可以限制其他类型对象的总数。 例如,你可以限制在一个命名空间中可以创建的 CronJob 的数量。

清理

删除你的命名空间:

kubectl delete namespace quota-pod-example

接下来

集群管理人员参考

应用开发人员参考

5 - 安装网络策略驱动

5.1 - 使用 Antrea 提供 NetworkPolicy

本页展示了如何在 kubernetes 中安装和使用 Antrea CNI 插件。 要了解 Antrea 项目的背景,请阅读 Antrea 介绍

准备开始

你需要拥有一个 kuernetes 集群。 遵循 kubeadm 入门指南自行创建一个。

使用 kubeadm 部署 Antrea

遵循入门指南 为 kubeadm 部署 Antrea 。

接下来

一旦你的集群已经运行,你可以遵循 声明网络策略 来尝试 Kubernetes NetworkPolicy。

5.2 - 使用 Calico 提供 NetworkPolicy

本页展示了几种在 Kubernetes 上快速创建 Calico 集群的方法。

准备开始

确定你想部署一个云版本还是本地版本的集群。

在 Google Kubernetes Engine (GKE) 上创建一个 Calico 集群

先决条件gcloud

  1. 启动一个带有 Calico 的 GKE 集群,需要加上参数 --enable-network-policy

    语法

    gcloud container clusters create [CLUSTER_NAME] --enable-network-policy
    

    示例

    gcloud container clusters create my-calico-cluster --enable-network-policy
    
  1. 使用如下命令验证部署是否正确。

    kubectl get pods --namespace=kube-system
    

    Calico 的 Pod 名以 calico 打头,检查确认每个 Pod 状态为 Running

使用 kubeadm 创建一个本地 Calico 集群

使用 kubeadm 在 15 分钟内得到一个本地单主机 Calico 集群,请参考 Calico 快速入门

接下来

集群运行后, 你可以按照声明网络策略去尝试使用 Kubernetes NetworkPolicy。

5.3 - 使用 Cilium 提供 NetworkPolicy

本页展示如何使用 Cilium 提供 NetworkPolicy。

关于 Cilium 的背景知识,请阅读 Cilium 介绍

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

在 Minikube 上部署 Cilium 用于基本测试

为了轻松熟悉 Cilium,你可以根据 Cilium Kubernetes 入门指南 在 minikube 中执行一个 Cilium 的基本 DaemonSet 安装。

要启动 minikube,需要的最低版本为 1.5.2,使用下面的参数运行:

minikube version
minikube version: v1.5.2
minikube start --network-plugin=cni

对于 minikube 你可以使用 Cilium 的 CLI 工具安装它。 为此,先用以下命令下载最新版本的 CLI:

curl -LO https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz

然后用以下命令将下载的文件解压缩到你的 /usr/local/bin 目录:

sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz

运行上述命令后,你现在可以用以下命令安装 Cilium:

cilium install

随后 Cilium 将自动检测集群配置,并创建和安装合适的组件以成功完成安装。 这些组件为:

  • Secret cilium-ca 中的证书机构 (CA) 和 Hubble(Cilium 的可观测层)所用的证书。
  • 服务账号。
  • 集群角色。
  • ConfigMap。
  • Agent DaemonSet 和 Operator Deployment。

安装之后,你可以用 cilium status 命令查看 Cilium Deployment 的整体状态。 在此处查看 status 命令的预期输出。

入门指南其余的部分用一个示例应用说明了如何强制执行 L3/L4(即 IP 地址 + 端口)的安全策略以及 L7 (如 HTTP)的安全策略。

部署 Cilium 用于生产用途

关于部署 Cilium 用于生产的详细说明,请参见 Cilium Kubernetes 安装指南。 此文档包括详细的需求、说明和生产用途 DaemonSet 文件示例。

了解 Cilium 组件

部署使用 Cilium 的集群会添加 Pod 到 kube-system 命名空间。要查看 Pod 列表,运行:

kubectl get pods --namespace=kube-system -l k8s-app=cilium

你将看到像这样的 Pod 列表:

NAME           READY   STATUS    RESTARTS   AGE
cilium-kkdhz   1/1     Running   0          3m23s
...

你的集群中的每个节点上都会运行一个 cilium Pod,通过使用 Linux BPF 针对该节点上的 Pod 的入站、出站流量实施网络策略控制。

接下来

集群运行后, 你可以按照声明网络策略试用基于 Cilium 的 Kubernetes NetworkPolicy。玩得开心,如果你有任何疑问,请到 Cilium Slack 频道联系我们。

5.4 - 使用 kube-router 提供 NetworkPolicy

本页展示如何使用 Kube-router 提供 NetworkPolicy。

准备开始

你需要拥有一个运行中的 Kubernetes 集群。如果你还没有集群,可以使用任意的集群 安装程序如 Kops、Bootkube、Kubeadm 等创建一个。

安装 kube-router 插件

kube-router 插件自带一个网络策略控制器,监视来自于 Kubernetes API 服务器的 NetworkPolicy 和 Pod 的变化,根据策略指示配置 iptables 规则和 ipsets 来允许或阻止流量。 请根据 通过集群安装程序尝试 kube-router 指南安装 kube-router 插件。

接下来

在你安装了 kube-router 插件后,可以参考 声明网络策略 去尝试使用 Kubernetes NetworkPolicy。

5.5 - 使用 Romana 提供 NetworkPolicy

本页展示如何使用 Romana 作为 NetworkPolicy。

准备开始

完成 kubeadm 入门指南中的 1、2、3 步。

使用 kubeadm 安装 Romana

按照容器化安装指南, 使用 kubeadm 安装。

应用网络策略

使用以下的一种方式应用网络策略:

接下来

Romana 安装完成后,你可以按照 声明网络策略 去尝试使用 Kubernetes NetworkPolicy。

5.6 - 使用 Weave Net 提供 NetworkPolicy

本页展示如何使用 Weave Net 提供 NetworkPolicy。

准备开始

你需要拥有一个 Kubernetes 集群。按照 kubeadm 入门指南 来启动一个。

安装 Weave Net 插件

按照通过插件集成 Kubernetes 指南执行安装。

Kubernetes 的 Weave Net 插件带有 网络策略控制器, 可自动监控 Kubernetes 所有名字空间的 NetworkPolicy 注释, 配置 iptables 规则以允许或阻止策略指示的流量。

测试安装

验证 weave 是否有效。

输入以下命令:

kubectl get pods -n kube-system -o wide

输出类似这样:

NAME                                    READY     STATUS    RESTARTS   AGE       IP              NODE
weave-net-1t1qg                         2/2       Running   0          9d        192.168.2.10    worknode3
weave-net-231d7                         2/2       Running   1          7d        10.2.0.17       worknodegpu
weave-net-7nmwt                         2/2       Running   3          9d        192.168.2.131   masternode
weave-net-pmw8w                         2/2       Running   0          9d        192.168.2.216   worknode2

每个 Node 都有一个 weave Pod,所有 Pod 都是 Running2/2 READY。 (2/2 表示每个 Pod 都有 weaveweave-npc。)

接下来

安装 Weave Net 插件后,你可以参考 声明网络策略 来试用 Kubernetes NetworkPolicy。 如果你有任何疑问,请通过 Slack 上的 #weave-community 频道或者 Weave 用户组 联系我们。

6 - 使用 Kubernetes API 访问集群

本页展示了如何使用 Kubernetes API 访问集群。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

访问 Kubernetes API

使用 kubectl 进行首次访问

首次访问 Kubernetes API 时,请使用 Kubernetes 命令行工具 kubectl

要访问集群,你需要知道集群位置并拥有访问它的凭证。 通常,当你完成入门指南时,这会自动设置完成,或者由其他人设置好集群并将凭证和位置提供给你。

使用此命令检查 kubectl 已知的位置和凭证:

kubectl config view

许多样例 提供了使用 kubectl 的介绍。完整文档请见 kubectl 手册

直接访问 REST API

kubectl 处理对 API 服务器的定位和身份验证。如果你想通过 http 客户端(如 curlwget 或浏览器)直接访问 REST API,你可以通过多种方式对 API 服务器进行定位和身份验证:

  1. 以代理模式运行 kubectl(推荐)。 推荐使用此方法,因为它用存储的 apiserver 位置并使用自签名证书验证 API 服务器的标识。 使用这种方法无法进行中间人(MITM)攻击。
  2. 另外,你可以直接为 HTTP 客户端提供位置和身份认证。 这适用于被代理混淆的客户端代码。 为防止中间人攻击,你需要将根证书导入浏览器。

使用 Go 或 Python 客户端库可以在代理模式下访问 kubectl。

使用 kubectl 代理

下列命令使 kubectl 运行在反向代理模式下。它处理 API 服务器的定位和身份认证。

像这样运行它:

kubectl proxy --port=8080 &

参见 kubectl 代理 获取更多细节。

然后你可以通过 curl,wget,或浏览器浏览 API,像这样:

curl http://localhost:8080/api/

输出类似如下:

{
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.0.1.149:443"
    }
  ]
}

不使用 kubectl 代理

通过将身份认证令牌直接传给 API 服务器,可以避免使用 kubectl 代理,像这样:

使用 grep/cut 方式:

# 查看所有的集群,因为你的 .kubeconfig 文件中可能包含多个上下文
kubectl config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'

# 从上述命令输出中选择你要与之交互的集群的名称
export CLUSTER_NAME="some_server_name"

# 指向引用该集群名称的 API 服务器
APISERVER=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$CLUSTER_NAME\")].cluster.server}")

# 创建一个 secret 来保存默认服务账户的令牌
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: default-token
  annotations:
    kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token
EOF

# 等待令牌控制器使用令牌填充 secret:
while ! kubectl describe secret default-token | grep -E '^token' >/dev/null; do
  echo "waiting for token..." >&2
  sleep 1
done

# 获取令牌
TOKEN=$(kubectl get secret default-token -o jsonpath='{.data.token}' | base64 --decode)

# 使用令牌玩转 API
curl -X GET $APISERVER/api --header "Authorization: Bearer $TOKEN" --insecure

输出类似如下:

{
  "kind": "APIVersions",
  "versions": [
    "v1"
  ],
  "serverAddressByClientCIDRs": [
    {
      "clientCIDR": "0.0.0.0/0",
      "serverAddress": "10.0.1.149:443"
    }
  ]
}

上面例子使用了 --insecure 标志位。这使它易受到 MITM 攻击。 当 kubectl 访问集群时,它使用存储的根证书和客户端证书访问服务器。 (已安装在 ~/.kube 目录下)。 由于集群认证通常是自签名的,因此可能需要特殊设置才能让你的 http 客户端使用根证书。

在一些集群中,API 服务器不需要身份认证;它运行在本地,或由防火墙保护着。 对此并没有一个标准。 配置对 API 的访问 讲解了作为集群管理员可如何对此进行配置。

编程方式访问 API

Kubernetes 官方支持 GoPythonJavadotnetJavaScriptHaskell 语言的客户端库。还有一些其他客户端库由对应作者而非 Kubernetes 团队提供并维护。 参考客户端库了解如何使用其他语言来访问 API 以及如何执行身份认证。

Go 客户端

Go 客户端可以使用与 kubectl 命令行工具相同的 kubeconfig 文件 定位和验证 API 服务器。参见这个 例子

package main

import (
  "context"
  "fmt"
  "k8s.io/apimachinery/pkg/apis/meta/v1"
  "k8s.io/client-go/kubernetes"
  "k8s.io/client-go/tools/clientcmd"
)

func main() {
  // 在 kubeconfig 中使用当前上下文
  // path-to-kubeconfig -- 例如 /root/.kube/config
  config, _ := clientcmd.BuildConfigFromFlags("", "<path-to-kubeconfig>")
  // 创建 clientset
  clientset, _ := kubernetes.NewForConfig(config)
  // 访问 API 以列出 Pod
  pods, _ := clientset.CoreV1().Pods("").List(context.TODO(), v1.ListOptions{})
  fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))
}

如果该应用程序部署为集群中的一个 Pod,请参阅从 Pod 内访问 API

Python 客户端

要使用 Python 客户端,运行下列命令: pip install kubernetes。 参见 Python 客户端库主页了解更多安装选项。

Python 客户端可以使用与 kubectl 命令行工具相同的 kubeconfig 文件 定位和验证 API 服务器。参见这个 例子

from kubernetes import client, config

config.load_kube_config()

v1=client.CoreV1Api()
print("Listing pods with their IPs:")
ret = v1.list_pod_for_all_namespaces(watch=False)
for i in ret.items:
    print("%s\t%s\t%s" % (i.status.pod_ip, i.metadata.namespace, i.metadata.name))

Java 客户端

要安装 Java 客户端,运行:

# 克隆 Java 库
git clone --recursive https://github.com/kubernetes-client/java

# 安装项目文件、POM 等
cd java
mvn install

参阅 https://github.com/kubernetes-client/java/releases 了解当前支持的版本。

Java 客户端可以使用 kubectl 命令行所使用的 kubeconfig 文件 以定位 API 服务器并向其认证身份。 参看此示例

package io.kubernetes.client.examples;

import io.kubernetes.client.ApiClient;
import io.kubernetes.client.ApiException;
import io.kubernetes.client.Configuration;
import io.kubernetes.client.apis.CoreV1Api;
import io.kubernetes.client.models.V1Pod;
import io.kubernetes.client.models.V1PodList;
import io.kubernetes.client.util.ClientBuilder;
import io.kubernetes.client.util.KubeConfig;
import java.io.FileReader;
import java.io.IOException;

/**
 * A simple example of how to use the Java API from an application outside a kubernetes cluster
 *
 * <p>Easiest way to run this: mvn exec:java
 * -Dexec.mainClass="io.kubernetes.client.examples.KubeConfigFileClientExample"
 *
 */
public class KubeConfigFileClientExample {
  public static void main(String[] args) throws IOException, ApiException {

    // file path to your KubeConfig
    String kubeConfigPath = "~/.kube/config";

    // loading the out-of-cluster config, a kubeconfig from file-system
    ApiClient client =
        ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath))).build();

    // set the global default api-client to the in-cluster one from above
    Configuration.setDefaultApiClient(client);

    // the CoreV1Api loads default api-client from global configuration.
    CoreV1Api api = new CoreV1Api();

    // invokes the CoreV1Api client
    V1PodList list = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
    System.out.println("Listing all pods: ");
    for (V1Pod item : list.getItems()) {
      System.out.println(item.getMetadata().getName());
    }
  }
}

.Net 客户端

要使用 .Net 客户端,运行下面的命令: dotnet add package KubernetesClient --version 1.6.1。 参见 .Net 客户端库页面了解更多安装选项。 关于可支持的版本,参见https://github.com/kubernetes-client/csharp/releases

.Net 客户端可以使用与 kubectl CLI 相同的 kubeconfig 文件来定位并验证 API 服务器。 参见样例

using System;
using k8s;

namespace simple
{
    internal class PodList
    {
        private static void Main(string[] args)
        {
            var config = KubernetesClientConfiguration.BuildDefaultConfig();
            IKubernetes client = new Kubernetes(config);
            Console.WriteLine("Starting Request!");

            var list = client.ListNamespacedPod("default");
            foreach (var item in list.Items)
            {
                Console.WriteLine(item.Metadata.Name);
            }
            if (list.Items.Count == 0)
            {
                Console.WriteLine("Empty!");
            }
        }
    }
}

JavaScript 客户端

要安装 JavaScript 客户端,运行下面的命令: npm install @kubernetes/client-node。 参考https://github.com/kubernetes-client/javascript/releases了解可支持的版本。

JavaScript 客户端可以使用 kubectl 命令行所使用的 kubeconfig 文件 以定位 API 服务器并向其认证身份。 参见此例

const k8s = require('@kubernetes/client-node');

const kc = new k8s.KubeConfig();
kc.loadFromDefault();

const k8sApi = kc.makeApiClient(k8s.CoreV1Api);

k8sApi.listNamespacedPod('default').then((res) => {
    console.log(res.body);
});

Haskell 客户端

参考 https://github.com/kubernetes-client/haskell/releases 了解支持的版本。

Haskell 客户端 可以使用 kubectl 命令行所使用的 kubeconfig 文件 以定位 API 服务器并向其认证身份。 参见此例

exampleWithKubeConfig :: IO ()
exampleWithKubeConfig = do
    oidcCache <- atomically $ newTVar $ Map.fromList []
    (mgr, kcfg) <- mkKubeClientConfig oidcCache $ KubeConfigFile "/path/to/kubeconfig"
    dispatchMime
            mgr
            kcfg
            (CoreV1.listPodForAllNamespaces (Accept MimeJSON))
        >>= print

接下来

7 - 为节点发布扩展资源

本文展示了如何为节点指定扩展资源(Extended Resource)。 扩展资源允许集群管理员发布节点级别的资源,这些资源在不进行发布的情况下无法被 Kubernetes 感知。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

获取你的节点名称

kubectl get nodes

选择一个节点用于此练习。

在你的一个节点上发布一种新的扩展资源

为在一个节点上发布一种新的扩展资源,需要发送一个 HTTP PATCH 请求到 Kubernetes API server。 例如:假设你的一个节点上带有四个 dongle 资源。 下面是一个 PATCH 请求的示例,该请求为你的节点发布四个 dongle 资源。

PATCH /api/v1/nodes/<your-node-name>/status HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json
Host: k8s-master:8080

[
  {
    "op": "add",
    "path": "/status/capacity/example.com~1dongle",
    "value": "4"
  }
]

注意:Kubernetes 不需要了解 dongle 资源的含义和用途。 前面的 PATCH 请求告诉 Kubernetes 你的节点拥有四个你称之为 dongle 的东西。

启动一个代理(proxy),以便你可以很容易地向 Kubernetes API server 发送请求:

kubectl proxy

在另一个命令窗口中,发送 HTTP PATCH 请求。 用你的节点名称替换 <your-node-name>

curl --header "Content-Type: application/json-patch+json" \
  --request PATCH \
  --data '[{"op": "add", "path": "/status/capacity/example.com~1dongle", "value": "4"}]' \
  http://localhost:8001/api/v1/nodes/<your-node-name>/status

输出显示该节点的 dongle 资源容量(capacity)为 4:

"capacity": {
  "cpu": "2",
  "memory": "2049008Ki",
  "example.com/dongle": "4",

描述你的节点:

kubectl describe node <your-node-name>

输出再次展示了 dongle 资源:

Capacity:
  cpu: 2
  memory: 2049008Ki
  example.com/dongle: 4

现在,应用开发者可以创建请求一定数量 dongle 资源的 Pod 了。 参见将扩展资源分配给容器

讨论

扩展资源类似于内存和 CPU 资源。例如,正如一个节点拥有一定数量的内存和 CPU 资源, 它们被节点上运行的所有组件共享,该节点也可以拥有一定数量的 dongle 资源, 这些资源同样被节点上运行的所有组件共享。 此外,正如应用开发者可以创建请求一定数量的内存和 CPU 资源的 Pod, 他们也可以创建请求一定数量 dongle 资源的 Pod。

扩展资源对 Kubernetes 是不透明的。Kubernetes 不知道扩展资源含义相关的任何信息。 Kubernetes 只了解一个节点拥有一定数量的扩展资源。 扩展资源必须以整形数量进行发布。 例如,一个节点可以发布 4 个 dongle 资源,但是不能发布 4.5 个。

存储示例

假设一个节点拥有一种特殊类型的磁盘存储,其容量为 800 GiB。 你可以为该特殊存储创建一个名称,如 example.com/special-storage。 然后你就可以按照一定规格的块(如 100 GiB)对其进行发布。 在这种情况下,你的节点将会通知它拥有八个 example.com/special-storage 类型的资源。

Capacity:
 ...
 example.com/special-storage: 8

如果你想要允许针对特殊存储任意(数量)的请求,你可以按照 1 字节大小的块来发布特殊存储。 在这种情况下,你将会发布 800Gi 数量的 example.com/special-storage 类型的资源。

Capacity:
 ...
 example.com/special-storage:  800Gi

然后,容器就能够请求任意数量(多达 800Gi)字节的特殊存储。

Capacity:
 ...
 example.com/special-storage:  800Gi

清理

这里是一个从节点移除 dongle 资源发布的 PATCH 请求。

PATCH /api/v1/nodes/<your-node-name>/status HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json
Host: k8s-master:8080

[
  {
    "op": "remove",
    "path": "/status/capacity/example.com~1dongle",
  }
]

启动一个代理,以便你可以很容易地向 Kubernetes API 服务器发送请求:

kubectl proxy

在另一个命令窗口中,发送 HTTP PATCH 请求。用你的节点名称替换 <your-node-name>

curl --header "Content-Type: application/json-patch+json" \
  --request PATCH \
  --data '[{"op": "remove", "path": "/status/capacity/example.com~1dongle"}]' \
  http://localhost:8001/api/v1/nodes/<your-node-name>/status

验证 dongle 资源的发布已经被移除:

kubectl describe node <your-node-name> | grep dongle

(你应该看不到任何输出)

接下来

针对应用开发人员

针对集群管理员

8 - 自动扩缩集群 DNS 服务

本页展示了如何在你的 Kubernetes 集群中启用和配置 DNS 服务的自动扩缩功能。

准备开始

  • 你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

    要获知版本信息,请输入 kubectl version.
  • 本指南假设你的节点使用 AMD64 或 Intel 64 CPU 架构

  • 确保 Kubernetes DNS 已启用。

确定是否 DNS 水平自动扩缩特性已经启用

在 kube-system 命名空间中列出集群中的 Deployment

kubectl get deployment --namespace=kube-system

输出类似如下这样:

NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
...
kube-dns-autoscaler    1/1     1            1           ...
...

如果在输出中看到 “kube-dns-autoscaler”,说明 DNS 水平自动扩缩已经启用, 可以跳到调优 DNS 自动扩缩参数

获取 DNS Deployment 的名称

列出集群内 kube-system 命名空间中的 DNS Deployment:

kubectl get deployment -l k8s-app=kube-dns --namespace=kube-system

输出类似如下这样:

NAME      READY   UP-TO-DATE   AVAILABLE   AGE
...
coredns   2/2     2            2           ...
...

如果看不到 DNS 服务的 Deployment,你也可以通过名字来查找:

kubectl get deployment --namespace=kube-system

并在输出中寻找名称为 corednskube-dns 的 Deployment。

你的扩缩目标为:

Deployment/<your-deployment-name>

其中 <your-deployment-name> 是 DNS Deployment 的名称。 例如,如果你的 DNS Deployment 名称是 coredns,则你的扩展目标是 Deployment/coredns。

启用 DNS 水平自动扩缩

在本节,我们创建一个新的 Deployment。Deployment 中的 Pod 运行一个基于 cluster-proportional-autoscaler-amd64 镜像的容器。

创建文件 dns-horizontal-autoscaler.yaml,内容如下所示:

kind: ServiceAccount
apiVersion: v1
metadata:
  name: kube-dns-autoscaler
  namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: system:kube-dns-autoscaler
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["list", "watch"]
  - apiGroups: [""]
    resources: ["replicationcontrollers/scale"]
    verbs: ["get", "update"]
  - apiGroups: ["apps"]
    resources: ["deployments/scale", "replicasets/scale"]
    verbs: ["get", "update"]
# 待以下 issue 修复后,请删除 Configmaps
# kubernetes-incubator/cluster-proportional-autoscaler#16
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "create"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: system:kube-dns-autoscaler
subjects:
  - kind: ServiceAccount
    name: kube-dns-autoscaler
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:kube-dns-autoscaler
  apiGroup: rbac.authorization.k8s.io

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kube-dns-autoscaler
  namespace: kube-system
  labels:
    k8s-app: kube-dns-autoscaler
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      k8s-app: kube-dns-autoscaler
  template:
    metadata:
      labels:
        k8s-app: kube-dns-autoscaler
    spec:
      priorityClassName: system-cluster-critical
      securityContext:
        seccompProfile:
          type: RuntimeDefault
        supplementalGroups: [ 65534 ]
        fsGroup: 65534
      nodeSelector:
        kubernetes.io/os: linux
      containers:
      - name: autoscaler
        image: registry.k8s.io/cpa/cluster-proportional-autoscaler:1.8.4
        resources:
            requests:
                cpu: "20m"
                memory: "10Mi"
        command:
          - /cluster-proportional-autoscaler
          - --namespace=kube-system
          - --configmap=kube-dns-autoscaler
          # 应该保持目标与 cluster/addons/dns/kube-dns.yaml.base 同步。
          - --target=<SCALE_TARGET>
          # 当集群使用大节点(有更多核)时,“coresPerReplica”应该占主导地位。
          # 如果使用小节点,“nodesPerReplica“ 应该占主导地位。
          - --default-params={"linear":{"coresPerReplica":256,"nodesPerReplica":16,"preventSinglePointFailure":true,"includeUnschedulableNodes":true}}
          - --logtostderr=true
          - --v=2
      tolerations:
      - key: "CriticalAddonsOnly"
        operator: "Exists"
      serviceAccountName: kube-dns-autoscaler

在此文件中,将 <SCALE_TARGET> 替换成扩缩目标。

进入到包含配置文件的目录中,输入如下命令创建 Deployment:

kubectl apply -f dns-horizontal-autoscaler.yaml

命令成功执行后的输出为:

deployment.apps/kube-dns-autoscaler created

DNS 水平自动扩缩现在已经启用了。

调优 DNS 自动扩缩参数

验证 kube-dns-autoscaler ConfigMap 是否存在:

kubectl get configmap --namespace=kube-system

输出类似于:

NAME                  DATA      AGE
...
kube-dns-autoscaler   1         ...
...

修改此 ConfigMap 中的数据:

kubectl edit configmap kube-dns-autoscaler --namespace=kube-system

找到如下这行内容:

linear: '{"coresPerReplica":256,"min":1,"nodesPerReplica":16}'

根据需要修改对应的字段。“min” 字段表明 DNS 后端的最小数量。 实际后端的数量通过使用如下公式来计算:

replicas = max( ceil( cores × 1/coresPerReplica ) , ceil( nodes × 1/nodesPerReplica ) )

注意 coresPerReplicanodesPerReplica 的值都是浮点数。

背后的思想是,当一个集群使用具有很多核心的节点时,由 coresPerReplica 来控制。 当一个集群使用具有较少核心的节点时,由 nodesPerReplica 来控制。

其它的扩缩模式也是支持的,详情查看 cluster-proportional-autoscaler

禁用 DNS 水平自动扩缩

有几个可供调优的 DNS 水平自动扩缩选项。具体使用哪个选项因环境而异。

选项 1:kube-dns-autoscaler Deployment 缩容至 0 个副本

此选项适用于所有场景。运行如下命令:

kubectl scale deployment --replicas=0 kube-dns-autoscaler --namespace=kube-system

输出如下所示:

deployment.apps/kube-dns-autoscaler scaled

验证当前副本数为 0:

kubectl get rs --namespace=kube-system

输出内容中,在 DESIRED 和 CURRENT 列显示为 0:

NAME                                 DESIRED   CURRENT   READY   AGE
...
kube-dns-autoscaler-6b59789fc8            0         0         0       ...
...

选项 2:删除 kube-dns-autoscaler Deployment

如果 kube-dns-autoscaler 为你所控制,也就说没有人会去重新创建它,可以选择此选项:

kubectl delete deployment kube-dns-autoscaler --namespace=kube-system

输出内容如下所示:

deployment.apps "kube-dns-autoscaler" deleted

选项 3:从主控节点删除 kube-dns-autoscaler 清单文件

如果 kube-dns-autoscaler 在插件管理器 的控制之下,并且具有操作主控节点的写权限,可以使用此选项。

登录到主控节点,删除对应的清单文件。 kube-dns-autoscaler 对应的路径一般为:

/etc/kubernetes/addons/dns-horizontal-autoscaler/dns-horizontal-autoscaler.yaml

当清单文件被删除后,插件管理器将删除 kube-dns-autoscaler Deployment。

理解 DNS 水平自动扩缩工作原理

  • cluster-proportional-autoscaler 应用独立于 DNS 服务部署。

  • autoscaler Pod 运行一个客户端,它通过轮询 Kubernetes API 服务器获取集群中节点和核心的数量。

  • 系统会基于当前可调度的节点个数、核心数以及所给的扩缩参数,计算期望的副本数并应用到 DNS 后端。

  • 扩缩参数和数据点会基于一个 ConfigMap 来提供给 autoscaler,它会在每次轮询时刷新它的参数表, 以与最近期望的扩缩参数保持一致。

  • 扩缩参数是可以被修改的,而且不需要重建或重启 autoscaler Pod。

  • autoscaler 提供了一个控制器接口来支持两种控制模式:linearladder

接下来

9 - 从轮询切换为基于 CRI 事件的更新来获取容器状态

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

本页展示了如何迁移节点以使用基于事件的更新来获取容器状态。 与依赖轮询的传统方法相比,基于事件的实现可以减少 kubelet 对节点资源的消耗。 你可以将这个特性称为事件驱动的 Pod 生命周期事件生成器 (PLEG)。 这是在 Kubernetes 项目内部针对关键实现细节所用的名称。

基于轮询的方法称为通用 PLEG

准备开始

  • 你需要运行提供此特性的 Kubernetes 版本。 Kubernetes 1.27 提供了对基于事件更新容器状态的 Beta 支持。 此特性处于 Beta 阶段,默认被禁用
  • 你的 Kubernetes 服务器版本必须不低于版本 1.26. 要获知版本信息,请输入 kubectl version. 如果你正在运行不同版本的 Kubernetes,请查阅对应版本的文档。
  • 所使用的容器运行时必须支持容器生命周期事件。 如果容器运行时未声明对容器生命周期事件的支持,即使你已启用了此特性门控, kubelet 也会自动切换回传统的通用 PLEG。

为什么要切换到事件驱动的 PLEG?

  • 通用 PLEG 由于频繁轮询容器状态而产生了不可忽略的开销。
  • 这种开销会被 kubelet 的并行轮询容器状态的机制加剧, 限制了可扩缩性,还会导致性能和可靠性问题。
  • 事件驱动的 PLEG 的目标是通过替换定期轮询来减少闲置时的非必要任务。

切换为事件驱动的 PLEG

  1. 启用特性门控 EventedPLEG 后启动 kubelet。 你可以通过编辑 kubelet 配置文件并重启 kubelet 服务来管理 kubelet 特性门控。 你需要在使用此特性的所有节点上执行此操作。
  1. 确保节点被腾空后再继续。

  2. 启用容器事件生成后启动容器运行时。

    版本 1.7+

    版本 1.26+

    通过验证配置,检查 CRI-O 是否已配置为发送 CRI 事件:

    crio config | grep enable_pod_events
    

    如果已启用,输出应类似于:

    enable_pod_events = true
    

    要启用它,可使用 --enable-pod-events=true 标志或添加以下配置来启动 CRI-O 守护进程:

    [crio.runtime]
    enable_pod_events: true
    
    你的 Kubernetes 服务器版本必须不低于版本 1.26. 要获知版本信息,请输入 kubectl version.
  1. 确认 kubelet 正使用基于事件的容器阶段变更监控。 要检查这一点,可在 kubelet 日志中查找 EventedPLEG 词条。

    输出类似于:

    I0314 11:10:13.909915 1105457 feature_gate.go:249] feature gates: &{map[EventedPLEG:true]}
    

    如果你将 --v 设置为 4 及更高值,你可能会看到更多条目表明 kubelet 正在使用基于事件的容器状态监控。

    I0314 11:12:42.009542 1110177 evented.go:238] "Evented PLEG: Generated pod status from the received event" podUID=3b2c6172-b112-447a-ba96-94e7022912dc
    I0314 11:12:44.623326 1110177 evented.go:238] "Evented PLEG: Generated pod status from the received event" podUID=b3fba5ea-a8c5-4b76-8f43-481e17e8ec40
    I0314 11:12:44.714564 1110177 evented.go:238] "Evented PLEG: Generated pod status from the received event" podUID=b3fba5ea-a8c5-4b76-8f43-481e17e8ec40
    

接下来

10 - 改变默认 StorageClass

本文展示了如何改变默认的 Storage Class,它用于为没有特殊需求的 PersistentVolumeClaims 配置 volumes。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

为什么要改变默认存储类?

取决于安装模式,你的 Kubernetes 集群可能和一个被标记为默认的已有 StorageClass 一起部署。 这个默认的 StorageClass 以后将被用于动态的为没有特定存储类需求的 PersistentVolumeClaims 配置存储。更多细节请查看 PersistentVolumeClaim 文档

预先安装的默认 StorageClass 可能不能很好的适应你期望的工作负载;例如,它配置的存储可能太过昂贵。 如果是这样的话,你可以改变默认 StorageClass,或者完全禁用它以防止动态配置存储。

删除默认 StorageClass 可能行不通,因为它可能会被你集群中的扩展管理器自动重建。 请查阅你的安装文档中关于扩展管理器的细节,以及如何禁用单个扩展。

改变默认 StorageClass

  1. 列出你的集群中的 StorageClass:

    kubectl get storageclass
    

    输出类似这样:

    NAME                 PROVISIONER               AGE
    standard (default)   kubernetes.io/gce-pd      1d
    gold                 kubernetes.io/gce-pd      1d
    

    默认 StorageClass 以 (default) 标记。

  1. 标记默认 StorageClass 非默认:

    默认 StorageClass 的注解 storageclass.kubernetes.io/is-default-class 设置为 true。 注解的其它任意值或者缺省值将被解释为 false

    要标记一个 StorageClass 为非默认的,你需要改变它的值为 false

    kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
    

    这里的 standard 是你选择的 StorageClass 的名字。

  1. 标记一个 StorageClass 为默认的:

    和前面的步骤类似,你需要添加/设置注解 storageclass.kubernetes.io/is-default-class=true

    kubectl patch storageclass <your-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
    

    请注意,最多只能有一个 StorageClass 能够被标记为默认。 如果它们中有两个或多个被标记为默认,Kubernetes 将忽略这个注解, 也就是它将表现为没有默认 StorageClass。

  1. 验证你选用的 StorageClass 为默认的:

    kubectl get storageclass
    

    输出类似这样:

    NAME             PROVISIONER               AGE
    standard         kubernetes.io/gce-pd      1d
    gold (default)   kubernetes.io/gce-pd      1d
    

接下来

11 - 更改 PersistentVolume 的回收策略

本文展示了如何更改 Kubernetes PersistentVolume 的回收策略。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

为什么要更改 PersistentVolume 的回收策略

PersistentVolumes 可以有多种回收策略,包括 "Retain"、"Recycle" 和 "Delete"。 对于动态配置的 PersistentVolumes 来说,默认回收策略为 "Delete"。 这表示当用户删除对应的 PersistentVolumeClaim 时,动态配置的 volume 将被自动删除。 如果 volume 包含重要数据时,这种自动行为可能是不合适的。 那种情况下,更适合使用 "Retain" 策略。 使用 "Retain" 时,如果用户删除 PersistentVolumeClaim,对应的 PersistentVolume 不会被删除。 相反,它将变为 Released 状态,表示所有的数据可以被手动恢复。

更改 PersistentVolume 的回收策略

  1. 列出你集群中的 PersistentVolumes

    kubectl get pv
    

    输出类似于这样:

    NAME                                       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM             STORAGECLASS     REASON    AGE
    pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim1    manual                     10s
    pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim2    manual                     6s
    pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim3    manual                     3s
    

    这个列表同样包含了绑定到每个卷的 claims 名称,以便更容易的识别动态配置的卷。

  1. 选择你的 PersistentVolumes 中的一个并更改它的回收策略:

    kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
    

    这里的 <your-pv-name> 是你选择的 PersistentVolume 的名字。

  1. 验证你选择的 PersistentVolume 拥有正确的策略:

    kubectl get pv
    

    输出类似于这样:

    NAME                                       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM             STORAGECLASS     REASON    AGE
    pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim1    manual                     40s
    pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Delete          Bound     default/claim2    manual                     36s
    pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94   4Gi        RWO           Retain          Bound     default/claim3    manual                     33s
    

    在前面的输出中,你可以看到绑定到申领 default/claim3 的卷的回收策略为 Retain。 当用户删除申领 default/claim3 时,它不会被自动删除。

接下来

参考

12 - Kubernetes 云管理控制器

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

由于云驱动的开发和发布的步调与 Kubernetes 项目不同,将服务提供商专用代码抽象到 cloud-controller-manager 二进制中有助于云服务厂商在 Kubernetes 核心代码之外独立进行开发。

cloud-controller-manager 可以被链接到任何满足 cloudprovider.Interface 约束的云服务提供商。为了兼容旧版本,Kubernetes 核心项目中提供的 cloud-controller-manager 使用和 kube-controller-manager 相同的云服务类库。 已经在 Kubernetes 核心项目中支持的云服务提供商预计将通过使用 in-tree 的 cloud-controller-manager 过渡为非 Kubernetes 核心代码。

管理

需求

每个云服务都有一套各自的需求用于系统平台的集成,这不应与运行 kube-controller-manager 的需求有太大差异。作为经验法则,你需要:

  • 云服务认证/授权:你的云服务可能需要使用令牌或者 IAM 规则以允许对其 API 的访问
  • kubernetes 认证/授权:cloud-controller-manager 可能需要 RBAC 规则以访问 kubernetes apiserver
  • 高可用:类似于 kube-controller-manager,你可能希望通过主节点选举(默认开启)配置一个高可用的云管理控制器。

运行云管理控制器

你需要对集群配置做适当的修改以成功地运行云管理控制器:

  • kubeletkube-apiserverkube-controller-manager 必须根据用户对外部 CCM 的使用进行设置。 如果用户有一个外部的 CCM(不是 Kubernetes 控制器管理器中的内部云控制器回路), 那么必须添加 --cloud-provider=external 参数。否则,不应添加此参数。

请记住,设置集群使用云管理控制器将用多种方式更改集群行为:

  • 指定了 --cloud-provider=external 的组件将被添加一个 node.cloudprovider.kubernetes.io/uninitialized 的污点,导致其在初始化过程中不可调度(NoSchedule)。 这将标记该节点在能够正常调度前,需要外部的控制器进行二次初始化。 请注意,如果云管理控制器不可用,集群中的新节点会一直处于不可调度的状态。 这个污点很重要,因为调度器可能需要关于节点的云服务特定的信息,比如他们的区域或类型 (高端 CPU、GPU 支持、内存较大、临时实例等)。
  • 集群中节点的云服务信息将不再能够从本地元数据中获取,取而代之的是所有获取节点信息的 API 调用都将通过云管理控制器。这意味着你可以通过限制到 kubelet 云服务 API 的访问来提升安全性。 在更大的集群中你可能需要考虑云管理控制器是否会遇到速率限制, 因为它现在负责集群中几乎所有到云服务的 API 调用。

云管理控制器可以实现:

  • 节点控制器 - 负责使用云服务 API 更新 kubernetes 节点并删除在云服务上已经删除的 kubernetes 节点。
  • 服务控制器 - 负责在云服务上为类型为 LoadBalancer 的 service 提供负载均衡器。
  • 路由控制器 - 负责在云服务上配置网络路由。
  • 如果你使用的是 out-of-tree 提供商,请按需实现其余任意特性。

示例

如果当前 Kubernetes 内核支持你使用的云服务,并且想要采用云管理控制器,请参见 kubernetes 内核中的云管理控制器

对于不在 Kubernetes 核心代码库中的云管理控制器,你可以在云服务厂商或 SIG 领导者的源中找到对应的项目。

对于已经存在于 Kubernetes 内核中的提供商,你可以在集群中将 in-tree 云管理控制器作为守护进程运行。请使用如下指南:

# 这是一个如何将 cloud-controller-manager 安装为集群中的 Daemonset 的示例。
# 本例假定你的主控节点可以运行 pod 并具有角色 node-role.kubernetes.io/master
# 请注意,这里的 Daemonset 不能直接在你的云上工作,此例只是一个指导。

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: cloud-controller-manager
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: system:cloud-controller-manager
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: cloud-controller-manager
  namespace: kube-system
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    k8s-app: cloud-controller-manager
  name: cloud-controller-manager
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: cloud-controller-manager
  template:
    metadata:
      labels:
        k8s-app: cloud-controller-manager
    spec:
      serviceAccountName: cloud-controller-manager
      containers:
      - name: cloud-controller-manager
        # 对于树内驱动,我们使用 registry.k8s.io/cloud-controller-manager,
        # 镜像可以替换为其他树外驱动的镜像
        image: registry.k8s.io/cloud-controller-manager:v1.8.0
        command:
        - /usr/local/bin/cloud-controller-manager
        - --cloud-provider=[YOUR_CLOUD_PROVIDER]  # 在此处添加你自己的云驱动!
        - --leader-elect=true
        - --use-service-account-credentials
        # 这些标志因每个云驱动而异
        - --allocate-node-cidrs=true
        - --configure-cloud-routes=true
        - --cluster-cidr=172.17.0.0/16
      tolerations:
      # 这一设置是必需的,为了让 CCM 可以自行引导
      - key: node.cloudprovider.kubernetes.io/uninitialized
        value: "true"
        effect: NoSchedule
      # 这些容忍度使得守护进程能够在控制平面节点上运行
      # 如果你的控制平面节点不应该运行 pod,请删除它们
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      # 这是为了限制 CCM 仅在主节点上运行
      # 节点选择器可能因你的集群设置而异
      nodeSelector:
        node-role.kubernetes.io/master: ""

限制

运行云管理控制器会有一些可能的限制。虽然以后的版本将处理这些限制,但是知道这些生产负载的限制很重要。

对 Volume 的支持

云管理控制器未实现 kube-controller-manager 中的任何 volume 控制器, 因为和 volume 的集成还需要与 kubelet 协作。由于我们引入了 CSI (容器存储接口, container storage interface) 并对弹性 volume 插件添加了更强大的支持, 云管理控制器将添加必要的支持,以使云服务同 volume 更好的集成。 请在这里了解更多关于 out-of-tree CSI volume 插件的信息。

可扩展性

通过云管理控制器查询你的云提供商的 API 以检索所有节点的信息。 对于非常大的集群,请考虑可能的瓶颈,例如资源需求和 API 速率限制。

鸡和蛋的问题

云管理控制器的目标是将云服务特性的开发从 Kubernetes 核心项目中解耦。 不幸的是,Kubernetes 项目的许多方面都假设云服务提供商的特性同项目紧密结合。 因此,这种新架构的采用可能导致某些场景下,当一个请求需要从云服务提供商获取信息时, 在该请求没有完成的情况下云管理控制器不能返回那些信息。

Kubelet 中的 TLS 引导特性是一个很好的例子。 目前,TLS 引导认为 kubelet 有能力从云提供商(或本地元数据服务)获取所有的地址类型(私有、公用等), 但在被初始化之前,云管理控制器不能设置节点地址类型,而这需要 kubelet 拥有 TLS 证书以和 API 服务器通信。

随着整个动议的演进,将来的发行版中将作出改变来解决这些问题。

接下来

要构建和开发你自己的云管理控制器,请阅读 开发云管理控制器 文档。

13 - 配置 kubelet 镜像凭据提供程序

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

从 Kubernetes v1.20 开始,kubelet 可以使用 exec 插件动态获得针对某容器镜像库的凭据。 kubelet 使用 Kubernetes 版本化 API 通过标准输入输出(标准输入、标准输出和标准错误)和 exec 插件通信。这些插件允许 kubelet 动态请求容器仓库的凭据,而不是将静态凭据存储在磁盘上。 例如,插件可能会与本地元数据服务器通信,以获得 kubelet 正在拉取的镜像的短期凭据。

如果以下任一情况属实,你可能对此功能感兴趣:

  • 需要调用云提供商的 API 来获得镜像库的身份验证信息。
  • 凭据的到期时间很短,需要频繁请求新凭据。
  • 将镜像库凭据存储在磁盘或者 imagePullSecret 是不可接受的。

本指南演示如何配置 kubelet 的镜像凭据提供程序插件机制。

准备开始

  • 你需要一个 Kubernetes 集群,其节点支持 kubelet 凭据提供程序插件。 这种支持在 Kubernetes 1.30 中可用; Kubernetes v1.24 和 v1.25 将此作为 Beta 特性包含在内,默认启用。
  • 凭据提供程序 exec 插件的一种可用的实现。你可以构建自己的插件或使用云提供商提供的插件。
你的 Kubernetes 服务器版本必须不低于版本 v1.26. 要获知版本信息,请输入 kubectl version.

在节点上安装插件

凭据提供程序插件是将由 kubelet 运行的可执行二进制文件。 你需要确保插件可执行文件存在于你的集群的每个节点上,并存储在已知目录中。 稍后配置 kubelet 标志需要该目录。

配置 kubelet

为了使用这个特性,kubelet 需要设置以下两个标志:

  • --image-credential-provider-config —— 凭据提供程序插件配置文件的路径。
  • --image-credential-provider-bin-dir —— 凭据提供程序插件二进制可执行文件所在目录的路径。

配置 kubelet 凭据提供程序

kubelet 会读取通过 --image-credential-provider-config 设定的配置文件, 以确定应该为哪些容器镜像调用哪些 exec 插件。 如果你正在使用基于 ECR-based 插件, 这里有个样例配置文件你可能最终会使用到:

apiVersion: kubelet.config.k8s.io/v1
kind: CredentialProviderConfig
# providers 是将由 kubelet 启用的凭据提供程序帮助插件列表。
# 多个提供程序可能与单个镜像匹配,在这种情况下,来自所有提供程序的凭据将返回到 kubelet。
# 如果为单个镜像调用了多个提供程序,则返回结果会被合并。
# 如果提供程序返回重叠的身份验证密钥,则使用提供程序列表中较早的值。
providers:
  # name 是凭据提供程序的必需名称。 
  # 它必须与 kubelet 看到的提供程序可执行文件的名称相匹配。
  # 可执行文件必须在 kubelet 的 bin 目录中
  # (由 --image-credential-provider-bin-dir 标志设置)。
  - name: ecr-credential-provider
    # matchImages 是一个必需的字符串列表,用于匹配镜像以确定是否应调用此提供程序。
    # 如果其中一个字符串与 kubelet 请求的镜像相匹配,则该插件将被调用并有机会提供凭据。
    # 镜像应包含注册域和 URL 路径。
    #
    # matchImages 中的每个条目都是一个模式字符串,可以选择包含端口和路径。
    # 可以在域中使用通配符,但不能在端口或路径中使用。
    # 支持通配符作为子域(例如 "*.k8s.io" 或 "k8s.*.io")和顶级域(例如 "k8s.*")。
    # 还支持匹配部分子域,如 "app*.k8s.io"。
    # 每个通配符只能匹配一个子域段,因此 "*.io" **不** 匹配 "*.k8s.io"。
    #
    # 当以下所有条件都为真时,镜像和 matchImage 之间存在匹配:
    #
    # - 两者都包含相同数量的域部分并且每个部分都匹配。
    # - matchImages 的 URL 路径必须是目标镜像 URL 路径的前缀。
    # - 如果 matchImages 包含端口,则该端口也必须在镜像中匹配。
    #
    # matchImages 的示例值:
    #
    # - 123456789.dkr.ecr.us-east-1.amazonaws.com
    # - *.azurecr.io
    # - gcr.io
    # - *.*.registry.io
    # - registry.io:8080/path
    matchImages:
      - "*.dkr.ecr.*.amazonaws.com"
      - "*.dkr.ecr.*.amazonaws.com.cn"
      - "*.dkr.ecr-fips.*.amazonaws.com"
      - "*.dkr.ecr.us-iso-east-1.c2s.ic.gov"
      - "*.dkr.ecr.us-isob-east-1.sc2s.sgov.gov"
    # defaultCacheDuration 是插件将在内存中缓存凭据的默认持续时间。
    # 如果插件响应中未提供缓存持续时间。此字段是必需的。
    defaultCacheDuration: "12h"
    # exec CredentialProviderRequest 的必需输入版本。
    # 返回的 CredentialProviderResponse 必须使用与输入相同的编码版本。当前支持的值为:
    # - credentialprovider.kubelet.k8s.io/v1
    apiVersion: credentialprovider.kubelet.k8s.io/v1
    # 执行命令时传递给命令的参数。
    # 可选
    # args:
    #  - --example-argument
    # env 定义了额外的环境变量以暴露给进程。
    # 这些与主机环境以及 client-go 用于将参数传递给插件的变量结合在一起。
    # 可选
    env:
      - name: AWS_PROFILE
        value: example_profile

providers 字段是 kubelet 所使用的已启用插件列表。每个条目都有几个必填字段:

  • name:插件的名称,必须与传入 --image-credential-provider-bin-dir 的目录中存在的可执行二进制文件的名称相匹配。
  • matchImages:字符串列表,用于匹配镜像以确定是否应调用此提供程序。 更多相关信息参见后文。
  • defaultCacheDuration:如果插件未指定缓存时长,kubelet 将在内存中缓存凭据的默认时长。
  • apiVersion:kubelet 和 exec 插件在通信时将使用的 API 版本。

每个凭据提供程序也可以被赋予可选的参数和环境变量。 你可以咨询插件实现者以确定给定插件需要哪些参数和环境变量集。

配置镜像匹配

kubelet 使用每个凭据提供程序的 matchImages 字段来确定是否应该为 Pod 正在使用的给定镜像调用插件。 matchImages 中的每个条目都是一个镜像模式字符串,可以选择包含端口和路径。 可以在域中使用通配符,但不能在端口或路径中使用。 支持通配符作为子域,如 *.k8s.iok8s.*.io,以及顶级域,如 k8s.*。 还支持匹配部分子域,如 app*.k8s.io。每个通配符只能匹配一个子域段, 因此 *.io 不匹配 *.k8s.io

当以下条件全部满足时,镜像名称和 matchImage 条目之间存在匹配:

  • 两者都包含相同数量的域部分并且每个部分都匹配。
  • 匹配图片的 URL 路径必须是目标图片 URL 路径的前缀。
  • 如果 matchImages 包含端口,则该端口也必须在镜像中匹配。

matchImages 模式的一些示例值:

  • 123456789.dkr.ecr.us-east-1.amazonaws.com
  • *.azurecr.io
  • gcr.io
  • *.*.registry.io
  • foo.registry.io:8080/path

接下来

14 - 配置 API 对象配额

本文讨论如何为 API 对象配置配额,包括 PersistentVolumeClaim 和 Service。 配额限制了可以在命名空间中创建的特定类型对象的数量。 你可以在 ResourceQuota 对象中指定配额。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

创建命名空间

创建一个命名空间以便本例中创建的资源和集群中的其余部分相隔离。

kubectl create namespace quota-object-example

创建 ResourceQuota

下面是一个 ResourceQuota 对象的配置文件:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-quota-demo
spec:
  hard:
    persistentvolumeclaims: "1"
    services.loadbalancers: "2"
    services.nodeports: "0"

创建 ResourceQuota:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-objects.yaml --namespace=quota-object-example

查看 ResourceQuota 的详细信息:

kubectl get resourcequota object-quota-demo --namespace=quota-object-example --output=yaml

输出结果表明在 quota-object-example 命名空间中,至多只能有一个 PersistentVolumeClaim, 最多两个 LoadBalancer 类型的服务,不能有 NodePort 类型的服务。

status:
  hard:
    persistentvolumeclaims: "1"
    services.loadbalancers: "2"
    services.nodeports: "0"
  used:
    persistentvolumeclaims: "0"
    services.loadbalancers: "0"
    services.nodeports: "0"

创建 PersistentVolumeClaim

下面是一个 PersistentVolumeClaim 对象的配置文件:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-quota-demo
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

创建 PersistentVolumeClaim:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-objects-pvc.yaml --namespace=quota-object-example

确认已创建完 PersistentVolumeClaim:

kubectl get persistentvolumeclaims --namespace=quota-object-example

输出信息表明 PersistentVolumeClaim 存在并且处于 Pending 状态:

NAME             STATUS
pvc-quota-demo   Pending

尝试创建第二个 PersistentVolumeClaim

下面是第二个 PersistentVolumeClaim 的配置文件:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-quota-demo-2
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi

尝试创建第二个 PersistentVolumeClaim:

kubectl apply -f https://k8s.io/examples/admin/resource/quota-objects-pvc-2.yaml --namespace=quota-object-example

输出信息表明第二个 PersistentVolumeClaim 没有创建成功,因为这会超出命名空间的配额。

persistentvolumeclaims "pvc-quota-demo-2" is forbidden:
exceeded quota: object-quota-demo, requested: persistentvolumeclaims=1,
used: persistentvolumeclaims=1, limited: persistentvolumeclaims=1

说明

下面这些字符串可被用来标识那些能被配额限制的 API 资源:

字符串API 对象
"pods"Pod
"services"Service
"replicationcontrollers"ReplicationController
"resourcequotas"ResourceQuota
"secrets"Secret
"configmaps"ConfigMap
"persistentvolumeclaims"PersistentVolumeClaim
"services.nodeports"NodePort 类型的 Service
"services.loadbalancers"LoadBalancer 类型的 Service

清理

删除你的命名空间:

kubectl delete namespace quota-object-example

接下来

集群管理员参考

应用开发者参考

15 - 控制节点上的 CPU 管理策略

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

按照设计,Kubernetes 对 Pod 执行相关的很多方面进行了抽象,使得用户不必关心。 然而,为了正常运行,有些工作负载要求在延迟和/或性能方面有更强的保证。 为此,kubelet 提供方法来实现更复杂的负载放置策略,同时保持抽象,避免显式的放置指令。

有关资源管理的详细信息, 请参阅 Pod 和容器的资源管理文档。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 v1.26. 要获知版本信息,请输入 kubectl version.

如果你正在运行一个旧版本的 Kubernetes,请参阅与该版本对应的文档。

CPU 管理策略

默认情况下,kubelet 使用 CFS 配额 来执行 Pod 的 CPU 约束。 当节点上运行了很多 CPU 密集的 Pod 时,工作负载可能会迁移到不同的 CPU 核, 这取决于调度时 Pod 是否被扼制,以及哪些 CPU 核是可用的。 许多工作负载对这种迁移不敏感,因此无需任何干预即可正常工作。

然而,有些工作负载的性能明显地受到 CPU 缓存亲和性以及调度延迟的影响。 对此,kubelet 提供了可选的 CPU 管理策略,来确定节点上的一些分配偏好。

配置

CPU 管理策略通过 kubelet 参数 --cpu-manager-policyKubeletConfiguration 中的 cpuManagerPolicy 字段来指定。 支持两种策略:

  • none:默认策略。
  • static:允许为节点上具有某些资源特征的 Pod 赋予增强的 CPU 亲和性和独占性。

CPU 管理器定期通过 CRI 写入资源更新,以保证内存中 CPU 分配与 cgroupfs 一致。 同步频率通过新增的 Kubelet 配置参数 --cpu-manager-reconcile-period 来设置。 如果不指定,默认与 --node-status-update-frequency 的周期相同。

Static 策略的行为可以使用 --cpu-manager-policy-options 参数来微调。 该参数采用一个逗号分隔的 key=value 策略选项列表。 如果你禁用 CPUManagerPolicyOptions 特性门控, 则你不能微调 CPU 管理器策略。这种情况下,CPU 管理器仅使用其默认设置运行。

除了顶级的 CPUManagerPolicyOptions 特性门控, 策略选项分为两组:Alpha 质量(默认隐藏)和 Beta 质量(默认可见)。 这些组分别由 CPUManagerPolicyAlphaOptionsCPUManagerPolicyBetaOptions 特性门控来管控。 不同于 Kubernetes 标准,这里是由这些特性门控来管控选项组,因为为每个单独选项都添加一个特性门控过于繁琐。

更改 CPU 管理器策略

由于 CPU 管理器策略只能在 kubelet 生成新 Pod 时应用,所以简单地从 "none" 更改为 "static" 将不会对现有的 Pod 起作用。 因此,为了正确更改节点上的 CPU 管理器策略,请执行以下步骤:

  1. 腾空节点。
  2. 停止 kubelet。
  3. 删除旧的 CPU 管理器状态文件。该文件的路径默认为 /var/lib/kubelet/cpu_manager_state。 这将清除 CPUManager 维护的状态,以便新策略设置的 cpu-sets 不会与之冲突。
  4. 编辑 kubelet 配置以将 CPU 管理器策略更改为所需的值。
  5. 启动 kubelet。

对需要更改其 CPU 管理器策略的每个节点重复此过程。 跳过此过程将导致 kubelet crashlooping 并出现以下错误:

could not restore state from checkpoint: configured policy "static" differs from state checkpoint policy "none", please drain this node and delete the CPU manager checkpoint file "/var/lib/kubelet/cpu_manager_state" before restarting Kubelet

none 策略

none 策略显式地启用现有的默认 CPU 亲和方案,不提供操作系统调度器默认行为之外的亲和性策略。 通过 CFS 配额来实现 Guaranteed PodsBurstable Pods 的 CPU 使用限制。

static 策略

static 策略针对具有整数型 CPU requestsGuaranteed Pod, 它允许该类 Pod 中的容器访问节点上的独占 CPU 资源。这种独占性是使用 cpuset cgroup 控制器来实现的。

此策略管理一个 CPU 共享池,该共享池最初包含节点上所有的 CPU 资源。 可独占性 CPU 资源数量等于节点的 CPU 总量减去通过 kubelet --kube-reserved--system-reserved 参数保留的 CPU 资源。 从 1.17 版本开始,可以通过 kubelet --reserved-cpus 参数显式地指定 CPU 预留列表。 由 --reserved-cpus 指定的显式 CPU 列表优先于由 --kube-reserved--system-reserved 指定的 CPU 预留。 通过这些参数预留的 CPU 是以整数方式,按物理核心 ID 升序从初始共享池获取的。 共享池是 BestEffortBurstable Pod 运行的 CPU 集合。 Guaranteed Pod 中的容器,如果声明了非整数值的 CPU requests,也将运行在共享池的 CPU 上。 只有 Guaranteed Pod 中,指定了整数型 CPU requests 的容器,才会被分配独占 CPU 资源。

Guaranteed Pod 调度到节点上时,如果其容器符合静态分配要求, 相应的 CPU 会被从共享池中移除,并放置到容器的 cpuset 中。 因为这些容器所使用的 CPU 受到调度域本身的限制,所以不需要使用 CFS 配额来进行 CPU 的绑定。 换言之,容器 cpuset 中的 CPU 数量与 Pod 规约中指定的整数型 CPU limit 相等。 这种静态分配增强了 CPU 亲和性,减少了 CPU 密集的工作负载在节流时引起的上下文切换。

考虑以下 Pod 规格的容器:

spec:
  containers:
  - name: nginx
    image: nginx

该 Pod 属于 BestEffort QoS 类型,因为其未指定 requestslimits 值。 所以该容器运行在共享 CPU 池中。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
      requests:
        memory: "100Mi"

该 Pod 属于 Burstable QoS 类型,因为其资源 requests 不等于 limits,且未指定 cpu 数量。 所以该容器运行在共享 CPU 池中。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
      requests:
        memory: "100Mi"
        cpu: "1"

该 Pod 属于 Burstable QoS 类型,因为其资源 requests 不等于 limits。 所以该容器运行在共享 CPU 池中。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
      requests:
        memory: "200Mi"
        cpu: "2"

该 Pod 属于 Guaranteed QoS 类型,因为其 requests 值与 limits相等。 同时,容器对 CPU 资源的限制值是一个大于或等于 1 的整数值。 所以,该 nginx 容器被赋予 2 个独占 CPU。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "1.5"
      requests:
        memory: "200Mi"
        cpu: "1.5"

该 Pod 属于 Guaranteed QoS 类型,因为其 requests 值与 limits相等。 但是容器对 CPU 资源的限制值是一个小数。所以该容器运行在共享 CPU 池中。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"

该 Pod 属于 Guaranteed QoS 类型,因其指定了 limits 值,同时当未显式指定时, requests 值被设置为与 limits 值相等。 同时,容器对 CPU 资源的限制值是一个大于或等于 1 的整数值。 所以,该 nginx 容器被赋予 2 个独占 CPU。

Static 策略选项

你可以使用以下特性门控根据成熟度级别打开或关闭选项组:

  • CPUManagerPolicyBetaOptions 默认启用。禁用以隐藏 beta 级选项。
  • CPUManagerPolicyAlphaOptions 默认禁用。启用以显示 alpha 级选项。 你仍然必须使用 CPUManagerPolicyOptions kubelet 选项启用每个选项。

静态 CPUManager 策略存在以下策略选项:

  • full-pcpus-only(Beta,默认可见)(1.22 或更高版本)
  • distribute-cpus-across-numa(alpha,默认隐藏)(1.23 或更高版本)
  • align-by-socket(Alpha,默认隐藏)(1.25 或更高版本)

如果使用 full-pcpus-only 策略选项,static 策略总是会分配完整的物理核心。 默认情况下,如果不使用该选项,static 策略会使用拓扑感知最适合的分配方法来分配 CPU。 在启用了 SMT 的系统上,此策略所分配是与硬件线程对应的、独立的虚拟核。 这会导致不同的容器共享相同的物理核心, 该行为进而会导致吵闹的邻居问题

启用该选项之后,只有当一个 Pod 里所有容器的 CPU 请求都能够分配到完整的物理核心时, kubelet 才会接受该 Pod。 如果 Pod 没有被准入,它会被置于 Failed 状态,错误消息是 SMTAlignmentError

如果使用 distribute-cpus-across-numa 策略选项, 在需要多个 NUMA 节点来满足分配的情况下, static 策略会在 NUMA 节点上平均分配 CPU。 默认情况下,CPUManager 会将 CPU 分配到一个 NUMA 节点上,直到它被填满, 剩余的 CPU 会简单地溢出到下一个 NUMA 节点。 这会导致依赖于同步屏障(以及类似的同步原语)的并行代码出现不期望的瓶颈, 因为此类代码的运行速度往往取决于最慢的工作线程 (由于至少一个 NUMA 节点存在可用 CPU 较少的情况,因此速度变慢)。 通过在 NUMA 节点上平均分配 CPU, 应用程序开发人员可以更轻松地确保没有某个工作线程单独受到 NUMA 影响, 从而提高这些类型应用程序的整体性能。

如果指定了 align-by-socket 策略选项,那么在决定如何分配 CPU 给容器时,CPU 将被视为在 CPU 的插槽边界对齐。 默认情况下,CPUManager 在 NUMA 边界对齐 CPU 分配,如果需要从多个 NUMA 节点提取出 CPU 以满足分配,将可能会导致系统性能下降。 尽管 align-by-socket 策略试图确保从 NUMA 节点的最小数量分配所有 CPU,但不能保证这些 NUMA 节点将位于同一个 CPU 的插槽上。 通过指示 CPUManager 在 CPU 的插槽边界而不是 NUMA 边界显式对齐 CPU,我们能够避免此类问题。 注意,此策略选项不兼容 TopologyManagersingle-numa-node 策略,并且不适用于 CPU 的插槽数量大于 NUMA 节点数量的硬件。

可以通过将 full-pcpus-only=true 添加到 CPUManager 策略选项来启用 full-pcpus-only 选项。 同样地,可以通过将 distribute-cpus-across-numa=true 添加到 CPUManager 策略选项来启用 distribute-cpus-across-numa 选项。 当两者都设置时,它们是“累加的”,因为 CPU 将分布在 NUMA 节点的 full-pcpus 块中,而不是单个核心。 可以通过将 align-by-socket=true 添加到 CPUManager 策略选项来启用 align-by-socket 策略选项。 同样,也能够将 distribute-cpus-across-numa=true 添加到 full-pcpus-onlydistribute-cpus-across-numa 策略选项中。

16 - 控制节点上的拓扑管理策略

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

越来越多的系统利用 CPU 和硬件加速器的组合来支持要求低延迟的任务和高吞吐量的并行计算。 这类负载包括电信、科学计算、机器学习、金融服务和数据分析等。 此类混合系统需要有高性能环境支持。

为了获得最佳性能,需要进行与 CPU 隔离、内存和设备局部性有关的优化。 但是,在 Kubernetes 中,这些优化由各自独立的组件集合来处理。

拓扑管理器(Topology Manager) 是一个 kubelet 组件,旨在协调负责这些优化的一组组件。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 v1.18. 要获知版本信息,请输入 kubectl version.

拓扑管理器如何工作

在引入拓扑管理器之前,Kubernetes 中的 CPU 和设备管理器相互独立地做出资源分配决策。 这可能会导致在多处理系统上出现不符合期望的资源分配情况;由于这些与期望相左的分配,对性能或延迟敏感的应用将受到影响。 这里的不符合期望意指,例如,CPU 和设备是从不同的 NUMA 节点分配的,因此会导致额外的延迟。

拓扑管理器是一个 Kubelet 组件,扮演信息源的角色,以便其他 Kubelet 组件可以做出与拓扑结构相对应的资源分配决定。

拓扑管理器为组件提供了一个称为 建议提供者(Hint Provider) 的接口,以发送和接收拓扑信息。 拓扑管理器具有一组节点级策略,具体说明如下。

拓扑管理器从 建议提供者 接收拓扑信息,作为表示可用的 NUMA 节点和首选分配指示的位掩码。 拓扑管理器策略对所提供的建议执行一组操作,并根据策略对提示进行约减以得到最优解; 如果存储了与预期不符的建议,则该建议的优选字段将被设置为 false。 在当前策略中,首选是最窄的优选掩码。 所选建议将被存储为拓扑管理器的一部分。 取决于所配置的策略,所选建议可用来决定节点接受或拒绝 Pod。 之后,建议会被存储在拓扑管理器中,供 建议提供者 在作资源分配决策时使用。

拓扑管理器作用域和策略

拓扑管理器目前:

  • 对所有 QoS 类的 Pod 执行对齐操作。
  • 针对建议提供者所提供的拓扑建议,对请求的资源进行对齐。

如果满足这些条件,则拓扑管理器将对齐请求的资源。

为了定制如何进行对齐,拓扑管理器提供了两种不同的方式:scopepolicy

scope 定义了你希望的资源对齐粒度(例如,是在 pod 还是 container 层级上对齐)。 policy 定义了对齐时实际使用的策略(例如,best-effortrestrictedsingle-numa-node 等等)。 可以在下文找到现今可用的各种 scopespolicies 的具体信息。

拓扑管理器作用域

拓扑管理器可以在以下不同的作用域内进行资源对齐:

  • container (默认)
  • pod

在 kubelet 启动时,可以使用 --topology-manager-scope 标志来选择其中任一选项。

容器作用域

默认使用的是 container 作用域。

在该作用域内,拓扑管理器依次进行一系列的资源对齐, 也就是,对(Pod 中的)每一个容器计算单独的对齐。 换句话说,在该特定的作用域内,没有根据特定的 NUMA 节点集来把容器分组的概念。 实际上,拓扑管理器会把单个容器任意地对齐到 NUMA 节点上。

容器分组的概念是在以下的作用域内特别实现的,也就是 pod 作用域。

Pod 作用域

启动 kubelet 时附带 --topology-manager-scope=pod 命令行选项,就可以选择 pod 作用域。

该作用域允许把一个 Pod 里的所有容器作为一个分组,分配到一个共同的 NUMA 节点集。 也就是,拓扑管理器会把一个 Pod 当成一个整体, 并且试图把整个 Pod(所有容器)分配到一个单个的 NUMA 节点或者一个共同的 NUMA 节点集。 以下的例子说明了拓扑管理器在不同的场景下使用的对齐方式:

  • 所有容器可以被分配到一个单一的 NUMA 节点,实际上也是这样分配的;
  • 所有容器可以被分配到一个共享的 NUMA 节点集,实际上也是这样分配的。

整个 Pod 所请求的某种资源总量是根据 有效 request/limit 公式来计算的,因此,对某一种资源而言,该总量等于以下数值中的最大值:

  • 所有应用容器请求之和;
  • 初始容器请求的最大值。

pod 作用域与 single-numa-node 拓扑管理器策略一起使用, 对于延时敏感的工作负载,或者对于进行 IPC 的高吞吐量应用程序,都是特别有价值的。 把这两个选项组合起来,你可以把一个 Pod 里的所有容器都放到一个单个的 NUMA 节点, 使得该 Pod 消除了 NUMA 之间的通信开销。

single-numa-node 策略下,只有当可能的分配方案中存在合适的 NUMA 节点集时,Pod 才会被接受。 重新考虑上述的例子:

  • 节点集只包含单个 NUMA 节点时,Pod 就会被接受,
  • 然而,节点集包含多个 NUMA 节点时,Pod 就会被拒绝 (因为满足该分配方案需要两个或以上的 NUMA 节点,而不是单个 NUMA 节点)。

简要地说,拓扑管理器首先计算出 NUMA 节点集,然后使用拓扑管理器策略来测试该集合, 从而决定拒绝或者接受 Pod。

拓扑管理器策略

拓扑管理器支持四种分配策略。 你可以通过 Kubelet 标志 --topology-manager-policy 设置策略。 所支持的策略有四种:

  • none (默认)
  • best-effort
  • restricted
  • single-numa-node

none 策略

这是默认策略,不执行任何拓扑对齐。

best-effort 策略

对于 Pod 中的每个容器,具有 best-effort 拓扑管理策略的 kubelet 将调用每个建议提供者以确定资源可用性。 使用此信息,拓扑管理器存储该容器的首选 NUMA 节点亲和性。 如果亲和性不是首选,则拓扑管理器将存储该亲和性,并且无论如何都将 Pod 接纳到该节点。

之后建议提供者可以在进行资源分配决策时使用这个信息。

restricted 策略

对于 Pod 中的每个容器,配置了 restricted 拓扑管理策略的 kubelet 调用每个建议提供者以确定其资源可用性。 使用此信息,拓扑管理器存储该容器的首选 NUMA 节点亲和性。 如果亲和性不是首选,则拓扑管理器将从节点中拒绝此 Pod。 这将导致 Pod 处于 Terminated 状态,且 Pod 无法被节点接受。

一旦 Pod 处于 Terminated 状态,Kubernetes 调度器将不会尝试重新调度该 Pod。 建议使用 ReplicaSet 或者 Deployment 来触发重新部署 Pod。 还可以通过实现外部控制环,以触发重新部署具有 Topology Affinity 错误的 Pod。

如果 Pod 被允许运行在某节点,则建议提供者可以在做出资源分配决定时使用此信息。

single-numa-node 策略

对于 Pod 中的每个容器,配置了 single-numa-node 拓扑管理策略的 kubelet 调用每个建议提供者以确定其资源可用性。 使用此信息,拓扑管理器确定是否支持单 NUMA 节点亲和性。 如果支持,则拓扑管理器将存储此信息,然后 建议提供者 可以在做出资源分配决定时使用此信息。 如果不支持,则拓扑管理器将拒绝 Pod 运行于该节点。 这将导致 Pod 处于 Terminated 状态,且 Pod 无法被节点接受。

一旦 Pod 处于 Terminated 状态,Kubernetes 调度器将不会尝试重新调度该 Pod。 建议使用多副本的 Deployment 来触发重新部署 Pod。 还可以通过实现外部控制环,以触发重新部署具有 Topology Affinity 错误的 Pod。

拓扑管理器策略选项

对拓扑管理器策略选项的支持需要启用 TopologyManagerPolicyOptions 特性门控(默认启用)。

你可以使用以下特性门控根据成熟度级别打开和关闭这些选项组:

  • TopologyManagerPolicyBetaOptions 默认启用。启用以显示 Beta 级别选项。
  • TopologyManagerPolicyAlphaOptions 默认禁用。启用以显示 Alpha 级别选项。

你仍然需要使用 TopologyManagerPolicyOptions kubelet 选项来启用每个选项。

存在以下策略选项:

  • prefer-closest-numa-nodes(Beta,默认可见,TopologyManagerPolicyOptionsTopologyManagerPolicyAlphaOptions 特性门控必须被启用)。 prefer-closest-numa-nodes 策略选项在 Kubernetes 1.30 中是 Beta 版。

如果 prefer-closest-numa-nodes 策略选项被指定,则在做出准入决策时 best-effortrestricted 策略将偏向于彼此之间距离较短的一组 NUMA 节点。 你可以通过将 prefer-closest-numa-nodes=true 添加到拓扑管理器策略选项来启用此选项。 默认情况下,如果没有此选项,拓扑管理器会在单个 NUMA 节点或(在需要多个 NUMA 节点时)最小数量的 NUMA 节点上对齐资源。 然而,TopologyManager 无法感知到 NUMA 距离且在做出准入决策时也没有考虑这些距离。 这种限制出现在多插槽以及单插槽多 NUMA 系统中,如果拓扑管理器决定在非相邻 NUMA 节点上对齐资源, 可能导致对执行延迟敏感和高吞吐的应用程序出现明显的性能下降。

Pod 与拓扑管理器策略的交互

考虑以下 Pod 规范中的容器:

spec:
  containers:
  - name: nginx
    image: nginx

该 Pod 以 BestEffort QoS 类运行,因为没有指定资源 requestslimits

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
      requests:
        memory: "100Mi"

由于 requests 数少于 limits,因此该 Pod 以 Burstable QoS 类运行。

如果选择的策略是 none 以外的任何其他策略,拓扑管理器都会评估这些 Pod 的规范。 拓扑管理器会咨询建议提供者,获得拓扑建议。 若策略为 static,则 CPU 管理器策略会返回默认的拓扑建议,因为这些 Pod 并没有显式地请求 CPU 资源。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"

此 Pod 独立使用 CPU 请求量,以 Guaranteed QoS 类运行,因为其 requests 值等于 limits 值。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"

此 Pod 和其他资源共享 CPU 请求量,以 Guaranteed QoS 类运行,因为其 requests 值等于 limits 值。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        example.com/deviceA: "1"
        example.com/deviceB: "1"
      requests:
        example.com/deviceA: "1"
        example.com/deviceB: "1"

因为未指定 CPU 和内存请求,所以 Pod 以 BestEffort QoS 类运行。

拓扑管理器将考虑以上 Pod。拓扑管理器将咨询建议提供者即 CPU 和设备管理器,以获取 Pod 的拓扑提示。 对于独立使用 CPU 请求量的 Guaranteed Pod,static CPU 管理器策略将返回独占 CPU 相关的拓扑提示, 而设备管理器将返回有关所请求设备的提示。

对于与其他资源 CPU 共享请求量的 Guaranteed Pod,static CPU 管理器策略将返回默认的拓扑提示,因为没有独享的 CPU 请求;而设备管理器 则针对所请求的设备返回有关提示。

在上述两种 Guaranteed Pod 的情况中,none CPU 管理器策略会返回默认的拓扑提示。

对于 BestEffort Pod,由于没有 CPU 请求,static CPU 管理器策略将发送默认拓扑提示, 而设备管理器将为每个请求的设备发送提示。

基于此信息,拓扑管理器将为 Pod 计算最佳提示并存储该信息,并且供 提示提供程序在进行资源分配时使用。

已知的局限性

  1. 拓扑管理器所能处理的最大 NUMA 节点个数是 8。若 NUMA 节点数超过 8, 枚举可能的 NUMA 亲和性并为之生成提示时会发生状态爆炸。
  2. 调度器无法感知拓扑,所以有可能一个 Pod 被调度到一个节点之后,会因为拓扑管理器的缘故在该节点上启动失败。

17 - 自定义 DNS 服务

本页说明如何配置 DNS Pod,以及定制集群中 DNS 解析过程。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的集群必须运行 CoreDNS 插件。

你的 Kubernetes 服务器版本必须不低于版本 v1.12. 要获知版本信息,请输入 kubectl version.

介绍

DNS 是使用 插件管理器 集群插件自动启动的 Kubernetes 内置服务。

如果你在使用 Deployment 运行 CoreDNS,则该 Deployment 通常会向外暴露为一个具有 静态 IP 地址 Kubernetes 服务。 kubelet 使用 --cluster-dns=<DNS 服务 IP> 标志将 DNS 解析器的信息传递给每个容器。

DNS 名称也需要域名。你可在 kubelet 中使用 --cluster-domain=<默认本地域名> 标志配置本地域名。

DNS 服务器支持正向查找(A 和 AAAA 记录)、端口发现(SRV 记录)、反向 IP 地址发现(PTR 记录)等。 更多信息,请参见 Service 与 Pod 的 DNS

如果 Pod 的 dnsPolicy 设置为 default,则它将从 Pod 运行所在节点继承名称解析配置。 Pod 的 DNS 解析行为应该与节点相同。 但请参阅已知问题

如果你不想这样做,或者想要为 Pod 使用其他 DNS 配置,则可以使用 kubelet 的 --resolv-conf 标志。将此标志设置为 "" 可以避免 Pod 继承 DNS。 将其设置为有别于 /etc/resolv.conf 的有效文件路径可以设定 DNS 继承不同的配置。

CoreDNS

CoreDNS 是通用的权威 DNS 服务器,可以用作集群 DNS,符合 DNS 规范

CoreDNS ConfigMap 选项

CoreDNS 是模块化且可插拔的 DNS 服务器,每个插件都为 CoreDNS 添加了新功能。 可以通过维护 Corefile,即 CoreDNS 配置文件, 来配置 CoreDNS 服务器。作为一个集群管理员,你可以修改 CoreDNS Corefile 的 ConfigMap, 以更改 DNS 服务发现针对该集群的工作方式。

在 Kubernetes 中,CoreDNS 安装时使用如下默认 Corefile 配置:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health {
            lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
            ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }    

Corefile 配置包括以下 CoreDNS 插件

  • errors:错误记录到标准输出。

  • health:在 http://localhost:8080/health 处提供 CoreDNS 的健康报告。 在这个扩展语法中,lameduck 会使此进程不健康,等待 5 秒后进程被关闭。

  • ready:在端口 8181 上提供的一个 HTTP 端点, 当所有能够表达自身就绪的插件都已就绪时,在此端点返回 200 OK。

  • kubernetes:CoreDNS 将基于服务和 Pod 的 IP 来应答 DNS 查询。 你可以在 CoreDNS 网站找到有关此插件的更多细节

    • 你可以使用 ttl 来定制响应的 TTL。默认值是 5 秒钟。TTL 的最小值可以是 0 秒钟, 最大值为 3600 秒。将 TTL 设置为 0 可以禁止对 DNS 记录进行缓存。
    • pods insecure 选项是为了与 kube-dns 向后兼容。
    • 你可以使用 pods verified 选项,该选项使得仅在相同名字空间中存在具有匹配 IP 的 Pod 时才返回 A 记录。
    • 如果你不使用 Pod 记录,则可以使用 pods disabled 选项。
  • prometheus:CoreDNS 的度量指标值以 Prometheus 格式(也称为 OpenMetrics)在 http://localhost:9153/metrics 上提供。
  • forward: 不在 Kubernetes 集群域内的任何查询都将转发到预定义的解析器 (/etc/resolv.conf)。
  • cache:启用前端缓存。
  • loop:检测简单的转发环,如果发现死循环,则中止 CoreDNS 进程。
  • reload:允许自动重新加载已更改的 Corefile。 编辑 ConfigMap 配置后,请等待两分钟,以使更改生效。
  • loadbalance:这是一个轮转式 DNS 负载均衡器, 它在应答中随机分配 A、AAAA 和 MX 记录的顺序。

你可以通过修改 ConfigMap 来更改默认的 CoreDNS 行为。

使用 CoreDNS 配置存根域和上游域名服务器

CoreDNS 能够使用 forward 插件配置存根域和上游域名服务器。

示例

如果集群操作员在 "10.150.0.1" 处运行了 Consul 域服务器, 且所有 Consul 名称都带有后缀 .consul.local。要在 CoreDNS 中对其进行配置, 集群管理员可以在 CoreDNS 的 ConfigMap 中创建加入以下字段。

consul.local:53 {
    errors
    cache 30
    forward . 10.150.0.1
}

要显式强制所有非集群 DNS 查找通过特定的域名服务器(位于 172.16.0.1),可将 forward 指向该域名服务器,而不是 /etc/resolv.conf

forward .  172.16.0.1

最终的包含默认的 Corefile 配置的 ConfigMap 如下所示:

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . 172.16.0.1
        cache 30
        loop
        reload
        loadbalance
    }
    consul.local:53 {
        errors
        cache 30
        forward . 10.150.0.1
    }    

接下来

18 - 调试 DNS 问题

这篇文章提供了一些关于 DNS 问题诊断的方法。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的集群必须使用了 CoreDNS 插件 或者其前身,kube-dns

你的 Kubernetes 服务器版本必须不低于版本 v1.6. 要获知版本信息,请输入 kubectl version.

创建一个简单的 Pod 作为测试环境

apiVersion: v1
kind: Pod
metadata:
  name: dnsutils
  namespace: default
spec:
  containers:
  - name: dnsutils
    image: registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3
    command:
      - sleep
      - "infinity"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

使用上面的清单来创建一个 Pod:

kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml
pod/dnsutils created

验证其状态:

kubectl get pods dnsutils
NAME      READY     STATUS    RESTARTS   AGE
dnsutils   1/1       Running   0          <some-time>

一旦 Pod 处于运行状态,你就可以在该环境里执行 nslookup。 如果你看到类似下列的内容,则表示 DNS 是正常运行的。

kubectl exec -i -t dnsutils -- nslookup kubernetes.default

输出为:

Server:    10.0.0.10
Address 1: 10.0.0.10

Name:      kubernetes.default
Address 1: 10.0.0.1

如果 nslookup 命令执行失败,请检查下列内容:

先检查本地的 DNS 配置

查看 resolv.conf 文件的内容 (阅读定制 DNS 服务 和 后文的已知问题 ,获取更多信息)

kubectl exec -ti dnsutils -- cat /etc/resolv.conf

验证 search 和 nameserver 的配置是否与下面的内容类似 (注意 search 根据不同的云提供商可能会有所不同):

search default.svc.cluster.local svc.cluster.local cluster.local google.internal c.gce_project_id.internal
nameserver 10.0.0.10
options ndots:5

下列错误表示 CoreDNS (或 kube-dns)插件或者相关服务出现了问题:

kubectl exec -i -t dnsutils -- nslookup kubernetes.default

输出为:

Server:    10.0.0.10
Address 1: 10.0.0.10

nslookup: can't resolve 'kubernetes.default'

或者

kubectl exec -i -t dnsutils -- nslookup kubernetes.default

输出为:

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

nslookup: can't resolve 'kubernetes.default'

检查 DNS Pod 是否运行

使用 kubectl get pods 命令来验证 DNS Pod 是否运行。

kubectl get pods --namespace=kube-system -l k8s-app=kube-dns

输出为:

NAME                       READY     STATUS    RESTARTS   AGE
...
coredns-7b96bf9f76-5hsxb   1/1       Running   0           1h
coredns-7b96bf9f76-mvmmt   1/1       Running   0           1h
...

如果你发现没有 CoreDNS Pod 在运行,或者该 Pod 的状态是 failed 或者 completed, 那可能这个 DNS 插件在你当前的环境里并没有成功部署,你将需要手动去部署它。

检查 DNS Pod 里的错误

使用 kubectl logs 命令来查看 DNS 容器的日志信息。

如查看 CoreDNS 的日志信息:

kubectl logs --namespace=kube-system -l k8s-app=kube-dns

下列是一个正常运行的 CoreDNS 日志信息:

.:53
2018/08/15 14:37:17 [INFO] CoreDNS-1.2.2
2018/08/15 14:37:17 [INFO] linux/amd64, go1.10.3, 2e322f6
CoreDNS-1.2.2
linux/amd64, go1.10.3, 2e322f6
2018/08/15 14:37:17 [INFO] plugin/reload: Running configuration MD5 = 24e6c59e83ce706f07bcc82c31b1ea1c

查看是否日志中有一些可疑的或者意外的消息。

检查是否启用了 DNS 服务

使用 kubectl get service 命令来检查 DNS 服务是否已经启用。

kubectl get svc --namespace=kube-system

输出为:

NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)             AGE
...
kube-dns     ClusterIP   10.0.0.10      <none>        53/UDP,53/TCP        1h
...

如果你已经创建了 DNS 服务,或者该服务应该是默认自动创建的但是它并没有出现, 请阅读调试服务 来获取更多信息。

DNS 的端点公开了吗?

你可以使用 kubectl get endpoints 命令来验证 DNS 的端点是否公开了。

kubectl get endpoints kube-dns --namespace=kube-system
NAME       ENDPOINTS                       AGE
kube-dns   10.180.3.17:53,10.180.3.17:53    1h

如果你没看到对应的端点,请阅读 调试服务的端点部分。

若需要了解更多的 Kubernetes DNS 例子,请在 Kubernetes GitHub 仓库里查看 cluster-dns 示例

DNS 查询有被接收或者执行吗?

你可以通过给 CoreDNS 的配置文件(也叫 Corefile)添加 log 插件来检查查询是否被正确接收。 CoreDNS 的 Corefile 被保存在一个叫 corednsConfigMap 里,使用下列命令来编辑它:

kubectl -n kube-system edit configmap coredns

然后按下面的例子给 Corefile 添加 log

apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        log
        errors
        health
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          upstream
          fallthrough in-addr.arpa ip6.arpa
        }
        prometheus :9153
        forward . /etc/resolv.conf
        cache 30
        loop
        reload
        loadbalance
    }    

保存这些更改后,你可能会需要等待一到两分钟让 Kubernetes 把这些更改应用到 CoreDNS 的 Pod 里。

接下来,发起一些查询并依照前文所述查看日志信息,如果 CoreDNS 的 Pod 接收到这些查询, 你将可以在日志信息里看到它们。

下面是日志信息里的查询例子:

.:53
2018/08/15 14:37:15 [INFO] CoreDNS-1.2.0
2018/08/15 14:37:15 [INFO] linux/amd64, go1.10.3, 2e322f6
CoreDNS-1.2.0
linux/amd64, go1.10.3, 2e322f6
2018/09/07 15:29:04 [INFO] plugin/reload: Running configuration MD5 = 162475cdf272d8aa601e6fe67a6ad42f
2018/09/07 15:29:04 [INFO] Reloading complete
172.17.0.18:41675 - [07/Sep/2018:15:29:11 +0000] 59925 "A IN kubernetes.default.svc.cluster.local. udp 54 false 512" NOERROR qr,aa,rd,ra 106 0.000066649s

CoreDNS 是否有足够的权限?

CoreDNS 必须能够列出 serviceendpoint 相关的资源来正确解析服务名称。

示例错误消息:

2022-03-18T07:12:15.699431183Z [INFO] 10.96.144.227:52299 - 3686 "A IN serverproxy.contoso.net.cluster.local. udp 52 false 512" SERVFAIL qr,aa,rd 145 0.000091221s

首先,获取当前的 ClusterRole system:coredns

kubectl describe clusterrole system:coredns -n kube-system

预期输出:

PolicyRule:
  Resources                        Non-Resource URLs  Resource Names  Verbs
  ---------                        -----------------  --------------  -----
  endpoints                        []                 []              [list watch]
  namespaces                       []                 []              [list watch]
  pods                             []                 []              [list watch]
  services                         []                 []              [list watch]
  endpointslices.discovery.k8s.io  []                 []              [list watch]

如果缺少任何权限,请编辑 ClusterRole 来添加它们:

kubectl edit clusterrole system:coredns -n kube-system

EndpointSlices 权限的插入示例:

...
- apiGroups:
  - discovery.k8s.io
  resources:
  - endpointslices
  verbs:
  - list
  - watch
...

你的服务在正确的名字空间中吗?

未指定名字空间的 DNS 查询仅作用于 Pod 所在的名字空间。

如果 Pod 和服务的名字空间不相同,则 DNS 查询必须指定服务所在的名字空间。

该查询仅限于 Pod 所在的名字空间:

kubectl exec -i -t dnsutils -- nslookup <service-name>

指定名字空间的查询:

kubectl exec -i -t dnsutils -- nslookup <service-name>.<namespace>

要进一步了解名字解析,请查看 Pod 与 Service 的 DNS

已知问题

有些 Linux 发行版本(比如 Ubuntu)默认使用一个本地的 DNS 解析器(systemd-resolved)。 systemd-resolved 会用一个存根文件(Stub File)来覆盖 /etc/resolv.conf 内容, 从而可能在上游服务器中解析域名产生转发环(forwarding loop)。 这个问题可以通过手动指定 kubelet 的 --resolv-conf 标志为正确的 resolv.conf(如果是 systemd-resolved, 则这个文件路径为 /run/systemd/resolve/resolv.conf)来解决。 kubeadm 会自动检测 systemd-resolved 并对应的更改 kubelet 的命令行标志。

Kubernetes 的安装并不会默认配置节点的 resolv.conf 文件来使用集群的 DNS 服务,因为这个配置对于不同的发行版本是不一样的。这个问题应该迟早会被解决的。

Linux 的 libc(又名 glibc)默认将 DNS nameserver 记录限制为 3, 而 Kubernetes 需要使用 1 条 nameserver 记录。 这意味着如果本地的安装已经使用了 3 个 nameserver,那么其中有些条目将会丢失。 要解决此限制,节点可以运行 dnsmasq,以提供更多 nameserver 条目。 你也可以使用 kubelet 的 --resolv-conf 标志来解决这个问题。

如果你使用 Alpine 3.17 或更早版本作为你的基础镜像,DNS 可能会由于 Alpine 的设计问题而无法工作。 在 musl 1.24 版本之前,DNS 存根解析器都没有包括 TCP 回退, 这意味着任何超过 512 字节的 DNS 调用都会失败。请将你的镜像升级到 Alpine 3.18 或更高版本。

接下来

19 - 声明网络策略

本文可以帮助你开始使用 Kubernetes 的 NetworkPolicy API 声明网络策略去管理 Pod 之间的通信

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 v1.8. 要获知版本信息,请输入 kubectl version.

你首先需要有一个支持网络策略的 Kubernetes 集群。已经有许多支持 NetworkPolicy 的网络提供商,包括:

创建一个nginx Deployment 并且通过服务将其暴露

为了查看 Kubernetes 网络策略是怎样工作的,可以从创建一个nginx Deployment 并且通过服务将其暴露开始

kubectl create deployment nginx --image=nginx
deployment.apps/nginx created

将此 Deployment 以名为 nginx 的 Service 暴露出来:

kubectl expose deployment nginx --port=80
service/nginx exposed

上述命令创建了一个带有一个 nginx 的 Deployment,并将之通过名为 nginx 的 Service 暴露出来。名为 nginx 的 Pod 和 Deployment 都位于 default 名字空间内。

kubectl get svc,pod
NAME                        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/kubernetes          10.100.0.1    <none>        443/TCP    46m
service/nginx               10.100.0.16   <none>        80/TCP     33s

NAME                        READY         STATUS        RESTARTS   AGE
pod/nginx-701339712-e0qfq   1/1           Running       0          35s

通过从 Pod 访问服务对其进行测试

你应该可以从其它的 Pod 访问这个新的 nginx 服务。 要从 default 命名空间中的其它 Pod 来访问该服务。可以启动一个 busybox 容器:

kubectl run busybox --rm -ti --image=busybox:1.28 -- /bin/sh

在你的 Shell 中,运行下面的命令:

wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
remote file exists

限制 nginx 服务的访问

如果想限制对 nginx 服务的访问,只让那些拥有标签 access: true 的 Pod 访问它, 那么可以创建一个如下所示的 NetworkPolicy 对象:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          access: "true"

NetworkPolicy 对象的名称必须是一个合法的 DNS 子域名.

为服务指定策略

使用 kubectl 根据上面的 nginx-policy.yaml 文件创建一个 NetworkPolicy:

kubectl apply -f https://k8s.io/examples/service/networking/nginx-policy.yaml
networkpolicy.networking.k8s.io/access-nginx created

测试没有定义访问标签时访问服务

如果你尝试从没有设定正确标签的 Pod 中去访问 nginx 服务,请求将会超时:

kubectl run busybox --rm -ti --image=busybox:1.28 -- /bin/sh

在 Shell 中运行命令:

wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
wget: download timed out

定义访问标签后再次测试

创建一个拥有正确标签的 Pod,你将看到请求是被允许的:

kubectl run busybox --rm -ti --labels="access=true" --image=busybox:1.28 -- /bin/sh

在 Shell 中运行命令:

wget --spider --timeout=1 nginx
Connecting to nginx (10.100.0.16:80)
remote file exists

20 - 开发云控制器管理器

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

一个 Kubernetes 控制平面组件, 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager) 允许将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。

通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑, cloud-controller-manager 组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。

背景

由于云驱动的开发和发布与 Kubernetes 项目本身步调不同,将特定于云环境的代码抽象到 cloud-controller-manager 二进制组件有助于云厂商独立于 Kubernetes 核心代码推进其驱动开发。

Kubernetes 项目提供 cloud-controller-manager 的框架代码,其中包含 Go 语言的接口, 便于你(或者你的云驱动提供者)接驳你自己的实现。这意味着每个云驱动可以通过从 Kubernetes 核心代码导入软件包来实现一个 cloud-controller-manager; 每个云驱动会通过调用 cloudprovider.RegisterCloudProvider 接口来注册其自身实现代码, 从而更新一个用来记录可用云驱动的全局变量。

开发

树外(Out of Tree)

要为你的云环境构建一个树外(Out-of-Tree)云控制器管理器:

  1. 使用满足 cloudprovider.Interface 接口的实现来创建一个 Go 语言包。
  2. 使用来自 Kubernetes 核心代码库的 cloud-controller-manager 中的 main.go 作为 main.go 的模板。如上所述,唯一的区别应该是将导入的云包不同。
  3. main.go 中导入你的云包,确保你的包有一个 init 块来运行 cloudprovider.RegisterCloudProvider

很多云驱动都将其控制器管理器代码以开源代码的形式公开。 如果你在开发一个新的 cloud-controller-manager,你可以选择某个树外(Out-of-Tree) 云控制器管理器作为出发点。

树内(In Tree)

对于树内(In-Tree)驱动,你可以将树内云控制器管理器作为集群中的 DaemonSet 来运行。 有关详细信息,请参阅云控制器管理器管理

21 - 启用/禁用 Kubernetes API

本页展示怎么用集群的 控制平面. 启用/禁用 API 版本。

通过 API 服务器的命令行参数 --runtime-config=api/<version> , 可以开启/关闭某个指定的 API 版本。 此参数的值是一个逗号分隔的 API 版本列表。 此列表中,后面的值可以覆盖前面的值。

命令行参数 runtime-config 支持两个特殊的值(keys):

  • api/all:指所有已知的 API
  • api/legacy:指过时的 API。过时的 API 就是明确地 弃用 的 API。

例如:为了停用除去 v1 版本之外的全部其他 API 版本, 就用参数 --runtime-config=api/all=false,api/v1=true 启动 kube-apiserver

接下来

阅读完整的文档, 以了解 kube-apiserver 组件。

22 - 静态加密机密数据

Kubernetes 中允许允许用户编辑的持久 API 资源数据的所有 API 都支持静态加密。 例如,你可以启用静态加密 Secret。 此静态加密是对 etcd 集群或运行 kube-apiserver 的主机上的文件系统的任何系统级加密的补充。

本页展示如何启用和配置静态 API 数据加密。

准备开始

  • 你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

  • 此任务假设你将 Kubernetes API 服务器组件以静态 Pod 方式运行在每个控制平面节点上。

  • 集群的控制平面必须使用 etcd v3.x(主版本 3,任何次要版本)。

  • 要加密自定义资源,你的集群必须运行 Kubernetes v1.26 或更高版本。

  • 在 Kubernetes v1.27 或更高版本中可以使用通配符匹配资源。

要获知版本信息,请输入 kubectl version.

确定是否已启用静态数据加密

默认情况下,API 服务器将资源的明文表示存储在 etcd 中,没有静态加密。

kube-apiserver 进程使用 --encryption-provider-config 参数指定配置文件的路径, 所指定的配置文件的内容将控制 Kubernetes API 数据在 etcd 中的加密方式。 如果你在运行 kube-apiserver 时没有使用 --encryption-provider-config 命令行参数, 则你未启用静态加密。如果你在运行 kube-apiserver 时使用了 --encryption-provider-config 命令行参数,并且此参数所引用的文件指定 identity 提供程序作为加密提供程序列表中的第一个, 则你未启用静态加密(默认的 identity 提供程序不提供任何机密性保护)。

如果你在运行 kube-apiserver 时使用了 --encryption-provider-config 命令行参数, 并且此参数所引用的文件指定一个不是 identity 的提供程序作为加密提供程序列表中的第一个, 则你已启用静态加密。然而此项检查并未告知你先前向加密存储的迁移是否成功。如果你不确定, 请参阅确保所有相关数据都已加密

理解静态数据加密

---
#
# 注意:这是一个示例配置。请勿将其用于你自己的集群!
#
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example # 自定义资源 API
    providers:
      # 此配置不提供数据机密性。
      # 第一个配置的 provider 正在指定将资源存储为纯文本的 "identity" 机制。
      - identity: {} # 纯文本,换言之未加密
      - aesgcm:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - aescbc:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
            - name: key2
              secret: dGhpcyBpcyBwYXNzd29yZA==
      - secretbox:
          keys:
            - name: key1
              secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
  - resources:
      - events
    providers:
      - identity: {} # 即使如下指定 *.* 也不会加密 events
  - resources:
      - '*.apps' # 通配符匹配需要 Kubernetes 1.27 或更高版本
    providers:
      - aescbc:
          keys:
          - name: key2
            secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
  - resources:
      - '*.*' # 通配符匹配需要 Kubernetes 1.27 或更高版本
    providers:
      - aescbc:
          keys:
          - name: key3
            secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw==

每个 resources 数组项目是一个单独的完整的配置。 resources.resources 字段是应加密的 Kubernetes 资源(例如 Secret、ConfigMap 或其他资源)名称 (resourceresource.group)的数组。

如果自定义资源被添加到 EncryptionConfiguration 并且集群版本为 1.26 或更高版本, 则 EncryptionConfiguration 中提到的任何新创建的自定义资源都将被加密。 在该版本之前存在于 etcd 中的任何自定义资源和配置不会被加密,直到它们被下一次写入到存储为止。 这与内置资源的行为相同。请参阅确保所有 Secret 都已加密一节。

providers 数组是可能的加密提供程序的有序列表,用于你所列出的 API。 每个提供程序支持多个密钥 - 解密时会按顺序尝试这些密钥, 如果这是第一个提供程序,其第一个密钥将被用于加密。

每个条目只能指定一个提供程序类型(可以是 identityaescbc,但不能在同一个项目中同时指定二者)。 列表中的第一个提供程序用于加密写入存储的资源。 当从存储器读取资源时,与存储的数据匹配的所有提供程序将按顺序尝试解密数据。 如果由于格式或密钥不匹配而导致没有提供程序能够读取存储的数据,则会返回一个错误,以防止客户端访问该资源。

EncryptionConfiguration 支持使用通配符指定应加密的资源。 使用 “*.<group>” 加密 group 内的所有资源(例如以上例子中的 “*.apps”)或使用 “*.*” 加密所有资源。“*.” 可用于加密核心组中的所有资源。“*.*” 将加密所有资源,甚至包括 API 服务器启动之后添加的自定义资源。

如果启用了通配符,但想要针对特定资源退出加密,则可以通过添加带有资源名称的新 resources 数组项, 后跟附带 identity 提供商的 providers 数组项。例如,如果启用了 “*.*”, 但想要排除对 events 资源的加密,则应向 resources 数组添加一个新项(以 events 为资源名称), 后跟包含 identity 的提供程序数组。新项应如下所示:

- resources:
    - events
  providers:
    - identity: {}

确保新项列在资源数组中的通配符 “*.*” 项之前,使新项优先。

有关 EncryptionConfiguration 结构体的更多详细信息, 请参阅加密配置 API

可用的提供程序

在为集群的 Kubernetes API 数据配置静态加密之前,你需要选择要使用的提供程序。

下表描述了每个可用的提供程序:

Kubernetes 静态数据加密的提供程序
名称 加密类型 强度 速度 密钥长度
identity N/A N/A N/A
不加密写入的资源。当设置为第一个提供程序时,已加密的资源将在新值写入时被解密。
aescbc 带有 PKCS#7 填充的 AES-CBC 32 字节
由于 CBC 容易受到密文填塞攻击(Padding Oracle Attack),不推荐使用。密钥材料可从控制面主机访问。
aesgcm 带有随机数的 AES-GCM 每写入 200k 次后必须轮换 最快 16、24 或者 32 字节
不建议使用,除非实施了自动密钥轮换方案。密钥材料可从控制面主机访问。
kms v1 (自 Kubernetes 1.28 起弃用) 针对每个资源使用不同的 DEK 来完成信封加密。 最强 慢(kms V2 相比 32 字节
通过数据加密密钥(DEK)使用 AES-GCM 加密数据; DEK 根据 Key Management Service(KMS)中的配置通过密钥加密密钥(Key Encryption Keys,KEK)加密。 密钥轮换方式简单,每次加密都会生成一个新的 DEK,KEK 的轮换由用户控制。
阅读如何配置 KMS V1 提供程序
kms v2 针对每个 API 服务器使用不同的 DEK 来完成信封加密。 最强 32 字节
通过数据加密密钥(DEK)使用 AES-GCM 加密数据; DEK 根据 Key Management Service(KMS)中的配置通过密钥加密密钥(Key Encryption Keys,KEK)加密。 Kubernetes 基于秘密的种子数为每次加密生成一个新的 DEK。 每当 KEK 轮换时,种子也会轮换。 如果使用第三方工具进行密钥管理,这是一个不错的选择。 从 `v1.29` 开始,该功能处于稳定阶段。
阅读如何配置 KMS V2 提供程序
secretbox XSalsa20 和 Poly1305 更快 32 字节
使用相对较新的加密技术,在需要高度评审的环境中可能不被接受。密钥材料可从控制面主机访问。

如果你没有另外指定,identity 提供程序将作为默认选项。 identity 提供程序不会加密存储的数据,并且提供无附加的机密保护。

密钥存储

本地密钥存储

使用本地管理的密钥对 Secret 数据进行加密可以防止 etcd 受到威胁,但无法防范主机受到威胁的情况。 由于加密密钥被存储在主机上的 EncryptionConfiguration YAML 文件中,有经验的攻击者可以访问该文件并提取加密密钥。

托管的(KMS)密钥存储

KMS 提供程序使用封套加密:Kubernetes 使用一个数据密钥来加密资源,然后使用托管的加密服务来加密该数据密钥。 Kubernetes 为每个资源生成唯一的数据密钥。API 服务器将数据密钥的加密版本与密文一起存储在 etcd 中; API 服务器在读取资源时,调用托管的加密服务并提供密文和(加密的)数据密钥。 在托管的加密服务中,提供程序使用密钥加密密钥来解密数据密钥,解密数据密钥后恢复为明文。 在控制平面和 KMS 之间的通信需要在传输过程中提供 TLS 这类保护。

使用封套加密会依赖于密钥加密密钥,此密钥不存储在 Kubernetes 中。 就 KMS 而言,如果攻击者意图未经授权地访问明文值,则需要同时入侵 etcd 第三方 KMS 提供程序。

保护加密密钥

你应该采取适当的措施来保护允许解密的机密信息,无论是本地加密密钥还是允许 API 服务器调用 KMS 的身份验证令牌。

即使你依赖提供商来管理主加密密钥(或多个密钥)的使用和生命周期, 你仍然有责任确保托管加密服务的访问控制和其他安全措施满足你的安全需求。

加密你的数据

生成加密密钥

以下步骤假设你没有使用 KMS,因此这些步骤还假设你需要生成加密密钥。 如果你已有加密密钥,请跳至编写加密配置文件

首先生成新的加密密钥,然后使用 base64 对其进行编码:

生成 32 字节随机密钥并对其进行 base64 编码。你可以使用这个命令:

head -c 32 /dev/urandom | base64

如果你想使用 PC 的内置硬件熵源,可以使用 /dev/hwrng 而不是 /dev/urandom。 并非所有 Linux 设备都提供硬件随机数生成器。

生成 32 字节随机密钥并对其进行 base64 编码。你可以使用此命令:

head -c 32 /dev/urandom | base64

生成 32 字节随机密钥并对其进行 base64 编码。你可以使用此命令:

# 不要在已设置随机数生成器种子的会话中运行此命令。
[Convert]::ToBase64String((1..32|%{[byte](Get-Random -Max 256)}))

复制加密密钥

使用安全的文件传输机制,将该加密密钥的副本提供给所有其他控制平面主机。

至少,使用传输加密 - 例如,安全 shell(SSH)。为了提高安全性, 请在主机之间使用非对称加密,或更改你正在使用的方法,以便依赖 KMS 加密。

编辑加密配置文件

创建一个新的加密配置文件。其内容应类似于:

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example
    providers:
      - aescbc:
          keys:
            - name: key1
              # 参见以下文本了解有关 Secret 值的详情
              secret: <BASE 64 ENCODED SECRET>
      - identity: {} # 这个回退允许读取未加密的 Secret;
                     # 例如,在初始迁移期间

要创建新的加密密钥(不使用 KMS),请参阅生成加密密钥

使用新的加密配置文件

你将需要把新的加密配置文件挂载到 kube-apiserver 静态 Pod。以下是这个操作的示例:

  1. 将新的加密配置文件保存到控制平面节点上的 /etc/kubernetes/enc/enc.yaml

  2. 编辑 kube-apiserver 静态 Pod 的清单:/etc/kubernetes/manifests/kube-apiserver.yaml, 代码范例如下:

    ---
    #
    # 这是一个静态 Pod 的清单片段。
    # 检查是否适用于你的集群和 API 服务器。
    #
    apiVersion: v1
    kind: Pod
    metadata:
    annotations:
     kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.20.30.40:443
    creationTimestamp: null
    labels:
     app.kubernetes.io/component: kube-apiserver
     tier: control-plane
    name: kube-apiserver
    namespace: kube-system
    spec:
    containers:
    - command:
     - kube-apiserver
     ...
     - --encryption-provider-config=/etc/kubernetes/enc/enc.yaml  # 增加这一行
     volumeMounts:
     ...
     - name: enc                           # 增加这一行
       mountPath: /etc/kubernetes/enc      # 增加这一行
       readOnly: true                      # 增加这一行
     ...
    volumes:
    ...
    - name: enc                             # 增加这一行
     hostPath:                             # 增加这一行
       path: /etc/kubernetes/enc           # 增加这一行
       type: DirectoryOrCreate             # 增加这一行
    ...
    
  1. 重启你的 API 服务器。

你现在已经为一个控制平面主机进行了加密。典型的 Kubernetes 集群有多个控制平面主机,因此需要做的事情更多。

重新配置其他控制平面主机

如果你的集群中有多个 API 服务器,应轮流将更改部署到每个 API 服务器。

确保在每个控制平面主机上使用相同的加密配置。

验证数据已被加密

数据在写入 etcd 时会被加密。重新启动你的 kube-apiserver 后,任何新创建或更新的 Secret 或在 EncryptionConfiguration 中配置的其他资源类别都应在存储时被加密。

如果想要检查,你可以使用 etcdctl 命令行程序来检索你的 Secret 数据内容。

以下示例演示了如何对加密 Secret API 进行检查。

  1. 创建一个新的 Secret,名称为 secret1,命名空间为 default

    kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    
  1. 使用 etcdctl 命令行工具,从 etcd 中读取 Secret:

    ETCDCTL_API=3 etcdctl get /registry/secrets/default/secret1 [...] | hexdump -C
    

    这里的 [...] 是用来连接 etcd 服务的额外参数。

    例如:

    ETCDCTL_API=3 etcdctl \
       --cacert=/etc/kubernetes/pki/etcd/ca.crt   \
       --cert=/etc/kubernetes/pki/etcd/server.crt \
       --key=/etc/kubernetes/pki/etcd/server.key  \
       get /registry/secrets/default/secret1 | hexdump -C
    

    输出类似于(有删减):

    00000000  2f 72 65 67 69 73 74 72  79 2f 73 65 63 72 65 74  |/registry/secret|
    00000010  73 2f 64 65 66 61 75 6c  74 2f 73 65 63 72 65 74  |s/default/secret|
    00000020  31 0a 6b 38 73 3a 65 6e  63 3a 61 65 73 63 62 63  |1.k8s:enc:aescbc|
    00000030  3a 76 31 3a 6b 65 79 31  3a c7 6c e7 d3 09 bc 06  |:v1:key1:.l.....|
    00000040  25 51 91 e4 e0 6c e5 b1  4d 7a 8b 3d b9 c2 7c 6e  |%Q...l..Mz.=..|n|
    00000050  b4 79 df 05 28 ae 0d 8e  5f 35 13 2c c0 18 99 3e  |.y..(..._5.,...>|
    [...]
    00000110  23 3a 0d fc 28 ca 48 2d  6b 2d 46 cc 72 0b 70 4c  |#:..(.H-k-F.r.pL|
    00000120  a5 fc 35 43 12 4e 60 ef  bf 6f fe cf df 0b ad 1f  |..5C.N`..o......|
    00000130  82 c4 88 53 02 da 3e 66  ff 0a                    |...S..>f..|
    0000013a
    
  1. 验证存储的密钥前缀是否为 k8s:enc:aescbc:v1:,这表明 aescbc provider 已加密结果数据。 确认 etcd 中显示的密钥名称和上述 EncryptionConfiguration 中指定的密钥名称一致。 在此例中,你可以看到在 etcdEncryptionConfiguration 中使用了名为 key1 的加密密钥。

  2. 通过 API 检索,验证 Secret 是否被正确解密:

    kubectl get secret secret1 -n default -o yaml
    

    其输出应该包含 mykey: bXlkYXRh,其中 mydata 的内容使用 base64 进行加密, 请参阅解密 Secret 了解如何完全解码 Secret 内容。

确保所有相关数据都被加密

仅仅确保新对象被加密通常是不够的:你还希望对已经存储的对象进行加密。

例如,你已经配置了集群,使得 Secret 在写入时进行加密。 为每个 Secret 执行替换操作将加密那些对象保持不变的静态内容。

你可以在集群中的所有 Secret 上进行此项变更:

# 以能够读写所有 Secret 的管理员身份运行此命令
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

上面的命令读取所有 Secret,然后使用相同的数据更新这些 Secret,以便应用服务端加密。

防止纯文本检索

如果你想确保对特定 API 类型的唯一访问是使用加密完成的,你可以移除 API 服务器以明文形式读取该 API 的支持数据的能力。

一旦集群中的所有 Secret 都被加密,你就可以删除加密配置中的 identity 部分。例如:

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <BASE 64 ENCODED SECRET>
      - identity: {} # 删除此行

…然后依次重新启动每个 API 服务器。此更改可防止 API 服务器访问纯文本 Secret,即使是意外访问也是如此。

轮换解密密钥

在不发生停机的情况下更改 Kubernetes 的加密密钥需要多步操作,特别是在有多个 kube-apiserver 进程正在运行的高可用环境中。

  1. 生成一个新密钥并将其添加为所有控制平面节点上当前提供程序的第二个密钥条目
  2. 重新启动所有 kube-apiserver 进程以确保每台服务器都可以使用新密钥加密任何数据
  3. 对新的加密密钥进行安全备份。如果你丢失了此密钥的所有副本,则需要删除用已丢失的密钥加密的所有资源, 并且在静态加密被破坏期间,工作负载可能无法按预期运行。
  4. 将新密钥设置为 keys 数组中的第一个条目,以便将其用于新编写的静态加密
  5. 重新启动所有 kube-apiserver 进程,以确保每个控制平面主机现在使用新密钥进行加密
  6. 作为特权用户,运行 kubectl get secrets --all-namespaces -o json | kubectl replace -f - 以用新密钥加密所有现有的 Secret
  7. 将所有现有 Secret 更新为使用新密钥并对新密钥进行安全备份后,从配置中删除旧的解密密钥。

解密所有数据

此示例演示如何停止静态加密 Secret API。如果你所加密的是其他 API 类型,请调整对应步骤来适配。

要禁用静态加密,请将 identity 提供程序作为加密配置文件中的第一个条目:

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      # 在此列出你之前静态加密的任何其他资源
    providers:
      - identity: {} # 添加此行
      - aescbc:
          keys:
            - name: key1
              secret: <BASE 64 ENCODED SECRET> # 将其保留在适当的位置并确保它位于 "identity" 之后

然后运行以下命令以强制解密所有 Secret:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

将所有现有加密资源替换为不使用加密的支持数据后,你可以从 kube-apiserver 中删除加密设置。

配置自动重新加载

你可以配置加密提供程序配置的自动重新加载。 该设置决定了 API 服务器 是仅在启动时加载一次为 --encryption-provider-config 指定的文件, 还是在每次你更改该文件时都自动加载。 启用此选项可允许你在不重启 API 服务器的情况下更改静态加密所需的密钥。

要允许自动重新加载, 可使用 --encryption-provider-config-automatic-reload=true 运行 API 服务器。

接下来

23 - 解密已静态加密的机密数据

Kubernetes 中允许允许你写入持久性 API 资源数据的所有 API 都支持静态加密。 例如,你可以为 Secret 启用静态加密。 此静态加密是对 etcd 集群或运行 kube-apiserver 的主机上的文件系统的所有系统级加密的补充。

本文介绍如何停止静态加密 API 数据,以便 API 数据以未加密的形式存储。 你可能希望这样做以提高性能;但通常情况下,如果加密某些数据是个好主意,那么继续加密这些数据也是一个好主意。

准备开始

  • 你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

  • 此任务假设你将 Kubernetes API 服务器组件以静态 Pod 方式运行在每个控制平面节点上。

  • 集群的控制平面必须使用 etcd v3.x(主版本 3,任何次要版本)。

  • 要加密自定义资源,你的集群必须运行 Kubernetes v1.26 或更高版本。

  • 你应该有一些已加密的 API 数据。

要获知版本信息,请输入 kubectl version.

确定静态加密是否已被启用

默认情况下,API 服务器使用一个名为 identity 的提供程序来存储资源的明文表示。 默认的 identity 提供程序不提供任何机密性保护。

kube-apiserver 进程接受参数 --encryption-provider-config,该参数指定了配置文件的路径。 如果你指定了一个路径,那么该文件的内容将控制 Kubernetes API 数据在 etcd 中的加密方式。 如果未指定,则表示你未启用静态加密。

该配置文件的格式是 YAML,表示名为 EncryptionConfiguration 的配置 API 类别。 你可以在静态加密配置中查看示例配置。

如果设置了 --encryption-provider-config,检查哪些资源(如 secrets)已配置为进行加密, 并查看所适用的是哪个提供程序。确保该资源类型首选的提供程序 不是 identity; 只有在想要禁用静态加密时,才可将 identity无加密)设置为默认值。 验证资源首选的提供程序是否不是 identity,这意味着写入该类型资源的任何新信息都将按照配置被加密。 如果在任何资源的首选提供程序中看到 identity,这意味着这些资源将以非加密的方式写入 etcd 中。

解密所有数据

本例展示如何停止对 Secret API 进行静态加密。如果你正在加密其他 API 类别,可以相应调整以下步骤。

找到加密配置文件

首先,找到 API 服务器的配置文件。在每个控制平面节点上,kube-apiserver 的静态 Pod 清单指定了一个命令行参数 --encryption-provider-config。你很可能会发现此文件通过 hostPath 卷挂载到静态 Pod 中。 一旦你找到到此卷,就可以在节点文件系统中找到此文件并对其进行检查。

配置 API 服务器以解密对象

要禁用静态加密,将 identity 提供程序设置为加密配置文件中的第一个条目。

例如,如果你现有的 EncryptionConfiguration 文件内容如下:

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            # 你加密时不要使用这个(无效)的示例密钥
            - name: example
              secret: 2KfZgdiq2K0g2YrYpyDYs9mF2LPZhQ==

然后将其更改为:

---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - identity: {} # 增加这一行
      - aescbc:
          keys:
            - name: example
              secret: 2KfZgdiq2K0g2YrYpyDYs9mF2LPZhQ==

并重启此节点上的 kube-apiserver Pod。

重新配置其他控制平面主机

如果你的集群中有多个 API 服务器,应轮流对每个 API 服务器部署这些更改。

确保在每个控制平面主机上使用相同的加密配置。

强制解密

然后运行以下命令强制解密所有 Secret:

# 如果你正在解密不同类别的对象,请相应更改 "secrets"
kubectl get secrets --all-namespaces -o json | kubectl replace -f -

一旦你用未加密的后台数据替换了所有现有的已加密资源,即可从 kube-apiserver 中删除这些加密设置。

要移除的命令行选项为:

  • --encryption-provider-config
  • --encryption-provider-config-automatic-reload

再次重启 kube-apiserver Pod 以应用新的配置。

重新配置其他控制平面主机

如果你的集群中有多个 API 服务器,应再次轮流对每个 API 服务器部署这些更改。

确保在每个控制平面主机上使用相同的加密配置。

接下来

24 - 关键插件 Pod 的调度保证

Kubernetes 核心组件(如 API 服务器、调度器、控制器管理器)在控制平面节点上运行。 但是插件必须在常规集群节点上运行。 其中一些插件对于功能完备的集群至关重要,例如 Heapster、DNS 和 UI。 如果关键插件被逐出(手动或作为升级等其他操作的副作用)或者变成挂起状态,集群可能会停止正常工作。 关键插件进入挂起状态的例子有:集群利用率过高;被逐出的关键插件 Pod 释放了空间,但该空间被之前悬决的 Pod 占用;由于其它原因导致节点上可用资源的总量发生变化。

注意,把某个 Pod 标记为关键 Pod 并不意味着完全避免该 Pod 被逐出;它只能防止该 Pod 变成永久不可用。 被标记为关键性的静态 Pod 不会被逐出。但是,被标记为关键性的非静态 Pod 总是会被重新调度。

标记关键 Pod

要将 Pod 标记为关键性(critical),设置 Pod 的 priorityClassName 为 system-cluster-critical 或者 system-node-criticalsystem-node-critical 是最高级别的可用性优先级,甚至比 system-cluster-critical 更高。

25 - IP Masquerade Agent 用户指南

此页面展示如何配置和启用 ip-masq-agent

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

IP Masquerade Agent 用户指南

ip-masq-agent 配置 iptables 规则以隐藏位于集群节点 IP 地址后面的 Pod 的 IP 地址。 这通常在将流量发送到集群的 Pod CIDR 范围之外的目的地时使用。

关键术语

  • NAT(网络地址转换): 是一种通过修改 IP 地址头中的源和/或目标地址信息将一个 IP 地址重新映射 到另一个 IP 地址的方法。通常由执行 IP 路由的设备执行。
  • 伪装: NAT 的一种形式,通常用于执行多对一地址转换,其中多个源 IP 地址被隐藏在 单个地址后面,该地址通常是执行 IP 路由的设备。在 Kubernetes 中, 这是节点的 IP 地址。
  • CIDR(无类别域间路由): 基于可变长度子网掩码,允许指定任意长度的前缀。 CIDR 引入了一种新的 IP 地址表示方法,现在通常称为 CIDR 表示法, 其中地址或路由前缀后添加一个后缀,用来表示前缀的位数,例如 192.168.2.0/24。
  • 本地链路: 本地链路是仅对网段或主机所连接的广播域内的通信有效的网络地址。 IPv4 的本地链路地址在 CIDR 表示法的地址块 169.254.0.0/16 中定义。

ip-masq-agent 配置 iptables 规则,以便在将流量发送到集群节点的 IP 和集群 IP 范围之外的目标时处理伪装节点或 Pod 的 IP 地址。这本质上隐藏了集群节点 IP 地址后面的 Pod IP 地址。在某些环境中,去往"外部"地址的流量必须从已知的机器地址发出。 例如,在 Google Cloud 中,任何到互联网的流量都必须来自 VM 的 IP。 使用容器时,如 Google Kubernetes Engine,从 Pod IP 发出的流量将被拒绝出站。 为了避免这种情况,我们必须将 Pod IP 隐藏在 VM 自己的 IP 地址后面 - 通常称为"伪装"。 默认情况下,代理配置为将 RFC 1918 指定的三个私有 IP 范围视为非伪装 CIDR。 这些范围是 10.0.0.0/8172.16.0.0/12192.168.0.0/16。 默认情况下,代理还将链路本地地址(169.254.0.0/16)视为非伪装 CIDR。 代理程序配置为每隔 60 秒从 /etc/config/ip-masq-agent 重新加载其配置, 这也是可修改的。

masq/non-masq example

代理配置文件必须使用 YAML 或 JSON 语法编写,并且可能包含三个可选值:

  • nonMasqueradeCIDRsCIDR 表示法中的字符串列表,用于指定不需伪装的地址范围。
  • masqLinkLocal:布尔值(true/false),表示是否为本地链路前缀 169.254.0.0/16 的流量提供伪装。默认为 false。
  • resyncInterval:代理从磁盘重新加载配置的重试时间间隔。 例如 '30s',其中 's' 是秒,'ms' 是毫秒。

10.0.0.0/8、172.16.0.0/12 和 192.168.0.0/16 范围内的流量不会被伪装。 任何其他流量(假设是互联网)将被伪装。 Pod 访问本地目的地的例子,可以是其节点的 IP 地址、另一节点的地址或集群的 IP 地址范围内的一个 IP 地址。 默认情况下,任何其他流量都将伪装。以下条目展示了 ip-masq-agent 的默认使用的规则:

iptables -t nat -L IP-MASQ-AGENT
target     prot opt source               destination
RETURN     all  --  anywhere             169.254.0.0/16       /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             10.0.0.0/8           /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             172.16.0.0/12        /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             192.168.0.0/16       /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
MASQUERADE  all  --  anywhere             anywhere             /* ip-masq-agent: outbound traffic should be subject to MASQUERADE (this match must come after cluster-local CIDR matches) */ ADDRTYPE match dst-type !LOCAL

默认情况下,在 GCE/Google Kubernetes Engine 中,如果启用了网络策略, 或者你使用的集群 CIDR 不在 10.0.0.0/8 范围内, 则 ip-masq-agent 将在你的集群中运行。 如果你在其他环境中运行,可以将 ip-masq-agent DaemonSet 添加到你的集群中。

创建 ip-masq-agent

通过运行以下 kubectl 指令创建 ip-masq-agent:

kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/ip-masq-agent/master/ip-masq-agent.yaml

你必须同时将适当的节点标签应用于集群中希望代理运行的任何节点。

kubectl label nodes my-node node.kubernetes.io/masq-agent-ds-ready=true

更多信息可以通过 ip-masq-agent 文档这里找到。

在大多数情况下,默认的规则集应该足够;但是,如果你的集群不是这种情况,则可以创建并应用 ConfigMap 来自定义受影响的 IP 范围。 例如,要允许 ip-masq-agent 仅作用于 10.0.0.0/8,你可以在一个名为 "config" 的文件中创建以下 ConfigMap

运行以下命令将 ConfigMap 添加到你的集群:

kubectl create configmap ip-masq-agent --from-file=config --namespace=kube-system

这将更新位于 /etc/config/ip-masq-agent 的一个文件,该文件以 resyncInterval 为周期定期检查并应用于集群节点。 重新同步间隔到期后,你应该看到你的更改在 iptables 规则中体现:

iptables -t nat -L IP-MASQ-AGENT
Chain IP-MASQ-AGENT (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             169.254.0.0/16       /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN     all  --  anywhere             10.0.0.0/8           /* ip-masq-agent: cluster-local
MASQUERADE  all  --  anywhere             anywhere             /* ip-masq-agent: outbound traffic should be subject to MASQUERADE (this match must come after cluster-local CIDR matches) */ ADDRTYPE match dst-type !LOCAL

默认情况下,本地链路范围(169.254.0.0/16)也由 ip-masq agent 处理, 该代理设置适当的 iptables 规则。要使 ip-masq-agent 忽略本地链路, 可以在 ConfigMap 中将 masqLinkLocal 设置为 true。

nonMasqueradeCIDRs:
  - 10.0.0.0/8
resyncInterval: 60s
masqLinkLocal: true

26 - 限制存储使用量

此示例演示如何限制一个名字空间中的存储使用量。

演示中用到了以下资源:ResourceQuotaLimitRangePersistentVolumeClaim

准备开始

  • 你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

    要获知版本信息,请输入 kubectl version.

场景:限制存储使用量

集群管理员代表用户群操作集群,该管理员希望控制单个名字空间可以消耗多少存储空间以控制成本。

该管理员想要限制:

  1. 名字空间中持久卷申领(persistent volume claims)的数量
  2. 每个申领(claim)可以请求的存储量
  3. 名字空间可以具有的累计存储量

使用 LimitRange 限制存储请求

LimitRange 添加到名字空间会为存储请求大小强制设置最小值和最大值。 存储是通过 PersistentVolumeClaim 来发起请求的。 执行限制范围控制的准入控制器会拒绝任何高于或低于管理员所设阈值的 PVC。

在此示例中,请求 10Gi 存储的 PVC 将被拒绝,因为它超过了最大 2Gi。

apiVersion: v1
kind: LimitRange
metadata:
  name: storagelimits
spec:
  limits:
  - type: PersistentVolumeClaim
    max:
      storage: 2Gi
    min:
      storage: 1Gi

当底层存储提供程序需要某些最小值时,将会用到所设置最小存储请求值。 例如,AWS EBS volumes 的最低要求为 1Gi。

使用 StorageQuota 限制 PVC 数目和累计存储容量

管理员可以限制某个名字空间中的 PVC 个数以及这些 PVC 的累计容量。 如果 PVC 的数目超过任一上限值,新的 PVC 将被拒绝。

在此示例中,名字空间中的第 6 个 PVC 将被拒绝,因为它超过了最大计数 5。 或者,当与上面的 2Gi 最大容量限制结合在一起时, 意味着 5Gi 的最大配额不能支持 3 个都是 2Gi 的 PVC。 后者实际上是向名字空间请求 6Gi 容量,而该名字空间已经设置上限为 5Gi。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: storagequota
spec:
  hard:
    persistentvolumeclaims: "5"
    requests.storage: "5Gi"

小结

限制范围对象可以用来设置可请求的存储量上限,而资源配额对象则可以通过申领计数和 累计存储容量有效地限制名字空间耗用的存储量。 这两种机制使得集群管理员能够规划其集群存储预算而不会发生任一项目超量分配的风险。

27 - 迁移多副本的控制面以使用云控制器管理器

一个 Kubernetes 控制平面组件, 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager) 允许将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。

通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑, cloud-controller-manager 组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。

背景

作为云驱动提取工作 的一部分,所有特定于云的控制器都必须移出 kube-controller-manager。 所有在 kube-controller-manager 中运行云控制器的现有集群必须迁移到特定于云厂商的 cloud-controller-manager 中运行这些控制器。

领导者迁移(Leader Migration)提供了一种机制,使得 HA 集群可以通过这两个组件之间共享资源锁, 在升级多副本的控制平面时,安全地将“特定于云”的控制器从 kube-controller-manager 迁移到 cloud-controller-manager。 对于单节点控制平面,或者在升级过程中可以容忍控制器管理器不可用的情况,则不需要领导者迁移, 亦可以忽略本指南。

领导者迁移可以通过在 kube-controller-managercloud-controller-manager 上设置 --enable-leader-migration 来启用。 领导者迁移仅在升级期间适用,并且在升级完成后可以安全地禁用或保持启用状态。

本指南将引导你手动将控制平面从内置的云驱动的 kube-controller-manager 升级为 同时运行 kube-controller-managercloud-controller-manager。 如果使用某种工具来部署和管理集群,请参阅对应工具和云驱动的文档以获取迁移的具体说明。

准备开始

假定控制平面正在运行 Kubernetes 版本 N,要升级到版本 N+1。 尽管可以在同一版本内进行迁移,但理想情况下,迁移应作为升级的一部分执行, 以便可以配置的变更可以与发布版本变化对应起来。 N 和 N+1 的确切版本值取决于各个云厂商。例如,如果云厂商构建了一个可与 Kubernetes 1.24 配合使用的 cloud-controller-manager,则 N 可以为 1.23,N+1 可以为 1.24。

控制平面节点应运行 kube-controller-manager 并启用领导者选举,这也是默认设置。 在版本 N 中,树内云驱动必须设置 --cloud-provider 标志,而且 cloud-controller-manager 应该尚未部署。

树外云驱动必须已经构建了一个实现了领导者迁移的 cloud-controller-manager。 如果云驱动导入了 v0.21.0 或更高版本的 k8s.io/cloud-providerk8s.io/controller-manager, 则可以进行领导者迁移。 但是,对 v0.22.0 以下的版本,领导者迁移是一项 Alpha 阶段功能,需要在 cloud-controller-manager 中启用特性门控 ControllerManagerLeaderMigration

本指南假定每个控制平面节点的 kubelet 以静态 Pod 的形式启动 kube-controller-managercloud-controller-manager,静态 Pod 的定义在清单文件中。 如果组件以其他设置运行,请相应地调整这里的步骤。

关于鉴权,本指南假定集群使用 RBAC。如果其他鉴权模式授予 kube-controller-managercloud-controller-manager 组件权限,请以与该模式匹配的方式授予所需的访问权限。

授予访问迁移租约的权限

控制器管理器的默认权限仅允许访问其主租约(Lease)对象。为了使迁移正常进行, 需要授权它访问其他 Lease 对象。

你可以通过修改 system::leader-locking-kube-controller-manager 角色来授予 kube-controller-manager 对 Lease API 的完全访问权限。 本任务指南假定迁移 Lease 的名称为 cloud-provider-extraction-migration

kubectl patch -n kube-system role 'system::leader-locking-kube-controller-manager' -p '{"rules": [ {"apiGroups":[ "coordination.k8s.io"], "resources": ["leases"], "resourceNames": ["cloud-provider-extraction-migration"], "verbs": ["create", "list", "get", "update"] } ]}' --type=merge

system::leader-locking-cloud-controller-manager 角色执行相同的操作。

kubectl patch -n kube-system role 'system::leader-locking-cloud-controller-manager' -p '{"rules": [ {"apiGroups":[ "coordination.k8s.io"], "resources": ["leases"], "resourceNames": ["cloud-provider-extraction-migration"], "verbs": ["create", "list", "get", "update"] } ]}' --type=merge

初始领导者迁移配置

领导者迁移可以选择使用一个表示如何将控制器分配给不同管理器的配置文件。 目前,对于树内云驱动,kube-controller-manager 运行 routeservicecloud-node-lifecycle。以下示例配置显示的是这种分配。

领导者迁移可以不指定配置的情况下启用。请参阅默认配置 以获取更多详细信息。

kind: LeaderMigrationConfiguration
apiVersion: controllermanager.config.k8s.io/v1
leaderName: cloud-provider-extraction-migration
controllerLeaders:
  - name: route
    component: kube-controller-manager
  - name: service
    component: kube-controller-manager
  - name: cloud-node-lifecycle
    component: kube-controller-manager

或者,由于控制器可以在任一控制器管理器下运行,因此将双方的 component 设置为 * 可以使迁移双方的配置文件保持一致。

# 通配符版本
kind: LeaderMigrationConfiguration
apiVersion: controllermanager.config.k8s.io/v1
leaderName: cloud-provider-extraction-migration
controllerLeaders:
  - name: route
    component: *
  - name: service
    component: *
  - name: cloud-node-lifecycle
    component: *

在每个控制平面节点上,请将如上内容保存到 /etc/leadermigration.conf 中, 并更新 kube-controller-manager 清单,以便将文件挂载到容器内的同一位置。 另外,请更新同一清单,添加以下参数:

  • --enable-leader-migration 在控制器管理器上启用领导者迁移
  • --leader-migration-config=/etc/leadermigration.conf 设置配置文件

在每个节点上重新启动 kube-controller-manager。这时,kube-controller-manager 已启用领导者迁移,为迁移准备就绪。

部署云控制器管理器

在版本 N+1 中,如何将控制器分配给不同管理器的预期分配状态可以由新的配置文件表示, 如下所示。请注意,各个 controllerLeaderscomponent 字段从 kube-controller-manager 更改为 cloud-controller-manager。 或者,使用上面提到的通配符版本,它具有相同的效果。

kind: LeaderMigrationConfiguration
apiVersion: controllermanager.config.k8s.io/v1
leaderName: cloud-provider-extraction-migration
controllerLeaders:
  - name: route
    component: cloud-controller-manager
  - name: service
    component: cloud-controller-manager
  - name: cloud-node-lifecycle
    component: cloud-controller-manager

当创建版本 N+1 的控制平面节点时,应将如上内容写入到 /etc/leadermigration.conf。 你需要更新 cloud-controller-manager 的清单,以与版本 N 的 kube-controller-manager 相同的方式挂载配置文件。 类似地,添加 --enable-leader-migration--leader-migration-config=/etc/leadermigration.confcloud-controller-manager 的参数中。

使用已更新的 cloud-controller-manager 清单创建一个新的 N+1 版本的控制平面节点, 同时设置 kube-controller-manager--cloud-provider 标志为 external。 版本为 N+1 的 kube-controller-manager 不能启用领导者迁移, 因为在使用外部云驱动的情况下,它不再运行已迁移的控制器,因此不参与迁移。

请参阅云控制器管理器管理 了解有关如何部署 cloud-controller-manager 的更多细节。

升级控制平面

现在,控制平面同时包含 N 和 N+1 版本的节点。 版本 N 的节点仅运行 kube-controller-manager,而版本 N+1 的节点同时运行 kube-controller-managercloud-controller-manager。 根据配置所指定,已迁移的控制器在版本 N 的 kube-controller-manager 或版本 N+1 的 cloud-controller-manager 下运行,具体取决于哪个控制器管理器拥有迁移租约对象。 任何时候都不会有同一个控制器在两个控制器管理器下运行。

以滚动的方式创建一个新的版本为 N+1 的控制平面节点,并将版本 N 中的一个关闭, 直到控制平面仅包含版本为 N+1 的节点。 如果需要从 N+1 版本回滚到 N 版本,则将 kube-controller-manager 启用了领导者迁移的、 且版本为 N 的节点添加回控制平面,每次替换 N+1 版本中的一个,直到只有版本 N 的节点为止。

(可选)禁用领导者迁移

现在,控制平面已经完成升级,同时运行版本 N+1 的 kube-controller-managercloud-controller-manager。领导者迁移的任务已经结束,可以被安全地禁用以节省一个 Lease 资源。在将来可以安全地重新启用领导者迁移,以完成回滚。

在滚动管理器中,更新 cloud-controller-manager 的清单以同时取消设置 --enable-leader-migration--leader-migration-config= 标志,并删除 /etc/leadermigration.conf 的挂载,最后删除 /etc/leadermigration.conf。 要重新启用领导者迁移,请重新创建配置文件,并将其挂载和启用领导者迁移的标志添加回到 cloud-controller-manager

默认配置

从 Kubernetes 1.22 开始,领导者迁移提供了一个默认配置,它适用于控制器与管理器间默认的分配关系。 可以通过设置 --enable-leader-migration,但不设置 --leader-migration-config= 来启用默认配置。

对于 kube-controller-managercloud-controller-manager,如果没有用参数来启用树内云驱动或者改变控制器属主, 则可以使用默认配置来避免手动创建配置文件。

特殊情况:迁移节点 IPAM 控制器

如果你的云供应商提供了节点 IPAM 控制器的实现,你应该切换到 cloud-controller-manager 中的实现。 通过在其标志中添加 --controllers=*,-nodeipam 来禁用 N+1 版本的 kube-controller-manager 中的节点 IPAM 控制器。 然后将 nodeipam 添加到迁移的控制器列表中。

# 通配符版本,带有 nodeipam
kind: LeaderMigrationConfiguration
apiVersion: controllermanager.config.k8s.io/v1
leaderName: cloud-provider-extraction-migration
controllerLeaders:
  - name: route
    component: *
  - name: service
    component: *
  - name: cloud-node-lifecycle
    component: *
  - name: nodeipam
-   component: *

接下来

28 - 名字空间演练

Kubernetes 名字空间 有助于不同的项目、团队或客户去共享 Kubernetes 集群。

名字空间通过以下方式实现这点:

  1. 名字设置作用域.
  2. 为集群中的部分资源关联鉴权和策略的机制。

使用多个名字空间是可选的。

此示例演示了如何使用 Kubernetes 名字空间细分集群。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

环境准备

此示例作如下假设:

  1. 你已拥有一个配置好的 Kubernetes 集群
  2. 你已对 Kubernetes 的 Pod服务Deployment 有基本理解。

理解默认名字空间

默认情况下,Kubernetes 集群会在配置集群时实例化一个默认名字空间,用以存放集群所使用的默认 Pod、Service 和 Deployment 集合。

假设你有一个新的集群,你可以通过执行以下操作来检查可用的名字空间:

kubectl get namespaces
NAME      STATUS    AGE
default   Active    13m

创建新的名字空间

在本练习中,我们将创建两个额外的 Kubernetes 名字空间来保存我们的内容。

我们假设一个场景,某组织正在使用共享的 Kubernetes 集群来支持开发和生产:

开发团队希望在集群中维护一个空间,以便他们可以查看用于构建和运行其应用程序的 Pod、Service 和 Deployment 列表。在这个空间里,Kubernetes 资源被自由地加入或移除, 对谁能够或不能修改资源的限制被放宽,以实现敏捷开发。

运维团队希望在集群中维护一个空间,以便他们可以强制实施一些严格的规程, 对谁可以或谁不可以操作运行生产站点的 Pod、Service 和 Deployment 集合进行控制。

该组织可以遵循的一种模式是将 Kubernetes 集群划分为两个名字空间:developmentproduction

让我们创建两个新的名字空间来保存我们的工作。

文件 namespace-dev.yaml 描述了 development 名字空间:

apiVersion: v1
kind: Namespace
metadata:
  name: development
  labels:
    name: development

使用 kubectl 创建 development 名字空间。

kubectl create -f https://k8s.io/examples/admin/namespace-dev.yaml

将下列的内容保存到文件 namespace-prod.yaml 中, 这些内容是对 production 名字空间的描述:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    name: production

让我们使用 kubectl 创建 production 名字空间。

kubectl create -f https://k8s.io/examples/admin/namespace-prod.yaml

为了确保一切正常,我们列出集群中的所有名字空间。

kubectl get namespaces --show-labels
NAME          STATUS    AGE       LABELS
default       Active    32m       <none>
development   Active    29s       name=development
production    Active    23s       name=production

在每个名字空间中创建 Pod

Kubernetes 名字空间为集群中的 Pod、Service 和 Deployment 提供了作用域。

与一个名字空间交互的用户不会看到另一个名字空间中的内容。

为了演示这一点,让我们在 development 名字空间中启动一个简单的 Deployment 和 Pod。

我们首先检查一下当前的上下文:

kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://130.211.122.180
  name: lithe-cocoa-92103_kubernetes
contexts:
- context:
    cluster: lithe-cocoa-92103_kubernetes
    user: lithe-cocoa-92103_kubernetes
  name: lithe-cocoa-92103_kubernetes
current-context: lithe-cocoa-92103_kubernetes
kind: Config
preferences: {}
users:
- name: lithe-cocoa-92103_kubernetes
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
    token: 65rZW78y8HbwXXtSXuUw9DbP4FLjHi4b
- name: lithe-cocoa-92103_kubernetes-basic-auth
  user:
    password: h5M0FtUUIflBSdI7
    username: admin
kubectl config current-context
lithe-cocoa-92103_kubernetes

下一步是为 kubectl 客户端定义一个上下文,以便在每个名字空间中工作。 "cluster" 和 "user" 字段的值将从当前上下文中复制。

kubectl config set-context dev --namespace=development \
  --cluster=lithe-cocoa-92103_kubernetes \
  --user=lithe-cocoa-92103_kubernetes

kubectl config set-context prod --namespace=production \
  --cluster=lithe-cocoa-92103_kubernetes \
  --user=lithe-cocoa-92103_kubernetes

默认情况下,上述命令会添加两个上下文到 .kube/config 文件中。 你现在可以查看上下文并根据你希望使用的名字空间并在这两个新的请求上下文之间切换。

查看新的上下文:

kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://130.211.122.180
  name: lithe-cocoa-92103_kubernetes
contexts:
- context:
    cluster: lithe-cocoa-92103_kubernetes
    user: lithe-cocoa-92103_kubernetes
  name: lithe-cocoa-92103_kubernetes
- context:
    cluster: lithe-cocoa-92103_kubernetes
    namespace: development
    user: lithe-cocoa-92103_kubernetes
  name: dev
- context:
    cluster: lithe-cocoa-92103_kubernetes
    namespace: production
    user: lithe-cocoa-92103_kubernetes
  name: prod
current-context: lithe-cocoa-92103_kubernetes
kind: Config
preferences: {}
users:
- name: lithe-cocoa-92103_kubernetes
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
    token: 65rZW78y8HbwXXtSXuUw9DbP4FLjHi4b
- name: lithe-cocoa-92103_kubernetes-basic-auth
  user:
    password: h5M0FtUUIflBSdI7
    username: admin

让我们切换到 development 名字空间进行操作。

kubectl config use-context dev

你可以使用下列命令验证当前上下文:

kubectl config current-context
dev

此时,我们从命令行向 Kubernetes 集群发出的所有请求都限定在 development 名字空间中。

让我们创建一些内容。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: snowflake
  name: snowflake
spec:
  replicas: 2
  selector:
    matchLabels:
      app: snowflake
  template:
    metadata:
      labels:
        app: snowflake
    spec:
      containers:
      - image: registry.k8s.io/serve_hostname
        imagePullPolicy: Always
        name: snowflake

应用清单文件来创建 Deployment。

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

我们创建了一个副本大小为 2 的 Deployment,该 Deployment 运行名为 snowflake 的 Pod, 其中包含一个仅提供主机名服务的基本容器。

kubectl get deployment
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
snowflake    2/2     2            2           2m
kubectl get pods -l app=snowflake
NAME                         READY     STATUS    RESTARTS   AGE
snowflake-3968820950-9dgr8   1/1       Running   0          2m
snowflake-3968820950-vgc4n   1/1       Running   0          2m

这很棒,开发人员可以做他们想要的事情,而不必担心影响 production 名字空间中的内容。

让我们切换到 production 名字空间,展示一个名字空间中的资源如何对另一个名字空间不可见。

kubectl config use-context prod

production 名字空间应该是空的,下列命令应该返回的内容为空。

kubectl get deployment
kubectl get pods

生产环境需要以放牛的方式运维,让我们创建一些名为 cattle 的 Pod。

kubectl create deployment cattle --image=registry.k8s.io/serve_hostname --replicas=5
kubectl get deployment
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
cattle       5/5     5            5           10s
kubectl get pods -l run=cattle
NAME                      READY     STATUS    RESTARTS   AGE
cattle-2263376956-41xy6   1/1       Running   0          34s
cattle-2263376956-kw466   1/1       Running   0          34s
cattle-2263376956-n4v97   1/1       Running   0          34s
cattle-2263376956-p5p3i   1/1       Running   0          34s
cattle-2263376956-sxpth   1/1       Running   0          34s

此时,应该很清楚的展示了用户在一个名字空间中创建的资源对另一个名字空间是不可见的。

随着 Kubernetes 中的策略支持的发展,我们将扩展此场景,以展示如何为每个名字空间提供不同的授权规则。

29 - 操作 Kubernetes 中的 etcd 集群

etcd 是 一致且高可用的键值存储,用作 Kubernetes 所有集群数据的后台数据库。

如果你的 Kubernetes 集群使用 etcd 作为其后台数据库, 请确保你针对这些数据有一份 备份计划。

你可以在官方文档中找到有关 etcd 的深入知识。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

你需要有一个 Kubernetes 集群,并且必须配置 kubectl 命令行工具以与你的集群通信。 建议在至少有两个不充当控制平面的节点上运行此任务。如果你还没有集群, 你可以使用 minikube 创建一个。

先决条件

  • 运行的 etcd 集群个数成员为奇数。

  • etcd 是一个 leader-based 分布式系统。确保主节点定期向所有从节点发送心跳,以保持集群稳定。

  • 确保不发生资源不足。

    集群的性能和稳定性对网络和磁盘 I/O 非常敏感。任何资源匮乏都会导致心跳超时, 从而导致集群的不稳定。不稳定的情况表明没有选出任何主节点。 在这种情况下,集群不能对其当前状态进行任何更改,这意味着不能调度新的 Pod。

  • 保持 etcd 集群的稳定对 Kubernetes 集群的稳定性至关重要。 因此,请在专用机器或隔离环境上运行 etcd 集群, 以满足所需资源需求

  • 在生产环境中运行的 etcd 最低推荐版本为 3.4.22+3.5.6+

资源需求

使用有限的资源运行 etcd 只适合测试目的。为了在生产中部署,需要先进的硬件配置。 在生产中部署 etcd 之前,请查看所需资源参考文档

启动 etcd 集群

本节介绍如何启动单节点和多节点 etcd 集群。

单节点 etcd 集群

只为测试目的使用单节点 etcd 集群。

  1. 运行以下命令:

    etcd --listen-client-urls=http://$PRIVATE_IP:2379 \
       --advertise-client-urls=http://$PRIVATE_IP:2379
    
  2. 使用参数 --etcd-servers=$PRIVATE_IP:2379 启动 Kubernetes API 服务器。

    确保将 PRIVATE_IP 设置为 etcd 客户端 IP。

多节点 etcd 集群

出于耐用性和高可用性考量,在生产环境中应以多节点集群的方式运行 etcd,并且定期备份。 建议在生产环境中使用五个成员的集群。 有关该内容的更多信息,请参阅常见问题文档

可以通过静态成员信息或动态发现的方式配置 etcd 集群。 有关集群的详细信息,请参阅 etcd 集群文档

例如,考虑运行以下客户端 URL 的五个成员的 etcd 集群:http://$IP1:2379http://$IP2:2379http://$IP3:2379http://$IP4:2379http://$IP5:2379。 要启动 Kubernetes API 服务器:

  1. 运行以下命令:

    etcd --listen-client-urls=http://$IP1:2379,http://$IP2:2379,http://$IP3:2379,http://$IP4:2379,http://$IP5:2379 --advertise-client-urls=http://$IP1:2379,http://$IP2:2379,http://$IP3:2379,http://$IP4:2379,http://$IP5:2379
    
  2. 使用参数 --etcd-servers=$IP1:2379,$IP2:2379,$IP3:2379,$IP4:2379,$IP5:2379 启动 Kubernetes API 服务器。

    确保将 IP<n> 变量设置为客户端 IP 地址。

使用负载均衡器的多节点 etcd 集群

要运行负载均衡的 etcd 集群:

  1. 建立一个 etcd 集群。
  2. 在 etcd 集群前面配置负载均衡器。例如,让负载均衡器的地址为 $LB
  3. 使用参数 --etcd-servers=$LB:2379 启动 Kubernetes API 服务器。

加固 etcd 集群

对 etcd 的访问相当于集群中的 root 权限,因此理想情况下只有 API 服务器才能访问它。 考虑到数据的敏感性,建议只向需要访问 etcd 集群的节点授予权限。

想要确保 etcd 的安全,可以设置防火墙规则或使用 etcd 提供的安全特性,这些安全特性依赖于 x509 公钥基础设施(PKI)。 首先,通过生成密钥和证书对来建立安全的通信通道。 例如,使用密钥对 peer.keypeer.cert 来保护 etcd 成员之间的通信, 而 client.keyclient.cert 用于保护 etcd 与其客户端之间的通信。 请参阅 etcd 项目提供的示例脚本, 以生成用于客户端身份验证的密钥对和 CA 文件。

安全通信

若要使用安全对等通信对 etcd 进行配置,请指定参数 --peer-key-file=peer.key--peer-cert-file=peer.cert,并使用 HTTPS 作为 URL 模式。

类似地,要使用安全客户端通信对 etcd 进行配置,请指定参数 --key-file=k8sclient.key--cert-file=k8sclient.cert,并使用 HTTPS 作为 URL 模式。 使用安全通信的客户端命令的示例:

ETCDCTL_API=3 etcdctl --endpoints 10.2.0.9:2379 \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  member list

限制 etcd 集群的访问

配置安全通信后,限制只有 Kubernetes API 服务器可以访问 etcd 集群。使用 TLS 身份验证来完成此任务。

例如,考虑由 CA etcd.ca 信任的密钥对 k8sclient.keyk8sclient.cert。 当 etcd 配置为 --client-cert-auth 和 TLS 时,它使用系统 CA 或由 --trusted-ca-file 参数传入的 CA 验证来自客户端的证书。指定参数 --client-cert-auth=true--trusted-ca-file=etcd.ca 将限制对具有证书 k8sclient.cert 的客户端的访问。

一旦正确配置了 etcd,只有具有有效证书的客户端才能访问它。要让 Kubernetes API 服务器访问, 可以使用参数 --etcd-certfile=k8sclient.cert--etcd-keyfile=k8sclient.key--etcd-cafile=ca.cert 配置。

替换失败的 etcd 成员

etcd 集群通过容忍少数成员故障实现高可用性。 但是,要改善集群的整体健康状况,请立即替换失败的成员。当多个成员失败时,逐个替换它们。 替换失败成员需要两个步骤:删除失败成员和添加新成员。

虽然 etcd 在内部保留唯一的成员 ID,但建议为每个成员使用唯一的名称,以避免人为错误。 例如,考虑一个三成员的 etcd 集群。假定 URL 分别为:member1=http://10.0.0.1member2=http://10.0.0.2member3=http://10.0.0.3。当 member1 失败时,将其替换为 member4=http://10.0.0.4

  1. 获取失败的 member1 的成员 ID:

    etcdctl --endpoints=http://10.0.0.2,http://10.0.0.3 member list
    

    显示以下信息:

    8211f1d0f64f3269, started, member1, http://10.0.0.1:2380, http://10.0.0.1:2379
    91bc3c398fb3c146, started, member2, http://10.0.0.2:2380, http://10.0.0.2:2379
    fd422379fda50e48, started, member3, http://10.0.0.3:2380, http://10.0.0.3:2379
    
  1. 执行以下操作之一:

    1. 如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信, 请从 --etcd-servers 标志中移除删除失败的成员,然后重新启动每个 Kubernetes API 服务器。
    2. 如果每个 Kubernetes API 服务器都与单个 etcd 成员通信, 则停止与失败的 etcd 通信的 Kubernetes API 服务器。
  1. 停止故障节点上的 etcd 服务器。除了 Kubernetes API 服务器之外的其他客户端可能会造成流向 etcd 的流量, 可以停止所有流量以防止写入数据目录。
  1. 移除失败的成员:

    etcdctl member remove 8211f1d0f64f3269
    

    显示以下信息:

    Removed member 8211f1d0f64f3269 from cluster
    
  1. 增加新成员:

    etcdctl member add member4 --peer-urls=http://10.0.0.4:2380
    

    显示以下信息:

    Member 2be1eb8f84b7f63e added to cluster ef37ad9dc622a7c4
    
  1. 在 IP 为 10.0.0.4 的机器上启动新增加的成员:

    export ETCD_NAME="member4"
    export ETCD_INITIAL_CLUSTER="member2=http://10.0.0.2:2380,member3=http://10.0.0.3:2380,member4=http://10.0.0.4:2380"
    export ETCD_INITIAL_CLUSTER_STATE=existing
    etcd [flags]
    
  1. 执行以下操作之一:

    1. 如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信, 则将新增的成员添加到 --etcd-servers 标志,然后重新启动每个 Kubernetes API 服务器。
    2. 如果每个 Kubernetes API 服务器都与单个 etcd 成员通信,请启动在第 2 步中停止的 Kubernetes API 服务器。 然后配置 Kubernetes API 服务器客户端以再次将请求路由到已停止的 Kubernetes API 服务器。 这通常可以通过配置负载均衡器来完成。

有关集群重新配置的详细信息,请参阅 etcd 重构文档

备份 etcd 集群

所有 Kubernetes 对象都存储在 etcd 上。 定期备份 etcd 集群数据对于在灾难场景(例如丢失所有控制平面节点)下恢复 Kubernetes 集群非常重要。 快照文件包含所有 Kubernetes 状态和关键信息。为了保证敏感的 Kubernetes 数据的安全,可以对快照文件进行加密。

备份 etcd 集群可以通过两种方式完成:etcd 内置快照和卷快照。

内置快照

etcd 支持内置快照。快照可以从使用 etcdctl snapshot save 命令的活动成员中获取, 也可以通过从 etcd 数据目录 复制 member/snap/db 文件,该 etcd 数据目录目前没有被 etcd 进程使用。获取快照不会影响成员的性能。

下面是一个示例,用于获取 $ENDPOINT 所提供的键空间的快照到文件 snapshot.db

ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshot.db

验证快照:

ETCDCTL_API=3 etcdctl --write-out=table snapshot status snapshot.db
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| fe01cf57 |       10 |          7 | 2.1 MB     |
+----------+----------+------------+------------+

卷快照

如果 etcd 运行在支持备份的存储卷(如 Amazon Elastic Block 存储)上,则可以通过获取存储卷的快照来备份 etcd 数据。

使用 etcdctl 选项的快照

我们还可以使用 etcdctl 提供的各种选项来制作快照。例如:

ETCDCTL_API=3 etcdctl -h 

列出 etcdctl 可用的各种选项。例如,你可以通过指定端点、证书等来制作快照,如下所示:

ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=<trusted-ca-file> --cert=<cert-file> --key=<key-file> \
  snapshot save <backup-file-location>

可以从 etcd Pod 的描述中获得 trusted-ca-filecert-filekey-file

为 etcd 集群扩容

通过交换性能,对 etcd 集群扩容可以提高可用性。缩放不会提高集群性能和能力。 一般情况下不要扩大或缩小 etcd 集群的集合。不要为 etcd 集群配置任何自动缩放组。 强烈建议始终在任何官方支持的规模上运行生产 Kubernetes 集群时使用静态的五成员 etcd 集群。

合理的扩展是在需要更高可靠性的情况下,将三成员集群升级为五成员集群。 请参阅 etcd 重构文档 以了解如何将成员添加到现有集群中的信息。

恢复 etcd 集群

etcd 支持从 major.minor 或其他不同 patch 版本的 etcd 进程中获取的快照进行恢复。 还原操作用于恢复失败的集群的数据。

在启动还原操作之前,必须有一个快照文件。它可以是来自以前备份操作的快照文件, 也可以是来自剩余数据目录的快照文件。

在恢复集群时,使用 --data-dir 选项来指定集群应被恢复到哪个文件夹。

ETCDCTL_API=3 etcdctl --data-dir <data-dir-location> snapshot restore snapshot.db

其中 <data-dir-location> 是将在恢复过程中创建的目录。

另一个例子是先导出 ETCDCTL_API 环境变量:

export ETCDCTL_API=3
etcdctl --data-dir <data-dir-location> snapshot restore snapshot.db

如果 <data-dir-location> 与之前的文件夹相同,请先删除此文件夹并停止 etcd 进程,再恢复集群。 否则,需要在恢复后更改 etcd 配置并重新启动 etcd 进程才能使用新的数据目录。

有关从快照文件还原集群的详细信息和示例,请参阅 etcd 灾难恢复文档

如果还原的集群的访问 URL 与前一个集群不同,则必须相应地重新配置 Kubernetes API 服务器。 在本例中,使用参数 --etcd-servers=$NEW_ETCD_CLUSTER 而不是参数 --etcd-servers=$OLD_ETCD_CLUSTER 重新启动 Kubernetes API 服务器。用相应的 IP 地址替换 $NEW_ETCD_CLUSTER$OLD_ETCD_CLUSTER。 如果在 etcd 集群前面使用负载均衡,则可能需要更新负载均衡器。

如果大多数 etcd 成员永久失败,则认为 etcd 集群失败。在这种情况下,Kubernetes 不能对其当前状态进行任何更改。 虽然已调度的 Pod 可能继续运行,但新的 Pod 无法调度。在这种情况下, 恢复 etcd 集群并可能需要重新配置 Kubernetes API 服务器以修复问题。

升级 etcd 集群

有关 etcd 升级的更多详细信息,请参阅 etcd 升级文档。

维护 etcd 集群

有关 etcd 维护的更多详细信息,请参阅 etcd 维护文档。

你还可以将碎片整理工具作为 Kubernetes CronJob 运行,以确保定期进行碎片整理。 有关详细信息,请参阅 etcd-defrag-cronjob.yaml

30 - 为系统守护进程预留计算资源

Kubernetes 的节点可以按照 Capacity 调度。默认情况下 pod 能够使用节点全部可用容量。 这是个问题,因为节点自己通常运行了不少驱动 OS 和 Kubernetes 的系统守护进程。 除非为这些系统守护进程留出资源,否则它们将与 Pod 争夺资源并导致节点资源短缺问题。

kubelet 公开了一个名为 'Node Allocatable' 的特性,有助于为系统守护进程预留计算资源。 Kubernetes 推荐集群管理员按照每个节点上的工作负载密度配置 'Node Allocatable'。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 1.8. 要获知版本信息,请输入 kubectl version.

你的 kubernetes 服务器版本必须至少是 1.17 版本,才能使用 kubelet 命令行选项 --reserved-cpus 设置显式预留 CPU 列表

节点可分配资源

节点容量

Kubernetes 节点上的 'Allocatable' 被定义为 Pod 可用计算资源量。 调度器不会超额申请 'Allocatable'。 目前支持 'CPU'、'memory' 和 'ephemeral-storage' 这几个参数。

可分配的节点暴露为 API 中 v1.Node 对象的一部分,也是 CLI 中 kubectl describe node 的一部分。

kubelet 中,可以为两类系统守护进程预留资源。

启用 QoS 和 Pod 级别的 cgroups

为了恰当地在节点范围实施节点可分配约束,你必须通过 --cgroups-per-qos 标志启用新的 cgroup 层次结构。这个标志是默认启用的。 启用后,kubelet 将在其管理的 cgroup 层次结构中创建所有终端用户的 Pod。

配置 cgroup 驱动

kubelet 支持在主机上使用 cgroup 驱动操作 cgroup 层次结构。 该驱动通过 --cgroup-driver 标志进行配置。

支持的参数值如下:

  • cgroupfs 是默认的驱动,在主机上直接操作 cgroup 文件系统以对 cgroup 沙箱进行管理。
  • systemd 是可选的驱动,使用 init 系统支持的资源的瞬时切片管理 cgroup 沙箱。

取决于相关容器运行时的配置,操作员可能需要选择一个特定的 cgroup 驱动来保证系统正常运行。 例如,如果操作员使用 containerd 运行时提供的 systemd cgroup 驱动时, 必须配置 kubelet 使用 systemd cgroup 驱动。

Kube 预留值

  • Kubelet 标志--kube-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi][,][pid=1000]
  • Kubelet 标志--kube-reserved-cgroup=

kube-reserved 用来给诸如 kubelet、容器运行时、节点问题监测器等 Kubernetes 系统守护进程记述其资源预留值。 该配置并非用来给以 Pod 形式运行的系统守护进程预留资源。kube-reserved 通常是节点上 Pod 密度 的函数。

除了 cpu内存ephemeral-storage 之外,pid 可用来指定为 Kubernetes 系统守护进程预留指定数量的进程 ID。

要选择性地对 Kubernetes 系统守护进程上执行 kube-reserved 保护,需要把 kubelet 的 --kube-reserved-cgroup 标志的值设置为 kube 守护进程的父控制组。

推荐将 Kubernetes 系统守护进程放置于顶级控制组之下(例如 systemd 机器上的 runtime.slice)。 理想情况下每个系统守护进程都应该在其自己的子控制组中运行。 请参考这个设计方案, 进一步了解关于推荐控制组层次结构的细节。

请注意,如果 --kube-reserved-cgroup 不存在,Kubelet 将 不会 创建它。 如果指定了一个无效的 cgroup,Kubelet 将会无法启动。就 systemd cgroup 驱动而言, 你要为所定义的 cgroup 设置名称时要遵循特定的模式: 所设置的名字应该是你为 --kube-reserved-cgroup 所给的参数值加上 .slice 后缀。

系统预留值

  • Kubelet 标志--system-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi][,][pid=1000]
  • Kubelet 标志--system-reserved-cgroup=

system-reserved 用于为诸如 sshdudev 等系统守护进程记述其资源预留值。 system-reserved 也应该为 kernel 预留 内存,因为目前 kernel 使用的内存并不记在 Kubernetes 的 Pod 上。 同时还推荐为用户登录会话预留资源(systemd 体系中的 user.slice)。

除了 cpu内存ephemeral-storage 之外,pid 可用来指定为 Kubernetes 系统守护进程预留指定数量的进程 ID。

要想为系统守护进程上可选地实施 system-reserved 约束,请指定 kubelet 的 --system-reserved-cgroup 标志值为 OS 系统守护进程的父级控制组。

推荐将 OS 系统守护进程放在一个顶级控制组之下(例如 systemd 机器上的 system.slice)。

请注意,如果 --system-reserved-cgroup 不存在,kubelet 不会 创建它。 如果指定了无效的 cgroup,kubelet 将会失败。就 systemd cgroup 驱动而言, 你在指定 cgroup 名字时要遵循特定的模式: 该名字应该是你为 --system-reserved-cgroup 参数所设置的值加上 .slice 后缀。

显式预留的 CPU 列表

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

Kubelet 标志: --reserved-cpus=0-3 KubeletConfiguration 标志reservedSystemCPUs: 0-3

reserved-cpus 旨在为操作系统守护程序和 Kubernetes 系统守护程序预留一组明确指定编号的 CPU。 reserved-cpus 适用于不打算针对 cpuset 资源为操作系统守护程序和 Kubernetes 系统守护程序定义独立的顶级 cgroups 的系统。 如果 Kubelet 没有 指定参数 --system-reserved-cgroup--kube-reserved-cgroup, 则 reserved-cpus 的设置将优先于 --kube-reserved--system-reserved 选项。

此选项是专门为电信/NFV 用例设计的,在这些用例中不受控制的中断或计时器可能会影响其工作负载性能。 你可以使用此选项为系统或 Kubernetes 守护程序以及中断或计时器显式定义 cpuset, 这样系统上的其余 CPU 可以专门用于工作负载,因不受控制的中断或计时器的影响得以降低。 要将系统守护程序、Kubernetes 守护程序和中断或计时器移动到此选项定义的显式 cpuset 上,应使用 Kubernetes 之外的其他机制。 例如:在 CentOS 系统中,可以使用 tuned 工具集来执行此操作。

驱逐阈值

Kubelet 标志--eviction-hard=[memory.available<500Mi]

节点级别的内存压力将导致系统内存不足,这将影响到整个节点及其上运行的所有 Pod。 节点可以暂时离线直到内存已经回收为止。为了防止系统内存不足(或减少系统内存不足的可能性), kubelet 提供了资源不足管理。 驱逐操作只支持 memoryephemeral-storage。 通过 --eviction-hard 标志预留一些内存后,当节点上的可用内存降至预留值以下时, kubelet 将尝试驱逐 Pod。 如果节点上不存在系统守护进程,Pod 将不能使用超过 capacity-eviction-hard 所指定的资源量。 因此,为驱逐而预留的资源对 Pod 是不可用的。

实施节点可分配约束

Kubelet 标志--enforce-node-allocatable=pods[,][system-reserved][,][kube-reserved]

调度器将 'Allocatable' 视为 Pod 可用的 capacity(资源容量)。

kubelet 默认对 Pod 执行 'Allocatable' 约束。 无论何时,如果所有 Pod 的总用量超过了 'Allocatable',驱逐 Pod 的措施将被执行。 有关驱逐策略的更多细节可以在节点压力驱逐页找到。 可通过设置 kubelet --enforce-node-allocatable 标志值为 pods 控制这个措施。

可选地,通过在同一标志中同时指定 kube-reservedsystem-reserved 值, 可以使 kubelet 强制实施 kube-reservedsystem-reserved 约束。 请注意,要想执行 kube-reserved 或者 system-reserved 约束, 需要对应设置 --kube-reserved-cgroup 或者 --system-reserved-cgroup

一般原则

系统守护进程一般会被按照类似 Guaranteed 的 Pod 一样对待。 系统守护进程可以在与其对应的控制组中出现突发资源用量,这一行为要作为 Kubernetes 部署的一部分进行管理。 例如,kubelet 应该有它自己的控制组并和容器运行时共享 kube-reserved 资源。 不过,如果执行了 kube-reserved 约束,则 kubelet 不可出现突发负载并用光节点的所有可用资源。

在执行 system-reserved 预留策略时请加倍小心,因为它可能导致节点上的关键系统服务出现 CPU 资源短缺、 因为内存不足而被终止或者无法在节点上创建进程。 建议只有当用户详尽地描述了他们的节点以得出精确的估计值, 并且对该组中进程因内存不足而被杀死时,有足够的信心将其恢复时, 才可以强制执行 system-reserved 策略。

  • 作为起步,可以先针对 pods 上执行 'Allocatable' 约束。
  • 一旦用于追踪系统守护进程的监控和告警的机制到位,可尝试基于用量估计的方式执行 kube-reserved 策略。
  • 随着时间推进,在绝对必要的时候可以执行 system-reserved 策略。

随着时间推进和越来越多特性被加入,kube 系统守护进程对资源的需求可能也会增加。 以后 Kubernetes 项目将尝试减少对节点系统守护进程的利用,但目前这件事的优先级并不是最高。 所以,将来的发布版本中 Allocatable 容量是有可能降低的。

示例场景

这是一个用于说明节点可分配(Node Allocatable)计算方式的示例:

  • 节点拥有 32Gi memory16 CPU100Gi Storage 资源
  • --kube-reserved 被设置为 cpu=1,memory=2Gi,ephemeral-storage=1Gi
  • --system-reserved 被设置为 cpu=500m,memory=1Gi,ephemeral-storage=1Gi
  • --eviction-hard 被设置为 memory.available<500Mi,nodefs.available<10%

在这个场景下,'Allocatable' 将会是 14.5 CPUs、28.5Gi 内存以及 88Gi 本地存储。 调度器保证这个节点上的所有 Pod 的内存 requests 总量不超过 28.5Gi,存储不超过 '88Gi'。 当 Pod 的内存使用总量超过 28.5Gi 或者磁盘使用总量超过 88Gi 时,kubelet 将会驱逐它们。 如果节点上的所有进程都尽可能多地使用 CPU,则 Pod 加起来不能使用超过 14.5 CPUs 的资源。

当没有执行 kube-reserved 和/或 system-reserved 策略且系统守护进程使用量超过其预留时, 如果节点内存用量高于 31.5Gi 或 storage 大于 90Gi,kubelet 将会驱逐 Pod。

31 - 以非 root 用户身份运行 Kubernetes 节点组件

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

这个文档描述了怎样不使用 root 特权,而是通过使用 用户命名空间 去运行 Kubernetes 节点组件(例如 kubelet、CRI、OCI、CNI)。

这种技术也叫做 rootless 模式(Rootless mode)

准备开始

你的 Kubernetes 服务器版本必须不低于版本 1.22. 要获知版本信息,请输入 kubectl version.

使用 Rootless 模式的 Docker/Podman 运行 Kubernetes

kind

kind 支持使用 Rootless 模式的 Docker 或者 Podman 运行 Kubernetes。

请参阅使用 Rootless 模式的 Docker 运行 kind

minikube

minikube 也支持使用 Rootless 模式的 Docker 或 Podman 运行 Kubernetes。

请参阅 Minikube 文档:

在非特权容器内运行 Kubernetes

sysbox

Sysbox 是一个开源容器运行时 (类似于 “runc”),支持在 Linux 用户命名空间隔离的非特权容器内运行系统级工作负载, 比如 Docker 和 Kubernetes。

查看 Sysbox 快速入门指南: Kubernetes-in-Docker 了解更多细节。

Sysbox 支持在非特权容器内运行 Kubernetes, 而不需要 cgroup v2 和 “KubeletInUserNamespace” 特性门控。 Sysbox 通过在容器内暴露特定的 /proc/sys 文件系统, 以及其它一些先进的操作系统虚拟化技术来实现。

直接在主机上运行 Rootless 模式的 Kubernetes

K3s

K3s 实验性支持了 Rootless 模式。

请参阅使用 Rootless 模式运行 K3s 页面中的用法.

Usernetes

Usernetes 是 Kubernetes 的一个参考发行版, 它可以在不使用 root 特权的情况下安装在 $HOME 目录下。

Usernetes 支持使用 containerd 和 CRI-O 作为 CRI 运行时。 Usernetes 支持配置了 Flannel (VXLAN)的多节点集群。

关于用法,请参阅 Usernetes 仓库

手动部署一个在用户命名空间运行 kubelet 的节点

本节提供在用户命名空间手动运行 Kubernetes 的注意事项。

创建用户命名空间

第一步是创建一个 用户命名空间

如果你正在尝试使用用户命名空间的容器(例如 Rootless 模式的 Docker/Podman 或 LXC/LXD) 运行 Kubernetes,那么你已经准备就绪,可以直接跳到下一小节。

否则你需要通过传递参数 CLONE_NEWUSER 调用 unshare(2),自己创建一个命名空间。

用户命名空间也可以通过如下所示的命令行工具取消共享:

在取消命名空间的共享之后,你也必须对其它的命名空间例如 mount 命名空间取消共享。

在取消 mount 命名空间的共享之后,你需要调用 chroot() 或者 pivot_root(), 但是你必须在这个命名空间内挂载可写的文件系统到几个目录上。

请确保这个命名空间内(不是这个命名空间外部)至少以下几个目录是可写的:

  • /etc
  • /run
  • /var/logs
  • /var/lib/kubelet
  • /var/lib/cni
  • /var/lib/containerd (参照 containerd)
  • /var/lib/containers (参照 CRI-O)

创建委派 cgroup 树

除了用户命名空间,你也需要有一个版本为 cgroup v2 的可写 cgroup 树。

如果你在一个采用 systemd 机制的主机上使用用户命名空间的容器(例如 Rootless 模式的 Docker/Podman 或 LXC/LXD)来运行 Kubernetes,那么你已经准备就绪。

否则你必须创建一个具有 Delegate=yes 属性的 systemd 单元,来委派一个具有可写权限的 cgroup 树。

在你的节点上,systemd 必须已经配置为允许委派。更多细节请参阅 Rootless 容器文档的 cgroup v2 部分。

配置网络

节点组件的网络命名空间必须有一个非本地回路的网卡。它可以使用 slirp4netnsVPNKitlxc-user-nic(1) 等工具进行配置。

Pod 的网络命名空间可以使用常规的 CNI 插件配置。对于多节点的网络,已知 Flannel (VXLAN、8472/UDP) 可以正常工作。

诸如 kubelet 端口(10250/TCP)和 NodePort 服务端口之类的端口必须通过外部端口转发器 (例如 RootlessKit、slirp4netns 或 socat(1)) 从节点网络命名空间暴露给主机。

你可以使用 K3s 的端口转发器。更多细节请参阅 在 Rootless 模式下运行 K3s。 该实现可以在 k3s 的 pkg/rootlessports中找到。

配置 CRI

kubelet 依赖于容器运行时。你需要部署一个容器运行时(例如 containerd 或 CRI-O), 并确保它在 kubelet 启动之前已经在用户命名空间内运行。

containerd 1.4 开始支持在用户命名空间运行 containerd 的 CRI 插件。

在用户命名空间运行 containerd 必须进行如下配置:

version = 2

[plugins."io.containerd.grpc.v1.cri"]
# 禁用 AppArmor
  disable_apparmor = true
# 忽略配置 oom_score_adj 时的错误
  restrict_oom_score_adj = true
# 禁用 hugetlb cgroup v2 控制器(因为 systemd 不支持委派 hugetlb controller)
  disable_hugetlb_controller = true

[plugins."io.containerd.grpc.v1.cri".containerd]
# 如果内核 >= 5.11 , 也可以使用 non-fuse overlayfs, 但需要禁用 SELinux
  snapshotter = "fuse-overlayfs"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
# 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 SystemdCgroup 驱动
# (除非你在命名空间内运行了另一个 systemd)
  SystemdCgroup = false

配置文件的默认路径是 /etc/containerd/config.toml。 可以用 containerd -c /path/to/containerd/config.toml 来指定该路径。

CRI-O 1.22 开始支持在用户命名空间运行 CRI-O。

CRI-O 必须配置一个环境变量 _CRIO_ROOTLESS=1

也推荐使用以下配置:

[crio]
  storage_driver = "overlay"
# 如果内核 >= 5.11 , 也可以使用 non-fuse overlayfs, 但需要禁用 SELinux
  storage_option = ["overlay.mount_program=/usr/local/bin/fuse-overlayfs"]

[crio.runtime]
# 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 "systemd" 驱动
# (除非你在命名空间内运行了另一个 systemd)
  cgroup_manager = "cgroupfs"

配置文件的默认路径是 /etc/containerd/config.toml。 可以用 containerd -c /path/to/containerd/config.toml 来指定该路径。

配置 kubelet

在用户命名空间运行 kubelet 必须进行如下配置:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  KubeletInUserNamespace: true
# 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 "systemd" 驱动
# (除非你在命名空间内运行了另一个 systemd)
cgroupDriver: "cgroupfs"

KubeletInUserNamespace 特性门控被启用时, kubelet 会忽略节点内由于配置如下几个 sysctl 参数值而可能产生的错误。

  • vm.overcommit_memory
  • vm.panic_on_oom
  • kernel.panic
  • kernel.panic_on_oops
  • kernel.keys.root_maxkeys
  • kernel.keys.root_maxbytes.

在用户命名空间内, kubelet 也会忽略任何由于打开 /dev/kmsg 而产生的错误。 这个特性门控也允许 kube-proxy 忽略由于配置 RLIMIT_NOFILE 而产生的一个错误。

KubeletInUserNamespace 特性门控从 Kubernetes v1.22 被引入, 标记为 "alpha" 状态。

通过挂载特制的 proc 文件系统 (比如 Sysbox), 也可以在不使用这个特性门控的情况下在用户命名空间运行 kubelet,但这不受官方支持。

配置 kube-proxy

在用户命名空间运行 kube-proxy 需要进行以下配置:

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "iptables" # or "userspace"
conntrack:
# 跳过配置 sysctl 的值 "net.netfilter.nf_conntrack_max"
  maxPerCore: 0
# 跳过配置 "net.netfilter.nf_conntrack_tcp_timeout_established"
  tcpEstablishedTimeout: 0s
# 跳过配置 "net.netfilter.nf_conntrack_tcp_timeout_close"
  tcpCloseWaitTimeout: 0s

注意事项

  • 大部分“非本地”的卷驱动(例如 nfsiscsi)不能正常工作。 已知诸如 localhostPathemptyDirconfigMapsecretdownwardAPI 这些本地卷是能正常工作的。

  • 一些 CNI 插件可能不正常工作。已知 Flannel (VXLAN) 是能正常工作的。

更多细节请参阅 rootlesscontaine.rs 站点的 Caveats and Future work 页面。

另请参见

32 - 安全地清空一个节点

本页展示了如何在确保 PodDisruptionBudget 的前提下, 安全地清空一个节点

准备开始

此任务假定你已经满足了以下先决条件:

  1. 在节点清空期间,不要求应用具有高可用性
  2. 你已经了解了 PodDisruptionBudget 的概念, 并为需要它的应用配置了 PodDisruptionBudget

(可选)配置干扰预算

为了确保你的负载在维护期间仍然可用,你可以配置一个 PodDisruptionBudget。 如果可用性对于正在清空的该节点上运行或可能在该节点上运行的任何应用程序很重要, 首先 配置一个 PodDisruptionBudgets 并继续遵循本指南。

建议为你的 PodDisruptionBudgets 设置 AlwaysAllow 不健康 Pod 驱逐策略, 以在节点清空期间支持驱逐异常的应用程序。 默认行为是等待应用程序的 Pod 变为 健康后, 才能进行清空操作。

使用 kubectl drain 从服务中删除一个节点

在对节点执行维护(例如内核升级、硬件维护等)之前, 可以使用 kubectl drain 从节点安全地逐出所有 Pod。 安全的驱逐过程允许 Pod 的容器体面地终止, 并确保满足指定的 PodDisruptionBudgets

kubectl drain 的成功返回,表明所有的 Pod(除了上一段中描述的被排除的那些), 已经被安全地逐出(考虑到期望的终止宽限期和你定义的 PodDisruptionBudget)。 然后就可以安全地关闭节点, 比如关闭物理机器的电源,如果它运行在云平台上,则删除它的虚拟机。

首先,确定想要清空的节点的名称。可以用以下命令列出集群中的所有节点:

kubectl get nodes

接下来,告诉 Kubernetes 清空节点:

kubectl drain --ignore-daemonsets <节点名称>

如果存在 DaemonSet 管理的 Pod,你将需要为 kubectl 设置 --ignore-daemonsets 以成功地清空节点。 kubectl drain 子命令自身实际上不清空节点上的 DaemonSet Pod 集合: DaemonSet 控制器(作为控制平面的一部分)会立即用新的等效 Pod 替换缺少的 Pod。 DaemonSet 控制器还会创建忽略不可调度污点的 Pod,这种污点允许在你正在清空的节点上启动新的 Pod。

一旦它返回(没有报错), 你就可以下线此节点(或者等价地,如果在云平台上,删除支持该节点的虚拟机)。 如果要在维护操作期间将节点留在集群中,则需要运行:

kubectl uncordon <node name>

然后告诉 Kubernetes,它可以继续在此节点上调度新的 Pod。

并行清空多个节点

kubectl drain 命令一次只能发送给一个节点。 但是,你可以在不同的终端或后台为不同的节点并行地运行多个 kubectl drain 命令。 同时运行的多个 drain 命令仍然遵循你指定的 PodDisruptionBudget

例如,如果你有一个三副本的 StatefulSet, 并设置了一个 PodDisruptionBudget,指定 minAvailable: 2。 如果所有的三个 Pod 处于健康(healthy)状态, 并且你并行地发出多个 drain 命令,那么 kubectl drain 只会从 StatefulSet 中逐出一个 Pod, 因为 Kubernetes 会遵守 PodDisruptionBudget 并确保在任何时候只有一个 Pod 不可用 (最多不可用 Pod 个数的计算方法:replicas - minAvailable)。 任何会导致处于健康(healthy) 状态的副本数量低于指定预算的清空操作都将被阻止。

驱逐 API

如果你不喜欢使用 kubectl drain (比如避免调用外部命令,或者更细化地控制 Pod 驱逐过程), 你也可以用驱逐 API 通过编程的方式达到驱逐的效果。 更多信息,请参阅 API 发起的驱逐

接下来

  • 执行配置 PDB 中的各个步骤, 保护你的应用。

33 - 保护集群

本文档涉及与保护集群免受意外或恶意访问有关的主题,并对总体安全性提出建议。

准备开始

  • 你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

    要获知版本信息,请输入 kubectl version.

控制对 Kubernetes API 的访问

因为 Kubernetes 是完全通过 API 驱动的,所以,控制和限制谁可以通过 API 访问集群, 以及允许这些访问者执行什么样的 API 动作,就成为了安全控制的第一道防线。

为所有 API 交互使用传输层安全(TLS)

Kubernetes 期望集群中所有的 API 通信在默认情况下都使用 TLS 加密, 大多数安装方法也允许创建所需的证书并且分发到集群组件中。 请注意,某些组件和安装方法可能使用 HTTP 来访问本地端口, 管理员应该熟悉每个组件的设置,以识别可能不安全的流量。

API 认证

安装集群时,选择一个 API 服务器的身份验证机制,去使用与之匹配的公共访问模式。 例如,小型的单用户集群可能希望使用简单的证书或静态承载令牌方法。 更大的集群则可能希望整合现有的、OIDC、LDAP 等允许用户分组的服务器。

所有 API 客户端都必须经过身份验证,即使它是基础设施的一部分,比如节点、代理、调度程序和卷插件。 这些客户端通常使用 服务帐户 或 X509 客户端证书,并在集群启动时自动创建或是作为集群安装的一部分进行设置。

如果你希望获取更多信息,请参考认证参考文档

API 授权

一旦通过身份认证,每个 API 的调用都将通过鉴权检查。 Kubernetes 集成基于角色的访问控制(RBAC)组件, 将传入的用户或组与一组绑定到角色的权限匹配。 这些权限将动作(get、create、delete)和资源(Pod、Service、Node)进行组合,并可在名字空间或者集群范围生效。 Kubernetes 提供了一组可直接使用的角色,这些角色根据客户可能希望执行的操作提供合理的责任划分。 建议你同时使用 NodeRBAC 两个鉴权组件,再与 NodeRestriction 准入插件结合使用。

与身份验证一样,简单而广泛的角色可能适合于较小的集群,但是随着更多的用户与集群交互, 可能需要将团队划分到有更多角色限制的、 单独的名字空间中去。

就鉴权而言,很重要的一点是理解对象上的更新操作如何导致在其它地方发生对应行为。 例如,用户可能不能直接创建 Pod,但允许他们通过创建 Deployment 来创建这些 Pod, 这将让他们间接创建这些 Pod。 同样地,从 API 删除一个节点将导致调度到这些节点上的 Pod 被中止,并在其他节点上重新创建。 原生的角色设计代表了灵活性和常见用例之间的平衡,但须限制的角色应该被仔细审查, 以防止意外的权限升级。如果内置的角色无法满足你的需求,则可以根据使用场景需要创建特定的角色。

如果你希望获取更多信息,请参阅鉴权参考

控制对 Kubelet 的访问

Kubelet 公开 HTTPS 端点,这些端点提供了对节点和容器的强大的控制能力。 默认情况下,Kubelet 允许对此 API 进行未经身份验证的访问。

生产级别的集群应启用 Kubelet 身份认证和授权。

进一步的信息,请参考 Kubelet 身份验证/授权参考

控制运行时负载或用户的能力

Kubernetes 中的授权故意设计成较高抽象级别,侧重于对资源的粗粒度行为。 更强大的控制是 策略 的形式呈现的,根据使用场景限制这些对象如何作用于集群、自身和其他资源。

限制集群上的资源使用

资源配额(Resource Quota)限制了赋予命名空间的资源的数量或容量。 资源配额通常用于限制名字空间可以分配的 CPU、内存或持久磁盘的数量, 但也可以控制每个名字空间中存在多少个 Pod、Service 或 Volume。

限制范围(Limit Range) 限制上述某些资源的最大值或者最小值,以防止用户使用类似内存这样的通用保留资源时请求不合理的过高或过低的值, 或者在没有指定的情况下提供默认限制。

控制容器运行的特权

Pod 定义包含了一个安全上下文, 用于描述一些访问请求,如以某个节点上的特定 Linux 用户(如 root)身份运行, 以特权形式运行,访问主机网络,以及一些在宿主节点上不受约束地运行的其它控制权限等等。

你可以配置 Pod 安全准入来在某个 名字空间中 强制实施特定的 Pod 安全标准(Pod Security Standard), 或者检查安全上的缺陷。

一般来说,大多数应用程序需要对主机资源的有限制的访问, 这样它们可以在不访问主机信息的情况下,成功地以 root 账号(UID 0)运行。 但是,考虑到与 root 用户相关的特权,在编写应用程序容器时,你应该使用非 root 用户运行。 类似地,希望阻止客户端应用程序从其容器中逃逸的管理员,应该应用 BaselineRestricted Pod 安全标准。

防止容器加载不需要的内核模块

如果在某些情况下,Linux 内核会根据需要自动从磁盘加载内核模块, 这类情况的例子有挂接了一个硬件或挂载了一个文件系统。 与 Kubernetes 特别相关的是,即使是非特权的进程也可能导致某些网络协议相关的内核模块被加载, 而这只需创建一个适当类型的套接字。 这就可能允许攻击者利用管理员假定未使用的内核模块中的安全漏洞。

为了防止特定模块被自动加载,你可以将它们从节点上卸载或者添加规则来阻止这些模块。 在大多数 Linux 发行版上,你可以通过创建类似 /etc/modprobe.d/kubernetes-blacklist.conf 这种文件来做到这一点,其中的内容如下所示:

# DCCP is unlikely to be needed, has had multiple serious
# vulnerabilities, and is not well-maintained.
blacklist dccp

# SCTP is not used in most Kubernetes clusters, and has also had
# vulnerabilities in the past.
blacklist sctp

为了更大范围地阻止内核模块被加载,你可以使用 Linux 安全模块(如 SELinux) 来彻底拒绝容器的 module_request 权限,从而防止在任何情况下系统为容器加载内核模块。 (Pod 仍然可以使用手动加载的模块,或者使用由内核代表某些特权进程所加载的模块。)

限制网络访问

基于名字空间的网络策略 允许应用程序作者限制其它名字空间中的哪些 Pod 可以访问自身名字空间内的 Pod 和端口。 现在已经有许多支持网络策略的 Kubernetes 网络驱动

配额(Quota)和限制范围(Limit Range)也可用于控制用户是否可以请求节点端口或负载均衡服务。 在很多集群上,节点端口和负载均衡服务也可控制用户的应用程序是否在集群之外可见。

此外也可能存在一些基于插件或基于环境的网络规则,能够提供额外的保护能力。 例如各节点上的防火墙、物理隔离集群节点以防止串扰或者高级的网络策略等。

限制云元数据 API 访问

云平台(AWS、Azure、GCE 等)经常将 metadata 本地服务暴露给实例。 默认情况下,这些 API 可由运行在实例上的 Pod 访问,并且可以包含 该云节点的凭据或配置数据(如 kubelet 凭据)。 这些凭据可以用于在集群内升级或在同一账户下升级到其他云服务。

在云平台上运行 Kubernetes 时,需要限制对实例凭据的权限,使用 网络策略 限制 Pod 对元数据 API 的访问,并避免使用配置数据来传递机密信息。

控制 Pod 可以访问的节点

默认情况下,对 Pod 可以运行在哪些节点上是没有任何限制的。 Kubernetes 给最终用户提供了 一组丰富的策略用于控制 Pod 所放置的节点位置, 以及基于污点的 Pod 放置和驱逐。 对于许多集群,使用这些策略来分离工作负载可以作为一种约定,要求作者遵守或者通过工具强制。

对于管理员,Beta 阶段的准入插件 PodNodeSelector 可用于强制某名字空间中的 Pod 使用默认的或特定的节点选择算符。 如果最终用户无法改变名字空间,这一机制可以有效地限制特定工作负载中所有 Pod 的放置位置。

保护集群组件免受破坏

本节描述保护集群免受破坏的一些常用模式。

限制访问 etcd

拥有对 API 的 etcd 后端的写访问权限相当于获得了整个集群的 root 权限, 读访问权限也可能被利用,实现相当快速的权限提升。 对于从 API 服务器访问其 etcd 服务器,管理员应该总是使用比较强的凭证,如通过 TLS 客户端证书来实现双向认证。 通常,我们建议将 etcd 服务器隔离到只有 API 服务器可以访问的防火墙后面。

启用审计日志

审计日志是 Beta 特性, 负责记录 API 操作以便在发生破坏时进行事后分析。 建议启用审计日志,并将审计文件归档到安全服务器上。

限制使用 Alpha 和 Beta 特性

Kubernetes 的 Alpha 和 Beta 特性还在努力开发中,可能存在导致安全漏洞的缺陷或错误。 要始终评估 Alpha 和 Beta 特性可能给你的安全态势带来的风险。 当你怀疑存在风险时,可以禁用那些不需要使用的特性。

经常轮换基础设施证书

一项机密信息或凭据的生命期越短,攻击者就越难使用该凭据。 在证书上设置较短的生命期并实现自动轮换是控制安全的一个好方法。 使用身份验证提供程序时,应该使用那些可以控制所发布令牌的合法时长的提供程序, 并尽可能设置较短的生命期。 如果在外部集成场景中使用服务帐户令牌,则应该经常性地轮换这些令牌。 例如,一旦引导阶段完成,就应该撤销用于配置节点的引导令牌,或者取消它的授权。

在启用第三方集成之前,请先审查它们

许多集成到 Kubernetes 的第三方软件或服务都可能改变你的集群的安全配置。 启用集成时,在授予访问权限之前,你应该始终检查扩展所请求的权限。 例如,许多安全性集成中可能要求查看集群上的所有 Secret 的访问权限, 本质上该组件便成为了集群的管理员。 当有疑问时,如果可能的话,将要集成的组件限制在某指定名字空间中运行。

如果执行 Pod 创建操作的组件能够在 kube-system 这类名字空间中创建 Pod, 则这类组件也可能获得意外的权限,因为这些 Pod 可以访问服务账户的 Secret, 或者,如果对应服务帐户被授权访问宽松的 PodSecurityPolicy, 它们就能以较高的权限运行。

如果你使用 Pod 安全准入, 并且允许任何组件在一个允许执行特权 Pod 的名字空间中创建 Pod,这些 Pod 就可能从所在的容器中逃逸,利用被拓宽的访问权限来实现特权提升。

你不应该允许不可信的组件在任何系统名字空间(名字以 kube- 开头)中创建 Pod, 也不允许它们在访问权限授权可被利用来提升特权的名字空间中创建 Pod。

对 Secret 进行静态加密

一般情况下,etcd 数据库包含了通过 Kubernetes API 可以访问到的所有信息, 并且可能为攻击者提供对你的集群的状态的较多的可见性。 你要始终使用经过充分审查的备份和加密方案来加密备份数据, 并考虑在可能的情况下使用全盘加密。

对于 Kubernetes API 中的信息,Kubernetes 支持可选的静态数据加密。 这让你可以确保当 Kubernetes 存储对象(例如 SecretConfigMap)的数据时,API 服务器写入的是加密的对象。 这种加密意味着即使有权访问 etcd 备份数据的某些人也无法查看这些对象的内容。 在 Kubernetes 1.30 中,你也可以加密自定义资源; 针对以 CustomResourceDefinition 形式定义的扩展 API,对其执行静态加密的能力作为 v1.26 版本的一部分已添加到 Kubernetes。

接收安全更新和报告漏洞的警报

请加入 kubernetes-announce 组,这样你就能够收到有关安全公告的邮件。有关如何报告漏洞的更多信息, 请参见安全报告页面。

接下来

34 - 通过配置文件设置 kubelet 参数

通过保存在硬盘的配置文件设置 kubelet 的部分配置参数,这可以作为命令行参数的替代。

建议通过配置文件的方式提供参数,因为这样可以简化节点部署和配置管理。

创建配置文件

KubeletConfiguration 结构体定义了可以通过文件配置的 kubelet 配置子集,

配置文件必须是这个结构体中参数的 JSON 或 YAML 表现形式。 确保 kubelet 可以读取该文件。

下面是一个 kubelet 配置文件示例:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: "192.168.0.8"
port: 20250
serializeImagePulls: false
evictionHard:
    memory.available:  "100Mi"
    nodefs.available:  "10%"
    nodefs.inodesFree: "5%"
    imagefs.available: "15%"

在此示例中,kubelet 配置为以下设置:

  1. address:kubelet 将在 192.168.0.8 IP 地址上提供服务。
  2. port:kubelet 将在 20250 端口上提供服务。
  3. serializeImagePulls:并行拉取镜像。
  4. evictionHard:kubelet 将在以下情况之一驱逐 Pod:
    • 当节点的可用内存降至 100MiB 以下时。
    • 当节点主文件系统的已使用 inode 超过 95%。
    • 当镜像文件系统的可用空间小于 15% 时。
    • 当节点主文件系统的 inode 超过 95% 正在使用时。

imagefs 是一个可选的文件系统,容器运行时使用它来存储容器镜像和容器可写层。

启动通过配置文件配置的 kubelet 进程

启动 kubelet 需要将 --config 参数设置为 kubelet 配置文件的路径。kubelet 将从此文件加载其配置。

请注意,命令行参数与配置文件有相同的值时,就会覆盖配置文件中的该值。 这有助于确保命令行 API 的向后兼容性。

请注意,kubelet 配置文件中的相对文件路径是相对于 kubelet 配置文件的位置解析的, 而命令行参数中的相对路径是相对于 kubelet 的当前工作目录解析的。

请注意,命令行参数和 kubelet 配置文件的某些默认值不同。 如果设置了 --config,并且没有通过命令行指定值,则 KubeletConfiguration 版本的默认值生效。在上面的例子中,version 是 kubelet.config.k8s.io/v1beta1

kubelet 配置文件的插件目录

自 Kubernetes v1.28.0 起,kubelet 被扩展以支持一个插件配置目录。 该目录的位置可以使用 --config-dir 标志来指定,默认为 "",也就是被禁用状态。

只有在为 kubelet 进程设置环境变量 KUBELET_CONFIG_DROPIN_DIR_ALPHA (该变量的值无关紧要)时才可以设置 --config-dir。对于 Kubernetes v1.30, 如果你未设置该变量而指定了 --config-dir,kubelet 将返回错误并且启动失败。 你不能使用 kubelet 配置文件指定插件配置目录;只能使用 CLI 参数 --config-dir 进行设置。

你可以以类似于 kubelet 配置文件的方式使用 kubelet 配置目录。

例如,你可能想要为所有节点设置一个基准的 kubelet 配置,但你可能想要自定义 address 字段。 可以按如下方式操作:

kubelet 配置文件的主要内容如下:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 20250
serializeImagePulls: false
evictionHard:
    memory.available:  "200Mi"

--config-dir 目录中某个文件的内容如下:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: "192.168.0.8"

在启动时,kubelet 会合并来自以下几部分的配置:

  • 命令行参数(优先级最低)。
  • kubelet 配置文件。
  • 排序的插件配置文件。
  • 在命令行中指定的特性门控(优先级最高)。

这将产生与之前示例中使用的单个配置文件相同的结果。

接下来

35 - 通过名字空间共享集群

本页展示如何查看、使用和删除名字空间。 本页同时展示如何使用 Kubernetes 名字空间来划分集群。

准备开始

查看名字空间

列出集群中现有的名字空间:

kubectl get namespaces
NAME              STATUS   AGE
default           Active   11d
kube-node-lease   Active   11d
kube-public       Active   11d
kube-system       Active   11d

初始状态下,Kubernetes 具有四个名字空间:

  • default 无名字空间对象的默认名字空间
  • kube-node-lease 此名字空间保存与每个节点关联的租约(Lease)对象。 节点租约允许 kubelet 发送[心跳](/zh-cn/docs/concepts/architecture/nodes/#heartbeats), 以便控制平面可以检测节点故障。
  • kube-public 自动创建且被所有用户可读的名字空间(包括未经身份认证的)。 此名字空间通常在某些资源在整个集群中可见且可公开读取时被集群使用。 此名字空间的公共方面只是一个约定,而不是一个必要条件。
  • kube-system 由 Kubernetes 系统创建的对象的名字空间

你还可以通过下列命令获取特定名字空间的摘要:

kubectl get namespaces <name>

或用下面的命令获取详细信息:

kubectl describe namespaces <name>
Name:           default
Labels:         <none>
Annotations:    <none>
Status:         Active

No resource quota.

Resource Limits
 Type       Resource    Min Max Default
 ----               --------    --- --- ---
 Container          cpu         -   -   100m

请注意,这些详情同时显示了资源配额(如果存在)以及资源限制区间。

资源配额跟踪并聚合 Namespace 中资源的使用情况, 并允许集群运营者定义 Namespace 可能消耗的 Hard 资源使用限制。

限制区间定义了单个实体在一个 Namespace 中可使用的最小/最大资源量约束。

参阅准入控制:限制区间

名字空间可以处于下列两个阶段中的一个:

  • Active 名字空间正在被使用中
  • Terminating 名字空间正在被删除,且不能被用于新对象。

更多细节,参阅 API 参考中的名字空间

创建名字空间

新建一个名为 my-namespace.yaml 的 YAML 文件,并写入下列内容:

apiVersion: v1
kind: Namespace
metadata:
  name: <insert-namespace-name-here>

然后运行:

kubectl create -f ./my-namespace.yaml

或者,你可以使用下面的命令创建名字空间:

kubectl create namespace <insert-namespace-name-here>

请注意,名字空间的名称必须是一个合法的 DNS 标签

可选字段 finalizers 允许观察者们在名字空间被删除时清除资源。 记住如果指定了一个不存在的终结器,名字空间仍会被创建, 但如果用户试图删除它,它将陷入 Terminating 状态。

更多有关 finalizers 的信息请查阅 设计文档中名字空间部分。

删除名字空间

删除名字空间使用命令:

kubectl delete namespaces <insert-some-namespace-name>

删除是异步的,所以有一段时间你会看到名字空间处于 Terminating 状态。

使用 Kubernetes 名字空间细分你的集群

默认情况下,Kubernetes 集群会在配置集群时实例化一个 default 名字空间,用以存放集群所使用的默认 Pod、Service 和 Deployment 集合。

假设你有一个新的集群,你可以通过执行以下操作来内省可用的名字空间:

kubectl get namespaces
NAME      STATUS    AGE
default   Active    13m

创建新的名字空间

在本练习中,我们将创建两个额外的 Kubernetes 名字空间来保存我们的内容。

在某组织使用共享的 Kubernetes 集群进行开发和生产的场景中:

  • 开发团队希望在集群中维护一个空间,以便他们可以查看用于构建和运行其应用程序的 Pod、Service 和 Deployment 列表。在这个空间里,Kubernetes 资源被自由地加入或移除, 对谁能够或不能修改资源的限制被放宽,以实现敏捷开发。
  • 运维团队希望在集群中维护一个空间,以便他们可以强制实施一些严格的规程, 对谁可以或不可以操作运行生产站点的 Pod、Service 和 Deployment 集合进行控制。

该组织可以遵循的一种模式是将 Kubernetes 集群划分为两个名字空间:developmentproduction。 让我们创建两个新的名字空间来保存我们的工作。

使用 kubectl 创建 development 名字空间。

kubectl create -f https://k8s.io/examples/admin/namespace-dev.json

让我们使用 kubectl 创建 production 名字空间。

kubectl create -f https://k8s.io/examples/admin/namespace-prod.json

为了确保一切正常,列出集群中的所有名字空间。

kubectl get namespaces --show-labels
NAME          STATUS    AGE       LABELS
default       Active    32m       <none>
development   Active    29s       name=development
production    Active    23s       name=production

在每个名字空间中创建 Pod

Kubernetes 名字空间为集群中的 Pod、Service 和 Deployment 提供了作用域。 与一个名字空间交互的用户不会看到另一个名字空间中的内容。 为了演示这一点,让我们在 development 名字空间中启动一个简单的 Deployment 和 Pod。

kubectl create deployment snowflake \
  --image=registry.k8s.io/serve_hostname \
  -n=development --replicas=2

我们创建了一个副本个数为 2 的 Deployment,运行名为 snowflake 的 Pod,其中包含一个负责提供主机名的基本容器。

kubectl get deployment -n=development
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
snowflake    2/2     2            2           2m
kubectl get pods -l app=snowflake -n=development
NAME                         READY     STATUS    RESTARTS   AGE
snowflake-3968820950-9dgr8   1/1       Running   0          2m
snowflake-3968820950-vgc4n   1/1       Running   0          2m

看起来还不错,开发人员能够做他们想做的事,而且他们不必担心会影响到 production 名字空间下面的内容。

让我们切换到 production 名字空间, 展示一下一个名字空间中的资源是如何对另一个名字空间隐藏的。 名字空间 production 应该是空的,下面的命令应该不会返回任何东西。

kubectl get deployment -n=production
kubectl get pods -n=production

生产环境下一般以养牛的方式运行负载,所以让我们创建一些 Cattle(牛)Pod。

kubectl create deployment cattle --image=registry.k8s.io/serve_hostname -n=production
kubectl scale deployment cattle --replicas=5 -n=production

kubectl get deployment -n=production
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
cattle       5/5     5            5           10s
kubectl get pods -l app=cattle -n=production
NAME                      READY     STATUS    RESTARTS   AGE
cattle-2263376956-41xy6   1/1       Running   0          34s
cattle-2263376956-kw466   1/1       Running   0          34s
cattle-2263376956-n4v97   1/1       Running   0          34s
cattle-2263376956-p5p3i   1/1       Running   0          34s
cattle-2263376956-sxpth   1/1       Running   0          34s

此时,应该很清楚地展示了用户在一个名字空间中创建的资源对另一个名字空间是隐藏的。

随着 Kubernetes 中的策略支持的发展,我们将扩展此场景,以展示如何为每个名字空间提供不同的授权规则。

理解使用名字空间的动机

单个集群应该能满足多个用户及用户组的需求(以下称为 “用户社区”)。

Kubernetes 名字空间 帮助不同的项目、团队或客户去共享 Kubernetes 集群。

名字空间通过以下方式实现这点:

  1. 名字设置作用域.
  2. 为集群中的部分资源关联鉴权和策略的机制。

使用多个名字空间是可选的。

每个用户社区都希望能够与其他社区隔离开展工作。 每个用户社区都有自己的:

  1. 资源(Pod、服务、副本控制器等等)
  2. 策略(谁能或不能在他们的社区里执行操作)
  3. 约束(该社区允许多少配额等等)

集群运营者可以为每个唯一用户社区创建名字空间。

名字空间为下列内容提供唯一的作用域:

  1. 命名资源(避免基本的命名冲突)
  2. 将管理权限委派给可信用户
  3. 限制社区资源消耗的能力

用例包括:

  1. 作为集群运营者, 我希望能在单个集群上支持多个用户社区。
  2. 作为集群运营者,我希望将集群分区的权限委派给这些社区中的受信任用户。
  3. 作为集群运营者,我希望能限定每个用户社区可使用的资源量,以限制对使用同一集群的其他用户社区的影响。
  4. 作为集群用户,我希望与我的用户社区相关的资源进行交互,而与其他用户社区在该集群上执行的操作无关。

理解名字空间和 DNS

当你创建服务时,Kubernetes 会创建相应的 DNS 条目。 此条目的格式为 <服务名称>.<名字空间名称>.svc.cluster.local。 这意味着如果容器使用 <服务名称>,它将解析为名字空间本地的服务。 这对于在多个名字空间(如开发、暂存和生产)中使用相同的配置非常有用。 如果要跨名字空间访问,则需要使用完全限定的域名(FQDN)。

接下来

36 - 升级集群

本页概述升级 Kubernetes 集群的步骤。

升级集群的方式取决于你最初部署它的方式、以及后续更改它的方式。

从高层规划的角度看,要执行的步骤是:

  • 升级控制平面
  • 升级集群中的节点
  • 升级 kubectl 之类的客户端
  • 根据新 Kubernetes 版本带来的 API 变化,调整清单文件和其他资源

准备开始

你必须有一个集群。 本页内容涉及从 Kubernetes 1.29 升级到 Kubernetes 1.30。 如果你的集群未运行 Kubernetes 1.29, 那请参考目标 Kubernetes 版本的文档。

升级方法

kubeadm

如果你的集群是使用 kubeadm 安装工具部署而来, 那么升级集群的详细信息,请参阅升级 kubeadm 集群

升级集群之后,要记得安装最新版本的 kubectl

手动部署

你应该按照下面的操作顺序,手动更新控制平面:

  • etcd (所有实例)
  • kube-apiserver (所有控制平面的宿主机)
  • kube-controller-manager
  • kube-scheduler
  • cloud controller manager (在你用到时)

现在,你应该安装最新版本的 kubectl

对于集群中的每个节点, 首先需要腾空节点, 然后使用一个运行了 kubelet 1.30 版本的新节点替换它; 或者升级此节点的 kubelet,并使节点恢复服务。

其他部署方式

参阅你的集群部署工具对应的文档,了解用于维护的推荐设置步骤。

升级后的任务

切换集群的存储 API 版本

对象序列化到 etcd,是为了提供集群中活动 Kubernetes 资源的内部表示法, 这些对象都使用特定版本的 API 编写。

当底层的 API 更改时,这些对象可能需要用新 API 重写。 如果不能做到这一点,会导致再也不能用 Kubernetes API 服务器解码、使用该对象。

对于每个受影响的对象,请使用最新支持的 API 读取它,然后使用所支持的最新 API 将其写回。

更新清单

升级到新版本 Kubernetes 就可以获取到新的 API。

你可以使用 kubectl convert 命令在不同 API 版本之间转换清单。 例如:

kubectl convert -f pod.yaml --output-version v1

kubectl 替换了 pod.yaml 的内容, 在新的清单文件中,kind 被设置为 Pod(未变), 但 apiVersion 则被修订了。

设备插件

如果你的集群正在运行设备插件(Device Plugin)并且节点需要升级到具有更新的设备插件(Device Plugin) API 版本的 Kubernetes 版本,则必须在升级节点之前升级设备插件以同时支持这两个插件 API 版本, 以确保升级过程中设备分配能够继续成功完成。

有关详细信息,请参阅 API 兼容性kubelet 设备管理器 API 版本

37 - 在集群中使用级联删除

本页面向你展示如何设置在你的集群执行垃圾收集 时要使用的级联删除 类型。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你还需要创建一个 Deployment 示例 以试验不同类型的级联删除。你需要为每种级联删除类型来重建 Deployment。

检查 Pod 上的属主引用

检查确认你的 Pods 上存在 ownerReferences 字段:

kubectl get pods -l app=nginx --output=yaml

输出中包含 ownerReferences 字段,类似这样:

apiVersion: v1
    ...
    ownerReferences:
    - apiVersion: apps/v1
      blockOwnerDeletion: true
      controller: true
      kind: ReplicaSet
      name: nginx-deployment-6b474476c4
      uid: 4fdcd81c-bd5d-41f7-97af-3a3b759af9a7
    ...

使用前台级联删除

默认情况下,Kubernetes 使用后台级联删除 以删除依赖某对象的其他对象。取决于你的集群所运行的 Kubernetes 版本, 你可以使用 kubectl 或者 Kubernetes API 来切换到前台级联删除。 要获知版本信息,请输入 kubectl version.

你可以使用 kubectl 或者 Kubernetes API 来基于前台级联删除来删除对象。

使用 kubectl

运行下面的命令:

kubectl delete deployment nginx-deployment --cascade=foreground

使用 Kubernetes API

  1. 启动一个本地代理会话:

    kubectl proxy --port=8080
    
  1. 使用 curl 来触发删除操作:

    curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
        -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
        -H "Content-Type: application/json"
    

    输出中包含 foregroundDeletion finalizer, 类似这样:

    "kind": "Deployment",
    "apiVersion": "apps/v1",
    "metadata": {
        "name": "nginx-deployment",
        "namespace": "default",
        "uid": "d1ce1b02-cae8-4288-8a53-30e84d8fa505",
        "resourceVersion": "1363097",
        "creationTimestamp": "2021-07-08T20:24:37Z",
        "deletionTimestamp": "2021-07-08T20:27:39Z",
        "finalizers": [
          "foregroundDeletion"
        ]
        ...
    

使用后台级联删除

  1. 创建一个 Deployment 示例
  2. 基于你的集群所运行的 Kubernetes 版本,使用 kubectl 或者 Kubernetes API 来删除 Deployment。 要获知版本信息,请输入 kubectl version.

你可以使用 kubectl 或者 Kubernetes API 来执行后台级联删除方式的对象删除操作。

Kubernetes 默认采用后台级联删除方式,如果你在运行下面的命令时不指定 --cascade 标志或者 propagationPolicy 参数时,用这种方式来删除对象。

使用 kubectl

运行下面的命令:

kubectl delete deployment nginx-deployment --cascade=background

使用 Kubernetes API

  1. 启动一个本地代理会话:

    kubectl proxy --port=8080
    
  1. 使用 curl 来触发删除操作:

    curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
        -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Background"}' \
        -H "Content-Type: application/json"
    

    输出类似于:

    "kind": "Status",
    "apiVersion": "v1",
    ...
    "status": "Success",
    "details": {
        "name": "nginx-deployment",
        "group": "apps",
        "kind": "deployments",
        "uid": "cc9eefb9-2d49-4445-b1c1-d261c9396456"
    }
    

删除属主对象和孤立的依赖对象

默认情况下,当你告诉 Kubernetes 删除某个对象时, 控制器 也会删除依赖该对象 的其他对象。 取决于你的集群所运行的 Kubernetes 版本,你也可以使用 kubectl 或者 Kubernetes API 来让 Kubernetes 孤立 这些依赖对象。 要获知版本信息,请输入 kubectl version.

使用 kubectl

运行下面的命令:

kubectl delete deployment nginx-deployment --cascade=orphan

使用 Kubernetes API

  1. 启动一个本地代理会话:

    kubectl proxy --port=8080
    
  1. 使用 curl 来触发删除操作:

    curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
        -d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
        -H "Content-Type: application/json"
    

    输出中在 finalizers 字段中包含 orphan,如下所示:

    "kind": "Deployment",
    "apiVersion": "apps/v1",
    "namespace": "default",
    "uid": "6f577034-42a0-479d-be21-78018c466f1f",
    "creationTimestamp": "2021-07-09T16:46:37Z",
    "deletionTimestamp": "2021-07-09T16:47:08Z",
    "deletionGracePeriodSeconds": 0,
    "finalizers": [
      "orphan"
    ],
    ...
    

你可以检查 Deployment 所管理的 Pods 仍然处于运行状态:

kubectl get pods -l app=nginx

接下来

38 - 使用 KMS 驱动进行数据加密

本页展示了如何配置密钥管理服务(Key Management Service,KMS)驱动和插件以启用 Secret 数据加密。 在 Kubernetes 1.30 中,存在两个版本的 KMS 静态加密方式。 如果可行的话,建议使用 KMS v2,因为(自 Kubernetes v1.28 起)KMS v1 已经被弃用并 (自 Kubernetes v1.29 起)默认被禁用。 KMS v2 提供了比 KMS v1 明显更好的性能特征。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你所需要的 Kubernetes 版本取决于你已选择的 KMS API 版本。Kubernetes 推荐使用 KMS v2。

  • 如果你选择了 KMS API v2,则应使用 Kubernetes v1.30 (如果你正在运行也支持 KMS API v2 的其他 Kubernetes 版本,需查阅该 Kubernetes 版本的文档)。
  • 如果你选择了 KMS API v1 来支持早于 v1.27 版本的集群,或者你有一个仅支持 KMS v1 的旧版 KMS 插件, 那么任何受支持的 Kubernetes 版本都可以良好工作。此 API 自 Kubernetes v1.28 起被弃用。 Kubernetes 不推荐使用此 API。
要获知版本信息,请输入 kubectl version.

KMS v1

特性状态: Kubernetes v1.28 [deprecated]
  • 需要 Kubernetes 1.10.0 或更高版本
  • 对于 1.29 及更高版本,KMS 的 v1 实现默认处于禁用状态。 要启用此特性,设置 --feature-gates=KMSv1=true 以配置 KMS v1 驱动。
  • 你的集群必须使用 etcd v3 或更高版本

KMS v2

特性状态: Kubernetes v1.29 [stable]
  • 你的集群必须使用 etcd v3 或更高版本

KMS 加密和为每个对象加密的密钥

KMS 加密驱动使用封套加密模型来加密 etcd 中的数据。数据使用数据加密密钥(DEK)加密。 这些 DEK 经一个密钥加密密钥(KEK)加密后在一个远端的 KMS 中存储和管理。

如果你使用(已弃用的)KMS v1 实现,每次加密将生成新的 DEK。

对于 KMS v2,每次加密将生成新的 DEK: API 服务器使用密钥派生函数根据秘密的种子数结合一些随机数据生成一次性的数据加密密钥。 种子会在 KEK 轮换时进行轮换 (有关更多详细信息,请参阅下面的“了解 key_id 和密钥轮换”章节)。

KMS 驱动使用 gRPC 通过 UNIX 域套接字与一个特定的 KMS 插件通信。 这个 KMS 插件作为一个 gRPC 服务器被部署在 Kubernetes 控制平面的相同主机上,负责与远端 KMS 的通信。

配置 KMS 驱动

为了在 API 服务器上配置 KMS 驱动,在加密配置文件中的 providers 数组中加入一个类型为 kms 的驱动,并设置下列属性:

KMS v1

  • apiVersion:针对 KMS 驱动的 API 版本。此项留空或设为 v1
  • name:KMS 插件的显示名称。一旦设置,就无法更改。
  • endpoint:gRPC 服务器(KMS 插件)的监听地址。该端点是一个 UNIX 域套接字。
  • cachesize:以明文缓存的数据加密密钥(DEK)的数量。一旦被缓存, 就可以直接使用 DEK 而无需另外调用 KMS;而未被缓存的 DEK 需要调用一次 KMS 才能解包。
  • timeout:在返回一个错误之前,kube-apiserver 等待 kms-plugin 响应的时间(默认是 3 秒)。

KMS v2

  • apiVersion:针对 KMS 驱动的 API 版本。此项设为 v2
  • name:KMS 插件的显示名称。一旦设置,就无法更改。
  • endpoint:gRPC 服务器(KMS 插件)的监听地址。该端点是一个 UNIX 域套接字。
  • timeout:在返回一个错误之前,kube-apiserver 等待 kms-plugin 响应的时间(默认是 3 秒)。

KMS v2 不支持 cachesize 属性。一旦服务器通过调用 KMS 解密了数据加密密钥(DEK), 所有的 DEK 将会以明文形式被缓存。一旦被缓存,DEK 可以无限期地用于解密操作,而无需再次调用 KMS。

参见理解静态配置加密

实现 KMS 插件

为实现一个 KMS 插件,你可以开发一个新的插件 gRPC 服务器或启用一个由你的云服务驱动提供的 KMS 插件。 你可以将这个插件与远程 KMS 集成,并把它部署到 Kubernetes 控制平面上。

启用由云服务驱动支持的 KMS

有关启用云服务驱动特定的 KMS 插件的说明,请咨询你的云服务驱动商。

开发 KMS 插件 gRPC 服务器

你可以使用 Go 语言的存根文件开发 KMS 插件 gRPC 服务器。 对于其他语言,你可以用 proto 文件创建可以用于开发 gRPC 服务器代码的存根文件。

KMS v1

  • 使用 Go:使用存根文件 api.pb.go 中的函数和数据结构开发 gRPC 服务器代码。
  • 使用 Go 以外的其他语言:用 protoc 编译器编译 proto 文件: api.proto 为指定语言生成存根文件。

KMS v2

  • 使用 Go:提供了一个高级简化这个过程。 底层实现可以使用存根文件 api.pb.go 中的函数和数据结构开发 gRPC 服务器代码。

  • 使用 Go 以外的其他语言:用 protoc 编译器编译 proto 文件: api.proto 为指定语言生成存根文件。

然后使用存根文件中的函数和数据结构开发服务器代码。

注意

KMS v1
  • kms 插件版本:v1beta1

    作为对过程调用 Version 的响应,兼容的 KMS 插件应把 v1beta1 作为 VersionResponse.version 版本返回。

  • 消息版本:v1beta1

    所有来自 KMS 驱动的消息都把 version 字段设置为 v1beta1

  • 协议:UNIX 域套接字 (unix)

    该插件被实现为一个在 UNIX 域套接字上侦听的 gRPC 服务器。 该插件部署时应在文件系统上创建一个文件来运行 gRPC UNIX 域套接字连接。 API 服务器(gRPC 客户端)配置了 KMS 驱动(gRPC 服务器)UNIX 域套接字端点,以便与其通信。 通过以 /@ 开头的端点,可以使用一个抽象的 Linux 套接字,即 unix:///@foo。 使用这种类型的套接字时必须小心,因为它们没有 ACL 的概念(与传统的基于文件的套接字不同)。 然而,这些套接字遵从 Linux 网络命名空间约束,因此只能由同一 Pod 中的容器进行访问,除非使用了主机网络。

KMS v2
  • KMS 插件版本:v2

    作为对过程调用 Status 的响应,兼容的 KMS 插件应把 StatusResponse.version 作为其 KMS 兼容版本返回。 该状态响应还应包括 "ok" 作为 StatusResponse.healthz 以及 key_id(远程 KMS KEK ID)作为 StatusResponse.key_id。Kubernetes 项目建议你的插件与稳定的 v2 KMS API 兼容。 Kubernetes 1.30 针对 KMS 还支持 v2beta1 API; Kubernetes 后续版本可能会继续支持该 Beta 版本。

    当一切健康时,API 服务器大约每分钟轮询一次 Status 过程调用, 而插件不健康时每 10 秒钟轮询一次。使用这些插件时要注意优化此调用,因为此调用将经受持续的负载。

  • 加密

    EncryptRequest 过程调用提供明文和一个 UID 以用于日志记录。 响应必须包括密文、使用的 KEK 的 key_id,以及可选的任意元数据,这些元数据可以 帮助 KMS 插件在未来的 DecryptRequest 调用中(通过 annotations 字段)进行解密。 插件必须保证所有不同的明文都会产生不同的响应 (ciphertext, key_id, annotations)

    如果插件返回一个非空的 annotations 映射,则所有映射键必须是完全限定域名, 例如 example.comannotation 的一个示例用例是 {"kms.example.io/remote-kms-auditid":"<远程 KMS 使用的审计 ID>"}

    当 API 服务器运行正常时,并不会高频执行 EncryptRequest 过程调用。 插件实现仍应力求使每个请求的延迟保持在 100 毫秒以下。

  • 解密

    DecryptRequest 过程调用提供 EncryptRequest 中的 (ciphertext, key_id, annotations) 和一个 UID 以用于日志记录。正如预期的那样,它是 EncryptRequest 调用的反向操作。插件必须验证 key_id 是否为其理解的密钥ID - 除非这些插件确定数据是之前自己加密的,否则不应尝试解密。

    在启动时,API 服务器可能会执行数千个 DecryptRequest 过程调用以填充其监视缓存。 因此,插件实现必须尽快执行这些调用,并应力求使每个请求的延迟保持在 10 毫秒以下。

  • 理解 key_id 和密钥轮换

    key_id 是目前使用的远程 KMS KEK 的公共、非机密名称。 它可能会在 API 服务器的常规操作期间记录,因此不得包含任何私有数据。 建议插件实现使用哈希来避免泄漏任何数据。 KMS v2 指标负责在通过 /metrics 端点公开之前对此值进行哈希。

    API 服务器认为从 Status 过程调用返回的 key_id 是权威性的。因此,此值的更改表示远程 KEK 已更改, 并且使用旧 KEK 加密的数据应在执行无操作写入时标记为过期(如下所述)。如果 EncryptRequest 过程调用返回与 Status 不同的 key_id,则响应将被丢弃,并且插件将被认为是不健康的。 因此,插件实现必须保证从 Status 返回的 key_idEncryptRequest 返回的 key_id 相同。 此外,插件必须确保 key_id 是稳定的,并且不会在不同值之间翻转(即在远程 KEK 轮换期间)。

    插件不能重新使用 key_id,即使在先前使用的远程 KEK 被恢复的情况下也是如此。 例如,如果插件使用了 key_id=A,切换到 key_id=B,然后又回到 key_id=A, 那么插件应报告 key_id=A_001 或使用一个新值,如 key_id=C

    由于 API 服务器大约每分钟轮询一次 Status,因此 key_id 轮换并不立即发生。 此外,API 服务器在三分钟内以最近一个有效状态为准。因此, 如果用户想采取被动方法进行存储迁移(即等待),则必须安排迁移在远程 KEK 轮换后的 3 + N + M 分钟内发生 (其中 N 表示插件观察 key_id 更改所需的时间,M 是允许处理配置更改的缓冲区时间 - 建议至少使用 5 分钟)。 请注意,执行 KEK 轮换不需要进行 API 服务器重启。

  • 协议:UNIX 域套接字 (unix)

    该插件被实现为一个在 UNIX 域套接字上侦听的 gRPC 服务器。 该插件部署时应在文件系统上创建一个文件来运行 gRPC UNIX 域套接字连接。 API 服务器(gRPC 客户端)配置了 KMS 驱动(gRPC 服务器)UNIX 域套接字端点,以便与其通信。 通过以 /@ 开头的端点,可以使用一个抽象的 Linux 套接字,即 unix:///@foo。 使用这种类型的套接字时必须小心,因为它们没有 ACL 的概念(与传统的基于文件的套接字不同)。 然而,这些套接字遵从 Linux 网络命名空间,因此只能由同一 Pod 中的容器进行访问,除非使用了主机网络。

将 KMS 插件与远程 KMS 整合

KMS 插件可以用任何受 KMS 支持的协议与远程 KMS 通信。 所有的配置数据,包括 KMS 插件用于与远程 KMS 通信的认证凭据,都由 KMS 插件独立地存储和管理。 KMS 插件可以用额外的元数据对密文进行编码,这些元数据是在把它发往 KMS 进行解密之前可能要用到的 (KMS v2 提供了专用的 annotations 字段简化了这个过程)。

部署 KMS 插件

确保 KMS 插件与 Kubernetes API 服务器运行在同一主机上。

使用 KMS 驱动加密数据

为了加密数据:

  1. 使用适合于 kms 驱动的属性创建一个新的 EncryptionConfiguration 文件,以加密 Secret 和 ConfigMap 等资源。 如果要加密使用 CustomResourceDefinition 定义的扩展 API,你的集群必须运行 Kubernetes v1.26 或更高版本。

  2. 设置 kube-apiserver 的 --encryption-provider-config 参数指向配置文件的位置。

  1. --encryption-provider-config-automatic-reload 布尔参数决定了磁盘内容发生变化时是否应自动重新加载 通过 --encryption-provider-config 设置的文件。这样可以在不重启 API 服务器的情况下进行密钥轮换。

  2. 重启你的 API 服务器。

KMS v1

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example
    providers:
      - kms:
          name: myKmsPluginFoo
          endpoint: unix:///tmp/socketfile.sock
          cachesize: 100
          timeout: 3s
      - kms:
          name: myKmsPluginBar
          endpoint: unix:///tmp/socketfile.sock
          cachesize: 100
          timeout: 3s

KMS v2

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example
    providers:
      - kms:
          apiVersion: v2
          name: myKmsPluginFoo
          endpoint: unix:///tmp/socketfile.sock
          timeout: 3s
      - kms:
          apiVersion: v2
          name: myKmsPluginBar
          endpoint: unix:///tmp/socketfile.sock
          timeout: 3s

--encryption-provider-config-automatic-reload 设置为 true 会将所有健康检查集中到同一个健康检查端点。 只有 KMS v1 驱动正使用且加密配置未被自动重新加载时,才能进行独立的健康检查。

下表总结了每个 KMS 版本的健康检查端点:

KMS 配置 没有自动重新加载 有自动重新加载
仅 KMS v1 Individual Healthchecks Single Healthcheck
仅 KMS v2 Single Healthcheck Single Healthcheck
KMS v1 和 v2 Individual Healthchecks Single Healthcheck
没有 KMS Single Healthcheck

Single Healthcheck 意味着唯一的健康检查端点是 /healthz/kms-providers

Individual Healthchecks 意味着每个 KMS 插件都有一个对应的健康检查端点, 并且这一端点基于插件在加密配置中的位置确定,例如 /healthz/kms-provider-0/healthz/kms-provider-1 等。

这些健康检查端点路径是由服务器硬编码、生成并控制的。 Individual Healthchecks 的索引序号对应于 KMS 加密配置被处理的顺序。

在执行确保所有 Secret 都加密中所给步骤之前, providers 列表应以 identity: {} 提供程序作为结尾,以允许读取未加密的数据。 加密所有资源后,应移除 identity 提供程序,以防止 API 服务器接受未加密的数据。

有关 EncryptionConfiguration 格式的更多详细信息,请参阅 kube-apiserver 加密 API 参考(v1)

验证数据已经加密

当静态加密被正确配置时,资源将在写入时被加密。 重启 kube-apiserver 后,所有新建或更新的 Secret 或在 EncryptionConfiguration 中配置的其他资源类型在存储时应该已被加密。 要验证这点,你可以用 etcdctl 命令行程序获取私密数据的内容。

  1. 在默认的命名空间里创建一个名为 secret1 的 Secret:

    kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    
  1. etcdctl 命令行,从 etcd 读取出 Secret:

    ETCDCTL_API=3 etcdctl get /kubernetes.io/secrets/default/secret1 [...] | hexdump -C
    

    其中 [...] 包含连接 etcd 服务器的额外参数。

  1. 验证对于 KMS v1,保存的 Secret 以 k8s:enc:kms:v1: 开头, 对于 KMS v2,保存的 Secret 以 k8s:enc:kms:v2: 开头,这表明 kms 驱动已经对结果数据加密。
  1. 验证通过 API 获取的 Secret 已被正确解密:

    kubectl describe secret secret1 -n default
    

    Secret 应包含 mykey: mydata

确保所有 Secret 都已被加密

当静态加密被正确配置时,资源将在写入时被加密。 这样我们可以执行就地零干预更新来确保数据被加密。

下列命令读取所有 Secret 并更新它们以便应用服务器端加密。如果因为写入冲突导致错误发生, 请重试此命令。对较大的集群,你可能希望根据命名空间或脚本更新去细分 Secret 内容。

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

从本地加密驱动切换到 KMS 驱动

为了从本地加密驱动切换到 kms 驱动并重新加密所有 Secret 内容:

  1. 在配置文件中加入 kms 驱动作为第一个条目,如下列样例所示

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
          - secrets
        providers:
          - kms:
              apiVersion: v2
              name : myKmsPlugin
              endpoint: unix:///tmp/socketfile.sock
          - aescbc:
              keys:
                - name: key1
                  secret: <BASE 64 ENCODED SECRET>
    
  1. 重启所有 kube-apiserver 进程。

  2. 运行下列命令使用 kms 驱动强制重新加密所有 Secret。

    kubectl get secrets --all-namespaces -o json | kubectl replace -f -
    

接下来

如果你不想再对 Kubernetes API 中保存的数据加密, 请阅读解密已静态存储的数据

39 - 使用 CoreDNS 进行服务发现

此页面介绍了 CoreDNS 升级过程以及如何安装 CoreDNS 而不是 kube-dns。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 v1.9. 要获知版本信息,请输入 kubectl version.

关于 CoreDNS

CoreDNS 是一个灵活可扩展的 DNS 服务器,可以作为 Kubernetes 集群 DNS。 与 Kubernetes 一样,CoreDNS 项目由 CNCF 托管。

通过替换现有集群部署中的 kube-dns,或者使用 kubeadm 等工具来为你部署和升级集群, 可以在你的集群中使用 CoreDNS 而非 kube-dns。

安装 CoreDNS

有关手动部署或替换 kube-dns,请参阅 CoreDNS 网站

迁移到 CoreDNS

使用 kubeadm 升级现有集群

在 Kubernetes 1.21 版本中,kubeadm 移除了对将 kube-dns 作为 DNS 应用的支持。 对于 kubeadm v1.30,所支持的唯一的集群 DNS 应用是 CoreDNS。

当你使用 kubeadm 升级使用 kube-dns 的集群时,你还可以执行到 CoreDNS 的迁移。 在这种场景中,kubeadm 将基于 kube-dns ConfigMap 生成 CoreDNS 配置("Corefile"), 保存存根域和上游名称服务器的配置。

升级 CoreDNS

你可以在 Kubernetes 中的 CoreDNS 版本 页面查看 kubeadm 为不同版本 Kubernetes 所安装的 CoreDNS 版本。

如果你只想升级 CoreDNS 或使用自己的定制镜像,也可以手动升级 CoreDNS。 参看指南和演练 文档了解如何平滑升级。 在升级你的集群过程中,请确保现有 CoreDNS 的配置("Corefile")被保留下来。

如果使用 kubeadm 工具来升级集群,则 kubeadm 可以自动处理保留现有 CoreDNS 配置这一事项。

CoreDNS 调优

当资源利用方面有问题时,优化 CoreDNS 的配置可能是有用的。 有关详细信息,请参阅有关扩缩 CoreDNS 的文档

接下来

你可以通过修改 CoreDNS 的配置("Corefile")来配置 CoreDNS, 以支持比 kube-dns 更多的用例。 请参考 kubernetes CoreDNS 插件的文档 或者 CoreDNS 博客上的博文 Kubernetes 的自定义 DNS 条目, 以了解更多信息。

40 - 在 Kubernetes 集群中使用 NodeLocal DNSCache

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

本页概述了 Kubernetes 中的 NodeLocal DNSCache 功能。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

要获知版本信息,请输入 kubectl version.

引言

NodeLocal DNSCache 通过在集群节点上作为 DaemonSet 运行 DNS 缓存代理来提高集群 DNS 性能。 在当今的体系结构中,运行在 'ClusterFirst' DNS 模式下的 Pod 可以连接到 kube-dns serviceIP 进行 DNS 查询。 通过 kube-proxy 添加的 iptables 规则将其转换为 kube-dns/CoreDNS 端点。 借助这种新架构,Pod 将可以访问在同一节点上运行的 DNS 缓存代理,从而避免 iptables DNAT 规则和连接跟踪。 本地缓存代理将查询 kube-dns 服务以获取集群主机名的缓存缺失(默认为 "cluster.local" 后缀)。

动机

  • 使用当前的 DNS 体系结构,如果没有本地 kube-dns/CoreDNS 实例,则具有最高 DNS QPS 的 Pod 可能必须延伸到另一个节点。 在这种场景下,拥有本地缓存将有助于改善延迟。
  • 跳过 iptables DNAT 和连接跟踪将有助于减少 conntrack 竞争并避免 UDP DNS 条目填满 conntrack 表。
  • 从本地缓存代理到 kube-dns 服务的连接可以升级为 TCP。 TCP conntrack 条目将在连接关闭时被删除,相反 UDP 条目必须超时 (默认 nf_conntrack_udp_timeout 是 30 秒)。
  • 将 DNS 查询从 UDP 升级到 TCP 将减少由于被丢弃的 UDP 包和 DNS 超时而带来的尾部等待时间; 这类延时通常长达 30 秒(3 次重试 + 10 秒超时)。 由于 nodelocal 缓存监听 UDP DNS 查询,应用不需要变更。
  • 在节点级别对 DNS 请求的度量和可见性。
  • 可以重新启用负缓存,从而减少对 kube-dns 服务的查询数量。

架构图

启用 NodeLocal DNSCache 之后,DNS 查询所遵循的路径如下:

NodeLocal DNSCache 流

Nodelocal DNSCache 流

此图显示了 NodeLocal DNSCache 如何处理 DNS 查询。

配置

可以使用以下步骤启动此功能:

  • 根据示例 nodelocaldns.yaml 准备一个清单,把它保存为 nodelocaldns.yaml
  • 如果使用 IPv6,在使用 'IP:Port' 格式的时候需要把 CoreDNS 配置文件里的所有 IPv6 地址用方括号包起来。 如果你使用上述的示例清单, 需要把配置行 L70 修改为: "health [__PILLAR__LOCAL__DNS__]:8080"。
  • 把清单里的变量更改为正确的值:

    kubedns=`kubectl get svc kube-dns -n kube-system -o jsonpath={.spec.clusterIP}`
    domain=<cluster-domain>
    localdns=<node-local-address>
    

    <cluster-domain> 的默认值是 "cluster.local"。<node-local-address> 是 NodeLocal DNSCache 选择的本地侦听 IP 地址。

  • 如果 kube-proxy 运行在 IPTABLES 模式:

    sed -i "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/__PILLAR__DNS__SERVER__/$kubedns/g" nodelocaldns.yaml
    

    node-local-dns Pod 会设置 __PILLAR__CLUSTER__DNS____PILLAR__UPSTREAM__SERVERS__。 在此模式下, node-local-dns Pod 会同时侦听 kube-dns 服务的 IP 地址和 <node-local-address> 的地址,以便 Pod 可以使用其中任何一个 IP 地址来查询 DNS 记录。

  • 如果 kube-proxy 运行在 IPVS 模式:

    sed -i "s/__PILLAR__LOCAL__DNS__/$localdns/g; s/__PILLAR__DNS__DOMAIN__/$domain/g; s/,__PILLAR__DNS__SERVER__//g; s/__PILLAR__CLUSTER__DNS__/$kubedns/g" nodelocaldns.yaml
    

    在此模式下,node-local-dns Pod 只会侦听 <node-local-address> 的地址。 node-local-dns 接口不能绑定 kube-dns 的集群 IP 地址,因为 IPVS 负载均衡使用的接口已经占用了该地址。 node-local-dns Pod 会设置 __PILLAR__UPSTREAM__SERVERS__

  • 运行 kubectl create -f nodelocaldns.yaml

  • 如果 kube-proxy 运行在 IPVS 模式,需要修改 kubelet 的 --cluster-dns 参数 NodeLocal DNSCache 正在侦听的 <node-local-address> 地址。 否则,不需要修改 --cluster-dns 参数,因为 NodeLocal DNSCache 会同时侦听 kube-dns 服务的 IP 地址和 <node-local-address> 的地址。

启用后,node-local-dns Pod 将在每个集群节点上的 kube-system 名字空间中运行。 此 Pod 在缓存模式下运行 CoreDNS, 因此每个节点都可以使用不同插件公开的所有 CoreDNS 指标。

如果要禁用该功能,你可以使用 kubectl delete -f <manifest> 来删除 DaemonSet。 你还应该回滚你对 kubelet 配置所做的所有改动。

StubDomains 和上游服务器配置

node-local-dns Pod 能够自动读取 kube-system 名字空间中 kube-dns ConfigMap 中保存的 StubDomains 和上游服务器信息。ConfigMap 中的内容需要遵从此示例中所给的格式。 node-local-dns ConfigMap 也可被直接修改,使用 Corefile 格式设置 stubDomain 配置。 某些云厂商可能不允许直接修改 node-local-dns ConfigMap 的内容。 在这种情况下,可以更新 kube-dns ConfigMap。

设置内存限制

node-local-dns Pod 使用内存来保存缓存项并处理查询。 由于它们并不监视 Kubernetes 对象变化,集群规模或者 Service/EndpointSlices 的数量都不会直接影响内存用量。内存用量会受到 DNS 查询模式的影响。 根据 CoreDNS 文档,

The default cache size is 10000 entries, which uses about 30 MB when completely filled. (默认的缓存大小是 10000 个表项,当完全填充时会使用约 30 MB 内存)

这一数值是(缓存完全被填充时)每个服务器块的内存用量。 通过设置小一点的缓存大小可以降低内存用量。

并发查询的数量会影响内存需求,因为用来处理查询请求而创建的 Go 协程都需要一定量的内存。 你可以在 forward 插件中使用 max_concurrent 选项设置并发查询数量上限。

如果一个 node-local-dns Pod 尝试使用的内存超出可提供的内存量 (因为系统资源总量的,或者所配置的资源约束)的原因, 操作系统可能会关闭这一 Pod 的容器。 发生这种情况时,被终止的("OOMKilled")容器不会清理其启动期间所添加的定制包过滤规则。 该 node-local-dns 容器应该会被重启(因其作为 DaemonSet 的一部分被管理), 但因上述原因可能每次容器失败时都会导致 DNS 有一小段时间不可用: the packet filtering rules direct DNS queries to a local Pod that is unhealthy (包过滤器规则将 DNS 查询转发到本地某个不健康的 Pod)。

通过不带限制地运行 node-local-dns Pod 并度量其内存用量峰值,你可以为其确定一个合适的内存限制值。 你也可以安装并使用一个运行在 “Recommender Mode(建议者模式)” 的 VerticalPodAutoscaler, 并查看该组件输出的建议信息。

41 - 在 Kubernetes 集群中使用 sysctl

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

本文档介绍如何通过 sysctl 接口在 Kubernetes 集群中配置和使用内核参数。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

对一些步骤,你需要能够重新配置在你的集群里运行的 kubelet 命令行的选项。

获取 Sysctl 的参数列表

在 Linux 中,管理员可以通过 sysctl 接口修改内核运行时的参数。在 /proc/sys/ 虚拟文件系统下存放许多内核参数。这些参数涉及了多个内核子系统,如:

  • 内核子系统(通常前缀为: kernel.
  • 网络子系统(通常前缀为: net.
  • 虚拟内存子系统(通常前缀为: vm.
  • MDADM 子系统(通常前缀为: dev.
  • 更多子系统请参见内核文档

若要获取完整的参数列表,请执行以下命令:

sudo sysctl -a

安全和非安全的 Sysctl 参数

Kubernetes 将 sysctl 参数分为 安全非安全的安全 的 sysctl 参数除了需要设置恰当的命名空间外,在同一节点上的不同 Pod 之间也必须是 相互隔离的。这意味着 Pod 上设置 安全的 sysctl 参数时:

  • 必须不能影响到节点上的其他 Pod
  • 必须不能损害节点的健康
  • 必须不允许使用超出 Pod 的资源限制的 CPU 或内存资源。

至今为止,大多数 有命名空间的 sysctl 参数不一定被认为是 安全 的。 以下几种 sysctl 参数是 安全的

  • kernel.shm_rmid_forced
  • net.ipv4.ip_local_port_range
  • net.ipv4.tcp_syncookies
  • net.ipv4.ping_group_range(从 Kubernetes 1.18 开始);
  • net.ipv4.ip_unprivileged_port_start(从 Kubernetes 1.22 开始);
  • net.ipv4.ip_local_reserved_ports(从 Kubernetes 1.27 开始,需要 kernel 3.16+);
  • net.ipv4.tcp_keepalive_time(从 Kubernetes 1.29 开始,需要 kernel 4.5+);
  • net.ipv4.tcp_fin_timeout(从 Kubernetes 1.29 开始,需要 kernel 4.6+);
  • net.ipv4.tcp_keepalive_intvl(从 Kubernetes 1.29 开始,需要 kernel 4.5+);
  • net.ipv4.tcp_keepalive_probes(从 Kubernetes 1.29 开始,需要 kernel 4.5+)。

在未来的 Kubernetes 版本中,若 kubelet 支持更好的隔离机制, 则上述列表中将会列出更多 安全的 sysctl 参数。

启用非安全的 Sysctl 参数

所有 安全的 sysctl 参数都默认启用。

所有 非安全的 sysctl 参数都默认禁用,且必须由集群管理员在每个节点上手动开启。 那些设置了不安全 sysctl 参数的 Pod 仍会被调度,但无法正常启动。

参考上述警告,集群管理员只有在一些非常特殊的情况下(如:高可用或实时应用调整), 才可以启用特定的 非安全的 sysctl 参数。 如需启用 非安全的 sysctl 参数,请你在每个节点上分别设置 kubelet 命令行参数,例如:

kubelet --allowed-unsafe-sysctls \
  'kernel.msg*,net.core.somaxconn' ...

如果你使用 Minikube,可以通过 extra-config 参数来配置:

minikube start --extra-config="kubelet.allowed-unsafe-sysctls=kernel.msg*,net.core.somaxconn"...

只有 有命名空间的 sysctl 参数可以通过该方式启用。

设置 Pod 的 Sysctl 参数

目前,在 Linux 内核中,有许多的 sysctl 参数都是 有命名空间的。 这就意味着可以为节点上的每个 Pod 分别去设置它们的 sysctl 参数。 在 Kubernetes 中,只有那些有命名空间的 sysctl 参数可以通过 Pod 的 securityContext 对其进行配置。

以下列出有命名空间的 sysctl 参数,在未来的 Linux 内核版本中,此列表可能会发生变化。

  • kernel.shm*,
  • kernel.msg*,
  • kernel.sem,
  • fs.mqueue.*,
  • 那些可以在容器网络命名空间中设置的 net.*。但是,也有例外(例如 net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_expect_max 可以在容器网络命名空间中设置,但在 Linux 5.12.2 之前它们是无命名空间的)。

没有命名空间的 sysctl 参数称为 节点级别的 sysctl 参数。 如果需要对其进行设置,则必须在每个节点的操作系统上手动地去配置它们, 或者通过在 DaemonSet 中运行特权模式容器来配置。

可使用 Pod 的 securityContext 来配置有命名空间的 sysctl 参数, securityContext 应用于同一个 Pod 中的所有容器。

此示例中,使用 Pod SecurityContext 来对一个安全的 sysctl 参数 kernel.shm_rmid_forced 以及两个非安全的 sysctl 参数 net.core.somaxconnkernel.msgmax 进行设置。 在 Pod 规约中对 安全的非安全的 sysctl 参数不做区分。

apiVersion: v1
kind: Pod
metadata:
  name: sysctl-example
spec:
  securityContext:
    sysctls:
    - name: kernel.shm_rmid_forced
      value: "0"
    - name: net.core.somaxconn
      value: "1024"
    - name: kernel.msgmax
      value: "65536"
  ...

最佳实践方案是将集群中具有特殊 sysctl 设置的节点视为 有污点的,并且只调度需要使用到特殊 sysctl 设置的 Pod 到这些节点上。建议使用 Kubernetes 的污点和容忍度特性 来实现它。

设置了 非安全的 sysctl 参数的 Pod 在禁用了这两种 非安全的 sysctl 参数配置的节点上启动都会失败。 与 节点级别的 sysctl 一样, 建议开启污点和容忍度特性为节点配置污点以便将 Pod 调度到正确的节点之上。

42 - 使用 NUMA 感知的内存管理器

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

Kubernetes 内存管理器(Memory Manager)为 Guaranteed QoS 类 的 Pods 提供可保证的内存(及大页面)分配能力。

内存管理器使用提示生成协议来为 Pod 生成最合适的 NUMA 亲和性配置。 内存管理器将这类亲和性提示输入给中央管理器(即 Topology Manager)。 基于所给的提示和 Topology Manager(拓扑管理器)的策略设置,Pod 或者会被某节点接受,或者被该节点拒绝。

此外,内存管理器还确保 Pod 所请求的内存是从尽量少的 NUMA 节点分配而来。

内存管理器仅能用于 Linux 主机。

准备开始

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你的 Kubernetes 服务器版本必须不低于版本 v1.21. 要获知版本信息,请输入 kubectl version.

为了使得内存资源与 Pod 规约中所请求的其他资源对齐:

  • CPU 管理器应该被启用,并且在节点(Node)上要配置合适的 CPU 管理器策略, 参见控制 CPU 管理策略
  • 拓扑管理器要被启用,并且要在节点上配置合适的拓扑管理器策略,参见 控制拓扑管理器策略

从 v1.22 开始,内存管理器通过特性门控 MemoryManager 默认启用。

在 v1.22 之前,kubelet 必须在启动时设置如下标志:

--feature-gates=MemoryManager=true

这样内存管理器特性才会被启用。

内存管理器如何运作?

内存管理器目前为 Guaranteed QoS 类中的 Pod 提供可保证的内存(和大页面)分配能力。 若要立即将内存管理器启用,可参照内存管理器配置节中的指南, 之后按将 Pod 放入 Guaranteed QoS 类 节中所展示的,准备并部署一个 Guaranteed Pod。

内存管理器是一个提示驱动组件(Hint Provider),负责为拓扑管理器提供拓扑提示, 后者根据这些拓扑提示对所请求的资源执行对齐操作。 内存管理器也会为 Pods 应用 cgroups 设置(即 cpuset.mems)。 与 Pod 准入和部署流程相关的完整流程图在Memory Manager KEP: Design Overview, 下面也有说明。

Pod 准入与部署流程中的内存管理器

在这个过程中,内存管理器会更新其内部存储于节点映射和内存映射中的计数器, 从而管理有保障的内存分配。

内存管理器在启动和运行期间按下述逻辑更新节点映射(Node Map)。

启动

当节点管理员应用 --reserved-memory 预留内存标志时执行此逻辑。 这时,节点映射会被更新以反映内存的预留,如 Memory Manager KEP: Memory Maps at start-up (with examples) 所说明。

当配置了 Static 策略时,管理员必须提供 --reserved-memory 标志设置。

运行时

参考文献 Memory Manager KEP: Memory Maps at runtime (with examples) 中说明了成功的 Pod 部署是如何影响节点映射的,该文档也解释了可能发生的内存不足 (Out-of-memory,OOM)情况是如何进一步被 Kubernetes 或操作系统处理的。

在内存管理器运作的语境中,一个重要的话题是对 NUMA 分组的管理。 每当 Pod 的内存请求超出单个 NUMA 节点容量时,内存管理器会尝试创建一个包含多个 NUMA 节点的分组,从而扩展内存容量。解决这个问题的详细描述在文档 Memory Manager KEP: How to enable the guaranteed memory allocation over many NUMA nodes? 中。同时,关于 NUMA 分组是如何管理的,你还可以参考文档 Memory Manager KEP: Simulation - how the Memory Manager works? (by examples)

内存管理器配置

其他管理器也要预先配置。接下来,内存管理器特性需要被启用, 并且采用 Static 策略(静态策略)运行。 作为可选操作,可以预留一定数量的内存给系统或者 kubelet 进程以增强节点的稳定性 (预留内存标志)。

策略

内存管理器支持两种策略。你可以通过 kubelet 标志 --memory-manager-policy 来选择一种策略:

  • None (默认)
  • Static

None 策略

这是默认的策略,并且不会以任何方式影响内存分配。该策略的行为好像内存管理器不存在一样。

None 策略返回默认的拓扑提示信息。这种特殊的提示会表明拓扑驱动组件(Hint Provider) (在这里是内存管理器)对任何资源都没有与 NUMA 亲和性关联的偏好。

Static 策略

Guaranteed Pod 而言,Static 内存管理器策略会返回拓扑提示信息, 该信息与内存分配有保障的 NUMA 节点集合有关,并且内存管理器还通过更新内部的节点映射 对象来完成内存预留。

BestEffortBurstable Pod 而言,因为不存在对有保障的内存资源的请求, Static 内存管理器策略会返回默认的拓扑提示,并且不会通过内部的节点映射对象来预留内存。

预留内存标志

节点可分配机制通常被节点管理员用来为 kubelet 或操作系统进程预留 K8S 节点上的系统资源,目的是提高节点稳定性。 有一组专用的标志可用于这个目的,为节点设置总的预留内存量。 此预配置的值接下来会被用来计算节点上对 Pods “可分配的”内存。

Kubernetes 调度器在优化 Pod 调度过程时,会考虑“可分配的”内存。 前面提到的标志包括 --kube-reserved--system-reserved--eviction-threshold。 这些标志值的综合计作预留内存的总量。

为内存管理器而新增加的 --reserved-memory 标志可以(让节点管理员)将总的预留内存进行划分, 并完成跨 NUMA 节点的预留操作。

标志设置的值是一个按 NUMA 节点的不同内存类型所给的内存预留的值的列表,用逗号分开。 可以使用分号作为分隔符来指定跨多个 NUMA 节点的内存预留。 只有在内存管理器特性被启用的语境下,这个参数才有意义。 内存管理器不会使用这些预留的内存来为容器负载分配内存。

例如,如果你有一个可用内存为 10Gi 的 NUMA 节点 "NUMA0",而参数 --reserved-memory 被设置成要在 "NUMA0" 上预留 1Gi 的内存,那么内存管理器会假定节点上只有 9Gi 内存可用于容器负载。

你也可以忽略此参数,不过这样做时,你要清楚,所有 NUMA 节点上预留内存的数量要等于节点可分配特性 所设定的内存量。如果至少有一个节点可分配参数值为非零,你就需要至少为一个 NUMA 节点设置 --reserved-memory。实际上,eviction-hard 阈值默认为 100Mi, 所以当使用 Static 策略时,--reserved-memory 是必须设置的。

此外,应尽量避免如下配置:

  1. 重复的配置,即同一 NUMA 节点或内存类型被设置不同的取值;
  2. 为某种内存类型设置约束值为零;
  3. 使用物理硬件上不存在的 NUMA 节点 ID;
  4. 使用名字不是 memoryhugepages-<size> 的内存类型名称 (特定的 <size> 的大页面也必须存在)。

语法:

--reserved-memory N:memory-type1=value1,memory-type2=value2,...

  • N(整数)- NUMA 节点索引,例如,0
  • memory-type(字符串)- 代表内存类型:
    • memory - 常规内存;
    • hugepages-2Mihugepages-1Gi - 大页面
  • value(字符串) - 预留内存的量,例如 1Gi

用法示例:

--reserved-memory 0:memory=1Gi,hugepages-1Gi=2Gi

或者

--reserved-memory 0:memory=1Gi --reserved-memory 1:memory=2Gi

--reserved-memory '0:memory=1Gi;1:memory=2Gi'

当你为 --reserved-memory 标志指定取值时,必须要遵从之前通过节点可分配特性标志所设置的值。 换言之,对每种内存类型而言都要遵从下面的规则:

sum(reserved-memory(i)) = kube-reserved + system-reserved + eviction-threshold

其中,i 是 NUMA 节点的索引。

如果你不遵守上面的公式,内存管理器会在启动时输出错误信息。

换言之,上面的例子我们一共要预留 3Gi 的常规内存(type=memory),即:

sum(reserved-memory(i)) = reserved-memory(0) + reserved-memory(1) = 1Gi + 2Gi = 3Gi

下面的例子中给出与节点可分配配置相关的 kubelet 命令行参数:

  • --kube-reserved=cpu=500m,memory=50Mi
  • --system-reserved=cpu=123m,memory=333Mi
  • --eviction-hard=memory.available<500Mi

下面是一个正确配置的示例:

--feature-gates=MemoryManager=true
--kube-reserved=cpu=4,memory=4Gi
--system-reserved=cpu=1,memory=1Gi
--memory-manager-policy=Static
--reserved-memory '0:memory=3Gi;1:memory=2148Mi'

我们对上面的配置做一个检查:

  1. kube-reserved + system-reserved + eviction-hard(default) = reserved-memory(0) + reserved-memory(1)
  2. 4GiB + 1GiB + 100MiB = 3GiB + 2148MiB
  3. 5120MiB + 100MiB = 3072MiB + 2148MiB
  4. 5220MiB = 5220MiB (这是对的)

将 Pod 放入 Guaranteed QoS 类

若所选择的策略不是 None,则内存管理器会辨识处于 Guaranteed QoS 类中的 Pod。 内存管理器为每个 Guaranteed Pod 向拓扑管理器提供拓扑提示信息。 对于不在 Guaranteed QoS 类中的其他 Pod,内存管理器向拓扑管理器提供默认的拓扑提示信息。

下面的来自 Pod 清单的片段将 Pod 加入到 Guaranteed QoS 类中。

当 Pod 的 CPU requests 等于 limits 且为整数值时,Pod 将运行在 Guaranteed QoS 类中。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"

此外,共享 CPU 的 Pods 在 requests 等于 limits 值时也运行在 Guaranteed QoS 类中。

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"

要注意的是,只有 CPU 和内存请求都被设置时,Pod 才会进入 Guaranteed QoS 类。

故障排查

下面的方法可用来排查为什么 Pod 无法被调度或者被节点拒绝:

  • Pod 状态 - 可表明拓扑亲和性错误
  • 系统日志 - 包含用来调试的有价值的信息,例如,关于所生成的提示信息
  • 状态文件 - 其中包含内存管理器内部状态的转储(包含节点映射和内存映射
  • 从 v1.22 开始,设备插件资源 API 可以用来检索关于为容器预留的内存的信息

Pod 状态 (TopologyAffinityError)

这类错误通常在以下情形出现:

  • 节点缺少足够的资源来满足 Pod 请求
  • Pod 的请求因为特定的拓扑管理器策略限制而被拒绝

错误信息会出现在 Pod 的状态中:

kubectl get pods
NAME         READY   STATUS                  RESTARTS   AGE
guaranteed   0/1     TopologyAffinityError   0          113s

使用 kubectl describe pod <id>kubectl get events 可以获得详细的错误信息。

Warning  TopologyAffinityError  10m   kubelet, dell8  Resources cannot be allocated with Topology locality

系统日志

针对特定的 Pod 搜索系统日志。

内存管理器为 Pod 所生成的提示信息可以在日志中找到。 此外,日志中应该也存在 CPU 管理器所生成的提示信息。

拓扑管理器将这些提示信息进行合并,计算得到唯一的最合适的提示数据。 此最佳提示数据也应该出现在日志中。

最佳提示表明要在哪里分配所有的资源。拓扑管理器会用当前的策略来测试此数据, 并基于得出的结论或者接纳 Pod 到节点,或者将其拒绝。

此外,你可以搜索日志查找与内存管理器相关的其他条目,例如 cgroupscpuset.mems 的更新信息等。

检查节点上内存管理器状态

我们首先部署一个 Guaranteed Pod 示例,其规约如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: guaranteed
spec:
  containers:
  - name: guaranteed
    image: consumer
    imagePullPolicy: Never
    resources:
      limits:
        cpu: "2"
        memory: 150Gi
      requests:
        cpu: "2"
        memory: 150Gi
    command: ["sleep","infinity"]

接下来,我们登录到 Pod 运行所在的节点,检查位于 /var/lib/kubelet/memory_manager_state 的状态文件:

{
   "policyName":"Static",
   "machineState":{
      "0":{
         "numberOfAssignments":1,
         "memoryMap":{
            "hugepages-1Gi":{
               "total":0,
               "systemReserved":0,
               "allocatable":0,
               "reserved":0,
               "free":0
            },
            "memory":{
               "total":134987354112,
               "systemReserved":3221225472,
               "allocatable":131766128640,
               "reserved":131766128640,
               "free":0
            }
         },
         "nodes":[
            0,
            1
         ]
      },
      "1":{
         "numberOfAssignments":1,
         "memoryMap":{
            "hugepages-1Gi":{
               "total":0,
               "systemReserved":0,
               "allocatable":0,
               "reserved":0,
               "free":0
            },
            "memory":{
               "total":135286722560,
               "systemReserved":2252341248,
               "allocatable":133034381312,
               "reserved":29295144960,
               "free":103739236352
            }
         },
         "nodes":[
            0,
            1
         ]
      }
   },
   "entries":{
      "fa9bdd38-6df9-4cf9-aa67-8c4814da37a8":{
         "guaranteed":[
            {
               "numaAffinity":[
                  0,
                  1
               ],
               "type":"memory",
               "size":161061273600
            }
         ]
      }
   },
   "checksum":4142013182
}

从这个状态文件,可以推断 Pod 被同时绑定到两个 NUMA 节点,即:

"numaAffinity":[
   0,
   1
],

术语绑定(pinned)意味着 Pod 的内存使用被(通过 cgroups 配置)限制到这些 NUMA 节点。

这也直接意味着内存管理器已经创建了一个 NUMA 分组,由这两个 NUMA 节点组成, 即索引值分别为 01 的 NUMA 节点。

注意 NUMA 分组的管理是有一个相对复杂的管理器处理的, 相关逻辑的进一步细节可在内存管理器的 KEP 中示例1跨 NUMA 节点节找到。

为了分析 NUMA 组中可用的内存资源,必须对分组内 NUMA 节点对应的条目进行汇总。

例如,NUMA 分组中空闲的“常规”内存的总量可以通过将分组内所有 NUMA 节点上空闲内存加和来计算,即将 NUMA 节点 0 和 NUMA 节点 1"memory" 节 (分别是 "free":0"free": 103739236352)相加,得到此分组中空闲的“常规” 内存总量为 0 + 103739236352 字节。

"systemReserved": 3221225472 这一行表明节点的管理员使用 --reserved-memory 为 NUMA 节点 0 上运行的 kubelet 和系统进程预留了 3221225472 字节 (即 3Gi)。

设备插件资源 API

kubelet 提供了一个 PodResourceLister gRPC 服务来启用对资源和相关元数据的检测。 通过使用它的 List gRPC 端点, 可以获得每个容器的预留内存信息,该信息位于 protobuf 协议的 ContainerMemory 消息中。 只能针对 Guaranteed QoS 类中的 Pod 来检索此信息。

接下来

43 - 验证已签名容器镜像

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

准备开始

你需要安装以下工具:

验证二进制签名

Kubernetes 发布过程使用 cosign 的无密钥签名对所有二进制工件(压缩包、 SPDX 文件、 独立的二进制文件)签名。要验证一个特定的二进制文件, 获取组件时要包含其签名和证书:

URL=https://dl.k8s.io/release/v%!f(string=1.).0/bin/linux/amd64
BINARY=kubectl

FILES=(
    "$BINARY"
    "$BINARY.sig"
    "$BINARY.cert"
)

for FILE in "${FILES[@]}"; do
    curl -sSfL --retry 3 --retry-delay 3 "$URL/$FILE" -o "$FILE"
done

然后使用 cosign verify-blob 验证二进制文件:

cosign verify-blob "$BINARY" \
  --signature "$BINARY".sig \
  --certificate "$BINARY".cert \
  --certificate-identity krel-staging@k8s-releng-prod.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com

验证镜像签名

完整的镜像签名列表请参见发行版本

从这个列表中选择一个镜像,并使用 cosign verify 命令来验证它的签名:

cosign verify registry.k8s.io/kube-apiserver-amd64:v%!f(string=1.).0 \
  --certificate-identity krel-trust@k8s-releng-prod.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com \
  | jq .

验证所有控制平面组件镜像

验证最新稳定版(v%!f(string=1.).0)所有已签名的控制平面组件镜像, 请运行以下命令:

curl -Ls "https://sbom.k8s.io/$(curl -Ls https://dl.k8s.io/release/stable.txt)/release" \
  | grep "SPDXID: SPDXRef-Package-registry.k8s.io" \
  | grep -v sha256 | cut -d- -f3- | sed 's/-/\//' | sed 's/-v1/:v1/' \
  | sort > images.txt
input=images.txt
while IFS= read -r image
do
  cosign verify "$image" \
    --certificate-identity krel-trust@k8s-releng-prod.iam.gserviceaccount.com \
    --certificate-oidc-issuer https://accounts.google.com \
    | jq .
done < "$input"

当你完成某个镜像的验证时,可以在你的 Pod 清单通过摘要值来指定该镜像,例如:

registry-url/image-name@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

要了解更多信息,请参考镜像拉取策略章节。

使用准入控制器验证镜像签名

有一些非控制平面镜像 (例如 conformance 镜像), 也可以在部署时使用 sigstore policy-controller 控制器验证其签名。以下是一些有助于你开始使用 policy-controller 的资源:

验证软件物料清单

你可以使用 sigstore 证书和签名或相应的 SHA 文件来验证 Kubernetes 软件物料清单(SBOM):

# 检索最新可用的 Kubernetes 发行版本
VERSION=$(curl -Ls https://dl.k8s.io/release/stable.txt)

# 验证 SHA512 sum
curl -Ls "https://sbom.k8s.io/$VERSION/release" -o "$VERSION.spdx"
echo "$(curl -Ls "https://sbom.k8s.io/$VERSION/release.sha512") $VERSION.spdx" | sha512sum --check

# 验证 SHA256 sum
echo "$(curl -Ls "https://sbom.k8s.io/$VERSION/release.sha256") $VERSION.spdx" | sha256sum --check

# 检索 sigstore 签名和证书
curl -Ls "https://sbom.k8s.io/$VERSION/release.sig" -o "$VERSION.spdx.sig"
curl -Ls "https://sbom.k8s.io/$VERSION/release.cert" -o "$VERSION.spdx.cert"

# 验证 sigstore 签名
cosign verify-blob \
    --certificate "$VERSION.spdx.cert" \
    --signature "$VERSION.spdx.sig" \
    --certificate-identity krel-staging@k8s-releng-prod.iam.gserviceaccount.com \
    --certificate-oidc-issuer https://accounts.google.com \
    "$VERSION.spdx"