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

返回本页常规视图.

集群管理

关于创建和管理 Kubernetes 集群的底层细节。

集群管理概述面向任何创建和管理 Kubernetes 集群的读者人群。 我们假设你大概了解一些核心的 Kubernetes 概念

规划集群

查阅安装中的指导,获取如何规划、建立以及配置 Kubernetes 集群的示例。本文所列的文章称为发行版

在选择一个指南前,有一些因素需要考虑:

  • 你是打算在你的计算机上尝试 Kubernetes,还是要构建一个高可用的多节点集群? 请选择最适合你需求的发行版。
  • 你正在使用类似 Google Kubernetes Engine 这样的被托管的 Kubernetes 集群, 还是管理你自己的集群
  • 你的集群是在本地还是云(IaaS) 上?Kubernetes 不能直接支持混合集群。 作为代替,你可以建立多个集群。
  • 如果你在本地配置 Kubernetes, 需要考虑哪种网络模型最适合。
  • 你的 Kubernetes 在裸机上还是虚拟机(VM) 上运行?
  • 你是想运行一个集群,还是打算参与开发 Kubernetes 项目代码? 如果是后者,请选择一个处于开发状态的发行版。 某些发行版只提供二进制发布版,但提供更多的选择。
  • 让你自己熟悉运行一个集群所需的组件

管理集群

加固集群

加固 kubelet

可选集群服务

1 - 节点关闭

在 Kubernetes 集群中,节点可以按计划的体面方式关闭, 也可能因断电或其他某些外部原因被意外关闭。如果节点在关闭之前未被排空,则节点关闭可能会导致工作负载失败。 节点可以体面关闭非体面关闭

节点体面关闭

特性状态: Kubernetes v1.21 [beta] (enabled by default: true)

kubelet 会尝试检测节点系统关闭事件并终止在节点上运行的所有 Pod。

在节点终止期间,kubelet 保证 Pod 遵从常规的 Pod 终止流程, 且不接受新的 Pod(即使这些 Pod 已经绑定到该节点)。

节点体面关闭特性依赖于 systemd,因为它要利用 systemd 抑制器锁机制, 在给定的期限内延迟节点关闭。

节点体面关闭特性受 GracefulNodeShutdown 特性门控控制, 在 1.21 版本中是默认启用的。

注意,默认情况下,下面描述的两个配置选项,shutdownGracePeriodshutdownGracePeriodCriticalPods 都是被设置为 0 的,因此不会激活节点体面关闭特性。 要激活此功能特性,这两个选项要适当配置,并设置为非零值。

一旦 systemd 检测到或收到节点关闭的通知,kubelet 就会在节点上设置一个 NotReady 状况,并将 reason 设置为 "node is shutting down"。 kube-scheduler 会重视此状况,不将 Pod 调度到受影响的节点上; 其他第三方调度程序也应当遵循相同的逻辑。这意味着新的 Pod 不会被调度到该节点上, 因此不会有新 Pod 启动。

如果检测到节点关闭正在进行中,kubelet 也会PodAdmission 阶段拒绝 Pod,即使是该 Pod 带有 node.kubernetes.io/not-ready:NoSchedule容忍度,也不会在此节点上启动。

当 kubelet 通过 API 在其 Node 上设置该状况时,kubelet 也开始终止在本地运行的所有 Pod。

在体面关闭过程中,kubelet 分两个阶段来终止 Pod:

  1. 终止在节点上运行的常规 Pod。
  2. 终止在节点上运行的关键 Pod

节点体面关闭的特性对应两个 KubeletConfiguration 选项:

  • shutdownGracePeriod

    指定节点应延迟关闭的总持续时间。这是 Pod 体面终止的时间总和,不区分常规 Pod 还是关键 Pod

  • shutdownGracePeriodCriticalPods

    在节点关闭期间指定用于终止关键 Pod 的持续时间。该值应小于 shutdownGracePeriod

例如,如果设置了 shutdownGracePeriod=30sshutdownGracePeriodCriticalPods=10s, 则 kubelet 将延迟 30 秒关闭节点。在关闭期间,将保留前 20(30 - 10)秒用于体面终止常规 Pod, 而保留最后 10 秒用于终止关键 Pod

基于 Pod 优先级的节点体面关闭

特性状态: Kubernetes v1.24 [beta] (enabled by default: true)

为了在节点体面关闭期间提供更多的灵活性,尤其是处理关闭期间的 Pod 排序问题, 节点体面关闭机制能够关注 Pod 的 PriorityClass 设置,前提是你已经在集群中启用了此功能特性。 此特性允许集群管理员基于 Pod 的优先级类(Priority Class) 显式地定义节点体面关闭期间 Pod 的处理顺序。

前文所述的节点体面关闭特性能够分两个阶段关闭 Pod, 首先关闭的是非关键的 Pod,之后再处理关键 Pod。 如果需要显式地以更细粒度定义关闭期间 Pod 的处理顺序,需要一定的灵活度, 这时可以使用基于 Pod 优先级的体面关闭机制。

当节点体面关闭能够处理 Pod 优先级时,节点体面关闭的处理可以分为多个阶段, 每个阶段关闭特定优先级类的 Pod。可以配置 kubelet 按确切的阶段处理 Pod, 且每个阶段可以独立设置关闭时间。

假设集群中存在以下自定义的 Pod 优先级类

Pod 优先级类名称 Pod 优先级类数值
custom-class-a 100000
custom-class-b 10000
custom-class-c 1000
regular/unset 0

kubelet 配置中, shutdownGracePeriodByPodPriority 看起来可能是这样:

Pod 优先级类数值 关闭期限
100000 10 秒
10000 180 秒
1000 120 秒
0 60 秒

对应的 kubelet 配置 YAML 将会是:

shutdownGracePeriodByPodPriority:
  - priority: 100000
    shutdownGracePeriodSeconds: 10
  - priority: 10000
    shutdownGracePeriodSeconds: 180
  - priority: 1000
    shutdownGracePeriodSeconds: 120
  - priority: 0
    shutdownGracePeriodSeconds: 60

上面的表格表明,所有 priority 值大于等于 100000 的 Pod 关闭期限只有 10 秒, 所有 priority 值介于 10000 和 100000 之间的 Pod 关闭期限是 180 秒, 所有 priority 值介于 1000 和 10000 之间的 Pod 关闭期限是 120 秒, 其他所有 Pod 关闭期限是 60 秒。

用户不需要为所有的优先级类都设置数值。例如,你也可以使用下面这种配置:

Pod 优先级类数值 关闭期限
100000 300 秒
1000 120 秒
0 60 秒

在上面这个场景中,优先级类为 custom-class-b 的 Pod 会与优先级类为 custom-class-c 的 Pod 在关闭时按相同期限处理。

如果在特定的范围内不存在 Pod,则 kubelet 不会等待对应优先级范围的 Pod。 kubelet 会直接跳到下一个优先级数值范围进行处理。

如果此功能特性被启用,但没有提供配置数据,则不会出现排序操作。

使用此功能特性需要启用 GracefulNodeShutdownBasedOnPodPriority 特性门控, 并将 kubelet 配置中的 shutdownGracePeriodByPodPriority 设置为期望的配置, 其中包含 Pod 的优先级类数值以及对应的关闭期限。

kubelet 子系统中会生成 graceful_shutdown_start_time_secondsgraceful_shutdown_end_time_seconds 度量指标以便监视节点关闭行为。

处理节点非体面关闭

特性状态: Kubernetes v1.28 [stable] (enabled by default: true)

节点关闭的操作可能无法被 kubelet 的节点关闭管理器检测到, 或是因为该命令没有触发 kubelet 所使用的抑制器锁机制,或是因为用户错误, 即 ShutdownGracePeriod 和 ShutdownGracePeriodCriticalPod 配置不正确。 请参考以上节点体面关闭部分了解更多详细信息。

当某节点关闭但 kubelet 的节点关闭管理器未检测到这一事件时, 在那个已关闭节点上、属于 StatefulSet 的 Pod 将停滞于终止状态,并且不能移动到新的运行节点上。 这是因为已关闭节点上的 kubelet 已不存在,亦无法删除 Pod, 因此 StatefulSet 无法创建同名的新 Pod。 如果 Pod 使用了卷,则 VolumeAttachments 无法从原来的已关闭节点上删除, 因此这些 Pod 所使用的卷也无法挂接到新的运行节点上。 最终,那些以 StatefulSet 形式运行的应用无法正常工作。 如果原来的已关闭节点被恢复,kubelet 将删除 Pod,新的 Pod 将被在不同的运行节点上创建。 如果原来的已关闭节点没有被恢复,那些在已关闭节点上的 Pod 将永远滞留在终止状态。

为了缓解上述情况,用户可以手动将具有 NoExecuteNoSchedule 效果的 node.kubernetes.io/out-of-service 污点添加到节点上,标记其无法提供服务。 如果 Node 被污点标记为无法提供服务,且节点上的 Pod 没有设置对应的容忍度, 那么这样的 Pod 将被强制删除,并且在节点上被终止的 Pod 将立即进行卷分离操作。 这样就允许那些在无法提供服务节点上的 Pod 能在其他节点上快速恢复。

在非体面关闭期间,Pod 分两个阶段终止:

  1. 强制删除没有匹配的 out-of-service 容忍度的 Pod。
  2. 立即对此类 Pod 执行分离卷操作。

存储超时强制解除挂接

在任何情况下,当 Pod 未能在 6 分钟内删除成功,如果节点当时不健康, Kubernetes 将强制解除挂接正在被卸载的卷。 任何运行在使用了强制解除挂接卷的节点之上的工作负载, 都将违反 CSI 规范, 该规范指出 ControllerUnpublishVolume "必须在调用卷上的所有 NodeUnstageVolumeNodeUnpublishVolume 执行且成功后被调用"。 在这种情况下,相关节点上的卷可能会遇到数据损坏。

强制存储解除挂接行为是可选的;用户可以选择使用"非体面节点关闭"特性。

可以通过在 kube-controller-manager 中设置 disable-force-detach-on-timeout 配置字段来禁用超时时存储强制解除挂接。 禁用超时强制解除挂接特性意味着,托管在异常超过 6 分钟的节点上的卷将不会保留其关联的 VolumeAttachment

应用此设置后,仍然关联卷到不健康 Pod 必须通过上述非体面节点关闭过程进行恢复。

Windows 体面节点关闭

特性状态: Kubernetes v1.32 [alpha] (enabled by default: false)

此服务会使用一个注册的服务控制处理程序函数将 preshutdown 事件延迟一段时间。

Windows 体面节点关闭是通过 1.32 中作为 Alpha 特性所引入的 WindowsGracefulNodeShutdown 特性门控进行控制的。

Windows 体面节点关闭无法被取消。

如果 kubelet 不是作为 Windows 服务运行,它将不能设置和监控 Preshutdown 事件,对应节点将不得不跑完上述非体面节点关闭的流程。

在启用 Windows 体面节点关闭特性但 kubelet 未作为 Windows 服务运行的情况下,kubelet 将继续运行而不会失败。 但是,kubelet 将在日志中记录一个错误,表明它需要作为一个 Windows 服务来运行。

接下来

了解更多以下信息:

2 - Node 自动扩缩容

自动在集群中制备和整合 Node,以适应需求并优化成本。

为了在集群中运行负载,你需要 Node。 集群中的 Node 可以被自动扩缩容: 通过动态制备整合的方式提供所需的容量并优化成本。 自动扩缩容操作是由 Node Autoscaler 执行的。

Node 制备

当集群中有 Pod 无法被调度到现有 Node 上时,系统将制备新的 Node 并将其添加到集群中,以容纳这些 Pod。 如果由于组合使用水平负载和 Node 自动扩缩容使得 Pod 个数随着时间发生变化,这种自动扩缩容机制将特别有用。

Autoscaler 通过创建和删除云驱动基础资源来制备 Node。最常见的支撑 Node 的资源是虚拟机(VM)。

制备的主要目标是使所有 Pod 可调度。 由于各种限制(如已达到配置的制备上限、制备配置与特定 Pod 集不兼容或云驱动容量不足),此目标不一定总是可以实现。 在制备之时,Node Autoscaler 通常还会尝试实现其他目标(例如最小化制备 Node 的成本或在故障域之间平衡 Node 的数量)。

在决定制备 Node 时针对 Node Autoscaler 有两个主要输入:

Autoscaler 配置也可以包含其他 Node 制备触发条件(例如 Node 个数低于配置的最小限制值)。

Pod 调度约束

Pod 可以通过调度约束表达只能调度到特定类别 Node 的限制。 Node Autoscaler 会考虑这些约束,确保 Pending 的 Pod 可以被调度到这些制备的 Node 上。

最常见的调度约束是通过 Pod 容器所指定的资源请求。 Autoscaler 将确保制备的 Node 具有足够资源来满足这些请求。 但是,Autoscaler 不会在 Pod 开始运行之后直接考虑这些 Pod 的真实资源用量。 要根据实际负载资源用量自动扩缩容 Node, 你可以组合使用水平负载自动扩缩容和 Node 自动扩缩容。

其他常见的 Pod 调度约束包括 Node 亲和性Pod 间亲和性/反亲和性或特定存储卷的要求。

Autoscaler 配置施加的 Node 约束

已制备的 Node 的具体规格(例如资源量、给定标签的存在与否)取决于 Autoscaler 配置。 Autoscaler 可以从一组预定义的 Node 配置中进行选择,或使用自动制备

自动制备

Node 自动制备是一种用户无需完全配置 Node 容许制备规格的制备模式。 Autoscaler 会基于 Pending 的 Pod 和预配置的约束(例如最小资源量或给定标签的需求)动态选择 Node 配置。

Node 整合

运行集群时的主要考量是确保所有可调度 Pod 都在运行,并尽可能降低集群成本。 为此,Pod 的资源请求应尽可能利用 Node 的更多资源。 从这个角度看,集群中的整体 Node 利用率可以用作集群成本效益的参考指标。

集群中的 Node 可以被自动整合,以提高整体 Node 利用率以及集群的成本效益。 整合操作通过移除一组利用率低的 Node 来实现。有时会同时制备一组不同的 Node 来替代。

与制备类似,整合操作在做出决策时仅考虑 Pod 的资源请求而非实际的资源用量。

在整合过程中,如果一个 Node 上仅运行 DaemonSet 和静态 Pod,这个 Node 就会被视为空的。 在整合期间移除空的 Node 要比操作非空 Node 更简单直接,Autoscaler 通常针对空 Node 整合进行优化。

在整合期间移除非空 Node 会有破坏性:Node 上运行的 Pod 会被终止,且可能需要被重新创建(例如由 Deployment 重新创建)。 不过,所有被重新创建的 Pod 都应该能够被调度到集群中的现有 Node 上,或调度到作为整合一部分而制备的替代 Node 上。 正常情况下,整合操作不应导致 Pod 处于 Pending 状态。

Autoscaler 配置还可以设为由其他状况触发整合(例如 Node 被创建后用掉的时间),以优化属性(例如集群中 Node 的最大生命期)。

执行整合的具体方式取决于给定 Autoscaler 的配置。

Autoscaler

上述章节中所述的功能由 Node Autoscaler 提供。 除了 Kubernetes API 之外,Autoscaler 还需要与云驱动 API 交互来制备和整合 Node。 这意味着 Autoscaler 需要与每个支持的云驱动进行显式集成。 给定的 Autoscaler 的性能和特性集在不同云驱动集成之间可能有所不同。

graph TD na[Node Autoscaler] k8s[Kubernetes] cp[云驱动] k8s --> |获取 Pod/Node|na na --> |腾空 Node|k8s na --> |创建/移除支撑 Node 的资源|cp cp --> |获取支撑 Node 的资源|na classDef white_on_blue fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef blue_on_white fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class na blue_on_white; class k8s,cp white_on_blue;

Autoscaler 实现

Cluster AutoscalerKarpenter 是目前由 SIG Autoscaling 维护的两个 Node Autoscaler。

对于集群用户来说,这两个 Autoscaler 都应提供类似的 Node 自动扩缩容体验。 两个 Autoscaler 都将为不可调度的 Pod 制备新的 Node,也都会整合利用率不高的 Node。

不同的 Autoscaler 还可能提供本文所述的 Node 自动扩缩容范围之外的其他特性,且这些额外的特性也会有所不同。

请参阅以下章节和特定 Autoscaler 的关联文档,了解哪个 Autoscaler 更适合你的使用场景。

Cluster Autoscaler

Cluster Autoscaler 通过向预先配置的 Node 组添加或移除 Node。 Node 组通常映射为某种云驱动资源组(最常见的是虚拟机组)。 单实例的 Cluster Autoscaler 将可以同时管理多个 Node 组。 在制备时,Cluster Autoscaler 将把 Node 添加到最贴合 Pending Pod 请求的组。 在整合时,Cluster Autoscaler 始终选择要移除的特定 Node,而不只是重新调整云驱动资源组的大小。

更多信息:

Karpenter

Karpenter 基于集群操作员所提供的 NodePool 配置来自动制备 Node。Karpenter 处理 Node 生命周期的所有方面,而不仅仅是自动扩缩容。 这包括 Node 达到某个生命期后的自动刷新,以及在有新 Worker Node 镜像被发布时的自动升级。 Karpenter 直接与特定的云驱动资源(通常是单独的虚拟机)交互,不依赖云驱动资源组。

更多上下文信息:

实现对比

Cluster Autoscaler 和 Karpenter 之间的主要差异:

  • Cluster Autoscaler 仅提供与 Node 自动扩缩容相关的特性。 而 Karpenter 的特性范围更大,还提供 Node 生命周期管理 (例如在 Node 达到某个生命期后利用中断来自动重新创建 Node,或自动将 Node 升级到新版本)。
  • Cluster Autoscaler 不支持自动制备,其可以制备的 Node 组必须被预先配置。 Karpenter 支持自动制备,因此用户只需为制备的 Node 配置一组约束,而不需要完整同质化的组。
  • Cluster Autoscaler 直接提供云驱动集成,这意味着这些集成组件是 Kubernetes 项目的一部分。 对于 Karpenter,Kubernetes 将 Karpenter 发布为一个库,云驱动可以集成这个库来构建 Node Autoscaler。
  • Cluster Autoscaler 为众多云驱动提供集成,包括一些小众的云驱动。 Karpenter 支持的云驱动相对较少,目前包括 AWSAzure

组合使用负载自动扩缩容与 Node 自动扩缩容

水平负载自动扩缩容

Node 自动扩缩容通常是为了响应 Pod 而发挥作用的。 它会制备新的 Node 容纳不可调度的 Pod,并在不再需要这些 Pod 时整合 Node。

水平负载自动扩缩容 自动扩缩负载副本的个数以保持各个副本达到预期的平均资源利用率。 换言之,它会基于应用负载而自动创建新的 Pod,并在负载减少时移除 Pod。

如果应用负载增加,其 Pod 的平均利用率也会增加,将提示负载自动扩缩容以创建新的 Pod。 Node 自动扩缩容随之应制备新的 Node 以容纳新的 Pod。

一旦应用负载减少,负载自动扩缩容应移除不必要的 Pod。 Node 自动扩缩容应按序整合不再需要的 Node。

如果配置正确,这种模式确保你的应用在需要时始终有足够的 Node 容量处理突发负载,你也无需在闲置时为这些 Node 容量支付费用。

垂直负载自动扩缩容

在使用 Node 自动扩缩容时,重要的是正确设置 Pod 资源请求。 如果给定 Pod 的请求过低,为其制备新的 Node 可能对 Pod 实际运行并无帮助。 如果给定 Pod 的请求过高,则可能对整合 Node 有所妨碍。

垂直负载自动扩缩容 基于其历史资源用量来自动调整 Pod 的资源请求。

你可以一起使用 Node 自动扩缩容和垂直负载自动扩缩容,以便在集群中保留 Node 自动扩缩容能力的同时调节 Pod 的资源请求。

本节以下组件提供与 Node 自动扩缩容相关的功能。

Descheduler

Descheduler 组件基于自定义策略提供 Node 整合功能,以及与优化 Node 和 Pod 相关的其他特性(例如删除频繁重启的 Pod)。

基于集群规模的负载 Autoscaler

Cluster Proportional AutoscalerCluster Proportional Vertical Autoscaler 基于集群中的 Node 个数进行水平和垂直负载自动扩缩容。 更多细节参阅基于集群规模自动扩缩容

接下来

3 - 证书

要了解如何为集群生成证书,参阅证书

4 - 集群网络系统

集群网络系统是 Kubernetes 的核心部分,但是想要准确了解它的工作原理可是个不小的挑战。 下面列出的是网络系统的的四个主要问题:

  1. 高度耦合的容器间通信:这个已经被 Podlocalhost 通信解决了。
  2. Pod 间通信:这是本文档讲述的重点。
  3. Pod 与 Service 间通信:涵盖在 Service 中。
  4. 外部与 Service 间通信:也涵盖在 Service 中。

Kubernetes 的宗旨就是在应用之间共享机器。 通常来说,共享机器需要两个应用之间不能使用相同的端口,但是在多个应用开发者之间 去大规模地协调端口是件很困难的事情,尤其是还要让用户暴露在他们控制范围之外的集群级别的问题上。

动态分配端口也会给系统带来很多复杂度 - 每个应用都需要设置一个端口的参数, 而 API 服务器还需要知道如何将动态端口数值插入到配置模块中,服务也需要知道如何找到对方等等。 与其去解决这些问题,Kubernetes 选择了其他不同的方法。

要了解 Kubernetes 网络模型,请参阅此处

Kubernetes IP 地址范围

Kubernetes 集群需要从以下组件中配置的可用地址范围中为 Pod、Service 和 Node 分配不重叠的 IP 地址:

  • 网络插件配置为向 Pod 分配 IP 地址。
  • kube-apiserver 配置为向 Service 分配 IP 地址。
  • kubelet 或 cloud-controller-manager 配置为向 Node 分配 IP 地址。
此图展示了 Kubernetes 集群中不同的网络范围

集群网络类型

根据配置的 IP 协议族,Kubernetes 集群可以分为以下几类:

  • 仅 IPv4:网络插件、kube-apiserver 和 kubelet/cloud-controller-manager 配置为仅分配 IPv4 地址。
  • 仅 IPv6:网络插件、kube-apiserver 和 kubelet/cloud-controller-manager 配置为仅分配 IPv6 地址。
  • IPv4/IPv6 或 IPv6/IPv4 双协议栈
    • 网络插件配置为分配 IPv4 和 IPv6 地址。
    • kube-apiserver 配置为分配 IPv4 和 IPv6 地址。
    • kubelet 或 cloud-controller-manager 配置为分配 IPv4 和 IPv6 地址。
    • 所有组件必须就配置的主要 IP 协议族达成一致。

Kubernetes 集群只考虑 Pod、Service 和 Node 对象中存在的 IP 协议族,而不考虑所表示对象的现有 IP。 例如,服务器或 Pod 的接口上可以有多个 IP 地址,但只有 node.status.addressespod.status.ips 中的 IP 地址被认为是实现 Kubernetes 网络模型和定义集群类型的。

如何实现 Kubernetes 的网络模型

网络模型由各节点上的容器运行时来实现。最常见的容器运行时使用 Container Network Interface (CNI) 插件来管理其网络和安全能力。 来自不同供应商 CNI 插件有很多。其中一些仅提供添加和删除网络接口的基本功能, 而另一些则提供更复杂的解决方案,例如与其他容器编排系统集成、运行多个 CNI 插件、高级 IPAM 功能等。

请参阅此页面了解 Kubernetes 支持的网络插件的非详尽列表。

接下来

网络模型的早期设计、运行原理都在联网设计文档里有详细描述。 关于未来的计划,以及旨在改进 Kubernetes 联网能力的一些正在进行的工作,可以参考 SIG Network 的 KEPs

5 - Admission Webhook 良好实践

在 Kubernetes 中设计和部署 Admission Webhook 的建议。

本页面提供了在 Kubernetes 中设计 Admission Webhook 时的良好实践和注意事项。 此信息适用于运行准入 Webhook 服务器或第三方应用程序的集群操作员, 这些程序用于修改或验证你的 API 请求。

在阅读本页之前,请确保你熟悉以下概念:

良好的 Webhook 设计的重要性

当任何创建、更新或删除请求发送到 Kubernetes API 时,就会发生准入控制。 准入控制器会拦截符合你定义的特定条件的请求。然后,这些请求会被发送到变更准入 Webhook(Mutating Admission Webhook) 或验证准入 Webhook(Validating Admission Webhook)。这些 Webhook 通常用于确保对象规范中的特定字段存在或具有特定允许值。

Webhook 是扩展 Kubernetes API 的强大机制。设计不良的 Webhook 由于对集群中对象具有很大的控制权, 常常会导致工作负载中断。与其他 API 扩展机制一样,对 Webhook 与所有工作负载、其他 Webhook、插件及附加组件的兼容性进行大规模测试是一个挑战。

此外,随着每个版本的发布,Kubernetes 会通过新增特性、将特性提升为测试版或稳定版以及弃用旧特性来添加或修改 API。 即使是稳定的 Kubernetes API 也可能会发生变化。例如,在 v1.29 中,Pod API 发生了变化, 以添加 Sidecar 容器特性。 虽然因为新的 Kubernetes API 导致 Kubernetes 对象进入损坏状态的情况很少见, 但那些在早期 API 版本中正常工作的 Webhook 可能无法适配该 API 的最新更改。 这可能会导致在你将集群升级到较新版本后出现意外行为。

本页面描述了常见的 Webhook 失败场景,以及如何通过谨慎和周到地设计与实现你的 Webhook 来避免这些问题。

识别是否使用 Admission Webhook

即使你没有运行自己的 Admission Webhook, 你在集群中运行的一些第三方应用程序也可能使用变更或验证准入 Webhook。

要检查你的集群是否存在变更性质的准入 Webhook,请运行以下命令:

kubectl get mutatingwebhookconfigurations

输出列出了集群中的所有变更准入控制器。

要检查你的集群是否存在验证性质的准入 Webhook,运行以下命令:

kubectl get validatingwebhookconfigurations

输出列出了集群中的所有验证性质准入控制器。

选择准入控制机制

Kubernetes 包含多个准入控制和策略执行选项。知道何时使用特定选项可以帮助你改善延迟和性能, 减少管理开销,并避免版本升级期间的问题。下表中描述的是你可以在准入时变更或验证资源的一些机制:

Kubernetes 中的变更和验证准入控制
机制 描述 使用场景
变更性准入策略 在准入前拦截 API 请求,并使用通用表达式语言(CEL)表达式进行必要的修改。
  • 执行资源准入前必须发生的关键修改。
  • 执行需要高级逻辑的复杂修改,例如调用外部 API。
变更性准入策略 在准入前拦截 API 请求,并使用通用表达式语言(CEL)表达式进行必要的修改。
  • 执行资源准入前必须发生的关键修改。
  • 执行简单的修改,例如调整标签或副本数量。
验证性准入策略 在准入前拦截 API 请求,并根据复杂的策略声明进行验证。
  • 在资源准入前验证关键配置。
  • 在准入前执行复杂的策略逻辑。
验证性准入策略 在准入前拦截 API 请求,并根据通用表达式语言(CEL)表达式进行验证。
  • 在资源准入前验证关键配置。
  • 使用 CEL 表达式执行策略逻辑。

一般来说,当你希望以可扩展的方式声明或配置逻辑时,可以使用 Webhook 准入控制。 当你希望声明更简单的逻辑而无需运行 Webhook 服务器的开销时,可以使用基于 CEL 的内置准入控制。Kubernetes 项目建议在可能的情况下使用基于 CEL 的准入控制。

为 CustomResourceDefinitions 使用内置验证和默认值

如果你使用 CustomResourceDefinitions, 请勿使用准入 Webhook 来验证 CustomResource 规约中的值,或者为其中的字段设置默认值。 Kubernetes 允许你在创建 CustomResourceDefinitions 时定义验证规则和字段的默认值。

要了解更多,请参阅以下资源:

性能和延迟

本节描述的是一些可以提高性能和减少延迟的建议。总结如下:

  • 整合 Webhook 并限制每个 Webhook 的 API 调用次数。
  • 使用审计日志检查反复执行相同操作的 Webhook。
  • 使用负载均衡确保 Webhook 的可用性。
  • 为每个 Webhook 设置较小的超时值。
  • 在设计 Webhook 时考虑集群的可用性需求。

设计低延迟的准入 Webhook

变更性质的准入 Webhook 是按顺序调用的。根据变更性质 Webhook 的设置,某些 Webhook 可能会被多次调用。对变更性质的 Webhook 的每次调用都会增加准入过程的延迟。 这一点与验证性质的 Webhook 不同,验证性质的 Webhook 是被并行调用的。

在设计你的变更性质 Webhook 时,请考虑你的延迟要求和容忍度。集群中的变更性 Webhook 越多, 延迟增加的可能性就越大。

考虑以下措施以减少延迟:

  • 整合对不同对象执行类似变更的 Webhook。
  • 减少变更性质 Webhook 服务器逻辑中进行的 API 调用次数。
  • 限制每个变更性质 Webhook 对应的匹配条件,以减少特定 API 请求所触发的 Webhook 数量。
  • 将多个小型的 Webhook 整合到一个服务器和配置中,以帮助进行排序和组织。

防止由相互竞争的控制器所引起的循环处理

考虑集群中运行的其他可能与你的 Webhook 所做的变更发生冲突的组件。例如,如果你的 Webhook 要添加某个标签,而另一个控制器要删除该标签,那么你的 Webhook 会被再次调用,从而导致循环处理。

要检测这些循环,可以尝试以下方法:

  1. 更新集群的审计策略以记录审计事件。使用以下参数:

    • level: RequestResponse
    • verbs: ["patch"]
    • omitStages: RequestReceived

    设置审计规则,为你的 Webhook 所变更的特定资源创建事件。

  2. 检查审计事件,查看是否有 Webhook 被多次重新调用并应用了相同的补丁到同一个对象的情况, 或者某个对象的字段被多次更新和回滚的情况。

设置较小的超时值

准入性质的 Webhook 应尽可能快速评估(通常在毫秒级别),因为它们会增加 API 请求的延迟。 为 Webhook 设置较小的超时值。

更多详细信息,请参见超时

使用负载均衡器确保 Webhook 可用性

准入性质的 Webhook 应该利用某种形式的负载均衡来提供高可用性和性能优势。 如果 Webhook 在集群内运行,你可以在类型为 ClusterIP 的服务后面运行多个 Webhook 后端。

这样可以确保请求被均匀分配到不同的后端实例上,提高处理能力和可靠性。

使用高可用部署模型

在设计 Webhook 时,请考虑集群的可用性需求。例如,在节点停机或可用区中断期间, Kubernetes 会将一些 Pod 标记为 NotReady,以便负载均衡器可以将流量重新路由到可用的可用区和节点。 这些对 Pod 的更新可能会触发你的变更性 Webhook。取决于受影响 Pod 的数量,变更性 Webhook 服务器有超时或导致 Pod 处理延迟的风险。结果是,流量不会像你所需要的那样被快速地重新路由。

在编写 Webhook 时,请考虑上述示例中的情况。排除那些由 Kubernetes 为响应不可避免的事件所执行的操作。

请求过滤

本节提供关于过滤哪些请求以触发特定 Webhook 的建议。总结如下:

  • 限制 Webhook 的作用范围,避免处理系统组件和只读请求。
  • 将 Webhook 限制到特定的名字空间。
  • 使用匹配条件执行细粒度的请求过滤。
  • 匹配对象的所有版本。

限制每个 Webhook 的作用范围

准入性质的 Webhook 仅在 API 请求与相应的 Webhook 配置匹配时才会被调用。 限制每个 Webhook 的作用范围,以减少对 Webhook 服务器的不必要调用。 考虑以下作用范围限制:

  • 避免匹配 kube-system 命名空间中的对象。如果你在 kube-system 名字空间中运行自己的 Pod,请使用 objectSelector 来避免对关键工作负载进行变更。
  • 不要对节点租约(Node Leases)进行变更,这些租约以 Lease 对象的形式存在于 kube-node-lease 系统命名空间中。对节点租约进行变更可能会导致节点升级失败。 只有在你确信验证控制不会对集群造成风险时,才对这个命名空间中的 Lease 对象应用验证规则。
  • 不要对 TokenReview 或 SubjectAccessReview 对象进行变更。这些始终是只读请求。 修改这些对象可能会破坏你的集群。
  • 使用 namespaceSelector 将每个 Webhook 限制到特定的名字空间上。

使用匹配条件过滤特定请求

准入控制器允许你使用多个字段来匹配符合特定条件的请求。例如, 你可以使用 namespaceSelector 来过滤针对特定命名空间的请求。

为了实现更细粒度的请求过滤,可以在 Webhook 配置中使用 matchConditions 字段。 该字段允许你编写多个 CEL 表达式,只有当这些表达式都评估为 true 时, 请求才会触发你的准入 Webhook。使用 matchConditions 可能会显著减少对 Webhook 服务器的调用次数。

更多详细信息,请参见匹配请求:matchConditions

匹配 API 的所有版本

默认情况下,系统会针对针对影响指定资源的所有 API 版本运行准入 Webhook。Webhook 配置中的 matchPolicy 字段控制此行为。在 matchPolicy 字段中指定值为 Equivalent 或省略该字段,以允许 Webhook 对所有 API 版本起作用。

更多详细信息,请参见匹配请求:matchPolicy

变更范围和字段注意事项

本节提供关于变更范围和对象字段特殊考虑的建议。总结如下:

  • 仅修补需要修补的字段。
  • 不要覆盖数组值。
  • 尽可能避免在变更中产生副作用。
  • 避免自我变更。
  • 以开放的形式失败并验证最终状态。
  • 为未来版本中对字段执行变更作规划。
  • 防止 Webhook 自我触发。
  • 不要更改不可变更的对象。

仅修补必要的字段

准入 Webhook 服务器发送 HTTP 响应来指示如何处理特定的 Kubernetes API 请求。 此响应是一个 AdmissionReview 对象。通过使用响应中的 patchType 字段和 patch 字段, 变更性 Webhook 可以添加具体的字段进行变更,之后才允许准入。确保你仅修改需要更改的字段。

例如,考虑一个配置为确保 web-server 部署至少具有三个副本的变更性质 Webhook。 当创建 Deployment 对象的某个请求与你的 Webhook 配置匹配时,Webhook 应仅更新 spec.replicas 字段中的值。

不要覆盖数组值

Kubernetes 对象规约中的字段可能包含数组。有些数组包含键值对 (如容器规约中的 envVar 字段),而其他数组则没有键(如 Pod 规约中的 readinessGates 字段)。 在某些情况下,数组字段中值的顺序可能很重要。例如,容器规约中 args 字段的参数顺序可能会影响容器。

在修改数组时,要考虑以下几点:

  • 尽可能使用 add JSONPatch 操作,而不是 replace,以避免意外替换掉必需的值。
  • 将不使用键值对的数组视为集合来处理。
  • 确保你所要修改的字段中的值不需要特定的顺序。
  • 除非绝对必要,否则不要覆盖现有的键值对。
  • 在修改标签字段时要小心。意外的修改可能会导致标签选择器失效,从而引发意外行为。

避免副作用

确保你的 Webhook 仅操作发送给它们的 AdmissionReview 内容, 而不进行带外更改。这些额外的更改(称为“副作用”)如果未妥善协调, 可能会在准入期间引发冲突。如果 Webhook 没有任何副作用,则应将 .webhooks[].sideEffects 字段设置为 None

如果在准入评估期间需要副作用,则必须在处理 dryRun 设置为 true 的 AdmissionReview 对象时抑制这些副作用,并且应将 .webhooks[].sideEffects 字段设置为 NoneOnDryRun

更多详细信息,请参见副作用

避免自我变更

在集群内运行的 Webhook 可能会因为其自身的部署拦截了启动自身 Pod 所需的资源而导致死锁。

例如,你可能配置了一个变更性质的准入 Webhook,仅当 Pod 中设置了特定标签(如 env: prod)时才允许创建 Pod 请求,而 Webhook 服务器却运行在一个没有设置 env 标签的 Deployment 中。

当运行 Webhook 服务器 Pod 的节点变得不健康时,Webhook 的 Deployment 会尝试将这些 Pod 重新调度到另一个节点。然而,由于 env 标签未设置, 现有的 Webhook 服务器会拒绝这些请求。结果是,迁移无法完成。

通过 namespaceSelector 排除运行 Webhook 的命名空间,以避免此问题。

避免依赖循环

依赖循环可能在如下场景中发生:

  • 两个 Webhook 相互检查对方的 Pod。如果这两个 Webhook 同时变得不可用, 那么任何一个 Webhook 都无法启动。
  • 你的 Webhook 拦截了集群插件组件(如网络插件或存储插件),而这些插件是 Webhook 所依赖的。如果 Webhook 和依赖的插件同时变得不可用,则两个组件都无法正常工作。

为了避免这种循环依赖,可以尝试以下方法:

失败时开放并验证最终状态

变更性质的准入 Webhook 支持 failurePolicy 配置字段。此字段指示如果 Webhook 失败,API 服务器是应允许还是拒绝请求。Webhook 失败可能是由于超时或服务器逻辑中的错误造成的。

默认情况下,准入 Webhook 将 failurePolicy 字段设置为 Fail。 如果 Webhook 失败,API 服务器将拒绝该请求。然而,默认情况下拒绝请求可能会导致在 Webhook 停机期间合规的请求也被拒绝。

通过将 failurePolicy 字段设置为 Ignore,可以让你的变更性质 Webhook 在失败时更为“开放”。 使用验证控制器检查请求的状态,确保它们符合你的策略。

这种方法有以下好处:

  • 变更性 Webhook 的停机不会影响合规资源的部署。
  • 策略执行发生在验证准入控制阶段。
  • 变更性 Webhooks 不会干扰集群中的其他控制器。

为未来的字段更新做计划

通常,在设计 Webhook 时应假设 Kubernetes API 可能在后续版本中会发生变化。 不要编写一个理所当然地认为某个 API 是稳定的服务器。例如,Kubernetes 中 Sidecar 容器的发布为 Pod API 添加了一个 restartPolicy 字段。

防止 Webhook 自我触发

响应广泛 API 请求的变更性质的 Webhook 可能会无意中触发自身。例如,考虑一个响应集群内所有请求的 Webhook。如果配置该 Webhook 为每次变更创建 Event 对象,则它会对自己的 Event 对象创建请求作出响应。

为了避免这种情况,可以考虑在 Webhook 创建的任何资源中设置一个唯一的标签, 并将此标签从 Webhook 的匹配条件中排除。

不要更改不可变更的对象

API 服务器中的一些 Kubernetes 对象是不可更改的。例如, 当你部署一个静态 Pod 时, 节点上的 kubelet 会在 API 服务器中创建一个镜像 Pod 来跟踪该静态 Pod。然而,对镜像 Pod 的更改不会被传播到静态 Pod。

不要在准入期间尝试对这些对象进行变更。所有镜像 Pod 都带有 kubernetes.io/config.mirror 注解。为了在排除镜像 Pod 的同时降低忽略注解的安全风险,可以仅允许静态 Pod 在特定的名字空间中运行。

变更性质 Webhook 的顺序与幂等性

本节提供关于 Webhook 顺序设计和幂等性 Webhook 的建议。总结如下:

  • 不要依赖特定的执行顺序。
  • 在准入前验证变更。
  • 检查是否存在其他控制器覆盖的变更。
  • 确保整个变更性 Webhook 集合是幂等的,而不仅仅是单个 Webhook 具有幂等性。

不要依赖变更准入 Webhook 的调用顺序

变更准入 Webhook 的执行顺序并不固定。某些因素可能会改变特定 Webhook 被调用的时机。不要指望你的 Webhook 在准入流程中的某个特定点运行, 因为其他 Webhook 仍可能对你所修改的对象进行进一步变更。

以下建议可能有助于最小化意外更改的风险:

确保集群中的变更准入 Webhook 具有幂等性

每个变更性质的准入 Webhook 都应该是幂等的。Webhook 应能够在已经修改过的对象上运行, 而不会在原始更改之外产生额外的更改。

此外,集群中的所有变更性质的 Webhook 集合也应当是幂等的。在准入控制的变更阶段结束后, 每个变更性质的 Webhook 都应能够针对该对象运行而不会对该对象产生额外的更改。

取决于你的环境,确保大规模幂等性可能会具有挑战性。以下建议可能有所帮助:

  • 使用验证性质的准入控制器来对关键工作负载的最终状态进行检查。
  • 在测试集群中测试你的部署,查看是否有对象被同一个 Webhook 多次修改。
  • 确保每个变更性 Webhook 的作用范围具体且受限。

以下示例展示的是一些幂等的变更逻辑:

  1. 对于 create Pod 请求,将 Pod 的字段 .spec.securityContext.runAsNonRoot 设置为 true

  2. 对于 create Pod 请求,如果容器的字段 .spec.containers[].resources.limits 未设置,则设置默认的资源限制。

  3. 对于 create Pod 请求,如果不存在名为 foo-sidecar 的容器, 则注入一个名为 foo-sidecar 的边车容器。

在这些情况下,Webhook 可以被安全地重新调用,或者允许已经设置了相关字段的对象通过准入控制。

以下示例展示了非幂等的变更逻辑:

  1. 对于 create Pod 请求,注入一个名称为 foo-sidecar 并附加当前时间戳的边车容器(例如 foo-sidecar-19700101-000000)。

    重新调用 Webhook 可能会导致同一个边车容器被多次注入到 Pod 中, 每次使用不同的容器名称。同样,如果边车容器已经存在于用户提供的 Pod 中, Webhook 也可能注入重复的容器。

  1. 对于 create/update Pod 请求,如果 Pod 已设置标签 env,则拒绝请求; 否则,向 Pod 添加标签 env: prod

    重新调用 Webhook 将导致 Webhook 在面对自身的输出时失败。

  1. 对于 create Pod 请求,在不检查是否已存在名为 foo-sidecar 的容器的情况下,追加一个名为 foo-sidecar 的边车容器。

    重新调用 Webhook 将导致 Pod 中出现重复的容器,这会使请求无效并被 API 服务器拒绝。

变更的测试与验证

本节提供关于测试变更性质 Webhook 和对已变更对象进行检验的建议。总结如下:

  • 在测试环境中测试 Webhook。
  • 避免违反验证规则的变更。
  • 测试小版本升级时的回归和冲突。
  • 在准入前验证变更的对象。

本节提供关于测试变更性质 Webhook 和对已变更对象进行检验的建议。总结如下:

稳健的测试应该是你发布新的 Webhook 或更新现有 Webhook 的核心部分。如果可能的话, 在一个与生产集群相似的预发布(staging)环境中测试对集群 Webhook 的所有更改。至少, 考虑使用 minikubekind 等工具创建一个小的测试集群来进行 Webhook 的更改测试。

确保变更不会违反验证规则

你的变更性质 Webhook 不应破坏对象在被准入前将被应用的任何验证规则。例如,考虑一个将 Pod 的默认 CPU 请求设置为特定值的变更性质 Webhook。如果该 Pod 的 CPU 限制设置为低于变更后的请求值,则该 Pod 将无法通过准入。

针对集群中运行的验证规则测试每个变更性质的 Webhook。

测试小版本升级以确保一致的行为

在将生产集群升级到新的小版本之前,在一个预发布环境中测试你的 Webhook 和工作负载。 比较结果,确保升级后你的 Webhook 仍能按预期运行。

此外,使用以下资源来了解 API 变更的相关信息:

在准入前验证变更

变更性质的 Webhook 会在所有验证性质的 Webhook 运行之前完成运行。 多项变更在对象的应用顺序并不稳定。因此,你所作的变更可能会被后续运行的变更性 Webhook 覆盖。

可以添加如 ValidatingAdmissionWebhook 或 ValidatingAdmissionPolicy 这样的验证性准入控制器到你的集群中,以确保你的变更是仍然存在的。例如, 考虑一个变更性质的 Webhook,它将 restartPolicy: Always 字段插入特定的初始化容器中, 使它们作为边车容器运行。你可以运行一个验证 Webhook 来确保这些初始化容器在所有变更完成后仍保留 restartPolicy: Always 配置。

详情请参阅以下资源:

变更性 Webhook 的部署

本节给出关于部署变更性准入 Webhook 的建议。总结如下:

  • 逐步推出 Webhook 配置,并按名字空间监控可能出现的问题。
  • 限制对 Webhook 配置资源的编辑访问权限。
  • 如果服务器位于集群内,则限制对运行 Webhook 服务器的命名空间的访问权限。

安装、启用变更性 Webhook

当你准备将变更性质的 Webhook 部署到集群时,请按照以下操作顺序进行:

  1. 安装 Webhook 服务器并启动它。
  2. 在 MutatingWebhookConfiguration 清单中将 failurePolicy 字段设置为 Ignore。这样可以避免因 Webhook 配置错误而导致的干扰。
  3. 在 MutatingWebhookConfiguration 清单中将 namespaceSelector 字段设置为一个测试命名空间。
  4. 将 MutatingWebhookConfiguration 部署到你的集群中。

在测试命名空间中监控 Webhook,检查是否有任何问题,然后将 Webhook 推广到其他命名空间。如果 Webhook 拦截了不应拦截的 API 请求, 请暂停推广并调整 Webhook 配置的范围。

限制对变更性 Webhook 的编辑访问

变更性质的 Webhook 是一种强大的 Kubernetes 控制器。使用 RBAC 或其他鉴权机制来限制对你的 Webhook 和服务器的编辑访问权限。 对于 RBAC,确保只有受信任的实体才可以具有以下访问权限:

  • 动词:create(创建)、update(更新)、patch(补丁)、 delete(删除)、deletecollection(删除集合)
  • API 组:admissionregistration.k8s.io/v1
  • API 资源类型:MutatingWebhookConfigurations

如果你的变更性 Webhook 的服务器在集群内运行,请限制对该命名空间中任何资源的创建或修改权限。

良好实现的示例

以下项目是“良好的”自定义 Webhook 服务器实现的示例。在设计你自己的 Webhook 时, 可以将它们作为起点。请勿直接使用这些示例,而是应根据你的具体环境进行调整和设计。

接下来

6 - 日志架构

应用日志可以让你了解应用内部的运行状况。日志对调试问题和监控集群活动非常有用。 大部分现代化应用都有某种日志记录机制。同样地,容器引擎也被设计成支持日志记录。 针对容器化应用,最简单且最广泛采用的日志记录方式就是写入标准输出和标准错误流。

但是,由容器引擎或运行时提供的原生功能通常不足以构成完整的日志记录方案。

例如,如果发生容器崩溃、Pod 被逐出或节点宕机等情况,你可能想访问应用日志。

在集群中,日志应该具有独立的存储,并且其生命周期与节点、Pod 或容器的生命周期相独立。 这个概念叫集群级的日志

集群级日志架构需要一个独立的后端用来存储、分析和查询日志。 Kubernetes 并不为日志数据提供原生的存储解决方案。 相反,有很多现成的日志方案可以集成到 Kubernetes 中。 下面各节描述如何在节点上处理和存储日志。

Pod 和容器日志

Kubernetes 从正在运行的 Pod 中捕捉每个容器的日志。

此示例使用带有一个容器的 Pod 的清单,该容器每秒将文本写入标准输出一次。

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox:1.28
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

要运行此 Pod,请执行以下命令:

kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml

输出为:

pod/counter created

要获取这些日志,请执行以下 kubectl logs 命令:

kubectl logs counter

输出类似于:

0: Fri Apr  1 11:42:23 UTC 2022
1: Fri Apr  1 11:42:24 UTC 2022
2: Fri Apr  1 11:42:25 UTC 2022

你可以使用 kubectl logs --previous 从容器的先前实例中检索日志。 如果你的 Pod 有多个容器,请如下通过将容器名称追加到该命令并使用 -c 标志来指定要访问哪个容器的日志:

kubectl logs counter -c count

容器日志流

特性状态: Kubernetes v1.32 [alpha] (enabled by default: false)

作为一种 Alpha 特性,kubelet 可以将容器产生的两个标准流的日志分开: 标准输出标准错误输出。 要使用此行为,你必须启用 PodLogsQuerySplitStreams 特性门控。 启用该特性门控后,Kubernetes 1.32 允许通过 Pod API 直接访问这些日志流。 你可以通过指定流名称(StdoutStderr)使用 stream 查询字符串来获取特定的流。 你必须具有读取该 Pod 的 log 子资源的权限。

要演示此特性,你可以创建一个定期向标准输出和标准错误流中写入文本的 Pod。

apiVersion: v1
kind: Pod
metadata:
  name: counter-err
spec:
  containers:
  - name: count
    image: busybox:1.28
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; echo "$i: err" >&2 ; i=$((i+1)); sleep 1; done']

要运行此 Pod,使用以下命令:

kubectl apply -f https://k8s.io/examples/debug/counter-pod-err.yaml

要仅获取 stderr 日志流,你可以运行以下命令:

kubectl get --raw "/api/v1/namespaces/default/pods/counter-err/log?stream=Stderr"

详见 kubectl logs 文档

节点的容器日志处理方式

节点级别的日志记录

容器运行时对写入到容器化应用程序的 stdoutstderr 流的所有输出进行处理和转发。 不同的容器运行时以不同的方式实现这一点;不过它们与 kubelet 的集成都被标准化为 CRI 日志格式

默认情况下,如果容器重新启动,kubelet 会保留一个终止的容器及其日志。 如果一个 Pod 被逐出节点,所对应的所有容器及其日志也会被逐出。

kubelet 通过 Kubernetes API 的特殊功能将日志提供给客户端访问。 访问这个日志的常用方法是运行 kubectl logs

日志轮换

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

kubelet 负责轮换容器日志并管理日志目录结构。 kubelet(使用 CRI)将此信息发送到容器运行时,而运行时则将容器日志写到给定位置。

你可以使用 kubelet 配置文件配置两个 kubelet 配置选项containerLogMaxSize (默认 10Mi)和 containerLogMaxFiles(默认 5)。 这些设置分别允许你分别配置每个日志文件大小的最大值和每个容器允许的最大文件数。

为了在工作负载生成的日志量较大的集群中执行高效的日志轮换,kubelet 还提供了一种机制,基于可以执行多少并发日志轮换以及监控和轮换日志所需要的间隔来调整日志的轮换方式。 你可以使用 kubelet 配置文件 配置两个 kubelet 配置选项containerLogMaxWorkerscontainerLogMonitorInterval

当类似于基本日志示例一样运行 kubectl logs 时, 节点上的 kubelet 会处理请求并直接从日志文件读取。kubelet 将返回该日志文件的内容。

系统组件日志

系统组件有两种类型:通常在容器中运行的组件和直接参与容器运行的组件。例如:

  • kubelet 和容器运行时不在容器中运行。kubelet 运行你的容器 (一起按 Pod 分组)
  • Kubernetes 调度器、控制器管理器和 API 服务器在 Pod 中运行 (通常是静态 Pod。 etcd 组件在控制平面中运行,最常见的也是作为静态 Pod。 如果你的集群使用 kube-proxy,则通常将其作为 DaemonSet 运行。

日志位置

kubelet 和容器运行时写入日志的方式取决于节点使用的操作系统:

在使用 systemd 的 Linux 节点上,kubelet 和容器运行时默认写入 journald。 你要使用 journalctl 来阅读 systemd 日志;例如:journalctl -u kubelet

如果 systemd 不存在,kubelet 和容器运行时将写入到 /var/log 目录中的 .log 文件。 如果你想将日志写入其他地方,你可以通过辅助工具 kube-log-runner 间接运行 kubelet, 并使用该工具将 kubelet 日志重定向到你所选择的目录。

默认情况下,kubelet 指示你的容器运行时将日志写入 /var/log/pods 中的目录。

有关 kube-log-runner 的更多信息,请阅读系统日志

默认情况下,kubelet 将日志写入目录 C:\var\logs 中的文件(注意这不是 C:\var\log)。

尽管 C:\var\log 是这些日志的 Kubernetes 默认位置, 但一些集群部署工具会将 Windows 节点设置为将日志放到 C:\var\log\kubelet

如果你想将日志写入其他地方,你可以通过辅助工具 kube-log-runner 间接运行 kubelet, 并使用该工具将 kubelet 日志重定向到你所选择的目录。

但是,kubelet 默认指示你的容器运行时在目录 C:\var\log\pods 中写入日志。

有关 kube-log-runner 的更多信息,请阅读系统日志


对于在 Pod 中运行的 Kubernetes 集群组件,其日志会写入 /var/log 目录中的文件, 相当于绕过默认的日志机制(组件不会写入 systemd 日志)。 你可以使用 Kubernetes 的存储机制将持久存储映射到运行该组件的容器中。

kubelet 允许将 Pod 日志目录从默认的 /var/log/pods 更改为自定义路径。 可以通过在 kubelet 的配置文件中配置 podLogsDir 参数来进行此调整。

有关 etcd 及其日志的详细信息,请查阅 etcd 文档。 同样,你可以使用 Kubernetes 的存储机制将持久存储映射到运行该组件的容器中。

集群级日志架构

虽然 Kubernetes 没有为集群级日志记录提供原生的解决方案,但你可以考虑几种常见的方法。 以下是一些选项:

  • 使用在每个节点上运行的节点级日志记录代理。
  • 在应用程序的 Pod 中,包含专门记录日志的边车(Sidecar)容器。
  • 将日志直接从应用程序中推送到日志记录后端。

使用节点级日志代理

使用节点级日志代理

你可以通过在每个节点上使用节点级的日志记录代理来实现集群级日志记录。 日志记录代理是一种用于暴露日志或将日志推送到后端的专用工具。 通常,日志记录代理程序是一个容器,它可以访问包含该节点上所有应用程序容器的日志文件的目录。

由于日志记录代理必须在每个节点上运行,推荐以 DaemonSet 的形式运行该代理。

节点级日志在每个节点上仅创建一个代理,不需要对节点上的应用做修改。

容器向标准输出和标准错误输出写出数据,但在格式上并不统一。 节点级代理收集这些日志并将其进行转发以完成汇总。

使用边车容器运行日志代理

你可以通过以下方式之一使用边车(Sidecar)容器:

  • 边车容器将应用程序日志传送到自己的标准输出。
  • 边车容器运行一个日志代理,配置该日志代理以便从应用容器收集日志。

传输数据流的边车容器

带数据流容器的边车容器

利用边车容器,写入到自己的 stdoutstderr 传输流, 你就可以利用每个节点上的 kubelet 和日志代理来处理日志。 边车容器从文件、套接字或 journald 读取日志。 每个边车容器向自己的 stdoutstderr 流中输出日志。

这种方法允许你将日志流从应用程序的不同部分分离开,其中一些可能缺乏对写入 stdoutstderr 的支持。重定向日志背后的逻辑是最小的,因此它的开销不大。 另外,因为 stdoutstderr 由 kubelet 处理,所以你可以使用内置的工具 kubectl logs

例如,某 Pod 中运行一个容器,且该容器使用两个不同的格式写入到两个不同的日志文件。 下面是这个 Pod 的清单:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox:1.28
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done      
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

不建议在同一个日志流中写入不同格式的日志条目,即使你成功地将其重定向到容器的 stdout 流。 相反,你可以创建两个边车容器。每个边车容器可以从共享卷跟踪特定的日志文件, 并将文件内容重定向到各自的 stdout 流。

下面是运行两个边车容器的 Pod 的清单:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox:1.28
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done      
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-1
    image: busybox:1.28
    args: [/bin/sh, -c, 'tail -n+1 -F /var/log/1.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-2
    image: busybox:1.28
    args: [/bin/sh, -c, 'tail -n+1 -F /var/log/2.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

现在当你运行这个 Pod 时,你可以运行如下命令分别访问每个日志流:

kubectl logs counter count-log-1

输出类似于:

0: Fri Apr  1 11:42:26 UTC 2022
1: Fri Apr  1 11:42:27 UTC 2022
2: Fri Apr  1 11:42:28 UTC 2022
...
kubectl logs counter count-log-2

输出类似于:

Fri Apr  1 11:42:29 UTC 2022 INFO 0
Fri Apr  1 11:42:30 UTC 2022 INFO 0
Fri Apr  1 11:42:31 UTC 2022 INFO 0
...

如果你在集群中安装了节点级代理,由代理自动获取上述日志流,而无需任何进一步的配置。 如果你愿意,你可以将代理配置为根据源容器解析日志行。

即使对于 CPU 和内存使用率较低的 Pod(CPU 为几毫核,内存为几兆字节),将日志写入一个文件, 将这些日志流写到 stdout 也有可能使节点所需的存储量翻倍。 如果你有一个写入特定文件的应用程序,则建议将 /dev/stdout 设置为目标文件,而不是采用流式边车容器方法。

边车容器还可用于轮换应用程序本身无法轮换的日志文件。 这种方法的一个例子是定期运行 logrotate 的小容器。 但是,直接使用 stdoutstderr 更直接,而将轮换和保留策略留给 kubelet。

集群中安装的节点级代理会自动获取这些日志流,而无需进一步配置。 如果你愿意,你也可以配置代理程序来解析源容器的日志行。

注意,尽管 CPU 和内存使用率都很低(以多个 CPU 毫核指标排序或者按内存的兆字节排序), 向文件写日志然后输出到 stdout 流仍然会成倍地增加磁盘使用率。 如果你的应用向单一文件写日志,通常最好设置 /dev/stdout 作为目标路径, 而不是使用流式的边车容器方式。

如果应用程序本身不能轮换日志文件,则可以通过边车容器实现。 这种方式的一个例子是运行一个小的、定期轮换日志的容器。 然而,还是推荐直接使用 stdoutstderr,将日志的轮换和保留策略交给 kubelet。

具有日志代理功能的边车容器

含日志代理的边车容器

如果节点级日志记录代理程序对于你的场景来说不够灵活, 你可以创建一个带有单独日志记录代理的边车容器,将代理程序专门配置为与你的应用程序一起运行。

下面是两个配置文件,可以用来实现一个带日志代理的边车容器。 第一个文件包含用来配置 fluentd 的 ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluentd.conf: |
    <source>
      type tail
      format none
      path /var/log/1.log
      pos_file /var/log/1.log.pos
      tag count.format1
    </source>

    <source>
      type tail
      format none
      path /var/log/2.log
      pos_file /var/log/2.log.pos
      tag count.format2
    </source>

    <match **>
      type google_cloud
    </match>    

第二个清单描述了一个运行 fluentd 边车容器的 Pod。 该 Pod 挂载一个卷,fluentd 可以从这个卷上拣选其配置数据。

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox:1.28
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done      
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-agent
    image: registry.k8s.io/fluentd-gcp:1.30
    env:
    - name: FLUENTD_ARGS
      value: -c /etc/fluentd-config/fluentd.conf
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: config-volume
      mountPath: /etc/fluentd-config
  volumes:
  - name: varlog
    emptyDir: {}
  - name: config-volume
    configMap:
      name: fluentd-config

从应用中直接暴露日志目录

直接从应用程序暴露日志

从各个应用中直接暴露和推送日志数据的集群日志机制已超出 Kubernetes 的范围。

接下来

7 - Kubernetes 控制平面组件的兼容版本

自 v1.32 版本发布以来,我们为 Kubernetes 控制平面组件引入了可配置的版本兼容性和仿真选项, 为集群管理员提供更多控制选项并增加可用的细粒度步骤来让升级更加安全。

仿真版本

仿真选项通过控制平面组件的 --emulated-version 参数来设置。 此选项允许控制平面组件仿真 Kubernetes 早期版本的行为(API、特性等)。

使用时,可用的功能将与仿真版本相匹配:

  • 在仿真版本之后引入的所有功能在二进制版本中将不可用。
  • 在仿真版本之后移除的所有功能将可用。

这使得特定 Kubernetes 版本的二进制文件能够以足够的精确度仿真之前某个版本的行为, 与其他系统组件的互操作性可以在仿真版本中进行定义。

--emulated-version 必须小于或等于 binaryVersion。 有关支持的仿真版本范围,参阅 --emulated-version 参数的帮助信息。

8 - Kubernetes 系统组件指标

通过系统组件指标可以更好地了解系统组个内部发生的情况。系统组件指标对于构建仪表板和告警特别有用。

Kubernetes 组件以 Prometheus 格式生成度量值。 这种格式是结构化的纯文本,旨在使人和机器都可以阅读。

Kubernetes 中组件的指标

在大多数情况下,可以通过 HTTP 访问组件的 /metrics 端点来获取组件的度量值。 对于那些默认情况下不暴露端点的组件,可以使用 --bind-address 标志启用。

这些组件的示例:

在生产环境中,你可能需要配置 Prometheus 服务器或 某些其他指标搜集器以定期收集这些指标,并使它们在某种时间序列数据库中可用。

请注意,kubelet 还会在 /metrics/cadvisor/metrics/resource/metrics/probes 端点中公开度量值。这些度量值的生命周期各不相同。

如果你的集群使用了 RBAC, 则读取指标需要通过基于用户、组或 ServiceAccount 的鉴权,要求具有允许访问 /metrics 的 ClusterRole。 例如:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus
rules:
  - nonResourceURLs:
      - "/metrics"
    verbs:
      - get

指标生命周期

Alpha 指标 → 稳定的指标 → 弃用的指标 → 隐藏的指标 → 删除的指标

Alpha 指标没有稳定性保证。这些指标可以随时被修改或者删除。

稳定的指标可以保证不会改变。这意味着:

  • 稳定的、不包含已弃用(deprecated)签名的指标不会被删除(或重命名)
  • 稳定的指标的类型不会被更改

已弃用的指标最终将被删除,不过仍然可用。 这类指标包含注解,标明其被废弃的版本。

例如:

  • 被弃用之前:

    # HELP some_counter this counts things
    # TYPE some_counter counter
    some_counter 0
    
  • 被弃用之后:

    # HELP some_counter (Deprecated since 1.15.0) this counts things
    # TYPE some_counter counter
    some_counter 0
    

隐藏的指标不会再被发布以供抓取,但仍然可用。 要使用隐藏指标,请参阅显式隐藏指标节。

删除的指标不再被发布,亦无法使用。

显示隐藏指标

如上所述,管理员可以通过设置可执行文件的命令行参数来启用隐藏指标, 如果管理员错过了上一版本中已经弃用的指标的迁移,则可以把这个用作管理员的逃生门。

show-hidden-metrics-for-version 标志接受版本号作为取值,版本号给出 你希望显示该发行版本中已弃用的指标。 版本表示为 x.y,其中 x 是主要版本,y 是次要版本。补丁程序版本不是必须的, 即使指标可能会在补丁程序发行版中弃用,原因是指标弃用策略规定仅针对次要版本。

该参数只能使用前一个次要版本。如果管理员将先前版本设置为 show-hidden-metrics-for-version, 则先前版本中隐藏的度量值会再度生成。不允许使用过旧的版本,因为那样会违反指标弃用策略。

以指标 A 为例,此处假设 A 在 1.n 中已弃用。根据指标弃用策略,我们可以得出以下结论:

  • 在版本 1.n 中,这个指标已经弃用,且默认情况下可以生成。
  • 在版本 1.n+1 中,这个指标默认隐藏,可以通过命令行参数 show-hidden-metrics-for-version=1.n 来再度生成。
  • 在版本 1.n+2 中,这个指标就将被从代码中移除,不会再有任何逃生窗口。

如果你要从版本 1.12 升级到 1.13,但仍依赖于 1.12 中弃用的指标 A,则应通过命令行设置隐藏指标: --show-hidden-metrics=1.12,并记住在升级到 1.14 版本之前删除此指标依赖项。

组件指标

kube-controller-manager 指标

控制器管理器指标可提供有关控制器管理器性能和运行状况的重要洞察。 这些指标包括通用的 Go 语言运行时指标(例如 go_routine 数量)和控制器特定的度量指标, 例如可用于评估集群运行状况的 etcd 请求延迟或云提供商(AWS、GCE、OpenStack)的 API 延迟等。

从 Kubernetes 1.7 版本开始,详细的云提供商指标可用于 GCE、AWS、Vsphere 和 OpenStack 的存储操作。 这些指标可用于监控持久卷操作的运行状况。

比如,对于 GCE,这些指标称为:

cloudprovider_gce_api_request_duration_seconds { request = "instance_list"}
cloudprovider_gce_api_request_duration_seconds { request = "disk_insert"}
cloudprovider_gce_api_request_duration_seconds { request = "disk_delete"}
cloudprovider_gce_api_request_duration_seconds { request = "attach_disk"}
cloudprovider_gce_api_request_duration_seconds { request = "detach_disk"}
cloudprovider_gce_api_request_duration_seconds { request = "list_disk"}

kube-scheduler 指标

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

调度器会暴露一些可选的指标,报告所有运行中 Pod 所请求的资源和期望的约束值。 这些指标可用来构造容量规划监控面板、访问调度约束的当前或历史数据、 快速发现因为缺少资源而无法被调度的负载,或者将 Pod 的实际资源用量与其请求值进行比较。

kube-scheduler 组件能够辩识各个 Pod 所配置的资源 请求和约束。 在 Pod 的资源请求值或者约束值非零时,kube-scheduler 会以度量值时间序列的形式 生成报告。该时间序列值包含以下标签:

  • 名字空间
  • Pod 名称
  • Pod 调度所处节点,或者当 Pod 未被调度时用空字符串表示
  • 优先级
  • 为 Pod 所指派的调度器
  • 资源的名称(例如,cpu
  • 资源的单位,如果知道的话(例如,cores

一旦 Pod 进入完成状态(其 restartPolicyNeverOnFailure,且 其处于 SucceededFailed Pod 阶段,或者已经被删除且所有容器都具有 终止状态),该时间序列停止报告,因为调度器现在可以调度其它 Pod 来执行。 这两个指标称作 kube_pod_resource_requestkube_pod_resource_limit

这些指标通过 HTTP 端点 /metrics/resources 公开出来。 访问 /metrics/resources 端点需要鉴权,通常通过对 /metrics/resources 非资源 URL 的 get 访问授予访问权限。

在 Kubernetes 1.21 中,你必须使用 --show-hidden-metrics-for-version=1.20 标志来公开 Alpha 级稳定性的指标。

禁用指标

你可以通过命令行标志 --disabled-metrics 来关闭某指标。 在例如某指标会带来性能问题的情况下,这一操作可能是有用的。 标志的参数值是一组被禁止的指标(例如:--disabled-metrics=metric1,metric2)。

指标顺序性保证

具有无限维度的指标可能会在其监控的组件中引起内存问题。 为了限制资源使用,你可以使用 --allow-metric-labels 命令行选项来为指标动态配置允许的标签值列表。

在 Alpha 阶段,此选项只能接受一组映射值作为可以使用的指标标签。 每个映射值的格式为<指标名称>,<标签名称>=<可用标签列表>,其中 <可用标签列表> 是一个用逗号分隔的、可接受的标签名的列表。

最终的格式看起来会是这样:

--allow-metric-labels <指标名称>,<标签名称>='<可用值1>,<可用值2>...', <指标名称2>,<标签名称>='<可用值1>, <可用值2>...', ...

下面是一个例子:

--allow-metric-labels number_count_metric,odd_number='1,3,5', number_count_metric,even_number='2,4,6', date_gauge_metric,weekend='Saturday,Sunday'

除了从 CLI 中指定之外,还可以在配置文件中完成此操作。 你可以使用组件的 --allow-metric-labels-manifest 命令行参数指定该配置文件的路径。 以下是该配置文件的内容示例:

"metric1,label2": "v1,v2,v3"
"metric2,label1": "v1,v2,v3"

此外,cardinality_enforcement_unexpected_categorizations_total 元指标记录基数执行期间意外分类的计数,即每当遇到允许列表约束不允许的标签值时。

接下来

9 - Kubernetes 对象状态的指标

kube-state-metrics,一个生成和公开集群层面指标的插件代理。

Kubernetes API 中 Kubernetes 对象的状态可以被公开为指标。 一个名为 kube-state-metrics 的插件代理可以连接到 Kubernetes API 服务器并公开一个 HTTP 端点,提供集群中各个对象的状态所生成的指标。 此代理公开了关于对象状态的各种信息,如标签和注解、启动和终止时间、对象当前所处的状态或阶段。 例如,针对运行在 Pod 中的容器会创建一个 kube_pod_container_info 指标。 其中包括容器的名称、所属的 Pod 的名称、Pod 所在的命名空间、 容器镜像的名称、镜像的 ID、容器规约中的镜像名称、运行中容器的 ID 和用作标签的 Pod ID。

有能力(例如通过 Prometheus)抓取 kube-state-metrics 端点的外部组件现在可用于实现以下使用场景。

示例:使用来自 kube-state-metrics 的指标查询集群状态

通过 kube-state-metrics 生成的系列指标有助于进一步洞察集群,因为这些指标可用于查询。

如果你使用 Prometheus 或其他采用相同查询语言的工具,则以下 PromQL 查询将返回未就绪的 Pod 数:

count(kube_pod_status_ready{condition="false"}) by (namespace, pod)

示例:基于 kube-state-metrics 发出警报

kube-state-metrics 生成的指标还允许在集群中出现问题时发出警报。

如果你使用 Prometheus 或类似采用相同警报规则语言的工具,若有某些 Pod 处于 Terminating 状态超过 5 分钟,将触发以下警报:

groups:
- name: Pod state
  rules:
  - alert: PodsBlockedInTerminatingState
    expr: count(kube_pod_deletion_timestamp) by (namespace, pod) * count(kube_pod_status_reason{reason="NodeLost"} == 0) by (namespace, pod) > 0
    for: 5m
    labels:
      severity: page
    annotations:
      summary: Pod {{$labels.namespace}}/{{$labels.pod}} blocked in Terminating state.

10 - 系统日志

系统组件的日志记录集群中发生的事件,这对于调试非常有用。 你可以配置日志的精细度,以展示更多或更少的细节。 日志可以是粗粒度的,如只显示组件内的错误, 也可以是细粒度的,如显示事件的每一个跟踪步骤(比如 HTTP 访问日志、pod 状态更新、控制器动作或调度器决策)。

Klog

klog 是 Kubernetes 的日志库。 klog 为 Kubernetes 系统组件生成日志消息。

Kubernetes 正在进行简化其组件日志的努力。下面的 klog 命令行参数从 Kubernetes v1.23 开始已被废弃, 在 Kubernetes v1.26 中被移除:

  • --add-dir-header
  • --alsologtostderr
  • --log-backtrace-at
  • --log-dir
  • --log-file
  • --log-file-max-size
  • --logtostderr
  • --one-output
  • --skip-headers
  • --skip-log-headers
  • --stderrthreshold

输出总会被写到标准错误输出(stderr)之上,无论输出格式如何。 对输出的重定向将由调用 Kubernetes 组件的软件来处理。 这一软件可以是 POSIX Shell 或者类似 systemd 这样的工具。

在某些场合下,例如对于无发行主体的(distroless)容器或者 Windows 系统服务, 这些替代方案都是不存在的。那么你可以使用 kube-log-runner 可执行文件来作为 Kubernetes 的封装层,完成对输出的重定向。 在很多 Kubernetes 基础镜像中,都包含一个预先构建的可执行程序。 这个程序原来称作 /go-runner,而在服务器和节点的发行版本库中,称作 kube-log-runner

下表展示的是 kube-log-runner 调用与 Shell 重定向之间的对应关系:

用法 POSIX Shell(例如 Bash) kube-log-runner <options> <cmd>
合并 stderr 与 stdout,写出到 stdout 2>&1 kube-log-runner(默认行为 )
将 stderr 与 stdout 重定向到日志文件 1>>/tmp/log 2>&1 kube-log-runner -log-file=/tmp/log
输出到 stdout 并复制到日志文件中 2>&1 | tee -a /tmp/log kube-log-runner -log-file=/tmp/log -also-stdout
仅将 stdout 重定向到日志 >/tmp/log kube-log-runner -log-file=/tmp/log -redirect-stderr=false

klog 输出

传统的 klog 原生格式示例:

I1025 00:15:15.525108       1 httplog.go:79] GET /api/v1/namespaces/kube-system/pods/metrics-server-v0.3.1-57c75779f-9p8wg: (1.512ms) 200 [pod_nanny/v0.0.0 (linux/amd64) kubernetes/$Format 10.56.1.19:51756]

消息字符串可能包含换行符:

I1025 00:15:15.525108       1 example.go:79] This is a message
which has a line break.

结构化日志

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

结构化日志记录旨在日志消息中引入统一结构,以便以编程方式提取信息。 你可以方便地用更小的开销来处理结构化日志。 生成日志消息的代码决定其使用传统的非结构化的 klog 还是结构化的日志。

默认的结构化日志消息是以文本形式呈现的,其格式与传统的 klog 保持向后兼容:

<klog header> "<message>" <key1>="<value1>" <key2>="<value2>" ...

示例:

I1025 00:15:15.525108       1 controller_utils.go:116] "Pod status updated" pod="kube-system/kubedns" status="ready"

字符串在输出时会被添加引号。其他数值类型都使用 %+v 来格式化,因此可能导致日志消息会延续到下一行, 具体取决于数据本身

I1025 00:15:15.525108       1 example.go:116] "Example" data="This is text with a line break\nand \"quotation marks\"." someInt=1 someFloat=0.1 someStruct={StringField: First line,
second line.}

上下文日志

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

上下文日志建立在结构化日志之上。 它主要是关于开发人员如何使用日志记录调用:基于该概念的代码将更加灵活, 并且支持在结构化日志 KEP 中描述的额外用例。

如果开发人员在他们的组件中使用额外的函数,比如 WithValuesWithName, 那么日志条目将会包含额外的信息,这些信息会被调用者传递给函数。

对于 Kubernetes 1.32,这一特性是由 StructuredLogging 特性门控所控制的,默认启用。 这个基础设施是在 1.24 中被添加的,并不需要修改组件。 该 component-base/logs/example 命令演示了如何使用新的日志记录调用以及组件如何支持上下文日志记录。

$ cd $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/component-base/logs/example/cmd/
$ go run . --help
...
      --feature-gates mapStringBool  A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:
                                     AllAlpha=true|false (ALPHA - default=false)
                                     AllBeta=true|false (BETA - default=false)
                                     ContextualLogging=true|false (BETA - default=true)
$ go run . --feature-gates ContextualLogging=true
...
I0222 15:13:31.645988  197901 example.go:54] "runtime" logger="example.myname" foo="bar" duration="1m0s"
I0222 15:13:31.646007  197901 example.go:55] "another runtime" logger="example" foo="bar" duration="1h0m0s" duration="1m0s"

logger 键和 foo="bar" 会被函数的调用者添加上, 不需修改该函数,它就会记录 runtime 消息和 duration="1m0s" 值。

禁用上下文日志后,WithValuesWithName 什么都不会做, 并且会通过调用全局的 klog 日志记录器记录日志。 因此,这些附加信息不再出现在日志输出中:

$ go run . --feature-gates ContextualLogging=false
...
I0222 15:14:40.497333  198174 example.go:54] "runtime" duration="1m0s"
I0222 15:14:40.497346  198174 example.go:55] "another runtime" duration="1h0m0s" duration="1m0s"

JSON 日志格式

特性状态: Kubernetes v1.19 [alpha]

--logging-format=json 参数将日志格式从 klog 原生格式改为 JSON 格式。 JSON 日志格式示例(美化输出):

{
   "ts": 1580306777.04728,
   "v": 4,
   "msg": "Pod status updated",
   "pod":{
      "name": "nginx-1",
      "namespace": "default"
   },
   "status": "ready"
}

具有特殊意义的 key:

  • ts - Unix 时间风格的时间戳(必选项,浮点值)
  • v - 精细度(仅用于 info 级别,不能用于错误信息,整数)
  • err - 错误字符串(可选项,字符串)
  • msg - 消息(必选项,字符串)

当前支持 JSON 格式的组件列表:

日志精细度级别

参数 -v 控制日志的精细度。增大该值会增大日志事件的数量。 减小该值可以减小日志事件的数量。增大精细度会记录更多的不太严重的事件。 精细度设置为 0 时只记录关键(critical)事件。

日志位置

有两种类型的系统组件:运行在容器中的组件和不运行在容器中的组件。例如:

  • Kubernetes 调度器和 kube-proxy 在容器中运行。
  • kubelet 和容器运行时不在容器中运行。

在使用 systemd 的系统中,kubelet 和容器运行时写入 journald。 在别的系统中,日志写入 /var/log 目录下的 .log 文件中。 容器中的系统组件总是绕过默认的日志记录机制,写入 /var/log 目录下的 .log 文件。 与容器日志类似,你应该轮转 /var/log 目录下系统组件日志。 在 kube-up.sh 脚本创建的 Kubernetes 集群中,日志轮转由 logrotate 工具配置。 logrotate 工具,每天或者当日志大于 100MB 时,轮转日志。

日志查询

特性状态: Kubernetes v1.30 [beta] (enabled by default: false)

为了帮助在节点上调试问题,Kubernetes v1.27 引入了一个特性来查看节点上当前运行服务的日志。 要使用此特性,请确保已为节点启用了 NodeLogQuery 特性门控, 且 kubelet 配置选项 enableSystemLogHandlerenableSystemLogQuery 均被设置为 true。 在 Linux 上,我们假设可以通过 journald 查看服务日志。 在 Windows 上,我们假设可以在应用日志提供程序中查看服务日志。 在两种操作系统上,都可以通过读取 /var/log/ 内的文件查看日志。

假如你被授权与节点对象交互,你可以在所有节点或只是某个子集上试用此特性。 这里有一个从节点中检索 kubelet 服务日志的例子:

# 从名为 node-1.example 的节点中获取 kubelet 日志
kubectl get --raw "/api/v1/nodes/node-1.example/proxy/logs/?query=kubelet"

你也可以获取文件,前提是日志文件位于 kubelet 允许进行日志获取的目录中。 例如你可以从 Linux 节点上的 /var/log 中获取日志。

kubectl get --raw "/api/v1/nodes/<insert-node-name-here>/proxy/logs/?query=/<insert-log-file-name-here>"

kubelet 使用启发方式来检索日志。 如果你还未意识到给定的系统服务正将日志写入到操作系统的原生日志记录程序(例如 journald) 或 /var/log/ 中的日志文件,这会很有帮助。这种启发方式先检查原生的日志记录程序, 如果不可用,则尝试从 /var/log/<servicename>/var/log/<servicename>.log/var/log/<servicename>/<servicename>.log 中检索第一批日志。

可用选项的完整列表如下:

选项 描述
boot boot 显示来自特定系统引导的消息
pattern pattern 通过提供的兼容 PERL 的正则表达式来过滤日志条目
query query 是必需的,指定返回日志的服务或文件
sinceTime 显示日志的 RFC3339 起始时间戳(包含)
untilTime 显示日志的 RFC3339 结束时间戳(包含)
tailLines 指定要从日志的末尾检索的行数;默认为获取全部日志

更复杂的查询示例:

# 从名为 node-1.example 且带有单词 "error" 的节点中获取 kubelet 日志
kubectl get --raw "/api/v1/nodes/node-1.example/proxy/logs/?query=kubelet&pattern=error"

接下来

11 - 追踪 Kubernetes 系统组件

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

系统组件追踪功能记录各个集群操作的时延信息和这些操作之间的关系。

Kubernetes 组件基于 gRPC 导出器的 OpenTelemetry 协议 发送追踪信息,并用 OpenTelemetry Collector 收集追踪信息,再将其转交给追踪系统的后台。

追踪信息的收集

Kubernetes 组件具有内置的 gRPC 导出器,供 OTLP 导出追踪信息,可以使用 OpenTelemetry Collector, 也可以不使用 OpenTelemetry Collector。

关于收集追踪信息、以及使用收集器的完整指南,可参见 Getting Started with the OpenTelemetry Collector。 不过,还有一些特定于 Kubernetes 组件的事项值得注意。

默认情况下,Kubernetes 组件使用 gRPC 的 OTLP 导出器来导出追踪信息,将信息写到 IANA OpenTelemetry 端口。 举例来说,如果收集器以 Kubernetes 组件的边车模式运行, 以下接收器配置会收集 span 信息,并将它们写入到标准输出。

receivers:
  otlp:
    protocols:
      grpc:
exporters:
  # 用适合你后端环境的导出器替换此处的导出器
  logging:
    logLevel: debug
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [logging]

要在不使用收集器的情况下直接将追踪信息发送到后端,请在 Kubernetes 追踪配置文件中指定端点字段以及所需的追踪后端地址。 该方法不需要收集器并简化了整体结构。

对于追踪后端标头配置,包括身份验证详细信息,环境变量可以与 OTEL_EXPORTER_OTLP_HEADERS 一起使用,请参阅 OTLP 导出器配置

另外,对于 Kubernetes 集群名称、命名空间、Pod 名称等追踪资源属性配置, 环境变量也可以与 OTEL_RESOURCE_ATTRIBUTES 配合使用,请参见 OTLP Kubernetes 资源

组件追踪

kube-apiserver 追踪

kube-apiserver 为传入的 HTTP 请求、传出到 webhook 和 etcd 的请求以及重入的请求生成 span。 由于 kube-apiserver 通常是一个公开的端点,所以它通过出站的请求传播 W3C 追踪上下文, 但不使用入站请求的追踪上下文。

在 kube-apiserver 中启用追踪

要启用追踪特性,需要使用 --tracing-config-file=<<配置文件路径> 为 kube-apiserver 提供追踪配置文件。下面是一个示例配置,它为万分之一的请求记录 span,并使用了默认的 OpenTelemetry 端点。

apiVersion: apiserver.config.k8s.io/v1beta1
kind: TracingConfiguration
# 默认值
#endpoint: localhost:4317
samplingRatePerMillion: 100

有关 TracingConfiguration 结构体的更多信息,请参阅 API 服务器配置 API (v1beta1)

kubelet 追踪

特性状态: Kubernetes v1.27 [beta] (enabled by default: true)

kubelet CRI 接口和实施身份验证的 HTTP 服务器被插桩以生成追踪 span。 与 API 服务器一样,端点和采样率是可配置的。 追踪上下文传播也是可以配置的。始终优先采用父 span 的采样决策。 用户所提供的追踪配置采样率将被应用到不带父级的 span。 如果在没有配置端点的情况下启用,将使用默认的 OpenTelemetry Collector 接收器地址 “localhost:4317”。

在 kubelet 中启用追踪

要启用追踪,需应用追踪配置。 以下是 kubelet 配置的示例代码片段,每 10000 个请求中记录一个请求的 span,并使用默认的 OpenTelemetry 端点:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  KubeletTracing: true
tracing:
  # 默认值
  #endpoint: localhost:4317
  samplingRatePerMillion: 100

如果 samplingRatePerMillion 被设置为一百万 (1000000),则所有 span 都将被发送到导出器。

Kubernetes v1.32 中的 kubelet 收集与垃圾回收、Pod 同步例程以及每个 gRPC 方法相关的 Span。 kubelet 借助 gRPC 来传播跟踪上下文,以便 CRI-O 和 containerd 这类带有跟踪插桩的容器运行时可以在其导出的 Span 与 kubelet 所提供的跟踪上下文之间建立关联。所得到的跟踪数据会包含 kubelet 与容器运行时 Span 之间的父子链接关系,从而为调试节点问题提供有用的上下文信息。

请注意导出 span 始终会对网络和 CPU 产生少量性能开销,具体取决于系统的总体配置。 如果在启用追踪的集群中出现类似性能问题,可以通过降低 samplingRatePerMillion 或通过移除此配置来彻底禁用追踪来缓解问题。

稳定性

追踪工具仍在积极开发中,未来它会以多种方式发生变化。 这些变化包括:span 名称、附加属性、检测端点等等。 此类特性在达到稳定版本之前,不能保证追踪工具的向后兼容性。

接下来

12 - Kubernetes 中的代理

本文讲述了 Kubernetes 中所使用的代理。

代理

用户在使用 Kubernetes 的过程中可能遇到几种不同的代理(proxy):

  1. kubectl proxy

    • 运行在用户的桌面或 pod 中
    • 从本机地址到 Kubernetes apiserver 的代理
    • 客户端到代理使用 HTTP 协议
    • 代理到 apiserver 使用 HTTPS 协议
    • 指向 apiserver
    • 添加认证头信息
  1. apiserver proxy

    • 是一个建立在 apiserver 内部的“堡垒”
    • 将集群外部的用户与集群 IP 相连接,这些 IP 是无法通过其他方式访问的
    • 运行在 apiserver 进程内
    • 客户端到代理使用 HTTPS 协议 (如果配置 apiserver 使用 HTTP 协议,则使用 HTTP 协议)
    • 通过可用信息进行选择,代理到目的地可能使用 HTTP 或 HTTPS 协议
    • 可以用来访问 Node、 Pod 或 Service
    • 当用来访问 Service 时,会进行负载均衡
  1. kube proxy

    • 在每个节点上运行
    • 代理 UDP、TCP 和 SCTP
    • 不支持 HTTP
    • 提供负载均衡能力
    • 只用来访问 Service
  1. apiserver 之前的代理/负载均衡器:

    • 在不同集群中的存在形式和实现不同 (如 nginx)
    • 位于所有客户端和一个或多个 API 服务器之间
    • 存在多个 API 服务器时,扮演负载均衡器的角色
  1. 外部服务的云负载均衡器:

    • 由一些云供应商提供 (如 AWS ELB、Google Cloud Load Balancer)
    • Kubernetes 服务类型为 LoadBalancer 时自动创建
    • 通常仅支持 UDP/TCP 协议
    • SCTP 支持取决于云供应商的负载均衡器实现
    • 不同云供应商的云负载均衡器实现不同

Kubernetes 用户通常只需要关心前两种类型的代理,集群管理员通常需要确保后面几种类型的代理设置正确。

请求重定向

代理已经取代重定向功能,重定向功能已被弃用。

13 - API 优先级和公平性

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

对于集群管理员来说,控制 Kubernetes API 服务器在过载情况下的行为是一项关键任务。 kube-apiserver 有一些控件(例如:命令行标志 --max-requests-inflight--max-mutating-requests-inflight), 可以限制将要接受的未处理的请求,从而防止过量请求入站,潜在导致 API 服务器崩溃。 但是这些标志不足以保证在高流量期间,最重要的请求仍能被服务器接受。

API 优先级和公平性(APF)是一种替代方案,可提升上述最大并发限制。 APF 以更细粒度的方式对请求进行分类和隔离。 它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。 通过使用公平排队技术从队列中分发请求,这样, 一个行为不佳的控制器就不会饿死其他控制器 (即使优先级相同)。

本功能特性在设计上期望其能与标准控制器一起工作得很好; 这类控制器使用通知组件(Informers)获得信息并对 API 请求的失效作出反应, 在处理失效时能够执行指数型回退。其他客户端也以类似方式工作。

启用/禁用 API 优先级和公平性

API 优先级与公平性(APF)特性由命令行标志控制,默认情况下启用。 有关可用 kube-apiserver 命令行参数以及如何启用和禁用的说明, 请参见参数。 APF 的命令行参数是 "--enable-priority-and-fairness"。 此特性也与某个 API 组相关: (a) 稳定的 v1 版本,在 1.29 中引入,默认启用; (b) v1beta3 版本,默认被启用,在 1.29 中被弃用。 你可以通过添加以下内容来禁用 Beta 版的 v1beta3 API 组:

kube-apiserver \
--runtime-config=flowcontrol.apiserver.k8s.io/v1beta3=false \
  # ...其他配置不变

命令行标志 --enable-priority-fairness=false 将彻底禁用 APF 特性。

递归服务器场景

在递归服务器场景中,必须谨慎使用 API 优先级和公平性。这些场景指的是服务器 A 在处理一个请求时, 会向服务器 B 发出一个辅助请求。服务器 B 可能会进一步向服务器 A 发出辅助请求。 当优先级和公平性控制同时应用于原始请求及某些辅助请求(无论递归多深)时,存在优先级反转和/或死锁的风险。

递归的一个例子是 kube-apiserver 向服务器 B 发出一个准入 Webhook 调用, 而在处理该调用时,服务器 B 进一步向 kube-apiserver 发出一个辅助请求。 另一个递归的例子是,某个 APIService 对象指示 kube-apiserver 将某个 API 组的请求委托给自定义的外部服务器 B(这被称为"聚合")。

当原始请求被确定为属于某个特定优先级别时,将辅助请求分类为更高的优先级别是一个可行的解决方案。 当原始请求可能属于任何优先级时,辅助受控请求必须免受优先级和公平性限制。 一种实现方法是使用下文中讨论的配置分类和处理的对象。 另一种方法是采用前面提到的技术,在服务器 B 上完全禁用优先级和公平性。第三种方法是, 当服务器 B 不是 kube-apiserver 时,最简单的做法是在服务器 B 的代码中禁用优先级和公平性。

概念

APF 特性包含几个不同的功能。 传入的请求通过 FlowSchema 按照其属性分类,并分配优先级。 每个优先级维护自定义的并发限制,加强了隔离度,这样不同优先级的请求,就不会相互饿死。 在同一个优先级内,公平排队算法可以防止来自不同 流(Flow) 的请求相互饿死。 该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败。

优先级

如果未启用 APF,API 服务器中的整体并发量将受到 kube-apiserver 的参数 --max-requests-inflight--max-mutating-requests-inflight 的限制。 启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的 优先级 中。 每个传入的请求都会分配一个优先级;每个优先级都有各自的限制,设定特定限制允许分发的并发请求数。

例如,默认配置包括针对领导者选举请求、内置控制器请求和 Pod 请求都单独设置优先级。 这表示即使异常的 Pod 向 API 服务器发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功。

优先级的并发限制会被定期调整,允许利用率较低的优先级将并发度临时借给利用率很高的优先级。 这些限制基于一个优先级可以借出多少个并发度以及可以借用多少个并发度的额定限制和界限, 所有这些均源自下述配置对象。

请求占用的席位

上述并发管理的描述是基线情况。各个请求具有不同的持续时间, 但在与一个优先级的并发限制进行比较时,这些请求在任何给定时刻都以同等方式进行计数。 在这个基线场景中,每个请求占用一个并发单位。 我们用 “席位(Seat)” 一词来表示一个并发单位,其灵感来自火车或飞机上每位乘客占用一个固定座位的供应方式。

但有些请求所占用的席位不止一个。有些请求是服务器预估将返回大量对象的 list 请求。 我们发现这类请求会给服务器带来异常沉重的负担。 出于这个原因,服务器估算将返回的对象数量,并认为请求所占用的席位数与估算得到的数量成正比。

watch 请求的执行时间调整

APF 管理 watch 请求,但这需要考量基线行为之外的一些情况。 第一个关注点是如何判定 watch 请求的席位占用时长。 取决于请求参数不同,对 watch 请求的响应可能以针对所有预先存在的对象 create 通知开头,也可能不这样。 一旦最初的突发通知(如果有)结束,APF 将认为 watch 请求已经用完其席位。

每当向服务器通知创建/更新/删除一个对象时,正常通知都会以并发突发的方式发送到所有相关的 watch 响应流。 为此,APF 认为每个写入请求都会在实际写入完成后花费一些额外的时间来占用席位。 服务器估算要发送的通知数量,并调整写入请求的席位数以及包含这些额外工作后的席位占用时间。

排队

即使在同一优先级内,也可能存在大量不同的流量源。 在过载情况下,防止一个请求流饿死其他流是非常有价值的 (尤其是在一个较为常见的场景中,一个有故障的客户端会疯狂地向 kube-apiserver 发送请求, 理想情况下,这个有故障的客户端不应对其他客户端产生太大的影响)。 公平排队算法在处理具有相同优先级的请求时,实现了上述场景。 每个请求都被分配到某个 流(Flow) 中,该 由对应的 FlowSchema 的名字加上一个 流区分项(Flow Distinguisher) 来标识。 这里的流区分项可以是发出请求的用户、目标资源的名字空间或什么都不是。 系统尝试为不同流中具有相同优先级的请求赋予近似相等的权重。 要启用对不同实例的不同处理方式,多实例的控制器要分别用不同的用户名来执行身份认证。

将请求划分到流中之后,APF 功能将请求分配到队列中。 分配时使用一种称为混洗分片(Shuffle-Sharding)的技术。 该技术可以相对有效地利用队列隔离低强度流与高强度流。

排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、 公平性(当总流量超标时,各个独立的流将都会取得进展)、 突发流量的容忍度以及排队引发的额外延迟之间进行权衡。

豁免请求

某些特别重要的请求不受制于此特性施加的任何限制。 这些豁免可防止不当的流控配置完全禁用 API 服务器。

资源

流控 API 涉及两种资源。 PriorityLevelConfiguration 定义可用的优先级和可处理的并发预算量,还可以微调排队行为。 FlowSchema 用于对每个入站请求进行分类,并与一个 PriorityLevelConfiguration 相匹配。

PriorityLevelConfiguration

一个 PriorityLevelConfiguration 表示单个优先级。每个 PriorityLevelConfiguration 对未完成的请求数有各自的限制,对排队中的请求数也有限制。

PriorityLevelConfiguration 的额定并发限制不是指定请求绝对数量,而是以“额定并发份额”的形式指定。 API 服务器的总并发量限制通过这些份额按例分配到现有 PriorityLevelConfiguration 中, 为每个级别按照数量赋予其额定限制。 集群管理员可以更改 --max-requests-inflight (或 --max-mutating-requests-inflight)的值, 再重新启动 kube-apiserver 来增加或减小服务器的总流量, 然后所有的 PriorityLevelConfiguration 将看到其最大并发增加(或减少)了相同的比例。

一个优先级可以借出的并发数界限以及可以借用的并发数界限在 PriorityLevelConfiguration 表现该优先级的额定限制。 这些界限值乘以额定限制/100.0 并取整,被解析为绝对席位数量。 某优先级的动态调整并发限制范围被约束在 (a) 其额定限制的下限值减去其可借出的席位和 (b) 其额定限制的上限值加上它可以借用的席位之间。 在每次调整时,通过每个优先级推导得出动态限制,具体过程为回收最近出现需求的所有借出的席位, 然后在刚刚描述的界限内共同公平地响应有关这些优先级最近的席位需求。

当入站请求的数量大于分配的 PriorityLevelConfiguration 中允许的并发级别时, type 字段将确定对额外请求的处理方式。 Reject 类型,表示多余的流量将立即被 HTTP 429(请求过多)错误所拒绝。 Queue 类型,表示对超过阈值的请求进行排队,将使用阈值分片和公平排队技术来平衡请求流之间的进度。

公平排队算法支持通过排队配置对优先级微调。 可以在增强建议中阅读算法的详细信息, 但总之:

  • queues 递增能减少不同流之间的冲突概率,但代价是增加了内存使用量。 值为 1 时,会禁用公平排队逻辑,但仍允许请求排队。
  • queueLengthLimit 递增可以在不丢弃任何请求的情况下支撑更大的突发流量, 但代价是增加了等待时间和内存使用量。
  • 修改 handSize 允许你调整过载情况下不同流之间的冲突概率以及单个流可用的整体并发性。

下表显示了有趣的随机分片配置集合,每行显示给定的老鼠(低强度流) 被不同数量的大象挤压(高强度流)的概率。 表来源请参阅: https://play.golang.org/p/Gi0PLgVHiUg

混分切片配置示例
随机分片 队列数 1 头大象 4 头大象 16 头大象
12 32 4.428838398950118e-09 0.11431348830099144 0.9935089607656024
10 32 1.550093439632541e-08 0.0626479840223545 0.9753101519027554
10 64 6.601827268370426e-12 0.00045571320990370776 0.49999929150089345
9 64 3.6310049976037345e-11 0.00045501212304112273 0.4282314876454858
8 64 2.25929199850899e-10 0.0004886697053040446 0.35935114681123076
8 128 6.994461389026097e-13 3.4055790161620863e-06 0.02746173137155063
7 128 1.0579122850901972e-11 6.960839379258192e-06 0.02406157386340147
7 256 7.597695465552631e-14 6.728547142019406e-08 0.0006709661542533682
6 256 2.7134626662687968e-12 2.9516464018476436e-07 0.0008895654642000348
6 512 4.116062922897309e-14 4.982983350480894e-09 2.26025764343413e-05
6 1024 6.337324016514285e-16 8.09060164312957e-11 4.517408062903668e-07

FlowSchema

FlowSchema 匹配一些入站请求,并将它们分配给优先级。 每个入站请求都会对 FlowSchema 测试是否匹配, 首先从 matchingPrecedence 数值最低的匹配开始, 然后依次进行,直到首个匹配出现。

当给定的请求与某个 FlowSchema 的 rules 的其中一条匹配,那么就认为该请求与该 FlowSchema 匹配。 判断规则与该请求是否匹配,不仅要求该条规则的 subjects 字段至少存在一个与该请求相匹配, 而且要求该条规则的 resourceRulesnonResourceRules (取决于传入请求是针对资源 URL 还是非资源 URL)字段至少存在一个与该请求相匹配。

对于 subjects 中的 name 字段和资源和非资源规则的 verbsapiGroupsresourcesnamespacesnonResourceURLs 字段, 可以指定通配符 * 来匹配任意值,从而有效地忽略该字段。

FlowSchema 的 distinguisherMethod.type 字段决定了如何把与该模式匹配的请求分散到各个流中。 可能是 ByUser,在这种情况下,一个请求用户将无法饿死其他容量的用户; 或者是 ByNamespace,在这种情况下,一个名字空间中的资源请求将无法饿死其它名字空间的资源请求; 或者为空(或者可以完全省略 distinguisherMethod), 在这种情况下,与此 FlowSchema 匹配的请求将被视为单个流的一部分。 资源和你的特定环境决定了如何选择正确一个 FlowSchema。

默认值

每个 kube-apiserver 会维护两种类型的 APF 配置对象:强制的(Mandatory)和建议的(Suggested)。

强制的配置对象

有四种强制的配置对象对应内置的守护行为。这里的行为是服务器在还未创建对象之前就具备的行为, 而当这些对象存在时,其规约反映了这类行为。四种强制的对象如下:

  • 强制的 exempt 优先级用于完全不受流控限制的请求:它们总是立刻被分发。 强制的 exempt FlowSchema 把 system:masters 组的所有请求都归入该优先级。 如果合适,你可以定义新的 FlowSchema,将其他请求定向到该优先级。
  • 强制的 catch-all 优先级与强制的 catch-all FlowSchema 结合使用, 以确保每个请求都分类。一般而言,你不应该依赖于 catch-all 的配置, 而应适当地创建自己的 catch-all FlowSchema 和 PriorityLevelConfiguration (或使用默认安装的 global-default 配置)。 因为这一优先级不是正常场景下要使用的,catch-all 优先级的并发度份额很小, 并且不会对请求进行排队。

建议的配置对象

建议的 FlowSchema 和 PriorityLevelConfiguration 包含合理的默认配置。 你可以修改这些对象或者根据需要创建新的配置对象。如果你的集群可能承受较重负载, 那么你就要考虑哪种配置最合适。

建议的配置把请求分为六个优先级:

  • node-high 优先级用于来自节点的健康状态更新。
  • system 优先级用于 system:nodes 组(即 kubelet)的与健康状态更新无关的请求; kubelet 必须能连上 API 服务器,以便工作负载能够调度到其上。
  • leader-election 优先级用于内置控制器的领导选举的请求 (特别是来自 kube-system 名字空间中 system:kube-controller-managersystem:kube-scheduler 用户和服务账号,针对 endpointsconfigmapsleases 的请求)。 将这些请求与其他流量相隔离非常重要,因为领导者选举失败会导致控制器发生故障并重新启动, 这反过来会导致新启动的控制器在同步信息时,流量开销更大。
  • workload-high 优先级用于内置控制器的其他请求。
  • workload-low 优先级用于来自所有其他服务帐户的请求,通常包括来自 Pod 中运行的控制器的所有请求。
  • global-default 优先级可处理所有其他流量,例如:非特权用户运行的交互式 kubectl 命令。

建议的 FlowSchema 用来将请求导向上述的优先级内,这里不再一一列举。

强制的与建议的配置对象的维护

每个 kube-apiserver 都独立地维护其强制的与建议的配置对象, 这一维护操作既是服务器的初始行为,也是其周期性操作的一部分。 因此,当存在不同版本的服务器时,如果各个服务器对于配置对象中的合适内容有不同意见, 就可能出现抖动。

每个 kube-apiserver 都会对强制的与建议的配置对象执行初始的维护操作, 之后(每分钟)对这些对象执行周期性的维护。

对于强制的配置对象,维护操作包括确保对象存在并且包含合适的规约(如果存在的话)。 服务器会拒绝创建或更新与其守护行为不一致的规约。

对建议的配置对象的维护操作被设计为允许其规约被重载。删除操作是不允许的, 维护操作期间会重建这类配置对象。如果你不需要某个建议的配置对象, 你需要将它放在一边,并让其规约所产生的影响最小化。 对建议的配置对象而言,其维护方面的设计也支持在上线新的 kube-apiserver 时完成自动的迁移动作,即便可能因为当前的服务器集合存在不同的版本而可能造成抖动仍是如此。

对建议的配置对象的维护操作包括基于服务器建议的规约创建对象 (如果对象不存在的话)。反之,如果对象已经存在,维护操作的行为取决于是否 kube-apiserver 或者用户在控制对象。如果 kube-apiserver 在控制对象, 则服务器确保对象的规约与服务器所给的建议匹配,如果用户在控制对象, 对象的规约保持不变。

关于谁在控制对象这个问题,首先要看对象上的 apf.kubernetes.io/autoupdate-spec 注解。如果对象上存在这个注解,并且其取值为true,则 kube-apiserver 在控制该对象。如果存在这个注解,并且其取值为false,则用户在控制对象。 如果这两个条件都不满足,则需要进一步查看对象的 metadata.generation。 如果该值为 1,则 kube-apiserver 控制对象,否则用户控制对象。 这些规则是在 1.22 发行版中引入的,而对 metadata.generation 的考量是为了便于从之前较简单的行为迁移过来。希望控制建议的配置对象的用户应该将对象的 apf.kubernetes.io/autoupdate-spec 注解设置为 false

对强制的或建议的配置对象的维护操作也包括确保对象上存在 apf.kubernetes.io/autoupdate-spec 这一注解,并且其取值准确地反映了是否 kube-apiserver 在控制着对象。

维护操作还包括删除那些既非强制又非建议的配置,同时注解配置为 apf.kubernetes.io/autoupdate-spec=true 的对象。

健康检查并发豁免

推荐配置没有为本地 kubelet 对 kube-apiserver 执行健康检查的请求进行任何特殊处理 ——它们倾向于使用安全端口,但不提供凭据。 在推荐配置中,这些请求将分配 global-default FlowSchema 和 global-default 优先级, 这样其他流量可以排除健康检查。

如果添加以下 FlowSchema,健康检查请求不受速率限制。

apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
  name: health-for-strangers
spec:
  matchingPrecedence: 1000
  priorityLevelConfiguration:
    name: exempt
  rules:
    - nonResourceRules:
      - nonResourceURLs:
          - "/healthz"
          - "/livez"
          - "/readyz"
        verbs:
          - "*"
      subjects:
        - kind: Group
          group:
            name: "system:unauthenticated"

可观察性

指标

当你开启了 APF 后,kube-apiserver 会暴露额外指标。 监视这些指标有助于判断你的配置是否不当地限制了重要流量, 或者发现可能会损害系统健康的,行为不良的工作负载。

成熟度水平 BETA

  • apiserver_flowcontrol_rejected_requests_total 是一个计数器向量, 记录被拒绝的请求数量(自服务器启动以来累计值), 可按标签 flow_chema(表示与请求匹配的 FlowSchema)、priority_level (表示分配给请该求的优先级)和 reason 分解。 reason 标签将是以下值之一:

    • queue-full,表明已经有太多请求排队
    • concurrency-limit,表示将 PriorityLevelConfiguration 配置为 Reject 而不是 Queue,或者
    • time-out,表示在其排队时间超期的请求仍在队列中。
    • cancelled,表示该请求未被清除锁定,已从队列中移除。
  • apiserver_flowcontrol_dispatched_requests_total 是一个计数器向量, 记录开始执行的请求数量(自服务器启动以来的累积值), 可按 flow_schemapriority_level 分解。
  • apiserver_flowcontrol_current_inqueue_requests 是一个测量向量, 记录排队中的(未执行)请求的瞬时数量,可按 priority_levelflow_schema 分解。

  • apiserver_flowcontrol_current_executing_requests 是一个测量向量, 记录执行中(不在队列中等待)请求的瞬时数量,可按 priority_levelflow_schema 分解。

  • apiserver_flowcontrol_current_executing_seats 是一个测量向量, 记录了按 priority_levelflow_schema 细分的瞬时占用席位数量。

  • apiserver_flowcontrol_request_wait_duration_seconds 是一个直方图向量, 记录了按 flow_schemapriority_levelexecute 标签细分的请求在队列中等待的时长。 execute 标签表示请求是否已开始执行。

  • apiserver_flowcontrol_nominal_limit_seats 是一个测量向量, 记录了每个优先级的额定并发限制。 此值是根据 API 服务器的总并发限制和优先级的配置额定并发份额计算得出的。

成熟度水平 ALPHA

  • apiserver_current_inqueue_requests 是一个测量向量, 记录最近排队请求数量的高水位线, 由标签 request_kind 分组,标签的值为 mutatingreadOnly。 这些高水位线表示在最近一秒钟内看到的最大数字。 它们补充说明了老的测量向量 apiserver_current_inflight_requests (该量保存了最后一个窗口中,正在处理的请求数量的高水位线)。
  • apiserver_current_inqueue_seats 是一个测量向量, 记录了排队请求中每个请求将占用的最大席位数的总和, 按 flow_schemapriority_level 两个标签进行分组。
  • apiserver_flowcontrol_read_vs_write_current_requests 是一个直方图向量, 在每个纳秒结束时记录请求数量的观察值,可按标签 phase(取值为 waitingexecuting) 和 request_kind(取值为 mutatingreadOnly)分解。 每个观察到的值是一个介于 0 和 1 之间的比值,计算方式为请求数除以该请求数的对应限制 (等待的队列长度限制和执行所用的并发限制)。
  • apiserver_flowcontrol_request_concurrency_in_use 是一个规范向量, 包含占用席位的瞬时数量,可按 priority_levelflow_schema 分解。
  • apiserver_flowcontrol_priority_level_request_utilization 是一个直方图向量, 在每个纳秒结束时记录请求数量的观察值, 可按标签 phase(取值为 waitingexecuting)和 priority_level 分解。 每个观察到的值是一个介于 0 和 1 之间的比值,计算方式为请求数除以该请求数的对应限制 (等待的队列长度限制和执行所用的并发限制)。
  • apiserver_flowcontrol_priority_level_seat_utilization 是一个直方图向量, 在每个纳秒结束时记录某个优先级并发度限制利用率的观察值,可按标签 priority_level 分解。 此利用率是一个分数:(占用的席位数)/(并发限制)。 此指标考虑了除 WATCH 之外的所有请求的所有执行阶段(包括写入结束时的正常延迟和额外延迟, 以覆盖相应的通知操作);对于 WATCH 请求,只考虑传递预先存在对象通知的初始阶段。 该向量中的每个直方图也带有 phase: executing(等待阶段没有席位限制)的标签。
  • apiserver_flowcontrol_request_queue_length_after_enqueue 是一个直方图向量, 记录请求队列的长度,可按 priority_levelflow_schema 分解。 每个排队中的请求都会为其直方图贡献一个样本,并在添加请求后立即上报队列的长度。 请注意,这样产生的统计数据与无偏调查不同。

  • apiserver_flowcontrol_request_concurrency_limitapiserver_flowcontrol_nominal_limit_seats 相同。在优先级之间引入并发度借用之前, 此字段始终等于 apiserver_flowcontrol_current_limit_seats (它过去不作为一个独立的指标存在)。
  • apiserver_flowcontrol_lower_limit_seats 是一个测量向量,包含每个优先级的动态并发度限制的下限。
  • apiserver_flowcontrol_upper_limit_seats 是一个测量向量,包含每个优先级的动态并发度限制的上限。
  • apiserver_flowcontrol_demand_seats 是一个直方图向量, 统计每纳秒结束时每个优先级的(席位需求)/(额定并发限制)比率的观察值。 某优先级的席位需求是针对排队的请求和初始执行阶段的请求,在请求的初始和最终执行阶段占用的最大席位数之和。
  • apiserver_flowcontrol_demand_seats_high_watermark 是一个测量向量, 为每个优先级包含了上一个并发度借用调整期间所观察到的最大席位需求。
  • apiserver_flowcontrol_demand_seats_average 是一个测量向量, 为每个优先级包含了上一个并发度借用调整期间所观察到的时间加权平均席位需求。
  • apiserver_flowcontrol_demand_seats_stdev 是一个测量向量, 为每个优先级包含了上一个并发度借用调整期间所观察到的席位需求的时间加权总标准偏差。
  • apiserver_flowcontrol_demand_seats_smoothed 是一个测量向量, 为每个优先级包含了上一个并发度调整期间确定的平滑包络席位需求。
  • apiserver_flowcontrol_target_seats 是一个测量向量, 包含每个优先级触发借用分配问题的并发度目标值。
  • apiserver_flowcontrol_seat_fair_frac 是一个测量向量, 包含了上一个借用调整期间确定的公平分配比例。
  • apiserver_flowcontrol_current_limit_seats 是一个测量向量, 包含每个优先级的上一次调整期间得出的动态并发限制。
  • apiserver_flowcontrol_request_execution_seconds 是一个直方图向量, 记录请求实际执行需要花费的时间, 可按标签 flow_schemapriority_level 分解。
  • apiserver_flowcontrol_watch_count_samples 是一个直方图向量, 记录给定写的相关活动 WATCH 请求数量, 可按标签 flow_schemapriority_level 分解。
  • apiserver_flowcontrol_work_estimated_seats 是一个直方图向量, 记录与估计席位(最初阶段和最后阶段的最多人数)相关联的请求数量, 可按标签 flow_schemapriority_level 分解。
  • apiserver_flowcontrol_request_dispatch_no_accommodation_total 是一个事件数量的计数器,这些事件在原则上可能导致请求被分派, 但由于并发度不足而没有被分派, 可按标签 flow_schemapriority_level 分解。
  • apiserver_flowcontrol_epoch_advance_total 是一个计数器向量, 记录了将优先级进度计向后跳跃以避免数值溢出的尝试次数, 按 priority_levelsuccess 两个标签进行分组。

使用 API 优先级和公平性的最佳实践

当某个给定的优先级级别超过其所被允许的并发数时,请求可能会遇到延迟增加, 或以错误 HTTP 429 (Too Many Requests) 的形式被拒绝。 为了避免这些 APF 的副作用,你可以修改你的工作负载或调整你的 APF 设置,确保有足够的席位来处理请求。

要检测请求是否由于 APF 而被拒绝,可以检查以下指标:

  • apiserver_flowcontrol_rejected_requests_total: 每个 FlowSchema 和 PriorityLevelConfiguration 拒绝的请求总数。
  • apiserver_flowcontrol_current_inqueue_requests: 每个 FlowSchema 和 PriorityLevelConfiguration 中排队的当前请求数。
  • apiserver_flowcontrol_request_wait_duration_seconds:请求在队列中等待的延迟时间。
  • apiserver_flowcontrol_priority_level_seat_utilization: 每个 PriorityLevelConfiguration 的席位利用率。

工作负载修改

为了避免由于 APF 导致请求排队、延迟增加或被拒绝,你可以通过以下方式优化请求:

  • 减少请求执行的速率。在固定时间段内减少请求数量将导致在某一给定时间点需要的席位数更少。
  • 避免同时发出大量消耗较多席位的请求。请求可以被优化为使用更少的席位或降低延迟, 使这些请求占用席位的时间变短。列表请求根据请求期间获取的对象数量可能会占用多个席位。 例如通过使用分页等方式限制列表请求中取回的对象数量,可以在更短时间内使用更少的总席位数。 此外,将列表请求替换为监视请求将需要更低的总并发份额,因为监视请求仅在初始的通知突发阶段占用 1 个席位。 如果在 1.27 及更高版本中使用流式列表,因为集合的整个状态必须以流式传输, 所以监视请求在其初始的通知突发阶段将占用与列表请求相同数量的席位。 请注意,在这两种情况下,监视请求在此初始阶段之后将不再保留任何席位。

请注意,由于请求数量增加或现有请求的延迟增加,APF 可能会导致请求排队或被拒绝。 例如,如果通常需要 1 秒执行的请求开始需要 60 秒,由于延迟增加, 请求所占用的席位时间可能超过了正常情况下的时长,APF 将开始拒绝请求。 如果在没有工作负载显著变化的情况下,APF 开始在多个优先级级别上拒绝请求, 则可能存在控制平面性能的潜在问题,而不是工作负载或 APF 设置的问题。

优先级和公平性设置

你还可以修改默认的 FlowSchema 和 PriorityLevelConfiguration 对象, 或创建新的对象来更好地容纳你的工作负载。

APF 设置可以被修改以实现下述目标:

  • 给予高优先级请求更多的席位。
  • 隔离那些非必要或开销大的请求,因为如果与其他流共享,这些请求可能会耗尽所有并发级别。

给予高优先级请求更多的席位

  1. 如果有可能,你可以通过提高 max-requests-inflightmax-mutating-requests-inflight 参数的值为特定 kube-apiserver 提高所有优先级级别均可用的席位数量。另外, 如果在请求的负载均衡足够好的情况下,水平扩缩 kube-apiserver 实例的数量将提高集群中每个优先级级别的总并发数。
  1. 你可以创建一个新的 FlowSchema,在其中引用并发级别更高的 PriorityLevelConfiguration。 这个新的 PriorityLevelConfiguration 可以是现有的级别,也可以是具有自己一组额定并发份额的新级别。 例如,你可以引入一个新的 FlowSchema 来将请求的 PriorityLevelConfiguration 从全局默认值更改为工作负载较低的级别,以增加用户可用的席位数。 创建一个新的 PriorityLevelConfiguration 将减少为现有级别指定的席位数。 请注意,编辑默认的 FlowSchema 或 PriorityLevelConfiguration 需要将 apf.kubernetes.io/autoupdate-spec 注解设置为 false。
  1. 你还可以为服务于高优先级请求的 PriorityLevelConfiguration 提高 NominalConcurrencyShares。 此外在 1.26 及更高版本中,你可以为有竞争的优先级级别提高 LendablePercent,以便给定优先级级别可以借用更多的席位。

隔离非关键请求以免饿死其他流

为了进行请求隔离,你可以创建一个 FlowSchema,使其主体与发起这些请求的用户匹配, 或者创建一个与请求内容匹配(对应 resourceRules)的 FlowSchema。 接下来,你可以将该 FlowSchema 映射到一个具有较低席位份额的 PriorityLevelConfiguration。

例如,假设来自 default 名字空间中运行的 Pod 的每个事件列表请求使用 10 个席位,并且执行时间为 1 分钟。 为了防止这些开销大的请求影响使用现有服务账号 FlowSchema 的其他 Pod 的请求,你可以应用以下 FlowSchema 将这些列表调用与其他请求隔离开来。

用于隔离列表事件请求的 FlowSchema 对象示例:

apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
  name: list-events-default-service-account
spec:
  distinguisherMethod:
    type: ByUser
  matchingPrecedence: 8000
  priorityLevelConfiguration:
    name: catch-all
  rules:
    - resourceRules:
      - apiGroups:
          - '*'
        namespaces:
          - default
        resources:
          - events
        verbs:
          - list
      subjects:
        - kind: ServiceAccount
          serviceAccount:
            name: default
            namespace: default
  • 这个 FlowSchema 用于抓取 default 名字空间中默认服务账号所发起的所有事件列表调用。 匹配优先级为 8000,低于现有服务账号 FlowSchema 所用的 9000,因此这些列表事件调用将匹配到 list-events-default-service-account 而不是服务账号。
  • 通用 PriorityLevelConfiguration 用于隔离这些请求。通用优先级级别具有非常小的并发份额,并且不对请求进行排队。

接下来

14 - 安装扩展(Addon)

Add-on 扩展了 Kubernetes 的功能。

本文列举了一些可用的 add-on 以及到它们各自安装说明的链接。该列表并不试图详尽无遗。

联网和网络策略

  • ACI 通过 Cisco ACI 提供集成的容器网络和安全网络。
  • Antrea 在第 3/4 层执行操作,为 Kubernetes 提供网络连接和安全服务。Antrea 利用 Open vSwitch 作为网络的数据面。 Antrea 是一个沙箱级的 CNCF 项目
  • Calico 是一个联网和网络策略供应商。 Calico 支持一套灵活的网络选项,因此你可以根据自己的情况选择最有效的选项,包括非覆盖和覆盖网络,带或不带 BGP。 Calico 使用相同的引擎为主机、Pod 和(如果使用 Istio 和 Envoy)应用程序在服务网格层执行网络策略。
  • Canal 结合 Flannel 和 Calico,提供联网和网络策略。
  • Cilium 是一种网络、可观察性和安全解决方案,具有基于 eBPF 的数据平面。 Cilium 提供了简单的 3 层扁平网络, 能够以原生路由(routing)和覆盖/封装(overlay/encapsulation)模式跨越多个集群, 并且可以使用与网络寻址分离的基于身份的安全模型在 L3 至 L7 上实施网络策略。 Cilium 可以作为 kube-proxy 的替代品;它还提供额外的、可选的可观察性和安全功能。 Cilium 是一个毕业级别的 CNCF 项目
  • CNI-Genie 使 Kubernetes 无缝连接到 Calico、Canal、Flannel 或 Weave 等其中一种 CNI 插件。 CNI-Genie 是一个沙箱级的 CNCF 项目
  • Contiv 为各种用例和丰富的策略框架提供可配置的网络 (带 BGP 的原生 L3、带 vxlan 的覆盖、标准 L2 和 Cisco-SDN/ACI)。 Contiv 项目完全开源。 其安装程序 提供了基于 kubeadm 和非 kubeadm 的安装选项。
  • Contrail 基于 Tungsten Fabric,是一个开源的多云网络虚拟化和策略管理平台。 Contrail 和 Tungsten Fabric 与业务流程系统(例如 Kubernetes、OpenShift、OpenStack 和 Mesos)集成在一起, 为虚拟机、容器或 Pod 以及裸机工作负载提供了隔离模式。
  • Flannel 是一个可以用于 Kubernetes 的 overlay 网络提供者。
  • Gateway API 是一个由 SIG Network 社区管理的开源项目, 为服务网络建模提供一种富有表达力、可扩展和面向角色的 API。
  • Knitter 是在一个 Kubernetes Pod 中支持多个网络接口的插件。
  • Multus 是一个多插件, 可在 Kubernetes 中提供多种网络支持,以支持所有 CNI 插件(例如 Calico、Cilium、Contiv、Flannel), 而且包含了在 Kubernetes 中基于 SRIOV、DPDK、OVS-DPDK 和 VPP 的工作负载。
  • OVN-Kubernetes 是一个 Kubernetes 网络驱动, 基于 OVN(Open Virtual Network)实现,是从 Open vSwitch (OVS) 项目衍生出来的虚拟网络实现。OVN-Kubernetes 为 Kubernetes 提供基于覆盖网络的网络实现, 包括一个基于 OVS 实现的负载均衡器和网络策略。
  • Nodus 是一个基于 OVN 的 CNI 控制器插件, 提供基于云原生的服务功能链 (SFC)。
  • NSX-T 容器插件(NCP) 提供了 VMware NSX-T 与容器协调器(例如 Kubernetes)之间的集成,以及 NSX-T 与基于容器的 CaaS / PaaS 平台(例如关键容器服务(PKS)和 OpenShift)之间的集成。
  • Nuage 是一个 SDN 平台,可在 Kubernetes Pods 和非 Kubernetes 环境之间提供基于策略的联网,并具有可视化和安全监控。
  • Romana 是一个 Pod 网络的第三层解决方案,并支持 NetworkPolicy API。
  • Spiderpool 为 Kubernetes 提供了下层网络和 RDMA 高速网络解决方案,兼容裸金属、虚拟机和公有云等运行环境。
  • Weave Net 提供在网络分组两端参与工作的联网和网络策略,并且不需要额外的数据库。

服务发现

  • CoreDNS 是一种灵活的,可扩展的 DNS 服务器,可以 安装为集群内的 Pod 提供 DNS 服务。

可视化管理

  • Dashboard 是一个 Kubernetes 的 Web 控制台界面。

基础设施

插桩

遗留 Add-on

还有一些其它 add-on 归档在已废弃的 cluster/addons 路径中。

维护完善的 add-on 应该被链接到这里。欢迎提出 PR!

15 - 协调领导者选举

特性状态: Kubernetes v1.31 [alpha] (enabled by default: false)

Kubernetes 1.32 包含一个 Alpha 特性, 允许控制平面组件通过协调领导者选举确定性地选择一个领导者。 这对于在集群升级期间满足 Kubernetes 版本偏差约束非常有用。 目前,唯一内置的选择策略是 OldestEmulationVersion, 此策略会优先选择最低仿真版本作为领导者,其次按二进制版本选择领导者,最后会按创建时间戳选择领导者。

启用协调领导者选举

确保你在启动 API 服务器CoordinatedLeaderElection 特性门控被启用, 并且 coordination.k8s.io/v1alpha1 API 组被启用。

此操作可以通过设置 --feature-gates="CoordinatedLeaderElection=true"--runtime-config="coordination.k8s.io/v1alpha1=true" 标志来完成。

组件配置

前提是你已启用 CoordinatedLeaderElection 特性门控并且 启用了 coordination.k8s.io/v1alpha1 API 组, 兼容的控制平面组件会自动使用 LeaseCandidate 和 Lease API 根据需要选举领导者。

对于 Kubernetes 1.32, 当特性门控和 API 组被启用时, 两个控制平面组件(kube-controller-manager 和 kube-scheduler)会自动使用协调领导者选举。