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

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

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

image

0. 問題状況: 「明らかにノードは複数あるのに…」

テストのためにMySQL、Nginx、そしてhttpdのPodを順番に作成しました。私たちの期待は当然、「1番ノード、2番ノード、3番ノード…と仲良く分けられるだろう?」でした。

しかし現実は異なりました。

$ kubectl get pod -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE                               NOMINATED NODE
mysql   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順序を知らない ❌

多くの方がスケジューラーが「ラウンドロビン(Round-Robin)」方式を使うから、ノード名順序(A->B->C)でデプロイするだろうと考えます。しかしKubernetesスケジューラーは徹底的に「スコア(Score)」ベースで動きます。

  1. Filtering: 条件に合わないノードを脱落させる
  2. Scoring: 残ったノードにスコアを付ける (0~100点)
  3. Ranking: 1位のノードを選択する

つまり、rr03ノードが継続して選択された理由は順序のためではなく、スケジューラーの目にはrr03が常に「一番の候補」に見えたからです。


2. 犯人分析: なぜ満杯のノードが1位になったのか? 🤔

空のノードがあるにもかかわらず、すでにPodが3つもあるノードが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イメージはレイヤー(Layer)構造です。
  • 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)設定

「私と同じようなPodがいる場所は嫌だ!」と強制的に設定する方法です。

podAntiAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector: ...

このオプションを使うと、スコア計算に関係なく、必ず重複しないように配置されます。


4. 結論 📝

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

今日の試行錯誤はここまで!高慢なスケジューラーの心を理解するのに役立ったことを願っています。👋


Comments

コメントを残す

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