Edit This Page

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

Podが稼働するNodeを特定のものに指定したり、優先条件を指定して制限することができます。 これを実現するためにはいくつかの方法がありますが、推奨されている方法はラベルでの選択です。 スケジューラーが最適な配置を選択するため、一般的にはこのような制限は不要です(例えば、複数のPodを別々のNodeへデプロイしたり、Podを配置する際にリソースが不十分なNodeにはデプロイされないことが挙げられます)が、 SSDが搭載されているNodeにPodをデプロイしたり、同じアベイラビリティーゾーン内で通信する異なるサービスのPodを同じNodeにデプロイする等、柔軟な制御が必要なこともあります。

nodeSelector

nodeSelectorは、Nodeを選択するための、最も簡単で推奨されている手法です。 nodeSelectorはPodSpecのフィールドです。これはkey-valueペアのマップを特定します。 あるノードでPodを稼働させるためには、そのノードがラベルとして指定されたkey-valueペアを保持している必要があります(複数のラベルを保持することも可能です)。 最も一般的な使用方法は、1つのkey-valueペアを付与する方法です。

以下に、nodeSelectorの使用例を紹介します。

ステップ0: 前提条件

この例では、KubernetesのPodに関して基本的な知識を有していることと、Kubernetesクラスターのセットアップがされていることが前提となっています。

ステップ1: Nodeへのラベルの付与

kubectl get nodesで、クラスターのノードの名前を取得してください。 そして、ラベルを付与するNodeを選び、kubectl label nodes <node-name> <label-key>=<label-value>で選択したNodeにラベルを付与します。 例えば、Nodeの名前が'kubernetes-foo-node-1.c.a-robinson.internal'、付与するラベルが'disktype=ssd'の場合、kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssdによってラベルが付与されます。

kubectl get nodes --show-labelsによって、ノードにラベルが付与されたかを確認することができます。 また、kubectl describe node "nodename"から、そのNodeの全てのラベルを表示することもできます。

ステップ2: PodへのnodeSelectorフィールドの追加

該当のPodのconfigファイルに、nodeSelectorのセクションを追加します: 例として以下のconfigファイルを扱います:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx

nodeSelectorを以下のように追加します:

pods/pod-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

kubectl apply -f https://k8s.io/examples/pods/pod-nginx.yamlにより、Podは先ほどラベルを付与したNodeへスケジュールされます。 kubectl get pods -o wideで表示される"NODE"の列から、PodがデプロイされているNodeを確認することができます。

補足: ビルトインNodeラベル

明示的に付与するラベルの他に、事前にNodeへ付与されているものもあります。 以下のようなラベルが該当します。

  • kubernetes.io/hostname
  • failure-domain.beta.kubernetes.io/zone
  • failure-domain.beta.kubernetes.io/region
  • beta.kubernetes.io/instance-type
  • kubernetes.io/os
  • kubernetes.io/arch
備考: これらのラベルは、クラウドプロバイダー固有であり、確実なものではありません。 例えば、kubernetes.io/hostnameの値はNodeの名前と同じである環境もあれば、異なる環境もあります。

Nodeの隔離や制限

Nodeにラベルを付与することで、Podは特定のNodeやNodeグループにスケジュールされます。 これにより、特定のPodを、確かな隔離性や安全性、特性を持ったNodeで稼働させることができます。 この目的でラベルを使用する際に、Node上のkubeletプロセスに上書きされないラベルキーを選択することが強く推奨されています。 これは、安全性が損なわれたNodeがkubeletの認証情報をNodeのオブジェクトに設定したり、スケジューラーがそのようなNodeにデプロイすることを防ぎます。

NodeRestrictionプラグインは、kubeletがnode-restriction.kubernetes.io/プレフィックスを有するラベルの設定や上書きを防ぎます。 Nodeの隔離にラベルのプレフィックスを使用するためには、以下の3点を確認してください。

  1. NodeRestrictionを使用するため、Kubernetesのバージョンがv1.11以上であること。
  2. Node authorizerを使用していることと、NodeRestriction admission pluginが有効になっていること。
  3. Nodeにnode-restriction.kubernetes.io/ プレフィックスのラベルを付与し、そのラベルがnode selectorに指定されていること。 例えば、example.com.node-restriction.kubernetes.io/fips=true または example.com.node-restriction.kubernetes.io/pci-dss=trueのようなラベルです。

Affinity と Anti-Affinity

nodeSelectorはPodの稼働を特定のラベルが付与されたNodeに制限する最も簡単な方法です。 Affinity/Anti-Affinityでは、より柔軟な指定方法が提供されています。 拡張機能は以下の通りです。

  1. 様々な指定方法がある ("AND条件"に限らない)
  2. 必須条件ではなく優先条件を指定でき、条件を満たさない場合でもPodをスケジュールさせることができる
  3. Node自体のラベルではなく、Node(または他のトポロジカルドメイン)上で稼働している他のPodのラベルに対して条件を指定することができ、そのPodと同じ、または異なるドメインで稼働させることができる

Affinityは"Node Affinity"と"Inter-Pod Affinity/Anti-Affinity"の2種類から成ります。 Node affinityはnodeSelector(前述の2つのメリットがあります)に似ていますが、Inter-Pod Affinity/Anti-Affinityは、上記の3番目の機能に記載している通り、NodeのラベルではなくPodのラベルに対して制限をかけます。

nodeSelectorは問題なく使用することができますが、Node affinityはnodeSelectorで指定できる条件を全て実現できるため、将来的には推奨されなくなります。

Node Affinity

Node Affinityはα機能としてKubernetesのv1.2から導入されました。 Node Affinityは概念的には、NodeのラベルによってPodがどのNodeにスケジュールされるかを制限するnodeSelectorと同様です。

現在は2種類のNode Affinityがあり、requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutionです。 前者はNodeにスケジュールされるPodが条件を満たすことが必須(nodeSelectorに似ていますが、より柔軟に条件を指定できます)であり、後者は条件を指定できますが保証されるわけではなく、優先的に考慮されます。 "IgnoredDuringExecution"の意味するところは、nodeSelectorの機能と同様であり、Nodeのラベルが変更され、Podがその条件を満たさなくなった場合でも PodはそのNodeで稼働し続けるということです。 将来的には、requiredDuringSchedulingIgnoredDuringExecutionに、PodのNode Affinityに記された必須要件を満たさなくなったNodeからそのPodを退避させることができる機能を備えたrequiredDuringSchedulingRequiredDuringExecutionが提供される予定です。

それぞれの使用例として、 requiredDuringSchedulingIgnoredDuringExecution は、"インテルCPUを供えたNode上でPodを稼働させる"、 preferredDuringSchedulingIgnoredDuringExecutionは、"ゾーンXYZでPodの稼働を試みますが、実現不可能な場合には他の場所で稼働させる" といった方法が挙げられます。

Node Affinityは、PodSpecのaffinityフィールドにあるnodeAffinityフィールドで特定します。

Node Affinityを使用したPodの例を以下に示します:

pods/pod-with-node-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0

このNode Affinityでは、Podはキーがkubernetes.io/e2e-az-name、値がe2e-az1またはe2e-az2のラベルが付与されたNodeにしか配置されません。 加えて、キーがanother-node-label-key、値がanother-node-label-valueのラベルが付与されたNodeが優先されます。

この例ではオペレーターInが使われています。 Node Affinityでは、InNotInExistsDoesNotExistGtLtのオペレーターが使用できます。 NotInDoesNotExistはNode Anti-Affinity、またはPodを特定のNodeにスケジュールさせない場合に使われるTaintsに使用します。

nodeSelectornodeAffinityの両方を指定した場合、Podは両方の条件を満たすNodeにスケジュールされます。

nodeAffinity内で複数のnodeSelectorTermsを指定した場合、PodはいずれかのnodeSelectorTermsを満たしたNodeへスケジュールされます。

nodeSelectorTerms内で複数のmatchExpressionsを指定した場合にはPodは全てのmatchExpressionsを満たしたNodeへスケジュールされます。

PodがスケジュールされたNodeのラベルを削除したり変更しても、Podは削除されません。 言い換えると、AffinityはPodをスケジュールする際にのみ考慮されます。

preferredDuringSchedulingIgnoredDuringExecution内のweightフィールドは、1から100の範囲で指定します。 全ての必要条件(リソースやRequiredDuringScheduling Affinity等)を満たしたNodeに対して、スケジューラーはそのNodeがMatchExpressionsを満たした場合に、このフィルードの"weight"を加算して合計を計算します。 このスコアがNodeの他の優先機能のスコアと組み合わせれ、最も高いスコアを有したNodeが優先されます。

Inter-Pod Affinity/Anti-Affinity

Inter-Pod AffinityとAnti-Affinityは、Nodeのラベルではなく、すでにNodeで稼働しているPodのラベルに従ってPodがスケジュールされるNodeを制限します。 このポリシーは、"XにてルールYを満たすPodがすでに稼働している場合、このPodもXで稼働させる(Anti-Affinityの場合は稼働させない)"という形式です。 Yはnamespaceのリストで指定したLabelSelectorで表されます。 Nodeと異なり、Podはnamespaceで区切られているため(それゆえPodのラベルも暗黙的にnamespaceで区切られます)、Podのラベルを指定するlabel selectorは、どのnamespaceにselectorを適用するかを指定する必要があります。 概念的に、XはNodeや、ラック、クラウドプロバイダゾーン、クラウドプロバイダのリージョン等を表すトポロジードメインです。 これらを表すためにシステムが使用するNode LabelのキーであるtopologyKeyを使うことで、トポロジードメインを指定することができます。 先述のセクション補足: ビルトインNodeラベルにてラベルの例が紹介されています。

備考: Inter-Pod AffinityとAnti-Affinityは、大規模なクラスター上で使用する際にスケジューリングを非常に遅くする恐れのある多くの処理を要します。 そのため、数百台以上のNodeから成るクラスターでは使用することを推奨されません。
備考: Pod Anti-Affinityは、Nodeに必ずラベルが付与されている必要があります。 例えば、クラスターの全てのNodeが、topologyKeyで指定されたものに合致する適切なラベルが必要になります。 それらが付与されていないNodeが存在する場合、意図しない挙動を示すことがあります。

Node Affinityと同様に、Pod AffinityとPod Anti-Affinityにも必須条件と優先条件を示すrequiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutionがあります。 前述のNode Affinityのセクションを参照してください。 requiredDuringSchedulingIgnoredDuringExecutionを指定するAffinityの使用例は、"Service AのPodとService BのPodが密に通信する際、それらを同じゾーンで稼働させる場合"です。 また、preferredDuringSchedulingIgnoredDuringExecutionを指定するAnti-Affinityの使用例は、"ゾーンをまたいでPodのサービスを稼働させる場合"(Podの数はゾーンの数よりも多いため、必須条件を指定すると合理的ではありません)です。

Inter-Pod Affinityは、PodSpecのaffinityフィールド内にpodAffinityで指定し、Inter-Pod Anti-Affinityは、podAntiAffinityで指定します。

Pod Affinityを使用したPodの例

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

このPodのAffifnityは、Pod AffinityとPod Anti-Affinityを1つずつ定義しています。 この例では、podAffinityrequiredDuringSchedulingIgnoredDuringExecutionpodAntiAffinitypreferredDuringSchedulingIgnoredDuringExecutionが設定されています。 Pod Affinityは、「キーが"security"、値が"S1"のラベルが付与されたPodが少なくとも1つは稼働しているNodeが同じゾーンにあれば、PodはそのNodeにスケジュールされる」という条件を指定しています(より正確には、キーが"security"、値が"S1"のラベルが付与されたPodが稼働しており、キーがfailure-domain.beta.kubernetes.io/zone、値がVであるNodeが少なくとも1つはある状態で、 Node Nがキーfailure-domain.beta.kubernetes.io/zone、値Vのラベルを持つ場合に、PodはNode Nで稼働させることができます)。 Pod Anti-Affinityは、「すでにあるNode上で、キーが"security"、値が"S2"であるPodが稼働している場合に、Podを可能な限りそのNode上で稼働させない」という条件を指定しています (topologyKeyfailure-domain.beta.kubernetes.io/zoneであった場合、キーが"security"、値が"S2"であるであるPodが稼働しているゾーンと同じゾーン内のNodeにはスケジュールされなくなります)。 Pod AffinityとPod Anti-Affinityや、requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecutionに関する他の使用例はデザインドックを参照してください。

Pod AffinityとPod Anti-Affinityで使用できるオペレーターは、InNotInExistsDoesNotExistです。

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

  1. Affinityと、requiredDuringSchedulingIgnoredDuringExecutionを指定したPod Anti-Affinityでは、topologyKeyを指定しないことは許可されていません。
  2. requiredDuringSchedulingIgnoredDuringExecutionを指定したPod Anti-Affinityでは、kubernetes.io/hostnametopologyKeyを制限するため、アドミッションコントローラーLimitPodHardAntiAffinityTopologyが導入されました。 トポロジーをカスタマイズする場合には、アドミッションコントローラーを修正または無効化する必要があります。
  3. preferredDuringSchedulingIgnoredDuringExecutionを指定したPod Anti-Affinityでは、topologyKeyを指定しなかった場合、"全てのトポロジー"と解釈されます("全てのトポロジー"とは、ここではkubernetes.io/hostnamefailure-domain.beta.kubernetes.io/zonefailure-domain.beta.kubernetes.io/regionを合わせたものを意味します)。
  4. 上記の場合を除き、topologyKey は任意のラベルとキーを指定することができあます。

labelSelectortopologyKeyに加え、labelSelectorが合致すべきnamespacesのリストを特定することも可能です(これはlabelSelectortopologyKeyを定義することと同等です)。 省略した場合や空の場合は、AffinityとAnti-Affinityが定義されたPodのnamespaceがデフォルトで設定されます。

requiredDuringSchedulingIgnoredDuringExecutionが指定されたAffinityとAnti-Affinityでは、matchExpressionsに記載された全ての条件が満たされるNodeにPodがスケジュールされます。

実際的なユースケース

Inter-Pod AffinityとAnti-Affinityは、ReplicaSet、StatefulSet、Deploymentなどのより高レベルなコレクションと併せて使用すると更に有用です。 Workloadが、Node等の定義された同じトポロジーに共存させるよう、簡単に設定できます。

常に同じNodeで稼働させる場合

3つのノードから成るクラスターでは、ウェブアプリケーションはredisのようにインメモリキャッシュを保持しています。 このような場合、ウェブサーバーは可能な限りキャッシュと共存させることが望ましいです。

ラベルapp=storeを付与した3つのレプリカから成るredisのdeploymentを記述したyamlファイルを示します。 Deploymentには、1つのNodeにレプリカを共存させないためにPodAntiAffinityを付与しています。

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を記載した以下のyamlファイルには、podAntiAffinitypodAffinityが設定されています。 全てのレプリカがapp=storeのラベルが付与されたPodと同じゾーンで稼働するよう、スケジューラーに設定されます。 また、それぞれのウェブサーバーは1つのノードで稼働されないことも保証されます。

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-1 node-2 node-3
webserver-1 webserver-2 webserver-3
cache-1 cache-2 cache-3

このように、3つのweb-serverは期待通り自動的にキャッシュと共存しています。

kubectl get pods -o wide

出力は以下のようになります:

NAME                           READY     STATUS    RESTARTS   AGE       IP           NODE
redis-cache-1450370735-6dzlj   1/1       Running   0          8m        10.192.4.2   kube-node-3
redis-cache-1450370735-j2j96   1/1       Running   0          8m        10.192.2.2   kube-node-1
redis-cache-1450370735-z73mh   1/1       Running   0          8m        10.192.3.1   kube-node-2
web-server-1287567482-5d4dz    1/1       Running   0          7m        10.192.2.3   kube-node-1
web-server-1287567482-6f7v5    1/1       Running   0          7m        10.192.4.3   kube-node-3
web-server-1287567482-s330j    1/1       Running   0          7m        10.192.3.2   kube-node-2
同じNodeに共存させない場合

上記の例では PodAntiAffinitytopologyKey: "kubernetes.io/hostname"と合わせて指定することで、redisクラスター内の2つのインスタンスが同じホストにデプロイされない場合を扱いました。 同様の方法で、Anti-Affinityを用いて高可用性を実現したStatefulSetの使用例はZooKeeper tutorialを参照してください。

nodeName

nodeNameはNodeの選択を制限する最も簡単な方法ですが、制約があることからあまり使用されません。 nodeNameはPodSpecのフィールドです。 ここに値が設定されると、schedulerはそのPodを考慮しなくなり、その名前が付与されているNodeのkubeletはPodを稼働させようとします。 そのため、PodSpecにnodeNameが指定されると、上述のNodeの選択方法よりも優先されます。

nodeNameを使用することによる制約は以下の通りです:

  • その名前のNodeが存在しない場合、Podは起動されす、自動的に削除される場合があります。
  • その名前のNodeにPodを稼働させるためのリソースがない場合、Podの起動は失敗し、理由はOutOfmemoryやOutOfcpuになります。
  • クラウド上のNodeの名前は予期できず、変更される可能性があります。

nodeNameを指定したPodの設定ファイルの例を示します:

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

上記のPodはkube-01という名前のNodeで稼働します。

次の項目

Taintsを使うことで、NodeはPodを追い出すことができます。

Node AffinityInter-Pod Affinity/Anti-Affinity には、Taintsの要点に関して様々な背景が紹介されています。