--- title: 在运行中的集群上重新配置节点的 kubelet content_type: task --- {{< feature-state for_k8s_version="v1.22" state="deprecated" >}} {{< caution >}} [动态 kubelet 配置](https://github.com/kubernetes/enhancements/issues/281) 已经废弃不建议使用。请选择其他方法将配置分发到集群中的节点。 {{< /caution >}} [动态 kubelet 配置](https://github.com/kubernetes/enhancements/issues/281) 允许你通过部署一个所有节点都会使用的 ConfigMap 达到在运行中的 Kubernetes 集群中更改 kubelet 配置的目的。 {{< warning >}} 所有 kubelet 配置参数都可以被动态更改,但对某些参数来说这类更改是不安全的。 在决定动态更改参数之前,你需要深刻理解这个改动将会如何影响集群的行为。 在将变更扩散到整个集群之前,你需要先在小规模的节点集合上仔细地测试这些配置变动。 特定字段相关的配置建议可以在文档 [`KubeletConfiguration`](/docs/reference/config-api/kubelet-config.v1beta1/)中找到。 {{< /warning >}} ## {{% heading "prerequisites" %}} 你需要一个 Kubernetes 集群。 你需要 v1.11 或更高版本的 kubectl,并配置好与集群的通信。 {{< version-check >}} 你的集群 API 服务器版本(如 v1.12)不能和你的 kubectl 版本相差超过一个小版本号。 例如,如果你的集群在运行 v1.16,那么你可以使用 v1.15、v1.16、v1.17 的 kubectl, 所有其他的组合都是 [不支持的](/zh/docs/setup/release/version-skew-policy/#kubectl)。 在某些例子中使用了命令行工具 [jq](https://stedolan.github.io/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 也是合法的。 {{< warning >}} 尽管通过就地更新 ConfigMap 来更改配置是 *可能的*。 但是这样做会导致所有使用该 ConfigMap 配置的 kubelet 同时更新。 更安全的做法是按惯例将 ConfigMap 视为不可变更的,借助于 `kubectl` 的 `--append-hash` 选项逐步把更新推广到 `node.spec.configSource`。 {{< /warning >}} ### 节点鉴权器的自动 RBAC 规则 以前,你需要手动创建 RBAC 规则以允许节点访问其分配的 ConfigMap。节点鉴权器现在 能够自动配置这些规则。 ### 生成包含当前配置的文件 动态 kubelet 配置特性允许你为整个配置对象提供一个重载配置,而不是靠单个字段的叠加。 这是一个更简单的模型,可以更轻松地跟踪配置值的来源,更便于调试问题。 然而,相应的代价是你必须首先了解现有配置,以确保你只更改你打算修改的字段。 组件 kubelet 从其配置文件中加载配置数据,不过你可以通过设置命令行标志 来重载文件中的一些配置。这意味着,如果你仅知道配置文件的内容,而你不知道 命令行重载了哪些配置,你就无法知道 kubelet 的运行时配置是什么。 因为你需要知道运行时所使用的配置才能重载之,你可以从 kubelet 取回其运行时配置。 你可以通过访问 kubelet 的 `configz` 末端来生成包含节点当前配置的配置文件; 这一操作可以通过 `kubectl proxy` 来完成。 下一节解释如何完成这一操作。 {{< caution >}} 组件 `kubelet` 上的 `configz` 末端是用来协助调试的,并非 kubelet 稳定行为的一部分。 请不要在产品环境下依赖此末端的行为,也不要在自动化工具中使用此末端。 {{< /caution >}} 关于如何使用配置文件来配置 kubelet 行为的更多信息可参见 [通过配置文件设置 kubelet 参数](/zh/docs/tasks/administer-cluster/kubelet-config-file) 文档。 #### 生成配置文件 {{< note >}} 下面的任务步骤中使用了 `jq` 命令以方便处理 JSON 数据。为了完成这里讲述的任务, 你需要安装 `jq`。如果你更希望手动提取 `kubeletconfig` 子对象,也可以对这里 的对应步骤做一些调整。 {{< /note >}} 1. 选择要重新配置的节点。在本例中,此节点的名称为 `NODE_NAME`。 2. 使用以下命令在后台启动 kubectl 代理: ```shell kubectl proxy --port=8001 & ``` 3. 运行以下命令从 `configz` 端点中下载并解压配置。这个命令很长,因此在复制粘贴时要小心。 **如果你使用 zsh**,请注意常见的 zsh 配置要添加反斜杠转义 URL 中变量名称周围的大括号。 例如:在粘贴时,`${NODE_NAME}` 将被重写为 `$\{NODE_NAME\}`。 你必须在运行命令之前删除反斜杠,否则命令将失败。 ```bash 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} ``` {{< note >}} 你需要手动将 `kind` 和 `apiVersion` 添加到下载对象中,因为它们不是由 `configz` 末端 返回的。 {{< /note >}} #### 修改配置文件 使用文本编辑器,改变上述操作生成的文件中一个参数。 例如,你或许会修改 QPS 参数 `eventRecordQPS`。 #### 把配置文件推送到控制平面 用以下命令把编辑后的配置文件推送到控制平面: ```bash kubectl -n kube-system create configmap my-node-config \ --from-file=kubelet=kubelet_configz_${NODE_NAME} \ --append-hash -o yaml ``` 下面是合法响应的一个例子: ```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`。 #### 配置节点使用新的配置 ```bash kubectl edit node ${NODE_NAME} ``` 在你的文本编辑器中,在 `spec` 下增添以下 YAML: ```yaml configSource: configMap: name: CONFIG_MAP_NAME namespace: kube-system kubeletConfigKey: kubelet ``` 你必须同时指定 `name`、`namespace` 和 `kubeletConfigKey` 这三个属性。 `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`)过滤出配置状态: ```bash kubectl get no ${NODE_NAME} -o json | jq '.status.config' ``` 以下是一个响应示例: ```json { "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` 中显示出错误信息的结构体。 错误可能出现在列表[理解节点状态配置错误信息](#understanding-node-config-status-errors)中。 你可以在 kubelet 日志中搜索相同的文本以获取更多详细信息和有关错误的上下文。 #### 做出更多的改变 {#make-more-changes} 按照下面的工作流程做出更多的改变并再次推送它们。 你每次推送一个 ConfigMap 的新内容时,kubeclt 的 `--append-hash` 选项都会给 ConfigMap 创建一个新的名称。 最安全的上线策略是首先创建一个新的 ConfigMap,然后更新节点以使用新的 ConfigMap。 #### 重置节点以使用其本地默认配置 要重置节点,使其使用节点创建时使用的配置,可以用 `kubectl edit node $ {NODE_NAME}` 命令编辑节点,并删除 `node.spec.configSource` 字段。 #### 观察节点正在使用本地默认配置 在删除此字段后,`node.status.config` 最终变成空,所有配置源都已重置为 `nil`。 这表示本地默认配置成为了 `assigned`、`active` 和 `lastKnownGood` 配置, 并且没有报告错误。 ## `kubectl patch` 示例 你可以使用几种不同的机制来更改节点的 configSource。 本例使用`kubectl patch`: ```bash 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`)重新启动时,它会读取新的元数据并使用新配置。 当记录的元数据已被完全解析时,意味着它包含选择一个指定的配置版本所需的所有信息 -- 通常是 `UID` 和 `ResourceVersion`。 这与 `node.spec.configSource` 形成对比,后者通过幂等的 `namespace/name` 声明来标识 目标 ConfigMap;kubelet 尝试使用此 ConfigMap 的最新版本。 当你在调试节点上问题时,可以检查 kubelet 的配置元数据和检查点。kubelet 的检查点目录结构是: ```none - --dynamic-config-dir (用于管理动态配置的根目录) |-- meta | - assigned (编码后的 kubeletconfig/v1beta1.SerializedNodeConfigSource 对象,对应赋予的配置) | - last-known-good (编码后的 kubeletconfig/v1beta1.SerializedNodeConfigSource 对象,对应最近已知可用配置) | - checkpoints | - uid1 (用 uid1 来标识的对象版本目录) | - resourceVersion1 (uid1 对象 resourceVersion1 版本下所有解压文件的目录) | - ... | - ... ``` ## 理解 `Node.Status.Config.Error` 消息 {#understanding-node-config-status-errors} 下表描述了使用动态 kubelet 配置时可能发生的错误消息。 你可以在 kubelet 日志中搜索相同的文本来获取有关错误的其他详细信息和上下文。 {{< table caption = "理解 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 遇到了一些内部问题。 {{< /table >}} ## {{% heading "whatsnext" %}} - 关于如何通过配置文件来配置 kubelet 的更多细节信息,可参阅 [使用配置文件设置 kubelet 参数](/zh/docs/tasks/administer-cluster/kubelet-config-file). - 阅读 API 文档中 [`NodeConfigSource`](/docs/reference/generated/kubernetes-api/{{< param "version" >}}/#nodeconfigsource-v1-core) 说明 - 查阅[`KubeletConfiguration`](/docs/reference/config-api/kubelet-config.v1beta1/)文献进一步了解 kubelet 配置信息。