kube-controller-manager の詰まりを切り分けて調整する(49 個の controller、/flagz、--concurrent-*-syncs)
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)
/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_seconds | API サーバーの遅さか処理自体の重さ |
| 全体が遅い | rest_client_rate_limiter_duration_seconds | クライアント側のレート制限(--kube-api-qps 20 / --kube-api-burst 30) |
| API 応答が遅い | rest_client_request_duration_seconds | kube-apiserver / etcd 側 |
キュー待ちの p99(controller 別):
promql
histogram_quantile(0.99,
sum by (name, le) (rate(workqueue_queue_duration_seconds_bucket[5m])))調整の順番
- キューが溜まっている controller の
--concurrent-*-syncsを上げる(例: deployment / job は既定 5、daemonset は 2、disruption は 1、namespace は 10、gc は 20)。API リクエストも増える - レート制限で待っているなら
--kube-api-qps/--kube-api-burstを上げる。これはクライアントごとの値で、プロセス全体の合計上限ではない。kube-apiserver 側の負荷も一緒に見る - 個別の調整: EndpointSlice は
--endpointslice-updates-batch-periodで更新をまとめる、終了済み Pod が多いなら--terminated-pod-gc-threshold(既定 12500)を下げる、ResourceQuota は--resource-quota-sync-period(既定 5 分) - 重い 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 向けの話
関連
- Kubernetes: Pod が Running / Ready にならないときの切り分け手順
- オートスケールの「振動」を防ぐ: AWS のポリシー選び(Target Tracking / Step / Simple / Scheduled)と Kubernetes HPA の tolerance
出典
- @yosshi_ · 2026-10-27
- Qiita「kube-controller-manager の controller 一覧」: qiita.com