Podトポロジー分散制約
トポロジー分散制約 を使用するとリージョン、ゾーン、ノードおよびその他のユーザー定義されたトポロジー領域などの障害ドメインをまたがって、クラスター内のPodがどのように分散されるかを制御できます。 これにより、高可用性と効率的なリソース活用を実現できます。
クラスターレベルの制約をデフォルトとして設定するか、個々のワークロードに対してトポロジー分散制約を設定できます。
動機
最大20ノードのクラスターがあり、使用するレプリカ数を自動的にスケーリングするワークロードを実行したいとします。 このワークロードには、2つのPodが存在する場合もあれば、15のPodが存在する場合もあります。 Podが2つだけの場合、単一ノードの障害でワークロードがオフラインになるリスクがあるため、同じノードで両方のPodを実行したくありません。
この基本的な使用方法に加えて、高可用性とクラスターの利用率を向上させるための高度な使用方法があります。
スケールアップしてより多くのPodを実行すると、また別の懸念事項が重要になります。 5つのPodを実行する3つのノードがあるとします。 ノードは該当数のレプリカを実行するために十分なキャパシティを持っていますが、このワークロードとやり取りするクライアントは3つの異なるデータセンター(またはインフラストラクチャゾーン)に分散されています。 単一ノードの障害についての懸念は減りましたが、レイテンシーが予想よりも高く、異なるゾーン間でネットワークトラフィックを送信する際にネットワークコストがかかっていることに気づきます。
通常の運用では、各インフラストラクチャゾーンに同数のレプリカをスケジュールし、問題が発生した場合はクラスターが自己修復するようにしたいと考えるでしょう。
Podトポロジー分散制約は、このようなシナリオに対処するための手段を提供します。
topologySpreadConstraints
フィールド
Pod APIには、spec.topologySpreadConstraints
フィールドが含まれています。
このフィールドの使用方法は次のようになります:
---
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# トポロジー分散制約を設定
topologySpreadConstraints:
- maxSkew: <integer>
minDomains: <integer> # オプション
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector: <object>
matchLabelKeys: <list> # オプション; v1.27以降ベータ
nodeAffinityPolicy: [Honor|Ignore] # オプション; v1.26以降ベータ
nodeTaintsPolicy: [Honor|Ignore] # オプション; v1.26以降ベータ
### 他のPodのフィールドはここにあります
kubectl explain Pod.spec.topologySpreadConstraints
を実行するか、PodのAPIリファレンスのschedulingセクションを参照して、このフィールドについて詳しく読むことができます。
分散制約の定義
クラスター全体の既存のPodに対して、新しいPodをどのように配置するかをkube-schedulerに指示するために、1つまたは複数のtopologySpreadConstraints
エントリを定義できます。
これらのフィールドは次の通りです:
maxSkewは、Podが不均等に分散される程度を表します。 このフィールドは必須であり、0より大きい数値を指定する必要があります。 このフィールドのセマンティクスは、
whenUnsatisfiable
の値によって異なります:whenUnsatisfiable: DoNotSchedule
を選択した場合、maxSkew
は対象トポロジー内の一致するPodの数と、グローバル最小値(適格ドメイン内の一致するPodの最小数、または適格ドメインの数がMinDomainsより少ない場合は0)の間で許容される最大の差を定義します。 例えば、3つのゾーンがあり、それぞれ2、2、1つの一致するPodがありMaxSkew
が1に設定されている場合、グローバル最小値は1です。whenUnsatisfiable: ScheduleAnyway
を選択した場合、スケジューラーはスキューの削減に役立つトポロジーを優先します。
minDomainsは、適格ドメインの最小数を示します。 このフィールドはオプションです。 ドメインとは、特定のトポロジーのインスタンスを指します。 適格ドメインとは、ノードセレクターに一致するノードのドメインを指します。
備考:
Kubernetes v1.30以前では、minDomains
フィールドは、MinDomainsInPodTopologySpread
フィーチャーゲートが有効になっている場合のみ利用可能でした(v1.28以降はデフォルト)。 より古いKubernetesクラスターでは、明示的に無効になっているか、フィールドが利用できない場合があります。minDomains
の値を指定する場合、その値は0より大きくする必要があります。minDomains
は、whenUnsatisfiable: DoNotSchedule
と組み合わせてのみ指定できます。- トポロジーキーに一致する適格ドメインの数が
minDomains
より少ない場合、Podトポロジー分散はグローバル最小値を0として扱い、skew
の計算を行います。 グローバル最小値は、適格ドメイン内の一致するPodの最小数であり、適格ドメインの数がminDomains
より少ない場合は0になります。 - トポロジーキーに一致する適格ドメインの数が
minDomains
と等しいかそれ以上の場合、この値はスケジューリングに影響しません。 minDomains
を指定しない場合、制約はminDomains
が1であるかのように動作します。
topologyKeyはノードラベルのキーです。 このキーと同じ値を持つノードは、同じトポロジー内にあると見なされます。 トポロジー内の各インスタンス(つまり、<key, value>ペア)をドメインと呼びます。 スケジューラーは、各ドメインに均等な数のPodを配置しようとします。 また、適格ドメインはnodeAffinityPolicyとnodeTaintsPolicyの要件を満たすノードのドメインとして定義します。
whenUnsatisfiableは、Podが分散制約を満たさない場合の処理方法を示します:
DoNotSchedule
(デフォルト)は、スケジューラーにスケジュールしないように指示します。ScheduleAnyway
は、スケジューラーにスキューを最小化するノードを優先してスケジュールするよう指示します。
labelSelectorは、一致するPodを見つけるために使用されます。 このラベルセレクターに一致するPodは、対応するトポロジードメイン内のPodの数を決定するためにカウントされます。 詳細については、ラベルセレクターを参照してください。
matchLabelKeysは、分散を計算するPodを選択するためのPodラベルキーのリストです。 このキーは、Podラベルから値を検索するために使用され、これらのkey-valueラベルは
labelSelector
とAND演算され、新しいPodのために分散が計算される既存のPodのグループを選択します。 同じキーはmatchLabelKeys
とlabelSelector
の両方に存在してはいけません。labelSelector
が設定されていない場合は、matchLabelKeys
を設定することはできません。 Podラベルに存在しないキーは無視されます。 nullまたは空のリストは、labelSelector
に対してのみ一致します。matchLabelKeys
を使用すると、異なるリビジョン間でpod.spec
を更新する必要がありません。 コントローラー/オペレーターは、異なるリビジョンに対して同じラベルキーを異なる値に設定するだけです。 スケジューラーは、matchLabelKeys
に基づいて値を自動的に推定します。 例えばDeploymentを構成する場合、Deploymentコントローラーによって自動的に追加されるpod-template-hashをキーとするラベルを使用して、単一のDeployment内の異なるリビジョンを区別できます。topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: foo matchLabelKeys: - pod-template-hash
備考:
matchLabelKeys
フィールドは、ベータレベルのフィールドであり、1.27でデフォルトで有効になっています。MatchLabelKeysInPodTopologySpread
フィーチャーゲートを無効にすることで無効にできます。nodeAffinityPolicyは、Podのトポロジー分散スキューを計算する際に、PodのnodeAffinity/nodeSelectorをどのように扱うかを示します。 オプションは次の通りです:
- Honor: nodeAffinity/nodeSelectorに一致するノードのみが計算に含まれます。
- Ignore: nodeAffinity/nodeSelectorは無視されます。 すべてのノードが計算に含まれます。
この値がnullの場合、動作はHonorポリシーと同等です。
備考:
nodeAffinityPolicy
はベータレベルのフィールドであり、1.26でデフォルトで有効になっています。NodeInclusionPolicyInPodTopologySpread
フィーチャーゲートを無効にすることで無効にできます。nodeTaintsPolicyは、Podのトポロジー分散スキューを計算する際に、ノードのtaintをどのように扱うかを示します。 オプションは次の通りです:
- Honor: taintのないノード、新しいPodがtolerationを持つtaintされたノードが含まれます。
- Ignore: ノードのtaintは無視されます。 すべてのノードが含まれます。
この値がnullの場合、動作はIgnoreポリシーと同等です。
備考:
nodeTaintsPolicy
はベータレベルのフィールドであり、1.26でデフォルトで有効になっています。NodeInclusionPolicyInPodTopologySpread
フィーチャーゲートを無効にすることで無効にできます。
PodにtopologySpreadConstraints
が複数定義されている場合、これらの制約は論理AND演算を使用して結合され、kube-schedulerは新しいPodに対して構成された制約をすべて満たすノードを探します。
ノードラベル
トポロジー分散制約は、ノードラベルを使用して、各ノードがどのトポロジードメインに属するかを識別します。 例えば、ノードには次のようなラベルがあるかもしれません:
region: us-east-1
zone: us-east-1a
備考:
簡潔にするため、この例ではwell-knownラベルキーのtopology.kubernetes.io/zone
とtopology.kubernetes.io/region
は使用していません。
ただし、ここで使用されているregion
およびzone
といったプライベート(修飾されていない)ラベルキーよりも、これらの登録済みラベルキーが推奨されます。
異なるコンテキスト間でのプライベートラベルキーの意味について、信頼できる前提として扱うことはできません。
次のラベルを持つ4ノードのクラスターがあるとします:
NAME STATUS ROLES AGE VERSION LABELS
node1 Ready <none> 4m26s v1.16.0 node=node1,zone=zoneA
node2 Ready <none> 3m58s v1.16.0 node=node2,zone=zoneA
node3 Ready <none> 3m17s v1.16.0 node=node3,zone=zoneB
node4 Ready <none> 2m43s v1.16.0 node=node4,zone=zoneB
クラスターは論理的には以下のように表されます:
一貫性
グループ内のすべてのPodに同じトポロジー分散制約を適用する必要があります。
通常、Deploymentなどのワークロードコントローラーを使用している場合、Podテンプレートがこれを自動的に処理します。 異なる分散制約を混在させると、KubernetesはフィールドのAPI定義に従いますが、その動作は混乱しやすくなりトラブルシューティングがより困難になる可能性があります。
トポロジードメイン(例えばクラウドプロバイダーのリージョン)内のすべてのノードに、一貫してラベルが付与されていることを保証するメカニズムが必要です。
手動でノードにラベルを付与する必要がないように、多くのクラスターはkubernetes.io/hostname
のようなwell-knownラベルを自動的に設定します。
ご自身のクラスターがこれをサポートしているかどうかを確認してください。
トポロジー分散制約の例
例: 単一のトポロジー分散制約
4ノードのクラスターがあり、foo: bar
というラベルの付いた3つのPodがそれぞれ node1、node2、node3に配置されているとします:
新しいPodを既存のPodとゾーン全体に均等に分散したい場合は、次のようなマニフェストを使用できます:
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
このマニフェストでは、topologyKey: zone
はzone: <任意の値>
というラベルが付いたノードにのみ均等に分散が適用されることを意味します(ラベルzone
がないノードはスキップされます)。
whenUnsatisfiable: DoNotSchedule
フィールドは、スケジューラーが制約を満たせない場合に、新しいPodを保留状態にするようにスケジューラーに指示します。
スケジューラーがこの新しいPodをゾーンA
に配置した場合、Podの分布は[3, 1]
になります。
これは実際のスキューが2(3 - 1
として計算)であることを意味し、maxSkew: 1
に違反します。
この例の制約とコンテキストを満たすためには、新しいPodはゾーンB
のノードにのみ配置される必要があります:
または
Podの仕様を調整することで、様々な要件に対応できます:
maxSkew
をより大きい値(例えば2
)に変更すると、新しいPodをゾーンA
に配置できるようになります。topologyKey
をnode
に変更すると、ゾーン単位ではなくノード単位でPodを均等に分散させるようになります。 上記の例では、maxSkew
が1
のままである場合、新しいPodはnode4
にのみ配置可能です。whenUnsatisfiable: DoNotSchedule
をwhenUnsatisfiable: ScheduleAnyway
に変更すると、新しいPodが常にスケジュール可能になります(他のスケジュールAPIが満たされていると仮定)。 ただし、一致するPodが少ないトポロジードメインに配置されることが好ましいです。 (この設定は、リソース使用率などの他の内部スケジューリング優先度と共に正規化されることに注意してください)。
例: 複数のトポロジー分散制約
これは前の例に基づいています。
4ノードのクラスターがあり、foo: bar
というラベルの付いた3つのPodがそれぞれ node1、node2、node3に配置されているとします:
2つのトポロジー分散制約を組み合わせて、ノードとゾーンの両方でPodの分散を制御できます:
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
- maxSkew: 1
topologyKey: node
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
この場合、最初の制約に一致させるために、新しいPodはゾーンB
のノードにのみ配置できます。
一方、2番目の制約に関しては、新しいPodはnode4
にのみスケジュールできます。
スケジューラーは、定義されたすべての制約を満たすオプションのみを考慮するため、有効な配置はnode4
のみです。
例: トポロジー分散制約の競合
複数の制約は競合する可能性があります。 2つのゾーンにまたがる3ノードのクラスターがあるとします:
このクラスターにtwo-constraints.yaml
(前の例のマニフェスト)を適用すると、Pod mypod
がPending
状態のままであることがわかります。
これは、最初の制約を満たすために、Pod mypod
はゾーンB
にしか配置できないのに対して、2番目の制約に関しては、Pod mypod
はノードnode2
にしかスケジュールできないために発生します。
2つの制約の共通部分として空集合が返され、スケジューラーはPodを配置できません。
この状況を克服するためには、maxSkew
の値を増やすか、制約の1つをwhenUnsatisfiable: ScheduleAnyway
に変更します。
状況によっては、バグ修正のロールアウトが進まない理由をトラブルシューティングする場合などで、既存のPodを手動で削除することもあります。
ノードアフィニティとノードセレクターとの相互作用
新しいPodにspec.nodeSelector
またはspec.affinity.nodeAffinity
が定義されている場合、スケジューラーは一致しないノードをスキュー計算からスキップします。
例: ノードアフィニティを使用したトポロジー分散制約
ゾーンAからCにまたがる5ノードクラスターがあるとします:
そして、ゾーンC
を除外する必要があることがわかっているとします。
この場合、以下のようにマニフェストを作成して、Pod mypod
をゾーンC
ではなくゾーンB
に配置することができます。
同様に、Kubernetesはspec.nodeSelector
を尊重します。
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: zone
operator: NotIn
values:
- zoneC
containers:
- name: pause
image: registry.k8s.io/pause:3.1
暗黙的な規則
ここで注目すべき暗黙的な規則がいくつかあります:
新しいPodと同じNamespaceを持つPodのみが一致する候補となります。
スケジューラーは、すべての
topologySpreadConstraints[*].topologyKey
が同時に存在するノードのみを考慮します。 これらのtopologyKeys
のいずれかが欠落しているノードはバイパスされます。 これは次のことを意味します:- これらのバイパスされたノードにあるPodは
maxSkew
の計算に影響しません。 上記の例では、ノードnode1
に"zone"ラベルがない場合、2つのPodは無視され、新しいPodはゾーンA
にスケジュールされます。 - 新しいPodがこれらのノードにスケジュールされる可能性はありません。
上記の例では、ノード
node5
に誤字のあるラベルzone-typo: zoneC
がある(かつzone
ラベルが設定されていない)とします。node5
がクラスターに参加しても、バイパスされ、このワークロードのPodはそのノードにスケジュールされません。
- これらのバイパスされたノードにあるPodは
新しいPodの
topologySpreadConstraints[*].labelSelector
が自身のラベルと一致しない場合に何が起こるかに注意してください。 上記の例では、新しいPodのラベルを削除しても、すでに制約が満たされているため、ゾーンB
のノードに配置できます。 ただしその配置後、クラスターの不均衡の度合いは変わらず、ゾーンA
にはfoo: bar
というラベルが付いた2つのPodがあり、ゾーンB
にはfoo: bar
というラベルが付いた1つのPodがあります。 これが期待する動作ではない場合、ワークロードのtopologySpreadConstraints[*].labelSelector
を更新して、Podテンプレート内のラベルと一致するようにします。
クラスターレベルのデフォルト制約
クラスターにはデフォルトのトポロジー分散制約を設定することができます。 デフォルトのトポロジー分散制約は、次の場合にのみPodに適用されます:
.spec.topologySpreadConstraints
に制約が定義されていない。- PodがService、ReplicaSet、StatefulSet、またはReplicationControllerに属している。
デフォルトの制約は、スケジューリングプロファイルのPodTopologySpread
プラグイン引数の一部として設定できます。
制約は、上記のAPIと同じように指定されますが、labelSelector
は空である必要があります。
Podが属するService、ReplicaSet、StatefulSet、またはReplicationControllerから計算されたセレクターが使用されます。
設定例は次のようになります:
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: PodTopologySpread
args:
defaultConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
defaultingType: List
ビルトインのデフォルト制約
Kubernetes v1.24 [stable]
Podトポロジー分散のためのクラスターレベルのデフォルト制約を構成しない場合、kube-schedulerは次のデフォルトのトポロジー制約を指定したかのように動作します:
defaultConstraints:
- maxSkew: 3
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway
- maxSkew: 5
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: ScheduleAnyway
また、同等の動作を提供する従来のSelectorSpread
プラグインは、デフォルトで無効になっています。
備考:
PodTopologySpread
プラグインは、分散制約で指定されていないトポロジーキーを持つノードにスコアをつけません。
これにより、デフォルトのトポロジー制約を使用する場合、従来のSelectorSpread
プラグインと比較して、デフォルトの動作が異なる場合があります。
ノードにkubernetes.io/hostname
とtopology.kubernetes.io/zone
ラベルの両方が設定されることを想定していない場合、Kubernetesのデフォルトを使用する代わりに独自の制約を定義してください。
クラスターにデフォルトのPod分散制約を使用したくない場合は、PodTopologySpread
プラグイン構成のdefaultingType
をList
に設定し、defaultConstraints
を空のままにすることで、これらのデフォルトを無効にできます:
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: PodTopologySpread
args:
defaultConstraints: []
defaultingType: List
podAffinityとpodAntiAffinityとの比較
Kubernetesでは、Pod間のアフィニティとアンチアフィニティによって、Podがより密集させるか分散させるかといった、Podが互いにどのようにスケジュールされるかを制御できます。
podAffinity
- Podを引き付けます。 適切なトポロジードメインに任意の数のPodを配置できます。
podAntiAffinity
- Podを遠ざけます。
requiredDuringSchedulingIgnoredDuringExecution
モードに設定すると、単一のトポロジードメインには1つのPodしかスケジュールできません。preferredDuringSchedulingIgnoredDuringExecution
モードに設定すると、この制約を強制できません。
より細かい制御を行うには、トポロジー分散制約を指定して、異なるトポロジードメインにPodを分散させることで、高可用性やコスト削減を実現できます。 またワークロードのローリングアップデートやレプリカのスムーズなスケールアウトにも役立ちます。
詳しくは、Podのトポロジー分散制約に関する機能強化提案のMotivationセクションを参照してください。
既知の制限
Podの削除後も、制約が満たされていることは保証されません。 例えば、Deploymentのスケールダウンによって、Podの分布が不均衡になる可能性があります。
Podの分布をリバランスするには、Deschedulerなどのツールを使用できます。
taintされたノードに一致するPodは優先されます。 Issue 80921を参照してください。
スケジューラーは、クラスター内のすべてのゾーンやその他のトポロジードメインを事前に把握しているわけではありません。 これらはクラスター内の既存のノードから決定されます。 オートスケールされるクラスターでは、ノードプール(またはノードグループ)が0ノードにスケールされ、クラスターがスケールアップすることを期待している場合に問題が発生する可能性があります。 この場合、トポロジードメインはその中に少なくとも1つのノードが存在するまで考慮されないためです。
これを回避するためには、Podのトポロジー分散制約を認識し、全体のトポロジードメインの集合も認識しているノードオートスケーラーを使用することができます。
次の項目
- ブログ記事Introducing PodTopologySpreadでは、いくつかの高度な使用例を含め、
maxSkew
について詳しく説明しています。 - PodのAPIリファレンスのschedulingセクションを読んでください。