[Kubernetes] 3つのPodを起動したのに、なぜ1つのノードに集中するのか? (スケジューラーの秘密) 🧐

Kubernetesを運用していると、「あれ?なんでここに表示されるんだろう?」と思う瞬間が訪れます。最も代表的な誤解が、「Podはノードに均等に(ラウンドロビンで)分散されるだろう」という思い込みです。

今日は、私が実際に経験した「Podの偏り現象」を通して、Kubernetesスケジューラーが実際にどのようにスコアを付け、ノードを選択するのか、その見えないロジックを徹底的に解明していきます。🚀

image

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スケジューラーは徹底的に「スコア」ベースで動きます。

  1. Filtering: 条件に合わないノードは脱落
  2. Scoring: 残ったノードにスコアを付ける(0~100点)
  3. 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. 結論 📝

  1. KubernetesスケジューラーはABC順でデプロイしない。
  2. requestsを設定しないと、スケジューラーはPodのサイズを「0」と見なす。
  3. この場合、イメージレイヤーが少しでも残っているノードが加点され、Podが1箇所に集中する可能性がある。
  4. 安定した運用のためには、Resource Requestsの設定は選択ではなく必須です!

今日の試行錯誤はここまで!鼻の高いスケジューラーの心を理解する一助となれば幸いです。👋


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です