在运行中的集群上重新配置节点的 kubelet

FEATURE STATE: Kubernetes v1.22 [deprecated]
注意: 动态 kubelet 配置 已经废弃不建议使用。请选择其他方法将配置分发到集群中的节点。

动态 kubelet 配置 允许你通过部署一个所有节点都会使用的 ConfigMap 达到在运行中的 Kubernetes 集群中更改 kubelet 配置的目的。

警告: 所有 kubelet 配置参数都可以被动态更改,但对某些参数来说这类更改是不安全的。 在决定动态更改参数之前,你需要深刻理解这个改动将会如何影响集群的行为。 在将变更扩散到整个集群之前,你需要先在小规模的节点集合上仔细地测试这些配置变动。 特定字段相关的配置建议可以在文档 KubeletConfiguration中找到。

准备开始

你需要一个 Kubernetes 集群。 你需要 v1.11 或更高版本的 kubectl,并配置好与集群的通信。 要获知版本信息,请输入 kubectl version. 你的集群 API 服务器版本(如 v1.12)不能和你的 kubectl 版本相差超过一个小版本号。 例如,如果你的集群在运行 v1.16,那么你可以使用 v1.15、v1.16、v1.17 的 kubectl, 所有其他的组合都是 不支持的

在某些例子中使用了命令行工具 jq。 你并不一定需要 jq 才能完成这些任务,因为总是有一些手工替代的方式。

针对你重新配置的每个节点,你必须设置 kubelet 的标志 -dynamic-config-dir,使之指向一个可写的目录。

重配置 集群中运行节点上的 kubelet

基本工作流程概览

在运行中的集群中配置 kubelet 的基本工作流程如下:

  1. 编写一个包含 kubelet 配置的 YAML 或 JSON 文件。
  2. 将此文件包装在 ConfigMap 中并将其保存到 Kubernetes 控制平面。
  3. 更新 kubelet 所在节点对象以使用此 ConfigMap。

每个 kubelet 都会在其各自的节点对象上监测(Watch)配置引用。当引用更改时,kubelet 将下载新的配置文件, 更新本地引用指向该文件,然后退出。 为了使该功能正常地工作,你必须运行操作系统级别的服务管理器(如 systemd), 它将会在 kubelet 退出后将其重启。 kubelet 重新启动时,将开始使用新配置。

新配置将会完全地覆盖 --config 所提供的配置,并被命令行标志覆盖。 新配置中未指定的值将收到适合配置版本的默认值 (e.g. kubelet.config.k8s.io/v1beta1),除非被命令行标志覆盖。

节点 kubelet 配置状态可通过 node.spec.status.config 获取。 一旦你更新了一个节点去使用新的 ConfigMap, 就可以通过观察此状态来确认该节点是否正在使用预期配置。

本文中使用命令 kubectl edit 来编辑节点,还有其他的方式可以修改节点的规约, 比如更利于脚本化工作流程的 kubectl patch

本文仅仅讲述在单节点上使用每个 ConfigMap。请注意对于多个节点使用相同的 ConfigMap 也是合法的。

警告: 尽管通过就地更新 ConfigMap 来更改配置是 可能的。 但是这样做会导致所有使用该 ConfigMap 配置的 kubelet 同时更新。 更安全的做法是按惯例将 ConfigMap 视为不可变更的,借助于 kubectl--append-hash 选项逐步把更新推广到 node.spec.configSource

节点鉴权器的自动 RBAC 规则

以前,你需要手动创建 RBAC 规则以允许节点访问其分配的 ConfigMap。节点鉴权器现在 能够自动配置这些规则。

生成包含当前配置的文件

动态 kubelet 配置特性允许你为整个配置对象提供一个重载配置,而不是靠单个字段的叠加。 这是一个更简单的模型,可以更轻松地跟踪配置值的来源,更便于调试问题。 然而,相应的代价是你必须首先了解现有配置,以确保你只更改你打算修改的字段。

组件 kubelet 从其配置文件中加载配置数据,不过你可以通过设置命令行标志 来重载文件中的一些配置。这意味着,如果你仅知道配置文件的内容,而你不知道 命令行重载了哪些配置,你就无法知道 kubelet 的运行时配置是什么。

因为你需要知道运行时所使用的配置才能重载之,你可以从 kubelet 取回其运行时配置。 你可以通过访问 kubelet 的 configz 末端来生成包含节点当前配置的配置文件; 这一操作可以通过 kubectl proxy 来完成。 下一节解释如何完成这一操作。

注意: 组件 kubelet 上的 configz 末端是用来协助调试的,并非 kubelet 稳定行为的一部分。 请不要在产品环境下依赖此末端的行为,也不要在自动化工具中使用此末端。

关于如何使用配置文件来配置 kubelet 行为的更多信息可参见 通过配置文件设置 kubelet 参数 文档。

生成配置文件

说明: 下面的任务步骤中使用了 jq 命令以方便处理 JSON 数据。为了完成这里讲述的任务, 你需要安装 jq。如果你更希望手动提取 kubeletconfig 子对象,也可以对这里 的对应步骤做一些调整。
  1. 选择要重新配置的节点。在本例中,此节点的名称为 NODE_NAME

  2. 使用以下命令在后台启动 kubectl 代理:

    kubectl proxy --port=8001 &
    
  1. 运行以下命令从 configz 端点中下载并解压配置。这个命令很长,因此在复制粘贴时要小心。 如果你使用 zsh,请注意常见的 zsh 配置要添加反斜杠转义 URL 中变量名称周围的大括号。 例如:在粘贴时,${NODE_NAME} 将被重写为 $\{NODE_NAME\}。 你必须在运行命令之前删除反斜杠,否则命令将失败。

    NODE_NAME="the-name-of-the-node-you-are-reconfiguring"; curl -sSL "http://localhost:8001/api/v1/nodes/${NODE_NAME}/proxy/configz" | jq '.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1"' > kubelet_configz_${NODE_NAME}
    
说明: 你需要手动将 kindapiVersion 添加到下载对象中,因为它们不是由 configz 末端 返回的。

修改配置文件

使用文本编辑器,改变上述操作生成的文件中一个参数。 例如,你或许会修改 QPS 参数 eventRecordQPS

把配置文件推送到控制平面

用以下命令把编辑后的配置文件推送到控制平面:

kubectl -n kube-system create configmap my-node-config \
  --from-file=kubelet=kubelet_configz_${NODE_NAME} \
  --append-hash -o yaml

下面是合法响应的一个例子:

apiVersion: v1
kind: ConfigMap
metadata:
  creationTimestamp: 2017-09-14T20:23:33Z
  name: my-node-config-gkt4c2m4b2
  namespace: kube-system
  resourceVersion: "119980"
  selfLink: /api/v1/namespaces/kube-system/configmaps/my-node-config-gkt4c2m4b2
  uid: 946d785e-998a-11e7-a8dd-42010a800006
data:
  kubelet: |
        {...}

你会在 kube-system 命名空间中创建 ConfigMap,因为 kubelet 是 Kubernetes 的系统组件。

--append-hash 选项给 ConfigMap 内容附加了一个简短校验和。 这对于先编辑后推送的工作流程很方便, 因为它自动并确定地为新 ConfigMap 生成新的名称。 在以下示例中,包含生成的哈希字符串的对象名被称为 CONFIG_MAP_NAME

配置节点使用新的配置

kubectl edit node ${NODE_NAME}

在你的文本编辑器中,在 spec 下增添以下 YAML:

configSource:
    configMap:
        name: CONFIG_MAP_NAME
        namespace: kube-system
        kubeletConfigKey: kubelet

你必须同时指定 namenamespacekubeletConfigKey 这三个属性。 kubeletConfigKey 这个参数通知 kubelet ConfigMap 中的哪个键下面包含所要的配置。

观察节点开始使用新配置

kubectl get node ${NODE_NAME} -o yaml 命令读取节点并检查 node.status.config 内容。 状态部分报告了对应 active(使用中的)配置、assigned(被赋予的)配置和 lastKnownGood(最近已知可用的)配置的配置源。

  • active 是 kubelet 当前运行时所使用的版本。
  • assigned 参数是 kubelet 基于 node.spec.configSource 所解析出来的最新版本。
  • lastKnownGood 参数是 kubelet 的回退版本;如果在 node.spec.configSource 中 包含了无效的配置值,kubelet 可以回退到这个版本。

如果用本地配置部署节点,使其设置成默认值,这个 lastKnownGood 配置可能不存在。 在 kubelet 配置好后,将更新 lastKnownGood 为一个有效的 assigned 配置。 决定如何确定某配置成为 lastKnownGood 配置的细节并不在 API 保障范畴, 不过目前实现中采用了 10 分钟的宽限期。

你可以使用以下命令(使用 jq)过滤出配置状态:

kubectl get no ${NODE_NAME} -o json | jq '.status.config'

以下是一个响应示例:

{
  "active": {
    "configMap": {
      "kubeletConfigKey": "kubelet",
      "name": "my-node-config-9mbkccg2cc",
      "namespace": "kube-system",
      "resourceVersion": "1326",
      "uid": "705ab4f5-6393-11e8-b7cc-42010a800002"
    }
  },
  "assigned": {
    "configMap": {
      "kubeletConfigKey": "kubelet",
      "name": "my-node-config-9mbkccg2cc",
      "namespace": "kube-system",
      "resourceVersion": "1326",
      "uid": "705ab4f5-6393-11e8-b7cc-42010a800002"
    }
  },
  "lastKnownGood": {
    "configMap": {
      "kubeletConfigKey": "kubelet",
      "name": "my-node-config-9mbkccg2cc",
      "namespace": "kube-system",
      "resourceVersion": "1326",
      "uid": "705ab4f5-6393-11e8-b7cc-42010a800002"
    }
  }
}

如果你没有安装 jq,你可以查看整个响应对象,查找其中的 node.status.config 部分。

如果发生错误,kubelet 会在 Node.Status.Config.Error 中显示出错误信息的结构体。 错误可能出现在列表理解节点状态配置错误信息中。 你可以在 kubelet 日志中搜索相同的文本以获取更多详细信息和有关错误的上下文。

做出更多的改变

按照下面的工作流程做出更多的改变并再次推送它们。 你每次推送一个 ConfigMap 的新内容时,kubeclt 的 --append-hash 选项都会给 ConfigMap 创建一个新的名称。 最安全的上线策略是首先创建一个新的 ConfigMap,然后更新节点以使用新的 ConfigMap。

重置节点以使用其本地默认配置

要重置节点,使其使用节点创建时使用的配置,可以用 kubectl edit node $ {NODE_NAME} 命令编辑节点,并删除 node.spec.configSource 字段。

观察节点正在使用本地默认配置

在删除此字段后,node.status.config 最终变成空,所有配置源都已重置为 nil。 这表示本地默认配置成为了 assignedactivelastKnownGood 配置, 并且没有报告错误。

kubectl patch 示例

你可以使用几种不同的机制来更改节点的 configSource。

本例使用kubectl patch

kubectl patch node ${NODE_NAME} -p "{\"spec\":{\"configSource\":{\"configMap\":{\"name\":\"${CONFIG_MAP_NAME}\",\"namespace\":\"kube-system\",\"kubeletConfigKey\":\"kubelet\"}}}}"

了解 Kubelet 如何为配置生成检查点

当为节点赋予新配置时,kubelet 会下载并解压配置负载为本地磁盘上的一组文件。 kubelet 还记录一些元数据,用以在本地跟踪已赋予的和最近已知良好的配置源,以便 kubelet 在重新启动时知道使用哪个配置,即使 API 服务器变为不可用。 在为配置信息和相关元数据生成检查点之后,如果检测到已赋予的配置发生改变,则 kubelet 退出。 当 kubelet 被 OS 级服务管理器(例如 systemd)重新启动时,它会读取新的元数据并使用新配置。

当记录的元数据已被完全解析时,意味着它包含选择一个指定的配置版本所需的所有信息 -- 通常是 UIDResourceVersion。 这与 node.spec.configSource 形成对比,后者通过幂等的 namespace/name 声明来标识 目标 ConfigMap;kubelet 尝试使用此 ConfigMap 的最新版本。

当你在调试节点上问题时,可以检查 kubelet 的配置元数据和检查点。kubelet 的检查点目录结构是:

- --dynamic-config-dir (用于管理动态配置的根目录)
|-- meta
  | - assigned (编码后的 kubeletconfig/v1beta1.SerializedNodeConfigSource 对象,对应赋予的配置)
  | - last-known-good (编码后的 kubeletconfig/v1beta1.SerializedNodeConfigSource 对象,对应最近已知可用配置)
| - checkpoints
  | - uid1 (用 uid1 来标识的对象版本目录)
    | - resourceVersion1 (uid1 对象 resourceVersion1 版本下所有解压文件的目录)
    | - ...
  | - ...

理解 Node.Status.Config.Error 消息

下表描述了使用动态 kubelet 配置时可能发生的错误消息。 你可以在 kubelet 日志中搜索相同的文本来获取有关错误的其他详细信息和上下文。

理解 node.status.config.error 消息
错误信息 可能的原因
failed to load config, see Kubelet log for details kubelet 可能无法解析下载配置的有效负载,或者当尝试从磁盘中加载有效负载时,遇到文件系统错误。
failed to validate config, see Kubelet log for details 有效负载中的配置,与命令行标志所产生的覆盖配置以及特行门控的组合、配置文件本身、远程负载被 kubelet 判定为无效。
invalid NodeConfigSource, exactly one subfield must be non-nil, but all were nil 由于 API 服务器负责对 node.spec.configSource 执行验证,检查其中是否包含至少一个非空子字段,这个消息可能意味着 kubelet 比 API 服务器版本低,因而无法识别更新的源类型。
failed to sync: failed to download config, see Kubelet log for details kubelet 无法下载配置数据。可能是 node.spec.configSource 无法解析为具体的 API 对象,或者网络错误破坏了下载。处于此错误状态时,kubelet 将重新尝试下载。
failed to sync: internal failure, see Kubelet log for details kubelet 遇到了一些内部问题,因此无法更新其配置。 例如:发生文件系统错误或无法从内部缓存中读取对象。
internal failure, see Kubelet log for details 在对配置进行同步的循环之外操作配置时,kubelet 遇到了一些内部问题。

接下来