テック技報最新号へ →
インフラ

kube-controller-manager の詰まりを切り分けて調整する(49 個の controller、/flagz、--concurrent-*-syncs)

@yosshi_ · 2026-10-28
#infra#kubernetes#controller#monitoring#prometheus#tuning

kube-controller-manager には v1.37.1 時点で 49 個の controller が登録されていて、--controllers=* で選ばれるのは 43 個。Deployment が反映されない、Namespace の削除が終わらない、Node 障害時の退避が遅いといった症状のときに、どの controller のどこで待っているかを見て調整する手順をまとめる。

まず押さえること

  • --controllers は *(デフォルトで有効なもの全部)、foo で追加、-foo で無効化。bootstrap-signer-controller / token-cleaner-controller / selinux-warning-controller はデフォルト無効で、kubeadm は --controllers=*,bootstrapsigner,tokencleaner を付けている
  • feature gate が無効な controller は、--controllers に名前を足しても起動しない
  • Ingress controller、VPA、Cluster Autoscaler / Karpenter、Metrics Server、cert-manager、CoreDNS などは kube-controller-manager に含まれない。別途入れる
  • leader election により動くのは 1 台だけ。台数を増やしても処理能力は増えない(増えるのは可用性)

実際に効いているフラグを確かめる(/flagz)

v1.37 では z-pages の /flagz と /statusz がデフォルトで有効。検証環境なら API proxy 経由で読める(本番はポート 10257 の認証・認可設定が必要)。

sh
POD=$(kubectl -n kube-system get pods -l component=kube-controller-manager -o name | cut -d/ -f2)
kubectl get --raw "/api/v1/namespaces/kube-system/pods/https:${POD}:10257/proxy/flagz"
kubectl get --raw "/api/v1/namespaces/kube-system/pods/https:${POD}:10257/proxy/metrics"

人が読むための出力で、機械的なパースは保証されない。kubeadm 環境では use-service-account-credentials:true のように本体のデフォルトと違う値が入っていることがある。

メトリクスで切り分ける

症状見るメトリクス疑うこと
特定 controller だけ遅いworkqueue_depth、workqueue_queue_duration_seconds(name ラベル)ワーカー数不足
1 件の処理が遅いworkqueue_work_duration_secondsAPI サーバーの遅さか処理自体の重さ
全体が遅いrest_client_rate_limiter_duration_secondsクライアント側のレート制限(--kube-api-qps 20 / --kube-api-burst 30)
API 応答が遅いrest_client_request_duration_secondskube-apiserver / etcd 側

キュー待ちの p99(controller 別):

promql
histogram_quantile(0.99,
  sum by (name, le) (rate(workqueue_queue_duration_seconds_bucket[5m])))

調整の順番

  1. キューが溜まっている controller の --concurrent-*-syncs を上げる(例: deployment / job は既定 5、daemonset は 2、disruption は 1、namespace は 10、gc は 20)。API リクエストも増える
  2. レート制限で待っているなら --kube-api-qps / --kube-api-burst を上げる。これはクライアントごとの値で、プロセス全体の合計上限ではない。kube-apiserver 側の負荷も一緒に見る
  3. 個別の調整: EndpointSlice は --endpointslice-updates-batch-period で更新をまとめる、終了済み Pod が多いなら --terminated-pod-gc-threshold(既定 12500)を下げる、ResourceQuota は --resource-quota-sync-period(既定 5 分)
  4. 重い controller を別プロセスに分ける。--controllers で担当を絞り、--leader-elect-resource-name をグループごとに変える
sh
# Workload 系
kube-controller-manager \
  --controllers=deployment-controller,replicaset-controller,job-controller,statefulset-controller \
  --leader-elect-resource-name=kcm-workload
# Node 系
kube-controller-manager \
  --controllers=node-lifecycle-controller,node-ipam-controller,taint-eviction-controller \
  --leader-elect-resource-name=kcm-node

分けるときの注意

  • 同じ controller を 2 グループに入れると、両方の leader が同じオブジェクトを処理してしまう
  • 最後のグループを *,-deployment-controller,... のように「残り全部」にすると選び漏れを防げる
  • informer のキャッシュはプロセスごと。同じ informer を使う controller(deployment と replicaset、job / cronjob / ttl-after-finished、node-lifecycle と taint-eviction、endpoints / endpointslice 系)は同じプロセスにまとめる。Pod の informer の重複はどう分けても残る
  • HA にするならグループごとに複数台が要る
  • マネージド Kubernetes(EKS / GKE など)ではコントロールプレーンのフラグは触れない。ここは自前クラスターや kind・kubeadm 向けの話

関連

出典

X の元の投稿を開く