kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

Dynamic Kubelet Configurationを試す

f:id:kun432:20210120011530p:plain

kubernetesクラスタを構築していくつかのpodを走らせた後で、kubeletの設定を変更したい場合ってありますよね。Production環境だと多分こういう手順になるかと思います。

  • kubectl drainでnodeをクラスタから切り離し
  • node上で設定変更してkubelet再起動
  • kubectl uncordonでnodeをクラスタに戻す

ただ台数が多くなってくるとちょっとこれは辛い。ということで、動的にkubeletの設定変更を行うDynamic Kubelet Configurationを試してみました。

目次

参考にさせていただいたサイト

ほぼほぼ以下の通りにやればOKですが、少しだけ修正してます。

Dynamic Kubelet Configurationとは

Dynamic Kubelet Configurationを使うと、kubeletの起動時の設定をconfigMapで管理することができるようになります。つまりkubeletの設定もkubernetesのリソースとして管理ができるようになります。また、設定内容に不備があった場合の切り戻しも行えるようになります。

例によって、以下のレポジトリでクラスタが作成されているところからスタートします。

実際には上記のレポジトリから少し変更していて、Kubernetesのバージョンはv1.20になっていたりしますが、その点については影響はないと思います。ただjqコマンドとかは入ってないので別途入れる必要があります。(そのうち反映します)

Dynamic Kubelet Configurationの有効化

Dynamic Kubelet Configurationはデフォルトでは無効化されていますので、kubeletの起動オプションを変更して、有効化します。必要なのは以下の2つですが、1つ目はデフォルトで有効になっているため、2つ目だけです。

  • --feature-gates="DynamicKubeletConfig=true"
  • --dynamic-config-dir=<path>

worker nodeにログインします。

$ vagrant ssh worker-1

もし追加するなら/etc/default/kubeletあたりが良いかなと思うのですが、上記のサイトに従って/var/lib/kubelet/kubeadm-flags.envに追加しましょう。

$ sudo vi /var/lib/kubelet/kubeadm-flags.env

オプションが並んでいる最後に--dynamic-config-dir=/var/lib/kubelet-dynamicを追加します。指定したパスにconfigMapの設定が保存されます。

KUBELET_KUBEADM_ARGS="--network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.2 --dynamic-config-dir=/var/lib/kubelet-dynamic"

kubeletを再起動します。

$ sudo systemctl restart kubelet
$ sudo systemctl status kubelet

プロセスを確認すると、--dynamic-config-dirのオプションが指定されているのがわかりますね。

$ ps auxw | grep kubelet
root     27939  5.6  4.5 1712868 91800 ?       Ssl  02:34   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 --node-ip=10.240.0.31

同様にして他のworker nodeも変更します。

Kubeletの設定をConfigMapに移行する

Dynamic Kubelet Configurationでは、Kubeletの設定をConfigMapに保存します。ということは、最初に現在のKubeletの設定が必要になります。

Kubeletの設定は以下で取得できます。

$ kubectl proxy &

$ curl -sSL "http://localhost:8001/api/v1/nodes/worker-1/proxy/configz" | jq '.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1"' > kubelet_configz_worker

kubectl proxyでapiserverにアクセスできるようにして、apiserverの/api/v1/nodes/worker nodeの名前/proxy/configzにアクセスすると、現在のworker nodeの設定が取得できますので、これをファイルに出力しておきます。

ファイルの中身はこんな感じです。ちなみに、どのnodeを選んでも同じでした。

{
  "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": "systemd",
  "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としてcreateしてあげればいいのですが、JSONのままだとちょっと扱いづらいですね。今後のことも考えてYAMLでmanifest化します。JSONからYAMLへの変換はgojsontoyamlあたりを使えば良いと思います。

$ cat kubelet_configz_worker | gojsontoyaml > kubelet_configz_worker.yaml
$ kubectl -n kube-system create configmap my-node-config --from-file=kubelet=kubelet_configz_worker.yaml -o yaml --dry-run=client > dynamic-kubelet-config.yaml

出力されたmanifestはこうなりました。

apiVersion: v1
data:
  kubelet: |
    address: 0.0.0.0
    apiVersion: kubelet.config.k8s.io/v1beta1
    authentication:
      anonymous:
        enabled: false
      webhook:
        cacheTTL: 2m0s
        enabled: true
      x509:
        clientCAFile: /etc/kubernetes/pki/ca.crt
    authorization:
      mode: Webhook
      webhook:
        cacheAuthorizedTTL: 5m0s
        cacheUnauthorizedTTL: 30s
    cgroupDriver: systemd
    cgroupsPerQOS: true
    clusterDNS:
    - 10.96.0.10
    clusterDomain: cluster.local
    configMapAndSecretChangeDetectionStrategy: Watch
    containerLogMaxFiles: 5
    containerLogMaxSize: 10Mi
    contentType: application/vnd.kubernetes.protobuf
    cpuCFSQuota: true
    cpuCFSQuotaPeriod: 100ms
    cpuManagerPolicy: none
    cpuManagerReconcilePeriod: 10s
    enableControllerAttachDetach: true
    enableDebuggingHandlers: true
    enableServer: true
    enableSystemLogHandler: true
    enforceNodeAllocatable:
    - pods
    eventBurst: 10
    eventRecordQPS: 5
    evictionHard:
      imagefs.available: 15%
      memory.available: 100Mi
      nodefs.available: 10%
      nodefs.inodesFree: 5%
    evictionPressureTransitionPeriod: 5m0s
    failSwapOn: true
    fileCheckFrequency: 20s
    hairpinMode: promiscuous-bridge
    healthzBindAddress: 127.0.0.1
    healthzPort: 10248
    httpCheckFrequency: 20s
    imageGCHighThresholdPercent: 85
    imageGCLowThresholdPercent: 80
    imageMinimumGCAge: 2m0s
    iptablesDropBit: 15
    iptablesMasqueradeBit: 14
    kind: KubeletConfiguration
    kubeAPIBurst: 10
    kubeAPIQPS: 5
    logging:
      format: text
    makeIPTablesUtilChains: true
    maxOpenFiles: 1000000
    maxPods: 110
    nodeLeaseDurationSeconds: 40
    nodeStatusMaxImages: 50
    nodeStatusReportFrequency: 5m0s
    nodeStatusUpdateFrequency: 10s
    oomScoreAdj: -999
    podPidsLimit: -1
    port: 10250
    registryBurst: 10
    registryPullQPS: 5
    resolvConf: /run/systemd/resolve/resolv.conf
    rotateCertificates: true
    runtimeRequestTimeout: 2m0s
    serializeImagePulls: true
    shutdownGracePeriod: 0s
    shutdownGracePeriodCriticalPods: 0s
    staticPodPath: /etc/kubernetes/manifests
    streamingConnectionIdleTimeout: 4h0m0s
    syncFrequency: 1m0s
    tlsCertFile: /var/lib/kubelet/pki/kubelet.crt
    tlsPrivateKeyFile: /var/lib/kubelet/pki/kubelet.key
    topologyManagerPolicy: none
    topologyManagerScope: container
    volumePluginDir: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/
    volumeStatsAggPeriod: 1m0s
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: my-node-config
  namespace: kube-system

applyします。

$ kubectl apply -f dynamic-kubelet-config.yaml
configmap/my-node-config created

確認してみましょう。

$ kubectl -n kube-system get configmap my-node-config -o yaml
apiVersion: v1
data:
  kubelet: |
    address: 0.0.0.0
    apiVersion: kubelet.config.k8s.io/v1beta1
    authentication:
      anonymous:
        enabled: false
      webhook:
        cacheTTL: 2m0s
        enabled: true
      x509:
        clientCAFile: /etc/kubernetes/pki/ca.crt
    authorization:
      mode: Webhook
      webhook:
        cacheAuthorizedTTL: 5m0s
        cacheUnauthorizedTTL: 30s
    cgroupDriver: systemd
    cgroupsPerQOS: true
    clusterDNS:
    - 10.96.0.10
    clusterDomain: cluster.local
    configMapAndSecretChangeDetectionStrategy: Watch
    containerLogMaxFiles: 5
    containerLogMaxSize: 10Mi
    contentType: application/vnd.kubernetes.protobuf
    cpuCFSQuota: true
    cpuCFSQuotaPeriod: 100ms
    cpuManagerPolicy: none
    cpuManagerReconcilePeriod: 10s
    enableControllerAttachDetach: true
    enableDebuggingHandlers: true
    enableServer: true
    enableSystemLogHandler: true
    enforceNodeAllocatable:
    - pods
    eventBurst: 10
    eventRecordQPS: 5
    evictionHard:
      imagefs.available: 15%
      memory.available: 100Mi
      nodefs.available: 10%
      nodefs.inodesFree: 5%
    evictionPressureTransitionPeriod: 5m0s
    failSwapOn: true
    fileCheckFrequency: 20s
    hairpinMode: promiscuous-bridge
    healthzBindAddress: 127.0.0.1
    healthzPort: 10248
    httpCheckFrequency: 20s
    imageGCHighThresholdPercent: 85
    imageGCLowThresholdPercent: 80
    imageMinimumGCAge: 2m0s
    iptablesDropBit: 15
    iptablesMasqueradeBit: 14
    kind: KubeletConfiguration
    kubeAPIBurst: 10
    kubeAPIQPS: 5
    logging:
      format: text
    makeIPTablesUtilChains: true
    maxOpenFiles: 1000000
    maxPods: 110
    nodeLeaseDurationSeconds: 40
    nodeStatusMaxImages: 50
    nodeStatusReportFrequency: 5m0s
    nodeStatusUpdateFrequency: 10s
    oomScoreAdj: -999
    podPidsLimit: -1
    port: 10250
    registryBurst: 10
    registryPullQPS: 5
    resolvConf: /run/systemd/resolve/resolv.conf
    rotateCertificates: true
    runtimeRequestTimeout: 2m0s
    serializeImagePulls: true
    shutdownGracePeriod: 0s
    shutdownGracePeriodCriticalPods: 0s
    staticPodPath: /etc/kubernetes/manifests
    streamingConnectionIdleTimeout: 4h0m0s
    syncFrequency: 1m0s
    tlsCertFile: /var/lib/kubelet/pki/kubelet.crt
    tlsPrivateKeyFile: /var/lib/kubelet/pki/kubelet.key
    topologyManagerPolicy: none
    topologyManagerScope: container
    volumePluginDir: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/
    volumeStatsAggPeriod: 1m0s
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"kubelet":"address: 0.0.0.0\napiVersion: kubelet.config.k8s.io/v1beta1\nauthentication:\n  anonymous:\n    enabled: false\n  webhook:\n    cacheTTL: 2m0s\n    enabled: true\n  x509:\n    clientCAFile: /etc/kubernetes/pki/ca.crt\nauthorization:\n  mode: Webhook\n  webhook:\n    cacheAuthorizedTTL: 5m0s\n    cacheUnauthorizedTTL: 30s\ncgroupDriver: systemd\ncgroupsPerQOS: true\nclusterDNS:\n- 10.96.0.10\nclusterDomain: cluster.local\nconfigMapAndSecretChangeDetectionStrategy: Watch\ncontainerLogMaxFiles: 5\ncontainerLogMaxSize: 10Mi\ncontentType: application/vnd.kubernetes.protobuf\ncpuCFSQuota: true\ncpuCFSQuotaPeriod: 100ms\ncpuManagerPolicy: none\ncpuManagerReconcilePeriod: 10s\nenableControllerAttachDetach: true\nenableDebuggingHandlers: true\nenableServer: true\nenableSystemLogHandler: true\nenforceNodeAllocatable:\n- pods\neventBurst: 10\neventRecordQPS: 5\nevictionHard:\n  imagefs.available: 15%\n  memory.available: 100Mi\n  nodefs.available: 10%\n  nodefs.inodesFree: 5%\nevictionPressureTransitionPeriod: 5m0s\nfailSwapOn: true\nfileCheckFrequency: 20s\nhairpinMode: promiscuous-bridge\nhealthzBindAddress: 127.0.0.1\nhealthzPort: 10248\nhttpCheckFrequency: 20s\nimageGCHighThresholdPercent: 85\nimageGCLowThresholdPercent: 80\nimageMinimumGCAge: 2m0s\niptablesDropBit: 15\niptablesMasqueradeBit: 14\nkind: KubeletConfiguration\nkubeAPIBurst: 10\nkubeAPIQPS: 5\nlogging:\n  format: text\nmakeIPTablesUtilChains: true\nmaxOpenFiles: 1000000\nmaxPods: 110\nnodeLeaseDurationSeconds: 40\nnodeStatusMaxImages: 50\nnodeStatusReportFrequency: 5m0s\nnodeStatusUpdateFrequency: 10s\noomScoreAdj: -999\npodPidsLimit: -1\nport: 10250\nregistryBurst: 10\nregistryPullQPS: 5\nresolvConf: /run/systemd/resolve/resolv.conf\nrotateCertificates: true\nruntimeRequestTimeout: 2m0s\nserializeImagePulls: true\nshutdownGracePeriod: 0s\nshutdownGracePeriodCriticalPods: 0s\nstaticPodPath: /etc/kubernetes/manifests\nstreamingConnectionIdleTimeout: 4h0m0s\nsyncFrequency: 1m0s\ntlsCertFile: /var/lib/kubelet/pki/kubelet.crt\ntlsPrivateKeyFile: /var/lib/kubelet/pki/kubelet.key\ntopologyManagerPolicy: none\ntopologyManagerScope: container\nvolumePluginDir: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/\nvolumeStatsAggPeriod: 1m0s\n"},"kind":"ConfigMap","metadata":{"annotations":{},"creationTimestamp":null,"name":"my-node-config","namespace":"kube-system"}}
  creationTimestamp: "2021-02-17T16:00:49Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:kubelet: {}
      f:metadata:
        f:annotations:
          .: {}
          f:kubectl.kubernetes.io/last-applied-configuration: {}
    manager: kubectl-client-side-apply
    operation: Update
    time: "2021-02-17T16:00:49Z"
  name: my-node-config
  namespace: kube-system
  resourceVersion: "2480"
  uid: 1147e762-8e34-4efc-aa7c-16b3d6126452

nodeからConfigMapを参照するように変更する

Kubeletの設定がConfigMapになりましたので、次はnodeからこれを参照するようにします。nodeのmanifestの.specを参照します。

$ kubectl get node worker-1 -o yaml
(snip)
spec:
  podCIDR: 192.168.1.0/24
  podCIDRs:
  - 192.168.1.0/24
(snip)

ここに以下のように.spec. configSource. configMapを追加すればよいです。

spec:
  podCIDR: 192.168.1.0/24
  podCIDRs:
  - 192.168.1.0/24
  configSource:
    configMap:
      name: my-node-config
      namespace: kube-system
      kubeletConfigKey: kubelet

kubectl editでもよいですが、patchなら一発です。

$ kubectl patch node worker-1 -p '"spec": { "configSource": { "configMap": { "name": "my-node-config", "namespace": "kube-system", "kubeletConfigKey": "kubelet" } } }'

見てみましょう。

$ kubectl get node worker-1 -o yaml
(snip)
spec:
  configSource:
    configMap:
      kubeletConfigKey: kubelet
      name: my-node-config
      namespace: kube-system
  podCIDR: 192.168.1.0/24
  podCIDRs:
(snip)
status:
(snip)
  config:
    active:
      configMap:
        kubeletConfigKey: kubelet
        name: my-node-config
        namespace: kube-system
        resourceVersion: "2480"
        uid: 1147e762-8e34-4efc-aa7c-16b3d6126452
    assigned:
      configMap:
        kubeletConfigKey: kubelet
        name: my-node-config
        namespace: kube-system
        resourceVersion: "2480"
        uid: 1147e762-8e34-4efc-aa7c-16b3d6126452
 (snip)

ConfigMapを参照する設定が追加されていると同時に、configのステータスとしてactive, assignedなどが表示されていますね。

さらにnodeのイベントを見てみます。

$ kubectl describe node worker-1

ConfigMapを参照するように設定したと同時にkubeletが自動的に再起動されているのがわかりますね。

(snip)
Events:
  Type    Reason                   Age   From     Message
  ----    ------                   ----  ----     -------
...
  Normal  KubeletConfigChanged     3m12s              kubelet     Kubelet restarting to use /api/v1/namespaces/kube-system/configmaps/my-node-config, UID: 1147e762-8e34-4efc-aa7c-16b3d6126452, ResourceVersion: 2480, KubeletConfigKey: kubelet
  Normal  Starting                 2m56s              kubelet     Starting kubelet.
  Normal  NodeHasSufficientMemory  2m56s              kubelet     Node worker-1 status is now: NodeHasSufficientMemory
  Normal  NodeHasNoDiskPressure    2m56s              kubelet     Node worker-1 status is now: NodeHasNoDiskPressure
  Normal  NodeHasSufficientPID     2m56s              kubelet     Node worker-1 status is now: NodeHasSufficientPID
  Normal  NodeAllocatableEnforced  2m56s              kubelet     Updated Node Allocatable limit across pods

nodeのkubeletの設定をConfigMapで変更する

では変更してみましょう。元の設定にはevictionHardの設定はありますが、evictionSoftの設定はないので、それを追加してみたいと思います。ちなみにevictionSoftの設定を行う場合はevictionSoftGracePeriodの設定も必要です。

manifestを修正します。

(snip)
    evictionHard:
      imagefs.available: 15%
      memory.available: 100Mi
      nodefs.available: 10%
      nodefs.inodesFree: 5%
    evictionSoft:
      imagefs.available: 30%
      memory.available: 200Mi
      nodefs.available: 20%
      nodefs.inodesFree: 10%
    evictionSoftGracePeriod:
      memory.available: 1m30s
      nodefs.available: 1m30s
      nodefs.inodesFree: 1m30s
      imagefs.available: 1m30s
      imagefs.inodesFree: 1m30s
    evictionPressureTransitionPeriod: 5m0s
(snip)

適用します。

$ kubectl apply -f dynamic-kubelet-config.yaml
configmap/my-node-config configured

今回の環境ではworker nodeは2台ありますので、それぞれを見てみましょう。

$ kubectl describe node worker-1
(snip)
  Normal  KubeletConfigChanged     57s                kubelet     Kubelet restarting to use /api/v1/namespaces/kube-system/configmaps/my-node-config, UID: a58be86a-70fa-4309-8b59-da04b0a1bb5f, ResourceVersion: 5066, KubeletConfigKey: kubelet
  Normal  Starting                 42s                kubelet     Starting kubelet.
  Normal  NodeHasSufficientMemory  42s                kubelet     Node worker-1 status is now: NodeHasSufficientMemory
  Normal  NodeHasNoDiskPressure    42s                kubelet     Node worker-1 status is now: NodeHasNoDiskPressure
  Normal  NodeHasSufficientPID     42s                kubelet     Node worker-1 status is now: NodeHasSufficientPID
  Normal  NodeNotReady             42s                kubelet     Node worker-1 status is now: NodeNotReady
  Normal  NodeAllocatableEnforced  42s                kubelet     Updated Node Allocatable limit across pods
  Normal  NodeReady                42s                kubelet     Node worker-1 status is now: NodeReady
$ kubectl describe node worker-2
(snip)
  Normal  KubeletConfigChanged     18s                kubelet     Kubelet restarting to use /api/v1/namespaces/kube-system/configmaps/my-node-config, UID: a58be86a-70fa-4309-8b59-da04b0a1bb5f, ResourceVersion: 5172, KubeletConfigKey: kubelet
  Normal  Starting                 8s                 kubelet     Starting kubelet.
  Normal  NodeHasSufficientMemory  7s                 kubelet     Node worker-2 status is now: NodeHasSufficientMemory
  Normal  NodeHasNoDiskPressure    7s                 kubelet     Node worker-2 status is now: NodeHasNoDiskPressure
  Normal  NodeHasSufficientPID     7s                 kubelet     Node worker-2 status is now: NodeHasSufficientPID
  Normal  NodeAllocatableEnforced  7s                 kubelet     Updated Node Allocatable limit across pods

2台ともkubeletが再起動しているのがわかりますね。

kubeletの設定が反映されているかも確認してみます。

$ curl -sSL "http://localhost:8001/api/v1/nodes/worker-1/proxy/configz" | jq '.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1" | .evictionSoft, .evictionSoftGracePeriod'
{
  "imagefs.available": "30%",
  "memory.available": "200Mi",
  "nodefs.available": "20%",
  "nodefs.inodesFree": "10%"
}
{
  "imagefs.available": "1m30s",
  "imagefs.inodesFree": "1m30s",
  "memory.available": "1m30s",
  "nodefs.available": "1m30s",
  "nodefs.inodesFree": "1m30s"
}

こちらもOKですね。1回の変更で全てのnodeのkubeletの設定変更ができて、コードとして管理もできるので非常に良いですね!

設定をミスった場合

実はここに来るまでに少し失敗していたので、その点についても書いておきます。

上で書いたとおり、evictionSoftの設定を行う場合はevictionSoftGracePeriodの設定も必要ですが、evictionSoftGracePeriodの設定をせずに反映した場合どうなるでしょうか?

manifestを以下のように修正します。

(snip)
    evictionHard:
      imagefs.available: 15%
      memory.available: 100Mi
      nodefs.available: 10%
      nodefs.inodesFree: 5%
    evictionSoft:
      imagefs.available: 30%
      memory.available: 200Mi
      nodefs.available: 20%
      nodefs.inodesFree: 10%
    evictionPressureTransitionPeriod: 5m0s
(snip)

適用します。

$ kubectl apply -f dynamic-kubelet-config.yaml
configmap/my-node-config configured

適用してしばらくすると・・・

$ kubectl get node
NAME       STATUS     ROLES                  AGE   VERSION
master     Ready      control-plane,master   66m   v1.20.0
worker-1   NotReady   <none>                 55m   v1.20.0
worker-2   NotReady   <none>                 44m   v1.20.0

workerがNotReadyになってしまいました・・・workerにログインして見てみましょう。

kubeletのプロセスが動いていません。

$ ps auxw | grep kubelet
vagrant    879  0.0  0.0  14864  1044 pts/0    S+   02:15   0:00 grep --color=auto kubelet

起動に失敗しています。

$ sudo systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
  Drop-In: /etc/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: activating (auto-restart) (Result: exit-code) since Sun 2021-02-21 02:16:19 JST; 8s ago
     Docs: https://kubernetes.io/docs/home/
  Process: 1272 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (code=exited, status=255)
 Main PID: 1272 (code=exited, status=255)

(snip)

ログを見てみると・・・

Feb 21 02:13:10 worker-1 kubelet[30284]: F0221 02:13:10.041844   30284 server.go:269] failed to run Kubelet: failed to create kubelet: grace period must be specified for the soft eviction threshold nodefs.available

soft eviction threashholdを指定する場合はgrace periodの設定が必要、ということですね。

kubeletが動いていないとConfigMapを変更しても反映されません。したがって、手動で復旧する必要があるようです。

一旦、正常に動作していた状態のmanifestを元に戻します。

$ kubectl apply -f dynamic-kubelet-config.yaml

worker nodeにログインして、ローカルに保存されているkubeletの設定を削除して、kubeletを再起動します。

$ sudo rm -rf /var/lib/kubelet-dynamic/*
$ sudo systemctl restart kubelet

kubeletのプロセスが上がっていますね。

$ ps auxw | grep kube
root     10683  7.1  4.5 1712868 91860 ?       Ssl  02:25   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 --node-ip=10.240.0.31

nodeがReadyに戻りました。

$ kubectl get node
NAME       STATUS   ROLES                  AGE   VERSION
master     Ready    control-plane,master   87m   v1.20.0
worker-1   Ready    <none>                 76m   v1.20.0
worker-2   Ready    <none>                 64m   v1.20.0

設定が正しいかどうかまではチェックしてくれるというわけではないということですかね。ちょっと怖いですね。

まとめ

いちいち各worker nodeで設定変更しなくて済むのはとても便利ですし、manifestとしてコード管理できるのもよいです。反面、コンフィグの正常性までチェックしてくれるわけではなさそうなので、商用環境でやる場合には事前に検証してから実施したほうが良いですね。