ノード上へのPodのスケジューリング

Podを特定のノードで実行するように 制限 したり、特定のノードで実行することを 優先 させたりといった制約をかけることができます。 これを実現するためにはいくつかの方法がありますが、推奨されている方法は、すべてラベルセレクターを使用して選択を容易にすることです。 多くの場合、このような制約を設定する必要はなく、スケジューラーが自動的に妥当な配置を行います(例えば、Podを複数のノードに分散させ、空きリソースが十分でないノードにPodを配置しないようにすることができます)。 しかし、例えばSSDが接続されているノードにPodが配置されるようにしたり、多くの通信を行う2つの異なるサービスのPodを同じアベイラビリティーゾーンに配置したりする等、どのノードに配置するかを制御したい状況もあります。

Kubernetesが特定のPodの配置場所を選択するために、以下の方法があります:

ノードラベル

他の多くのKubernetesオブジェクトと同様に、ノードにもラベルがあります。手動でラベルを付けることができます。 また、Kubernetesはクラスター内のすべてのノードに対し、いくつかの標準ラベルを付けます。ノードラベルの一覧についてはよく使われるラベル、アノテーションとtaintを参照してください。

ノードの分離/制限

ノードにラベルを追加することで、Podを特定のノードまたはノードグループ上でのスケジューリングの対象にすることができます。この機能を使用すると、特定のPodが一定の独立性、安全性、または規制といった属性を持ったノード上でのみ実行されるようにすることができます。

ノード分離するのにラベルを使用する場合、kubeletが修正できないラベルキーを選択してください。 これにより、侵害されたノードが自身でそれらのラベルを設定することで、スケジューラーがそのノードにワークロードをスケジュールしてしまうのを防ぐことができます。

NodeRestrictionアドミッションプラグインは、kubeletがnode-restriction.kubernetes.io/というプレフィックスを持つラベルを設定または変更するのを防ぎます。

ラベルプレフィックスをNode分離に利用するには:

  1. ノード認可を使用していることと、NodeRestriction アドミッションプラグインが 有効 になっていることを確認します。
  2. node-restriction.kubernetes.io/プレフィックスを持つラベルをノードに追加し、 nodeSelectorでそれらのラベルを使用します。 例えば、example.com.node-restriction.kubernetes.io/fips=trueexample.com.node-restriction.kubernetes.io/pci-dss=trueなどです。

nodeSelector

nodeSelectorは、ノード選択制約の中で最もシンプルな推奨形式です。 Podのspec(仕様)にnodeSelectorフィールドを追加することで、ターゲットノードが持つべきノードラベルを指定できます。 Kubernetesは指定された各ラベルを持つノードにのみ、Podをスケジューリングします。

詳しい情報についてはPodをノードに割り当てるを参照してください。

アフィニティとアンチアフィニティ

nodeSelectorはPodを特定のラベルが付与されたノードに制限する最も簡単な方法です。 アフィニティとアンチアフィニティでは、定義できる制約の種類が拡張されています。 アフィニティとアンチアフィニティのメリットは以下の通りです。

  • アフィニティとアンチアフィニティで使われる言語は、より表現力が豊かです。nodeSelectorは指定されたラベルを全て持つノードを選択するだけです。アフィニティとアンチアフィニティは選択ロジックをより細かく制御することができます。
  • ルールが柔軟であったり優先での指定ができたりするため、一致するノードが見つからない場合でも、スケジューラーはPodをスケジュールします。
  • ノード自体のラベルではなく、ノード(または他のトポロジカルドメイン)上で稼働している他のPodのラベルを使ってPodを制約することができます。これにより、ノード上にどのPodを共存させるかのルールを定義することができます。

アフィニティ機能は、2種類のアフィニティで構成されています:

  • ノードアフィニティnodeSelectorフィールドと同様に機能しますが、より表現力が豊かで、より柔軟にルールを指定することができます。
  • Pod間アフィニティとアンチアフィニティは、他のPodのラベルを元に、Podを制約することができます。

ノードアフィニティ

ノードアフィニティは概念的には、ノードのラベルによってPodがどのノードにスケジュールされるかを制限するnodeSelectorと同様です。

ノードアフィニティには2種類あります:

  • requiredDuringSchedulingIgnoredDuringExecution: スケジューラーは、ルールが満たされない限り、Podをスケジュールすることができません。これはnodeSelectorと同じように機能しますが、より表現力豊かな構文になっています。
  • preferredDuringSchedulingIgnoredDuringExecution: スケジューラーは、対応するルールを満たすノードを探そうとします。 一致するノードが見つからなくても、スケジューラーはPodをスケジュールします。

Podのspec(仕様)にある.spec.affinity.nodeAffinityフィールドを使用して、ノードアフィニティを指定することができます。

例えば、次のようなPodのspec(仕様)を考えてみましょう:

apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - antarctica-east1
            - antarctica-west1
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: registry.k8s.io/pause:2.0

この例では、以下のルールが適用されます:

  • ノードにはtopology.kubernetes.io/zoneをキーとするラベルが必要で、そのラベルの値はantarctica-east1またはantarctica-west1のいずれかでなければなりません。
  • ノードにはキー名がanother-node-label-keyで、値がanother-node-label-valueのラベルを持つことが望ましいです。

operatorフィールドを使用して、Kubernetesがルールを解釈する際に使用できる論理演算子を指定することができます。InNotInExistsDoesNotExistGtLtが使用できます。

NotInDoesNotExistを使って、ノードのアンチアフィニティ動作を定義することができます。また、ノードのTaintを使用して、特定のノードからPodをはじくこともできます。

詳細についてはノードアフィニティを利用してPodをノードに割り当てるを参照してください。

ノードアフィニティの重み

preferredDuringSchedulingIgnoredDuringExecutionアフィニティタイプの各インスタンスに、1から100の範囲のweightを指定できます。 Podの他のスケジューリング要件をすべて満たすノードを見つけると、スケジューラーはそのノードが満たすすべての優先ルールを繰り返し実行し、対応する式のweight値を合計に加算します。

最終的な合計は、そのノードの他の優先度関数のスコアに加算されます。合計スコアが最も高いノードが、スケジューラーがPodのスケジューリングを決定する際に優先されます。

例えば、次のようなPodのspec(仕様)を考えてみましょう:

apiVersion: v1
kind: Pod
metadata:
  name: with-affinity-anti-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: label-1
            operator: In
            values:
            - key-1
      - weight: 50
        preference:
          matchExpressions:
          - key: label-2
            operator: In
            values:
            - key-2
  containers:
  - name: with-node-affinity
    image: registry.k8s.io/pause:2.0

preferredDuringSchedulingIgnoredDuringExecutionルールにマッチするノードとして、一つはlabel-1:key-1ラベル、もう一つはlabel-2:key-2ラベルの2つの候補がある場合、スケジューラーは各ノードのweightを考慮し、その重みとノードの他のスコアを加え、最終スコアが最も高いノードにPodをスケジューリングします。

スケジューリングプロファイルごとのノードアフィニティ

FEATURE STATE: Kubernetes v1.20 [beta]

複数のスケジューリングプロファイルを設定する場合、プロファイルにノードアフィニティを関連付けることができます。これは、プロファイルが特定のノード群にのみ適用される場合に便利です。スケジューラーの設定にあるNodeAffinityプラグインargsフィールドにaddedAffinityを追加すると実現できます。例えば:

apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration

profiles:
  - schedulerName: default-scheduler
  - schedulerName: foo-scheduler
    pluginConfig:
      - name: NodeAffinity
        args:
          addedAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
              - matchExpressions:
                - key: scheduler-profile
                  operator: In
                  values:
                  - foo

addedAffinityは、Podの仕様(spec)で指定されたノードアフィニティに加え、.spec.schedulerNamefoo-schedulerに設定したすべてのPodに適用されます。つまり、Podにマッチするためには、ノードはaddedAffinityとPodの.spec.NodeAffinityを満たす必要があるのです。

addedAffinityはエンドユーザーには見えないので、その動作はエンドユーザーにとって予期しないものになる可能性があります。スケジューラープロファイル名と明確な相関関係のあるノードラベルを使用すべきです。

Pod間のアフィニティとアンチアフィニティ

Pod間のアフィニティとアンチアフィニティは、ノードのラベルではなく、すでにノード上で稼働しているPodのラベルに従って、Podがどのノードにスケジュールされるかを制限できます。

Xはノードや、ラック、クラウドプロバイダーのゾーンやリージョン等を表すトポロジードメインで、YはKubernetesが満たそうとするルールである場合、Pod間のアフィニティとアンチアフィニティのルールは、"XにてルールYを満たすPodがすでに稼働している場合、このPodもXで実行すべき(アンチアフィニティの場合はすべきではない)"という形式です。

これらのルール(Y)は、オプションの関連する名前空間のリストを持つラベルセレクターで表現されます。PodはKubernetesの名前空間オブジェクトであるため、Podラベルも暗黙的に名前空間を持ちます。Kubernetesが指定された名前空間でラベルを探すため、Podラベルのラベルセレクターは、名前空間を指定する必要があります。

トポロジードメイン(X)はtopologyKeyで表現され、システムがドメインを示すために使用するノードラベルのキーになります。具体例はよく知られたラベル、アノテーションとTaintを参照してください。

Pod間のアフィニティとアンチアフィニティの種類

ノードアフィニティと同様に、Podアフィニティとアンチアフィニティにも下記の2種類があります:

  • requiredDuringSchedulingIgnoredDuringExecution
  • preferredDuringSchedulingIgnoredDuringExecution

例えば、requiredDuringSchedulingIgnoredDuringExecutionアフィニティを使用して、2つのサービスのPodはお互いのやり取りが多いため、同じクラウドプロバイダーゾーンに併置するようにスケジューラーに指示することができます。 同様に、preferredDuringSchedulingIgnoredDuringExecutionアンチアフィニティを使用して、あるサービスのPodを複数のクラウドプロバイダーゾーンに分散させることができます。

Pod間アフィニティを使用するには、Pod仕様(spec)のaffinity.podAffinityフィールドで指定します。Pod間アンチアフィニティを使用するには、Pod仕様(spec)のaffinity.podAntiAffinityフィールドで指定します。

Podアフィニティ使用例

次のようなPod仕様(spec)を考えてみましょう:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: registry.k8s.io/pause:2.0

この例では、PodアフィニティルールとPodアンチアフィニティルールを1つずつ定義しています。 Podアフィニティルールは"ハード"なrequiredDuringSchedulingIgnoredDuringExecutionを使用し、アンチアフィニティルールは"ソフト"なpreferredDuringSchedulingIgnoredDuringExecutionを使用しています。

アフィニティルールは、スケジューラーがノードにPodをスケジュールできるのは、そのノードが、security=S1ラベルを持つ1つ以上の既存のPodと同じゾーンにある場合のみであることを示しています。より正確には、現在Podラベルsecurity=S1を持つPodが1つ以上あるノードが、そのゾーン内に少なくとも1つ存在する限り、スケジューラーはtopology.kubernetes.io/zone=Vラベルを持つノードにPodを配置しなければなりません。

アンチアフィニティルールは、security=S2ラベルを持つ1つ以上のPodと同じゾーンにあるノードには、スケジューラーがPodをスケジュールしないようにすることを示しています。より正確には、PodラベルSecurity=S2を持つPodが稼働している他のノードが、同じゾーン内に存在する場合、スケジューラーはtopology.kubernetes.io/zone=Rラベルを持つノードにはPodを配置しないようにしなければなりません。

Podアフィニティとアンチアフィニティの使用例についてもっと知りたい方はデザイン案を参照してください。

Podアフィニティとアンチアフィニティのoperatorフィールドで使用できるのは、InNotInExistsDoesNotExistです。

原則として、topologyKeyには任意のラベルキーが指定できますが、パフォーマンスやセキュリティの観点から、以下の例外があります:

  • Podアフィニティとアンチアフィニティでは、requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution内のどちらも、topologyKeyフィールドが空であることは許可されていません。
  • PodアンチアフィニティルールのrequiredDuringSchedulingIgnoredDuringExecutionでは、アドミッションコントローラーLimitPodHardAntiAffinityTopologytopologyKeykubernetes.io/hostnameに制限しています。アドミッションコントローラーを修正または無効化すると、トポロジーのカスタマイズができるようになります。

labelSelectortopologyKeyに加え、labelSelectortopologyKeyと同じレベルのnamespacesフィールドを使用して、labelSelectorが合致すべき名前空間のリストを任意に指定することができます。省略または空の場合、namespacesがデフォルトで、アフィニティとアンチアフィニティが定義されたPodの名前空間に設定されます。

名前空間セレクター

FEATURE STATE: Kubernetes v1.24 [stable]

namespaceSelectorを使用し、ラベルで名前空間の集合に対して検索することによって、名前空間を選択することができます。 アフィニティ項はnamespaceSelectornamespacesフィールドによって選択された名前空間に適用されます。 要注意なのは、空のnamespaceSelector({})はすべての名前空間にマッチし、nullまたは空のnamespacesリストとnullのnamespaceSelectorは、ルールが定義されているPodの名前空間にマッチします。

実践的なユースケース

Pod間アフィニティとアンチアフィニティは、ReplicaSet、StatefulSet、Deploymentなどのより高レベルなコレクションと併せて使用するとさらに有用です。これらのルールにより、ワークロードのセットが同じ定義されたトポロジーに併置されるように設定できます。たとえば、2つの関連するPodを同じノードに配置することが好ましい場合です。

例えば、3つのノードで構成されるクラスターを想像してください。そのクラスターを使用してウェブアプリケーションを実行し、さらにインメモリーキャッシュ(Redisなど)を使用します。この例では、ウェブアプリケーションとメモリーキャッシュの間のレイテンシーは実用的な範囲の低さも想定しています。Pod間アフィニティやアンチアフィニティを使って、ウェブサーバーとキャッシュをなるべく同じ場所に配置することができます。

以下のRedisキャッシュのDeploymentの例では、各レプリカはラベルapp=storeが付与されています。podAntiAffinityルールは、app=storeラベルを持つ複数のレプリカを単一ノードに配置しないよう、スケジューラーに指示します。これにより、各キャッシュが別々のノードに作成されます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine

次のウェブサーバーのDeployment例では、app=web-storeラベルが付与されたレプリカを作成します。Podアフィニティルールは、各レプリカを、app=storeラベルが付与されたPodを持つノードに配置するようスケジューラーに指示します。Podアンチアフィニティルールは、1つのノードに複数のapp=web-storeサーバーを配置しないようにスケジューラーに指示します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

上記2つのDeploymentが生成されると、以下のようなクラスター構成になり、各ウェブサーバーはキャッシュと同位置に、3つの別々のノードに配置されます。

node-1node-2node-3
webserver-1webserver-2webserver-3
cache-1cache-2cache-3

全体的な効果として、各キャッシュインスタンスは、同じノード上で実行している単一のクライアントによってアクセスされる可能性が高いです。この方法は、スキュー(負荷の偏り)とレイテンシーの両方を最小化することを目的としています。

Podアンチアフィニティを使用する理由は他にもあります。 この例と同様の方法で、アンチアフィニティを用いて高可用性を実現したStatefulSetの使用例はZooKeeperチュートリアルを参照してください。

nodeName

nodeNameはアフィニティやnodeSelectorよりも直接的なノード選択形式になります。nodeNameはPod仕様(spec)内のフィールドです。nodeNameフィールドが空でない場合、スケジューラーはPodを考慮せずに、指定されたノードにあるkubeletがそのノードにPodを配置しようとします。nodeNameを使用すると、nodeSelectorやアフィニティおよびアンチアフィニティルールを使用するよりも優先されます。

nodeNameを使ってノードを選択する場合の制約は以下の通りです:

  • 指定されたノードが存在しない場合、Podは実行されず、場合によっては自動的に削除されることがあります。
  • 指定されたノードがPodを収容するためのリソースを持っていない場合、Podの起動は失敗し、OutOfmemoryやOutOfcpuなどの理由が表示されます。
  • クラウド環境におけるノード名は、常に予測可能で安定したものではありません。

以下は、nodeNameフィールドを使用したPod仕様(spec)の例になります:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-01

上記のPodはkube-01というNodeでのみ実行されます。

Podトポロジー分散制約

トポロジー分散制約 を使って、リージョン、ゾーン、ノードなどの障害ドメイン間、または定義したその他のトポロジードメイン間で、クラスター全体にどのようにPodを分散させるかを制御することができます。これにより、パフォーマンス、予想される可用性、または全体的な使用率を向上させることができます。

詳しい仕組みについては、トポロジー分散制約を参照してください。

次の項目

最終更新 October 15, 2024 at 3:18 AM PST: Merge pull request #48346 from windsonsea/metricy (50a9341)