ノード上へのPodのスケジューリング
Podを特定のノードで実行するように 制限 したり、特定のノードで実行することを 優先 させたりといった制約をかけることができます。 これを実現するためにはいくつかの方法がありますが、推奨されている方法は、すべてラベルセレクターを使用して選択を容易にすることです。 多くの場合、このような制約を設定する必要はなく、スケジューラーが自動的に妥当な配置を行います(例えば、Podを複数のノードに分散させ、空きリソースが十分でないノードにPodを配置しないようにすることができます)。 しかし、例えばSSDが接続されているノードにPodが配置されるようにしたり、多くの通信を行う2つの異なるサービスのPodを同じアベイラビリティーゾーンに配置したりする等、どのノードに配置するかを制御したい状況もあります。
Kubernetesが特定のPodの配置場所を選択するために、以下の方法があります:
- ノードラベルに対してマッチングを行うnodeSelectorフィールド
- アフィニティとアンチアフィニティ
- nodeNameフィールド
- Podのトポロジー分散制約
ノードラベル
他の多くのKubernetesオブジェクトと同様に、ノードにもラベルがあります。手動でラベルを付けることができます。 また、Kubernetesはクラスター内のすべてのノードに対し、いくつかの標準ラベルを付けます。ノードラベルの一覧についてはよく使われるラベル、アノテーションとtaintを参照してください。
備考:
これらのラベルの値はクラウドプロバイダー固有のもので、信頼性を保証できません。 例えば、kubernetes.io/hostname
の値はある環境ではノード名と同じになり、他の環境では異なる値になることがあります。ノードの分離/制限
ノードにラベルを追加することで、Podを特定のノードまたはノードグループ上でのスケジューリングの対象にすることができます。この機能を使用すると、特定のPodが一定の独立性、安全性、または規制といった属性を持ったノード上でのみ実行されるようにすることができます。
ノード分離するのにラベルを使用する場合、kubeletが修正できないラベルキーを選択してください。 これにより、侵害されたノードが自身でそれらのラベルを設定することで、スケジューラーがそのノードにワークロードをスケジュールしてしまうのを防ぐことができます。
NodeRestriction
アドミッションプラグインは、kubeletがnode-restriction.kubernetes.io/
というプレフィックスを持つラベルを設定または変更するのを防ぎます。
ラベルプレフィックスをNode分離に利用するには:
- ノード認可を使用していることと、
NodeRestriction
アドミッションプラグインが 有効 になっていることを確認します。 node-restriction.kubernetes.io/
プレフィックスを持つラベルをノードに追加し、 nodeSelectorでそれらのラベルを使用します。 例えば、example.com.node-restriction.kubernetes.io/fips=true
やexample.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をスケジュールします。
備考:
上記の2種類にあるIgnoredDuringExecution
は、KubernetesがPodをスケジュールした後にノードラベルが変更されても、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がルールを解釈する際に使用できる論理演算子を指定することができます。In
、NotIn
、Exists
、DoesNotExist
、Gt
、Lt
が使用できます。
NotIn
とDoesNotExist
を使って、ノードのアンチアフィニティ動作を定義することができます。また、ノードのTaintを使用して、特定のノードからPodをはじくこともできます。
備考:
nodeSelector
とnodeAffinity
の両方を指定した場合、両方の条件を満たさないとPodはノードにスケジュールされません。
nodeAffinity
タイプに関連付けられたnodeSelectorTerms
内に、複数の条件を指定した場合、Podは指定した条件のいずれかを満たしたノードへスケジュールされます(条件はORされます)。
nodeSelectorTerms
内の条件に関連付けられた1つのmatchExpressions
フィールド内に、複数の条件を指定した場合、Podは全ての条件を満たしたノードへスケジュールされます(条件はANDされます)。
詳細についてはノードアフィニティを利用して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をスケジューリングします。
備考:
この例でKubernetesにPodを正常にスケジュールさせるには、kubernetes.io/os=linux
ラベルを持つ既存のノードが必要です。スケジューリングプロファイルごとのノードアフィニティ
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.schedulerName
をfoo-scheduler
に設定したすべてのPodに適用されます。つまり、Podにマッチするためには、ノードはaddedAffinity
とPodの.spec.NodeAffinity
を満たす必要があるのです。
addedAffinity
はエンドユーザーには見えないので、その動作はエンドユーザーにとって予期しないものになる可能性があります。スケジューラープロファイル名と明確な相関関係のあるノードラベルを使用すべきです。
備考:
DaemonSetのPodを作成するDaemonSetコントローラーは、スケジューリングプロファイルをサポートしていません。DaemonSetコントローラーがPodを作成すると、デフォルトのKubernetesスケジューラーがそれらのPodを配置し、DaemonSetコントローラーのnodeAffinity
ルールに優先して従います。Pod間のアフィニティとアンチアフィニティ
Pod間のアフィニティとアンチアフィニティは、ノードのラベルではなく、すでにノード上で稼働しているPodのラベルに従って、Podがどのノードにスケジュールされるかを制限できます。
Xはノードや、ラック、クラウドプロバイダーのゾーンやリージョン等を表すトポロジードメインで、YはKubernetesが満たそうとするルールである場合、Pod間のアフィニティとアンチアフィニティのルールは、"XにてルールYを満たすPodがすでに稼働している場合、このPodもXで実行すべき(アンチアフィニティの場合はすべきではない)"という形式です。
これらのルール(Y)は、オプションの関連する名前空間のリストを持つラベルセレクターで表現されます。PodはKubernetesの名前空間オブジェクトであるため、Podラベルも暗黙的に名前空間を持ちます。Kubernetesが指定された名前空間でラベルを探すため、Podラベルのラベルセレクターは、名前空間を指定する必要があります。
トポロジードメイン(X)はtopologyKey
で表現され、システムがドメインを示すために使用するノードラベルのキーになります。具体例はよく知られたラベル、アノテーションとTaintを参照してください。
備考:
Pod間アフィニティとアンチアフィニティはかなりの処理量を必要とするため、大規模クラスターでのスケジューリングが大幅に遅くなる可能性があります そのため、数百台以上のノードから成るクラスターでの使用は推奨されません。備考:
Podのアンチアフィニティは、ノードに必ず一貫性の持つラベルが付与されている必要があります。 言い換えると、クラスターの全てのノードが、topologyKey
に合致する適切なラベルが必要になります。
一部、または全部のノードにtopologyKey
ラベルが指定されていない場合、意図しない挙動に繋がる可能性があります。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
フィールドで使用できるのは、In
、NotIn
、 Exists
、 DoesNotExist
です。
原則として、topologyKey
には任意のラベルキーが指定できますが、パフォーマンスやセキュリティの観点から、以下の例外があります:
- Podアフィニティとアンチアフィニティでは、
requiredDuringSchedulingIgnoredDuringExecution
とpreferredDuringSchedulingIgnoredDuringExecution
内のどちらも、topologyKey
フィールドが空であることは許可されていません。 - Podアンチアフィニティルールの
requiredDuringSchedulingIgnoredDuringExecution
では、アドミッションコントローラーLimitPodHardAntiAffinityTopology
がtopologyKey
をkubernetes.io/hostname
に制限しています。アドミッションコントローラーを修正または無効化すると、トポロジーのカスタマイズができるようになります。
labelSelector
とtopologyKey
に加え、labelSelector
とtopologyKey
と同じレベルのnamespaces
フィールドを使用して、labelSelector
が合致すべき名前空間のリストを任意に指定することができます。省略または空の場合、namespaces
がデフォルトで、アフィニティとアンチアフィニティが定義されたPodの名前空間に設定されます。
名前空間セレクター
Kubernetes v1.24 [stable]
namespaceSelector
を使用し、ラベルで名前空間の集合に対して検索することによって、名前空間を選択することができます。
アフィニティ項はnamespaceSelector
とnamespaces
フィールドによって選択された名前空間に適用されます。
要注意なのは、空の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-1 | node-2 | node-3 |
---|---|---|
webserver-1 | webserver-2 | webserver-3 |
cache-1 | cache-2 | cache-3 |
全体的な効果として、各キャッシュインスタンスは、同じノード上で実行している単一のクライアントによってアクセスされる可能性が高いです。この方法は、スキュー(負荷の偏り)とレイテンシーの両方を最小化することを目的としています。
Podアンチアフィニティを使用する理由は他にもあります。 この例と同様の方法で、アンチアフィニティを用いて高可用性を実現したStatefulSetの使用例はZooKeeperチュートリアルを参照してください。
nodeName
nodeName
はアフィニティやnodeSelector
よりも直接的なノード選択形式になります。nodeName
はPod仕様(spec)内のフィールドです。nodeName
フィールドが空でない場合、スケジューラーはPodを考慮せずに、指定されたノードにあるkubeletがそのノードにPodを配置しようとします。nodeName
を使用すると、nodeSelector
やアフィニティおよびアンチアフィニティルールを使用するよりも優先されます。
nodeName
を使ってノードを選択する場合の制約は以下の通りです:
- 指定されたノードが存在しない場合、Podは実行されず、場合によっては自動的に削除されることがあります。
- 指定されたノードがPodを収容するためのリソースを持っていない場合、Podの起動は失敗し、OutOfmemoryやOutOfcpuなどの理由が表示されます。
- クラウド環境におけるノード名は、常に予測可能で安定したものではありません。
備考:
nodeName
は、カスタムスケジューラーや、設定済みのスケジューラーをバイパスする必要がある高度なユースケースで使用することを目的としています。
スケジューラーをバイパスすると、割り当てられたノードに過剰なPodの配置をしようとした場合には、Podの起動に失敗することがあります。
ノードアフィニティまたはnodeSelector
フィールドを使用すれば、スケジューラーをバイパスせずに、特定のノードにPodを割り当てることができます。以下は、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を分散させるかを制御することができます。これにより、パフォーマンス、予想される可用性、または全体的な使用率を向上させることができます。
詳しい仕組みについては、トポロジー分散制約を参照してください。
次の項目
- TaintとTolerationについてもっと読む。
- ノードアフィニティとPod間アフィニティ/アンチアフィニティのデザインドキュメントを読む。
- トポロジーマネージャーがノードレベルのリソース割り当ての決定にどのように関与しているかについて学ぶ。
- nodeSelectorの使用方法について学ぶ。
- アフィニティとアンチアフィニティの使用方法について学ぶ。