云控制器管理器的鸡和蛋问题
Kubernetes 1.31 完成了 Kubernetes 历史上最大规模的迁移,移除了树内云提供商。虽然组件迁移已完成,但这给用户和安装器项目(例如 kOps 或 Cluster API)带来了一些额外的复杂性。我们将讨论这些额外的步骤和故障点,并为集群所有者提供建议。这次迁移很复杂,一些逻辑必须从核心组件中提取出来,构建了四个新的子系统。
- Cloud controller manager (KEP-2392)
- API server network proxy (KEP-1281)
- kubelet credential provider plugins (KEP-2133)
- 使用 CSI 进行存储迁移 (KEP-625)
cloud controller manager 是控制平面的一部分。它是一个关键组件,替代了之前在 kube-controller-manager 和 kubelet 中存在的一些功能。
Kubernetes 组件
cloud controller manager 最关键的功能之一是 node controller,它负责节点的初始化。
如下图所示,当 kubelet 启动时,它会向 apiserver 注册 Node 对象,并对节点进行 Taint,以便 cloud-controller-manager 可以首先处理。初始 Node 缺少云提供商特定的信息,例如节点地址以及包含云提供商特定信息(如节点、区域和实例类型信息)的标签。
先有鸡还是先有蛋问题序列图
这个新的初始化过程增加了节点就绪的延迟。之前,kubelet 可以在创建节点的同时初始化节点。由于逻辑已转移到 cloud-controller-manager,这可能会在集群引导期间为那些不将 controller manager 作为控制平面其他组件部署的 Kubernetes 架构(通常是静态 Pod、独立二进制文件或使用 tolerations 容忍 taints 并使用 hostNetwork
的 daemonsets/deployments)造成先有鸡还是先有蛋问题。
依赖问题的示例
如上所述,在引导期间,cloud-controller-manager 可能会因无法调度而导致集群无法正常初始化。以下是该问题可能表现出的几个具体示例以及导致这些问题发生的根本原因。
这些示例假定您使用 Kubernetes 资源(例如 Deployment、DaemonSet 或类似资源)来控制 cloud-controller-manager 的生命周期。由于这些方法依赖 Kubernetes 来调度 cloud-controller-manager,因此必须注意确保其能正常调度。
示例:cloud controller manager 因未初始化 Taint 而无法调度
正如Kubernetes 文档所述,当 kubelet 启动时带有命令行标志 --cloud-provider=external
,其对应的 Node
对象将添加一个名为 node.cloudprovider.kubernetes.io/uninitialized
的 no schedule taint。由于 cloud-controller-manager 负责移除该 no schedule taint,这可能会导致由 Kubernetes 资源(例如 Deployment
或 DaemonSet
)管理的 cloud-controller-manager 无法调度。
如果在控制平面初始化期间 cloud-controller-manager 无法被调度,那么生成的 Node
对象都将带有 node.cloudprovider.kubernetes.io/uninitialized
no schedule taint。这也意味着该 taint 不会被移除,因为 cloud-controller-manager 负责移除它。如果 no schedule taint 未被移除,那么关键工作负载(例如容器网络接口控制器)将无法调度,集群将处于不健康状态。
示例:cloud controller manager 因 not-ready Taint 而无法调度
下一个示例可能发生在以下情况:容器网络接口 (CNI) 正在等待来自 cloud-controller-manager (CCM) 的 IP 地址信息,而 CCM 尚未容忍该将被 CNI 移除的 taint。
Kubernetes 文档将 node.kubernetes.io/not-ready
taint 描述如下:
“节点控制器通过监控节点的健康状况来检测节点是否就绪,并相应地添加或移除此 taint。”
可能导致 Node 资源带有此 taint 的条件之一是该节点的容器网络尚未初始化。由于 cloud-controller-manager 负责向 Node 资源添加 IP 地址,而容器网络控制器需要 IP 地址才能正确配置容器网络,因此在某些情况下,节点可能会永久卡在不就绪和未初始化状态。
这种情况的发生原因与第一个示例类似,尽管在这种情况下,node.kubernetes.io/not-ready
taint 与 no execute effect 一起使用,因此会导致 cloud-controller-manager 无法在带有该 taint 的节点上运行。如果 cloud-controller-manager 无法执行,那么它将无法初始化节点。这将导致容器网络控制器无法正常运行,节点最终将同时带有 node.cloudprovider.kubernetes.io/uninitialized
和 node.kubernetes.io/not-ready
taint,使集群处于不健康状态。
我们的建议
运行 cloud-controller-manager 没有“正确的方式”。具体细节取决于集群管理员和用户的特定需求。在规划集群和 cloud-controller-managers 的生命周期时,请考虑以下指南:
对于在同一个集群中运行的 cloud-controller-managers,它们正在管理这些集群。
- 使用 host network 模式,而不是 pod network:在大多数情况下,cloud controller manager 需要与基础设施关联的 API 服务端点通信。将 “hostNetwork” 设置为 true 将确保 cloud controller 使用主机网络而不是容器网络,因此将拥有与主机操作系统相同的网络访问权限。它还将移除对网络插件的依赖。这将确保 cloud controller 能够访问基础设施端点(务必对照基础设施提供商的说明检查网络配置)。
- 使用可扩展的资源类型。
Deployments
和DaemonSets
对于控制 cloud controller 的生命周期很有用。它们允许轻松访问运行多个副本以实现冗余,并利用 Kubernetes 调度确保在集群中的正确位置。在使用这些原语控制 cloud controllers 的生命周期并运行多个副本时,必须记住启用领导者选举,否则您的控制器将相互冲突,可能导致节点无法初始化到集群中。 - 将 controller manager 容器定位到控制平面。可能存在其他需要运行在控制平面之外的控制器(例如 Azure 的节点管理器控制器)。但 controller managers 本身应部署到控制平面。使用节点选择器或 affinity 节来指导 cloud controllers 的调度到控制平面,以确保它们运行在受保护的空间。Cloud controllers 对于向集群添加和移除节点至关重要,因为它们在 Kubernetes 和物理基础设施之间形成了链接。将它们运行在控制平面上将有助于确保它们与核心集群控制器具有相似的优先级,并与非特权用户工作负载有所隔离。
- 值得注意的是,使用 anti-affinity 节来防止 cloud controllers 运行在同一主机上也非常有用,以确保单个节点故障不会降低 cloud controller 的性能。
- 确保 tolerations 允许操作。在 cloud controller 容器的 manifest 上使用 tolerations,以确保它能调度到正确的节点,并且能够在节点初始化的情况下运行。这意味着 cloud controllers 应该容忍
node.cloudprovider.kubernetes.io/uninitialized
taint,并且还应该容忍与控制平面相关的任何 taint(例如node-role.kubernetes.io/control-plane
或node-role.kubernetes.io/master
)。容忍node.kubernetes.io/not-ready
taint 也很有用,以确保即使节点尚未可用于健康监控,cloud controller 也能运行。
对于不在其管理的集群上运行的 cloud-controller-managers(例如,在单独集群上托管的控制平面),其规则受限于运行 cloud-controller-manager 的环境依赖性。在自管理集群上运行的建议可能不适用,因为冲突类型和网络限制会不同。对于这些场景,请查阅您的拓扑结构的架构和要求。
示例
这是一个 Kubernetes Deployment 示例,突出显示了上述指南。重要的是要注意,这仅用于演示目的,对于生产用途,请查阅您的云提供商文档。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: cloud-controller-manager
name: cloud-controller-manager
namespace: kube-system
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: cloud-controller-manager
strategy:
type: Recreate
template:
metadata:
labels:
app.kubernetes.io/name: cloud-controller-manager
annotations:
kubernetes.io/description: Cloud controller manager for my infrastructure
spec:
containers: # the container details will depend on your specific cloud controller manager
- name: cloud-controller-manager
command:
- /bin/my-infrastructure-cloud-controller-manager
- --leader-elect=true
- -v=1
image: registry/my-infrastructure-cloud-controller-manager@latest
resources:
requests:
cpu: 200m
memory: 50Mi
hostNetwork: true # these Pods are part of the control plane
nodeSelector:
node-role.kubernetes.io/control-plane: ""
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: "kubernetes.io/hostname"
labelSelector:
matchLabels:
app.kubernetes.io/name: cloud-controller-manager
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/master
operator: Exists
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 120
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 120
- effect: NoSchedule
key: node.cloudprovider.kubernetes.io/uninitialized
operator: Exists
- effect: NoSchedule
key: node.kubernetes.io/not-ready
operator: Exists
在决定如何部署 cloud controller manager 时,值得注意的是不推荐使用集群比例或基于资源的 pod 自动伸缩。运行 cloud controller manager 的多个副本是确保高可用性和冗余的良好实践,但对提高性能没有帮助。通常,在任何给定时间,只有一个 cloud controller manager 实例会协调集群。