【初心者向け】Kubernetes Taintの基本

【初心者向け】Kubernetes Taintの基本

Kubernetesは、コンテナ化されたワークロードを自動でデプロイ、スケーリング、管理するための強力なプラットフォームです。その中心的な機能の一つに、Pod(Kubernetesにおける最小のデプロイ可能なコンピューティング単位)をどのノード(ワーカーマシン)で実行するかを決定する「スケジューリング」があります。Kubernetesのスケジューラーは賢く、ノードのリソース使用状況や指定されたリソース要求などを考慮して、最適なノードを選択します。

しかし、時にはこのデフォルトのスケジューリングだけでは不十分な場合があります。例えば、特定のハードウェア(例:GPU)を持つノードだけに特定のPodを配置したい、あるいは一時的に問題が発生したノードやメンテナンス中のノードに新しいPodが配置されないようにしたい、といった要望が出てくることがあります。

このような高度なPod配置制御を実現するために、Kubernetesにはいくつかの機能が用意されています。その中でも特に重要な概念の一つが、「Taint(テイント)」と「Toleration(トレラレーション)」です。

この記事では、Kubernetes初心者の方に向けて、TaintとTolerationの基本を、その目的、動作原理、具体的な使い方、そしてユースケースを交えながら、約5000語の詳細な説明で解説します。この記事を読めば、TaintとTolerationがどのようなもので、どのようにPodの配置を制御するのか、そしてなぜそれが重要なのかを深く理解できるでしょう。

1. はじめに:なぜPodの配置制御が必要なのか?

Kubernetesは、クラスター内の複数のノードにPodを分散して配置することで、可用性の向上やリソースの効率的な利用を図ります。デフォルトでは、Kubernetesのスケジューラーは、Podが必要とするリソース(CPU、メモリなど)が十分にあり、Podが指定したその他の基本的な条件(例:ノード上のポートの競合がないか)を満たすノードの中から、比較的負荷の低いノードなどを選んでPodを配置します。

この自動的な配置は多くのユースケースでうまく機能しますが、現実の運用では、よりきめ細やかな制御が必要になる場面が多々あります。例をいくつか挙げてみましょう。

  • 特殊なハードウェアの利用: 機械学習に使うPodはGPUを持つノードにだけ配置したい、特定のI/O性能を持つノードにデータベースPodを配置したい、といった場合。デフォルトのスケジューラーはハードウェアの種類を直接認識しません。
  • ライセンスコストの管理: 特定の商用ソフトウェアは、そのライセンスが紐づいたノード上でしか実行できない、あるいはライセンスコストが高いノードには特定のPodしか配置したくない、といった場合。
  • ノードの分離: 開発環境用のPodと本番環境用のPodを物理的に異なるノードで実行したい、あるいはセキュリティ上の理由から特定の種類のPodを隔離されたノードグループに配置したい、といった場合。
  • 一時的なノードの除外: 特定のノードでハードウェアのメンテナンスを行う必要がある、あるいはそのノードで障害が発生し、一時的に新しいPodが配置されないようにしたい、といった場合。

これらのシナリオでは、単にリソースがあるかないかだけでなく、「このPodはこの種類のノードには配置してはいけない」「このノードには特定の種類のPodしか配置してはいけない」といった、より複雑な制約が必要になります。TaintとTolerationは、このような「ノードへのPodの配置禁止」や「特定のPodに対する配置禁止の緩和」といった制御を可能にするための仕組みです。

2. KubernetesにおけるPod配置の基本と限界

Taintを理解するために、まずはKubernetesがどのようにPodをノードに配置するか、その基本をおさらいしておきましょう。

2.1 スケジューラーの役割

Podが作成されると、Kubernetesのコントロールプレーンの一部である「スケジューラー」がそのPodを検知します。スケジューラーの主な役割は、そのPodを実行するのに最適なノードをクラスター内の利用可能なノードの中から選ぶことです。

スケジューリングのプロセスは大きく分けて二つのフェーズがあります。

  1. フィルタリング(Predicates): Podを実行するための基本的な要件(リソース要求を満たしているか、ノードポートの競合がないか、など)を満たすノードを選び出します。このフェーズで条件を満たさないノードは候補から除外されます。
  2. スコアリング(Priorities): フィルタリングを通過したノードに対して、それぞれのノードの「望ましさ」をスコアリングします。リソースの空き具合、ノード間のPodの分散、Podの指定するアフィニティ/アンチアフィニティなどの様々な要素が考慮され、最もスコアの高いノードが最終的な配置先として決定されます。

2.2 デフォルトの配置ロジックの限界

デフォルトのスケジューラーは非常に柔軟で、多くの状況に対応できます。しかし、先ほど述べたような「特定のノードに特定のPodを配置する/しない」といったポリシーベースの制御や、ノードの属性(ハードウェアの種類、場所など)に基づく厳密な配置制限には、デフォルトのロジックだけでは対応しきれません。

例えば、単に「GPUが必要なPod」を指定しただけでは、スケジューラーはGPUを持つノードを優先的に選ぶわけではありません。Podが要求するCPUやメモリを満たすノードであれば、GPUの有無に関わらず配置しようとします。逆に、GPUを持たないノードにはGPUを使うPodを配置してほしくない、という要件もデフォルトでは満たせません。

このような限界を克服するために、Kubernetesでは以下のような、より詳細なPod配置制御の仕組みが提供されています。

  • Node Selector: Podの定義で特定のラベルを持つノードを指定し、そのラベルを持つノードにのみPodを配置するように制約を設けるシンプルな方法。ただし、これは必須条件であり、条件を満たすノードがない場合はPodはPending状態になります。
  • Node Affinity / Anti-Affinity: Node Selectorをより柔軟にしたもので、特定のラベルを持つノードに対する「アフィニティ(親和性)」や「アンチアフィニティ(反親和性)」を指定できます。「requiredDuringSchedulingIgnoredDuringExecution」(スケジューリング時に必須、実行中のノード変更は無視)や「preferredDuringSchedulingIgnoredDuringExecution」(スケジューリング時に優先、実行中のノード変更は無視)といったルールを設定できます。
  • TaintとToleration: これが本記事のテーマです。ノード側に「このノードには特定のPodを配置したくない」という印(Taint)をつけ、Pod側に「この印があっても、このPodは配置されても構わない」という許容(Toleration)を設定することで、Podの配置を制御します。

Node SelectorやNode Affinityが「このPodをどこに配置したいか」というPod側の希望や要件を主に表現するのに対し、Taintは「このノードにはどのようなPodに来てほしくないか」というノード側の意向を主に表現する仕組みと言えます。そして、Tolerationは、そのノード側の意向に対し、特定のPodが「例外」として配置されることを可能にするものです。

3. Taintとは

いよいよTaintについて詳しく見ていきましょう。

3.1 定義と目的

Taint(テイント)は、英語で「汚れ」「染み」「汚点」といった意味を持つ単語です。Kubernetesにおいては、ノードに対して「汚点」をつけることを指します。この「汚点」は、特定の種類のPodがそのノードにスケジュールされるのを避けるための目印となります。

簡単に言えば、ノードにTaintを設定すると、「このノードは、特定の条件を満たさない限り、普通のPodには使わせたくない」という意思表示になります。

3.2 Taintの構成要素

Taintは、以下の3つの要素で構成されます。

  1. キー (Key): Taintの種類を識別するための文字列です。例えば、gpuspecial-hardwaremaintenance など、自由に名前を付けられます。(Kubernetesのラベルやアノテーションと同じ命名規則に従います。)
  2. 値 (Value): オプションです。キーに対する詳細な情報を表現するための文字列です。例えば、キーがgpuの場合、値としてnvidiaamdを指定する、といった使い方ができます。値が必要ない場合は省略可能です。
  3. エフェクト (Effect): これがTaintの最も重要な要素です。Taintが付与されたノードに対して、どのような種類の「避けさせる」動作を期待するかを定義します。後述する3種類のエフェクトがあります。

これらの要素は、key=value:effect または key:effect(値がない場合)という形式で表現されます。例えば:

  • gpu=nvidia:NoSchedule
    • キー: gpu
    • 値: nvidia
    • エフェクト: NoSchedule
    • 意味: このノードにはGPU(nvidia製)があり、特別な理由がない限り、新しいPodはこのノードにスケジュールしないでください(NoSchedule)。
  • maintenance:NoExecute
    • キー: maintenance
    • 値: なし
    • エフェクト: NoExecute
    • 意味: このノードはメンテナンス中です。新しいPodはスケジュールしないでください かつ 既に実行中のPodも可能な限り強制排除してください(NoExecute)。

4. Taintのエフェクト

Taintのエフェクトは、そのTaintがノード上のPodに与える影響の種類を定義します。Kubernetesでは、主に以下の3種類のエフェクトがサポートされています。

4.1 NoSchedule

  • 意味: このエフェクトを持つTaintが付与されたノードには、そのTaintを許容しない(Tolerationを持たない)新しいPodはスケジュールされません。
  • 影響:
    • 新しいPod: Taintを許容するTolerationを持たない新しいPodは、このノードをスケジューリングの候補から外します。他の利用可能なノードにスケジュールされます。もしこのノード以外にスケジュール可能なノードがない場合、PodはPending状態のままになります。
    • 既に実行中のPod: このエフェクトは、既にそのノードで実行されているPodには影響を与えません。Podが強制排除されたり、再起動されたりすることはありません。
  • ユースケース:
    • 特定のハードウェア(GPUなど)を持つノードを、そのハードウェアを使うPod専用にしたいが、既に実行中のPodは中断したくない場合。
    • ノードのメンテナンスを開始する前に、新しいPodが来ないようにしたいが、現在実行中のPodはそのまま稼働させておきたい場合。
  • 動作: スケジューラーのフィルタリング段階で、TaintとTolerationのマッチングが行われ、Taintを許容しないPodはこのノードから除外されます。

4.2 PreferNoSchedule

  • 意味: このエフェクトを持つTaintが付与されたノードは、そのTaintを許容しない新しいPodのスケジューリング先としては可能な限り避けられます
  • 影響:
    • 新しいPod: Taintを許容するTolerationを持たない新しいPodは、スケジューラーによってこのノードへの配置が「推奨されない」と判断されます。スケジューラーは、他の適切なノードがあればそちらを優先します。しかし、もし他のノードが非常に混み合っている、あるいはこのノード以外にPodを配置できるノードがないなど、やむを得ない状況であれば、このノードにスケジュールされる可能性はあります。これは「ベストエフォート」な回避策です。
    • 既に実行中のPod: NoSchedule と同様に、既に実行中のPodには影響を与えません。
  • ユースケース:
    • 特定の種類のノードを優先的に避けたいが、必須ではない場合。例えば、開発環境用のノードを本番環境のPodから可能な限り避けたいが、リソースが逼迫している場合は開発ノードを使っても構わない、といった緩やかな制御をしたい場合。
  • 動作: スケジューラーのスコアリング段階で、TaintとTolerationのマッチングが考慮され、Taintを許容しないPodにとってこのノードのスコアが低く評価されます。

4.3 NoExecute

  • 意味: このエフェクトを持つTaintが付与されたノードには、そのTaintを許容しない新しいPodはスケジュールされません。さらに、既にそのノードで実行中のPodも、そのTaintを許容するTolerationを持たない場合は強制的にノードから排除(Evict)されます
  • 影響:
    • 新しいPod: NoSchedule と同様に、Taintを許容するTolerationを持たない新しいPodはスケジュールされません。
    • 既に実行中のPod: Taintを許容するTolerationを持っていないPodは、そのノードから強制的に排除されます。これは、Podが終了し、ReplicaSetやDeploymentなどのコントローラーによって別のノードで再作成されることを意味します。Tolerationに tolerationSeconds が指定されている場合は、指定された秒数だけ猶予期間が与えられます。
  • ユースケース:
    • ノードで障害が発生した、あるいは深刻な問題が発生し、そのノード上のPodを全て別のノードに移したい場合。
    • ノードのOSアップデートやファームウェア更新など、Podの実行を一時停止する必要があるメンテナンスを行う場合。
    • マスターノードなど、特定のシステムPod以外は一切実行させたくないノード。
  • 動作: スケジューラーのフィルタリング段階での新規Podの拒否に加えて、Kubelet(各ノードで実行されるKubernetesのエージェント)がノードのTaintを監視し、Taintを許容しない実行中のPodを排除します。

5. Tolerationとは

Taintがノード側の「特定のPodに来てほしくない」という意思表示であるのに対し、Toleration(トレラレーション)はPod側の「このTaintがあっても、このノードに配置されても構わない」という許容です。Tolerationは英語で「寛容」「許容」といった意味です。

TaintとTolerationはセットで機能します。ノードにTaintがある場合、そのTaintに対応するTolerationをPodが持っていなければ、デフォルトではそのノードにスケジュールされません(エフェクトによる)。しかし、Podが対応するTolerationを持っていれば、そのノードにスケジュールされる可能性があります(ただし、Tolerationはあくまで「配置を妨げない」だけであり、「必ずそこに配置する」わけではありません。スケジューラーは他の要素も考慮して最終的な配置先を決定します)。

5.1 Tolerationの構成要素

Tolerationは、Podの定義(Pod Spec)の中に記述されます。TolerationもTaintと同様に以下の要素で構成されます。

  1. キー (Key): 許容したいTaintのキーと一致させるための文字列です。
  2. オペレーター (Operator): キーと値の組み合わせがどのようにTaintと一致するかを指定します。
    • Equal: キーと値が両方とも完全に一致する場合にマッチします。(デフォルト)
    • Exists: 値に関わらず、キーがTaintと一致する場合にマッチします。このオペレーターを使う場合、Tolerationの値は指定しません。
  3. 値 (Value): オペレーターがEqualの場合に、許容したいTaintの値と一致させるための文字列です。オペレーターがExistsの場合は指定しません。
  4. エフェクト (Effect): 許容したいTaintのエフェクトと一致させるための文字列です。指定しない場合、全てのTaintエフェクト(NoSchedule, PreferNoSchedule, NoExecute)を許容します。特定のTaintエフェクトのみを許容したい場合に指定します。
  5. tolerationSeconds: エフェクトがNoExecuteの場合にのみ有効なオプションです。NoExecute Taintが付与されたノードで、このPodがノードから強制排除されるまでの猶予時間(秒数)を指定します。このオプションを指定しない場合、NoExecute Taintが付与されると即座に排除されます。

PodのYAMLファイルでは、spec.tolerations フィールドの下にTolerationのリストとして定義します。

yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod-with-toleration
spec:
containers:
- name: my-container
image: nginx
tolerations:
- key: "gpu"
operator: "Equal" # or "Exists"
value: "nvidia" # required if operator is "Equal"
effect: "NoSchedule" # or "PreferNoSchedule", "NoExecute", or omit for all effects
- key: "maintenance"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 3600 # stay on the node for 3600 seconds even if NoExecute taint is added

上記の例では、このPodは以下の2つのTaintを許容します。
1. キーがgpu、値がnvidia、エフェクトがNoScheduleのTaint。
2. キーがmaintenance、値は任意、エフェクトがNoExecuteのTaint。このTaintが付与されても、Podは3600秒間はノードに留まります。

5.2 TolerationとTaintの関係性

TolerationとTaintは、互いに対応することで効果を発揮します。PodのTolerationリスト内のいずれかのTolerationが、ノードに付与されたTaintのいずれかと「マッチ」した場合、そのTaintによってPodがそのノードにスケジュールされることが妨げられなくなります。

マッチングのルールは以下の通りです。PodのToleration T がノードのTaint t とマッチすると判断されるのは、以下のいずれかの条件を満たす場合です。

  1. T が空、かつ t が空。
  2. T.keyt.key が一致し、かつ T.effectt.effect が一致し、かつ以下のいずれかを満たす場合:
    • T.operatorEqual かつ T.valuet.value が一致。
    • T.operatorExists。(この場合、T.value は存在しないか、無視されます。)

より詳細なマッチングルール:
* 特定のTaintを完全に許容: key, value, effect の全てを指定し、operator: Equal とする。
* 特定キーの任意の値と特定エフェクトを許容: key, effect を指定し、operator: Exists とする。
* 特定キーの任意の値と任意のエフェクトを許容: key を指定し、operator: Exists とし、effect を省略する。
* 任意のエフェクトを持つ全てのTaintを許容: keyvalueeffectoperator の全てを省略(空のtolerationsリスト)、あるいは operator: Exists のTolerationで key も省略、effect も省略。ただし、KubernetesのAPIでは空のToleration要素 {} は有効ですが、特定のTaintに対して何らかの形でマッチさせるためには、少なくともキーやエフェクトを指定するのが一般的です。通常は、Podに空のtolerations: [] を指定しても、ノードのTaintは無視されません。 全てのTaintを許容するには、特殊なTolerationが必要です(後述の「全てのTaintを許容する特別なToleration」を参照)。
* 特定のキー、特定の値を持つ全てのTaintエフェクトを許容: key, value を指定し、operator: Equal とし、effect を省略する。

重要なポイント:
* Podは複数のTolerationを持つことができます。
* ノードも複数のTaintを持つことができます。
* Podがノードにスケジュールされるためには、ノードが持つ全てのTaintに対して、Podがそれらを許容するTolerationを少なくとも1つ持っている必要があります(NoSchedule および NoExecute エフェクトの場合)。
* 例外として、PreferNoSchedule エフェクトのTaintは、たとえPodが許容するTolerationを持っていなくても、他の選択肢がない場合に配置される可能性がある「推奨」レベルの制約です。

全てのTaintを許容する特別なToleration

特定の状況(例えば、システムアドミン用のPodなど)で、クラスター内の全てのノードの、全てのTaintを無視してどのノードにもスケジュール可能にしたい場合があります。これを実現するには、以下のような特別なTolerationをPodに定義します。

yaml
tolerations:
- operator: "Exists"

このTolerationは、キーも値もエフェクトも指定せず、オペレーターとして Exists を使用します。これは、「存在する全てのTaint」を許容するという意味になります。このようなPodは、Taintによる配置制限を完全に無視できます。

6. TaintとTolerationの動作原理の詳細

スケジューラーがPodをノードに配置する際、TaintとTolerationは以下のように関与します。

6.1 スケジューリング判定フローにおけるTaint/Toleration

Podが作成されると、スケジューラーはまだどのノードにも割り当てられていないPod(pod.spec.nodeName が空のPod)を検知します。そして、そのPodに対して最適なノードを探します。

  1. ノード候補リストの生成: スケジューラーは、クラスター内の全ての利用可能なノードのリストを取得します。
  2. フィルタリング (Predicates): スケジューラーは、各ノードに対してPodの基本的な要件(リソース、ポートなど)を満たすかどうかを評価します。この段階で、TaintとTolerationのマッチングが行われます。
    • ノードに NoSchedule または NoExecute エフェクトを持つTaintが付与されている場合、スケジューラーはそのTaintをチェックします。
    • PodがそのTaintを許容するTolerationを持っていない場合、そのノードはPodの配置先候補から除外されます
    • PodがそのTaintを許容するTolerationを持っている場合、そのTaintについては問題なしと判断され、ノードは候補リストに残ります。(ただし、そのノードに他の満たせない制約がない限り)。
    • ノードに PreferNoSchedule エフェクトを持つTaintがある場合、フィルタリング段階ではノードは除外されません。
  3. スコアリング (Priorities): フィルタリングを通過したノードに対してスコアリングが行われます。様々な要因が考慮されますが、ここでもTaintとTolerationが関与します。
    • ノードに PreferNoSchedule エフェクトを持つTaintが付与されている場合、スケジューラーはそのTaintをチェックします。
    • PodがそのTaintを許容するTolerationを持っていない場合、そのノードのスコアは低くなります。
    • PodがそのTaintを許容するTolerationを持っている場合、スコアには影響しません(あるいはわずかに影響する)。
    • NoScheduleNoExecute エフェクトのTaintは、フィルタリングで既に処理されているため、通常スコアリングには直接影響しません。
  4. 最終決定: スコアが最も高いノードがPodの配置先として決定されます。スケジューラーはそのノード名をPodの spec.nodeName フィールドに書き込みます。
  5. ノードでの実行: ノード上のKubeletが、自身のノード名が指定されたPodを検知し、そのPodのコンテナを起動します。

6.2 NoExecute エフェクトとKubeletの役割

NoExecute エフェクトは、スケジューラーだけでなく、ノード上で実行されているKubeletとも連携して動作します。

  • スケジューラーの役割: 新しいPodが NoExecute Taintを持つノードにスケジュールされないように、フィルタリングで除外します。
  • Kubeletの役割: Kubeletは、自身が実行されているノードに付与されたTaintを常に監視しています。もしノードに NoExecute Taintが付与された場合、Kubeletはそのノード上で実行中のPodのTolerationを確認します。
    • Podがその NoExecute Taintを許容するTolerationを持っていない場合、KubeletはそのPodを強制的に停止し、ノードから排除します(Evict)。
    • Podがその NoExecute Taintを許容するTolerationを持っており、かつ tolerationSeconds が指定されている場合、KubeletはそのPodを tolerationSeconds の間、ノードに留まらせます。指定された時間が経過するか、ノードからTaintが削除されるか、あるいはPodがTaintを許容しない状態になった場合に、Podは排除されます。
    • Podがその NoExecute Taintを許容するTolerationを持っており、かつ tolerationSeconds が指定されていない場合、そのTaintによる排除の対象にはなりません。

この Kubelet による排除機能は、ノードの状態変化(ネットワーク断、リソース枯渇などによる自動的なTaint付与)に対応して、問題のあるノードから実行中のPodを自動的に退避させる際にも利用されます。

6.3 複数のTaintと複数のToleration

ノードに複数のTaintが付与されている場合、またはPodに複数のTolerationが定義されている場合の動作は以下のようになります。

  • ノードに複数のTaint:
    • Podがノードにスケジュールされるためには、ノードに付与されている NoSchedule または NoExecute エフェクトを持つ全てのTaintに対して、Podがそれぞれを許容するTolerationを少なくとも1つずつ持っている必要があります。いずれか1つのTaintでも許容できない場合、Podはスケジュールされません。
    • PreferNoSchedule エフェクトのTaintは、たとえPodが許容できなくても、他のTaintが全て許容されていればノードはフィルタリングを通過します。スコアリングにおいてそのノードの優先度が下がるだけです。
  • Podに複数のToleration:
    • Podが持つTolerationのリストは、ノードのTaintリストと比較されます。Podが持つTolerationのいずれかが、ノードの特定のTaintとマッチすれば、そのTaintによる配置制限は解除されます。

例:
ノード node-1 に以下のTaintがあるとします。
gpu=nvidia:NoSchedule
disk=ssd:NoSchedule
network=low-latency:PreferNoSchedule
maintenance:NoExecute

Pod pod-a が以下のTolerationを持つとします。
yaml
tolerations:
- key: "gpu"
operator: "Equal"
value: "nvidia"
effect: "NoSchedule"
- key: "disk"
operator: "Exists" # 任意の値のdisk Taintを許容
effect: "NoSchedule"

このPodは gpu=nvidia:NoScheduledisk=ssd:NoSchedule のTaintを許容します。しかし、maintenance:NoExecute のTaintを許容するTolerationを持っていません。したがって、このPodは node-1 にはスケジュールされません(NoExecute Taintがあるため)。

Pod pod-b が以下のTolerationを持つとします。
yaml
tolerations:
- key: "gpu"
operator: "Equal"
value: "nvidia"
effect: "NoSchedule"
- key: "disk"
operator: "Exists"
effect: "NoSchedule"
- key: "maintenance"
operator: "Exists"
effect: "NoExecute"

このPodは gpu=nvidia:NoScheduledisk=ssd:NoSchedulemaintenance:NoExecute の全ての NoSchedule および NoExecute エフェクトのTaintを許容します。したがって、このPodは node-1 にスケジュールされる可能性があります。ただし、network=low-latency:PreferNoSchedule のTaintを許容しないため、他のノードに比べてこのノードへの優先度は下がるでしょう。

7. 具体的なユースケース

TaintとTolerationは、様々なシナリオでPod配置の強力な制御手段として利用できます。いくつかの代表的なユースケースを見てみましょう。

7.1 特定ハードウェアを持つノードの分離

特定のハードウェア(例:GPU、高性能SSD、特殊なNIC)を持つノードを、そのハードウェアを必要とする特定のPodのためだけに予約したい場合にTaintが役立ちます。

方法:
1. 特殊なハードウェアを持つノードにTaintを設定します。例えば、gpu=nvidia:NoSchedule のようなTaintを付与します。
2. そのハードウェアを必要とするPodの定義に、対応するToleration(例:キーgpu、値nvidia、エフェクトNoScheduleを許容するToleration)を追加します。

結果: Taintを持つノードには、Tolerationを持たない一般的なPodはスケジュールされなくなります。Tolerationを持つPodのみがそのノードにスケジュールされる候補となります。これにより、限られた特殊なリソースを効率的に利用できます。

7.2 問題のあるノードの一時的な隔離

ノードで一時的な障害が発生したり、パフォーマンが低下したりした場合に、新しいPodがそのノードに配置されるのを避けたい場合があります。

方法:
1. 問題のあるノードにTaintを設定します。例えば、unhealthy:NoSchedule または unhealthy:NoExecute のようなTaintを付与します。
2. 必要に応じて、そのノード上で実行中のPodのうち、問題の影響を受けない特定のPodだけを継続して実行させたい場合は、それらのPodに unhealthy Taintを許容するTolerationを設定します。

結果: NoSchedule Taintの場合、新しいPodはそのノードに来なくなります。既に実行中のPodはそのままです。NoExecute Taintの場合、新しいPodは来なくなり、さらにTolerationを持たない実行中のPodは強制的に排除され、健全なノードで再起動されます。これにより、問題のあるノードを安全に運用から一時的に切り離すことができます。問題が解決したら、Taintを削除することでノードを通常運用に戻せます。

7.3 マスターノードの隔離

Kubernetesのコントロールプレーンコンポーネント(APIサーバー、スケジューラー、コントローラーマネージャー、etcdなど)は通常、マスターノード(あるいはコントロールプレーンノード)で実行されます。これらのノードはクラスター全体の管理を担っており、安定した動作が非常に重要です。一般的なアプリケーションPodがマスターノードに誤って配置されると、リソースを消費したり、システムコンポーネメントの動作を妨げたりする可能性があります。

デフォルトで、多くのKubernetesディストリビューション(例:kubeadmで構築したクラスター)では、マスターノードに特定のTaintが自動的に付与されています。一般的なTaintは以下の通りです。

node-role.kubernetes.io/control-plane:NoSchedule (または node-role.kubernetes.io/master:NoSchedule – 以前のバージョン)

方法:
1. (通常は自動的に設定されていますが)マスターノードに上記のTaintが付与されていることを確認します。
2. KubernetesのシステムPod(kube-system名前空間にあるPodなど)は、通常、このTaintを許容するTolerationをデフォルトで持っています。
3. ユーザーがデプロイする一般的なアプリケーションPodは、このTaintを許容するTolerationを持たないため、マスターノードにはスケジュールされません。

結果: システムコンポーネント以外のPodがマスターノードに配置されるのを防ぎ、マスターノードの安定性を保ちます。

7.4 ライセンスコストの高いノードの分離

特定の商用ソフトウェアは、ノード単位でライセンスが必要であり、そのコストが高い場合があります。このようなノードには、そのソフトウェアを使用する特定のアプリケーションPodのみを配置したい場合があります。

方法:
1. ライセンスコストの高いノードにTaintを設定します。例えば、license=expensive:NoSchedule のようなTaintを付与します。
2. ライセンスソフトウェアを使用するアプリケーションPodの定義に、対応するToleration(例:キーlicense、値expensive、エフェクトNoScheduleを許容するToleration)を追加します。

結果: ライセンスコストの高いノードには、ライセンスソフトウェアを使用しないPodはスケジュールされなくなります。これにより、ライセンスコストを抑えつつ、必要なPodを適切なノードに配置できます。

7.5 データセンター/アベイラビリティゾーン間のノード分離

複数のデータセンターやアベイラビリティゾーンにまたがるKubernetesクラスターを構築している場合、特定のPodを特定の物理的な場所に固定したい場合があります(例:リージョン固有のデータにアクセスするPod、規制要件を満たすPod)。

方法:
1. 各データセンター/アベイラビリティゾーンのノードに、その場所を示すTaintを設定します。例えば、datacenter=tokyo:NoSchedulezone=us-east-1a:NoSchedule のようなTaintを付与します。
2. 特定の場所に配置したいPodの定義に、対応するTolerationを追加します。また、確実にその場所に配置するためには、Node Selector や Node Affinity も併用することが推奨されます。

結果: Taintによって、意図しない場所にPodがスケジュールされるのを防ぐことができます。Tolerationと Node Affinity を組み合わせることで、「このゾーンのノードに来てほしいが、他のゾーンのノードには絶対行かないでほしい」といった厳密な制御が可能になります。

8. TaintとTolerationの使い方

実際にTaintをノードに設定したり、TolerationをPodに設定したりする方法を解説します。

8.1 Taintの設定/削除

Taintは kubectl taint コマンドを使って設定・削除するのが最も一般的です。

Taintの設定:

コマンド形式:
kubectl taint nodes <node-name> <key>=<value>:<effect>

  • <node-name>: Taintを設定したいノードの名前。
  • <key>: Taintのキー。
  • <value>: Taintの値(オプション)。
  • <effect>: Taintのエフェクト(NoSchedulePreferNoScheduleNoExecuteのいずれか)。

値がない場合は、<key>:<effect> の形式になります。

例:ノード worker-node-1 にGPUがあることを示す NoSchedule Taintを設定する。
bash
kubectl taint nodes worker-node-1 gpu=nvidia:NoSchedule

例:ノード worker-node-2 をメンテナンスのために隔離する NoExecute Taintを設定する(値なし)。
bash
kubectl taint nodes worker-node-2 maintenance:NoExecute

設定されたTaintの確認:

kubectl describe node <node-name> コマンドの出力で確認できます。Taints: の項目を探してください。

“`bash
kubectl describe node worker-node-1

出力例の一部

Taints: gpu=nvidia:NoSchedule

“`

Taintの削除:

削除したいTaintのキー、値、エフェクトを指定し、最後にハイフン(-)を付けます。

コマンド形式:
kubectl taint nodes <node-name> <key>=<value>:<effect>-

値がない場合は、<key>:<effect>- の形式になります。

例:ノード worker-node-1 から gpu=nvidia:NoSchedule Taintを削除する。
bash
kubectl taint nodes worker-node-1 gpu=nvidia:NoSchedule-

例:ノード worker-node-2 から maintenance:NoExecute Taintを削除する。
bash
kubectl taint nodes worker-node-2 maintenance:NoExecute-

もしキーのみを指定して、そのキーを持つ全てのTaint(値やエフェクトに関わらず)を削除したい場合は、値やエフェクトを指定せずにハイフンを付けます(ただし、これはあまり一般的ではありません)。

例:ノード worker-node-1 からキーがgpuの全てのTaintを削除する(非推奨、特定のTaintを特定して削除する方が安全)。
“`bash

注意: これはキーが ‘gpu’ の Taint の全てを削除する意図だが、実際には

正確なキー=値:エフェクト の指定が必要。

例: gpu=nvidia:NoSchedule- を指定しないと、特定の Taint は削除できない。

値やエフェクトを指定せず ‘-‘ を付けると、そのキーを持つ Taint のうち、

コマンドラインで指定したキーと一致する部分(値やエフェクトがない場合)を削除しようとするが、

既存の Taint が key=value:effect 形式の場合はマッチしない。

常に key=value:effect- の形式で特定して削除するのが正しい方法です。

“`
[補足] NodeSpecでのTaint設定(アドバンスト):

YAMLファイルでノードの定義(NodeSpec)を編集することでもTaintを設定・削除できます。これは、自動化されたノードプロビジョニングや、クラスター状態のIaC(Infrastructure as Code)管理において使用されることがあります。kubectl edit node <node-name> コマンドや、オペレーターなどを使用します。初心者の方は、まずは kubectl taint コマンドを使うことから始めるのがおすすめです。

8.2 Tolerationの設定

Tolerationは、Podの定義(YAMLファイル)の spec.tolerations フィールドに記述します。これは Podだけでなく、ReplicaSet, Deployment, StatefulSet, DaemonSet, JobなどのPodテンプレートを持つKubernetesオブジェクトにも設定できます。通常はこれらのコントローラーに設定し、そこから作成されるPodにTolerationが引き継がれるようにします。

例:gpu=nvidia:NoSchedule Taintを許容するPodのYAML定義

yaml
apiVersion: v1
kind: Pod
metadata:
name: gpu-workload-pod
spec:
containers:
- name: main-container
image: my-gpu-image
# ... その他のコンテナ設定 (リソース要求など) ...
tolerations:
- key: "gpu"
operator: "Equal"
value: "nvidia"
effect: "NoSchedule"

例:maintenance:NoExecute Taintを許容し、かつ60秒の猶予時間を持つPodのYAML定義(値なしTaintの許容)

yaml
apiVersion: v1
kind: Pod
metadata:
name: critical-service-pod
spec:
containers:
- name: main-container
image: my-critical-image
# ... その他のコンテナ設定 ...
tolerations:
- key: "maintenance"
operator: "Exists" # 値がないTaintの場合、Existsオペレーターを使うのが一般的
effect: "NoExecute"
tolerationSeconds: 60 # NoExecute Taintが付与されてから60秒間は排除されない

例:任意のエフェクトを持つキー special-node のTaint全てを許容するPodのYAML定義

yaml
apiVersion: v1
kind: Pod
metadata:
name: flexible-pod
spec:
containers:
- name: main-container
image: my-flexible-image
# ... その他のコンテナ設定 ...
tolerations:
- key: "special-node"
operator: "Exists" # キーが special-node であれば、値、エフェクトに関わらず許容
# effect: を省略することで、全ての効果 (NoSchedule, PreferNoSchedule, NoExecute) を許容

9. 実践的な手順例

TaintとTolerationを使ってPodの配置を制御する具体的な手順を追ってみましょう。

例1: GPUノードの分離とGPUを使うPodの配置

シナリオ: クラスター内にGPUを持つノードがいくつかあり、それらのノードをGPUワークロード専用にしたい。それ以外のPodはGPUノードに配置したくない。

手順:

  1. GPUノードを特定し、Taintを設定する:
    まず、GPUを持つノードの名前を確認します。ここでは gpu-node-1 という名前だと仮定します。
    このノードに、gpu=nvidia:NoSchedule というTaintを設定します。(実際のGPUの種類やメーカーに合わせてキーや値を調整してください。)

    bash
    kubectl taint nodes gpu-node-1 gpu=nvidia:NoSchedule

    これで、Tolerationを持たない新しいPodは gpu-node-1 にはスケジュールされなくなります。既にこのノードで動いているPodは影響を受けません。

  2. GPUを使うアプリケーションPodの定義にTolerationを追加する:
    GPUを使って計算を行うアプリケーションのPod定義YAMLファイル(例: gpu-app-pod.yaml)を作成または編集します。spec.tolerations フィールドに、ノードに設定したTaintに対応するTolerationを追加します。

    yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: my-gpu-workload
    spec:
    containers:
    - name: gpu-container
    image: my-gpu-docker-image # GPUドライバなどが含まれるイメージ
    # ... その他の設定(リソース要求など) ...
    resources:
    limits:
    nvidia.com/gpu: 1 # 1つのGPUを使用する場合のリソース要求例
    tolerations:
    - key: "gpu"
    operator: "Equal"
    value: "nvidia"
    effect: "NoSchedule"
    # オプション:確実にGPUノードに配置したい場合は Node Affinity も併用
    affinity:
    nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchExpressions:
    - key: "gpu"
    operator: "In"
    values: ["nvidia"] # ノードのラベルも利用する

    注: この例ではNode Affinityも追加しています。Tolerationはあくまで「Taintがあっても配置を妨げられない」という設定であり、必ずしもそのノードを選ぶわけではありません。Node Affinityと組み合わせることで、「GPUラベルが付いたノードで、かつGPU Taintがあっても構わない」という強い意図を表現し、GPUノードへの配置を確実にします。GPUノードに適切なラベル(例: gpu=nvidia)が付与されている必要があります。

  3. Podをデプロイする:
    定義したPod(またはそのDeploymentなど)をクラスターに適用します。

    bash
    kubectl apply -f gpu-app-pod.yaml

    スケジューラーは、gpu-node-1 が持つ gpu=nvidia:NoSchedule Taintをチェックしますが、このPodが対応するTolerationを持っているため、そのTaintによる除外は行いません。他のスケジューリング条件(リソース、Node Affinityなど)も満たされれば、gpu-node-1 にPodがスケジュールされる可能性が高くなります。

  4. Tolerationを持たないPodの動作確認:
    Tolerationを持たない、普通のNginx Podなどをデプロイしてみてください。

    yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: normal-nginx-pod
    spec:
    containers:
    - name: nginx
    image: nginx

    このPodは gpu=nvidia:NoSchedule Taintを許容するTolerationを持たないため、スケジューラーは gpu-node-1 をこのPodの配置先候補から除外します。PodはGPUノード以外の利用可能なノードにスケジュールされるか、他のノードがない場合はPending状態になります。

例2: ノードをメンテナンスのために隔離する (NoExecute エフェクトの使用)

シナリオ: worker-node-2 というノードで、OSのカーネルアップデートなど、実行中のPodを全て退避させてから行いたいメンテナンス作業がある。メンテナンス中は新しいPodも来ないようにしたい。

手順:

  1. ノードに NoExecute Taintを設定する:
    worker-node-2maintenance:NoExecute というTaintを設定します。値は不要なので省略します。

    bash
    kubectl taint nodes worker-node-2 maintenance:NoExecute

    このコマンドを実行すると、以下の動作が起こります。
    * 新しいPod: Tolerationを持たない新しいPodは worker-node-2 にスケジュールされなくなります。
    * 既に実行中のPod: worker-node-2 上で既に実行されているPodのうち、maintenance:NoExecute Taintを許容するTolerationを持たないものは、即座に強制的に排除(Evict)されます。これらのPodはReplicaSetなどによって別のノードで自動的に再作成されます。

  2. (オプション)特定のPodだけノードに残したい場合:
    例えば、DaemonSetでデプロイされたログ収集エージェントなど、そのノード上で常に実行されていてほしいPodがあるかもしれません。これらのPodは、ノードのメンテナンス中であっても、メンテナンス自体に支障がなければ実行し続けたい場合があります。
    そのようなPodの定義に、maintenance:NoExecute Taintを許容するTolerationを追加します。猶予時間は不要な場合が多いですが、必要なら tolerationSeconds を指定します。

    “`yaml
    apiVersion: apps/v1
    kind: DaemonSet

    … metadata …

    spec:
    # … selector, template …
    template:
    # … metadata …
    spec:
    containers:
    – name: log-agent
    image: my-log-agent-image
    # … その他の設定 …
    tolerations:
    – key: “maintenance”
    operator: “Exists”
    effect: “NoExecute”
    # tolerationSeconds は省略(ノードからの排除は望まないため)
    ``
    DaemonSetは通常、特定のTaintを許容するデフォルトのTolerationを持っていることがありますが、明示的に設定することも可能です。このTolerationを持つPodは、ノードに
    maintenance:NoExecute` Taintが付与されても排除されず、そのまま実行を継続します。

  3. メンテナンス作業の実施:
    Podが退避されたことを確認してから、安心して worker-node-2 でメンテナンス作業を実施します。

  4. メンテナンス完了後、Taintを削除する:
    メンテナンスが完了し、ノードが正常な状態に戻ったら、設定したTaintを削除します。

    bash
    kubectl taint nodes worker-node-2 maintenance:NoExecute-

    Taintが削除されると、スケジューラーは再び worker-node-2 を新しいPodの配置先候補として考慮するようになります。また、メンテナンス中にPending状態になっていた、このノードに配置可能なPodがあれば、スケジュールされる可能性があります。

10. TaintとTolerationの注意点とベストプラクティス

TaintとTolerationは強力な機能ですが、誤って使用すると予期しない挙動を引き起こす可能性もあります。以下の点に注意しましょう。

  • Tolerationは「許可」であり「強制」ではない: Tolerationは、あくまで「そのTaintがあるノードに配置されても構わない」という許可を与えるだけです。Tolerationを持つからといって、必ずそのTaintを持つノードにスケジュールされるわけではありません。スケジューラーはリソース要求、Node Affinity、ノードの負荷など、他の多くの要素も考慮して最適なノードを決定します。特定のノードグループにPodを確実に配置したい場合は、Tolerationと合わせて Node Affinity や Node Selector を使用することを強く推奨します(例1のGPUノードのように)。
  • 他のPod配置制御メカニズムとの比較と組み合わせ:
    • Node Selector: 最もシンプル。「このラベルを持つノードにだけ配置」。Taintと異なり、Pod側の必須条件としてノードを選択します。Taint/Tolerationは「このTaintがあるノードを避ける」というノード側の制約と、それに対するPod側の許容です。Node Selectorは「ポジティブな選択」、Taint/Tolerationは「ネガティブな制約と例外」と考えると分かりやすいかもしれません。
    • Node Affinity/Anti-Affinity: Node Selectorよりも柔軟。「このラベルを持つノードを優先する(または避ける)」「必須条件としてこのラベルを持つノードに配置(または避ける)」。これはPod側の希望や要件を表現する仕組みです。Taint/Tolerationはノード側の意向を表現し、Podがそれにどう対応するかをTolerationで示す仕組みです。ユースケースによっては、Taint/TolerationとNode Affinity/Anti-Affinityを組み合わせて使用することで、よりきめ細かい制御が可能になります(例1のように、Taintでノードへの配置を制限し、Node Affinityで特定のPodをそのノードグループへ誘導する)。
  • Taintの設定は計画的に: 特に NoExecute エフェクトは、実行中のPodに影響を与えます。運用中のノードに突然 NoExecute Taintを設定すると、多くのPodが意図せず再起動する可能性があります。事前にTaintの影響を理解し、必要であれば猶予時間(tolerationSeconds)を持つTolerationを設定しておく、あるいは段階的に適用するといった計画が必要です。
  • デフォルトのTaintの理解: マスターノードなどにデフォルトで付与されているTaintの存在を認識しておきましょう。もしシステムPod以外のPodをマスターノードで実行したい場合は、そのTaintを許容するTolerationを明示的に設定する必要があります(これは通常推奨されませんが、特定の運用シナリオでは必要になることもあります)。
  • 複数のTaint/Toleration: ノードが複数のTaintを持つ場合、Podはその全ての NoSchedule/NoExecute Taintを許容するTolerationを持っていなければスケジュールされません。Podが複数のTolerationを持つ場合、ノードのTaintリストと比較して、いずれかのTolerationが各TaintとマッチすればOKです。このマッチングルールを正確に理解することが重要です。
  • tolerationSeconds の適切な設定: NoExecute Taintによる排除は、ノードが不安定になった際にPodを退避させる重要な機能です。tolerationSeconds を適切に設定することで、一時的なネットワーク瞬断などですぐにPodが排除されるのを防ぎつつ、ノードの障害が継続する場合は Podを退避させるといった柔軟な対応が可能になります。サービスの特性に合わせて適切な値を設定しましょう。

11. まとめ

この記事では、KubernetesのTaintとTolerationについて、初心者向けに詳しく解説しました。

  • Taint はノードに「汚点」をつけ、特定のPodがそのノードにスケジュールされるのを避けるための仕組みです。キー、値、そして NoSchedule, PreferNoSchedule, NoExecute の3種類のエフェクトを持ちます。
  • Toleration はPodに設定し、ノードのTaintを「許容」するための仕組みです。Taintのキー、値、エフェクトに対応する形で設定し、tolerationSecondsNoExecute による排除の猶予時間を設定できます。
  • TaintとTolerationはセットで機能します。Podがノードの NoSchedule または NoExecute Taintを許容するTolerationを持っていなければ、そのノードにはスケジュールされません。NoExecute Taintは、実行中のPodも排除する可能性があります。
  • これらの仕組みを使うことで、GPUノードの分離、問題ノードの隔離、マスターノードの保護、ライセンス管理、物理的な場所によるPod配置制御など、様々な高度なPod配置要件を実現できます。
  • Taintは kubectl taint コマンドで設定・削除し、TolerationはPodのYAML定義に記述します。
  • Tolerationは「許可」であり「強制」ではないため、特定のノードグループへの確実な配置には Node Affinity などを組み合わせることが推奨されます。

TaintとTolerationは、Kubernetesクラスターをより柔軟かつ効率的に運用するために不可欠な概念です。特に、異種混合ノード構成のクラスターや、特定のノード障害時にサービスへの影響を最小限に抑えたい場合にその真価を発揮します。

この記事が、Kubernetes初心者の方々がTaintとTolerationを理解し、自身のクラスター運用に役立てる一助となれば幸いです。実際に手を動かして、様々なTaintとTolerationの組み合わせを試してみることで、より深く理解できるでしょう。

12. 参考資料

これらの公式ドキュメントは、本記事よりもさらに詳細な情報や最新の情報を提供しています。理解を深めるために、ぜひ参照してみてください。

コメントする

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

上部へスクロール