1 - Kubelet Checkpoint API

特性状态: Kubernetes v1.30 [beta](默认启用)

为容器生成检查点这个功能可以为一个正在运行的容器创建有状态的拷贝。 一旦容器有一个有状态的拷贝,你就可以将其移动到其他计算机进行调试或类似用途。

如果你将通过检查点操作生成的容器数据移动到能够恢复该容器的一台计算机, 所恢复的容器将从之前检查点操作执行的时间点继续运行。 你也可以检视所保存的数据,前提是你拥有这类操作的合适工具。

创建容器的检查点可能会产生安全隐患。 通常,一个检查点包含执行检查点操作时容器中所有进程的所有内存页。 这意味着以前存在于内存中的一切内容现在都在本地磁盘上获得。 这里的内容包括一切私密数据和可能用于加密的密钥。 底层 CRI 实现(该节点上的容器运行时)应创建只有 root 用户可以访问的检查点存档。 另外重要的是记住:如果检查点存档被转移到另一个系统,该检查点存档的所有者将可以读取所有内存页。

操作

post 对指定的容器执行检查点操作

告知 kubelet 对指定 Pod 中的特定容器执行检查点操作。

查阅 Kubelet 身份验证/鉴权参考了解如何控制对 kubelet 检查点接口的访问。

Kubelet 将对底层 CRI 实现请求执行检查点操作。 在该检查点请求中,Kubelet 将检查点存档的名称设置为 checkpoint-<pod 全称>-<容器名称>-<时间戳>.tar, 还会请求将该检查点存档存储到其根目录(由 --root-dir 定义)下的 checkpoints 子目录中。 这个目录默认为 /var/lib/kubelet/checkpoints。

检查点存档的格式为 tar,可以使用 tar 的一种实现来读取。存档文件的内容取决于底层 CRI 实现(该节点的容器运行时)。

HTTP 请求

POST /checkpoint/{namespace}/{pod}/{container}

参数

  • namespace (路径参数):string,必需

    名字空间(Namespace)
  • pod (路径参数):string,必需

    Pod
  • container (路径参数):string,必需

    容器(Container)
  • timeout (查询参数):integer

    等待检查点创建完成的超时时间(单位为秒)。 如果超时值为零或未设定,将使用默认的 CRI 超时时间值。 生成检查点所需的时长直接取决于容器所用的内存。容器使用的内存越多,创建相应检查点所需的时间越长。

响应

200: OK

401: Unauthorized

404: Not Found(如果 ContainerCheckpoint 特性门控被禁用)

404: Not Found(如果指定的 namespace、pod 或 container 无法被找到)

500: Internal Server Error(如果执行检查点操作期间 CRI 实现遇到一个错误(参阅错误消息了解更多细节))

500: Internal Server Error(如果 CRI 实现未实现检查点 CRI API(参阅错误消息了解更多细节))

2 - Linux 内核版本要求

说明: 本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循CNCF 网站指南,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读内容指南。

许多特性依赖于特定的内核功能,并且有最低的内核版本要求。 然而,单纯依赖内核版本号可能不足以满足某些操作系统发行版, 因为像 RHEL、Ubuntu 和 SUSE 等发行版的维护者们通常会将选定的特性反向移植到较旧的内核版本(保留较旧的内核版本)。

Pod sysctl

在 Linux 中,sysctl() 系统调用在运行时配置内核参数。 你可以使用名为 sysctl 的命令行工具来配置这些参数,许多参数通过 proc 文件系统暴露。

某些 sysctl 仅可用于足够新的内核上。

以下 sysctl 具有最低的内核版本要求, 并在安全集中得到了支持:

  • net.ipv4.ip_local_reserved_ports(自 Kubernetes 1.27 起,需要内核 3.16+)。
  • net.ipv4.tcp_keepalive_time(自 Kubernetes 1.29 起,需要内核 4.5+)。
  • net.ipv4.tcp_fin_timeout(自 Kubernetes 1.29 起,需要内核 4.6+)。
  • net.ipv4.tcp_keepalive_intvl(自 Kubernetes 1.29 起,需要内核 4.5+)。
  • net.ipv4.tcp_keepalive_probes(自 Kubernetes 1.29 起,需要内核 4.5+)。
  • net.ipv4.tcp_syncookies(自内核 4.6+ 添加了命名空间作用域)。
  • net.ipv4.tcp_rmem(自 Kubernetes 1.32,需要内核 4.15+)。
  • net.ipv4.tcp_wmem(自 Kubernetes 1.32,需要内核 4.15+)。
  • net.ipv4.vs.conn_reuse_mode(用于 ipvs 代理模式,需要内核 4.1+)。

kube proxy nftables 代理模式

对于 Kubernetes 1.36,kube-proxy 的 nftables 模式要求 nft 命令行工具为 v1.0.1 或更高版本,要求内核为 v5.13 或更高版本。

出于测试/开发目的,你可以使用较旧的内核,如果你在 kube-proxy 配置中设置 nftables.skipKernelVersionCheck 选项, 最老可以回溯到 v5.4。但在生产环境中不推荐这样做,因为这可能会导致系统上其他 nftables 用户出现问题。

v2 控制组

Kubernetes 对 CGroup v1 的支持从 v1.31 开始处于维护模式;推荐使用 CGroup v2。 在 Linux 5.8 中,为了方便使用,系统层面的 cpu.stat 文件被添加到根 CGroup。

在 runc 文档中,不推荐使用低于 5.2 的内核,因为其缺少冻结特性。

压力阻塞信息(PSI)

Linux 内核 v4.20 及更高版本支持压力阻塞信息, 但需要以下配置:

  • 内核必须使用 CONFIG_PSI=y 选项进行编译(大多数现代发行版默认启用此选项)。 你可以通过运行 zgrep CONFIG_PSI /proc/config.gz 来检查内核的配置。
  • 某些 Linux 发行版可能会将 PSI 编译进内核,但默认情况下是禁用的。 如果是这样,你需要在启动时通过向内核命令行添加 psi=1 参数来启用它。

其他内核要求

某些特性可能依赖于新的内核功能并具有特定的内核要求:

  1. 递归只读挂载: 这是通过应用 MOUNT_ATTR_RDONLY 属性和 AT_RECURSIVE 标志来实现的,使用的是在 Linux 内核 v5.12 中添加的 mount_setattr(2)。
  2. Pod 用户命名空间支持需要最低内核版本 6.5+,参阅 KEP-127。
  3. 对于节点系统交换, 直到内核 6.3 才支持将 tmpfs 设置为 noswap。

Linux 内核长期维护

你可以在 kernel.org 找到活动的内核版本。

通常会提供多个长期维护内核版本,用于将 Bug 修复反向移植到较旧的内核树。 特别是对于较旧的树,只有重要的 Bug 修复才会被应用到此类内核,这些内核通常不会频繁发布新版本。 请参阅 Linux 内核网站,了解 Longterm 类别中的发布列表。

接下来

3 - Kubelet Pod Info gRPC API

特性状态: Kubernetes v1.35 [alpha](默认禁用)

Kubelet Pod Info gRPC API 为节点本地组件提供了一种直接从 kubelet 查询节点上运行的 Pod 信息的方法。 这通过移除对 Kubernetes API 服务器的节点本地信息依赖来提高可靠性,并减少控制平面的负载。

通过 UNIX 套接字上的文件权限,该 API 的访问权限被限制为本地管理员用户(通常为 root)。

端点

API 在以下 UNIX 套接字上监听: /var/lib/kubelet/pods/kubelet.sock

说明:

此 API 在 Windows 节点上不受支持。

操作

API 提供以下 gRPC 方法:

ListPods

返回 kubelet 在节点上当前管理的所有 Pod 列表。

WatchPods

返回 Pod 更新流。每当 Pod 的状态在本地发生变化时,kubelet 会通过该流发送更新后的 Pod 信息。

GetPod

返回由 UID 标识的特定 Pod 的信息。

API 定义

API 使用以下 protobuf 定义:

import "google/protobuf/field_mask.proto";
import "k8s.io/api/core/v1/generated.proto";

service Pods {
    // ListPods 返回 v1.Pod 列表,可选地按字段掩码过滤。
    rpc ListPods(PodListRequest) returns (PodListResponse) {}
    // WatchPods 返回 Pod 更新流,可选地按字段掩码过滤。
    rpc WatchPods(PodWatchRequest) returns (stream PodWatchResponse) {}
    // GetPod 返回给定 Pod UID 的 v1.Pod,可选地按字段掩码过滤。
    rpc GetPod(PodGetRequest) returns (PodGetResponse) {}
}

message PodListRequest {
    // gRPC 元数据中的可选字段掩码,用于指定要返回的 Pod 字段。
}

message PodListResponse {
    repeated v1.Pod pods = 1;
}

message PodWatchRequest {
    // gRPC 元数据中的可选字段掩码,用于指定要返回的 Pod 字段。
}

message PodWatchResponse {
    v1.Pod pod = 1;
}

message PodGetRequest {
    string podUID = 1;
    // gRPC 元数据中的可选字段掩码,用于指定要返回的 Pod 字段。
}

message PodGetResponse {
    v1.Pod pod = 1;
}

字段选择

API 支持 google.protobuf.FieldMask,允许客户端仅请求他们需要的特定字段(例如 status.phase、status.podIPs)。 这实现了精简和高效的数据传输。如果未提供字段掩码,则返回完整的 v1.Pod 对象。

可靠性和可用性

API 提供 kubelet 本地已知的最新的信息,这些信息来自其内部缓存以及与容器运行时的协调。 即使节点失去与 Kubernetes 控制平面的连接,该 API 仍然可用。

如果 kubelet 最近重启且其内部状态尚未完全初始化,API 会返回 gRPC FAILED_PRECONDITION 错误。

4 - 关于 dockershim 移除和使用兼容 CRI 运行时的文章

这是关于 Kubernetes 弃用和移除 dockershim 或使用兼容 CRI 的容器运行时相关的文章和其他页面的列表。

Kubernetes 项目

你可以通过 GitHub 问题 Dockershim 移除反馈和问题 提供反馈。 (k/kubernetes/#106917)

外部来源

5 - 由 kubelet 填充的节点标签

Kubernetes 节点预先填充了一组标准 标签。

你还可以通过 kubelet 配置或使用 Kubernetes API 在节点上设置自己的标签。

预设标签

Kubernetes 在节点上设置的预设标签有:

说明:

这些标签的值是特定于云提供商的,并且不保证其可靠性。 例如,kubernetes.io/hostname 的值在某些环境中可能与节点名称相同, 而在其他环境中可能与节点名称不同。

接下来

6 - kubelet 所使用的本地文件和路径

kubelet 是一个运行在 Kubernetes 节点上的无状态进程。本文简要介绍了 kubelet 读写的文件。

说明:

本文仅供参考,而非描述保证会发生的行为或 API。 本文档列举 kubelet 所使用的资源。所给的信息属于实现细节,可能会在后续版本中发生变更。

kubelet 通常使用控制面作为需要在 Node 上运行的事物的真实来源,并使用容器运行时获取容器的当前状态。 只要你向 kubelet 提供 kubeconfig(API 客户端配置),kubelet 就会连接到你的控制面; 否则,节点将以**独立(Standalone)**模式运行。

在 Linux 节点上,kubelet 还需要读取 cgroups 和各种系统文件来收集指标。

在 Windows 节点上,kubelet 不依赖于路径,而是通过其他机制来收集指标。

kubelet 所使用的还有其他文件,包括其使用本地 Unix 域套接字进行通信的文件。 有些文件是 kubelet 要监听的套接字,而其他套接字则是 kubelet 先发现后作为客户端连接的。

说明:

本页列举的路径为 Linux 路径,若要映射到 Windows,你可以添加根磁盘 C:\ 替换 /(除非另行指定)。 例如,/var/lib/kubelet/device-plugins 映射到 C:\var\lib\kubelet\device-plugins。

配置

kubelet 配置文件

你可以使用命令行参数 --config 指定 kubelet 配置文件的路径。kubelet 还支持插件(Drop-in)配置文件来增强配置。

证书

证书和私钥通常位于 /var/lib/kubelet/pki,但你可以使用 --cert-dir kubelet 命令行参数进行配置。 证书文件的名称也是可以配置的。

清单

静态 Pod 的清单通常位于 /etc/kubernetes/manifests。 你可以使用 staticPodPath kubelet 配置选项进行配置。

systemd 单元设置

当 kubelet 作为 systemd 单元运行时,一些 kubelet 配置可以在 systemd 单元设置文件中声明。 这些配置通常包括:

状态

资源管理器的检查点文件

所有资源管理器将 Pod 与已分配资源之间的映射保存在状态文件中。 状态文件位于 kubelet 的基础目录,也称为根目录(但与节点根目录 / 不同)之下。 你可以使用 kubelet 命令行参数 --root-dir 来配置 kubelet 的基础目录。

文件名称:

设备管理器的检查点文件

设备管理器在与套接字文件相同的目录(/var/lib/kubelet/device-plugins/)中创建检查点。 对于设备管理器, 检查点文件的名称为 kubelet_internal_checkpoint。

Pod 状态检查点

特性状态: Kubernetes v1.35 [stable](默认启用)

如果某个节点已启用了 InPlacePodVerticalScaling 特性门控, 则 kubelet 存储有关 Pod 资源已分配和已应用状态的本地记录。
有关如何使用这些记录的更多细节, 请参阅调整分配给容器的 CPU 和内存资源。

文件名称如下:

  • allocated_pods_state:记录分配给该节点上每个 Pod 的资源。
  • actuated_pods_state:记录运行时已接受并应用于该节点上每个 Pod 的资源。

这些文件位于 kubelet 的基础目录中(在 Linux 系统中默认是 /var/lib/kubelet; 也可以通过 --root-dir 参数进行配置)。

容器运行时

kubelet 使用通过配置参数所配置的套接字与容器运行时进行通信:

  • containerRuntimeEndpoint 用于运行时操作
  • imageServiceEndpoint 用于镜像管理操作

这些端点的实际值取决于所使用的容器运行时。

设备插件

kubelet 在路径 /var/lib/kubelet/device-plugins/kubelet.sock 为各个要注册的设备插件公开一个套接字。

当设备插件注册自己时,它会为提供其套接字路径供 kubelet 连接使用。

设备插件套接字应位于 kubelet 基础目录中的 device-plugins 目录内。 在典型的 Linux 节点上,这意味着 /var/lib/kubelet/device-plugins。

Pod Resources API

Pod Resources API 将在路径 /var/lib/kubelet/pod-resources 上被公开。

DRA、CSI 和设备插件

kubelet 会查找通过 DRA 设备管理器或存储插件所管理的设备插件所创建的套接字文件,然后尝试连接到这些套接字。 kubelet 查找的目录是 kubelet 基础目录下的 plugins_registry, 因此在典型的 Linux 节点上这意味着 /var/lib/kubelet/plugins_registry。

请注意,对于设备插件,有两种备选的注册机制。每个给定的插件只能使用其中一种注册机制。

可以将套接字文件放入该目录的插件类型包括:

  • CSI 插件
  • DRA 插件
  • 设备管理器插件

(通常是 /var/lib/kubelet/plugins_registry)。

节点体面关闭

特性状态: Kubernetes v1.21 [beta](默认启用)

节点体面关闭将状态存储在本地目录 /var/lib/kubelet/graceful_node_shutdown_state。

镜像拉取记录

特性状态: Kubernetes v1.35 [beta](默认启用)

kubelet 存储镜像拉取的尝试记录和成功记录,并使用这些记录来验证镜像是否曾使用相同的凭据被成功拉取过。

这些记录作为文件缓存在 kubelet 基础目录下的 image_registry 目录中。 在典型的 Linux 节点上,这个路径通常为 /var/lib/kubelet/image_manager。
image_manager 目录下包含两个子目录:

  • pulling:存储 kubelet 正在尝试拉取的镜像的相关记录。
  • pulled:存储 kubelet 成功拉取的镜像记录,以及与拉取所用凭据相关的元数据。

更多细节请参阅确保镜像拉取凭据验证。

安全配置文件和配置

Seccomp

被 Pod 引用的 Seccomp 配置文件应放置在 /var/lib/kubelet/seccomp。 有关细节请参见 Seccomp 参考。

AppArmor

kubelet 不会通过特定于 Kubernetes 的路径加载或引用 AppArmor 配置文件。 AppArmor 配置文件通过节点操作系统被加载,而不是通过其路径被引用。

加锁

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

kubelet 的锁文件;通常为 /var/run/kubelet.lock。 kubelet 使用此文件确保尝试运行两个不同的、彼此冲突的 kubelet。 你可以使用 --lock-file kubelet 命令行参数来配置这个锁文件的路径。

如果同一节点上的两个 kubelet 使用不同的锁文件路径值,则这两个 kubelet 在同时运行时将不会检测到冲突。

接下来

7 - Kubelet 同步循环

kubelet 是每个节点上的主要 "节点代理",负责创建和监视 Pod。kubelet 运行一个同步循环,周期性地将期望状态(Pod 规约) 与运行中容器的实际状态进行协调。

  1. 同步循环(Sync Loop):同步循环将分配给其节点(nodeName 与节点匹配)的 Pod 的工作(从多个来源聚合)排入队列。在每次循环过程中,被称为 Pod 工作器(Pod Worker) 的子进程会尝试将这些 Pod 的期望状态与运行中容器的当前状态进行协调。
  2. 同步 Pod(Sync Pod):kubelet 的大部分逻辑存储在 podSyncer 接口中的一组函数里, 包括 SyncPod 函数及其变体(如 SyncTerminatingPod 和 SyncTerminatedPod)。 在每次同步循环中,每个 Pod 都会执行一个相关的 podSyncer 函数, 尝试将节点上的 Pod 状态驱动到期望状态。
  3. 容器运行时接口(CRI): 要实际运行容器,kubelet 使用 CRI 与容器运行时(如 containerd 或 CRI-O)通信。 kubelet 作为客户端,指示运行时创建 "Pod 沙箱",然后按照 Pod 规约中的 定义创建/启动各个容器。
  4. PLEG(Pod 生命周期事件生成器):kubelet 需要知道容器何时启动、停止或失败。 它依赖一个名为 PLEG 的组件,周期性地轮询运行时以获取所有容器的标准状态。 PLEG 生成的事件会唤醒同步循环以更新 Pod 状态。

由于这种轮询机制,通过 API 看到的状态(如 kubectl get pod) 相比节点上的即时实际情况可能会有轻微延迟。

8 - kubelet 配置目录合并

当使用 kubelet 的 --config-dir 标志来指定存放配置的目录时,不同类型的配置会有一些特定的行为。

以下是在配置合并过程中不同数据类型的一些行为示例:

结构字段

在 YAML 结构中有两种结构字段:独立(标量类型)和嵌入式(此结构包含标量类型)。 配置合并过程将处理独立构造字段和嵌入式构造字段的重载,以创建最终的 kubelet 配置。

例如,你可能想要为所有节点设置一个基准 kubelet 配置,但希望自定义 address 和 authorization 字段。 这种情况下,你可以按以下方式完成:

kubelet 主配置文件内容:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 20250
authorization:
  mode: Webhook
  webhook:
    cacheAuthorizedTTL: "5m"
    cacheUnauthorizedTTL: "30s"
serializeImagePulls: false
address: "192.168.0.1"

--config-dir 目录中文件的内容:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authorization:
  mode: AlwaysAllow
  webhook:
    cacheAuthorizedTTL: "8m"
    cacheUnauthorizedTTL: "45s"
address: "192.168.0.8"

生成的配置如下所示:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 20250
serializeImagePulls: false
authorization:
  mode: AlwaysAllow
  webhook:
    cacheAuthorizedTTL: "8m"
    cacheUnauthorizedTTL: "45s"
address: "192.168.0.8"

列表

你可以重载 kubelet 配置的切片/列表值。 但在合并过程中整个列表将被重载。 例如,你可以按以下方式重载 clusterDNS 列表:

kubelet 主配置文件的内容:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 20250
serializeImagePulls: false
clusterDNS:
  - "192.168.0.9"
  - "192.168.0.8"

--config-dir 目录中文件的内容:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
clusterDNS:
  - "192.168.0.2"
  - "192.168.0.3"
  - "192.168.0.5"

生成的配置如下所示:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 20250
serializeImagePulls: false
clusterDNS:
  - "192.168.0.2"
  - "192.168.0.3"
  - "192.168.0.5"

含嵌套结构的映射

映射中的各个字段(无论其值类型是布尔值、字符串等)都可以被选择性地重载。 但对于 map[string][]string 类型来说,与特定字段关联的整个列表都将被重载。 让我们通过一个例子更好地理解这一点,特别是 featureGates 和 staticPodURLHeader 这类字段:

kubelet 主配置文件的内容:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 20250
serializeImagePulls: false
featureGates:
  AllAlpha: false
  MemoryQoS: true
staticPodURLHeader:
  kubelet-api-support:
  - "Authorization: 234APSDFA"
  - "X-Custom-Header: 123"
  custom-static-pod:
  - "Authorization: 223EWRWER"
  - "X-Custom-Header: 456"

--config-dir 目录中文件的内容:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  MemoryQoS: false
  KubeletTracing: true
  DynamicResourceAllocation: true
staticPodURLHeader:
  custom-static-pod:
  - "Authorization: 223EWRWER"
  - "X-Custom-Header: 345"

生成的配置如下所示:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
port: 20250
serializeImagePulls: false
featureGates:
  AllAlpha: false
  MemoryQoS: false
  KubeletTracing: true
  DynamicResourceAllocation: true
staticPodURLHeader:
  kubelet-api-support:
  - "Authorization: 234APSDFA"
  - "X-Custom-Header: 123"
  custom-static-pod:
  - "Authorization: 223EWRWER"
  - "X-Custom-Header: 345"

9 - Kubelet 设备管理器 API 版本

本页详述了 Kubernetes 设备插件 API 与不同版本的 Kubernetes 本身之间的版本兼容性。

兼容性矩阵

v1alpha1v1beta1
Kubernetes 1.21-✓
Kubernetes 1.22-✓
Kubernetes 1.23-✓
Kubernetes 1.24-✓
Kubernetes 1.25-✓
Kubernetes 1.26-✓

简要说明:

  • ✓ 设备插件 API 和 Kubernetes 版本中的特性或 API 对象完全相同。
  • + 设备插件 API 具有 Kubernetes 集群中可能不存在的特性或 API 对象, 不是因为设备插件 API 添加了额外的新 API 调用,就是因为服务器移除了旧的 API 调用。 但它们的共同点是(大多数其他 API)都能工作。 请注意,Alpha API 可能会在次要版本的迭代过程中消失或出现重大变更。
  • - Kubernetes 集群具有设备插件 API 无法使用的特性,不是因为服务器添加了额外的 API 调用, 就是因为设备插件 API 移除了旧的 API 调用。但它们的共同点是(大多数 API)都能工作。

10 - kubelet systemd 看门狗

特性状态: Kubernetes v1.32 [beta](默认启用)

在 Linux 节点上,Kubernetes 1.36 支持与 systemd 集成,以允许操作系统监视程序恢复失败的 kubelet。 这种集成默认并未被启用。它可以作为一个替代方案,通过定期请求 kubelet 的 /healthz 端点进行健康检查。 如果 kubelet 在设定的超时时限内未对看门狗做出响应,看门狗将杀死 kubelet。

systemd 看门狗的工作原理是要求服务定期向 systemd 进程发送一个保持活跃的信号。 如果 systemd 进程在指定的超时时限内未接收到某服务发出的信号,则对应的服务被视为无响应并被终止。 之后 systemd 进程可以基于配置重启该服务。

配置

使用 systemd 看门狗需要在 kubelet 服务单元文件的 [Service] 部分配置 WatchdogSec 参数:

[Service]
WatchdogSec=30s

设置 WatchdogSec=30s 表示服务看门狗超时时限为 30 秒。 在 kubelet 内,sd_notify() 函数被调用,以 \( WatchdogSec \div 2\) 的时间间隔, 发送 WATCHDOG=1(保持活跃的消息)。如果在超时时限内看门狗未被“投喂”此信号,kubelet 将被杀死。 将 Restart 设置为 "always"、"on-failure"、"on-watchdog" 或 "on-abnormal" 将确保服务被自动重启。

systemd 配置相关的一些细节:

  1. 如果你将 systemd 的 WatchdogSec 值设置为 0,或省略不设置,则对应的单元上不启用 systemd 看门狗。
  2. kubelet 支持设置的最小看门狗超时时限为 1.0 秒;这是为了防止 kubelet 被意外杀死。 你可以在 systemd 单元定义中将 WatchdogSec 的值设置为短于 1 秒的超时时限, 但 Kubernetes 不支持任何更短的时间间隔。超时时限不必是整数的秒数。
  3. Kubernetes 项目建议将 WatchdogSec 时限设置为大约 15 秒。 系统支持超过 10 分钟的时限设置,但明确不推荐这样做。

示例配置

[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/home/
Wants=network-online.target
After=network-online.target

[Service]
ExecStart=/usr/bin/kubelet
# 配置看门狗的超时时限
WatchdogSec=30s
Restart=on-failure
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

接下来

有关 systemd 配置的细节,请参阅 systemd 文档。

11 - Seccomp 和 Kubernetes

Seccomp 表示安全计算(Secure Computing)模式,自 2.6.12 版本以来,一直是 Linux 内核的一个特性。 它可以用来沙箱化进程的权限,限制进程从用户态到内核态的调用。 Kubernetes 能使你自动将加载到节点上的 seccomp 配置文件应用到你的 Pod 和容器。

Seccomp 字段

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

有四种方式可以为 Pod 指定 seccomp 配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: pod
spec:
  securityContext:
    seccompProfile:
      type: Unconfined
  ephemeralContainers:
  - name: ephemeral-container
    image: debian
    securityContext:
      seccompProfile:
        type: RuntimeDefault
  initContainers:
  - name: init-container
    image: debian
    securityContext:
      seccompProfile:
        type: RuntimeDefault
  containers:
  - name: container
    image: docker.io/library/debian:stable
    securityContext:
      seccompProfile:
        type: Localhost
        localhostProfile: my-profile.json

上面的示例中的 Pod 以 Unconfined 运行,而 ephemeral-container 和 init-container 独立设置了 RuntimeDefault。 如果临时容器或 Init 容器没有明确设置 securityContext.seccompProfile 字段, 则此值将从 Pod 继承。同样的机制也适用于运行 Localhost 配置文件 my-profile.json 的容器。

一般来说,(临时)容器的字段优先级高于 Pod 层级的值,而未设置 seccomp 字段的容器则从 Pod 继承配置。

说明:

你不可以将 seccomp 配置文件应用到在容器的 securityContext 中设置了 privileged: true 的 Pod 或容器。特权容器始终以 Unconfined 运行。

对于 seccompProfile.type,可以使用以下值:

Unconfined
工作负载在没有任何 seccomp 限制的情况下运行。
RuntimeDefault
由容器运行时定义的默认 seccomp 配置文件被应用。这个默认的配置文件旨在提供一套强大的安全默认值,同时保持工作负载的功能不受影响。 不同的容器运行时及其版本之间的默认配置文件可能会有所不同, 例如在比较 CRI-O 和 containerd 的默认配置文件时就会发现不同。
Localhost
localhostProfile 将被应用,这一配置必须位于节点磁盘上(在 Linux 上是 /var/lib/kubelet/seccomp)。 在创建容器时,容器运行时会验证 seccomp 配置文件的可用性。如果此配置文件不存在,则容器创建将失败,并报错 CreateContainerError。

Localhost 配置文件

Seccomp 配置文件是遵循 OCI 运行时规范定义的 JSON 文件。配置文件主要根据所匹配的系统调用来定义操作,但也允许将特定值作为参数传递给系统调用。例如:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 38,
  "syscalls": [
    {
      "names": [
        "adjtimex",
        "alarm",
        "bind",
        "waitid",
        "waitpid",
        "write",
        "writev"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

上述配置文件中的 defaultAction 被定义为 SCMP_ACT_ERRNO,并可回退至 syscalls 中所定义的操作。 此错误通过 defaultErrnoRet 字段被定义为代码 38。

通常可以使用以下操作:

SCMP_ACT_ERRNO
返回指定的错误码。
SCMP_ACT_ALLOW
允许执行系统调用。
SCMP_ACT_KILL_PROCESS
杀死进程。
SCMP_ACT_KILL_THREAD 和 SCMP_ACT_KILL
仅杀死线程。
SCMP_ACT_TRAP
发送 SIGSYS 信号。
SCMP_ACT_NOTIFY 和 SECCOMP_RET_USER_NOTIF
通知用户空间。
SCMP_ACT_TRACE
使用指定的值通知跟踪进程。
SCMP_ACT_LOG
在将操作记录到 syslog 或 auditd 之后,允许执行系统调用。

SCMP_ACT_NOTIFY 或 SECCOMP_RET_USER_NOTIF 这类操作可能不被支持, 具体取决于所使用的容器运行时、OCI 运行时或 Linux 内核版本。也可能存在其他限制, 例如 SCMP_ACT_NOTIFY 不能用作 defaultAction 或用于某些系统调用(如 write)。 所有这些限制由 OCI 运行时 (runc、crun) 或 libseccomp 所定义。

syscalls JSON 数组包含对象列表,每个对象通过系统调用的 names 引用系统调用。 例如,SCMP_ACT_ALLOW 操作可用于创建包含如上例所示的系统调用的白名单。 也可以使用 SCMP_ACT_ERRNO 操作定义另一个列表,但会有不同的返回值(errnoRet)。

你还可以指定传递给某些系统调用的参数(args)。有关这些高级用例的细节,请参见 OCI 运行时规范 和 Seccomp Linux 内核文档。

进一步阅读

12 - 节点状态

在 Kubernetes 中,节点的状态是管理 Kubernetes 集群的一个关键方面。在本文中,我们将简要介绍如何监控和维护节点状态以确保集群的健康和稳定。

节点状态字段

一个节点的状态包含以下信息:

你可以使用 kubectl 来查看节点状态和其他细节信息:

kubectl describe node <节点名称>

下面对输出的每个部分进行详细描述。

地址

这些字段的用法取决于你的云服务商或者物理机配置。

  • HostName:由节点的内核报告。可以通过 kubelet 的 --hostname-override 参数覆盖。
  • ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。
  • InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。

状况

conditions 字段描述了所有 Running 节点的状况。状况的示例包括:

节点状况及每种状况适用场景的描述
节点状况描述
Ready如节点是健康的并已经准备好接收 Pod 则为 True;False 表示节点不健康而且不能接收 Pod;Unknown 表示节点控制器在最近 node-monitor-grace-period 期间(默认 50 秒)没有收到节点的消息
DiskPressureTrue 表示节点存在磁盘空间压力,即磁盘可用量低,否则为 False
MemoryPressureTrue 表示节点存在内存压力,即节点内存可用量低,否则为 False
PIDPressureTrue 表示节点存在进程压力,即节点上进程过多;否则为 False
NetworkUnavailableTrue 表示节点网络配置不正确;否则为 False

说明:

如果使用命令行工具来打印已保护(Cordoned)节点的细节,其中的 Condition 字段可能包括 SchedulingDisabled。SchedulingDisabled 不是 Kubernetes API 中定义的 Condition,被保护起来的节点在其规约中被标记为不可调度(Unschedulable)。

在 Kubernetes API 中,节点的状况表示节点资源中 .status 的一部分。 例如,以下 JSON 结构描述了一个健康节点:

"conditions": [
  {
    "type": "Ready",
    "status": "True",
    "reason": "KubeletReady",
    "message": "kubelet is posting ready status",
    "lastHeartbeatTime": "2019-06-05T18:38:35Z",
    "lastTransitionTime": "2019-06-05T11:41:27Z"
  }
]

当节点上出现问题时,Kubernetes 控制面会自动创建与影响节点的状况对应的 污点。 例如当 Ready 状况的 status 保持 Unknown 或 False 的时间长于 kube-controller-manager 的 NodeMonitorGracePeriod(默认为 50 秒)时, 会造成 Unknown 状态下为节点添加 node.kubernetes.io/unreachable 污点或在 False 状态下为节点添加 node.kubernetes.io/not-ready 污点。

这些污点会影响悬决的 Pod,因为调度器在将 Pod 分配到节点时会考虑节点的污点。 已调度到节点的当前 Pod 可能会由于施加的 NoExecute 污点被驱逐。 Pod 还可以设置容忍度, 使得这些 Pod 仍然能够调度到且继续运行在设置了特定污点的节点上。

进一步的细节可参阅基于污点的驱逐 和根据状况为节点设置污点。

容量(Capacity)与可分配(Allocatable)

这两个值描述节点上的可用资源:CPU、内存和可以调度到节点上的 Pod 的个数上限。

capacity 块中的字段标示节点拥有的资源总量。 allocatable 块指示节点上可供普通 Pod 使用的资源量。

你可以通过学习如何在节点上预留计算资源 来进一步了解有关容量和可分配资源的信息。

信息(Info)

Info 指的是节点的一般信息,如内核版本、Kubernetes 版本(kubelet 和 kube-proxy 版本)、 容器运行时详细信息,以及节点使用的操作系统。 kubelet 从节点收集这些信息并将其发布到 Kubernetes API。

声明式特性

特性状态: Kubernetes v1.36 [beta](默认启用)

此字段列出了通过特性门控在节点的 kubelet 上当前已启用的特定 Kubernetes 特性。 kubelet 会将这些特性以字符串列表的形式报告到 Node 对象的 .status.declaredFeatures 字段中。

此字段用于记录正在积极开发的新特性; 已完成且不再需要特性门控的功能被视为基线功能,无需在此字段中声明。 这反映的是 Kubernetes 特性的启用情况,而非节点底层操作系统或内核的特性。

有关更多详细信息,请参阅节点声明特性。

心跳

Kubernetes 节点发送的心跳帮助你的集群确定每个节点的可用性,并在检测到故障时采取行动。

对于节点,有两种形式的心跳:

与节点的 .status 更新相比,Lease 是一种轻量级资源。 使用 Lease 来表达心跳在大型集群中可以减少这些更新对性能的影响。

kubelet 负责创建和更新节点的 .status,以及更新它们对应的 Lease。

  • 当节点状态发生变化时,或者在配置的时间间隔内没有更新事件时,kubelet 会更新 .status。 .status 更新的默认间隔为 5 分钟(比节点不可达事件的 40 秒默认超时时间长很多)。
  • kubelet 会创建并每 10 秒(默认更新间隔时间)更新 Lease 对象。 Lease 的更新独立于节点的 .status 更新而发生。 如果 Lease 的更新操作失败,kubelet 会采用指数回退机制,从 200 毫秒开始重试, 最长重试间隔为 7 秒钟。

13 - 节点重启后会发生什么

节点上的系统组件有时会重新启动,可能是因为升级、崩溃或管理员的显式操作。本文描述了当 kubelet、 容器运行时 或整个节点重新启动时,Pod 和节点会发生什么。

在健康的集群中,这些重启通常是安全的,不会破坏正在运行的工作负载。以下部分描述需要注意的影响, 这些影响在大型或负载较重的节点上会更加明显。 最具破坏性的情况是节点重启, 它包含容器运行时重启和 kubelet 重启,但后果更严重,因为节点上的每个容器都会首先停止。

kubelet 重启的影响

如果只有 kubelet 重新启动,已经运行的容器继续运行。 kubelet 重新建立其对节点的视图,并根据期望状态协调正在运行的容器。 在此期间,会发生以下情况:

  • kubelet 重新初始化并重新同步其缓存,这会向 API 服务器发送大量请求。 在有许多 Pod 的大型节点上,这种请求突发可能会很显著。
  • 在 kubelet 完成初始化之前,节点暂时报告为 NotReady。当节点处于 NotReady 状态时, 调度器不会在其上放置新的 Pod。
  • 节点心跳在 kubelet 宕机期间暂停, 一旦 kubelet 重新启动并完成初始化(此时 kubelet 会更新其 Lease 对象并再次发布节点状态),心跳就会恢复。
  • kubelet 在重启期间会保留运行中容器的就绪状态。每个 Pod 的就绪状态会驱动 EndpointSlice、 Endpoints 以及下游配置(如 Gateway 或 Ingress);这意味着每次重启时重置容器就绪状态会给 API 服务器和监视端点状态的组件带来很大负载, 并可能短暂地将健康的 Pod 从 Service 负载均衡中移除。此行为在 KEP-4781:修复 kubelet 重启后容器就绪状态不一致的问题 中有描述。长期以来,每次重启时将容器就绪状态重置为 false 是默认行为。 ChangeContainerStatusOnKubeletRestart 特性门控 允许你恢复到该行为,但这是一个已弃用的遗留后门,计划移除,因此你不应依赖它。 有关详细信息,请参阅 kubelet 重启期间的 Pod 行为。
  • 在 kubelet 初始启动期间,未使用的镜像和容器的 垃圾回收 以及由节点压力驱动的 Pod 驱逐都会暂停。 此暂停在 kubelet 完成其主要启动例程后仍会持续一段短暂的宽限期。 这种延迟可能会减慢节点对内存或磁盘压力的反应。
  • 正在进行的镜像拉取会被取消。根据容器运行时的不同,取消的拉取在重试时可能必须从头开始。
  • 当 kubelet 重新通过其准入检查时,节点上的 Pod 会再次运行 Pod 准入。如果节点的 标签或 污点在 kubelet 宕机期间发生了变化, 即使 Pod 已经在运行,它也可能在准入时失败并被拒绝。这是现有行为,是否应将其视为 BUG 仍在讨论中; 有关讨论和详细信息,请参阅 kubernetes/kubernetes#123859。

总体而言,在健康的集群中,kubelet 重启不会破坏正在运行的工作负载。然而,在节点过度使用的大型集群中, 重新初始化负载以及暂停的垃圾回收和驱逐可能会导致系统不稳定。

Kubernetes 没有定义容器运行时重启时的行为。 根据你使用的容器运行时,重启可能会触发所有本地容器的停止或重启。 然而,大多数与 Kubernetes 一起使用的容器运行时都使用一种允许你重启运行时而让容器继续执行的配置。

容器运行时重启的影响

当容器运行时(如 containerd 或 CRI-O) 重新启动时,kubelet 会失去与运行时的连接,直到运行时恢复。在此期间:

  • 在重启期间,exec 探针 会失败,因为 kubelet 无法在容器内运行命令。如果超时时间短且失败阈值低,失败的存活探针会导致容器重启, 失败的就绪探针会导致 Pod 在 Ready 状态之间波动。
  • kubelet 将节点报告为 NotReady,这会阻止在该节点上调度新的 Pod。
  • 容器操作(如重启、初始化和状态更新)会延迟,直到运行时再次可用。
  • 如果在运行时重启时,某个 Init 容器正在执行, 其执行状态可能会丢失,在这种情况下,Init 容器会再次运行。
  • 在极少数情况下,在精确时刻中断操作可能会导致状态不一致:

    • 中断的镜像拉取可能会留下不一致的镜像层,这可能会使镜像无法使用,直到再次拉取。

    • 中断的 Sandbox 创建,如果在 CNI 或 NRI 调用中途终止,可能会使 Sandbox 处于不一致状态, CNI 仅部分初始化,并可能出现资源泄漏。

在精确时刻中断操作是低概率事件,因此重启容器运行时通常是安全的操作。在负载较重的节点上, 每个操作都较慢,中断关键操作的时间窗口更大,遇到这些边缘情况的概率会增加。

节点重启的影响

节点重启是这些事件中最具破坏性的,因为节点上的每个容器都会停止。重启包含容器运行时重启和 kubelet 重启,但后果更严重:独立的 kubelet 或运行时重启会让已运行的容器保持不变, 而重启会首先停止每个容器。节点启动后,kubelet 和容器运行时重新启动,但没有任何容器实际运行。

在计划重启之前,你可以通过隔离(cordon)节点来减少影响,使调度器停止在其上放置新的 Pod, 然后腾空节点以优雅地驱逐现有 Pod。 当启用体面节点关闭时, kubelet 还会在检测到节点正在关闭时尝试干净地停止运行中的 Pod。

当节点恢复时:

  • 重启会停止所有容器,当节点恢复时,kubelet 会重新创建它们。如果节点宕机时间超过下面描述的配置容忍期, 只有由控制器管理的 Pod(如 Deployment、 StatefulSet 或 DaemonSet) 会获得替换 Pod。替换的 Pod 可能会调度到不同的节点上。独立 Pod(没有其他对象或控制器管理它们) 在删除后不会重新创建。
  • 节点更新租约并协调其状态。在 kubelet、容器运行时和网络准备就绪之前,节点会报告为 NotReady。 当节点处于 NotReady 状态时,节点可能会被标记污点 node.kubernetes.io/not-ready,在配置的容忍期过后,控制平面可以驱逐不容忍该污点的 Pod。
  • kubelet 对分配给该节点的 Pod 重新运行准入,因此 kubelet 重启下描述的标签和污点注意事项也适用于此。
  • 对于请求设备的 Pod,kubelet 会再次调用相关的 设备插件 来确认正在节点上恢复的 Pod 的设备分配。设备插件必须在重启后重新向 kubelet 注册,以便协调这些分配。
  • 与容器或 Pod 生命周期绑定的本地存储可能会丢失。当容器重新创建时,其可写层会被丢弃, 因此写入那里的数据无法在重启后存活。 emptyDir 卷的生命周期与 Pod 在节点上的时间相同: 内存支持的 emptyDir(medium: Memory)在重启时总是会丢失,因为它存储在 RAM 中, 而磁盘支持的 emptyDir 只要 Pod 不被驱逐或删除,就能在重启后存活,只有当 Pod 离开节点时才会被删除。

对于必须容忍节点重启的工作负载,请通过控制器运行 Pod, 对必须存活的数据使用持久卷, 并配置中断预算和探针, 以便只在 Pod 就绪后才向其发送流量。

接下来

14 - Linux 节点的交换(Swap)行为

要允许 Kubernetes 工作负载在 Linux 节点上使用交换分区, 你必须禁用 kubelet 在检测到交换分区时失败的默认行为, 并指定内存交换行为为 LimitedSwap:

可用的交换行为选项有:

NoSwap
(默认)在此节点上作为 Pod 运行的工作负载不会也不能使用交换分区。 然而,系统守护进程(包括 kubelet 本身!)等这类 Kubernetes 范围之外的进程可以利用交换分区。 这种行为有助于保护节点免受系统级别的内存峰值影响, 但这不能保护工作负载本身不受此类峰值的影响。
LimitedSwap
Kubernetes 工作负载可以使用交换内存,Pod 可用的交换量是自动确定的。

要了解更多,请阅读交换内存管理。