Kubernetesを運用していると、「あれ?なんでここに表示されるんだろう?」と思う瞬間が訪れます。最も代表的な誤解が、「Podはノードに均等に(ラウンドロビンで)分散されるだろう」という思い込みです。
今日は、私が実際に経験した「Podの偏り現象」を通して、Kubernetesスケジューラーが実際にどのようにスコアを付け、ノードを選択するのか、その見えないロジックを徹底的に解明していきます。🚀

0. 問題状況:「ノードは複数あるはずなのに…」
テストのために、MySQL、Nginx、そしてhttpdのPodを順番に作成しました。私たちの期待は当然、「1番ノード、2番ノード、3番ノード…と仲良く分かれるだろう?」でした。
しかし、現実は異なりました。
$ kubectl get pod -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODEmysql 1/1 Running 0 66m 10.16.2.12 gke-cluster-1-default-pool-rr03 nx 1/1 Running 0 16m 10.16.2.13 gke-cluster-1-default-pool-rr03 nx1 1/1 Running 0 6s 10.16.2.14 gke-cluster-1-default-pool-rr03 httpd 1/1 Running 0 4s 10.16.2.15 gke-cluster-1-default-pool-rr03
ご覧の通り、mysql、nx、さらには異なるイメージであるhttpdまで、すべてrr03という一つのノードにばかり作成され続けていました。他のノードは空っぽなのに!😱
なぜでしょうか?スケジューラーが故障したのでしょうか?
1. 誤解を解く:スケジューラーはABC順を知らない ❌
多くの方が、スケジューラーが「ラウンドロビン」方式を使うから、ノード名の順序(A→B→C)でデプロイされるだろうと考えています。しかし、Kubernetesスケジューラーは徹底的に「スコア」ベースで動きます。
- Filtering: 条件に合わないノードは脱落
- Scoring: 残ったノードにスコアを付ける(0~100点)
- Ranking: 1位のノードを選択
つまり、rr03ノードが常に選択された理由は、順序のためではなく、スケジューラーの目にはrr03が常に「一番の候補」に見えていたからです。
2. 犯人分析:なぜ満杯のノードが1位になったのか? 🤔
空のノードを差し置いて、すでに3つのPodがあるノードが1位になった理由は、2つの設定の組み合わせです。
① リソース要求(Requests)未設定 = 「透明人間」扱い
私はPodを作成する際に`resources.requests`(CPU/メモリ要求量)を設定しませんでした。
- スケジューラーの視点:「このPodはCPU 0、メモリ 0を使うのか?」
- 判断:rr03ノードにPodが100個あろうと1000個あろうと、Requestが設定されていないPodばかりであれば、スケジューラーから見るとそのノードの使用率は依然として「0%」です。
- 結果:「空のノードもrr03も、どうせどちらも余裕があるのは同じだね?(リソーススコア同点)」
② 決定打:イメージの局所性(Image Locality)&レイヤー共有
ここで疑問が生じます。「スコアが同点ならランダムに行くはずなのに、なぜわざわざrr03に行ったのだろう?」
犯人はImageLocalityの加点です。
- Nginxデプロイ時:mysqlのためにイメージがダウンロードされたノードに行けば速いからrr03を選択(理解可能)
- Httpdデプロイ時:「あれ?これは新しいイメージなのに?」
- しかし、Dockerイメージはレイヤー構造です。
- mysql、nginx、httpdはそれぞれ異なりますが、基盤となるBase Image(Debian、Alpineなど)や共通ライブラリレイヤーを共有する可能性が非常に高いです。
- 最終判定:
- 空のノード:「ベースレイヤーからすべて新しくダウンロードする必要がある」→加点なし
- rr03ノード:「あれ?隣のPodが使っていたベースレイヤーがもうあるじゃないか?ラッキー!」→加点付与(+)
結局、リソーススコアは同点(0点の影響)でしたが、イメージキャッシュのスコアでrr03がわずかにリードし、Podを吸い込み続けるブラックホールになったのです。🕳️
3. 解決方法:Podを分散させるには? 🛠️
この現象を防ぎ、クラスター全体にPodを均等に分散させるにはどうすればよいでしょうか?
✅ 方法1:リソース要求(Requests)を明示する(推奨)
Podに「私はこれくらいの大きさです!」と名札を付けてあげてください。
resources: requests: cpu: "200m" # 0.2コアを要求
こうすることで、スケジューラーは「あっ、rr03はもう満杯だ。空いているノードに送ろう!」と正常に判断し、負荷分散(Least Allocated)ロジックが機能します。
✅ 方法2:アンチアフィニティ(Anti-Affinity)設定
「私と同じものがいる場所は嫌だ!」と強制的に設定する方法です。
podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: ...
このオプションを使うと、スコア計算とは関係なく、必ず重複しないように配置されます。
4. 結論 📝
- KubernetesスケジューラーはABC順でデプロイしない。
- requestsを設定しないと、スケジューラーはPodのサイズを「0」と見なす。
- この場合、イメージレイヤーが少しでも残っているノードが加点され、Podが1箇所に集中する可能性がある。
- 安定した運用のためには、Resource Requestsの設定は選択ではなく必須です!
今日の試行錯誤はここまで!鼻の高いスケジューラーの心を理解する一助となれば幸いです。👋
コメントを残す