管理集群
- 1: 为集群超配节点容量
- 2: 用 kubeadm 进行管理
- 2.1: 添加 Linux 工作节点
- 2.2: 添加 Windows 工作节点
- 2.3: 升级 kubeadm 集群
- 2.4: 升级 Linux 节点
- 2.5: 升级 Windows 节点
- 2.6: 配置 cgroup 驱动
- 2.7: 使用 kubeadm 进行证书管理
- 2.8: 重新配置 kubeadm 集群
- 2.9: 更改 Kubernetes 软件包仓库
- 3: 从 dockershim 迁移
- 3.1: 将节点上的容器运行时从 Docker Engine 改为 containerd
- 3.2: 将 Docker Engine 节点从 dockershim 迁移到 cri-dockerd
- 3.3: 查明节点上所使用的容器运行时
- 3.4: 排查 CNI 插件相关的错误
- 3.5: 检查移除 Dockershim 是否对你有影响
- 3.6: 从 dockershim 迁移遥测和安全代理
- 4: 手动生成证书
- 5: 管理内存、CPU 和 API 资源
- 5.1: 为命名空间配置默认的内存请求和限制
- 5.2: 为命名空间配置默认的 CPU 请求和限制
- 5.3: 配置命名空间的最小和最大内存约束
- 5.4: 为命名空间配置 CPU 最小和最大约束
- 5.5: 为命名空间配置内存和 CPU 配额
- 5.6: 配置命名空间下 Pod 配额
- 6: 安装网络策略驱动
- 6.1: 使用 Antrea 提供 NetworkPolicy
- 6.2: 使用 Calico 提供 NetworkPolicy
- 6.3: 使用 Cilium 提供 NetworkPolicy
- 6.4: 使用 kube-router 提供 NetworkPolicy
- 6.5: 使用 Romana 提供 NetworkPolicy
- 6.6: 使用 Weave Net 提供 NetworkPolicy
- 7: 使用 Kubernetes API 访问集群
- 8: 为节点发布扩展资源
- 9: 自动扩缩集群 DNS 服务
- 10: 从轮询切换为基于 CRI 事件的更新来获取容器状态
- 11: 改变默认 StorageClass
- 12: 将 PersistentVolume 的访问模式更改为 ReadWriteOncePod
- 13: 更改 PersistentVolume 的回收策略
- 14: Kubernetes 云管理控制器
- 15: 配置 kubelet 镜像凭据提供程序
- 16: 配置 API 对象配额
- 17: 控制节点上的 CPU 管理策略
- 18: 控制节点上的拓扑管理策略
- 19: 自定义 DNS 服务
- 20: 调试 DNS 问题
- 21: 声明网络策略
- 22: 开发云控制器管理器
- 23: 启用/禁用 Kubernetes API
- 24: 静态加密机密数据
- 25: 解密已静态加密的机密数据
- 26: 关键插件 Pod 的调度保证
- 27: IP Masquerade Agent 用户指南
- 28: 限制存储使用量
- 29: 迁移多副本的控制面以使用云控制器管理器
- 30: 名字空间演练
- 31: 操作 Kubernetes 中的 etcd 集群
- 32: 为系统守护进程预留计算资源
- 33: 以非 root 用户身份运行 Kubernetes 节点组件
- 34: 安全地清空一个节点
- 35: 保护集群
- 36: 通过配置文件设置 kubelet 参数
- 37: 通过名字空间共享集群
- 38: 升级集群
- 39: 在集群中使用级联删除
- 40: 使用 KMS 驱动进行数据加密
- 41: 使用 CoreDNS 进行服务发现
- 42: 在 Kubernetes 集群中使用 NodeLocal DNSCache
- 43: 在 Kubernetes 集群中使用 sysctl
- 44: 使用 NUMA 感知的内存管理器
- 45: 验证已签名容器镜像
1 - 为集群超配节点容量
本页指导你在 Kubernetes 集群中配置节点超配。 节点超配是一种主动预留部分集群计算资源的策略。这种预留有助于减少在扩缩容事件期间调度新 Pod 所需的时间, 从而增强集群对突发流量或突发工作负载需求的响应能力。
通过保持一些未使用的容量,确保在新 Pod 被创建时资源可以立即可用,防止 Pod 在集群扩缩容时进入 Pending 状态。
准备开始
- 你需要有一个 Kubernetes 集群,并且 kubectl 命令行工具必须被配置为与你的集群通信。
- 你应该已经基本了解了 Deployment、Pod 优先级和 PriorityClass。
- 你的集群必须设置一个基于需求管理节点的自动扩缩程序。
创建 PriorityClass
首先为占位 Pod 定义一个 PriorityClass。 先创建一个优先级值为负数的 PriorityClass,稍后将其分配给占位 Pod。 接下来,你将部署使用此 PriorityClass 的 Deployment。
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: placeholder # 这些 Pod 表示占位容量
value: -1000
globalDefault: false
description: "Negative priority for placeholder pods to enable overprovisioning."
然后创建 PriorityClass:
kubectl apply -f https://k8s.io/examples/priorityclass/low-priority-class.yaml
接下来,你将定义一个 Deployment,使用优先级值为负数的 PriorityClass 并运行最小的容器。 当你将此 Deployment 添加到集群中时,Kubernetes 会运行这些占位 Pod 以预留容量。 每当出现容量短缺时,控制面将选择这些占位 Pod 中的一个作为第一个候选者进行抢占。
运行请求节点容量的 Pod
查看样例清单:
apiVersion: apps/v1
kind: Deployment
metadata:
name: capacity-reservation
# 你应决定要将此 Deployment 部署到哪个命名空间
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: capacity-placeholder
template:
metadata:
labels:
app.kubernetes.io/name: capacity-placeholder
annotations:
kubernetes.io/description: "Capacity reservation"
spec:
priorityClassName: placeholder
affinity: # 有可能的话,将这些 Pod 开销放到不同的节点
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: placeholder
topologyKey: "kubernetes.io/hostname"
containers:
- name: pause
image: registry.k8s.io/pause:3.6
resources:
requests:
cpu: "50m"
memory: "512Mi"
limits:
memory: "512Mi"
为占位 Pod 挑选一个命名空间
你应选择或创建占位 Pod 要进入的命名空间。
创建占位 Deployment
基于该清单创建 Deployment:
# 你要更改命名空间名称 "example"
kubectl --namespace example apply -f https://k8s.io/examples/deployments/deployment-with-capacity-reservation.yaml
调整占位资源请求
为占位 Pod 配置资源请求和限制,以定义你希望保持的超配资源量。 这种预留确保为新 Pod 保留可以使用的、特定量的 CPU 和内存。
要编辑 Deployment,可以修改 Deployment 清单文件中的 resources
一节,
设置合适的 requests
和 limits
。
你可以将该文件下载到本地,然后用自己喜欢的文本编辑器进行编辑。
你也可以使用 kubectl 来编辑 Deployment:
kubectl edit deployment capacity-reservation
例如,要为 5 个占位 Pod 预留 500m CPU 和 1Gi 内存,请为单个占位 Pod 定义以下资源请求和限制:
resources:
requests:
cpu: "100m"
memory: "200Mi"
limits:
cpu: "100m"
设置所需的副本数量
计算总预留资源
例如,有 5 个副本,每个预留 0.1 CPU 和 200MiB 内存:
CPU 预留总量:5 × 0.1 = 0.5(在 Pod 规约中,你将写入数量 500m
)
内存预留总量:5 × 200MiB = 1GiB(在 Pod 规约中,你将写入 1 Gi
)
要扩缩容 Deployment,请基于集群的大小和预期的工作负载调整副本数:
kubectl scale deployment capacity-reservation --replicas=5
验证扩缩容效果:
kubectl get deployment capacity-reservation
输出应反映出更新后的副本数:
NAME READY UP-TO-DATE AVAILABLE AGE
capacity-reservation 5/5 5 5 2m
说明:
一些自动扩缩组件,特别是 Karpenter, 在考虑节点扩缩容时将偏好的亲和性规则视为硬性规则。如果你使用 Karpenter 或其他使用同样启发式的节点扩缩容组件,你在此处设置的副本数也就是你的集群的最少节点数。
接下来
- 进一步了解 PriorityClass 及其如何影响 Pod 调度。
- 探索节点自动扩缩容, 以基于工作负载需求动态调整集群的大小。
- 了解 Pod 抢占, 这是 Kubernetes 处理资源竞争的关键机制。这篇文档还涵盖了驱逐, 虽然与占位 Pod 方法相关性较小,但也是 Kubernetes 在资源竞争时做出反应的一种机制。
2.1 - 添加 Linux 工作节点
本页介绍如何将 Linux 工作节点添加到 kubeadm 集群。
准备开始
- 每个要加入的工作节点都已安装 安装 kubeadm 中所需的组件,例如 kubeadm、kubelet 和 容器运行时。
- 一个正在运行的、由
kubeadm init
命令所创建的 kubeadm 集群,且该集群的创建遵循 使用 kubeadm 创建集群 文档中所给的步骤。 - 你需要对节点拥有超级用户权限。
添加 Linux 工作节点
要将新的 Linux 工作节点添加到集群中,请对每台机器执行以下步骤:
- 通过 SSH 或其他方式连接到该机器。
- 运行
kubeadm init
所输出的命令。例如:
sudo kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>
kubeadm join 的额外信息
说明:
要为 <control-plane-host>:<control-plane-port>
指定一个 IPv6 元组,
IPv6 地址必须用方括号括起来,例如:[2001:db8::101]:2073
。
如果你没有令牌,可以在控制平面节点上运行以下命令来获取:
# 在控制平面节点上运行此命令
sudo kubeadm token list
命令输出同以下内容类似:
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
8ewj1p.9r9hcjoqgajrj4gi 23h 2018-06-12T02:51:28Z authentication, The default bootstrap system:
signing token generated by bootstrappers:
'kubeadm init'. kubeadm:
default-node-token
默认情况下,节点加入令牌会在 24 小时后过期。当前令牌过期后,如果想把节点加入集群, 可以在控制平面节点上运行以下命令来创建新令牌:
# 在控制平面节点上运行此命令
sudo kubeadm token create
命令输出同以下内容类似:
5didvk.d09sbcov8ph2amjw
如果你没有 --discovery-token-ca-cert-hash
的具体值,可以在控制平面节点上运行以下命令来获取:
# 在控制平面节点上运行此命令
sudo cat /etc/kubernetes/pki/ca.crt | openssl x509 -pubkey | openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'
命令输出同以下内容类似:
8cb2de97839780a412b93877f8507ad6c94f73add17d5d7058e91741c9d5ec78
kubeadm join
命令的输出应该同下面内容类似:
[preflight] Running pre-flight checks
... (log output of join workflow) ...
Node join complete:
* Certificate signing request sent to control-plane and response
received.
* Kubelet informed of new secure connection details.
Run 'kubectl get nodes' on control-plane to see this machine join.
几秒钟后,你应该在 kubectl get nodes
的输出中看到该节点。
(例如,可以在控制平面节点上运行 kubectl
)。
说明:
集群节点通常是按顺序初始化的,因此 CoreDNS Pods 可能会全部运行在第一个控制平面节点上。
为了保证高可用,请在至少一个新节点加入后,使用
kubectl -n kube-system rollout restart deployment coredns
命令重新平衡 CoreDNS Pods。
接下来
- 参见如何添加 Windows 工作节点。
2.2 - 添加 Windows 工作节点
Kubernetes v1.18 [beta]
本页介绍如何将 Linux 工作节点添加到 kubeadm 集群。
准备开始
- 一个正在运行的 Windows Server 2022 (或更高版本)实例,且具备管理权限。
- 一个正在运行的、由
kubeadm init
命令创建的集群,且集群的创建遵循 使用 kubeadm 创建集群 文档中所给的步骤。
添加 Windows 工作节点
说明:
为了方便将 Windows 工作节点添加到集群,下面会用到代码仓库 https://sigs.k8s.io/sig-windows-tools 里的 PowerShell 脚本。
对每台机器执行以下操作:
- 在机器上打开一个 PowerShell 会话。
- 确保你是管理员或具有特权的用户。
然后继续执行下面的步骤。
安装 Containerd
要安装 Containerd,首先运行以下命令:
curl.exe -LO https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hostprocess/Install-Containerd.ps1
然后运行以下命令,但要首先将 CONTAINERD_VERSION
替换为
Containerd 仓库 中的最新发布版本。
版本号不能带有前缀 v
。例如,使用 1.7.22
而不是 v1.7.22
:
.\Install-Containerd.ps1 -ContainerDVersion CONTAINERD_VERSION
- 根据需要调整
Install-Containerd.ps1
的所有其他参数,例如netAdapterName
。 - 如果你的机器不支持 Hyper-V,且无法托管 Hyper-V 的隔离容器,
请设置
skipHypervisorSupportCheck
。 - 如果你要更改
Install-Containerd.ps1
中的可选参数CNIBinPath
和/或CNIConfigPath
,则需要配置已安装的 Windows CNI 插件,使之与这里的值匹配。
安装 kubeadm 和 kubelet
运行以下命令安装 kubeadm 和 kubelet:
curl.exe -LO https://raw.githubusercontent.com/kubernetes-sigs/sig-windows-tools/master/hostprocess/PrepareNode.ps1
.\PrepareNode.ps1 -KubernetesVersion v1.32
- 根据需要调整
PrepareNode.ps1
中的参数KubernetesVersion
。
运行 kubeadm join
运行 kubeadm init
所输出的命令。例如:
kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>
kubeadm join 的附加信息
说明:
要为 <control-plane-host>:<control-plane-port>
指定一个 IPv6 元组,
IPv6 地址必须用方括号括起来,例如:[2001:db8::101]:2073
。
如果你没有令牌,可以在控制平面节点上运行以下命令来获取:
# 在控制平面节点上运行此命令
sudo kubeadm token list
命令输出同以下内容类似:
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
8ewj1p.9r9hcjoqgajrj4gi 23h 2018-06-12T02:51:28Z authentication, The default bootstrap system:
signing token generated by bootstrappers:
'kubeadm init'. kubeadm:
default-node-token
默认情况下,节点加入令牌会在 24 小时后过期。当前令牌过期后,如果想把节点加入集群, 可以在控制平面节点上运行以下命令来创建新令牌:
# 在控制平面节点上运行此命令
sudo kubeadm token create
命令输出同以下内容类似:
5didvk.d09sbcov8ph2amjw
如果你没有 --discovery-token-ca-cert-hash
的具体值,可以在控制平面节点上运行以下命令来获取:
sudo cat /etc/kubernetes/pki/ca.crt | openssl x509 -pubkey | openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'
命令输出同以下内容类似:
8cb2de97839780a412b93877f8507ad6c94f73add17d5d7058e91741c9d5ec78
kubeadm join
命令的输出应该同以下内容类似:
[preflight] Running pre-flight checks
... (log output of join workflow) ...
Node join complete:
* Certificate signing request sent to control-plane and response
received.
* Kubelet informed of new secure connection details.
Run 'kubectl get nodes' on control-plane to see this machine join.
几秒钟后,你应该在 kubectl get nodes
的输出中看到该节点。
(例如,可以在控制平面节点上运行 kubectl
)。
网络配置
在混合了 Linux 和 Windows 节点的集群中,CNI 设置所需的步骤不仅仅是对清单文件运行
kubectl apply
。此外,运行在控制平面节点上的 CNI 插件必须能够支持在 Windows 工作节点上
运行的 CNI 插件。
目前只有少数 CNI 插件支持 Windows。以下是它们各自的设置说明:
在 Windows 上安装 kubectl (可选)
接下来
参见如何 添加 Linux 工作节点。
2.3 - 升级 kubeadm 集群
本页介绍如何将 kubeadm
创建的 Kubernetes 集群从 1.31.x 版本
升级到 1.32.x 版本以及从 1.32.x
升级到 1.32.y(其中 y > x
)。略过次版本号的升级是
不被支持的。更多详情请访问版本偏差策略。
要查看 kubeadm 创建的有关旧版本集群升级的信息,请参考以下页面:
- 将 kubeadm 集群从 1.30 升级到 1.31
- 将 kubeadm 集群从 1.29 升级到 1.30
- 将 kubeadm 集群从 1.28 升级到 1.29
- 将 kubeadm 集群从 1.27 升级到 1.28
Kubernetes 项目建议立即升级到最新的补丁版本,并确保你运行的是受支持的 Kubernetes 次要版本。 遵循此建议可帮助你保持安全。
升级工作的基本流程如下:
- 升级主控制平面节点
- 升级其他控制平面节点
- 升级工作节点
准备开始
- 务必仔细认真阅读发行说明。
- 集群应使用静态的控制平面和 etcd Pod 或者外部 etcd。
- 务必备份所有重要组件,例如存储在数据库中应用层面的状态。
kubeadm upgrade
不会影响你的工作负载,只会涉及 Kubernetes 内部的组件,但备份终究是好的。 - 必须禁用交换分区。
附加信息
- 下述说明了在升级过程中何时腾空每个节点。如果你正在对任何 kubelet 进行小版本升级, 你需要先腾空待升级的节点(或多个节点)。对于控制面节点,其上可能运行着 CoreDNS Pod 或者其它非常重要的负载。更多信息见腾空节点。
- Kubernetes 项目推荐你使用版本匹配的 kubelet 和 kubeadm。 但你也可以使用比 kubeadm 版本更低的 kubelet 版本,前提是该版本仍处于支持的版本范围内。 欲了解更多信息,请访问 kubeadm 与 kubelet 的版本差异。
- 升级后,因为容器规约的哈希值已更改,所有容器都会被重新启动。
- 要验证 kubelet 服务在升级后是否成功重启,可以执行
systemctl status kubelet
或journalctl -xeu kubelet
查看服务日志。 kubeadm upgrade
支持--config
和UpgradeConfiguration
API 类型 可用于配置升级过程。kubeadm upgrade
不支持重新配置现有集群。 请按照重新配置 kubeadm 集群中的步骤来进行。
升级 etcd 时的注意事项
由于 kube-apiserver
静态 Pod 始终在运行(即使你已经执行了腾空节点的操作),
因此当你执行包括 etcd 升级在内的 kubeadm 升级时,对服务器正在进行的请求将停滞,
因为要重新启动新的 etcd 静态 Pod。作为一种解决方法,可以在运行 kubeadm upgrade apply
命令之前主动停止 kube-apiserver
进程几秒钟。这样可以允许正在进行的请求完成处理并关闭现有连接,
并最大限度地减少 etcd 停机的后果。此操作可以在控制平面节点上按如下方式完成:
killall -s SIGTERM kube-apiserver # 触发 kube-apiserver 体面关闭
sleep 20 # 等待一下,以完成进行中的请求
kubeadm upgrade ... # 执行 kubeadm 升级命令
更改软件包仓库
如果你正在使用社区版的软件包仓库(pkgs.k8s.io
),
你需要启用所需的 Kubernetes 小版本的软件包仓库。
这一点在更改 Kubernetes 软件包仓库文档中有详细说明。
apt.kubernetes.io
和yum.kubernetes.io
)。
强烈建议使用托管在 pkgs.k8s.io
上的新软件包仓库来安装 2023 年 9 月 13 日之后发布的 Kubernetes 版本。
旧版软件包仓库已被弃用,其内容可能在未来的任何时间被删除,恕不另行通知。新的软件包仓库提供了从 Kubernetes v1.24.0 版本开始的下载。确定要升级到哪个版本
使用操作系统的包管理器找到最新的补丁版本 Kubernetes 1.32:
# 在列表中查找最新的 1.32 版本
# 它看起来应该是 1.32.x-*,其中 x 是最新的补丁版本
sudo apt update
sudo apt-cache madison kubeadm
# 在列表中查找最新的 1.32 版本
# 它看起来应该是 1.32.x-*,其中 x 是最新的补丁版本
sudo yum list --showduplicates kubeadm --disableexcludes=kubernetes
升级控制平面节点
控制面节点上的升级过程应该每次处理一个节点。
首先选择一个要先行升级的控制面节点。该节点上必须拥有
/etc/kubernetes/admin.conf
文件。
执行 “kubeadm upgrade”
对于第一个控制面节点
升级 kubeadm:
# 用最新的补丁版本号替换 1.32.x-* 中的 x sudo apt-mark unhold kubeadm && \ sudo apt-get update && sudo apt-get install -y kubeadm='1.32.x-*' && \ sudo apt-mark hold kubeadm
# 用最新的补丁版本号替换 1.32.x-* 中的 x sudo yum install -y kubeadm-'1.32.x-*' --disableexcludes=kubernetes
验证下载操作正常,并且 kubeadm 版本正确:
kubeadm version
验证升级计划:
sudo kubeadm upgrade plan
此命令检查你的集群是否可被升级,并取回你要升级的目标版本。 命令也会显示一个包含组件配置版本状态的表格。
说明:
kubeadm upgrade
也会自动对 kubeadm 在节点上所管理的证书执行续约操作。 如果需要略过证书续约操作,可以使用标志--certificate-renewal=false
。 更多的信息,可参阅证书管理指南。
选择要升级到的目标版本,运行合适的命令。例如:
# 将 x 替换为你为此次升级所选择的补丁版本号 sudo kubeadm upgrade apply v1.32.x
一旦该命令结束,你应该会看到:
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.32.x". Enjoy! [upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.
说明:
对于 v1.28 之前的版本,kubeadm 默认采用这样一种模式:在
kubeadm upgrade apply
期间立即升级插件(包括 CoreDNS 和 kube-proxy),而不管是否还有其他尚未升级的控制平面实例。 这可能会导致兼容性问题。从 v1.28 开始,kubeadm 默认采用这样一种模式: 在开始升级插件之前,先检查是否已经升级所有的控制平面实例。 你必须按顺序执行控制平面实例的升级,或者至少确保在所有其他控制平面实例已完成升级之前不启动最后一个控制平面实例的升级, 并且在最后一个控制平面实例完成升级之后才执行插件的升级。
手动升级你的 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
升级 kubelet 和 kubectl:
# 用最新的补丁版本替换 1.32.x-* 中的 x sudo apt-mark unhold kubelet kubectl && \ sudo apt-get update && sudo apt-get install -y kubelet='1.32.x-*' kubectl='1.32.x-*' && \ sudo apt-mark hold kubelet kubectl
# 用最新的补丁版本号替换 1.32.x-* 中的 x sudo yum install -y kubelet-'1.32.x-*' kubectl-'1.32.x-*' --disableexcludes=kubernetes
重启 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 升级后,备份目录 /etc/kubernetes/tmp
将保留,这些备份文件需要手动清理。
工作原理
kubeadm upgrade apply
做了以下工作:
- 检查你的集群是否处于可升级状态:
- API 服务器是可访问的
- 所有节点处于
Ready
状态 - 控制面是健康的
- 强制执行版本偏差策略。
- 确保控制面的镜像是可用的或可拉取到服务器上。
- 如果组件配置要求版本升级,则生成替代配置与/或使用用户提供的覆盖版本配置。
- 升级控制面组件或回滚(如果其中任何一个组件无法启动)。
- 应用新的
CoreDNS
和kube-proxy
清单,并强制创建所有必需的 RBAC 规则。 - 如果旧文件在 180 天后过期,将创建 API 服务器的新证书和密钥文件并备份旧文件。
kubeadm upgrade node
在其他控制平节点上执行以下操作:
- 从集群中获取 kubeadm
ClusterConfiguration
。 - (可选操作)备份 kube-apiserver 证书。
- 升级控制平面组件的静态 Pod 清单。
- 为本节点升级 kubelet 配置
kubeadm upgrade node
在工作节点上完成以下工作:
- 从集群取回 kubeadm
ClusterConfiguration
。 - 为本节点升级 kubelet 配置。
2.4 - 升级 Linux 节点
本页讲述了如何升级用 kubeadm 创建的 Linux 工作节点。
准备开始
你必须有 Shell 能访问所有节点,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。
要获知版本信息,请输入kubectl version
.- 你自己要熟悉升级剩余 kubeadm 集群的过程。 你需要先升级控制面节点,再升级 Linux 工作节点。
更改软件包仓库
如果你正在使用社区自治的软件包仓库(pkgs.k8s.io
),
你需要启用所需的 Kubernetes 小版本的软件包仓库。
这一点在更改 Kubernetes 软件包仓库文档中有详细说明。
apt.kubernetes.io
和yum.kubernetes.io
)。
强烈建议使用托管在 pkgs.k8s.io
上的新软件包仓库来安装 2023 年 9 月 13 日之后发布的 Kubernetes 版本。
旧版软件包仓库已被弃用,其内容可能在未来的任何时间被删除,恕不另行通知。新的软件包仓库提供了从 Kubernetes v1.24.0 版本开始的下载。升级工作节点
升级 kubeadm
升级 kubeadm:
# 将 1.32.x-* 中的 x 替换为最新的补丁版本
sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.32.x-*' && \
sudo apt-mark hold kubeadm
# 将 1.32.x-* 中的 x 替换为最新的补丁版本
sudo yum install -y kubeadm-'1.32.x-*' --disableexcludes=kubernetes
执行 "kubeadm upgrade"
对于工作节点,下面的命令会升级本地的 kubelet 配置:
sudo kubeadm upgrade node
腾空节点
将节点标记为不可调度并驱逐所有负载,准备节点的维护:
# 在控制平面节点上执行此命令
# 将 <node-to-drain> 替换为你正腾空的节点的名称
kubectl drain <node-to-drain> --ignore-daemonsets
升级 kubelet 和 kubectl
升级 kubelet 和 kubectl:
# 将 1.32.x-* 中的 x 替换为最新的补丁版本 sudo apt-mark unhold kubelet kubectl && \ sudo apt-get update && sudo apt-get install -y kubelet='1.32.x-*' kubectl='1.32.x-*' && \ sudo apt-mark hold kubelet kubectl
# 将 1.32.x-* 中的 x 替换为最新的补丁版本 sudo yum install -y kubelet-'1.32.x-*' kubectl-'1.32.x-*' --disableexcludes=kubernetes
重启 kubelet:
sudo systemctl daemon-reload sudo systemctl restart kubelet
取消对节点的保护
通过将节点标记为可调度,让节点重新上线:
# 在控制平面节点上执行此命令
# 将 <node-to-uncordon> 替换为你的节点名称
kubectl uncordon <node-to-uncordon>
接下来
- 查阅如何升级 Windows 节点。
2.5 - 升级 Windows 节点
Kubernetes v1.18 [beta]
本页解释如何升级用 kubeadm 创建的 Windows 节点。
准备开始
你必须有 Shell 能访问所有节点,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。
你的 Kubernetes 服务器版本必须不低于版本 1.17. 要获知版本信息,请输入kubectl version
.- 熟悉更新 kubeadm 集群中的其余组件。 在升级你的 Windows 节点之前你会想要升级控制面节点。
升级工作节点
升级 kubeadm
在 Windows 节点上升级 kubeadm:
# 将 1.32.0 替换为你希望的版本 curl.exe -Lo <kubeadm.exe 路径> "https://dl.k8s.io/v1.32.0/bin/windows/amd64/kubeadm.exe"
腾空节点
在一个能访问到 Kubernetes API 的机器上,将 Windows 节点标记为不可调度并 驱逐其上的所有负载,以便准备节点维护操作:
# 将 <要腾空的节点> 替换为你要腾空的节点的名称 kubectl drain <要腾空的节点> --ignore-daemonsets
你应该会看到类似下面的输出:
node/ip-172-31-85-18 cordoned node/ip-172-31-85-18 drained
升级 kubelet 配置
在 Windows 节点上,执行下面的命令来同步新的 kubelet 配置:
kubeadm upgrade node
升级 kubelet 和 kube-proxy
在 Windows 节点上升级并重启 kubelet:
stop-service kubelet curl.exe -Lo <kubelet.exe 路径> "https://dl.k8s.io/v1.32.0/bin/windows/amd64/kubelet.exe" restart-service kubelet
在 Windows 节点上升级并重启 kube-proxy:
stop-service kube-proxy curl.exe -Lo <kube-proxy.exe 路径> "https://dl.k8s.io/v1.32.0/bin/windows/amd64/kube-proxy.exe" restart-service kube-proxy
说明:
如果你是在 Pod 内的 HostProcess 容器中运行 kube-proxy,而不是作为 Windows 服务, 你可以通过应用更新版本的 kube-proxy 清单文件来升级 kube-proxy。
对节点执行 uncordon 操作
从一台能够访问到 Kubernetes API 的机器上,通过将节点标记为可调度,使之 重新上线:
# 将 <要腾空的节点> 替换为你的节点名称 kubectl uncordon <要腾空的节点>
接下来
- 查看如何升级 Linux 节点。
2.6 - 配置 cgroup 驱动
本页阐述如何配置 kubelet 的 cgroup 驱动以匹配 kubeadm 集群中的容器运行时的 cgroup 驱动。
准备开始
你应该熟悉 Kubernetes 的容器运行时需求。
配置容器运行时 cgroup 驱动
容器运行时页面提到,
由于 kubeadm 把 kubelet 视为一个
系统服务来管理,
所以对基于 kubeadm 的安装, 我们推荐使用 systemd
驱动,
不推荐 kubelet 默认的 cgroupfs
驱动。
此页还详述了如何安装若干不同的容器运行时,并将 systemd
设为其默认驱动。
配置 kubelet 的 cgroup 驱动
kubeadm 支持在执行 kubeadm init
时,传递一个 KubeletConfiguration
结构体。
KubeletConfiguration
包含 cgroupDriver
字段,可用于控制 kubelet 的 cgroup 驱动。
说明:
在版本 1.22 及更高版本中,如果用户没有在 KubeletConfiguration
中设置 cgroupDriver
字段,
kubeadm
会将它设置为默认值 systemd
。
在 Kubernetes v1.28 中,你可以以 Alpha 功能启用 cgroup 驱动的自动检测。 有关更多详情,请查看 systemd cgroup 驱动。
这是一个最小化的示例,其中显式的配置了此字段:
# kubeadm-config.yaml
kind: ClusterConfiguration
apiVersion: kubeadm.k8s.io/v1beta4
kubernetesVersion: v1.21.0
---
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
cgroupDriver: systemd
这样一个配置文件就可以传递给 kubeadm 命令了:
kubeadm init --config kubeadm-config.yaml
说明:
Kubeadm 对集群所有的节点,使用相同的 KubeletConfiguration
。
KubeletConfiguration
存放于 kube-system
命名空间下的某个
ConfigMap 对象中。
执行 init
、join
和 upgrade
等子命令会促使 kubeadm
将 KubeletConfiguration
写入到文件 /var/lib/kubelet/config.yaml
中,
继而把它传递给本地节点的 kubelet。
使用 cgroupfs
驱动
如仍需使用 cgroupfs
且要防止 kubeadm upgrade
修改现有系统中
KubeletConfiguration
的 cgroup 驱动,你必须显式声明它的值。
此方法应对的场景为:在将来某个版本的 kubeadm 中,你不想使用默认的 systemd
驱动。
参阅以下章节“修改 kubelet 的 ConfigMap ”,了解显式设置该值的方法。
如果你希望配置容器运行时来使用 cgroupfs
驱动,
则必须参考所选容器运行时的文档。
迁移到 systemd
驱动
要将现有 kubeadm 集群的 cgroup 驱动从 cgroupfs
就地升级为 systemd
,
需要执行一个与 kubelet 升级类似的过程。
该过程必须包含下面两个步骤:
说明:
还有一种方法,可以用已配置了systemd
的新节点替换掉集群中的老节点。
按这种方法,在加入新节点、确保工作负载可以安全迁移到新节点、及至删除旧节点这一系列操作之前,
只需执行以下第一个步骤。修改 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>
,以 取消节点隔离
在节点上依次执行上述步骤,确保工作负载有充足的时间被调度到其他节点。
流程完成后,确认所有节点和工作负载均健康如常。
2.7 - 使用 kubeadm 进行证书管理
Kubernetes v1.15 [stable]
由 kubeadm 生成的客户端证书在 1 年后到期。 本页说明如何使用 kubeadm 管理证书续订,同时也涵盖其他与 kubeadm 证书管理相关的说明。
Kubernetes 项目建议及时升级到最新的补丁版本,并确保你正在运行受支持的 Kubernetes 次要版本。 遵循这一建议有助于你确保安全。
准备开始
你应该熟悉 Kubernetes 中的 PKI 证书和要求。
你应该熟悉如何将一个配置文件传递给 kubeadm 命令。
本指南将介绍如何使用 openssl
命令(用于手动证书签名),但你可以使用你喜欢的工具。
这里的一些步骤使用 sudo
来获取管理员访问权限。你可以使用任何等效的工具。
使用自定义的证书
默认情况下,kubeadm 会生成运行一个集群所需的全部证书。 你可以通过提供你自己的证书来改变这个行为策略。
如果要这样做,你必须将证书文件放置在通过 --cert-dir
命令行参数或者 kubeadm 配置中的
certificatesDir
配置项指明的目录中。默认的值是 /etc/kubernetes/pki
。
如果在运行 kubeadm init
之前存在给定的证书和私钥对,kubeadm 将不会重写它们。
例如,这意味着你可以将现有的 CA 复制到 /etc/kubernetes/pki/ca.crt
和
/etc/kubernetes/pki/ca.key
中,而 kubeadm 将使用此 CA 对其余证书进行签名。
选择加密算法
kubeadm 允许你选择用于创建公钥和私钥的加密算法。这可以通过使用
kubeadm 配置中的 encryptionAlgorithm
字段来实现。
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
encryptionAlgorithm: <ALGORITHM>
<ALGORITHM>
可以是 RSA-2048
(默认)、RSA-3072
、RSA-4096
或 ECDSA-P256
之一。
选择证书有效期
kubeadm 允许你选择 CA 和 leaf 证书的有效期。
这可以通过使用 kubeadm 配置的 certificateValidityPeriod
和 caCertificateValidityPeriod
字段来完成:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
certificateValidityPeriod: 8760h # 默认:365 天 × 24 小时 = 1 年
caCertificateValidityPeriod: 87600h # 默认:365 天 × 24 小时 * 10 = 10 年
字段的值遵循 Go 语言的 time.Duration
格式,
支持的最长单位为 h
(小时)。
外部 CA 模式
只提供了 ca.crt
文件但是不提供 ca.key
文件也是可以的
(这只对 CA 根证书可用,其它证书不可用)。
如果所有的其它证书和 kubeconfig 文件已就绪,kubeadm 检测到满足以上条件就会激活
"外部 CA" 模式。kubeadm 将会在没有 CA 密钥文件的情况下继续执行。
否则,kubeadm 将独立运行 controller-manager,附加一个
--controllers=csrsigner
的参数,并且指明 CA 证书和密钥。
使用外部 CA 模式时,有多种方法可以准备组件证书。
手动准备组件证书
PKI 证书和要求包含有关如何手动准备 kubeadm 组件证书所需的所有信息。
本指南将介绍如何使用 openssl
命令(用于手动证书签名),但你可以使用你喜欢的工具。
通过签署 kubeadm 生成的 CSR 来准备证书
kubeadm 可以生成 CSR 文件,你可以使用 openssl
和外部 CA 等工具手动签署这些文件。
这些 CSR 文件将包含 kubeadm 部署的组件所需的所有证书规范。
使用 kubeadm 阶段自动准备组件证书
或者,可以使用 kubeadm 阶段命令来自动化此过程。
- 登录到将作为具有外部 CA 的 kubeadm 控制平面节点的主机。
- 将外部 CA 文件
ca.crt
和ca.key
复制到节点上的/etc/kubernetes/pki
目录中。 - 准备一个名为
config.yaml
的临时 kubeadm 配置文件, 该文件可以用在kubeadm init
命令中。确保此文件包含可包含在证书中的集群范围或特定于主机的所有重要信息, 例如ClusterConfiguration.controlPlaneEndpoint
、ClusterConfiguration.certSANs
和InitConfiguration.APIEndpoint
。 - 在同一主机上执行命令
kubeadm init stage kubeconfig all --config config.yaml
和kubeadm init stage certs all --config config.yaml
。 这些操作将在/etc/kubernetes/
及其pki
子目录下生成所有必需的 kubeconfig 文件和证书。
- 检查所生成的文件。删除
/etc/kubernetes/pki/ca.key
,删除或移动/etc/kubernetes/super-admin.conf
文件到安全的位置。 - 在将执行
kubeadm join
的节点上还需要删除/etc/kubernetes/kubelet.conf
, 仅在将执行kubeadm init
的第一个节点上需要此文件。 - 请注意,一些文件如
pki/sa.*
、pki/front-proxy-ca.*
和pki/etc/ca.*
在控制平面各节点上是相同的,你可以一次性生成它们并手动将其分发到将执行kubeadm join
的节点,或者你可以使用kubeadm init
的--upload-certs
和kubeadm join
的--certificate-key
特性来执行自动分发。
在所有节点上准备好证书后,调用 kubeadm init
和 kubeadm join
命令将这些节点加入集群。
kubeadm 将使用 /etc/kubernetes/
及其 pki
子目录下现有的 kubeconfig 和证书文件。
证书过期和管理
说明:
kubeadm
不能管理由外部 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.conf
、controller-manager.conf
和 scheduler.conf
)
使用的 kubeconfig 文件中嵌入的客户端证书的到期时间/剩余时间。
另外,kubeadm 会通知用户证书是否由外部管理; 在这种情况下,用户应该小心的手动/使用其他工具来管理证书更新。
上面的列表中没有包含 kubelet.conf
配置文件,因为 kubeadm 将 kubelet
配置为自动更新证书。
轮换的证书位于目录 /var/lib/kubelet/pki
。
要修复过期的 kubelet 客户端证书,请参阅
kubelet 客户端证书轮换失败。
说明:
在通过 kubeadm 1.17 之前的版本以 kubeadm init
创建的节点上,
有一个缺陷,
该缺陷使得你必须手动修改 kubelet.conf
文件的内容。
kubeadm init
操作结束之后,你必须更新 kubelet.conf
文件将 client-certificate-data
和 client-key-data
改为如下所示的内容以便使用轮换后的 kubelet 客户端证书:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
自动更新证书
kubeadm 会在控制面升级的时候更新所有证书。
这个功能旨在解决最简单的用例;如果你对此类证书的更新没有特殊要求, 并且定期执行 Kubernetes 版本升级(每次升级之间的间隔时间少于 1 年), 则 kubeadm 将确保你的集群保持最新状态并保持合理的安全性。
如果你对证书更新有更复杂的需求,则可通过将 --certificate-renewal=false
传递给
kubeadm upgrade apply
或者 kubeadm upgrade node
,从而选择不采用默认行为。
手动更新证书
你能随时通过 kubeadm certs renew
命令手动更新你的证书,只需带上合适的命令行选项。
如果你正在运行的集群具有多副本的控制平面,则需要在所有控制平面节点上执行此命令。
此命令用 CA(或者 front-proxy-CA )证书和存储在 /etc/kubernetes/pki
中的密钥执行更新。
kubeadm certs renew
使用现有的证书作为属性(Common Name、Organization、SAN 等)的权威来源,
而不依赖于 kubeadm-config
ConfigMap。强烈建议使它们保持同步。
即便如此,Kubernetes 项目仍然建议使用的证书与 ConfigMap 中的关联值保持同步,以避免任何混淆的风险。
执行完此命令之后你需要重启控制面 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
复制管理员证书(可选)
使用 kubeadm 构建的集群通常会将 admin.conf
证书复制到 $HOME/.kube/config
,
参阅使用 kubeadm 创建集群。
在这样的系统上,若要在更新 admin.conf
后更新 $HOME/.kube/config
的内容,你可以运行以下命令:
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
用 Kubernetes 证书 API 更新证书
本节提供有关如何使用 Kubernetes 证书 API 执行手动证书更新的更多详细信息。
注意:
这些是针对需要将其组织的证书基础结构集成到 kubeadm 构建的集群中的用户的高级主题。 如果默认的 kubeadm 配置满足了你的需求,则应让 kubeadm 管理证书。
设置一个签名者(Signer)
Kubernetes 证书颁发机构不是开箱即用。你可以配置外部签名者,例如 cert-manager, 也可以使用内置签名者。
内置签名者是
kube-controller-manager
的一部分。
要激活内置签名者,请传递 --cluster-signing-cert-file
和 --cluster-signing-key-file
参数。
如果你正在创建一个新的集群,你可以使用 kubeadm 的配置文件:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
controllerManager:
extraArgs:
- name: "cluster-signing-cert-file"
value: "/etc/kubernetes/pki/ca.crt"
- name: "cluster-signing-key-file"
value: "/etc/kubernetes/pki/ca.key"
创建证书签名请求(CSR)
有关使用 Kubernetes API 创建 CSR 的信息, 请参见创建 CertificateSigningRequest。
通过外部 CA 更新证书
本节提供有关如何使用外部 CA 执行手动更新证书的更多详细信息。
为了更好的与外部 CA 集成,kubeadm 还可以生成证书签名请求(CSR)。 CSR 表示向 CA 请求客户的签名证书。 在 kubeadm 术语中,通常由磁盘 CA 签名的任何证书都可以作为 CSR 生成。但是,CA 不能作为 CSR 生成。
使用证书签名请求(CSR)续订
可以通过生成新的 CSR 并使用外部 CA 对其进行签名来对证书进行续约。 有关使用 kubeadm 生成的 CSR 的更多详细信息,请参阅对 kubeadm 生成的证书签名请求(CSR)进行签名部分。
证书机构(CA)轮换
kubeadm 并不直接支持对 CA 证书的轮换或者替换。
关于手动轮换或者置换 CA 的更多信息, 可参阅手动轮换 CA 证书。
启用已签名的 kubelet 服务证书
默认情况下,kubeadm 所部署的 kubelet 服务证书是自签名(Self-Signed)。 这意味着从 metrics-server 这类外部服务发起向 kubelet 的链接时无法使用 TLS 来完成保护。
要在新的 kubeadm 集群中配置 kubelet 以使用被正确签名的服务证书,
你必须向 kubeadm init
传递如下最小配置数据:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
serverTLSBootstrap: true
如果你已经创建了集群,你必须通过执行下面的操作来完成适配:
- 找到
kube-system
名字空间中名为kubelet-config-1.32
的 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 将 KubeletConfiguration
的 rotateCertificates
字段设置为
true
;这意味着证书快要过期时,会生成一组针对服务证书的新的 CSR,而这些
CSR 也要被批准才能完成证书轮换。要进一步了解这里的细节,
可参阅证书轮换文档。
如果你在寻找一种能够自动批准这些 CSR 的解决方案,建议你与你的云提供商联系, 询问他们是否有 CSR 签名组件,用来以带外(out-of-band)的方式检查节点的标识符。
也可以使用第三方定制的控制器:
除非既能够验证 CSR 中的 CommonName,也能检查请求的 IP 和域名, 这类控制器还算不得安全的机制。 只有完成彻底的检查,才有可能避免有恶意的、能够访问 kubelet 客户端证书的第三方为任何 IP 或域名请求服务证书。
为其他用户生成 kubeconfig 文件
在集群创建过程中,kubeadm init
对 super-admin.conf
中的证书进行签名时,将其配置为
Subject: O = system:masters, CN = kubernetes-super-admin
。
system:masters
是一个例外的超级用户组,可以绕过鉴权层(例如 RBAC)。
文件 admin.conf
也由 kubeadm 在控制平面节点上创建,此文件包含设为
Subject: O = kubeadm:cluster-admins, CN = kubernetes-admin
的证书。
kubeadm:cluster-admins
是一个逻辑上属于 kubeadm 的组。
如果你的集群使用 RBAC(kubeadm 的默认设置),则 kubeadm:cluster-admins
组被绑定到 cluster-admin
ClusterRole。
警告:
避免共享 super-admin.conf
或 admin.conf
文件。
实际上,即使是管理员等工作人员,也只为其创建最小访问权限,
这种最小权限的方案适用于除例外(应急)访问之外的所有场景。
你可以使用 kubeadm kubeconfig user
命令为其他用户生成 kubeconfig 文件,这个命令支持命令行参数和
kubeadm 配置结构。
以上命令会将 kubeconfig 打印到终端上,也可以使用 kubeadm kubeconfig user ... > somefile.conf
输出到一个文件中。
如下 kubeadm 可以在 --config
后加的配置文件示例:
# example.yaml
apiVersion: kubeadm.k8s.io/v1beta4
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
签署由 kubeadm 生成的证书签名请求(CSR)
kubeadm certs generate-csr
命令为 kubeadm 所了解并管理的所有证书生成 CSR。
调用此命令将为常规证书生成 .csr
/ .key
文件对。
对于嵌入在 kubeconfig 文件中的证书,该命令将生成一个 .csr
/ .conf
对,
其中密钥已嵌入在 .conf
文件中。
CSR 文件包含 CA 签署证书的所有相关信息。 kubeadm 对其所有证书和 CSR 使用明确定义的规约。
默认证书目录是 /etc/kubernetes/pki
,而 kubeconfig 文件的默认目录是 /etc/kubernetes
。
这些默认值可以分别使用标志 --cert-dir
和 --kubeconfig-dir
覆盖。
要将自定义选项传递给 kubeadm certs generate-csr
,可以使用 --config
标志,
此标志接受 kubeadm 配置文件,
与诸如 kubeadm init
这类命令相似。
所有规约(例如额外的 SAN 和自定义 IP 地址)都必须存储在同一配置文件中,
并通过将其作为 --config
传递来用于所有相关的 kubeadm 命令。
说明:
本指南使用默认的 Kubernetes 目录 /etc/kubernetes
,需要超级用户权限。
如果你按照本指南使用你可以写入的目录
(通常这意味着使用 --cert-dir
和 --kubeconfig-dir
运行 kubeadm
),你可以省略 sudo
命令。
然后,你必须将生成的文件复制到 /etc/kubernetes
目录下,以便 kubeadm init
或 kubeadm join
能够找到它们。
准备 CA 和服务帐户文件
在将执行 kubeadm init
的主控制平面节点上,执行以下命令:
sudo kubeadm init phase certs ca
sudo kubeadm init phase certs etcd-ca
sudo kubeadm init phase certs front-proxy-ca
sudo kubeadm init phase certs sa
这些操作将使用 kubeadm 所需的所有自签名 CA 文件(证书和密钥)
以及服务帐户(公钥和私钥)填充控制平面节点的 /etc/kubernetes/pki
和 /etc/kubernetes/pki/etcd
目录。
说明:
如果你使用外部 CA,则你必须在带外生成相同的文件,并手动将它们复制到主控制平面节点上的 /etc/kubernetes
。
所有 CSR 被签名后,你可以删除根 CA 密钥(ca.key
),如外部 CA 模式部分中所述。
对于辅助控制平面节点(kubeadm join --control-plane
),无需执行前述命令。
根据你部署高可用集群的方式,
你要么从主控制平面节点手动复制相同的文件,要么使用 kubeadm init
的 --upload-certs
特性实现自动化分发。
生成 CSR
kubeadm certs generate-csr
命令为 kubeadm 所了解并管理的所有证书生成 CSR。
命令完成后,你必须手动删除不需要的 .csr
、.conf
或 .key
文件。
kubelet.conf 的注意事项
本节适用于控制平面和工作节点。
如果你从控制平面节点(外部 CA 模式)上删除了 ca.key
文件,
则该集群中的运行的 kube-controller-manager 将无法签署 kubelet 客户端证书。
如果你的设置中不存在用于签署这些证书的外部方法
(例如外部签名者),你可以按照本指南中的说明手动签署 kubelet.conf.csr
。
请注意,这也意味着自动
kubelet 客户端证书轮换将被禁用。
如果是这样,在证书即将到期时,你必须生成新的 kubelet.conf.csr
,签署证书,
将其嵌入到 kubelet.conf
中并重新启动 kubelet。
如果这不适用于你的配置,你可以跳过在辅助控制平面和工作节点
(调用 kubeadm join ...
的所有节点)上处理 kubelet.conf.csr
。
这是因为所运行的 kube-controller-manager 将负责签署新的 kubelet 客户端证书。
说明:
你必须在主控制平面节点(你最初运行 kubeadm init
的主机)上处理 kubelet.conf.csr
,
这是因为 kubeadm
将该节点视为引导集群的节点,并且需要预先填充的 kubelet.conf
。
控制平面节点
在主(kubeadm init
)和辅助(kubeadm join --control-plane
)
控制平面节点上执行以下命令以生成所有 CSR 文件:
sudo kubeadm certs generate-csr
如果要使用外部 etcd,请阅读 kubeadm 使用外部 etcd
指南了解 kubeadm 和 etcd 节点上需要哪些 CSR 文件。
你可以删除 /etc/kubernetes/pki/etcd
下的其他 .csr
和 .key
文件。
根据 kubelet.conf 的注意事项中的说明,
你可以决定保留或删除 kubelet.conf
和 kubelet.conf.csr
文件。
工作节点
根据 kubelet.conf 的注意事项中的解释,可以选择执行:
sudo kubeadm certs generate-csr
并仅保留 kubelet.conf
和 kubelet.conf.csr
文件,
或者完全跳过工作节点的步骤。
签署所有证书的 CSR
说明:
如果你使用外部 CA 并且已经拥有 openssl
的 CA 序列号文件(.srl
),
你可以将此类文件复制到将处理 CSR 的 kubeadm 节点。
要复制的 .srl
文件有 /etc/kubernetes/pki/ca.srl
、/etc/kubernetes/pki/front-proxy-ca.srl
和 /etc/kubernetes/pki/etcd/ca.srl
。
然后可以将文件移动到将处理 CSR 文件的新节点。
如果节点上的 CA 缺少 .srl
文件,下面的脚本将生成一个具有随机起始序列号的新 SRL 文件。
要了解有关 .srl
文件的更多信息,请参阅
openssl
关于 --CAserial
标志的文档。
对具有 CSR 文件的所有节点重复此步骤。
在 /etc/kubernetes
目录中编写以下脚本,进入该目录并执行该脚本。
该脚本将为 /etc/kubernetes
目录下存在的所有 CSR 文件生成证书。
#!/bin/bash
# 设置证书过期时间(以天为单位)
DAYS=365
# 处理除 front-proxy 和 etcd 之外的所有 CSR 文件
find ./ -name "*.csr" | grep -v "pki/etcd" | grep -v "front-proxy" | while read -r FILE;
do
echo "* Processing ${FILE} ..."
FILE=${FILE%.*} # 修剪扩展名
if [ -f "./pki/ca.srl" ]; then
SERIAL_FLAG="-CAserial ./pki/ca.srl"
else
SERIAL_FLAG="-CAcreateserial"
fi
openssl x509 -req -days "${DAYS}" -CA ./pki/ca.crt -CAkey ./pki/ca.key ${SERIAL_FLAG} \
-in "${FILE}.csr" -out "${FILE}.crt"
sleep 2
done
# 处理所有 etcd CSR
find ./pki/etcd -name "*.csr" | while read -r FILE;
do
echo "* Processing ${FILE} ..."
FILE=${FILE%.*} # 修剪扩展名
if [ -f "./pki/etcd/ca.srl" ]; then
SERIAL_FLAG=-CAserial ./pki/etcd/ca.srl
else
SERIAL_FLAG=-CAcreateserial
fi
openssl x509 -req -days "${DAYS}" -CA ./pki/etcd/ca.crt -CAkey ./pki/etcd/ca.key ${SERIAL_FLAG} \
-in "${FILE}.csr" -out "${FILE}.crt"
done
# 处理前端代理 CSR
echo "* Processing ./pki/front-proxy-client.csr ..."
openssl x509 -req -days "${DAYS}" -CA ./pki/front-proxy-ca.crt -CAkey ./pki/front-proxy-ca.key -CAcreateserial \
-in ./pki/front-proxy-client.csr -out ./pki/front-proxy-client.crt
在 kubeconfig 文件中嵌入证书
对具有 CSR 文件的所有节点重复此步骤。
在 /etc/kubernetes
目录中编写以下脚本,进入该目录并执行脚本。
此脚本将基于上一步从 CSR 中得到为 kubeconfig 文件签名的 .crt
文件,
并将它们嵌入到 kubeconfig 文件中。
#!/bin/bash
CLUSTER=kubernetes
find ./ -name "*.conf" | while read -r FILE;
do
echo "* Processing ${FILE} ..."
KUBECONFIG="${FILE}" kubectl config set-cluster "${CLUSTER}" --certificate-authority ./pki/ca.crt --embed-certs
USER=$(KUBECONFIG="${FILE}" kubectl config view -o jsonpath='{.users[0].name}')
KUBECONFIG="${FILE}" kubectl config set-credentials "${USER}" --client-certificate "${FILE}.crt" --embed-certs
done
执行清理
在具有 CSR 文件的所有节点上执行此步骤。
在 /etc/kubernetes
目录中编写以下脚本,进入该目录并执行脚本。
#!/bin/bash
# 清理 CSR 文件
rm -f ./*.csr ./pki/*.csr ./pki/etcd/*.csr # 清理所有 CSR 文件
# 清理已嵌入 kubeconfig 文件中的 CRT 文件
rm -f ./*.crt
(可选)将 .srl
文件移动到下一个要处理的节点。
或者,如果使用外部 CA,请删除 /etc/kubernetes/pki/ca.key
文件,
如外部 CA 节点部分中所述。
kubeadm 节点初始化
一旦 CSR 文件被签名并且所需的证书在要用作节点的主机上就位,你就可以使用命令
kubeadm init
和 kubeadm join
使用这些节点创建 Kubernetes 集群。
在 init
和 join
期间,kubeadm 使用在主机本地文件系统的
/etc/kubernetes
目录中找到的现有证书、加密密钥和 kubeconfig 文件。
2.8 - 重新配置 kubeadm 集群
kubeadm 不支持自动重新配置部署在托管节点上的组件的方式。 一种自动化的方法是使用自定义的 operator。
要修改组件配置,你必须手动编辑磁盘上关联的集群对象和文件。 本指南展示了实现 kubeadm 集群重新配置所需执行的正确步骤顺序。
准备开始
- 你需要一个使用 kubeadm 部署的集群
- 拥有管理员凭据(
/etc/kubernetes/admin.conf
) 和从安装了 kubectl 的主机到集群中正在运行的 kube-apiserver 的网络连接 - 在所有主机上安装文本编辑器
重新配置集群
kubeadm 在 ConfigMap 和其他对象中写入了一组集群范围的组件配置选项。
这些对象必须手动编辑,可以使用命令 kubectl edit
。
kubectl edit
命令将打开一个文本编辑器,你可以在其中直接编辑和保存对象。
你可以使用环境变量 KUBECONFIG
和 KUBE_EDITOR
来指定 kubectl
使用的 kubeconfig 文件和首选文本编辑器的位置。
例如:
KUBECONFIG=/etc/kubernetes/admin.conf KUBE_EDITOR=nano kubectl edit <parameters>
说明:
保存对这些集群对象的任何更改后,节点上运行的组件可能不会自动更新。 以下步骤将指导你如何手动执行该操作。
警告:
ConfigMaps 中的组件配置存储为非结构化数据(YAML 字符串)。 这意味着在更新 ConfigMap 的内容时不会执行验证。 你必须小心遵循特定组件配置的文档化 API 格式, 并避免引入拼写错误和 YAML 缩进错误。
应用集群配置更改
更新 ClusterConfiguration
在集群创建和升级期间,kubeadm 将其
ClusterConfiguration
写入 kube-system
命名空间中名为 kubeadm-config
的 ConfigMap。
要更改 ClusterConfiguration
中的特定选项,你可以使用以下命令编辑 ConfigMap:
kubectl edit cm -n kube-system kubeadm-config
配置位于 data.ClusterConfiguration
键下。
说明:
ClusterConfiguration
包括各种影响单个组件配置的选项, 例如
kube-apiserver、kube-scheduler、kube-controller-manager、
CoreDNS、etcd 和 kube-proxy。 对配置的更改必须手动反映在节点组件上。
在控制平面节点上反映 ClusterConfiguration
更改
kubeadm 将控制平面组件作为位于 /etc/kubernetes/manifests
目录中的静态 Pod 清单进行管理。
对 apiServer
、controllerManager
、scheduler
或 etcd
键下的
ClusterConfiguration
的任何更改都必须反映在控制平面节点上清单目录中的关联文件中。
此类更改可能包括:
extraArgs
- 需要更新传递给组件容器的标志列表extraVolumes
- 需要更新组件容器的卷挂载*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>
值必须是一个控制平面组件(apiserver
、controller-manager
或 scheduler
)的名称。
说明:
更新 /etc/kubernetes/manifests
中的文件将告诉 kubelet 重新启动相应组件的静态 Pod。
尝试一次对一个节点进行这些更改,以在不停机的情况下离开集群。
应用 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 服务
说明:
一次执行一个节点的这些更改,以允许正确地重新安排工作负载。
说明:
在 kubeadm upgrade
期间,kubeadm 从 kubelet-config
ConfigMap
下载 KubeletConfiguration
并覆盖 /var/lib/kubelet/config.yaml
的内容。
这意味着节点本地配置必须通过/var/lib/kubelet/kubeadm-flags.env
中的标志或在
kubeadm upgrade后手动更新
/var/lib/kubelet/config.yaml` 的内容来应用,
然后重新启动 kubelet。
应用 kube-proxy 配置更改
更新 KubeProxyConfiguration
在集群创建和升级期间,kubeadm 将其写入
KubeProxyConfiguration
在名为 kube-proxy
的 kube-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 delete po -n kube-system -l k8s-app=kube-proxy
将创建使用更新的 ConfigMap 的新 Pod。
说明:
由于 kubeadm 将 kube-proxy 部署为 DaemonSet,因此不支持特定于节点的配置。
应用 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 不允许在集群创建和升级期间配置 CoreDNS。
这意味着如果执行了 kubeadm upgrade apply
,你对
CoreDNS 对象的更改将丢失并且必须重新应用。
持久化重新配置
在受管节点上执行 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。
接下来
2.9 - 更改 Kubernetes 软件包仓库
本页介绍了如何在升级集群时启用包含 Kubernetes 次要版本的软件包仓库。
这仅适用于使用托管在 pkgs.k8s.io
上社区自治软件包仓库的用户。
启用新的 Kubernetes 小版本的软件包仓库。与传统的软件包仓库不同,
社区自治的软件包仓库所采用的结构为每个 Kubernetes 小版本都有一个专门的软件包仓库。
说明:
本指南仅介绍 Kubernetes 升级过程的一部分。 有关升级 Kubernetes 集群的更多信息, 请参阅升级指南。
说明:
仅在将集群升级到另一个次要版本时才需要执行此步骤。 如果你要升级到同一次要版本中的另一个补丁版本(例如:v1.32.5 到 v1.32.7)则不需要遵循本指南。 但是,如果你仍在使用旧的软件包仓库,则需要在升级之前迁移到社区自治的新软件包仓库 (有关如何执行此操作的更多详细信息,请参阅下一节)。
准备开始
本文假设你已经在使用社区自治的软件包仓库(pkgs.k8s.io
)。如果不是这种情况,
强烈建议按照官方公告中所述,
迁移到社区自治的软件包仓库。
apt.kubernetes.io
和yum.kubernetes.io
)。
强烈建议使用托管在 pkgs.k8s.io
上的新软件包仓库来安装 2023 年 9 月 13 日之后发布的 Kubernetes 版本。
旧版软件包仓库已被弃用,其内容可能在未来的任何时间被删除,恕不另行通知。新的软件包仓库提供了从 Kubernetes v1.24.0 版本开始的下载。验证是否正在使用 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.31/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.31/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.31/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.31/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl
你正在使用 Kubernetes 软件包仓库,本指南适用于你。 否则,强烈建议按照官方公告中所述, 迁移到 Kubernetes 软件包仓库。
说明:
Kubernetes 软件包仓库所用的 URL 不仅限于 pkgs.k8s.io
,还可以是以下之一:
pkgs.k8s.io
pkgs.kubernetes.io
packages.kubernetes.io
切换到其他 Kubernetes 软件包仓库
在从一个 Kubernetes 小版本升级到另一个版本时,应执行此步骤以获取所需 Kubernetes 小版本的软件包访问权限。
使用你所选择的文本编辑器打开定义 Kubernetes
apt
仓库的文件:nano /etc/apt/sources.list.d/kubernetes.list
你应该看到一行包含当前 Kubernetes 小版本的 URL。 例如,如果你正在使用 v1.31,你应该看到类似以下的输出:
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /
将 URL 中的版本更改为下一个可用的小版本,例如:
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /
- 保存文件并退出文本编辑器。继续按照相关的升级说明进行操作。
使用你所选择的文本编辑器打开定义 Kubernetes
yum
仓库的文件:nano /etc/yum.repos.d/kubernetes.repo
你应该看到一个文件包含当前 Kubernetes 小版本的两个 URL。 例如,如果你正在使用 v1.31,你应该看到类似以下的输出:
[kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/repodata/repomd.xml.key exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
将这些 URL 中的版本更改为下一个可用的小版本,例如:
[kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
- 保存文件并退出文本编辑器。继续按照相关的升级说明进行操作。
接下来
- 参见如何升级 Linux 节点的说明。
- 参见如何升级 Windows 节点的说明。
3 - 从 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 供应商以获得支持或一次升级多个版本。
你的集群中可以有不止一种类型的节点,尽管这不是常见的情况。
下面这些任务可以帮助你完成迁移:
接下来
3.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 的详细步骤。
- 从官方的 Docker 仓库安装
containerd.io
包。关于为你所使用的 Linux 发行版来设置 Docker 仓库,以及安装containerd.io
包的详细说明, 可参见开始使用 containerd。
配置 containerd:
sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml
重启 containerd:
sudo systemctl restart containerd
启动一个 Powershell 会话,将 $Version
设置为期望的版本(例如:$Version="1.4.3"
),
之后运行下面的命令:
下载 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
解压缩并执行配置:
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"
启动 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。
注意:
Docker 所提供的卸载 Docker Engine 命令指导中,存在删除 containerd 的风险。 在执行命令时要谨慎。
uncordon 节点
kubectl uncordon <node-to-uncordon>
将 <node-to-uncordon>
替换为你之前腾空的节点的名称。
3.2 - 将 Docker Engine 节点从 dockershim 迁移到 cri-dockerd
本页面为你展示如何迁移你的 Docker Engine 节点,使之使用 cri-dockerd
而不是 dockershim。
在以下场景中,你可以遵从这里的步骤执行操作:
- 你期望不再使用 dockershim,但仍然使用 Docker Engine 来在 Kubernetes 中运行容器。
- 你希望升级到 Kubernetes v1.32 且你的现有集群依赖于 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
,那么你不会被 dockershim 的移除影响到。
在开始之前,检查你的节点是否在使用 dockershim。
如果你想要迁移到 cri-dockerd
以便继续使用 Docker Engine 作为你的容器运行时,
你需要在所有被影响的节点上执行以下操作:
- 安装
cri-dockerd
; - 隔离(Cordon)并腾空(Drain)该节点;
- 配置 kubelet 使用
cri-dockerd
; - 重新启动 kubelet;
- 验证节点处于健康状态。
首先在非关键节点上测试这一迁移过程。
你应该针对所有希望迁移到 cri-dockerd
的节点执行以下步骤。
准备开始
- 安装了
cri-dockerd
并且该服务已经在各节点上启动; - 一个网络插件。
隔离并腾空节点
隔离节点,阻止新的 Pod 被调度到节点上:
kubectl cordon <NODE_NAME>
将
<NODE_NAME>
替换为节点名称。
腾空节点以安全地逐出所有运行中的 Pod:
kubectl drain <NODE_NAME> --ignore-daemonsets
配置 kubelet 使用 cri-dockerd
下面的步骤适用于用 kubeadm 工具安装的集群。如果你使用不同的工具, 你需要使用针对该工具的配置指令来修改 kubelet。
- 在每个被影响的节点上,打开
/var/lib/kubelet/kubeadm-flags.env
文件; - 将
--container-runtime-endpoint
标志,将其设置为unix:///var/run/cri-dockerd.sock
。 - 将
--container-runtime
标志修改为remote
(在 Kubernetes v1.27 及更高版本中不可用)。
kubeadm 工具将节点上的套接字存储为控制面上 Node
对象的注解。
要为每个被影响的节点更改此套接字:
编辑
Node
对象的 YAML 表示:KUBECONFIG=/path/to/admin.conf kubectl edit no <NODE_NAME>
根据下面的说明执行替换:
/path/to/admin.conf
:指向 kubectl 配置文件admin.conf
的路径;<NODE_NAME>
:你要修改的节点的名称。
将
kubeadm.alpha.kubernetes.io/cri-socket
标志从/var/run/dockershim.sock
更改为unix:///var/run/cri-dockerd.sock
;保存所作更改。保存时,
Node
对象被更新。
重启 kubelet
systemctl restart kubelet
验证节点处于健康状态
要检查节点是否在使用 cri-dockerd
端点,
按照找出你所使用的运行时页面所给的指令操作。
kubelet 的 --container-runtime-endpoint
标志取值应该是 unix:///var/run/cri-dockerd.sock
。
解除节点隔离
kubectl uncordon <NODE_NAME>
接下来
3.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。
说明:
如果你的节点在通过 cri-dockerd
使用 Docker Engine,
那么集群不会受到 Kubernetes 移除 dockershim 的影响。
可以通过检查 kubelet 的参数得知当前使用的是哪个 socket。
查看 kubelet 进程的启动命令
tr \\0 ' ' < /proc/"$(pgrep kubelet)"/cmdline
如有节点上没有
tr
或者pgrep
,就需要手动检查 kubelet 的启动命令
在命令的输出中,查找
--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 的端点。
- 如果你的节点使用 Kubernetes v1.23 或更早的版本,这两个参数不存在,
或者
如果你将节点上的容器运行时从 Docker Engine 改变为 containerd,可在
迁移到不同的运行时
找到更多信息。或者,如果你想在 Kubernetes v1.24 及以后的版本仍使用 Docker Engine,
可以安装 CRI 兼容的适配器实现,如 cri-dockerd
。
3.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 配置文件。
以下是针对各节点要执行的典型步骤的概述:
- 停止容器运行时和 kubelet 服务后,执行以下升级操作:
- 如果你正在运行 CNI 插件,请将它们升级到最新版本。
- 如果你使用的是非 CNI 插件,请将它们替换为 CNI 插件,并使用最新版本的插件。
- 更新插件配置文件以指定或匹配 CNI 规范支持的插件版本, 如后文 "containerd 配置文件示例"章节所示。
- 对于
containerd
,请确保你已安装 CNI loopback 插件的最新版本(v1.0.0 或更高版本)。 - 将节点组件(例如 kubelet)升级到 Kubernetes v1.24
- 升级到或安装最新版本的容器运行时。
- 通过重新启动容器运行时和 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 地址范围更新为合适的值。
3.5 - 检查移除 Dockershim 是否对你有影响
Kubernetes 的 dockershim
组件使得你可以把 Docker 用作 Kubernetes 的
容器运行时。
在 Kubernetes v1.24 版本中,内建组件 dockershim
被移除。
本页讲解你的集群把 Docker 用作容器运行时的运作机制,
并提供使用 dockershim
时,它所扮演角色的详细信息,
继而展示了一组操作,可用来检查移除 dockershim
对你的工作负载是否有影响。
检查你的应用是否依赖于 Docker
即使你是通过 Docker 创建的应用容器,也不妨碍你在其他任何容器运行时上运行这些容器。 这种使用 Docker 的方式并不构成对 Docker 作为一个容器运行时的依赖。
当用了别的容器运行时之后,Docker 命令可能不工作,或者产生意外的输出。 下面是判定你是否依赖于 Docker 的方法。
- 确认没有特权 Pod 执行 Docker 命令(如
docker ps
)、重新启动 Docker 服务(如systemctl restart docker.service
)或修改 Docker 配置文件/etc/docker/daemon.json
。 - 检查 Docker 配置文件(如
/etc/docker/daemon.json
)中容器镜像仓库的镜像(mirror)站点设置。 这些配置通常需要针对不同容器运行时来重新设置。 - 检查确保在 Kubernetes 基础设施之外的节点上运行的脚本和应用程序没有执行 Docker 命令。
可能的情况有:
- SSH 到节点排查故障;
- 节点启动脚本;
- 直接安装在节点上的监控和安全代理。
- 检查执行上述特权操作的第三方工具。 详细操作请参考从 dockershim 迁移遥测和安全代理。
- 确认没有对 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。
切换到 Containerd 容器运行时可以消除掉中间环节。 所有相同的容器都可由 Containerd 这类容器运行时来运行。 但是现在,由于直接用容器运行时调度容器,它们对 Docker 是不可见的。 因此,你以前用来检查这些容器的 Docker 工具或漂亮的 UI 都不再可用。
你不能再使用 docker ps
或 docker inspect
命令来获取容器信息。
由于你不能列出容器,因此你不能获取日志、停止容器,甚至不能通过 docker exec
在容器中执行命令。
说明:
如果你在用 Kubernetes 运行工作负载,最好通过 Kubernetes API 停止容器, 而不是通过容器运行时来停止它们(此建议适用于所有容器运行时,不仅仅是针对 Docker)。
你仍然可以下载镜像,或者用 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 作为一个独立的守护程序来缓解这个问题。
- 找到名称格式为
vX.Y.Z-containerd-cri
的最新 cAdvisor 版本(例如v0.42.0-containerd-cri
)。 - 按照 cAdvisor Kubernetes Daemonset 中的步骤来创建守护进程。
- 将已安装的指标收集器指向使用 cAdvisor 的
/metrics
端点。 该端点提供了全套的 Prometheus 容器指标。
替代方案:
- 使用替代的第三方指标收集解决方案。
- 从 Kubelet 摘要 API 收集指标,该 API 在
/stats/summary
提供服务。
接下来
- 阅读从 dockershim 迁移, 以了解你的下一步工作。
- 阅读弃用 Dockershim 的常见问题,了解更多信息。
3.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 ps
或 docker 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-cluster
,kubelet-stats/kubelet-metrics
,docker-container-stats
。
kubelet-stats
监视器此前已被供应商所弃用,现支持 kubelet-metrics
。
docker-container-stats
监视器受 dockershim 移除的影响。
不要为 docker-container-stats
监视器使用 Docker Engine 之外的运行时。
如何从依赖 dockershim 的代理迁移:
- 从所配置的监视器中移除
docker-container-stats
。 注意,若节点上已经安装了 Docker,在非 dockershim 环境中启用此监视器后会导致报告错误的指标; 如果节点未安装 Docker,则无法获得指标。 - 启用和配置
kubelet-metrics
监视器。
说明:
收集的指标会发生变化。具体请查看你的告警规则和仪表盘。名字中包含以下字符串的 Pod 可能访问 Docker:
signalfx-agent
Yahoo Kubectl Flame
Flame 不支持 Docker 以外的容器运行时,具体可见 https://github.com/yahoo/kubectl-flame/issues/51
4 - 手动生成证书
在使用客户端证书认证的场景下,你可以通过 easyrsa
、
openssl
或 cfssl
等工具以手工方式生成证书。
easyrsa
easyrsa 支持以手工方式为你的集群生成证书。
下载、解压、初始化打过补丁的
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
生成新的证书颁发机构(CA)。参数
--batch
用于设置自动模式; 参数--req-cn
用于设置新的根证书的通用名称(CN)。./easyrsa --batch "--req-cn=${MASTER_IP}@`date +%s`" build-ca nopass
生成服务器证书和秘钥。
参数
--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
- 拷贝文件
pki/ca.crt
、pki/issued/server.crt
和pki/private/server.key
到你的目录中。
在 API 服务器的启动参数中添加以下参数:
--client-ca-file=/yourdirectory/ca.crt --tls-cert-file=/yourdirectory/server.crt --tls-private-key-file=/yourdirectory/server.key
openssl
openssl 支持以手工方式为你的集群生成证书。
生成一个 2048 位的 ca.key 文件
openssl genrsa -out ca.key 2048
在 ca.key 文件的基础上,生成 ca.crt 文件(用参数
-days
设置证书有效期)openssl req -x509 -new -nodes -key ca.key -subj "/CN=${MASTER_IP}" -days 10000 -out ca.crt
生成一个 2048 位的 server.key 文件:
openssl genrsa -out server.key 2048
创建一个用于生成证书签名请求(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
基于上面的配置文件生成证书签名请求:
openssl req -new -key server.key -out server.csr -config csr.conf
基于 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
查看证书签名请求:
openssl req -noout -text -in ./server.csr
查看证书:
openssl x509 -noout -text -in ./server.crt
最后,为 API 服务器添加相同的启动参数。
cfssl
cfssl 是另一个用于生成证书的工具。
下载、解压并准备如下所示的命令行工具。
注意:你可能需要根据所用的硬件体系架构和 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
创建一个目录,用它保存所生成的构件和初始化 cfssl:
mkdir cert cd cert ../cfssl print-defaults config > config.json ../cfssl print-defaults csr > csr.json
创建一个 JSON 配置文件来生成 CA 文件,例如:
ca-config.json
:{ "signing": { "default": { "expiry": "8760h" }, "profiles": { "kubernetes": { "usages": [ "signing", "key encipherment", "server auth", "client auth" ], "expiry": "8760h" } } } }
创建一个 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>" }] }
生成 CA 秘钥文件(
ca-key.pem
)和证书文件(ca.pem
):../cfssl gencert -initca ca-csr.json | ../cfssljson -bare ca
创建一个 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>" }] }
为 API 服务器生成秘钥和证书,默认会分别存储为
server-key.pem
和server.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 认证文档所述。
5 - 管理内存、CPU 和 API 资源
5.1 - 为命名空间配置默认的内存请求和限制
本章介绍如何为命名空间配置默认的内存请求和限制。
一个 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
说明:
LimitRange
不会检查它应用的默认值的一致性。 这意味着 LimitRange
设置的 limit 的默认值可能小于客户端提交给
API 服务器的声明中为容器指定的 request 值。如果发生这种情况,最终会导致 Pod 无法调度。更多信息,
请参阅资源限制的 limit 和 request。
设置默认内存限制和请求的动机
如果你的命名空间设置了内存 资源配额, 那么为内存限制设置一个默认值会很有帮助。 以下是内存资源配额对命名空间的施加的三条限制:
命名空间中运行的每个 Pod 中的容器都必须有内存限制。 (如果为 Pod 中的每个容器声明了内存限制, Kubernetes 可以通过将其容器的内存限制相加推断出 Pod 级别的内存限制)。
内存限制用来在 Pod 被调度到的节点上执行资源预留。 预留给命名空间中所有 Pod 使用的内存总量不能超过规定的限制。
命名空间中所有 Pod 实际使用的内存总量也不能超过规定的限制。
当你添加 LimitRange 时:
如果该命名空间中的任何 Pod 的容器未指定内存限制, 控制面将默认内存限制应用于该容器, 这样 Pod 可以在受到内存 ResourceQuota 限制的命名空间中运行。
清理
删除你的命名空间:
kubectl delete namespace default-mem-example
接下来
集群管理员参考
应用开发者参考
5.2 - 为命名空间配置默认的 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
接下来
集群管理员参考
- 为命名空间配置默认内存请求和限制
- 为命名空间配置内存限制的最小值和最大值
- 为命名空间配置 CPU 限制的最小值和最大值
- 为命名空间配置内存和 CPU 配额
- 为命名空间配置 Pod 配额
- 为 API 对象配置配额
应用开发者参考
5.3 - 配置命名空间的最小和最大内存约束
本页介绍如何设置在名字空间 中运行的容器所使用的内存的最小值和最大值。你可以在 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
接下来
集群管理员参考
- 为命名空间配置默认内存请求和限制
- 为命名空间配置内存限制的最小值和最大值
- 为命名空间配置 CPU 限制的最小值和最大值
- 为命名空间配置内存和 CPU 配额
- 为命名空间配置 Pod 配额
- 为 API 对象配置配额
应用开发者参考
5.4 - 为命名空间配置 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。
说明:
当创建LimitRange
对象时,你也可以声明大页面和 GPU 的限制。
当这些资源同时声明了 default
和 defaultRequest
参数时,两个参数值必须相同。以下为某个仅包含一个容器的 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
接下来
集群管理员参考
应用开发者参考
5.5 - 为命名空间配置内存和 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
接下来
集群管理员参考
- 为命名空间配置默认内存请求和限制
- 为命名空间配置默认 CPU 请求和限制
- 为命名空间配置内存限制的最小值和最大值
- 为命名空间配置 CPU 限制的最小值和最大值
- 为命名空间配置 Pod 配额
- 为 API 对象配置配额
应用开发者参考
5.6 - 配置命名空间下 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
接下来
集群管理人员参考
- 为命名空间配置默认的内存请求和限制
- 为命名空间配置默认的 CPU 请求和限制
- 为命名空间配置内存的最小值和最大值约束
- 为命名空间配置 CPU 的最小值和最大值约束
- 为命名空间配置内存和 CPU 配额
- 为 API 对象的设置配额
应用开发人员参考
6 - 安装网络策略驱动
6.1 - 使用 Antrea 提供 NetworkPolicy
本页展示了如何在 kubernetes 中安装和使用 Antrea CNI 插件。 要了解 Antrea 项目的背景,请阅读 Antrea 介绍。
准备开始
你需要拥有一个 kuernetes 集群。 遵循 kubeadm 入门指南自行创建一个。
使用 kubeadm 部署 Antrea
遵循入门指南 为 kubeadm 部署 Antrea 。
接下来
一旦你的集群已经运行,你可以遵循 声明网络策略 来尝试 Kubernetes NetworkPolicy。
6.2 - 使用 Calico 提供 NetworkPolicy
本页展示了几种在 Kubernetes 上快速创建 Calico 集群的方法。
准备开始
在 Google Kubernetes Engine (GKE) 上创建一个 Calico 集群
先决条件:gcloud
启动一个带有 Calico 的 GKE 集群,需要加上参数
--enable-network-policy
。语法
gcloud container clusters create [CLUSTER_NAME] --enable-network-policy
示例
gcloud container clusters create my-calico-cluster --enable-network-policy
使用如下命令验证部署是否正确。
kubectl get pods --namespace=kube-system
Calico 的 Pod 名以
calico
打头,检查确认每个 Pod 状态为Running
。
使用 kubeadm 创建一个本地 Calico 集群
使用 kubeadm 在 15 分钟内得到一个本地单主机 Calico 集群,请参考 Calico 快速入门。
接下来
集群运行后, 你可以按照声明网络策略去尝试使用 Kubernetes NetworkPolicy。
6.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 频道联系我们。
6.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。
6.5 - 使用 Romana 提供 NetworkPolicy
本页展示如何使用 Romana 作为 NetworkPolicy。
准备开始
完成 kubeadm 入门指南中的 1、2、3 步。
使用 kubeadm 安装 Romana
按照容器化安装指南, 使用 kubeadm 安装。
应用网络策略
使用以下的一种方式应用网络策略:
- Romana 网络策略
- NetworkPolicy API
接下来
Romana 安装完成后,你可以按照 声明网络策略 去尝试使用 Kubernetes NetworkPolicy。
6.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 都是 Running
和 2/2 READY
。
(2/2
表示每个 Pod 都有 weave
和 weave-npc
。)
接下来
安装 Weave Net 插件后,你可以参考 声明网络策略 来试用 Kubernetes NetworkPolicy。 如果你有任何疑问,请通过 Slack 上的 #weave-community 频道或者 Weave 用户组 联系我们。
7 - 使用 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 客户端(如 curl
、wget
或浏览器)直接访问 REST API,你可以通过多种方式对 API 服务器进行定位和身份验证:
- 以代理模式运行 kubectl(推荐)。 推荐使用此方法,因为它用存储的 apiserver 位置并使用自签名证书验证 API 服务器的标识。 使用这种方法无法进行中间人(MITM)攻击。
- 另外,你可以直接为 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 官方支持 Go、Python、Java、 dotnet、JavaScript 和 Haskell 语言的客户端库。还有一些其他客户端库由对应作者而非 Kubernetes 团队提供并维护。 参考客户端库了解如何使用其他语言来访问 API 以及如何执行身份认证。
Go 客户端
- 要获取库,运行下列命令:
go get k8s.io/client-go/kubernetes-<kubernetes 版本号>
, 参见 https://github.com/kubernetes/client-go/releases 查看受支持的版本。 - 基于 client-go 客户端编写应用程序。
说明:
client-go 定义了自己的 API 对象,因此如果需要,从 client-go 而不是主仓库导入
API 定义,例如 import "k8s.io/client-go/kubernetes"
是正确做法。
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
接下来
8 - 为节点发布扩展资源
本文展示了如何为节点指定扩展资源(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
说明:
在前面的请求中,~1
为 patch 路径中 “/” 符号的编码。
JSON-Patch 中的操作路径值被解析为 JSON 指针。
更多细节,请查看 IETF RFC 6901 的第 3 节。
输出显示该节点的 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
(你应该看不到任何输出)
接下来
针对应用开发人员
针对集群管理员
9 - 自动扩缩集群 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
并在输出中寻找名称为 coredns
或 kube-dns
的 Deployment。
你的扩缩目标为:
Deployment/<your-deployment-name>
其中 <your-deployment-name>
是 DNS Deployment 的名称。
例如,如果你的 DNS Deployment 名称是 coredns
,则你的扩展目标是 Deployment/coredns。
说明:
CoreDNS 是 Kubernetes 的默认 DNS 服务。CoreDNS 设置标签 k8s-app=kube-dns
,
以便能够在原来使用 kube-dns
的集群中工作。
启用 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 ) )
注意 coresPerReplica
和 nodesPerReplica
的值都是浮点数。
背后的思想是,当一个集群使用具有很多核心的节点时,由 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 提供了一个控制器接口来支持两种控制模式:linear 和 ladder。
接下来
10 - 从轮询切换为基于 CRI 事件的更新来获取容器状态
Kubernetes v1.25 [alpha]
(enabled by default: false)本页展示了如何迁移节点以使用基于事件的更新来获取容器状态。 与依赖轮询的传统方法相比,基于事件的实现可以减少 kubelet 对节点资源的消耗。 你可以将这个特性称为事件驱动的 Pod 生命周期事件生成器 (PLEG)。 这是在 Kubernetes 项目内部针对关键实现细节所用的名称。
基于轮询的方法称为通用 PLEG。
准备开始
- 你需要运行提供此特性的 Kubernetes 版本。 Kubernetes 1.27 提供了对基于事件更新容器状态的 Beta 支持。 此特性处于 Beta 阶段,默认被禁用。
- 你的 Kubernetes 服务器版本必须不低于版本 1.26.
要获知版本信息,请输入
kubectl version
. 如果你正在运行不同版本的 Kubernetes,请查阅对应版本的文档。
- 所使用的容器运行时必须支持容器生命周期事件。 如果容器运行时未声明对容器生命周期事件的支持,即使你已启用了此特性门控, kubelet 也会自动切换回传统的通用 PLEG。
为什么要切换到事件驱动的 PLEG?
- 通用 PLEG 由于频繁轮询容器状态而产生了不可忽略的开销。
- 这种开销会被 kubelet 的并行轮询容器状态的机制加剧, 限制了可扩缩性,还会导致性能和可靠性问题。
- 事件驱动的 PLEG 的目标是通过替换定期轮询来减少闲置时的非必要任务。
切换为事件驱动的 PLEG
- 启用特性门控
EventedPLEG
后启动 kubelet。 你可以通过编辑 kubelet 配置文件并重启 kubelet 服务来管理 kubelet 特性门控。 你需要在使用此特性的所有节点上执行此操作。
确保节点被腾空后再继续。
启用容器事件生成后启动容器运行时。
你的 Kubernetes 服务器版本必须不低于版本 1.26. 要获知版本信息,请输入版本 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
kubectl version
.
确认 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
接下来
- 进一步了解 Kubernetes 增强提案 (KEP): kubelet Evented PLEG for Better Performance 中的设计理念。
11 - 改变默认 StorageClass
本文展示了如何改变默认的 Storage Class,它用于为没有特殊需求的 PersistentVolumeClaims 配置 volumes。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
要获知版本信息,请输入kubectl version
.为什么要改变默认存储类?
取决于安装模式,你的 Kubernetes 集群可能和一个被标记为默认的已有 StorageClass 一起部署。 这个默认的 StorageClass 以后将被用于动态的为没有特定存储类需求的 PersistentVolumeClaims 配置存储。更多细节请查看 PersistentVolumeClaim 文档。
预先安装的默认 StorageClass 可能不能很好的适应你期望的工作负载;例如,它配置的存储可能太过昂贵。 如果是这样的话,你可以改变默认 StorageClass,或者完全禁用它以防止动态配置存储。
删除默认 StorageClass 可能行不通,因为它可能会被你集群中的扩展管理器自动重建。 请查阅你的安装文档中关于扩展管理器的细节,以及如何禁用单个扩展。
改变默认 StorageClass
列出你的集群中的 StorageClass:
kubectl get storageclass
输出类似这样:
NAME PROVISIONER AGE standard (default) kubernetes.io/gce-pd 1d gold kubernetes.io/gce-pd 1d
默认 StorageClass 以
(default)
标记。
标记默认 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 的名字。
标记一个 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。
验证你选用的 StorageClass 为默认的:
kubectl get storageclass
输出类似这样:
NAME PROVISIONER AGE standard kubernetes.io/gce-pd 1d gold (default) kubernetes.io/gce-pd 1d
接下来
- 进一步了解 PersistentVolume。
12 - 将 PersistentVolume 的访问模式更改为 ReadWriteOncePod
本文演示了如何将现有 PersistentVolume 的访问模式更改为使用 ReadWriteOncePod
。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
你的 Kubernetes 服务器版本必须不低于版本 v1.22. 要获知版本信息,请输入kubectl version
.说明:
ReadWriteOncePod
访问模式在 Kubernetes v1.29 版本中已进阶至 Stable。
如果你运行的 Kubernetes 版本早于 v1.29,你可能需要启用一个特性门控。
请查阅你所用 Kubernetes 版本的文档。
我为什么要使用 ReadWriteOncePod
?
在 Kubernetes v1.22 之前,ReadWriteOnce
访问模式通常用于限制需要单个写者存储访问模式的工作负载对 PersistentVolume 的访问。
然而,这种访问模式有一个限制:它要求只能从单个节点上访问卷,但允许同一节点上的多个 Pod 同时读写同一个卷。
对于需要严格遵循单个写者访问模式以确保数据安全的应用,这种模式可能形成风险。
如果确保单个写者访问模式对于你的工作负载至关重要,请考虑将你的卷迁移到 ReadWriteOncePod
。
迁移现有 PersistentVolume
如果你有一些 PersistentVolume,可以将它们迁移为使用 ReadWriteOncePod
。
系统仅支持从 ReadWriteOnce
迁移到 ReadWriteOncePod
。
在此示例中,已经有一个 ReadWriteOnce
的 "cat-pictures-pvc" PersistentVolumeClaim
被绑定到了 "cat-pictures-pv" PersistentVolume,还有一个使用此
PersistentVolumeClaim 的 "cat-pictures-writer" Deployment。
说明:
如果你的存储插件支持动态制备, 系统将为你创建 "cat-pictures-pv",但其名称可能不同。 要获取你的 PersistentVolume 的名称,请运行以下命令:
kubectl get pvc cat-pictures-pvc -o jsonpath='{.spec.volumeName}'
你可以在进行更改之前查看 PVC。你可以在本地查看清单,
或运行 kubectl get pvc <PVC 名称> -o yaml
。这条命令的输出类似于:
# cat-pictures-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: cat-pictures-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
以下是一个依赖于此 PersistentVolumeClaim 的 Deployment 示例:
# cat-pictures-writer-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cat-pictures-writer
spec:
replicas: 3
selector:
matchLabels:
app: cat-pictures-writer
template:
metadata:
labels:
app: cat-pictures-writer
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
volumeMounts:
- name: cat-pictures
mountPath: /mnt
volumes:
- name: cat-pictures
persistentVolumeClaim:
claimName: cat-pictures-pvc
readOnly: false
第一步,你需要编辑 PersistentVolume 的 spec.persistentVolumeReclaimPolicy
并将其设置为 Retain
。此字段确保你在删除相应的 PersistentVolumeClaim 时不会删除 PersistentVolume:
kubectl patch pv cat-pictures-pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
接下来,你需要停止正在使用绑定到你要迁移的这个 PersistentVolume 上的 PersistentVolumeClaim 的所有工作负载,然后删除该 PersistentVolumeClaim。 在迁移完成之前,不要对 PersistentVolumeClaim 进行任何其他更改,例如调整卷的大小。
完成后,你需要清除 PersistentVolume 的 spec.claimRef.uid
以确保在重新创建时 PersistentVolumeClaim 能够绑定到它:
kubectl scale --replicas=0 deployment cat-pictures-writer
kubectl delete pvc cat-pictures-pvc
kubectl patch pv cat-pictures-pv -p '{"spec":{"claimRef":{"uid":""}}}'
之后,将 PersistentVolume 的有效访问模式列表替换为(仅)ReadWriteOncePod
:
kubectl patch pv cat-pictures-pv -p '{"spec":{"accessModes":["ReadWriteOncePod"]}}'
说明:
ReadWriteOncePod
访问模式不能与其他访问模式结合使用。
你要确保在更新时 ReadWriteOncePod
是 PersistentVolume 上的唯一访问模式,否则请求将失败。
接下来,你需要修改 PersistentVolumeClaim,将 ReadWriteOncePod
设置为唯一的访问模式。
你还应将 PersistentVolumeClaim 的 spec.volumeName
设置为 PersistentVolume 的名称,
以确保其绑定到特定的 PersistentVolume。
完成后,你可以重新创建你的 PersistentVolumeClaim 并启动你的工作负载:
# 重要提示:在 apply 操作之前必须编辑在 cat-pictures-pvc.yaml 中的 PVC。你需要:
# - 将 ReadWriteOncePod 设置为唯一的访问模式
# - 将 spec.volumeName 设置为 "cat-pictures-pv"
kubectl apply -f cat-pictures-pvc.yaml
kubectl apply -f cat-pictures-writer-deployment.yaml
最后,你可以编辑 PersistentVolume 的 spec.persistentVolumeReclaimPolicy
并将其设置回 Delete
,
如果你之前更改了这个字段的话。
kubectl patch pv cat-pictures-pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'
接下来
- 进一步了解 PersistentVolume。
- 进一步了解 PersistentVolumeClaim。
- 进一步了解配置 Pod 以使用 PersistentVolume 作为存储。
13 - 更改 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 的回收策略
列出你集群中的 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 名称,以便更容易的识别动态配置的卷。
选择你的 PersistentVolumes 中的一个并更改它的回收策略:
kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
这里的
<your-pv-name>
是你选择的 PersistentVolume 的名字。说明:
在 Windows 系统上,你必须对包含空格的 JSONPath 模板加双引号(而不是像上面 一样为 Bash 环境使用的单引号)。这也意味着你必须使用单引号或者转义的双引号 来处理模板中的字面值。例如:
kubectl patch pv <your-pv-name> -p "{\"spec\":{\"persistentVolumeReclaimPolicy\":\"Retain\"}}"
验证你选择的 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
时,它不会被自动删除。
接下来
- 进一步了解 PersistentVolumes
- 进一步了解 PersistentVolumeClaims
参考
- PersistentVolume
- 注意 PersistentVolume 的
.spec.persistentVolumeReclaimPolicy
字段。
- 注意 PersistentVolume 的
- PersistentVolumeClaim
14 - 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,你可能希望通过主节点选举(默认开启)配置一个高可用的云管理控制器。
运行云管理控制器
你需要对集群配置做适当的修改以成功地运行云管理控制器:
kubelet
、kube-apiserver
和kube-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 服务器通信。
随着整个动议的演进,将来的发行版中将作出改变来解决这些问题。
接下来
要构建和开发你自己的云管理控制器,请阅读 开发云管理控制器 文档。
15 - 配置 kubelet 镜像凭据提供程序
Kubernetes v1.26 [stable]
从 Kubernetes v1.20 开始,kubelet 可以使用 exec 插件动态获得针对某容器镜像库的凭据。 kubelet 使用 Kubernetes 版本化 API 通过标准输入输出(标准输入、标准输出和标准错误)和 exec 插件通信。这些插件允许 kubelet 动态请求容器仓库的凭据,而不是将静态凭据存储在磁盘上。 例如,插件可能会与本地元数据服务器通信,以获得 kubelet 正在拉取的镜像的短期凭据。
如果以下任一情况属实,你可能对此功能感兴趣:
- 需要调用云提供商的 API 来获得镜像库的身份验证信息。
- 凭据的到期时间很短,需要频繁请求新凭据。
- 将镜像库凭据存储在磁盘或者 imagePullSecret 是不可接受的。
本指南演示如何配置 kubelet 的镜像凭据提供程序插件机制。
准备开始
- 你需要一个 Kubernetes 集群,其节点支持 kubelet 凭据提供程序插件。 这种支持在 Kubernetes 1.32 中可用; Kubernetes v1.24 和 v1.25 将此作为 Beta 特性包含在内,默认启用。
- 凭据提供程序 exec 插件的一种可用的实现。你可以构建自己的插件或使用云提供商提供的插件。
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.io
或 k8s.*.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
接下来
- 阅读 kubelet 配置 API (v1) 参考中有关
CredentialProviderConfig
的详细信息。 - 阅读 kubelet 凭据提供程序 API 参考 (v1)。
16 - 配置 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
接下来
集群管理员参考
- 为命名空间配置默认的内存请求和限制
- 为命名空间配置默认的 CPU 请求和限制
- 为命名空间配置内存的最小和最大限制
- 为命名空间配置 CPU 的最小和最大限制
- 为命名空间配置 CPU 和内存配额
- 为命名空间配置 Pod 配额
应用开发者参考
17 - 控制节点上的 CPU 管理策略
Kubernetes v1.26 [stable]
按照设计,Kubernetes 对 Pod 执行相关的很多方面进行了抽象,使得用户不必关心。 然而,为了正常运行,有些工作负载要求在延迟和/或性能方面有更强的保证。 为此,kubelet 提供方法来实现更复杂的负载放置策略,同时保持抽象,避免显式的放置指令。
有关资源管理的详细信息, 请参阅 Pod 和容器的资源管理文档。
有关 kubelet 如何实现资源管理的详细信息, 请参阅节点资源管理文档。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
你的 Kubernetes 服务器版本必须不低于版本 v1.26. 要获知版本信息,请输入kubectl version
.如果你正在运行一个旧版本的 Kubernetes,请参阅与该版本对应的文档。
配置 CPU 管理策略
默认情况下,kubelet 使用 CFS 配额 来执行 Pod 的 CPU 约束。 当节点上运行了很多 CPU 密集的 Pod 时,工作负载可能会迁移到不同的 CPU 核, 这取决于调度时 Pod 是否被扼制,以及哪些 CPU 核是可用的。 许多工作负载对这种迁移不敏感,因此无需任何干预即可正常工作。
然而,有些工作负载的性能明显地受到 CPU 缓存亲和性以及调度延迟的影响。 对此,kubelet 提供了可选的 CPU 管理策略,来确定节点上的一些分配偏好。
Windows 支持
Kubernetes v1.32 [alpha]
(enabled by default: false)可以通过使用 "WindowsCPUAndMemoryAffinity" 特性门控在 Windows 上启用 CPU 管理器支持。 这个能力还需要容器运行时的支持。
配置
CPU 管理策略通过 kubelet 参数 --cpu-manager-policy
或 KubeletConfiguration
中的 cpuManagerPolicy
字段来指定。
支持两种策略:
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 质量(默认可见)。
这些组分别由 CPUManagerPolicyAlphaOptions
和 CPUManagerPolicyBetaOptions
特性门控来管控。
不同于 Kubernetes 标准,这里是由这些特性门控来管控选项组,因为为每个单独选项都添加一个特性门控过于繁琐。
更改 CPU 管理器策略
由于 CPU 管理器策略只能在 kubelet 生成新 Pod 时应用,所以简单地从 "none" 更改为 "static" 将不会对现有的 Pod 起作用。 因此,为了正确更改节点上的 CPU 管理器策略,请执行以下步骤:
- 腾空节点。
- 停止 kubelet。
- 删除旧的 CPU 管理器状态文件。该文件的路径默认为
/var/lib/kubelet/cpu_manager_state
。 这将清除 CPUManager 维护的状态,以便新策略设置的 cpu-sets 不会与之冲突。 - 编辑 kubelet 配置以将 CPU 管理器策略更改为所需的值。
- 启动 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
说明:
如果节点上的在线 CPU 集发生变化,则必须腾空该节点,并通过删除 kubelet
根目录中的状态文件 cpu_manager_state
来手动重置 CPU 管理器。
none
策略配置
该策略没有额外的配置项。
static
策略配置
此策略管理一个 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 升序从初始共享池获取的。
共享池是 BestEffort
和 Burstable
Pod 运行的 CPU 集合。
Guaranteed
Pod 中的容器,如果声明了非整数值的 CPU requests
,也将运行在共享池的 CPU 上。
只有 Guaranteed
Pod 中,指定了整数型 CPU requests
的容器,才会被分配独占 CPU 资源。
说明:
当启用 static 策略时,要求使用 --kube-reserved
和/或 --system-reserved
或
--reserved-cpus
来保证预留的 CPU 值大于零。
这是因为零预留 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 或更高版本)distribute-cpus-across-cores
(Alpha,默认隐藏) (1.31 或更高版本)strict-cpu-reservation
(Alpha,默认隐藏) (1.32 或更高版本)prefer-align-cpus-by-uncorecache
(Alpha, 默认隐藏) (1.32 或更高版本)
可以通过将 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-only
和 distribute-cpus-across-numa
策略选项中。
可以通过将 distribute-cpus-across-cores=true
添加到 CPUManager
策略选项中来启用 distribute-cpus-across-cores
选项。
当前,该选项不能与 full-pcpus-only
或 distribute-cpus-across-numa
策略选项同时使用。
可以通过将 strict-cpu-reservation=true
添加到 CPUManager 策略选项中,
然后删除 /var/lib/kubelet/cpu_manager_state
文件并重新启动 kubelet
来启用 strict-cpu-reservation
选项。
可以通过将 prefer-align-cpus-by-uncorecache
添加到 CPUManager
策略选项来启用
prefer-align-cpus-by-uncorecache
选项。
如果使用不兼容的选项,kubelet 将无法启动,并在日志中解释所出现的错误。
有关你可以配置的各个选项的行为的模式详细信息,请参阅节点资源管理文档。
18 - 控制节点上的拓扑管理策略
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 执行对齐操作。
- 针对建议提供者所提供的拓扑建议,对请求的资源进行对齐。
如果满足这些条件,则拓扑管理器将对齐请求的资源。
为了定制如何进行对齐,拓扑管理器提供了两个不同的选项:scope
和 policy
。
scope
定义了你希望的资源对齐粒度,例如是在 pod
还是 container
层级上对齐。
policy
定义了对齐时实际使用的策略,例如 best-effort
、restricted
和 single-numa-node
。
可以在下文找到现今可用的各种 scopes
和 policies
的具体信息。
说明:
为了将 Pod 规约中的 CPU 资源与其他请求资源对齐,需要启用 CPU 管理器并在节点上配置适当的 CPU 管理器策略。 参见控制节点上的 CPU 管理策略。
说明:
为了将 Pod 规约中的内存(和 hugepages)资源与所请求的其他资源对齐,需要启用内存管理器, 并且在节点配置适当的内存管理器策略。 查看内存管理器文档。
拓扑管理器作用域
拓扑管理器可以在以下不同的作用域内进行资源对齐:
container
(默认)pod
在 kubelet 启动时,你可以通过在
kubelet 配置文件中设置
topologyManagerScope
来选择其中任一选项。
container
作用域
默认使用的是 container
作用域。
你也可以在 kubelet 配置文件中明确将
topologyManagerScope
设置为 container
。
在该作用域内,拓扑管理器依次进行一系列的资源对齐, 也就是,对(Pod 中的)每一个容器计算单独的对齐。 换句话说,在该特定的作用域内,没有根据特定的 NUMA 节点集来把容器分组的概念。 实际上,拓扑管理器会把单个容器任意地对齐到 NUMA 节点上。
容器分组的概念是在以下的作用域内特别实现的,也就是 pod
作用域。
pod
作用域
要选择 pod
作用域,在 kubelet 配置文件中将
topologyManagerScope
设置为 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
说明:
如果拓扑管理器配置使用 pod 作用域, 那么在策略评估一个容器时,该容器反映的是整个 Pod 的要求, 所以该 Pod 里的每个容器都会应用 相同的 拓扑对齐决策。
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)
自 Kubernetes 1.28 起,prefer-closest-numa-nodes
选项进入 Beta 阶段。
在 Kubernetes 1.32 中,只要启用了
TopologyManagerPolicyOptions
和 TopologyManagerPolicyBetaOptions
特性门控,此策略选项默认可见。
拓扑管理器默认不会感知 NUMA 距离,并且在做出 Pod 准入决策时不会考虑这些距离。 这种限制出现在多插槽以及单插槽多 NUMA 系统中,如果拓扑管理器决定将资源对齐到不相邻的 NUMA 节点上, 可能导致执行延迟敏感和高吞吐的应用出现明显的性能下降。
如果你指定 prefer-closest-numa-nodes
策略选项,则在做出准入决策时 best-effort
和 restricted
策略将偏向于彼此之间距离较短的一组 NUMA 节点。
你可以通过将 prefer-closest-numa-nodes=true
添加到拓扑管理器策略选项来启用此选项。
默认情况下,如果没有此选项,拓扑管理器会在单个 NUMA 节点或(在需要多个 NUMA 节点时)最小数量的 NUMA 节点上对齐资源。
max-allowable-numa-nodes
(Beta)
自 Kubernetes 1.31 起,max-allowable-numa-nodes
选项进入 Beta 阶段。
在 Kubernetes 1.32 中,只要启用了
TopologyManagerPolicyOptions
和 TopologyManagerPolicyBetaOptions
特性门控,此策略选项默认可见。
Pod 被准入的时间与物理机上的 NUMA 节点数量相关。 默认情况下,Kubernetes 不会在检测到有 8 个以上 NUMA 节点的任何(Kubernetes)节点上运行启用拓扑管理器的 kubelet。
说明:
如果你选择 max-allowable-numa-nodes
策略选项,则可以允许在有 8 个以上 NUMA 节点的节点上启用拓扑管理器。
Kubernetes 项目对在有 8 个以上 NUMA 节点的(Kubernetes)节点上使用拓扑管理器的影响只有有限的数据。
由于缺少数据,所以不推荐在 Kubernetes 1.32 上使用此策略选项,你需自行承担风险。
你可以通过将 max-allowable-numa-nodes=true
添加到拓扑管理器策略选项来启用此选项。
设置 max-allowable-numa-nodes
的值本身不会影响 Pod 准入的延时,
但将 Pod 绑定到有多个 NUMA 节点的(Kubernetes)节点确实会产生影响。
Kubernetes 后续潜在的改进可能会提高 Pod 准入性能,并降低随着 NUMA 节点数量增加而产生的高延迟。
Pod 与拓扑管理器策略的交互
考虑以下 Pod 清单中的容器:
spec:
containers:
- name: nginx
image: nginx
该 Pod 以 BestEffort
QoS 类运行,因为没有指定资源 requests
或 limits
。
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 计算最佳提示并存储该信息,并且供 提示提供程序在进行资源分配时使用。
已知的局限性
- 拓扑管理器所能处理的最大 NUMA 节点个数是 8。若 NUMA 节点数超过 8,
枚举可能的 NUMA 亲和性并为之生成提示时会发生状态爆炸。
更多选项参见
max-allowable-numa-nodes
(Beta)。 - 调度器无法感知拓扑,所以有可能一个 Pod 被调度到一个节点之后,会因为拓扑管理器的缘故在该节点上启动失败。
19 - 自定义 DNS 服务
本页说明如何配置 DNS Pod,以及定制集群中 DNS 解析过程。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
你的集群必须运行 CoreDNS 插件。
你的 Kubernetes 服务器版本必须不低于版本 v1.12.
要获知版本信息,请输入 kubectl version
.
介绍
DNS 是使用 插件管理器 集群插件自动启动的 Kubernetes 内置服务。
说明:
CoreDNS 服务在其 metadata.name
字段被命名为 kube-dns
。
这是为了能够与依靠传统 kube-dns
服务名称来解析集群内部地址的工作负载具有更好的互操作性。
使用 kube-dns
作为服务名称可以抽离共有名称之后运行的是哪个 DNS 提供程序这一实现细节。
如果你在使用 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
}
说明:
CoreDNS 不支持 FQDN 作为存根域和域名服务器(例如 "ns.foo.com")。 转换期间,CoreDNS 配置中将忽略所有的 FQDN 域名服务器。
接下来
20 - 调试 DNS 问题
这篇文章提供了一些关于 DNS 问题诊断的方法。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
你的集群必须使用了 CoreDNS 插件
或者其前身,kube-dns
。
kubectl version
.创建一个简单的 Pod 作为测试环境
apiVersion: v1
kind: Pod
metadata:
name: dnsutils
namespace: default
spec:
containers:
- name: dnsutils
image: registry.k8s.io/e2e-test-images/agnhost:2.39
imagePullPolicy: IfNotPresent
restartPolicy: Always
说明:
此示例在 default
名字空间创建 Pod。
服务的 DNS 名字解析取决于 Pod 的名字空间。
详细信息请查阅 Pod 与 Service 的 DNS。
使用上面的清单来创建一个 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 和 kube-dns 部署而言,标签 k8s-app
的值都应该是 kube-dns
。
如果你发现没有 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
...
说明:
不管是 CoreDNS 还是 kube-dns,这个服务的名字都会是 kube-dns
。
如果你已经创建了 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 被保存在一个叫 coredns
的
ConfigMap 里,使用下列命令来编辑它:
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 必须能够列出 service 和 endpoint 相关的资源来正确解析服务名称。
示例错误消息:
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 或更高版本。
接下来
21 - 声明网络策略
本文可以帮助你开始使用 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 子域名.
说明:
NetworkPolicy 中包含选择策略所适用的 Pods 集合的podSelector
。
你可以看到上面的策略选择的是带有标签 app=nginx
的 Pods。
此标签是被自动添加到 nginx
Deployment 中的 Pod 上的。
如果 podSelector
为空,则意味着选择的是名字空间中的所有 Pods。为服务指定策略
使用 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
22 - 开发云控制器管理器
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)云控制器管理器:
- 使用满足
cloudprovider.Interface
接口的实现来创建一个 Go 语言包。 - 使用来自 Kubernetes 核心代码库的
cloud-controller-manager 中的
main.go
作为main.go
的模板。如上所述,唯一的区别应该是将导入的云包不同。 - 在
main.go
中导入你的云包,确保你的包有一个init
块来运行cloudprovider.RegisterCloudProvider
。
很多云驱动都将其控制器管理器代码以开源代码的形式公开。 如果你在开发一个新的 cloud-controller-manager,你可以选择某个树外(Out-of-Tree) 云控制器管理器作为出发点。
树内(In Tree)
对于树内(In-Tree)驱动,你可以将树内云控制器管理器作为集群中的 DaemonSet 来运行。 有关详细信息,请参阅云控制器管理器管理。
23 - 启用/禁用 Kubernetes API
本页展示怎么用集群的 控制平面. 启用/禁用 API 版本。
通过 API 服务器的命令行参数 --runtime-config=api/<version>
,
可以开启/关闭某个指定的 API 版本。
此参数的值是一个逗号分隔的 API 版本列表。
此列表中,后面的值可以覆盖前面的值。
命令行参数 runtime-config
支持两个特殊的值(keys):
api/all
:指所有已知的 APIapi/legacy
:指过时的 API。过时的 API 就是明确地 弃用 的 API。
例如:为了停用除去 v1 版本之外的全部其他 API 版本,
就用参数 --runtime-config=api/all=false,api/v1=true
启动 kube-apiserver
。
接下来
阅读完整的文档,
以了解 kube-apiserver
组件。
24 - 静态加密机密数据
Kubernetes 中允许允许用户编辑的持久 API 资源数据的所有 API 都支持静态加密。 例如,你可以启用静态加密 Secret。 此静态加密是对 etcd 集群或运行 kube-apiserver 的主机上的文件系统的任何系统级加密的补充。
本页展示如何启用和配置静态 API 数据加密。
说明:
此任务涵盖使用 Kubernetes API 存储的资源数据的加密。 例如,你可以加密 Secret 对象,包括它们包含的键值数据。
如果要加密安装到容器中的文件系统中的数据,则需要:
- 使用提供加密 volumes 的存储集成
- 在你自己的应用程序中加密数据
准备开始
你必须拥有一个 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
的提供程序作为加密提供程序列表中的第一个,
则你已启用静态加密。然而此项检查并未告知你先前向加密存储的迁移是否成功。如果你不确定,
请参阅确保所有相关数据都已加密。
注意:
重要: 对于高可用配置(有两个或多个控制平面节点),加密配置文件必须相同!
否则,kube-apiserver
组件无法解密存储在 etcd 中的数据。
理解静态数据加密
---
#
# 注意:这是一个示例配置。请勿将其用于你自己的集群!
#
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 或其他资源)名称
(resource
或 resource.group
)的数组。
如果自定义资源被添加到 EncryptionConfiguration
并且集群版本为 1.26 或更高版本,
则 EncryptionConfiguration
中提到的任何新创建的自定义资源都将被加密。
在该版本之前存在于 etcd 中的任何自定义资源和配置不会被加密,直到它们被下一次写入到存储为止。
这与内置资源的行为相同。请参阅确保所有 Secret 都已加密一节。
providers
数组是可能的加密提供程序的有序列表,用于你所列出的 API。
每个提供程序支持多个密钥 - 解密时会按顺序尝试这些密钥,
如果这是第一个提供程序,其第一个密钥将被用于加密。
每个条目只能指定一个提供程序类型(可以是 identity
或 aescbc
,但不能在同一个项目中同时指定二者)。
列表中的第一个提供程序用于加密写入存储的资源。
当从存储器读取资源时,与存储的数据匹配的所有提供程序将按顺序尝试解密数据。
如果由于格式或密钥不匹配而导致没有提供程序能够读取存储的数据,则会返回一个错误,以防止客户端访问该资源。
EncryptionConfiguration
支持使用通配符指定应加密的资源。
使用 “*.<group>
” 加密 group 内的所有资源(例如以上例子中的 “*.apps
”)或使用
“*.*
” 加密所有资源。“*.
” 可用于加密核心组中的所有资源。“*.*
”
将加密所有资源,甚至包括 API 服务器启动之后添加的自定义资源。
说明:
不允许在同一资源列表或跨多个条目中使用相互重疊的通配符,因为这样一来配置的一部分将无法生效。
resources
列表的处理顺序和优先级由配置中列出的顺序决定。
如果你有一个涵盖资源(resource)的通配符,并且想要过滤掉静态加密的特定类型资源,
则可以通过添加一个单独的 resources
数组项来实现此目的,
其中包含要豁免的资源的名称,还可以在其后跟一个 providers
数组项来指定 identity
提供商。
你可以将此数组项添加到列表中,以便它早于你指定加密的配置(不是 identity
的提供商)出现。
例如,如果启用了 '*.*
',并且你想要选择不加密 Event 和 ConfigMap,
请在 resources
中靠前的位置添加一个新的条目,后跟带有 identity
的 providers 数组项作为提供程序。较为特定的条目必须位于通配符条目之前。
新项目看起来类似于:
...
- resources:
- configmaps. # 特定于来自核心 API 组的资源,因为结尾是 “.”
- events
providers:
- identity: {}
# 然后是资源中的其他条目
确保新项列在资源数组中的通配符 “*.*
” 项之前,使新项优先。
有关 EncryptionConfiguration
结构体的更多详细信息,
请参阅加密配置 API。
注意:
如果通过加密配置无法读取资源(因为密钥已更改),唯一的方法是直接从底层 etcd 中删除该密钥。 任何尝试读取资源的调用将会失败,直到它被删除或提供有效的解密密钥。
可用的提供程序
在为集群的 Kubernetes API 数据配置静态加密之前,你需要选择要使用的提供程序。
下表描述了每个可用的提供程序:
名称 | 加密类型 | 强度 | 速度 | 密钥长度 |
---|---|---|---|---|
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,因此这些步骤还假设你需要生成加密密钥。 如果你已有加密密钥,请跳至编写加密配置文件。
注意:
与不加密相比,将原始加密密钥存储在 EncryptionConfig 中只能适度改善你的安全状况。
为了获得额外的保密性,请考虑使用 kms
提供程序,因为这依赖于 Kubernetes
集群外部保存的密钥。kms
的实现可以与硬件安全模块或由云提供商管理的加密服务配合使用。
要了解如何使用 KMS 设置静态加密,请参阅使用 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 加密。
编辑加密配置文件
注意:
加密配置文件可能包含可以解密 etcd 中内容的密钥。 如果此配置文件包含任何密钥信息,你必须在所有控制平面主机上合理限制权限, 以便只有运行 kube-apiserver 的用户可以读取此配置。
创建一个新的加密配置文件。其内容应类似于:
---
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。以下是这个操作的示例:
将新的加密配置文件保存到控制平面节点上的
/etc/kubernetes/enc/enc.yaml
。编辑
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 # 增加这一行 ...
- 重启你的 API 服务器。
注意:
你的配置文件包含可以解密 etcd 内容的密钥,因此你必须正确限制控制平面节点的访问权限,
以便只有能运行 kube-apiserver
的用户才能读取它。
你现在已经为一个控制平面主机进行了加密。典型的 Kubernetes 集群有多个控制平面主机,因此需要做的事情更多。
重新配置其他控制平面主机
如果你的集群中有多个 API 服务器,应轮流将更改部署到每个 API 服务器。
注意:
对于具有两个或更多控制平面节点的集群配置,每个控制平面节点的加密配置应该是相同的。
如果控制平面节点间的加密驱动配置不一致,这种差异可能导致 kube-apiserver 无法解密数据。
你在计划更新集群的加密配置时,请确保控制平面中的 API 服务器在任何时候都能解密存储的数据(即使是在更改逐步实施的过程中也是如此)。
确保在每个控制平面主机上使用相同的加密配置。
验证数据已被加密
数据在写入 etcd 时会被加密。重新启动你的 kube-apiserver
后,任何新创建或更新的 Secret
或在 EncryptionConfiguration
中配置的其他资源类别都应在存储时被加密。
如果想要检查,你可以使用 etcdctl
命令行程序来检索你的 Secret 数据内容。
以下示例演示了如何对加密 Secret API 进行检查。
创建一个新的 Secret,名称为
secret1
,命名空间为default
:kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
使用
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
验证存储的密钥前缀是否为
k8s:enc:aescbc:v1:
,这表明aescbc
provider 已加密结果数据。 确认etcd
中显示的密钥名称和上述EncryptionConfiguration
中指定的密钥名称一致。 在此例中,你可以看到在etcd
和EncryptionConfiguration
中使用了名为key1
的加密密钥。通过 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,以便应用服务端加密。
说明:
如果由于冲突写入而发生错误,请重试该命令。 多次运行此命令是安全的。
对于较大的集群,你可能希望通过命名空间或更新脚本来对 Secret 进行划分。
防止纯文本检索
如果你想确保对特定 API 类型的唯一访问是使用加密完成的,你可以移除 API 服务器以明文形式读取该 API 的支持数据的能力。
警告:
此更改可防止 API 服务器检索标记为静态加密但实际上以明文形式存储的资源。
当你为某个 API 配置静态加密时(例如:API 种类 Secret
,代表核心 API 组中的 secrets
资源),
你必须确保该集群中的所有这些资源确实被静态加密,
在后续步骤开始之前请检查此项。
一旦集群中的所有 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
进程正在运行的高可用环境中。
- 生成一个新密钥并将其添加为所有控制平面节点上当前提供程序的第二个密钥条目
- 重新启动所有
kube-apiserver
进程以确保每台服务器都可以使用新密钥加密任何数据 - 对新的加密密钥进行安全备份。如果你丢失了此密钥的所有副本,则需要删除用已丢失的密钥加密的所有资源, 并且在静态加密被破坏期间,工作负载可能无法按预期运行。
- 将新密钥设置为
keys
数组中的第一个条目,以便将其用于新编写的静态加密 - 重新启动所有
kube-apiserver
进程,以确保每个控制平面主机现在使用新密钥进行加密 - 作为特权用户,运行
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
以用新密钥加密所有现有的 Secret - 将所有现有 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 服务器。
该功能启用后,每分钟会轮询文件变化以监测修改情况。
apiserver_encryption_config_controller_automatic_reload_last_timestamp_seconds
指标用于标识新配置生效的时间。
这种设置可以在不重启 API 服务器的情况下轮换加密密钥。
接下来
- 进一步学习解密已静态加密的数据。
- 进一步学习 EncryptionConfiguration 配置 API(v1)。
25 - 解密已静态加密的机密数据
Kubernetes 中允许允许你写入持久性 API 资源数据的所有 API 都支持静态加密。 例如,你可以为 Secret 启用静态加密。 此静态加密是对 etcd 集群或运行 kube-apiserver 的主机上的文件系统的所有系统级加密的补充。
本文介绍如何停止静态加密 API 数据,以便 API 数据以未加密的形式存储。 你可能希望这样做以提高性能;但通常情况下,如果加密某些数据是个好主意,那么继续加密这些数据也是一个好主意。
说明:
此任务涵盖使用 Kubernetes API 存储的资源数据的加密。例如,你可以加密 Secret 对象,包括它们所包含的键值数据。
如果要加密安装到容器中的文件系统中的数据,则需要:
- 使用提供存储卷加密的存储集成方案
- 在你自己的应用中加密数据
准备开始
你必须拥有一个 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 服务器部署这些更改。
确保在每个控制平面主机上使用相同的加密配置。
接下来
26 - 关键插件 Pod 的调度保证
Kubernetes 核心组件(如 API 服务器、调度器、控制器管理器)在控制平面节点上运行。 但是插件必须在常规集群节点上运行。 其中一些插件对于功能完备的集群至关重要,例如 Heapster、DNS 和 UI。 如果关键插件被逐出(手动或作为升级等其他操作的副作用)或者变成挂起状态,集群可能会停止正常工作。 关键插件进入挂起状态的例子有:集群利用率过高;被逐出的关键插件 Pod 释放了空间,但该空间被之前悬决的 Pod 占用;由于其它原因导致节点上可用资源的总量发生变化。
注意,把某个 Pod 标记为关键 Pod 并不意味着完全避免该 Pod 被逐出;它只能防止该 Pod 变成永久不可用。 被标记为关键性的静态 Pod 不会被逐出。但是,被标记为关键性的非静态 Pod 总是会被重新调度。
标记关键 Pod
要将 Pod 标记为关键性(critical),设置 Pod 的 priorityClassName 为 system-cluster-critical
或者 system-node-critical
。
system-node-critical
是最高级别的可用性优先级,甚至比 system-cluster-critical
更高。
27 - 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/8
、172.16.0.0/12
和 192.168.0.0/16
。
默认情况下,代理还将链路本地地址(169.254.0.0/16)视为非伪装 CIDR。
代理程序配置为每隔 60 秒从 /etc/config/ip-masq-agent 重新加载其配置,
这也是可修改的。
代理配置文件必须使用 YAML 或 JSON 语法编写,并且可能包含三个可选值:
nonMasqueradeCIDRs
: CIDR 表示法中的字符串列表,用于指定不需伪装的地址范围。
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。
说明:
重要的是,该文件之所以被称为 config,是因为默认情况下,该文件将被用作
ip-masq-agent
查找的主键:
nonMasqueradeCIDRs:
- 10.0.0.0/8
resyncInterval: 60s
运行以下命令将 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
28 - 限制存储使用量
此示例演示如何限制一个名字空间中的存储使用量。
演示中用到了以下资源:ResourceQuota、 LimitRange 和 PersistentVolumeClaim。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
要获知版本信息,请输入kubectl version
.
场景:限制存储使用量
集群管理员代表用户群操作集群,该管理员希望控制单个名字空间可以消耗多少存储空间以控制成本。
该管理员想要限制:
- 名字空间中持久卷申领(persistent volume claims)的数量
- 每个申领(claim)可以请求的存储量
- 名字空间可以具有的累计存储量
使用 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。
使用 ResourceQuota 限制 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"
小结
限制范围对象可以用来设置可请求的存储量上限,而资源配额对象则可以通过申领计数和 累计存储容量有效地限制名字空间耗用的存储量。 这两种机制使得集群管理员能够规划其集群存储预算而不会发生任一项目超量分配的风险。
29 - 迁移多副本的控制面以使用云控制器管理器
一个 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-manager
或 cloud-controller-manager
上设置
--enable-leader-migration
来启用。
领导者迁移仅在升级期间适用,并且在升级完成后可以安全地禁用或保持启用状态。
本指南将引导你手动将控制平面从内置的云驱动的 kube-controller-manager
升级为
同时运行 kube-controller-manager
和 cloud-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-provider
和 k8s.io/controller-manager
,
则可以进行领导者迁移。
但是,对 v0.22.0 以下的版本,领导者迁移是一项 Alpha 阶段功能,需要在 cloud-controller-manager
中启用特性门控 ControllerManagerLeaderMigration
。
本指南假定每个控制平面节点的 kubelet 以静态 Pod 的形式启动 kube-controller-manager
和 cloud-controller-manager
,静态 Pod 的定义在清单文件中。
如果组件以其他设置运行,请相应地调整这里的步骤。
关于鉴权,本指南假定集群使用 RBAC。如果其他鉴权模式授予 kube-controller-manager
和 cloud-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
运行 route
、service
和
cloud-node-lifecycle
。以下示例配置显示的是这种分配。
领导者迁移可以不指定配置的情况下启用。请参阅默认配置 以获取更多详细信息。
kind: LeaderMigrationConfiguration
apiVersion: controllermanager.config.k8s.io/v1
leaderName: cloud-provider-extraction-migration
resourceLock: leases
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
resourceLock: leases
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 中,如何将控制器分配给不同管理器的预期分配状态可以由新的配置文件表示,
如下所示。请注意,各个 controllerLeaders
的 component
字段从 kube-controller-manager
更改为 cloud-controller-manager
。
或者,使用上面提到的通配符版本,它具有相同的效果。
kind: LeaderMigrationConfiguration
apiVersion: controllermanager.config.k8s.io/v1
leaderName: cloud-provider-extraction-migration
resourceLock: leases
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.conf
到 cloud-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-manager
和 cloud-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-manager
和 cloud-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-manager
和 cloud-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
resourceLock: leases
controllerLeaders:
- name: route
component: *
- name: service
component: *
- name: cloud-node-lifecycle
component: *
- name: nodeipam
- component: *
接下来
- 阅读领导者迁移控制器管理器 改进建议提案。
30 - 名字空间演练
Kubernetes 名字空间 有助于不同的项目、团队或客户去共享 Kubernetes 集群。
名字空间通过以下方式实现这点:
- 为名字设置作用域.
- 为集群中的部分资源关联鉴权和策略的机制。
使用多个名字空间是可选的。
此示例演示了如何使用 Kubernetes 名字空间细分集群。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
要获知版本信息,请输入kubectl version
.环境准备
此示例作如下假设:
- 你已拥有一个配置好的 Kubernetes 集群。
- 你已对 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 集群划分为两个名字空间:development
和 production
。
让我们创建两个新的名字空间来保存我们的工作。
文件 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 中的策略支持的发展,我们将扩展此场景,以展示如何为每个名字空间提供不同的授权规则。
31 - 操作 Kubernetes 中的 etcd 集群
etcd 是 一致且高可用的键值存储,用作 Kubernetes 所有集群数据的后台数据库。
如果你的 Kubernetes 集群使用 etcd 作为其后台数据库, 请确保你针对这些数据有一份 备份计划。
你可以在官方文档中找到有关 etcd 的深入知识。
准备开始
你在按照本页所述的步骤部署、管理、备份或恢复 etcd 之前, 你需要先理解运行 etcd 集群的一般期望。更多细节参阅 etcd 文档。
关键要点包括:
在生产环境中运行的 etcd 最低推荐版本为
3.4.22+
和3.5.6+
。etcd 是一个基于主节点(Leader-Based)的分布式系统。确保主节点定期向所有从节点发送心跳,以保持集群稳定。
你运行的 etcd 集群成员个数应为奇数。
确保不发生资源不足。
集群的性能和稳定性对网络和磁盘 I/O 非常敏感。任何资源匮乏都会导致心跳超时, 从而导致集群的不稳定。不稳定的情况表明没有选出任何主节点。 在这种情况下,集群不能对其当前状态进行任何更改,这意味着不能调度新的 Pod。
etcd 资源需求
使用有限的资源运行 etcd 只适合测试目的。在生产环境中部署 etcd,你需要有先进的硬件配置。 在生产中部署 etcd 之前,请查阅所需资源参考文档。
保持 etcd 集群的稳定对 Kubernetes 集群的稳定性至关重要。 因此,请在专用机器或隔离环境上运行 etcd 集群, 以满足所需资源需求。
工具
根据你想要达成的具体结果,你将需要安装 etcdctl
工具或 etcdutl
工具(可能两个工具都需要)。
了解 etcdctl 和 etcdutl
etcdctl
和 etcdutl
是用于与 etcd 集群交互的命令行工具,但它们有不同的用途:
etcdctl
:这是通过网络与 etcd 交互的主要命令行客户端。 它用于日常操作,比如管理键值对、管理集群、检查健康状态等。
etcdutl
:这是一个被设计用来直接操作 etcd 数据文件的管理工具, 包括跨 etcd 版本迁移数据、数据库碎片整理、恢复快照和验证数据一致性等操作。 对于网络操作,你应使用etcdctl
。
有关 etcdutl
细节,请参阅 etcd 恢复文档。
启动 etcd 集群
本节介绍如何启动单节点和多节点 etcd 集群。
本指南假定 etcd
已经安装好了。
单节点 etcd 集群
只为测试目的使用单节点 etcd 集群。
运行以下命令:
etcd --listen-client-urls=http://$PRIVATE_IP:2379 \ --advertise-client-urls=http://$PRIVATE_IP:2379
使用参数
--etcd-servers=$PRIVATE_IP:2379
启动 Kubernetes API 服务器。确保将
PRIVATE_IP
设置为 etcd 客户端 IP。
多节点 etcd 集群
出于耐用性和高可用性考量,在生产环境中应以多节点集群的方式运行 etcd,并且定期备份。 建议在生产环境中使用五个成员的集群。 有关该内容的更多信息,请参阅常见问题文档。
由于你正在使用 Kubernetes,你可以选择在一个或多个 Pod 内以容器形式运行 etcd。
kubeadm
工具默认会安装 etcd 的静态 Pod,
或者你可以部署一个独立的集群并指示
kubeadm 将该 etcd 集群用作控制平面的后端存储。
你可以通过静态成员信息或动态发现的方式配置 etcd 集群。 有关集群的详细信息,请参阅 etcd 集群文档。
例如,考虑运行以下客户端 URL 的五个成员的 etcd 集群:http://$IP1:2379
、
http://$IP2:2379
、http://$IP3:2379
、http://$IP4:2379
和 http://$IP5:2379
。
要启动 Kubernetes API 服务器:
运行以下命令:
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
使用参数
--etcd-servers=$IP1:2379,$IP2:2379,$IP3:2379,$IP4:2379,$IP5:2379
启动 Kubernetes API 服务器。确保将
IP<n>
变量设置为客户端 IP 地址。
使用负载均衡器的多节点 etcd 集群
要运行负载均衡的 etcd 集群:
- 建立一个 etcd 集群。
- 在 etcd 集群前面配置负载均衡器。例如,让负载均衡器的地址为
$LB
。 - 使用参数
--etcd-servers=$LB:2379
启动 Kubernetes API 服务器。
加固 etcd 集群
对 etcd 的访问相当于集群中的 root 权限,因此理想情况下只有 API 服务器才能访问它。 考虑到数据的敏感性,建议只向需要访问 etcd 集群的节点授予权限。
想要确保 etcd 的安全,可以设置防火墙规则或使用 etcd 提供的安全特性,这些安全特性依赖于 x509 公钥基础设施(PKI)。
首先,通过生成密钥和证书对来建立安全的通信通道。
例如,使用密钥对 peer.key
和 peer.cert
来保护 etcd 成员之间的通信,
而 client.key
和 client.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 集群的访问
配置安全通信后,使用 TLS 身份验证来限制只有 Kubernetes API 服务器可以访问 etcd 集群。
例如,考虑由 CA etcd.ca
信任的密钥对 k8sclient.key
和 k8sclient.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
配置。
说明:
Kubernetes 没有为 etcd 提供身份验证的计划。
替换失败的 etcd 成员
etcd 集群通过容忍少数成员故障实现高可用性。 但是,要改善集群的整体健康状况,请立即替换失败的成员。当多个成员失败时,逐个替换它们。 替换失败成员需要两个步骤:删除失败成员和添加新成员。
虽然 etcd 在内部保留唯一的成员 ID,但建议为每个成员使用唯一的名称,以避免人为错误。
例如,考虑一个三成员的 etcd 集群。假定 URL 分别为:member1=http://10.0.0.1
、member2=http://10.0.0.2
和 member3=http://10.0.0.3
。当 member1
失败时,将其替换为 member4=http://10.0.0.4
。
获取失败的
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
执行以下操作之一:
- 如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信,
请从
--etcd-servers
标志中移除删除失败的成员,然后重新启动每个 Kubernetes API 服务器。 - 如果每个 Kubernetes API 服务器都与单个 etcd 成员通信, 则停止与失败的 etcd 通信的 Kubernetes API 服务器。
- 如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信,
请从
- 停止故障节点上的 etcd 服务器。除了 Kubernetes API 服务器之外的其他客户端可能会造成流向 etcd 的流量, 可以停止所有流量以防止写入数据目录。
移除失败的成员:
etcdctl member remove 8211f1d0f64f3269
显示以下信息:
Removed member 8211f1d0f64f3269 from cluster
增加新成员:
etcdctl member add member4 --peer-urls=http://10.0.0.4:2380
显示以下信息:
Member 2be1eb8f84b7f63e added to cluster ef37ad9dc622a7c4
在 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]
执行以下操作之一:
- 如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信,
则将新增的成员添加到
--etcd-servers
标志,然后重新启动每个 Kubernetes API 服务器。 - 如果每个 Kubernetes API 服务器都与单个 etcd 成员通信,请启动在第 2 步中停止的 Kubernetes API 服务器。 然后配置 Kubernetes API 服务器客户端以再次将请求路由到已停止的 Kubernetes API 服务器。 这通常可以通过配置负载均衡器来完成。
- 如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信,
则将新增的成员添加到
有关集群重新配置的详细信息,请参阅 etcd 重构文档。
备份 etcd 集群
所有 Kubernetes 对象都存储在 etcd 中。 定期备份 etcd 集群数据对于在灾难场景(例如丢失所有控制平面节点)下恢复 Kubernetes 集群非常重要。 快照文件包含所有 Kubernetes 状态和关键信息。为了保证敏感的 Kubernetes 数据的安全,可以对快照文件进行加密。
备份 etcd 集群可以通过两种方式完成:etcd 内置快照和卷快照。
内置快照
etcd 支持内置快照。快照可以从使用 etcdctl snapshot save
命令的活动成员中创建,
也可以通过从目前没有被 etcd 进程使用的 etcd 数据目录
中拷贝 member/snap/db
文件。创建快照并不会影响 etcd 成员的性能。
下面是一个示例,用于创建 $ENDPOINT
所提供的键空间的快照到文件 snapshot.db
:
ETCDCTL_API=3 etcdctl --endpoints $ENDPOINT snapshot save snapshot.db
验证快照:
下面的例子展示了如何使用 etcdutl
工具来验证快照:
etcdutl --write-out=table snapshot status snapshot.db
此命令应该生成类似于下例的输出:
+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| fe01cf57 | 10 | 7 | 2.1 MB |
+----------+----------+------------+------------+
说明:
自 etcd v3.5.x 起,etcdctl snapshot status
的使用已被 弃用,
并计划在 etcd v3.6 中移除。建议改用 etcdutl
。
下面的例子展示了如何使用 etcdctl
工具来验证快照:
export ETCDCTL_API=3
etcdctl --write-out=table snapshot status snapshot.db
此命令应该生成类似于下例的输出:
Deprecated: Use `etcdutl snapshot status` instead.
+----------+----------+------------+------------+
| 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-file
、cert-file
和 key-file
。
为 etcd 集群扩容
通过交换性能,对 etcd 集群扩容可以提高可用性。缩放不会提高集群性能和能力。 一般情况下不要扩大或缩小 etcd 集群的集合。不要为 etcd 集群配置任何自动缩放组。 强烈建议始终在任何官方支持的规模上运行生产 Kubernetes 集群时使用静态的五成员 etcd 集群。
合理的扩展是在需要更高可靠性的情况下,将三成员集群升级为五成员集群。 请参阅 etcd 重构文档 以了解如何将成员添加到现有集群中的信息。
恢复 etcd 集群
注意:
如果集群中正在运行任何 API 服务器,则不应尝试还原 etcd 的实例。相反,请按照以下步骤还原 etcd:
- 停止所有 API 服务器实例
- 为所有 etcd 实例恢复状态
- 重启所有 API 服务器实例
我们还建议重启所有组件(例如 kube-scheduler
、kube-controller-manager
、kubelet
),
以确保它们不会依赖一些过时的数据。请注意,实际中还原会花费一些时间。
在还原过程中,关键组件将丢失领导锁并自行重启。
etcd 支持从 major.minor 或其他不同 patch 版本的 etcd 进程中获取的快照进行恢复。 还原操作用于恢复失败的集群的数据。
在启动还原操作之前,必须有一个快照文件。它可以是来自以前备份操作的快照文件, 也可以是来自剩余数据目录的快照文件。
在使用 etcdutl
恢复集群时,使用 --data-dir
选项来指定集群应被恢复到哪个文件夹。
etcdutl --data-dir <data-dir-location> snapshot restore snapshot.db
其中 <data-dir-location>
是将在恢复过程中创建的目录。
说明:
自 etcd v3.5.x 起,使用 etcdctl
进行恢复的功能已被 弃用,并计划在 etcd v3.6 中移除。
建议改用 etcdutl
。
下面的例子展示了如何使用 etcdctl
工具执行恢复操作:
export ETCDCTL_API=3
etcdctl --data-dir <data-dir-location> snapshot restore snapshot.db
如果 <data-dir-location>
与之前的文件夹相同,请先删除此文件夹并停止 etcd 进程,再恢复集群。
否则,在恢复后更改 etcd 配置并重启 etcd 进程将使用新的数据目录:
首先将 /etc/kubernetes/manifests/etcd.yaml
中 name: etcd-data
对应条目的
volumes.hostPath.path
改为 <data-dir-location>
,
然后执行 kubectl -n kube-system delete pod <name-of-etcd-pod>
或 systemctl restart kubelet.service
(或两段命令都执行)。
在恢复集群时,使用 --data-dir
选项来指定集群应被恢复到哪个文件夹。
etcdutl --data-dir <data-dir-location> snapshot restore snapshot.db
其中 <data-dir-location>
是将在恢复过程中创建的目录。
下面示例展示了如何使用 etcdctl
工具执行恢复操作:
说明:
自 etcd v3.5.x 版本起,使用 etcdctl
进行恢复的功能已被弃用,未来的可能会在 etcd 版本中被移除。
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 维护的更多详细信息,请参阅 etcd 维护文档。
集群碎片整理
碎片整理是一种昂贵的操作,因此应尽可能少地执行此操作。 另一方面,也有必要确保任何 etcd 成员都不会超过存储配额。 Kubernetes 项目建议在执行碎片整理时, 使用诸如 etcd-defrag 之类的工具。
你还可以将碎片整理工具作为 Kubernetes CronJob 运行,以确保定期进行碎片整理。
有关详细信息,请参阅
etcd-defrag-cronjob.yaml
。
32 - 为系统守护进程预留计算资源
Kubernetes 的节点可以按照 Capacity
调度。默认情况下 pod 能够使用节点全部可用容量。
这是个问题,因为节点自己通常运行了不少驱动 OS 和 Kubernetes 的系统守护进程。
除非为这些系统守护进程留出资源,否则它们将与 Pod 争夺资源并导致节点资源短缺问题。
kubelet
公开了一个名为 'Node Allocatable' 的特性,有助于为系统守护进程预留计算资源。
Kubernetes 推荐集群管理员按照每个节点上的工作负载密度配置 'Node Allocatable'。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
你可以使用 kubelet 配置文件来配置以下 kubelet 设置。
节点可分配资源
Kubernetes 节点上的 'Allocatable' 被定义为 Pod 可用计算资源量。 调度器不会超额申请 'Allocatable'。 目前支持 'CPU'、'memory' 和 'ephemeral-storage' 这几个参数。
可分配的节点暴露为 API 中 v1.Node
对象的一部分,也是 CLI 中
kubectl describe node
的一部分。
在 kubelet
中,可以为两类系统守护进程预留资源。
启用 QoS 和 Pod 级别的 cgroups
为了恰当地在节点范围实施节点可分配约束,你必须通过 cgroupsPerQOS
设置启用新的 cgroup 层次结构。这个设置是默认启用的。
启用后,kubelet
将在其管理的 cgroup 层次结构中创建所有终端用户的 Pod。
配置 cgroup 驱动
kubelet
支持在主机上使用 cgroup 驱动操作 cgroup 层次结构。
该驱动通过 cgroupDriver
设置进行配置。
支持的参数值如下:
cgroupfs
是默认的驱动,在主机上直接操作 cgroup 文件系统以对 cgroup 沙箱进行管理。systemd
是可选的驱动,使用 init 系统支持的资源的瞬时切片管理 cgroup 沙箱。
取决于相关容器运行时的配置,操作员可能需要选择一个特定的 cgroup 驱动来保证系统正常运行。
例如,如果操作员使用 containerd
运行时提供的 systemd
cgroup 驱动时,
必须配置 kubelet
使用 systemd
cgroup 驱动。
Kube 预留值
- KubeletConfiguration 设置:
kubeReserved: {}
。 示例值{cpu: 100m, memory: 100Mi, ephemeral-storage: 1Gi, pid=1000}
- KubeletConfiguration 设置:
kubeReservedCgroup: ""
kubeReserved
用来给诸如 kubelet
、容器运行时等
Kubernetes 系统守护进程记述其资源预留值。
该配置并非用来给以 Pod 形式运行的系统守护进程预留资源。kubeReserved
通常是节点上 Pod 密度
的函数。
除了 cpu
、内存
和 ephemeral-storage
之外,pid
可用来指定为
Kubernetes 系统守护进程预留指定数量的进程 ID。
要选择性地对 Kubernetes 系统守护进程上执行 kubeReserved
保护,需要把 kubelet 的
kubeReservedCgroup
设置的值设为 kube 守护进程的父控制组,
并将 kube-reserved
添加到 enforceNodeAllocatable
。
推荐将 Kubernetes 系统守护进程放置于顶级控制组之下(例如 systemd 机器上的 runtime.slice
)。
理想情况下每个系统守护进程都应该在其自己的子控制组中运行。
请参考这个设计方案,
进一步了解关于推荐控制组层次结构的细节。
请注意,如果 kubeReservedCgroup
不存在,Kubelet 将 不会 创建它。
如果指定了一个无效的 cgroup,Kubelet 将会无法启动。就 systemd
cgroup 驱动而言,
你要为所定义的 cgroup 设置名称时要遵循特定的模式:
所设置的名字应该是你为 kubeReservedCgroup
所给的参数值加上 .slice
后缀。
系统预留值
- KubeletConfiguration 设置:
systemReserved: {}
。 示例值{cpu: 100m, memory: 100Mi, ephemeral-storage: 1Gi, pid=1000}
- KubeletConfiguration 设置:
systemReservedCgroup: ""
systemReserved
用于为诸如 sshd
、udev
等系统守护进程记述其资源预留值。
systemReserved
也应该为 kernel
预留 内存
,因为目前 kernel
使用的内存并不记在 Kubernetes 的 Pod 上。
同时还推荐为用户登录会话预留资源(systemd 体系中的 user.slice
)。
除了 cpu
、内存
和 ephemeral-storage
之外,pid
可用来指定为
Kubernetes 系统守护进程预留指定数量的进程 ID。
要想为系统守护进程上可选地实施 systemReserved
约束,请指定 kubelet 的
systemReservedCgroup
设置值为 OS 系统守护进程的父级控制组,
并将 system-reserved
添加到 enforceNodeAllocatable
。
推荐将 OS 系统守护进程放在一个顶级控制组之下(例如 systemd 机器上的
system.slice
)。
请注意,如果 systemReservedCgroup
不存在,kubelet
不会 创建它。
如果指定了无效的 cgroup,kubelet
将会失败。就 systemd
cgroup 驱动而言,
你在指定 cgroup 名字时要遵循特定的模式:
该名字应该是你为 systemReservedCgroup
参数所设置的值加上 .slice
后缀。
显式预留的 CPU 列表
Kubernetes v1.17 [stable]
KubeletConfiguration 设置:reservedSystemCPUs:
。示例值 0-3
reservedSystemCPUs
旨在为操作系统守护程序和 Kubernetes 系统守护程序预留一组明确指定编号的 CPU。
reservedSystemCPUs
适用于不打算针对 cpuset 资源为操作系统守护程序和 Kubernetes
系统守护程序定义独立的顶级 cgroups 的系统。
如果 Kubelet 没有 指定参数 kubeReservedCgroup
和 systemReservedCgroup
,
则 reservedSystemCPUs
的设置将优先于 kubeReservedCgroup
和 systemReservedCgroup
选项。
此选项是专门为电信/NFV 用例设计的,在这些用例中不受控制的中断或计时器可能会影响其工作负载性能。 你可以使用此选项为系统或 Kubernetes 守护程序以及中断或计时器显式定义 cpuset, 这样系统上的其余 CPU 可以专门用于工作负载,因不受控制的中断或计时器的影响得以降低。 要将系统守护程序、Kubernetes 守护程序和中断或计时器移动到此选项定义的显式 cpuset 上,应使用 Kubernetes 之外的其他机制。 例如:在 CentOS 系统中,可以使用 tuned 工具集来执行此操作。
驱逐阈值
KubeletConfiguration 设置:
evictionHard: {memory.available: "100Mi", nodefs.available: "10%", nodefs.inodesFree: "5%", imagefs.available: "15%"}
。
示例值: {memory.available: "<500Mi"}
节点级别的内存压力将导致系统内存不足,这将影响到整个节点及其上运行的所有 Pod。
节点可以暂时离线直到内存已经回收为止。为了防止系统内存不足(或减少系统内存不足的可能性),
kubelet 提供了资源不足管理。
驱逐操作只支持 memory
和 ephemeral-storage
。
通过 evictionHard
设置预留一些内存后,当节点上的可用内存降至预留值以下时,
kubelet
将尝试驱逐 Pod。
如果节点上不存在系统守护进程,Pod 将不能使用超过 capacity-eviction-hard
所指定的资源量。
因此,为驱逐而预留的资源对 Pod 是不可用的。
实施节点可分配约束
KubeletConfiguration 设置:enforceNodeAllocatable: [pods]
。
示例值:[pods,system-reserved,kube-reserved]
调度器将 'Allocatable' 视为 Pod 可用的 capacity
(资源容量)。
kubelet
默认对 Pod 执行 'Allocatable' 约束。
无论何时,如果所有 Pod 的总用量超过了 'Allocatable',驱逐 Pod 的措施将被执行。
有关驱逐策略的更多细节可以在节点压力驱逐页找到。
可将 KubeletConfiguration enforceNodeAllocatable
设置为 pods
值来控制这个措施。
可选地,通过在同一设置中同时指定 kube-reserved
和 system-reserved
值,
可以使 kubelet
强制实施 kubeReserved
和 systemReserved
约束。
请注意,要想执行 kubeReserved
或者 systemReserved
约束,
需要对应设置 kubeReservedCgroup
或者 systemReservedCgroup
。
一般原则
系统守护进程一般会被按照类似
Guaranteed 的 Pod
一样对待。
系统守护进程可以在与其对应的控制组中出现突发资源用量,这一行为要作为 Kubernetes 部署的一部分进行管理。
例如,kubelet
应该有它自己的控制组并和容器运行时共享 kubeReserved
资源。
不过,如果执行了 kubeReserved
约束,则 kubelet 不可出现突发负载并用光节点的所有可用资源。
在执行 systemReserved
预留策略时请加倍小心,因为它可能导致节点上的关键系统服务出现 CPU 资源短缺、
因为内存不足而被终止或者无法在节点上创建进程。
建议只有当用户详尽地描述了他们的节点以得出精确的估计值,
并且对该组中进程因内存不足而被杀死时,有足够的信心将其恢复时,
才可以强制执行 systemReserved
策略。
- 作为起步,可以先针对
pods
上执行 'Allocatable' 约束。 - 一旦用于追踪系统守护进程的监控和告警的机制到位,可尝试基于用量估计的方式执行
kubeReserved
策略。 - 随着时间推进,在绝对必要的时候可以执行
systemReserved
策略。
随着时间推进和越来越多特性被加入,kube 系统守护进程对资源的需求可能也会增加。
以后 Kubernetes 项目将尝试减少对节点系统守护进程的利用,但目前这件事的优先级并不是最高。
所以,将来的发布版本中 Allocatable
容量是有可能降低的。
示例场景
这是一个用于说明节点可分配(Node Allocatable)计算方式的示例:
- 节点拥有
32Gi
memory
、16 CPU
和100Gi
Storage
资源 kubeReserved
被设置为{cpu: 1000m, memory: 2Gi, ephemeral-storage: 1Gi}
systemReserved
被设置为{cpu: 500m, memory: 1Gi, ephemeral-storage: 1Gi}
evictionHard
被设置为{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 的资源。
当没有执行 kubeReserved
和/或 systemReserved
策略且系统守护进程使用量超过其预留时,
如果节点内存用量高于 31.5Gi 或 storage
大于 90Gi,kubelet 将会驱逐 Pod。
33 - 以非 root 用户身份运行 Kubernetes 节点组件
Kubernetes v1.22 [alpha]
这个文档描述了怎样不使用 root 特权,而是通过使用 用户命名空间 去运行 Kubernetes 节点组件(例如 kubelet、CRI、OCI、CNI)。
这种技术也叫做 rootless 模式(Rootless mode)。
说明:
这个文档描述了怎么以非 root 用户身份运行 Kubernetes 节点组件以及 Pod。 如果你只是想了解如何以非 root 身份运行 Pod,请参阅 SecurityContext。准备开始
你的 Kubernetes 服务器版本必须不低于版本 1.22.
要获知版本信息,请输入 kubectl version
.
- 启用 cgroup v2
- 在 systemd 中启用 user session
- 根据不同的 Linux 发行版,配置 sysctl 的值
- 确保你的非特权用户被列在
/etc/subuid
和/etc/subgid
文件中 - 启用
KubeletInUserNamespace
特性门控
使用 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 的注意事项。
说明:
本节是面向 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 树。
说明:
Kubernetes 需要 cgroup v2 才支持在用户命名空间运行节点组件。 cgroup v1 是不支持的。如果你在一个采用 systemd 机制的主机上使用用户命名空间的容器(例如 Rootless 模式的 Docker/Podman 或 LXC/LXD)来运行 Kubernetes,那么你已经准备就绪。
否则你必须创建一个具有 Delegate=yes
属性的 systemd 单元,来委派一个具有可写权限的 cgroup 树。
在你的节点上,systemd 必须已经配置为允许委派。更多细节请参阅 Rootless 容器文档的 cgroup v2 部分。
配置网络
节点组件的网络命名空间必须有一个非本地回路的网卡。它可以使用 slirp4netns、 VPNKit、 lxc-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
注意事项
大部分“非本地”的卷驱动(例如
nfs
和iscsi
)不能正常工作。 已知诸如local
、hostPath
、emptyDir
、configMap
、secret
和downwardAPI
这些本地卷是能正常工作的。一些 CNI 插件可能不正常工作。已知 Flannel (VXLAN) 是能正常工作的。
更多细节请参阅 rootlesscontaine.rs 站点的 Caveats and Future work 页面。
另请参见
34 - 安全地清空一个节点
本页展示了如何在确保 PodDisruptionBudget 的前提下, 安全地清空一个节点。
准备开始
此任务假定你已经满足了以下先决条件:
- 在节点清空期间,不要求应用具有高可用性
- 你已经了解了 PodDisruptionBudget 的概念, 并为需要它的应用配置了 PodDisruptionBudget。
(可选)配置干扰预算
为了确保你的负载在维护期间仍然可用,你可以配置一个 PodDisruptionBudget。 如果可用性对于正在清空的该节点上运行或可能在该节点上运行的任何应用程序很重要, 首先 配置一个 PodDisruptionBudgets 并继续遵循本指南。
建议为你的 PodDisruptionBudgets 设置 AlwaysAllow
不健康 Pod 驱逐策略,
以在节点清空期间支持驱逐异常的应用程序。
默认行为是等待应用程序的 Pod 变为 健康后,
才能进行清空操作。
使用 kubectl drain
从服务中删除一个节点
在对节点执行维护(例如内核升级、硬件维护等)之前,
可以使用 kubectl drain
从节点安全地逐出所有 Pod。
安全的驱逐过程允许 Pod 的容器体面地终止,
并确保满足指定的 PodDisruptionBudgets
。
说明:
默认情况下,kubectl drain
将忽略节点上不能杀死的特定系统 Pod;
有关更多细节,请参阅
kubectl drain 文档。
kubectl drain
的成功返回,表明所有的 Pod(除了上一段中描述的被排除的那些),
已经被安全地逐出(考虑到期望的终止宽限期和你定义的 PodDisruptionBudget)。
然后就可以安全地关闭节点,
比如关闭物理机器的电源,如果它运行在云平台上,则删除它的虚拟机。
说明:
如果存在新的、能够容忍 node.kubernetes.io/unschedulable
污点的 Pod,
那么这些 Pod 可能会被调度到你已经清空的节点上。
除了 DaemonSet 之外,请避免容忍此污点。
如果你或另一个 API 用户(绕过调度器)直接为 Pod 设置了
nodeName
字段,
则即使你已将该节点清空并标记为不可调度,Pod 仍将被绑定到这个指定的节点并在该节点上运行。
首先,确定想要清空的节点的名称。可以用以下命令列出集群中的所有节点:
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 中的各个步骤, 保护你的应用。
35 - 保护集群
本文档涉及与保护集群免受意外或恶意访问有关的主题,并对总体安全性提出建议。
准备开始
你必须拥有一个 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 提供了一组可直接使用的角色,这些角色根据客户可能希望执行的操作提供合理的责任划分。 建议你同时使用 Node 和 RBAC 两个鉴权组件,再与 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 用户运行。 类似地,希望阻止客户端应用程序从其容器中逃逸的管理员,应该应用 Baseline 或 Restricted 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 服务器可以访问的防火墙后面。
注意:
允许集群中其它组件对整个主键空间(keyspace)拥有读或写权限去访问 etcd 实例, 相当于授予这些组件集群管理员的访问权限。 对于非主控组件,强烈推荐使用不同的 etcd 实例,或者使用 etcd 的访问控制列表 来限制这些组件只能读或写主键空间的一个子集。启用审计日志
审计日志是 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 存储对象(例如 Secret
或 ConfigMap
)的数据时,API 服务器写入的是加密的对象。
这种加密意味着即使有权访问 etcd 备份数据的某些人也无法查看这些对象的内容。
在 Kubernetes 1.32 中,你也可以加密自定义资源;
针对以 CustomResourceDefinition 形式定义的扩展 API,对其执行静态加密的能力作为 v1.26
版本的一部分已添加到 Kubernetes。
接收安全更新和报告漏洞的警报
请加入 kubernetes-announce 组,这样你就能够收到有关安全公告的邮件。有关如何报告漏洞的更多信息, 请参见安全报告页面。
接下来
- 阅读安全检查清单了解有关 Kubernetes 安全指南的更多信息。
- Seccomp Node 参考文档
36 - 通过配置文件设置 kubelet 参数
准备开始
此页面中的某些步骤使用 jq
工具。如果你没有 jq
,你可以通过操作系统的软件源安装它,或者从
https://jqlang.github.io/jq/ 中获取它。
某些步骤还涉及安装 curl
,它可以通过操作系统的软件源安装。
通过保存在硬盘的配置文件设置 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 配置为以下设置:
address
:kubelet 将在192.168.0.8
IP 地址上提供服务。port
:kubelet 将在20250
端口上提供服务。serializeImagePulls
:并行拉取镜像。evictionHard
:kubelet 将在以下情况之一驱逐 Pod:- 当节点的可用内存降至 100MiB 以下时。
- 当节点主文件系统的已使用 inode 超过 95%。
- 当镜像文件系统的可用空间小于 15% 时。
- 当节点主文件系统的 inode 超过 95% 正在使用时。
说明:
在示例中,通过只更改 evictionHard 的一个参数的默认值, 其他参数的默认值将不会被继承,他们会被设置为零。如果要提供自定义值,你应该分别设置所有阈值。
imagefs
是一个可选的文件系统,容器运行时使用它来存储容器镜像和容器可写层。
启动通过配置文件配置的 kubelet 进程
说明:
如果你使用 kubeadm 初始化你的集群,在使用 kubeadm init
创建你的集群的时候请使用 kubelet-config。
更多细节请阅读使用 kubeadm 配置 kubelet
启动 kubelet 需要将 --config
参数设置为 kubelet 配置文件的路径。kubelet 将从此文件加载其配置。
请注意,命令行参数与配置文件有相同的值时,就会覆盖配置文件中的该值。 这有助于确保命令行 API 的向后兼容性。
请注意,kubelet 配置文件中的相对文件路径是相对于 kubelet 配置文件的位置解析的, 而命令行参数中的相对路径是相对于 kubelet 的当前工作目录解析的。
请注意,命令行参数和 kubelet 配置文件的某些默认值不同。
如果设置了 --config
,并且没有通过命令行指定值,则 KubeletConfiguration
版本的默认值生效。在上面的例子中,version 是 kubelet.config.k8s.io/v1beta1
。
kubelet 配置文件的插件目录
Kubernetes v1.30 [beta]
你可以为 kubelet 指定一个插件配置目录。默认情况下,kubelet
不会在任何地方查找插件配置文件 - 你必须指定路径。
例如:--config-dir=/etc/kubernetes/kubelet.conf.d
对于 Kubernetes v1.28 到 v1.29,如果你还为 kubelet
进程设置了环境变量 KUBELET_CONFIG_DROPIN_DIR_ALPHA
(该变量的值无关紧要),
则只能指定 --config-dir
。
说明:
合法的 kubelet 插件配置文件的后缀必须为 .conf
。例如 99-kubelet-address.conf
。
kubelet 通过按字母数字顺序对整个文件名进行排序来处理其配置插件目录中的文件。
例如,首先处理 00-kubelet.conf
,然后用名为 01-kubelet.conf
的文件覆盖。
这些文件可能包含部分配置,但不应无效,并且必须包含类型元数据,特别是 apiVersion
和 kind
。
仅对 kubelet 内部存储的、最终生成的配置结构执行验证。
这为管理和合并来自不同来源的 kubelet 配置提供了灵活性,同时防止了不需要的配置。
但是,请务必注意,产生的行为会根据配置字段的数据类型而有所不同。
kubelet 配置结构中不同数据类型的合并方式不同。 有关详细信息,请参阅参考文档。
kubelet 配置合并顺序
在启动时,kubelet 会合并来自以下几部分的配置:
- 在命令行中指定的特性门控(优先级最低)。
- kubelet 配置文件。
- 排序的插件配置文件。
- 不包括特性门控的命令行参数(优先级最高)。
说明:
kubelet 的配置插件目录机制类似,但与 kubeadm
工具允许 patch 配置的方式不同。
kubeadm
工具使用特定的补丁策略,
而 kubelet 配置插件文件的唯一补丁策略是 replace
。kubelet 根据字母数字对后缀进行排序来确定合并顺序,
并替换更高优先级文件中存在的每个字段。
查看 kubelet 配置
由于现在可以使用此特性将配置分布在多个文件中,因此如果有人想要检查最终启动的配置, 他们可以按照以下步骤检查 kubelet 配置:
在终端中使用
kubectl proxy
启动代理服务器。kubectl proxy
其输出如下:
Starting to serve on 127.0.0.1:8001
打开另一个终端窗口并使用
curl
来获取 kubelet 配置。 将<node-name>
替换为节点的实际名称:curl -X GET http://127.0.0.1:8001/api/v1/nodes/<node-name>/proxy/configz | jq .
{ "kubeletconfig": { "enableServer": true, "staticPodPath": "/var/run/kubernetes/static-pods", "syncFrequency": "1m0s", "fileCheckFrequency": "20s", "httpCheckFrequency": "20s", "address": "192.168.1.16", "port": 10250, "readOnlyPort": 10255, "tlsCertFile": "/var/lib/kubelet/pki/kubelet.crt", "tlsPrivateKeyFile": "/var/lib/kubelet/pki/kubelet.key", "rotateCertificates": true, "authentication": { "x509": { "clientCAFile": "/var/run/kubernetes/client-ca.crt" }, "webhook": { "enabled": true, "cacheTTL": "2m0s" }, "anonymous": { "enabled": true } }, "authorization": { "mode": "AlwaysAllow", "webhook": { "cacheAuthorizedTTL": "5m0s", "cacheUnauthorizedTTL": "30s" } }, "registryPullQPS": 5, "registryBurst": 10, "eventRecordQPS": 50, "eventBurst": 100, "enableDebuggingHandlers": true, "healthzPort": 10248, "healthzBindAddress": "127.0.0.1", "oomScoreAdj": -999, "clusterDomain": "cluster.local", "clusterDNS": [ "10.0.0.10" ], "streamingConnectionIdleTimeout": "4h0m0s", "nodeStatusUpdateFrequency": "10s", "nodeStatusReportFrequency": "5m0s", "nodeLeaseDurationSeconds": 40, "imageMinimumGCAge": "2m0s", "imageMaximumGCAge": "0s", "imageGCHighThresholdPercent": 85, "imageGCLowThresholdPercent": 80, "volumeStatsAggPeriod": "1m0s", "cgroupsPerQOS": true, "cgroupDriver": "systemd", "cpuManagerPolicy": "none", "cpuManagerReconcilePeriod": "10s", "memoryManagerPolicy": "None", "topologyManagerPolicy": "none", "topologyManagerScope": "container", "runtimeRequestTimeout": "2m0s", "hairpinMode": "promiscuous-bridge", "maxPods": 110, "podPidsLimit": -1, "resolvConf": "/run/systemd/resolve/resolv.conf", "cpuCFSQuota": true, "cpuCFSQuotaPeriod": "100ms", "nodeStatusMaxImages": 50, "maxOpenFiles": 1000000, "contentType": "application/vnd.kubernetes.protobuf", "kubeAPIQPS": 50, "kubeAPIBurst": 100, "serializeImagePulls": true, "evictionHard": { "imagefs.available": "15%", "memory.available": "100Mi", "nodefs.available": "10%", "nodefs.inodesFree": "5%" }, "evictionPressureTransitionPeriod": "1m0s", "enableControllerAttachDetach": true, "makeIPTablesUtilChains": true, "iptablesMasqueradeBit": 14, "iptablesDropBit": 15, "featureGates": { "AllAlpha": false }, "failSwapOn": false, "memorySwap": {}, "containerLogMaxSize": "10Mi", "containerLogMaxFiles": 5, "configMapAndSecretChangeDetectionStrategy": "Watch", "enforceNodeAllocatable": [ "pods" ], "volumePluginDir": "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", "logging": { "format": "text", "flushFrequency": "5s", "verbosity": 3, "options": { "json": { "infoBufferSize": "0" } } }, "enableSystemLogHandler": true, "enableSystemLogQuery": false, "shutdownGracePeriod": "0s", "shutdownGracePeriodCriticalPods": "0s", "enableProfilingHandler": true, "enableDebugFlagsHandler": true, "seccompDefault": false, "memoryThrottlingFactor": 0.9, "registerNode": true, "localStorageCapacityIsolation": true, "containerRuntimeEndpoint": "unix:///var/run/crio/crio.sock" } }
接下来
- 参阅
KubeletConfiguration
进一步学习 kubelet 的配置。 - 在参考文档中了解有关 kubelet 配置合并的更多信息。
37 - 通过名字空间共享集群
本页展示如何查看、使用和删除名字空间。 本页同时展示如何使用 Kubernetes 名字空间来划分集群。
准备开始
- 你已拥有一个配置好的 Kubernetes 集群。
- 你已对 Kubernetes 的 Pod、 Service 和 Deployment 有基本理解。
查看名字空间
列出集群中现有的名字空间:
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 参考中的名字空间。
创建名字空间
说明:
避免使用前缀 kube-
创建名字空间,因为它是为 Kubernetes 系统名字空间保留的。
新建一个名为 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 集群划分为两个名字空间:development
和 production
。
让我们创建两个新的名字空间来保存我们的工作。
使用 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 集群。
名字空间通过以下方式实现这点:
- 为名字设置作用域.
- 为集群中的部分资源关联鉴权和策略的机制。
使用多个名字空间是可选的。
每个用户社区都希望能够与其他社区隔离开展工作。 每个用户社区都有自己的:
- 资源(Pod、服务、副本控制器等等)
- 策略(谁能或不能在他们的社区里执行操作)
- 约束(该社区允许多少配额等等)
集群运营者可以为每个唯一用户社区创建名字空间。
名字空间为下列内容提供唯一的作用域:
- 命名资源(避免基本的命名冲突)
- 将管理权限委派给可信用户
- 限制社区资源消耗的能力
用例包括:
- 作为集群运营者, 我希望能在单个集群上支持多个用户社区。
- 作为集群运营者,我希望将集群分区的权限委派给这些社区中的受信任用户。
- 作为集群运营者,我希望能限定每个用户社区可使用的资源量,以限制对使用同一集群的其他用户社区的影响。
- 作为集群用户,我希望与我的用户社区相关的资源进行交互,而与其他用户社区在该集群上执行的操作无关。
理解名字空间和 DNS
当你创建服务时,Kubernetes
会创建相应的 DNS 条目。
此条目的格式为 <服务名称>.<名字空间名称>.svc.cluster.local
。
这意味着如果容器使用 <服务名称>
,它将解析为名字空间本地的服务。
这对于在多个名字空间(如开发、暂存和生产)中使用相同的配置非常有用。
如果要跨名字空间访问,则需要使用完全限定的域名(FQDN)。
接下来
38 - 升级集群
本页概述升级 Kubernetes 集群的步骤。
Kubernetes 项目建议及时升级到最新的补丁版本,并确保使用受支持的 Kubernetes 版本。 遵循这一建议有助于保障安全。
升级集群的方式取决于你最初部署它的方式、以及后续更改它的方式。
从高层规划的角度看,要执行的步骤是:
准备开始
你必须有一个集群。 本页内容涉及从 Kubernetes 1.31 升级到 Kubernetes 1.32。 如果你的集群未运行 Kubernetes 1.31, 那请参考目标 Kubernetes 版本的文档。
升级方法
kubeadm
如果你的集群是使用 kubeadm
安装工具部署而来,
那么升级集群的详细信息,请参阅升级 kubeadm 集群。
升级集群之后,要记得安装最新版本的 kubectl
。
手动部署
注意:
这些步骤不考虑网络和存储插件等第三方扩展。
你应该按照下面的操作顺序,手动更新控制平面:
- etcd (所有实例)
- kube-apiserver (所有控制平面的宿主机)
- kube-controller-manager
- kube-scheduler
- cloud controller manager (在你用到时)
现在,你应该安装最新版本的 kubectl
。
对于集群中的每个节点, 首先需要腾空节点, 然后使用一个运行了 kubelet 1.32 版本的新节点替换它; 或者升级此节点的 kubelet,并使节点恢复服务。
注意:
在升级 kubelet 之前先进行节点排空,这样可以确保 Pod 被重新准入并且容器被重新创建。 这一步骤对于解决某些安全问题或其他关键错误是非常必要的。
其他部署方式
参阅你的集群部署工具对应的文档,了解用于维护的推荐设置步骤。
升级后的任务
切换集群的存储 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 版本。
39 - 在集群中使用级联删除
本页面向你展示如何设置在你的集群执行垃圾收集 时要使用的级联删除 类型。
准备开始
你必须拥有一个 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
启动一个本地代理会话:
kubectl proxy --port=8080
使用
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" ] ...
使用后台级联删除
- 创建一个 Deployment 示例。
- 基于你的集群所运行的 Kubernetes 版本,使用
kubectl
或者 Kubernetes API 来删除 Deployment。 要获知版本信息,请输入kubectl version
.
你可以使用 kubectl
或者 Kubernetes API 来执行后台级联删除方式的对象删除操作。
Kubernetes 默认采用后台级联删除方式,如果你在运行下面的命令时不指定
--cascade
标志或者 propagationPolicy
参数时,用这种方式来删除对象。
使用 kubectl
运行下面的命令:
kubectl delete deployment nginx-deployment --cascade=background
使用 Kubernetes API
启动一个本地代理会话:
kubectl proxy --port=8080
使用
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
启动一个本地代理会话:
kubectl proxy --port=8080
使用
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
接下来
- 了解 Kubernetes 中的属主与依赖
- 了解 Kubernetes finalizers
- 了解垃圾收集.
40 - 使用 KMS 驱动进行数据加密
本页展示了如何配置密钥管理服务(Key Management Service,KMS)驱动和插件以启用 Secret 数据加密。 在 Kubernetes 1.32 中,存在两个版本的 KMS 静态加密方式。 如果可行的话,建议使用 KMS v2,因为(自 Kubernetes v1.28 起)KMS v1 已经被弃用并 (自 Kubernetes v1.29 起)默认被禁用。 KMS v2 提供了比 KMS v1 明显更好的性能特征。
注意:
本文适用于正式发布的 KMS v2 实现(以及已废弃的 v1 实现)。 如果你使用的控制平面组件早于 Kubernetes v1.29,请查看集群所运行的 Kubernetes 版本文档中的相应页面。 早期版本的 Kubernetes 在信息安全方面具有不同的行为。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
你所需要的 Kubernetes 版本取决于你已选择的 KMS API 版本。Kubernetes 推荐使用 KMS v2。
- 如果你选择了 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.32 针对 KMS 还支持v2beta1
API; Kubernetes 后续版本可能会继续支持该 Beta 版本。当一切健康时,API 服务器大约每分钟轮询一次
Status
过程调用, 而插件不健康时每 10 秒钟轮询一次。使用这些插件时要注意优化此调用,因为此调用将经受持续的负载。
加密
EncryptRequest
过程调用提供明文和一个 UID 以用于日志记录。 响应必须包括密文、使用的 KEK 的key_id
,以及可选的任意元数据,这些元数据可以 帮助 KMS 插件在未来的DecryptRequest
调用中(通过annotations
字段)进行解密。 插件必须保证所有不同的明文都会产生不同的响应(ciphertext, key_id, annotations)
。如果插件返回一个非空的
annotations
映射,则所有映射键必须是完全限定域名, 例如example.com
。annotation
的一个示例用例是{"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_id
与EncryptRequest
返回的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 服务器重启。注意:
因为你未控制使用 DEK 执行的写入次数,所以 Kubernetes 项目建议至少每 90 天轮换一次 KEK。
协议: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 驱动加密数据
为了加密数据:
使用适合于
kms
驱动的属性创建一个新的EncryptionConfiguration
文件,以加密 Secret 和 ConfigMap 等资源。 如果要加密使用 CustomResourceDefinition 定义的扩展 API,你的集群必须运行 Kubernetes v1.26 或更高版本。设置 kube-apiserver 的
--encryption-provider-config
参数指向配置文件的位置。
--encryption-provider-config-automatic-reload
布尔参数决定了磁盘内容发生变化时是否应自动 重新加载 通过--encryption-provider-config
设置的文件。这样可以在不重启 API 服务器的情况下进行密钥轮换。重启你的 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-foo.sock
cachesize: 100
timeout: 3s
- kms:
name: myKmsPluginBar
endpoint: unix:///tmp/socketfile-bar.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-foo.sock
timeout: 3s
- kms:
apiVersion: v2
name: myKmsPluginBar
endpoint: unix:///tmp/socketfile-bar.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
命令行程序获取私密数据的内容。
在默认的命名空间里创建一个名为
secret1
的 Secret:kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
用
etcdctl
命令行,从 etcd 读取出 Secret:ETCDCTL_API=3 etcdctl get /kubernetes.io/secrets/default/secret1 [...] | hexdump -C
其中
[...]
包含连接 etcd 服务器的额外参数。
- 验证对于 KMS v1,保存的 Secret 以
k8s:enc:kms:v1:
开头, 对于 KMS v2,保存的 Secret 以k8s:enc:kms:v2:
开头,这表明kms
驱动已经对结果数据加密。
验证通过 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 内容:
在配置文件中加入
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>
重启所有
kube-apiserver
进程。运行下列命令使用
kms
驱动强制重新加密所有 Secret。kubectl get secrets --all-namespaces -o json | kubectl replace -f -
接下来
如果你不想再对 Kubernetes API 中保存的数据加密, 请阅读解密已静态存储的数据。
41 - 使用 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.32,所支持的唯一的集群 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 条目,
以了解更多信息。
42 - 在 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 的本地侦听 IP 地址可以是任何地址,只要该地址不和你的集群里现有的 IP 地址发生冲突。 推荐使用本地范围内的地址,例如,IPv4 链路本地区段 '169.254.0.0/16' 内的地址, 或者 IPv6 唯一本地地址区段 'fd00::/8' 内的地址。
可以使用以下步骤启动此功能:
- 根据示例
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,
并查看该组件输出的建议信息。
43 - 在 Kubernetes 集群中使用 sysctl
Kubernetes v1.21 [stable]
本文档介绍如何通过 sysctl 接口在 Kubernetes 集群中配置和使用内核参数。
说明:
从 Kubernetes 1.23 版本开始,kubelet 支持使用 /
或 .
作为 sysctl 参数的分隔符。
从 Kubernetes 1.25 版本开始,支持为 Pod 设置 sysctl 时使用设置名字带有斜线的 sysctl。
例如,你可以使用点或者斜线作为分隔符表示相同的 sysctl 参数,以点作为分隔符表示为: kernel.shm_rmid_forced
,
或者以斜线作为分隔符表示为:kernel/shm_rmid_forced
。
更多 sysctl 参数转换方法详情请参考 Linux man-pages
sysctl.d(5)。
准备开始
说明:
sysctl
是一个 Linux 特有的命令行工具,用于配置各种内核参数,
它在非 Linux 操作系统上无法使用。
你必须拥有一个 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+);net.ipv4.tcp_rmem
(从 Kubernetes 1.32 开始,需要 kernel 4.15+);net.ipv4.tcp_wmem
(从 Kubernetes 1.32 开始,需要 kernel 4.15+)。
说明:
安全 sysctl 参数有一些例外:
net.*
sysctl 参数不允许在启用主机网络的情况下使用。net.ipv4.tcp_syncookies
sysctl 参数在 Linux 内核 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_max
和net.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.somaxconn
和 kernel.msgmax
进行设置。
在 Pod 规约中对 安全的 和 非安全的 sysctl 参数不做区分。
警告:
为了避免破坏操作系统的稳定性,请你在了解变更后果之后再修改 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 参数时可能会导致一些严重问题, 如容器的错误行为、机器资源不足或节点被完全破坏,用户需自行承担风险。
最佳实践方案是将集群中具有特殊 sysctl 设置的节点视为 有污点的,并且只调度需要使用到特殊 sysctl 设置的 Pod 到这些节点上。建议使用 Kubernetes 的污点和容忍度特性 来实现它。
设置了 非安全的 sysctl 参数的 Pod 在禁用了这两种 非安全的 sysctl 参数配置的节点上启动都会失败。 与 节点级别的 sysctl 一样, 建议开启污点和容忍度特性或 为节点配置污点以便将 Pod 调度到正确的节点之上。
44 - 使用 NUMA 感知的内存管理器
Kubernetes v1.32 [stable]
(enabled by default: true)Kubernetes 内存管理器(Memory Manager)为 Guaranteed
QoS 类
的 Pods 提供可保证的内存(及大页面)分配能力。
内存管理器使用提示生成协议来为 Pod 生成最合适的 NUMA 亲和性配置。 内存管理器将这类亲和性提示输入给中央管理器(即 Topology Manager)。 基于所给的提示和 Topology Manager(拓扑管理器)的策略设置,Pod 或者会被某节点接受,或者被该节点拒绝。
此外,内存管理器还确保 Pod 所请求的内存是从尽量少的 NUMA 节点分配而来。
内存管理器仅能用于 Linux 主机。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
你的 Kubernetes 服务器版本必须是 v1.32. 要获知版本信息,请输入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),负责为拓扑管理器提供拓扑提示,
后者根据这些拓扑提示对所请求的资源执行对齐操作。
在 Linux 上,内存管理器也会为 Pod 应用 cgroups
设置(即 cpuset.mems
)。
与 Pod 准入和部署流程相关的完整流程图在Memory Manager KEP: Design Overview,
下面也有说明。
在这个过程中,内存管理器会更新其内部存储于节点映射和内存映射中的计数器, 从而管理有保障的内存分配。
内存管理器在启动和运行期间按下述逻辑更新节点映射(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)。
Windows 支持
Kubernetes v1.32 [alpha]
(enabled by default: false)Windows 支持可以通过 WindowsCPUAndMemoryAffinity
特性门控来启用,
并且需要容器运行时的支持。在 Windows 上,仅支持 BestEffort 策略。
内存管理器配置
其他管理器也要预先配置。接下来,内存管理器特性需要被启用,
并且采用 Static
策略(静态策略)运行。
作为可选操作,可以预留一定数量的内存给系统或者 kubelet 进程以增强节点的稳定性
(预留内存标志)。
策略
内存管理器支持两种策略。你可以通过 kubelet
标志 --memory-manager-policy
来选择一种策略:
None
(默认)Static
(仅 Linux)BestEffort
(仅 Windows)
None 策略
这是默认的策略,并且不会以任何方式影响内存分配。该策略的行为好像内存管理器不存在一样。
None
策略返回默认的拓扑提示信息。这种特殊的提示会表明拓扑驱动组件(Hint Provider)
(在这里是内存管理器)对任何资源都没有与 NUMA 亲和性关联的偏好。
Static 策略
对 Guaranteed
Pod 而言,Static
内存管理器策略会返回拓扑提示信息,
该信息与内存分配有保障的 NUMA 节点集合有关,并且内存管理器还通过更新内部的节点映射
对象来完成内存预留。
对 BestEffort
或 Burstable
Pod 而言,因为不存在对有保障的内存资源的请求,
Static
内存管理器策略会返回默认的拓扑提示,并且不会通过内部的节点映射对象来预留内存。
BestEffort 策略
Kubernetes v1.32 [alpha]
(enabled by default: false)此策略仅 Windows 上支持。
在 Windows 上,NUMA 节点分配方式与 Linux 上不同。 没有机制确保内存访问仅来自特定的 NUMA 节点。 相反,Windows 调度器将基于 CPU 的分配来选择最优的 NUMA 节点。 如果 Windows 调度器认为其他 NUMA 节点更优,Windows 可能会使用其他节点。
此策略会通过内部的 NodeMap 跟踪可用和请求的内存量。 内存管理器将尽力确保在进行分配之前,NUMA 节点上有足够的内存可用。 这意味着在大多数情况下,内存分配的工作模式是符合预期的。
预留内存标志
节点可分配机制通常被节点管理员用来为 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
是必须设置的。
此外,应尽量避免如下配置:
- 重复的配置,即同一 NUMA 节点或内存类型被设置不同的取值;
- 为某种内存类型设置约束值为零;
- 使用物理硬件上不存在的 NUMA 节点 ID;
- 使用名字不是
memory
或hugepages-<size>
的内存类型名称 (特定的<size>
的大页面也必须存在)。
语法:
--reserved-memory N:memory-type1=value1,memory-type2=value2,...
N
(整数)- NUMA 节点索引,例如,0
memory-type
(字符串)- 代表内存类型:memory
- 常规内存;hugepages-2Mi
或hugepages-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
说明:
默认的硬性驱逐阈值是 100MiB,不是零。
请记得在使用 --reserved-memory
设置要预留的内存量时,加上这个硬性驱逐阈值。
否则 kubelet 不会启动内存管理器,而会输出一个错误信息。
下面是一个正确配置的示例:
--kube-reserved=cpu=4,memory=4Gi
--system-reserved=cpu=1,memory=1Gi
--memory-manager-policy=Static
--reserved-memory '0:memory=3Gi;1:memory=2148Mi'
在 Kubernetes 1.32 之前,你还需要添加:
--feature-gates=MemoryManager=true
我们对上面的配置做一个检查:
kube-reserved + system-reserved + eviction-hard(default) = reserved-memory(0) + reserved-memory(1)
4GiB + 1GiB + 100MiB = 3GiB + 2148MiB
5120MiB + 100MiB = 3072MiB + 2148MiB
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 到节点,或者将其拒绝。
此外,你可以搜索日志查找与内存管理器相关的其他条目,例如 cgroups
和
cpuset.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 节点组成,
即索引值分别为 0
和 1
的 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 来检索此信息。
接下来
- Memory Manager KEP: Design Overview
- Memory Manager KEP: Memory Maps at start-up (with examples)
- Memory Manager KEP: Memory Maps at runtime (with examples)
- Memory Manager KEP: Simulation - how the Memory Manager works? (by examples)
- Memory Manager KEP: The Concept of Node Map and Memory Maps
- Memory Manager KEP: How to enable the guaranteed memory allocation over many NUMA nodes?
45 - 验证已签名容器镜像
Kubernetes v1.24 [alpha]
准备开始
你需要安装以下工具:
验证二进制签名
Kubernetes 发布过程使用 cosign 的无密钥签名对所有二进制工件(压缩包、 SPDX 文件、 独立的二进制文件)签名。要验证一个特定的二进制文件, 获取组件时要包含其签名和证书:
URL=https://dl.k8s.io/release/v1.32.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 2.0 需要指定 --certificate-identity
和 --certificate-oidc-issuer
选项。
想要进一步了解无密钥签名,请参考 Keyless Signatures。
Cosign 的早期版本还需要设置 COSIGN_EXPERIMENTAL=1
。
如需更多信息,请参考 sigstore Blog
验证镜像签名
完整的镜像签名列表请参见发行版本。
从这个列表中选择一个镜像,并使用 cosign verify
命令来验证它的签名:
cosign verify registry.k8s.io/kube-apiserver-amd64:v1.32.0 \
--certificate-identity krel-trust@k8s-releng-prod.iam.gserviceaccount.com \
--certificate-oidc-issuer https://accounts.google.com \
| jq .
验证所有控制平面组件镜像
验证最新稳定版(v1.32.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"