投稿日:
更新日:

Taint/Toleration による Pod 配置制御とノードの隔離

Authors

目次

banner.png

はじめに

Kubernetes には Pod の配置を制御する機能があります。 Pod の配置制御とは、任意の Pod を特定のノードにスケジュールしたり、逆に特定のノードに対して Pod をスケジュールさせないようにコントロールすることです。

最も簡易的な方法として、Node Selector を使用することで任意の Pod を所定のノードにスケジュールすることができます。 また、Node Selector に加え、より細やかな制御が可能な Node Affinity を使用することもできます。

Node Selector および Node Affinity が、ある Pod を特定のノードにスケジュールさせるために使用されるのに対し、Pod をあるノードから遠ざけたい、または特定のノードをワークロードから隔離したいという要件に対しては Taints/Tolerations という機能が用意されています。 Taints と Tolerations は互いに作用しあい、Pod が不適当なノードにスケジュールされるのを防ぎます。

Node Affinity の場合は、未指定の場合、どのノードにでも Pod をスケジュール可能ですが、Taints/Tolerations の場合は、指定しない限りそのノードに Pod がスケジュールされることはありません。

Taints/Tolerations は、例えば Production 用のノードには他の Pod をスケジュールさせたくない場合や、GPU(Graphics Processing Unit)FPGA(Field Programmable Gate Array) 等の特殊なデバイスを持つノードには特定の Pod 以外をスケジュールさせたくないといった場合に有効です。

今回のブログでは、Taints/Tolerations による Pod の配置制御とノードの隔離策についてまとめたいと思います。

Pod のスケジュールを制御する仕組み

thumbnail.png

Pod の配置を制御する仕組みとして Kubernetes ネイティブに以下の機能がサポートされています。

機能説明用途と特徴
Node Selector• 最も簡単なラベルベースのノード選択
• 単純な key=value の一致のみ対応
シンプルなラベルマッチングに基づいて Pod をスケジューリングする場合に適している。
Node Affinity• Node Selector の拡張版
• 同じくラベルを基準にするが、条件式(例: In, NotIn, Exists)や強度(必須/優先)を指定可能
柔軟なルール(優先配置や複数の条件)を必須とする場合に使用する。
Node Anti-Affinity• あるノード上で特定の Pod が動作している場合、他の Pod がそのノードにスケジュールされないように制御特定の Pod が他の Pod と同じノードで動作しないことを保証したい場合や、障害の影響範囲を分散させたい場合に使用する。
Pod Affinity• Pod が他の Pod と近接して配置されるようにスケジューリングを制御Pod 間の近接性を制御したい場合に使用する。
Pod Anti-Affinity• 特定の Pod が他の一連の Pod から離れてスケジュールされることを保証フェイルオーバと可用性を高めるために重要となる。
例えば、マイクロサービスのインスタンスが同一ノード上に配置されないように、Anti-Affinity を使用して分散させる。
Taints/Tolerations• ノードが特定の Pod を 受諾 / 拒否する仕組み特定のノードを隔離する等、専用ノードを作成する場合に使用する。

Node Selector

Node Selector は Pod を特定のノードにスケジュールするための最も基本的な方法です。 ノードのラベル(Key-Value 形式)に基づいて Pod を配置するノードを指定します。 もし、クラスタ内に条件を満たすノードが無い(指定されたラベルを持つノードが存在しない)場合、Pod は Pending となり、スケジューリングされることはありません。

Node Affinity

クラスタ内で Pod のスケジューリングを柔軟に制御するためのメカニズムの 1 つであり、ノードに設定された ラベル を基準に配置を制御します。 Node Affinity は、Pod を特定のノードに スケジューリングする際の優先条件を指定するための機能 となっており、特定のノードまたは特定の属性を持つノードに Pod が配置されるように制御することができます。

  • ルールの強制度合い

Node Affinity は、ルールの強制度合いによって 2 つのカテゴリに分けられます。

  • requiredDuringSchedulingIgnoredDuringExecution
    • 強制的(必須)のルール
    • この条件を満たさないノードには Pod はスケジューリングされない
  • preferredDuringSchedulingIgnoredDuringExecution
    • 任意(優先的)なルール
    • この条件を満たすノードがある場合にはスケジューリングするが、条件を満たさない場合でも配置される可能性がある

Node Anti-Affinity

Node Anti-Affinity は Node Affinity の逆で、Pod が特定の条件やルールに基づいて 「一緒に配置されない」ことを保証するための制約 となっています。 主に、ノード間または Pod 間の負荷分散、冗長性の向上を目的 として追加されている機能です。

Pod Affinity

Pod Affinity は、対象となる Pod 同士が近くにスケジュールされる近接性を保証します。 Pod Affinity は、任意の Pod 同士を物理的に近距離に配置することで、効率的な通信を行うことが可能となり、パフォーマンスを向上させることができます。

例えば、ある Pod がすでに特定のノードに配置されていて、その Pod と同じノードに別の Pod を配置したい場合に使用します。

Pod Anti-Affinity

Pod Anti-Affinity は Pod Affinity の逆で、特定の Pod が他の一連の Pod から離れてスケジュールされることを保証します。 これは特定の Pod が互いに障害領域(例:物理的な場所、ネットワーク、電源等)を分離することで、フェイルオーバや可用性を高める際に有効です。

例えば、マイクロサービスのデプロイ先が同一ノード上に集中しないように、Pod Anti-Affinity を使用して分散させることができます。

Taints/Tolerations

Taints は、ノードに特定の条件(ステイン・汚れ)を設定することで 「Pod を受け入れない意図を示す」仕組みであり、Tolerations は Pod がそのステインを許容できることを定義するものです。 これは、特定のノードにのみ Pod を配置したい場合や、逆に特定のワークロードを他のノードから隔離したい場合 に有効です。

Taints/Tolerations によるノードグループの隔離

本題となる Taints/Tolerations の使い方について説明します。

使用方法

Taints/Tolerations では、1 つもしくは複数の taints ラベルをノードに付与し、Pod には 1 つもしくは複数の tolerations 条件を設定します。

taint は "汚れ" という意味で、toleration は "容認" という意味です。 『"汚れ" を "容認" できる場合にはスケジュールするよ』といったニュアンスだと思われます。

  • Taints は "ノード" に設定
    • kubectl taint nodes <node-name> key=value:Effect
    • Effect には NoSchedule, PreferNoSchedule, NoExecute のいずれかを指定
  • Tolerations は "Pod" に設定
    tolerations:
      - key: 'key'
        operator: 'Equal'
        value: 'value'
        effect: 'NoSchedule'
    
$ kubectl describe node master.k8s
Name:         master.k8s
Role:
Labels:       beta.kubernetes.io/arch=amd64
              beta.kubernetes.io/os=linux
              kubernetes.io/hostname=master.k8s
              node-role.kubernetes.io/master=
Annotations:  node.alpha.kubernetes.io/ttl=0
              volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:       app=batch:NoSchedule ## これが taint ラベル

この例では、app=batch:NoSchedule が Taints の設定です。 Taints は <key>=<value>:<effect> で構成されており、app が key、batch が value、NoSchedule が effect となっています。

effect の種類

effect には、Pod がノードにスケジュールできない場合に、どのように扱うかという挙動を決定するためのパラメータを指定します。

effect には以下の 3 種類があり、Taints/Tolerations の双方で使用します。

  • NoSchedule
    • taint が許容できなければノードへスケジュールさせない
    • すでにスケジュールされている Pod はそのまま
  • PreferNoSchedule
    • taint が許容できるノードを探し、なければ許容できないノードであってもスケジュールする
  • NoExecute
    • スケジュール時に影響がある NoSchedulePreferNoSchedule と違って、NoExecute はノード上で実行中の Pod へも影響がある
    • もし NoExecute エフェクトをもった taint をノードへ追加した場合で、かつ Pod がその taint を許容できない場合、その Pod は停止(ノードから追い出される)される

メタデータに基づく Pod のスケジュール挙動

ノードに付与したメタデータに基づく Pod のスケジュール挙動を確認します。

ここでは、主に Pod 配置制御を確認する上で以下の 2 つを検証します。

  • Taints/Tolerations により部外 Pod が占有ノードにスケジュールされないこと
  • Node Affinity により特定の Pod が占有ノードにスケジュールできること

前提

検証には Amazon EKS を使用し、taint ラベル taint-type=special:NoSchedule、affinity ラベル type=special-1a がそれぞれ付与されているノードグループを作成します。

Kubernetes クラスタを構築した後、special-1a という taint ラベルを持つノードを 2 台起動させます。

マニフェスト

検証には以下のマニフェストを用いる。

  • ./deployment.yaml

対象となる Deployment を定義

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tolerations-sample-deployment
  namespace: ren510dev
  labels:
    app: tolerations-sample-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: tolerations-sample-pod
  template:
    metadata:
      labels:
        app: tolerations-sample-pod
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          imagePullPolicy: Always
          resources:
            limits:
              memory: '512Mi'
              cpu: '0.4'
            requests:
              memory: '256Mi'
              cpu: '0.2'
  • ./kustomization.yaml

対象となる Deployment に適用する Kustomize パッチを定義

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ren510dev
resources:
  - ./deployment.yaml
patches:
  ## tolerations をつけることで 占有ノードグループへのスケジュールを許可する
  - path: ../../../../kustomize/patches/tolerations/special-only.yaml
    target:
      group: apps
      version: v1
      kind: Deployment
      name: tolerations-sample-deployment
  ## node affinity で占有ノードグループを指定してスケジュールする
  - path: ../../../../kustomize/patches/nodeaffinity/special-1a.yaml
    target:
      group: apps
      version: v1
      kind: Deployment
      name: tolerations-sample-deployment
labels:
  - pairs:
      app.kubernetes.io/env: playground
      app.kubernetes.io/owner: cloud-platform
      app.kubernetes.io/group: tolerations-sample
    includeTemplates: true
  • ../../../../kustomize/patches/tolerations/special-only.yaml

対象の Deployment に適用する Tolerations パッチを定義

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment
spec:
  template:
    metadata:
      labels:
        nodeGroup: special-only
    spec:
      tolerations:
        - key: 'taint-type'
          operator: 'Equal'
          value: 'special'
          effect: 'NoSchedule'
  • ../../../../kustomize/patches/nodeaffinity/special-1a.yaml

対象の Deployment に適用する Affinity パッチを定義

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment
spec:
  template:
    metadata:
      labels:
        nodeAffinityKind: ''
        nodeAffinityType: special-1a # Node Affinity Path Type
        nodeAffinityName: special-1a # Node Affinity Patch Name
    spec:
      priorityClassName: ''
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: type
                    operator: In
                    values:
                      - special-1a

ノードグループの確認

EKS の場合、kubectl get node を実行しても、ノード名を直感的に把握することができないため、以下のようなスクリプトでノード名と taint ラベルを確認することにします。

#!/bin/bash

NODES=$(kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{" "}{end}')

for NODE in $NODES; do
  TYPE_LABEL=$(kubectl get node "$NODE" -o jsonpath='{.metadata.labels.type}' 2>/dev/null)
  TAINTS=$(kubectl get node "$NODE" -o jsonpath='{range .spec.taints[*]}{.key}{"="}{.value}{":"}{.effect}{"\n"}{end}' 2>/dev/null)

  if [ -z "$TYPE_LABEL" ]; then
    echo "$NODE => <no type label>"
  else
    echo "$NODE => $TYPE_LABEL"
  fi

  if [ -z "$TAINTS" ]; then
    echo "  Taints: <none>"
  else
    echo "  Taints:"
    echo "$TAINTS" | sed 's/^/    /'
  fi
done
### 実行結果
ip-10-251-0-183.ap-northeast-1.compute.internal => development
  Taints: <none>
ip-10-251-0-99.ap-northeast-1.compute.internal => special-1a
  Taints:
    taint-type=special:NoSchedule
ip-10-251-1-139.ap-northeast-1.compute.internal => production
  Taints: <none>
ip-10-251-1-52.ap-northeast-1.compute.internal => special-1a
  Taints:
    taint-type=special:NoSchedule
ip-10-251-10-198.ap-northeast-1.compute.internal => private-cpu
  Taints: <none>
ip-10-251-2-180.ap-northeast-1.compute.internal => development
  Taints: <none>
ip-10-251-2-83.ap-northeast-1.compute.internal => development
  Taints: <none>
ip-10-251-4-229.ap-northeast-1.compute.internal => development
  Taints: <none>
ip-10-251-5-227.ap-northeast-1.compute.internal => development
  Taints: <none>
ip-10-251-6-176.ap-northeast-1.compute.internal => system
  Taints: <none>
ip-10-251-8-13.ap-northeast-1.compute.internal => system
  Taints: <none>

ここで、以下の 2 ノードが taint ラベルを持っていることが分かります。

  • ip-10-251-0-99.ap-northeast-1.compute.internal
  • ip-10-251-1-52.ap-northeast-1.compute.internal

1. affinity + tolerations を付与している場合

まず、Pod が affinitytolerations を持っている状態で確認します。 利便性の観点からこれらは kustomize patch で定義して、ベースマニフェストをオーバーライドする形で使用します。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ren510dev
resources:
  - ./deployment.yaml
patches:
  ## tolerations をつけることで 占有ノードグループへのスケジュールを許可する
  - path: ../../../../kustomize/patches/tolerations/special-only.yaml
    target:
      group: apps
      version: v1
      kind: Deployment
      name: tolerations-sample-deployment
  ## node affinity で占有ノードグループを指定してスケジュールする
  - path: ../../../../kustomize/patches/nodeaffinity/special-1a.yaml
    target:
      group: apps
      version: v1
      kind: Deployment
      name: tolerations-sample-deployment
labels:
  - pairs:
      app.kubernetes.io/env: playground
      app.kubernetes.io/owner: cloud-platform
      app.kubernetes.io/group: tolerations-sample
    includeTemplates: true
### 適用
$ kustomize build . --load-restrictor=LoadRestrictionsNone | kubectl apply -f -

### 実行結果
$ kubectl get pod -o wide
NAME                                             READY   STATUS    RESTARTS   AGE    IP             NODE                                             NOMINATED NODE   READINESS GATES
tolerations-sample-deployment-5f465cc755-gqkr9   1/1     Running   0          40s    10.251.0.112   ip-10-251-0-99.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持つノード
tolerations-sample-deployment-5f465cc755-rzgnb   1/1     Running   0          47s    10.251.0.247   ip-10-251-1-52.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持つノード
tolerations-sample-deployment-5f465cc755-tkp2f   1/1     Running   0          52s    10.251.1.157   ip-10-251-0-99.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持つノード

tolerationsaffinity の両方のパッチを適用すると、そのワークロードは占有ノードに配置されていることが分かります。 ここで、tolerations は占有ノードに配置することを許可するために付与しており、affinity は占有ノードグループのうち、特定のノード(ゾーンタイプ)を指定してスケジュールするために使用しています。

2. tolerations のみを付与している場合

次に、Pod が tolerations のみを持っている場合の挙動を確認します。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ren510dev
resources:
  - ./deployment.yaml
patches:
  ## tolerations をつけることで 占有ノードグループへのスケジュールを許可する
  - path: ../../../../kustomize/patches/tolerations/special-only.yaml
    target:
      group: apps
      version: v1
      kind: Deployment
      name: tolerations-sample-deployment
  # ## node affinity で占有ノードグループを指定してスケジュールする
  # - path: ../../../../kustomize/patches/nodeaffinity/special-1a.yaml
  #   target:
  #     group: apps
  #     version: v1
  #     kind: Deployment
  #     name: tolerations-sample-deployment
labels:
  - pairs:
      app.kubernetes.io/env: playground
      app.kubernetes.io/owner: cloud-platform
      app.kubernetes.io/group: tolerations-sample
    includeTemplates: true
### 適用
$ kustomize build . --load-restrictor=LoadRestrictionsNone | kubectl apply -f -

### 実行結果
$ kubectl get pod -o wide
NAME                                             READY   STATUS    RESTARTS   AGE    IP             NODE                                             NOMINATED NODE   READINESS GATES
tolerations-sample-deployment-6cc55cccb7-2z8gs   1/1     Running   0          36s    10.251.1.252   ip-10-251-0-99.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持つノード
tolerations-sample-deployment-6cc55cccb7-p566d   1/1     Running   0          36s    10.251.1.118   ip-10-251-1-52.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持つノード
tolerations-sample-deployment-6cc55cccb7-vkffk   1/1     Running   0          36s    10.251.3.215   ip-10-251-2-83.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持たないノード

Pod はスケジュールされましたが、一つの Pod は占有ノード以外のノードグループに配置されていることが分かります。

つまり、tolerations のみを付与している場合は、その Pod は占有ノードにスケジュールすることができるというだけで、必ずしも占有ノードに配置されることは保証されません。 すなわち、占有ノードにスケジュールさせる場合は affinity を併用する必要があるということが分かります。

3. affinity のみを付与している場合

affinity のみを持っている場合を検証する。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ren510dev
resources:
  - ./deployment.yaml
patches:
  # ## tolerations をつけることで 占有ノードグループへのスケジュールを許可する
  # - path: ../../../../kustomize/patches/tolerations/special-only.yaml
  #   target:
  #     group: apps
  #     version: v1
  #     kind: Deployment
  #     name: tolerations-sample-deployment
  ## node affinity で占有ノードグループを指定してスケジュールする
  - path: ../../../../kustomize/patches/nodeaffinity/production.yaml ## production.yaml 等、special 以外の affinity を使用
    target:
      group: apps
      version: v1
      kind: Deployment
      name: tolerations-sample-deployment
labels:
  - pairs:
      app.kubernetes.io/env: playground
      app.kubernetes.io/owner: cloud-platform
      app.kubernetes.io/group: tolerations-sample
    includeTemplates: true
### 適用
$ kustomize build . --load-restrictor=LoadRestrictionsNone | kubectl apply -f -

### 実行結果
$ kubectl get pod -o wide
NAME                                             READY   STATUS    RESTARTS   AGE    IP             NODE                                              NOMINATED NODE   READINESS GATES
tolerations-sample-deployment-54c5755d86-9t5x8   1/1     Running   0          117s   10.251.0.42    ip-10-251-1-139.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持たないノード
tolerations-sample-deployment-54c5755d86-kcfrv   1/1     Running   0          117s   10.251.1.191   ip-10-251-1-139.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持たないノード
tolerations-sample-deployment-54c5755d86-z9hjh   1/1     Running   0          117s   10.251.1.63    ip-10-251-1-139.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持たないノード

Pod は taint ラベルが付与されている占有ノードグループに配置されることはなく、それ以外のノードに配置されています。 今回は affinity として指定している special-only は taint ラベルが付与されているため、この状態では Pod を配置することができず、Pending 状態となります。 試しに affinity として production 等を指定すると、Pod は taint ラベルを持たない他ノードに配置されることが確認できます。

ここで、ip-10-251-1-139.ap-northeast-1.compute.internal は production を affinity ラベルとして持っています。

4. affinity + tolerations のどちらも付与していない場合

最後に affinitytolerations も設定されていない Pod がどのようにスケジュールされるかを確認します。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ren510dev
resources:
  - ./deployment.yaml
patches:
  # ## tolerations をつけることで 占有ノードグループへのスケジュールを許可する
  # - path: ../../../../kustomize/patches/tolerations/special-only.yaml
  #   target:
  #     group: apps
  #     version: v1
  #     kind: Deployment
  #     name: tolerations-sample-deployment
  ## node affinity で占有ノードグループを指定してスケジュールする
  # - path: ../../../../kustomize/patches/nodeaffinity/special-1a.yaml
  #   target:
  #     group: apps
  #     version: v1
  #     kind: Deployment
  #     name: tolerations-sample-deployment
labels:
  - pairs:
      app.kubernetes.io/env: playground
      app.kubernetes.io/owner: cloud-platform
      app.kubernetes.io/group: tolerations-sample
    includeTemplates: true
### 適用
$ kustomize build . --load-restrictor=LoadRestrictionsNone | kubectl apply -f -

### 実行結果
$ kubectl get pod -o wide
NAME                                            READY   STATUS    RESTARTS   AGE     IP             NODE                                              NOMINATED NODE   READINESS GATES
tolerations-sample-deployment-77cc49b65-dkm2m   1/1     Running   0          2m21s   10.251.3.87    ip-10-251-2-83.ap-northeast-1.compute.internal    <none>           <none> ## taint ラベルを持たないノード
tolerations-sample-deployment-77cc49b65-fkn7s   1/1     Running   0          2m21s   10.251.1.153   ip-10-251-1-139.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持たないノード
tolerations-sample-deployment-77cc49b65-msn42   1/1     Running   0          2m21s   10.251.4.162   ip-10-251-5-227.ap-northeast-1.compute.internal   <none>           <none> ## taint ラベルを持たないノード

Pod は占有ノード(taint ラベルを持たないノード)以外のノードにスケジュールされることが分かります。

ここで、以下 3 つは何も taint ラベルを持たないノードです。

  • ip-10-251-2-83.ap-northeast-1.compute.internal
  • ip-10-251-1-139.ap-northeast-1.compute.internal
  • ip-10-251-5-227.ap-northeast-1.compute.internal

これは最も基本的なパターンで tolerations ラベルを持たない Pod は taint ラベルを持つ占有ノードに配置されることはなく、affinity ラベルを持たないため特定のノードグループにスケジュールすることも保証されていません。

つまり、kube-scheduler は空いているノードを見つけると、そのノードに Pod を適宜配置するようです。

ノードの状態に起因した Pod のスケジュール挙動

次に、Taint/Tolerations や Affinity を使用する状況下において、ノードがダウンした場合の Pod のスケジュール挙動を確認します。

前提

Pod に所定のラベルを設定した上で、例えばノードに何らかの障害(ストックアウト等)が発生し、1 台も起動しなかった場合の Pod のスケジューリング挙動を確認します。

ここでは、主に Pod 配置制御を確認する上で以下を検証します。

  • taint ラベルを付与している対象ノードが 0 台の時の Pod のスケジュール挙動

1. taint(占有)ノードが 0 台の場合

affinity + tolerations の状態において、対象としている taint ノードが 1 台も起動していない場合を検証します。 Pod が taint ノードにスケジュールされたことを確認した上で、cordon を実行して確かめることにします。

まず、対象ノードを確認します。 今回は以下のノードが taint ラベルを持っているため、これらのノードを cordon していきます。

  • ip-10-251-0-99.ap-northeast-1.compute.internal
  • ip-10-251-1-52.ap-northeast-1.compute.internal
$ kubectl cordon ip-10-251-0-99.ap-northeast-1.compute.internal
$ kubectl cordon ip-10-251-1-52.ap-northeast-1.compute.internal
$ kubectl get node
NAME                                               STATUS                     ROLES    AGE     VERSION                INTERNAL-IP     EXTERNAL-IP      OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
ip-10-251-0-183.ap-northeast-1.compute.internal    Ready                      <none>   40d     v1.28.8-eks-ae9a62a    10.251.0.183    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-0-8.ap-northeast-1.compute.internal      Ready                      <none>   19s     v1.29.12-eks-aeac579   10.251.0.8      <none>           Amazon Linux 2   5.10.230-223.885.amzn2.x86_64   containerd://1.7.23
ip-10-251-0-99.ap-northeast-1.compute.internal     Ready,SchedulingDisabled   <none>   6h28m   v1.29.12-eks-aeac579   10.251.0.99     <none>           Amazon Linux 2   5.10.230-223.885.amzn2.x86_64   containerd://1.7.23
ip-10-251-1-139.ap-northeast-1.compute.internal    Ready                      <none>   104d    v1.29.8-eks-a737599    10.251.1.139    <none>           Amazon Linux 2   5.10.226-214.879.amzn2.x86_64   containerd://1.7.22
ip-10-251-1-52.ap-northeast-1.compute.internal     Ready,SchedulingDisabled   <none>   6h28m   v1.29.12-eks-aeac579   10.251.1.52     <none>           Amazon Linux 2   5.10.230-223.885.amzn2.x86_64   containerd://1.7.23
ip-10-251-10-198.ap-northeast-1.compute.internal   Ready                      <none>   40d     v1.28.8-eks-ae9a62a    10.251.10.198   <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-2-180.ap-northeast-1.compute.internal    Ready                      <none>   40d     v1.28.8-eks-ae9a62a    10.251.2.180    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-2-83.ap-northeast-1.compute.internal     Ready                      <none>   40d     v1.28.8-eks-ae9a62a    10.251.2.83     <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-4-229.ap-northeast-1.compute.internal    Ready                      <none>   40d     v1.28.8-eks-ae9a62a    10.251.4.229    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-5-227.ap-northeast-1.compute.internal    Ready                      <none>   40d     v1.28.8-eks-ae9a62a    10.251.5.227    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-6-176.ap-northeast-1.compute.internal    Ready                      <none>   104d    v1.29.8-eks-a737599    10.251.6.176    <none>           Amazon Linux 2   5.10.226-214.879.amzn2.x86_64   containerd://1.7.22
ip-10-251-8-13.ap-northeast-1.compute.internal     Ready                      <none>   104d    v1.29.8-eks-a737599    10.251.8.13     <none>           Amazon Linux 2   5.10.226-214.879.amzn2.x86_64   containerd://1.7.22

taint ノードが SchedulingDisabled になったことを確認し、既存の Pod(taint ノードにスケジュールされた Pod)を削除します。

### 実行結果
$ kubectl get pod -o wide
NAME                                            READY   STATUS    RESTARTS   AGE     IP             NODE                                              NOMINATED NODE   READINESS GATES
tolerations-sample-deployment-5f465cc755-qtjd4  0/1     Pending   0          29s     <none>         <none>                                            <none>           <none>
tolerations-sample-deployment-5f465cc755-r9vzw  0/1     Pending   0          25s     <none>         <none>                                            <none>           <none>
tolerations-sample-deployment-5f465cc755-rfsrh  0/1     Pending   0          23s     <none>         <none>                                            <none>           <none>

この時、Pod は Reconciliation 処理によって再作成されますが、今回の affinitty + tolerations を満たすノードが存在しないため、他のノードに再スケジュールされることはなく Pending 状態に陥りました。

ここで、再度 uncordon してスケジュール可能な状態に戻してみます。

$ kubectl uncordon ip-10-251-0-99.ap-northeast-1.compute.internal
$ kubectl uncordon ip-10-251-1-52.ap-northeast-1.compute.internal

uncordon を実行したら、taint ノードへのスケジュールが有効になっていることを確認します。

$ kubectl get node
NAME                                               STATUS   ROLES    AGE     VERSION                INTERNAL-IP     EXTERNAL-IP      OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
ip-10-251-0-183.ap-northeast-1.compute.internal    Ready    <none>   40d     v1.28.8-eks-ae9a62a    10.251.0.183    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-0-8.ap-northeast-1.compute.internal      Ready    <none>   2m4s    v1.29.12-eks-aeac579   10.251.0.8      <none>           Amazon Linux 2   5.10.230-223.885.amzn2.x86_64   containerd://1.7.23
ip-10-251-0-99.ap-northeast-1.compute.internal     Ready    <none>   6h30m   v1.29.12-eks-aeac579   10.251.0.99     <none>           Amazon Linux 2   5.10.230-223.885.amzn2.x86_64   containerd://1.7.23
ip-10-251-1-139.ap-northeast-1.compute.internal    Ready    <none>   104d    v1.29.8-eks-a737599    10.251.1.139    <none>           Amazon Linux 2   5.10.226-214.879.amzn2.x86_64   containerd://1.7.22
ip-10-251-1-52.ap-northeast-1.compute.internal     Ready    <none>   6h30m   v1.29.12-eks-aeac579   10.251.1.52     <none>           Amazon Linux 2   5.10.230-223.885.amzn2.x86_64   containerd://1.7.23
ip-10-251-10-198.ap-northeast-1.compute.internal   Ready    <none>   40d     v1.28.8-eks-ae9a62a    10.251.10.198   <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-2-180.ap-northeast-1.compute.internal    Ready    <none>   40d     v1.28.8-eks-ae9a62a    10.251.2.180    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-2-83.ap-northeast-1.compute.internal     Ready    <none>   40d     v1.28.8-eks-ae9a62a    10.251.2.83     <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-4-229.ap-northeast-1.compute.internal    Ready    <none>   40d     v1.28.8-eks-ae9a62a    10.251.4.229    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-5-227.ap-northeast-1.compute.internal    Ready    <none>   40d     v1.28.8-eks-ae9a62a    10.251.5.227    <none>           Amazon Linux 2   5.10.217-205.860.amzn2.x86_64   containerd://1.7.11
ip-10-251-6-176.ap-northeast-1.compute.internal    Ready    <none>   104d    v1.29.8-eks-a737599    10.251.6.176    <none>           Amazon Linux 2   5.10.226-214.879.amzn2.x86_64   containerd://1.7.22
ip-10-251-8-13.ap-northeast-1.compute.internal     Ready    <none>   104d    v1.29.8-eks-a737599    10.251.8.13     <none>           Amazon Linux 2   5.10.226-214.879.amzn2.x86_64   containerd://1.7.22

すると、Pending 状態の Pod は占有ノードグループに 自動的に再配置される ことが分かります。

$ kubectl get pod -o wide
NAME                                             READY   STATUS    RESTARTS   AGE     IP             NODE                                             NOMINATED NODE   READINESS GATES
tolerations-sample-deployment-5f465cc755-4gm2q   1/1     Running   0          5m18s   10.251.1.77    ip-10-251-0-8.ap-northeast-1.compute.internal    <none>           <none> ## taint ラベルを持つノード
tolerations-sample-deployment-5f465cc755-96kkz   1/1     Running   0          5m13s   10.251.0.184   ip-10-251-0-8.ap-northeast-1.compute.internal    <none>           <none> ## taint ラベルを持つノード
tolerations-sample-deployment-5f465cc755-jp75n   1/1     Running   0          5m20s   10.251.0.155   ip-10-251-0-8.ap-northeast-1.compute.internal    <none>           <none> ## taint ラベルを持つノード

ここで、ip-10-251-0-8.ap-northeast-1.compute.internal は taint ラベルを持つ占有ノードグループです。

検証結果

検証結果をまとめると以下の通りとなります。

  • affinitty:特定の Pod を特定のノードにスケジュールさせるために利用
  • tolerations:占有ノードグループに対して特定の Pod をスケジュールさせないために利用

これらを組み合わせることで、特定の Pod のみを占有ノードグループへスケジュールさせるとともに、他の Pod が占有ノードグループへスケジュールされるのを防ぐことができます。

一方で、affinitty + tolerations を満たすノードが存在しない場合に Pod がスケジュールされると、その Pod は Pending 状態に陥ります。

もし、対象の Pod を一時的に占有(taint)ノードグループ以外のノードにスケジュールすることを許容する場合、taint ラベルに effect: PreferNoSchedule を指定することで実現できます。

しかし、この場合は、再度占有ノードが復帰しても、退避した Pod を自動的に元に戻す術はなく、手動での再作成が(削除後、Reconcilation させる)必要となります。

まとめ

今回は Pod の配置を制御する仕組みとして主に、Node Affinity と Taints/Tolerations を取り上げました。

Node Affinity は Pod を特定のノードにスケジュールする際に有効です。 また、Taints/Tolerations は占有ノードグループを構築した場合に、ノードを隔離して特定の Pod のみを受け入れるように制御することができます。

特に、Taints/Tolerations は開発環境と本番環境が単一のクラスタに集約されており、ノードグループ(GKE の場合はノードプール)単位で分割しているような場合に有効的に利用できると思います。