Kubelet の Dynamic Kubelet Configuration について
Kubelet の Dynamic Kubelet Configuration の概要
Dynamic Kubelet Configuration は kubelet の設定を kubernetes の API ( ConfigMap
) を利用して変更できるようにする機能です。
Dynamic Kubelet Configuration による Kubelet の設定変更の流れは下記の様になります。
- Dynamic Kubelet Configuration を有効化する(設定ファイルのパスを指定し、Kubelet の ConfigMap を参照するように Node のマニフェストを変更する)
- Kubelet の設定を持つ ConfigMap を新規に作成、または変更する
- Kubelet は ConfigMap の変更を検知し、設定を1で指定したパスに展開させる
- Kubelet が再起動され設定が反映される
なぜ必要?
元々 Kubelet の設定は起動時のオプションでしか設定できなかったので、設定を変更するためには VM に ssh してオプションを直接変更して再起動させるなどの方法しかありませんでした。 kubernetes の v1.10 で Kubelet の設定をファイルで指定できるようになり ( --config
)、設定のバージョン管理などは可能となりましたが、設定ファイルを各 VM に反映させる必要がありました。
Dynamic Kubelet Configuration は、Kubelet の設定を ConfigMap に持たせることでKubelet を kubernetes の他のリソースと同じ用に扱うことができるようにする機能で、これにより Kubelet の設定のバージョン管理ができるようなるだけでなく、設定の変更の適用や設定に不備があった場合のロールバックが容易になりました。
準備
kubeadm を使い kubernetes の環境を作成します。(参考: Self-managed Kubernetes in Google Compute Engine (GCE))
$ kubectl get node NAME STATUS ROLES AGE VERSION controller Ready master 4h54m v1.19.4 worker Ready <none> 4h50m v1.19.4
Dynamic Kubelet Configuration の有効化
Dynamic Kubelet Configuration を利用するには↓の2つの flag を設定する必要があります。
- --feature-gates="DynamicKubeletConfig=true"
- Dynamic Kubelet Configuration の有効化
- --dynamic-config-dir=<path>
- Kubelet の ConfigMap の設定を保存する場所の指定
--feature-gates="DynamicKubeletConfig=true"
については、デフォルトで true になってるので問題ないです。
--dynamic-config-dir=<path>
では、↑の概要で説明したように Kubelet の ConfigMap の変更を Kubelet で検知し、変更があった場合に一度その変更をローカルのストレージに保存し再起動時に参照するさいのパスを指定します。
kubeadm のインストール時に生成された Kubelet のユニットファイルを見てみると、 Kubelet の起動時に指定されるフラグが /var/lib/kubelet/kubeadm-flags.env
に記載されてることがわかる。
Kubelet のユニットファイル
# Note: This dropin only works with kubeadm and kubelet v1.11+ [Service] Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. EnvironmentFile=-/etc/default/kubelet ExecStart= ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
worker のインスタンスで /var/lib/kubelet/kubeadm-flags.env
を編集して --dynamic-config-dir=<path>
のフラグを下記のように追加する。
KUBELET_KUBEADM_ARGS="--network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.2 --dynamic-config-dir=/var/lib/kubelet-dynamic"
Kubelet の service を再起動する
sudo systemctl restart kubelet
Kubelet のプロセスを確認すると --dynamic-config-dir=/var/lib/kubelet-dynamic
のフラグが付いた状態で起動していることが確認できる。
$ ps aux | grep kubelet root 28593 15.2 1.2 1717092 94224 ? Ssl 14:43 0:00 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.2 --dynamic-config-dir=/var/lib/kubelet-dynamic
Kubelet の設定を変更する
概要で説明したように Dynamic Kubelet Configuration は稼働中の Kubelet の設定を持つ ConfigMap の変更を検知してるので、Kubelet の設定を ConfigMap に持たせる必要がある。
現状の Node の設定は apiserver に対して /api/v1/nodes/[NODE_NAME]/proxy/configz
の GET リクエストを送ることで取得できるので、apiserver にアクセスするために kubectl proxy
をバックグラウンドで実行し jq コマンドを利用して設定ファイルを作成する。
$ curl -sSL "http://localhost:8001/api/v1/nodes/worker/proxy/configz" | jq '.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1"' > kubelet_configz_worker
kubelet_configz_worker という名前で設定ファイルが作成された。
$ cat kubelet_configz_worker { "enableServer": true, "staticPodPath": "/etc/kubernetes/manifests", "syncFrequency": "1m0s", "fileCheckFrequency": "20s", "httpCheckFrequency": "20s", "address": "0.0.0.0", "port": 10250, "tlsCertFile": "/var/lib/kubelet/pki/kubelet.crt", "tlsPrivateKeyFile": "/var/lib/kubelet/pki/kubelet.key", "rotateCertificates": true, "authentication": { "x509": { "clientCAFile": "/etc/kubernetes/pki/ca.crt" }, "webhook": { "enabled": true, "cacheTTL": "2m0s" }, "anonymous": { "enabled": false } }, "authorization": { "mode": "Webhook", "webhook": { "cacheAuthorizedTTL": "5m0s", "cacheUnauthorizedTTL": "30s" } }, "registryPullQPS": 5, "registryBurst": 10, "eventRecordQPS": 5, "eventBurst": 10, "enableDebuggingHandlers": true, "healthzPort": 10248, "healthzBindAddress": "127.0.0.1", "oomScoreAdj": -999, "clusterDomain": "cluster.local", "clusterDNS": [ "10.96.0.10" ], "streamingConnectionIdleTimeout": "4h0m0s", "nodeStatusUpdateFrequency": "10s", "nodeStatusReportFrequency": "5m0s", "nodeLeaseDurationSeconds": 40, "imageMinimumGCAge": "2m0s", "imageGCHighThresholdPercent": 85, "imageGCLowThresholdPercent": 80, "volumeStatsAggPeriod": "1m0s", "cgroupsPerQOS": true, "cgroupDriver": "cgroupfs", "cpuManagerPolicy": "none", "cpuManagerReconcilePeriod": "10s", "topologyManagerPolicy": "none", "topologyManagerScope": "container", "runtimeRequestTimeout": "2m0s", "hairpinMode": "promiscuous-bridge", "maxPods": 110, "podPidsLimit": -1, "resolvConf": "/run/systemd/resolve/resolv.conf", "cpuCFSQuota": true, "cpuCFSQuotaPeriod": "100ms", "nodeStatusMaxImages": 50, "maxOpenFiles": 1000000, "contentType": "application/vnd.kubernetes.protobuf", "kubeAPIQPS": 5, "kubeAPIBurst": 10, "serializeImagePulls": true, "evictionHard": { "imagefs.available": "15%", "memory.available": "100Mi", "nodefs.available": "10%", "nodefs.inodesFree": "5%" }, "evictionPressureTransitionPeriod": "5m0s", "enableControllerAttachDetach": true, "makeIPTablesUtilChains": true, "iptablesMasqueradeBit": 14, "iptablesDropBit": 15, "failSwapOn": true, "containerLogMaxSize": "10Mi", "containerLogMaxFiles": 5, "configMapAndSecretChangeDetectionStrategy": "Watch", "enforceNodeAllocatable": [ "pods" ], "volumePluginDir": "/usr/libexec/kubernetes/kubelet-plugins/volume/exec/", "logging": { "format": "text" }, "enableSystemLogHandler": true, "shutdownGracePeriod": "0s", "shutdownGracePeriodCriticalPods": "0s", "kind": "KubeletConfiguration", "apiVersion": "kubelet.config.k8s.io/v1beta1" }
↑の設定ファイルから ConfigMap を作成する
$ kubectl -n kube-system create configmap my-node-config --from-file=kubelet=kubelet_configz_worker configmap/my-node-config created $ kubectl -n kube-system get cm my-node-config -o yaml apiVersion: v1 data: kubelet: | { "enableServer": true, "staticPodPath": "/etc/kubernetes/manifests", "syncFrequency": "1m0s", ... } kind: ConfigMap metadata: creationTimestamp: "2020-12-10T16:48:20Z" managedFields: - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:data: .: {} f:kubelet: {} manager: kubectl-create operation: Update time: "2020-12-10T16:48:20Z" name: my-node-config namespace: kube-system resourceVersion: "12622" uid: 23771797-73b5-49b7-84f9-15135b03ba5a
作成した ConfigMap を参照するように Node のマニフェストの spec
配下に kubectl edit node worker
で↓を追加します。
configSource: configMap: name: my-node-config namespace: kube-system kubeletConfigKey: kubelet
journalctl -u kubelet
で Kubelet のログを見ると Node のマニフェストの変更を終了したタイミングで Node.Spec.ConfigSource
が追加されたことが検知されて Kubelet が再起動されたことがわかる。
Dec 10 16:55:02 worker kubelet[28593]: I1210 16:55:02.911350 28593 watch.go:85] kubelet config controller: Node.Spec.ConfigSource was updated Dec 10 16:55:03 worker kubelet[28593]: I1210 16:55:03.298865 28593 configsync.go:96] kubelet config controller: Node.Spec.ConfigSource is non-empty, will checkpoint source and update config if necessary Dec 10 16:55:03 worker kubelet[28593]: I1210 16:55:03.299263 28593 download.go:194] kubelet config controller: attempting to download /api/v1/namespaces/kube-system/configmaps/my-node-config Dec 10 16:55:03 worker kubelet[28593]: I1210 16:55:03.312233 28593 download.go:199] kubelet config controller: successfully downloaded /api/v1/namespaces/kube-system/configmaps/my-node-config, UID: 23771797-73b5-49b7-84f9-15135b03ba5a, ResourceVersion: 12622 Dec 10 16:55:03 worker kubelet[28593]: I1210 16:55:03.413807 28593 configsync.go:205] kubelet config controller: Kubelet restarting to use /api/v1/namespaces/kube-system/configmaps/my-node-config, UID: 23771797-73b5-49b7-84f9-15135b03ba5a, ResourceVersion: 12622, KubeletConfigKey: kubelet Dec 10 16:55:13 worker systemd[1]: kubelet.service: Service hold-off time over, scheduling restart. Dec 10 16:55:13 worker systemd[1]: kubelet.service: Scheduled restart job, restart counter is at 1. Dec 10 16:55:13 worker systemd[1]: Stopped kubelet: The Kubernetes Node Agent. Dec 10 16:55:13 worker systemd[1]: Started kubelet: The Kubernetes Node Agent. ...
Node が my-node-config の ConfigMap を参照してるかを確認するには kubectl get node worker -o yaml
で status の config を見ればわかる。
config: active: configMap: kubeletConfigKey: kubelet name: my-node-config namespace: kube-system resourceVersion: "12622" uid: 23771797-73b5-49b7-84f9-15135b03ba5a assigned: configMap: kubeletConfigKey: kubelet name: my-node-config namespace: kube-system resourceVersion: "12622" uid: 23771797-73b5-49b7-84f9-15135b03ba5a lastKnownGood: configMap: kubeletConfigKey: kubelet name: my-node-config namespace: kube-system resourceVersion: "12622" uid: 23771797-73b5-49b7-84f9-15135b03ba5a
これで稼働中の Node が ConfigMap を参照するようになったので、次は ConfigMap を変更して Node の設定を変更してみる。
試しに registryPullQPS を変更してみる。
$ kubectl -n kube-system get cm my-node-config -o yaml | grep registryPullQPS "registryPullQPS": 10,
Kubelet のログを確認すると ConfigMap を追加したときと同じく変更を検知して Kubelet が再起動していることがわかる。
Dec 10 17:17:35 worker kubelet[20353]: I1210 17:17:35.715928 20353 watch.go:128] kubelet config controller: assigned ConfigMap was updated Dec 10 17:17:36 worker kubelet[20353]: I1210 17:17:36.324327 20353 configsync.go:96] kubelet config controller: Node.Spec.ConfigSource is non-empty, will checkpoint source and update config if necessary Dec 10 17:17:36 worker kubelet[20353]: I1210 17:17:36.324384 20353 download.go:181] kubelet config controller: checking in-memory store for /api/v1/namespaces/kube-system/configmaps/my-node-config Dec 10 17:17:36 worker kubelet[20353]: I1210 17:17:36.324407 20353 download.go:187] kubelet config controller: found /api/v1/namespaces/kube-system/configmaps/my-node-config in in-memory store, UID: 23771797-73b5-49b7-84f9-15135b03ba5a, ResourceVersion: 14953 Dec 10 17:17:36 worker kubelet[20353]: I1210 17:17:36.366558 20353 configsync.go:205] kubelet config controller: Kubelet restarting to use /api/v1/namespaces/kube-system/configmaps/my-node-config, UID: 23771797-73b5-49b7-84f9-15135b03ba5a, ResourceVersion: 14953, KubeletConfigKey: kubelet Dec 10 17:17:46 worker systemd[1]: kubelet.service: Service hold-off time over, scheduling restart. Dec 10 17:17:46 worker systemd[1]: kubelet.service: Scheduled restart job, restart counter is at 2. Dec 10 17:17:46 worker systemd[1]: Stopped kubelet: The Kubernetes Node Agent. Dec 10 17:17:46 worker systemd[1]: Started kubelet: The Kubernetes Node Agent.
Kubelet の設定を確認すると registryPullQPS が正常に変更されてることが確認できました。
$ curl -sSL "http://localhost:8001/api/v1/nodes/worker/proxy/configz" | jq '.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1"' | grep registryPullQPS "registryPullQPS": 10,