云控制器管理器的鸡和蛋问题

Kubernetes 1.31 完成了 Kubernetes 历史上最大规模的迁移,移除了树内云驱动。虽然组件迁移现已完成,但这给用户和安装程序项目(例如 kOps 或 Cluster API)带来了一些额外的复杂性。我们将探讨这些额外的步骤和故障点,并为集群所有者提出建议。这次迁移很复杂,一些逻辑必须从核心组件中提取出来,从而构建了四个新的子系统。

  1. 云控制器管理器(Cloud controller manager) (KEP-2392)
  2. API 服务器网络代理(API server network proxy) (KEP-1281)
  3. kubelet 凭据提供程序插件(kubelet credential provider plugins) (KEP-2133)
  4. 将存储迁移到使用 CSI (KEP-625)

云控制器管理器是控制平面的一部分。它是一个关键组件,取代了之前存在于 kube-controller-manager 和 kubelet 中的一些功能。

Components of Kubernetes

Kubernetes 的组件

云控制器管理器最关键的功能之一是节点控制器,它负责节点的初始化。

如下图所示,当 kubelet 启动时,它会向 apiserver 注册 Node 对象,并为节点添加污点,以便首先由云控制器管理器处理。初始的 Node 缺少特定于云驱动的信息,例如节点地址以及带有云驱动特定信息(如节点、区域和实例类型信息)的标签。

Chicken and egg problem sequence diagram

“鸡生蛋还是蛋生鸡”问题时序图

这个新的初始化过程给节点就绪增加了一些延迟。以前,kubelet 能够在创建节点的同时初始化节点。由于逻辑已移至云控制器管理器,对于那些不像控制平面其他组件那样部署控制器管理器(通常是静态 Pod、独立二进制文件或带有容忍污点的 DaemonSet/Deployment 并使用 `hostNetwork`)的 Kubernetes 架构来说,这可能在集群引导期间导致“鸡生蛋还是蛋生鸡”问题(下文将详细介绍)。

依赖问题的示例

如上所述,在引导期间,云控制器管理器可能无法调度,从而导致集群无法正常初始化。以下是一些具体示例,说明该问题如何表现以及可能发生的根本原因。

这些示例假设您使用 Kubernetes 资源(例如 Deployment、DaemonSet 或类似资源)来运行云控制器管理器以控制其生命周期。因为这些方法依赖 Kubernetes 来调度云控制器管理器,所以必须小心确保它能够正确调度。

示例:由于未初始化的污点,云控制器管理器无法调度

正如 Kubernetes 文档中所述,当 kubelet 启动时带有命令行标志 `--cloud-provider=external` 时,其对应的 `Node` 对象将被添加一个名为 `node.cloudprovider.kubernetes.io/uninitialized` 的 NoSchedule 污点。由于云控制器管理器负责移除这个 NoSchedule 污点,这可能导致由 Kubernetes 资源(如 `Deployment` 或 `DaemonSet`)管理的云控制器管理器无法调度的情况。

如果在控制平面初始化期间云控制器管理器无法被调度,那么生成的 `Node` 对象都将带有 `node.cloudprovider.kubernetes.io/uninitialized` 这个 NoSchedule 污点。这也意味着这个污点不会被移除,因为云控制器管理器负责移除它。如果这个 NoSchedule 污点不被移除,那么关键的工作负载,例如容器网络接口控制器,将无法调度,集群将处于不健康状态。

示例:由于未就绪的污点,云控制器管理器无法调度

下一个示例可能出现在容器网络接口(CNI)等待云控制器管理器(CCM)提供 IP 地址信息,而 CCM 没有容忍将被 CNI 移除的污点的情况下。

Kubernetes 文档对 `node.kubernetes.io/not-ready` 污点的描述如下:

“节点控制器通过监控节点的健康状况来检测节点是否就绪,并相应地添加或移除此污点。”

导致 Node 资源带有此污点的一个条件是该节点上的容器网络尚未初始化。由于云控制器管理器负责向 Node 资源添加 IP 地址,而容器网络控制器需要这些 IP 地址来正确配置容器网络,因此在某些情况下,节点可能会永久卡在未就绪和未初始化的状态。

这种情况的发生原因与第一个示例类似,但在这种情况下,`node.kubernetes.io/not-ready` 污点带有 NoExecute 效果,因此会导致云控制器管理器无法在带有该污点的节点上运行。如果云控制器管理器无法执行,它将无法初始化节点。这将级联导致容器网络控制器无法正常运行,节点最终将同时带有 `node.cloudprovider.kubernetes.io/uninitialized` 和 `node.kubernetes.io/not-ready` 污点,使集群处于不健康状态。

我们的建议

运行云控制器管理器没有唯一的“正确方法”。具体细节将取决于集群管理员和用户的特定需求。在规划集群和云控制器管理器的生命周期时,请考虑以下指导:

对于在它们所管理的同一个集群中运行的云控制器管理器。

  1. 使用主机网络模式,而不是 Pod 网络:在大多数情况下,云控制器管理器需要与基础架构关联的 API 服务端点通信。将 “hostNetwork” 设置为 true 将确保云控制器使用主机网络而不是容器网络,因此将具有与主机操作系统相同的网络访问权限。它还将消除对网络插件的依赖。这将确保云控制器可以访问基础架构端点(请务必根据您的基础架构提供商的说明检查您的网络配置)。
  2. 使用可扩展的资源类型。`Deployments` 和 `DaemonSets` 对于控制云控制器的生命周期很有用。它们可以轻松地运行多个副本以实现冗余,并利用 Kubernetes 调度来确保在集群中的正确放置。当使用这些原语来控制云控制器的生命周期并运行多个副本时,您必须记住启用领导者选举,否则您的控制器将相互冲突,可能导致节点在集群中无法初始化。
  3. 将控制器管理器容器部署到控制平面。可能存在其他需要运行在控制平面之外的控制器(例如,Azure 的节点管理器控制器)。但是,控制器管理器本身应该部署到控制平面。使用节点选择器或亲和性节来将云控制器的调度指向控制平面,以确保它们在受保护的空间中运行。云控制器对于向集群添加和移除节点至关重要,因为它们构成了 Kubernetes 和物理基础架构之间的联系。在控制平面上运行它们将有助于确保它们以与其他核心集群控制器相似的优先级运行,并且与非特权用户工作负载有一定的隔离。
    1. 值得注意的是,使用反亲和性节来防止云控制器在同一主机上运行,对于确保单个节点故障不会降低云控制器性能也非常有用。
  4. 确保容忍度允许操作。在云控制器容器的清单中使用容忍度,以确保它能调度到正确的节点,并且在节点初始化的情况下也能运行。这意味着云控制器应该容忍 `node.cloudprovider.kubernetes.io/uninitialized` 污点,并且还应该容忍与控制平面相关的任何污点(例如,`node-role.kubernetes.io/control-plane` 或 `node-role.kubernetes.io/master`)。容忍 `node.kubernetes.io/not-ready` 污点也很有用,以确保即使节点尚未可用于健康监控,云控制器也能运行。

对于不在其管理的集群上运行的云控制器管理器(例如,在单独集群上的托管控制平面中),规则受到运行云控制器管理器的集群环境的依赖关系的更多限制。在自管理集群上运行的建议可能不适用,因为冲突类型和网络限制会有所不同。对于这些场景,请查阅您的拓扑的架构和要求。

示例

这是一个 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

在决定如何部署云控制器管理器时,值得注意的是,不推荐使用集群比例或基于资源的 Pod 自动缩放。运行云控制器管理器的多个副本是确保高可用性和冗余的好做法,但无助于提高性能。通常,任何给定时间只有一个云控制器管理器实例在协调一个集群。