Kubernetes基礎 #6 - スケジューリング & オートスケーリング

Last Edited: 5/20/2025

このブログ記事では、Kubernetesにおけるスケジューリングとオートスケーリングについて紹介します。

DevOps

前回の記事では、データベースやファイルストレージシステムのデータ永続化を実現する方法について説明し、 これらのシステムを水平方向にスケーリングすることの難しさについて言及しました。代わりに垂直スケーリングを選択する場合、 ポッドが常にリソースをアップグレードしている特定のノードに割り当てられるようにする必要がありますが、これについてはまだ説明していません。 また、ポッドを特定のノードにスケジュールしたい他の状況もあります。そこで、この記事では、それを可能にするいくつかの機能と、 ワークロードの変化に応じて手動でレプリカ数を変更する必要がないようにするオートスケーリングについて説明します。

ノードセレクタ

単一のポッドを特定のノードに割り当てる最も簡単な方法は、ポッドの仕様にnodeNameフィールドでノードの名前(kubectl get nodesで確認可能)を指定することです。 これは、クラスター内のデータベースやファイルストレージシステムの垂直スケーリングなど、ポッドが1つのノードにのみスケジュールされる状況には十分です。 しかし、特定の要件を満たすノードが複数ある場合(例えば、機械学習ワークロードを実行するポッドをGPUを搭載した任意のノードに割り当てたい場合)、 それらのノードにポッドをスケジュールできるようにしたいことがあります。

デプロイメントやサービスがラベルとセレクタを使用して担当するポッドを識別できるように、ポッドもnodeSelectorをポッドの仕様に使用して割り当て可能なノードを識別でき、 一致するラベルを持つ任意のノードにスケジュールできます。kubectl label <node-name> <label-name>=<label-value>を使用してノードにラベルを割り当てることができます。 このメカニズムは直感的で使いやすいですが、複雑なスケジューリングロジックを強制することはできません。

テイントと許容

ノードセレクタがポッドを割り当てるノードを指定するのに対し、テイント(汚染の様な意味)と許容(汚染に対する耐性と捉えられる)はポッドを割り当てないノードを制御します。 具体的には、テイントを設定することで、ノードのテイントに対する許容を持たない新しいポッドがスケジュールされない(NoSchedule)、 スケジュールまたは実行されない(NoExecute)、または割り当て可能な他のノードがない場合を除いてスケジュールされない(PreferNoSchedule)ようにすることができます。 例えば、kubectl taint node cluster-worker1 gpu=true:NoExecuteのようにGPU機能を持つノードにテイントを設定すると、 テイントgpu=trueに対する許容を持たないポッドがそのノードにスケジュールまたは実行されるのを防ぎます。

example-pod.yaml
# ...
spec:
  # ...
  tolerations:
    - key: "gpu"
      operator: "Equal"
      value: "true"
      effect: "NoExecute"

特定のテイントに対する許容を割り当てるには、上記のように行うことができます。テイントと許容は、特定のポッドが特定のノードにスケジュールまたは実行されないことを保証できますが、 テイントに対する許容を持つポッドがテイントを持つノードにのみスケジュールされることを保証するものではありません。 したがって、スケジューリングが特定の望ましい方法で動作することを確実にするために、ノードセレクタと一緒に使用されることが多いです。

ノードアフィニティ

ノードセレクタはシンプルさゆえに強力ですが、複雑なロジックを指定することができません。例えば、2つの値のいずれかに一致するラベルと、 特定の値に一致する別のラベルを持つノードにポッドを割り当てるといったことができません。例えば、ノードセレクタでは、 SSDまたはHDDとGPUが利用可能なノードにポッドをスケジュールするという表現ができません。 ノードアフィニティを使えばまさにそれが可能になり、要件ではなく優先度を設定することもできます。

example-pod.yaml
# ...
spec:
  # ...
  affinity:
    nodeAffinity:
      requireDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchingExpressions:
            - key: "disk"
              operator: "In"
              values: 
                - "ssd"
                - "hhd"
            - key: "gpu"
              operator: "Equal"
              value: "true"

上記の例は、ポッドを割り当てるノードアフィニティを示しています。このノードアフィニティは、ノードラベルdiskssdまたはhhdのいずれかに一致し、 gputrueに一致することを要求しています。また、preferredDuring...を使用して、ポッドが一定の程度マッチするラベルを持つノードを優先しながら、 そのようなノードが利用できない場合でも他のノードに割り当てられるように指定することもできます。詳細については、Kubernetes公式ドキュメント(下記引用)を参照してください。 ノードアフィニティはノードセレクタの優れた代替手段ではありますが、一部のユースケースでは不必要に複雑で直感的でない場合があるため、賢く選択することが重要です。

リクエストとリミット

これまでの議論で見てきたように、ポッドのリソース要件のためにノードを指定することが多いです。 メモリとCPUについては、Kubernetesにはリクエストとリミットと呼ばれる機能があり、ポッドの最小および最大リソースを設定できます。 これにより、ポッドがノードのリソース全体を消費してノードのメモリ不足(OOM)になる状況(ノードが利用できなくなる)や、 リソース要件の高いポッドがそれらの要件を満たさないノードにスケジュールされる状況を防ぎます。

example-pod.yaml
# ...
spec:
  containers:
    - name: container
      image: my-image
      resources:
        requests:
          memory: "100Mi" # 100MiB
          cpu: "250m" # 0.25 CPU (25% of a CPU core power)
        limits:
          memory: "200Mi" # 200MiB
          cpu: "500m" # 0.5 CPU (50% of a CPU core power)

リソース要件のあるプロセスはコンテナ上で実行され、コンテナに依存するため、上記のようにコンテナごとにリクエストとリミットを設定します。 上記のリクエストでは、少なくとも100MiBのメモリと0.25 CPUを指定し、リソースリクエストを満たさないノードにポッドがスケジュールされるのを防ぎ、 200MiBと0.5 CPUに消費を制限することで、ポッド自体をOOMでクラッシュさせることによってノード全体がクラッシュするのを防ぎます。

水平ポッドオートスケーリング

これまで、デプロイメント内のポッド数を手動で増やすことでクラスターをスケーリングしてきました。 しかし、コスト効率と可用性のために適切なレプリカ数を設定するために、クラスターを常に監視することは現実的ではありません。 そのため、Kubernetesでは、CPU使用率に応じて定義されたしきい値内でデプロイメント内のポッド数を調整することで、 クラスターをオートスケーリングすることができます。この方法は水平ポッドオートスケーリング(HPA)と呼ばれ、 kubectl autoscale deployment <デプロイメント名> --cpu-percent=50 --min=1 --max=10のようなコマンドで実現できます。 これにより、最大50%のCPU使用率を達成するために1から10までのポッド数を自動的に調整するHPAが作成されます。

kubectl get hpaコマンドを使用して、現在のCPU使用率やレプリカ数(目標値に対して)などのHPAに関する情報を取得できます。 水平ノードオートスケーリング(自動的にノードを追加する)もクラスターを水平方向にオートスケーリングするもう一つの選択肢ですが、 実装はクラウドプロバイダーによって異なります。限られたユースケースでは、垂直ポッドオートスケーリングや垂直ノードオートスケーリングも存在します。 ただし、これらは主にKubernetesクラスターに適していないため、この記事の範囲外です。

結論

この記事では、ポッドのリソース要件を満たし、ノードのクラッシュを防ぎ、クラスターを簡単に維持するためのスケジューリングとオートスケーリングの設定方法の基本について説明しました。 これまでクラスターのセットアップに関する多くの基本的な概念をカバーしてきましたが、特に本番環境用のクラスターをセットアップするための重要な概念がまだ多く残っています。 したがって、次の記事ではそれらの概念のいくつかについて引き続き議論します。

リソース