nodeSelector と Node Affinity の違い
v1.37 時点(2026年10月26日 確認)
目次
この記事では、ノードのラベルを手がかりに Pod を置くノードを選ぶ nodeSelector と Node Affinity の違いを、図で説明します。どちらも kube-scheduler にノードの条件を伝える書き方ですが、Node Affinity では「必ず満たす条件」と「できれば満たしたい条件」を分けて書け、演算子も選べます。条件に合うノードが無いときに何が起きるのか、spec.nodeName でノードを直接指定するのと何が違うのかもわかります。後半では kind のマルチノードのクラスタでワーカーのノードにラベルを付け、Pod を特定のノードに寄せたり避けたりする手順を示します。
第15回の DaemonSet では、Pod のひな形の nodeSelector や Node Affinity で、Pod を置く対象のノードを絞れることに触れました。この回はその続きです。まだの方は先にこちらをどうぞ。
以下の図は後半の「手元で試す」で 1 つ目のワーカーのノードにラベル disktype=ssd を付けて 4 つの Pod を作り、2 つ目のワーカーにラベルを足す前の状態を表しています。
nginx-selector は nodeSelector で、nginx-required は Node Affinity の required で、どちらも disktype=ssd のラベルを持つノードを求めています。nginx-avoid は逆に、disktype のラベルを持たないノードを求めています。nginx-required-hdd はどのノードも持っていない disktype=hdd のラベルを求めたので、置けるノードが無く、ノードが決まらないまま待っています。図の赤は、この Pod がこのままでは動き出せない状態を表しています。Pod を置くノードを選ぶのは kube-scheduler で、Pod に書いた条件はその選び方を縛るものです。コントロールプレーンのノードに Pod が無いのは、第15回で見た NoSchedule の Taint があるからです。手順ではもう 1 つ、preferred の Pod も作りますが、置かれるノードが決まりきらないので図には描いていません。
参考:Assigning Pods to Nodes | Kubernetes、Kubernetes Scheduler | Kubernetes、Taints and Tolerations | Kubernetes
nodeSelector と Node Affinity とは
公式ドキュメントによると、Pod を特定のノードだけで動くように制限したり、特定のノードで動くことを好むようにしたりできます。やり方はいくつかあり、勧められているやり方はどれもラベルセレクタでノードを選びます。公式ドキュメントが挙げるやり方は、次の 4 つです。
- ノードのラベルに合わせる
nodeSelectorのフィールド - アフィニティとアンチアフィニティ
nodeNameのフィールド- Pod Topology Spread Constraints(Pod の分散配置。第33回)
nodeSelector は Pod を置くノードに求めるラベルを並べる、いちばん簡単な書き方 です。公式ドキュメントは nodeSelector を、勧められているノードの選び方の制約のうち最も簡単な形、と説明しています。詳しくは「nodeSelector」の節で見ます。
Node Affinity(ノードアフィニティ)は nodeSelector と同じくノードのラベルで条件を書き、より多くの書き方ができる仕組み です。アフィニティ(affinity)は一般に、引き寄せ合う性質を表す英語です。公式ドキュメントによると、Node Affinity は nodeSelector のように働きますが、表現力が高く、ゆるい(soft)規則も書けます。
参考:Assigning Pods to Nodes | Kubernetes、Assigning Pods to Nodes | Kubernetes
公式ドキュメントは nodeSelector と比べたアフィニティとアンチアフィニティの利点として、次の 3 つを挙げています。
- 書き方の表現力が高い(nodeSelector は「このキーがこの値」のラベルを並べ、それをすべて持つノードを選ぶだけ)
- 規則を「ゆるい」「好み」にでき、合うノードが無くても Pod はスケジュールされる
- ノードのラベルだけでなく、そのノードで動いているほかの Pod のラベルでも制約を書ける
3 つ目は Pod 間のアフィニティとアンチアフィニティ(inter-pod affinity/anti-affinity)と呼ばれる、もう 1 種類のアフィニティです。公式ドキュメントによると、ノードのラベルではなく、ノードですでに動いている Pod のラベルをもとに、Pod を置けるノードを制限します。Pod を分散させたりまとめたりする第33回で扱い、この回では Node Affinity だけを扱います。
参考:Assigning Pods to Nodes | Kubernetes、Assigning Pods to Nodes | Kubernetes
この回で扱う 3 つのフィールドを、v1.37 時点の公式ドキュメントと API リファレンスに沿って次の表にまとめます。
| フィールド | 書くもの | ノードを選ぶもの |
|---|---|---|
spec.nodeSelector | ノードに求めるラベル(キーと値の組) | kube-scheduler |
spec.affinity.nodeAffinity | ノードのラベル、またはノードのフィールド(第15回の例ではノードの名前)の条件。必ず満たす条件と、できれば満たしたい条件を書き分ける | kube-scheduler |
spec.nodeName | ノードの名前 | kube-scheduler はこの Pod を無視する。名前を書いたノードの kubelet が、そのノードに Pod を置こうとする |
参考:Pod | Kubernetes API Reference、Assigning Pods to Nodes | Kubernetes
なぜ nodeSelector と Node Affinity が必要なのか
公式ドキュメントによると、多くの場合、こうした制約を設ける必要はありません。スケジューラーが自動でほどよい置き方をするからです。例として、空いている資源が足りないノードに Pod を置かないよう、Pod をノードに分散させることが挙がっています。第4回と第29回で見たとおり、kube-scheduler は Pod を置けるノードに絞り込み、残ったノードに点数を付けて選びます。
参考:Assigning Pods to Nodes | Kubernetes、Kubernetes Scheduler | Kubernetes
ただし公式ドキュメントは Pod を置くノードを制御したい場面もある、と続けています。挙がっている例は、SSD(読み書きの速い記憶装置)がつながったノードで Pod を動かしたい場合と、やり取りの多い 2 つのアプリケーションの Pod を同じアベイラビリティゾーンに置きたい場合です(原文は services で、第21回の Service のリソースに限った話ではないと読めます)。アベイラビリティゾーンは一般に、クラウドの中で電源やネットワークが分かれた区画のことです。
OS の違うノードが混ざったクラスタも、その一例です。公式ドキュメントの Pod のページは、動くノードの OS が 2 種類以上あるクラスタでは、各ノードに kubernetes.io/os のラベルを正しく付け、OS のラベルにもとづく nodeSelector を Pod に書くべき(should)だとしています。OS にもとづく nodeSelector を書かないと、kube-scheduler がコンテナに合わない OS のノードに Pod を割り当てることがある、という説明です。第26回の hostPath の例にあった nodeSelector も、Pod を Linux のノードで動かすための指定だと読めるものでした。
nodeSelector と Node Affinity の仕組み
ノードのラベル
nodeSelector も Node Affinity も、ノードのラベルを手がかりにします。第4回と第15回で見たとおり、ラベルはリソースに付けるキーと値の組で、ノードにも付けられます。ラベルの仕組みそのものは第42回で扱います。公式ドキュメントによると、ノードのラベルは手で付けられるほか、Kubernetes がクラスタのすべてのノードに標準のラベルを付けます。クラウドのゾーンなどのラベルは、kubelet がその情報を知っている場合だけ付きます。kubelet が付けるラベルのうち、手順で見る 3 つは次の表のとおりです。
| ラベルのキー | 値 |
|---|---|
kubernetes.io/hostname | ノードのホスト名 |
kubernetes.io/os | ノードの OS(例:linux) |
kubernetes.io/arch | ノードの CPU のアーキテクチャ(例:amd64) |
参考:Node Labels Populated By The Kubelet | Kubernetes、Well-Known Labels, Annotations and Taints | Kubernetes、Well-Known Labels, Annotations and Taints | Kubernetes、Well-Known Labels, Annotations and Taints | Kubernetes
公式ドキュメントは、これらのラベルの値はクラウドプロバイダーによって違い、信頼できるとは限らない、と注意しています。たとえば kubernetes.io/hostname の値は環境によってノードの名前と同じこともあれば、違うこともあります。
第4回・第15回で見た node-role.kubernetes.io/control-plane もノードのラベルです。公式ドキュメントによると、kubeadm は自分が管理するコントロールプレーンのノードにこのラベルを付けます。このラベルを使うと、コントロールプレーンのノードだけに Pod を置いたり、コントロールプレーンのノードを避けたりしやすくなる、とも書かれています。
自分でラベルを付けるには、kubelet の設定で付けるか、Kubernetes の API を使います。公式のタスクでは第11回で使った kubectl label で kubectl label nodes <your-node-name> disktype=ssd のようにノードにラベルを付けています。公式ドキュメントによると、ラベルのキーは省略できる接頭辞と名前を / でつないだもので、kubernetes.io/ と k8s.io/ の接頭辞は Kubernetes の中心のコンポーネントのために予約されています。接頭辞を省いたキーは利用者のものとみなされます。この回の手順で付ける disktype も接頭辞の無いキーです。
参考:Node Labels Populated By The Kubelet | Kubernetes、Assign Pods to Nodes | Kubernetes、Labels and Selectors | Kubernetes
公式ドキュメントによると、ノードの隔離のためにラベルを使うなら、kubelet が書き換えられないキーを選びます。乗っ取られたノードが自分にそのラベルを付け、スケジューラーがそのノードに Pod を置いてしまうのを防ぐためです。そのためのラベルの接頭辞は注意点の表にまとめます。
nodeSelector
以下は disktype=ssd のラベルを持つノードに置く Pod のマニフェストです。公式のタスクの例(pods/pod-nginx.yaml)をそのまま引用しています。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssdnodeSelector にはノードに求めるラベルをキーと値の組で並べます。API リファレンスによると、nodeSelector は Pod がノードに収まるために真でなければならないセレクタで、Pod をそのノードにスケジュールするにはノードのラベルに合わなければなりません。組を 2 つ以上並べると、そのすべてを持つノードだけが選ばれます。公式のタスクは、この Pod は disktype=ssd のラベルを持つノードにスケジュールされる、と説明しています。
参考:Pod | Kubernetes API Reference、Assigning Pods to Nodes | Kubernetes、Assign Pods to Nodes | Kubernetes
Node Affinity の required と preferred
Node Affinity は Pod の spec.affinity.nodeAffinity に書きます。公式ドキュメントによると、Node Affinity には次の 2 種類があります。この記事では長い名前をそれぞれ required、preferred と略します。
| 種類 | 公式ドキュメントの説明 |
|---|---|
requiredDuringSchedulingIgnoredDuringExecution(required) | 規則を満たさない限り、スケジューラーは Pod をスケジュールできない |
preferredDuringSchedulingIgnoredDuringExecution(preferred) | スケジューラーは規則を満たすノードを探そうとする。合うノードが無くても、Pod をスケジュールする |
名前は「スケジュールするときは必須(required during scheduling)か好み(preferred during scheduling)、動いているあいだは無視(ignored during execution)」と読めます。第15回で触れた requiredDuringSchedulingIgnoredDuringExecution はこの required のことです。
後ろ半分の IgnoredDuringExecution は、Pod をスケジュールしたあとでノードのラベルが変わったときの扱いです。公式ドキュメントによると、このとき Pod は動き続けます。一方 API リファレンスの required の説明は、実行中のある時点で条件を満たさなくなった場合(更新などで)、システムが最終的に Pod をノードから追い出そうとするかもしれないし、しないかもしれない(may or may not)と書いていて、追い出される余地を残していると読めます。なお第15回では、DaemonSet の場合はラベルが変わって条件に合わなくなったノードの Pod を daemonset-controller がすぐ消す、と見ました。これは DaemonSet のコントローラーの働きで、スケジューラーの IgnoredDuringExecution とは別の話だと考えられます。
参考:Assigning Pods to Nodes | Kubernetes、Pod | Kubernetes API Reference、DaemonSet | Kubernetes
以下は required と preferred を 1 つずつ書いた Pod のマニフェストです。公式ドキュメントの例(pods/pod-with-node-affinity.yaml)をそのまま引用しています。
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:3.8公式ドキュメントはこの例を次のように説明しています。ノードは topology.kubernetes.io/zone のキーのラベルを持ち、その値が antarctica-east1 か antarctica-west1 でなければなりません(must)。さらに、another-node-label-key のキーで値が another-node-label-value のラベルを持つノードが好まれます(preferably)。antarctica-east1 と antarctica-west1 は 1 つの条件の In に並べた値なので、topology.kubernetes.io/zone の値がどちらかであるノードが合います。
antarctica-east1 などは南極(Antarctica)を名前にしたゾーンで、説明のための架空の値だと考えられます。registry.k8s.io/pause:3.8 は公式ドキュメントの例で使われているイメージです。この例で見るのはノードの条件で、コンテナの中身ではないと読めます。
マニフェストの形を見ると、required は条件のまとまり(term)を nodeSelectorTerms に並べ、preferred は重み(weight)と term(preference)の組を並べています。各フィールドを、API リファレンスと公式ドキュメントに沿って次の表にまとめます。
| フィールド | 内容 |
|---|---|
nodeSelectorTerms | 必須の項目で、required に書く term の並び |
matchExpressions | term の中に書く、ノードのラベルについての条件の並び |
matchFields | term の中に書く、ノードのフィールドについての条件の並び |
weight | preferred の各項目の重み。必須で、値は 1〜100 |
preference | preferred の各項目の term。必須 |
参考:NodeSelector | Kubernetes API Reference、NodeSelectorTerm | Kubernetes API Reference、Pod | Kubernetes API Reference、Assigning Pods to Nodes | Kubernetes
例のマニフェストでは、nodeSelectorTerms: のすぐ下の - matchExpressions: の - が 1 つの term の始まりで、その下の - key: ... の - が 1 つの条件の始まりです。例は term も条件も 1 つずつです。公式ドキュメントの言い方では、nodeSelectorTerms に term を 2 つ以上書くと、どれか 1 つの term を満たせば Pod をそのノードにスケジュールできます(term は OR でつながる)。1 つの term の matchExpressions に条件を 2 つ以上書くと、すべての条件を満たすときだけスケジュールできます(条件は AND でつながる)。API リファレンスは、空の term はどのノードにも合わない、とも書いています。
参考:Assigning Pods to Nodes | Kubernetes、NodeSelectorTerm | Kubernetes API Reference
matchFields は第15回で見た、daemonset-controller が Pod に書き込む条件(ノードの名前 metadata.name を指定する形)で使われていました。この回の手順では使いません。
公式ドキュメントによると、nodeSelector と nodeAffinity の両方を書いた場合、Pod がノードにスケジュールされるには両方を満たす必要があります。
演算子
matchExpressions の各条件はラベルのキー(key)、演算子(operator)、値の並び(values)で書きます。公式ドキュメントが挙げる演算子は次の表のとおりです。
| 演算子 | 合うとき |
|---|---|
In | ラベルの値が、values に並べた文字列のどれかである |
NotIn | ラベルの値が、values に並べた文字列のどれでもない |
Exists | そのキーのラベルがある |
DoesNotExist | そのキーのラベルが無い |
Gt | そのキーのラベルの値を整数として読んだものが、values の整数より大きい |
Lt | そのキーのラベルの値を整数として読んだものが、values の整数より小さい |
values の書き方は演算子で決まっています。API リファレンスによると、In と NotIn では空にできず、Exists と DoesNotExist では空にしなければなりません。Gt と Lt では要素を 1 つだけ書き、その値は整数として読まれます。公式ドキュメントによると、Gt と Lt は整数ではない値では働かず、整数として読めない値を渡すと Pod はスケジュールされません。
参考:ResourceSlice | Kubernetes API Reference、Assigning Pods to Nodes | Kubernetes
公式ドキュメントによると、NotIn と DoesNotExist を使うと、特定のノードを避ける(node anti-affinity)動きを書けます。特定のノードから Pod を遠ざけるには、ノードの Taint を使う方法もあります(第32回)。この回の手順では disktype のキーが無いことを条件にしたいので、DoesNotExist を使います。
weight と、kube-scheduler の 2 段階
preferred の weight はノードに付ける点数にかかわります。公式ドキュメントによると、スケジューラーは、Pod のほかのスケジューリングの条件をすべて満たすノードを見つけると、そのノードが満たす preferred の規則をすべて調べ、その規則の weight を足し合わせます。その合計はそのノードのほかの点数(公式ドキュメントの言い方では、ほかの優先度の関数の点数)に足されます。最終的な点数が最も高いノードが優先されます。
ほかの点数の例として、スケジューラーの設定のページには NodeResourcesFit というプラグインが載っています。NodeResourcesFit は、ノードが Pod の求める資源(第29回)を持っているかを確かめ、点数も付けるプラグインで、既定の付け方は(v1.37 時点で)LeastAllocated です。名前からは、資源の割り当てが少ないノードほど点数を高くする付け方だと読めます。
公式ドキュメントの例では preferred の規則を 2 つ書き、片方の weight を 1、もう片方を 50 にしています。それぞれの規則のラベルを持つノードが 1 つずつあると、スケジューラーは各ノードのほかの点数に weight を足し、最終的な点数が最も高いノードに Pod をスケジュールします。API リファレンスも、preferred の条件を 1 つ以上満たさないノードをスケジューラーが選ぶことはある(may)、と書いています。つまり preferred は合うノードに置かれることを保証しません。
参考:Assigning Pods to Nodes | Kubernetes、Pod | Kubernetes API Reference
第4回で見たとおり、kube-scheduler はノードを 2 段階で選びます。Pod を置けるノードに絞り込む filtering と、残ったノードに点数を付ける scoring です。公式ドキュメントのスケジューラーの設定のページによると、nodeSelector と Node Affinity を実装しているのは NodeAffinity というプラグイン(スケジューラーの処理を分担する部品)で、filter と score の両方の段階で働きます。preferred の weight は、さきほど見たとおりノードのほかの点数に足されるので、点数付けで効きます。API リファレンスはこの合計を求める対象を、資源の要求や required の条件などのスケジューリングの条件をすべて満たすノードとしています。このことから、nodeSelector と required は絞り込みで効くと読めます。
参考:Kubernetes Scheduler | Kubernetes、Scheduler Configuration | Kubernetes、Pod | Kubernetes API Reference、Assigning Pods to Nodes | Kubernetes
以下の図は、kube-scheduler がノードの決まっていない Pod のためにノードを選ぶ流れと、nodeSelector、required、preferred がどの段階で効くかを表しています。1 の箱に nodeSelector と required を置いたのは、この読み方によります。
絞り込みではラベルの条件のほかにも資源の requests(第29回)や Taint(第32回)などで、置けないノードが外されます。1 つもノードが残らなければ、Pod はどのノードにも割り当てられず、Pending のまま待ちます。図の赤い箱はその状態です。点数が同じノードが 2 つ以上あると、kube-scheduler はその中から無作為に 1 つを選びます。選んだノードは binding で kube-apiserver に伝えられ、Pod の spec.nodeName に入ります(第5回)。
参考:Kubernetes Scheduler | Kubernetes、Kubernetes Scheduler | Kubernetes、Scheduler Configuration | Kubernetes、Pod Lifecycle | Kubernetes、DaemonSet | Kubernetes
公式ドキュメントによると、スケジューラーが Pod を置けるノードを見つけられないと、Pod は置ける場所が見つかるまでスケジュールされないまま残ります。スケジューラーは置き場所を見つけられないたびに、イベント(Event)を記録します。第29回で引用した例では kubectl describe pod の Events: に、Reason が FailedScheduling、From が default-scheduler の行が出ていました。逆に言えば、あとからラベルを足すなどして条件に合うノードができれば、待っていた Pod はそのノードに置かれるはずです。後半の手順でこれを確かめます。
参考:Kubernetes Scheduler | Kubernetes、Resource Management for Pods and Containers | Kubernetes
nodeName との違い
Pod の spec.nodeName は、第5回・第8回で見たとおり、ふつうは kube-scheduler が binding で伝えたノードの名前が入るフィールドです。利用者がマニフェストに最初から書くこともできます。公式ドキュメントによると、nodeName はアフィニティや nodeSelector より直接的なノードの選び方です。nodeName が空でなければ、スケジューラーはその Pod を無視し、名前を書いたノードの kubelet がそのノードに Pod を置こうとします。nodeName を書くと、nodeSelector やアフィニティとアンチアフィニティの規則より優先されます。
公式ドキュメントが挙げる、nodeName でノードを選ぶときの制約は次の 3 つです。
- 名前のノードが無ければ Pod は動かず、場合によっては自動で消されることがある
- 名前のノードに Pod を収める資源が無ければ Pod は失敗し、理由に OutOfmemory や OutOfcpu などが示される
- クラウドの環境のノードの名前は予測できるとも変わらないとも限らない
公式ドキュメントは警告として、nodeName は独自のスケジューラーや、設定されたスケジューラーを飛ばす必要のある高度な使い方のためのものだ、と書いています。スケジューラーを飛ばすと、割り当てたノードが詰め込みすぎになったときに Pod が失敗することがあります。スケジューラーを飛ばさずに Pod を特定のノードに割り当てるには、Node Affinity か nodeSelector を使えます。API リファレンスの nodeName の説明にも Pod を特定のノードにスケジュールしたいという希望を表すためにこのフィールドを使うべきではない(should not)、とあります。
参考:Assigning Pods to Nodes | Kubernetes、Pod | Kubernetes API Reference
第27回で見た、WaitForFirstConsumer の StorageClass で nodeName を使うと PersistentVolumeClaim が Pending のままになる、という注意も、公式ドキュメントによるとスケジューラーが飛ばされるためです。
ここまでの 4 つの書き方を、条件に合うノードが無いときの違いで比べると、次の表のとおりです。
| 書き方 | 条件に合うノードが無いとき | ノードの決まり方 |
|---|---|---|
nodeSelector | Pod はスケジュールされず、置ける場所が見つかるまで待つ | kube-scheduler が選ぶ |
| Node Affinity の required | nodeSelector と同じ | kube-scheduler が選ぶ |
| Node Affinity の preferred | 規則に合わないノードにもスケジュールされる | kube-scheduler が選ぶ |
nodeName | 名前のノードが無ければ Pod は動かず、場合によっては自動で消されることがある | kube-scheduler を飛ばす |
参考:Assigning Pods to Nodes | Kubernetes、Kubernetes Scheduler | Kubernetes
基本的には、Pod を置くノードを決めたいときは nodeSelector か Node Affinity を使い、kube-scheduler に選ばせます。条件が「このラベルを持つこと」だけなら nodeSelector で足ります。演算子を使いたいときや、「できれば」の条件を書きたいときは Node Affinity を使います。
手元で試す
ここまでの内容を手元の kind のクラスタで試す手順を示します。公式のタスクに沿って 1 つ目のワーカーのノードにラベル disktype=ssd を付け、nodeSelector と required の Node Affinity の Pod がそのノードに置かれることを確かめます。続けて DoesNotExist でそのノードを避ける Pod を作ります。どのノードも持たないラベルを求めると、required の Pod は Pending のまま待ち、preferred の Pod は置かれることも比べます。最後に、2 つ目のワーカーにラベルを足して、待っていた Pod が置かれることを確かめます。
各段で何が表示されるかは、どれも公式ドキュメントから予想される結果で、手元で実行して確かめたものではありません。各段の最後に、成功かどうかを見分ける目安を書きます。
コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | マルチノード(クラスタ名 multi-node):コントロールプレーン 1、ワーカー 2 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
kind でクラスタを作る手順は、以下の記事で解説しています。この回は第15回・第25回と同じマルチノードのクラスタを使います。公式のタスクが、コントロールプレーンを兼ねないノードが 2 つ以上あるクラスタで試すことを勧めているからです。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。マルチノードのクラスタを用意する
第2回と同じ次の設定ファイルを使います。公式ドキュメントの例をそのまま引用しています。kind-multi-node.yaml という名前で、コマンドを実行するディレクトリに保存します。前の回までに保存したファイルが残っていれば、それを使えます。
# three node (two workers) cluster config
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: workerまず、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
multi-node が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回の kind が表示されても、この回では使いません。手元のマシンの資源(メモリなど)に余裕がなければ、kind delete cluster --name kind で消してから進めてください。
$ kind create cluster --name multi-node --config kind-multi-node.yaml
$ kind get clusters
kind get clusters に multi-node が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-multi-node を付けて、操作するクラスタをはっきりさせます。
続けて、ノードを一覧します。
$ kubectl get nodes --context kind-multi-node
ノードが 3 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
次に、ワーカーのノードだけを一覧します。第4回・第15回で見たとおり、コントロールプレーンのノードには node-role.kubernetes.io/control-plane のラベルが付いています。そこで、このラベルを持たないノードを選びます。キーの前に付ける ! はラベルセレクタの set-based の書き方で「そのキーのラベルを持たない」という意味です。公式ドキュメントの set-based の例も、セレクタを一重引用符で囲んでいます。一般に bash や zsh では ! が履歴の展開に使われるので、一重引用符で囲んでシェルに解釈させないようにします。
$ kubectl get nodes -l '!node-role.kubernetes.io/control-plane' --context kind-multi-node
参考:Labels and Selectors | Kubernetes、Labels and Selectors | Kubernetes、Well-Known Labels, Annotations and Taints | Kubernetes、Implementation details | Kubernetes、Configuration | kind、Admission Control in Kubernetes | Kubernetes
ノードが 2 つ表示されるはずです。名前は multi-node-worker や multi-node-worker2 のような形になると考えられます(公式ドキュメントでは確かめられなかった推測です)。ここからは、表示された順に「1 つ目のワーカー」「2 つ目のワーカー」と呼びます。2 つの名前をメモしておいてください。このあとのコマンドの <1 つ目のワーカーの名前> と <2 つ目のワーカーの名前> は、< と > も含めて、メモした名前に置き換えます。
前の回のクラスタを使い続けている場合は、default の Namespace に Pod が残っていないことと、ノードに disktype のラベルが無いことを確かめます。-L(--label-columns)は第11回で使った、指定したラベルの値を列にして表示する指定です。
$ kubectl get pods --context kind-multi-node
$ kubectl get nodes -L disktype --context kind-multi-node
1 つ目のコマンドで見つからないことを知らせる 1 行(第8回で推測した No resources found in default namespace. のような表示)が出て、2 つ目のコマンドで加わる DISKTYPE 列がどのノードでも空なら、そのまま進めます。このシリーズの前の回までの手順ではノードに disktype のラベルを付けていません。Pod が残っていたら、自分で作ったものかを確かめてから、kubectl delete pod <名前> --context kind-multi-node で消します。
ノードのラベルを見る
ノードをラベルの列つきで一覧します。--show-labels は第6回で使った、ラベルを最後の列に表示する指定です。
$ kubectl get nodes --show-labels --context kind-multi-node
最後の LABELS 列に、各ノードのラベルが キー=値 の形でカンマで区切って並ぶはずです。公式のタスクの例でも、...,kubernetes.io/hostname=worker0 のように並んでいます。手元では kubernetes.io/arch や kubernetes.io/os なども並び、コントロールプレーンのノードには node-role.kubernetes.io/control-plane も並ぶと考えられます。
参考:Assign Pods to Nodes | Kubernetes、Node Labels Populated By The Kubelet | Kubernetes、Implementation details | Kubernetes
LABELS 列は長くて読みにくいので、kubelet が付けるラベルのうち 3 つだけを列にして表示します。-L にはラベルのキーをカンマで区切って並べられます。
$ kubectl get nodes -L kubernetes.io/hostname,kubernetes.io/os,kubernetes.io/arch --context kind-multi-node
いつもの列の後ろに、3 つのラベルの値の列が加わるはずです。OS の列はどのノードも linux になると考えられます。アーキテクチャの列は手元のマシンによって amd64 や arm64 などになると考えられます。ホスト名の列は NAME 列のノードの名前と同じ値になっていると考えられます。第4回で見たとおり、kubelet は既定ではホスト名でノードを登録するからです。
参考:Well-Known Labels, Annotations and Taints | Kubernetes、Node Labels Populated By The Kubelet | Kubernetes、kubelet | Kubernetes
Pod を監視する
ここからは端末を 2 つ使います。第15回・第25回と同じく、1 つ目の端末で変化を追い続け、2 つ目の端末でラベルを付けたりマニフェストを渡したりします。この回で 1 つ目の端末が追うのは、default の Namespace の Pod です。2 つ目の端末を開いたら、cd でマニフェストを保存するディレクトリに移動しておきます。
1 つ目の端末で、Pod を監視します。-o wide を付けて、Pod が置かれたノードの名前(NODE 列)も表示します(第3回・第4回)。この監視は片付けの前まで止めずに続けます。
$ kubectl get pods --watch -o wide --context kind-multi-node
Pod がまだ無いので、さきほどと同じ見つからないことを知らせる 1 行が出たあと、止まったように見えると考えられます。
1 つ目のワーカーにラベルを付ける
2 つ目の端末で、公式のタスクと同じく、1 つ目のワーカーにラベル disktype=ssd を付けます。kind のノードはコンテナなので、本当に SSD がつながっているわけではありません。ラベルはあくまで目印です。
$ kubectl label nodes <1 つ目のワーカーの名前> disktype=ssd --context kind-multi-node
参考:Assign Pods to Nodes | Kubernetes、kubectl label | Kubernetes
第11回で見た Pod の場合と同じく、node/<1 つ目のワーカーの名前> labeled のような 1 行が出るはずです。1 つ目の端末は Pod を追っているので、何も変わらないはずです。
ラベルが付いたことを確かめます。
$ kubectl get nodes -L disktype --context kind-multi-node
DISKTYPE 列に ssd と表示されるのは 1 つ目のワーカーだけで、ほかの 2 つのノードでは空のはずです。ssd がほかのノードの行に付いていたら付け間違いです。kubectl label nodes <そのノードの名前> disktype- --context kind-multi-node で外し、名前を確かめてからやり直します(この外し方は片付けでも使います)。
nodeSelector で寄せる
以下は nodeSelector で disktype=ssd のノードを求める Pod のマニフェストです。「nodeSelector」の節で引用した公式のタスクの例から、名前を nginx-selector に変え、metadata.labels を省きました。nginx-selector.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: nginx-selector
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd参考:Assign Pods to Nodes | Kubernetes、Pod | Kubernetes API Reference
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f nginx-selector.yaml --context kind-multi-node
pod/nginx-selector が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には nginx-selector の行が書き足されていき、STATUS 列は Pending、ContainerCreating を経て Running になるはずです。NODE 列には 1 つ目のワーカーの名前が入るはずです。disktype=ssd のラベルを持つノードは 1 つ目のワーカーだけだからです。クラスタを作ったばかりなら、イメージの取得に時間がかかるので、ContainerCreating のまましばらく止まって見えることがあります(第9回)。
参考:Assign Pods to Nodes | Kubernetes、Pod Lifecycle | Kubernetes
成功の目安は、nginx-selector の NODE 列が 1 つ目のワーカーの名前で、STATUS 列が Running になっていることです。
required の Node Affinity で寄せる
以下は required の Node Affinity で disktype が ssd のノードを求める Pod のマニフェストです。公式のタスクの例(pods/pod-nginx-required-affinity.yaml)から、名前だけを nginx-required に変えました。nginx-required.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: nginx-required
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f nginx-required.yaml --context kind-multi-node
pod/nginx-required が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には nginx-required の行が書き足されていき、NODE 列は nginx-selector と同じ 1 つ目のワーカーになり、やがて Running になるはずです。公式のタスクも、この Pod は disktype=ssd のラベルを持つノードにだけスケジュールされる、と説明しています。書き方は違っても、nodeSelector の disktype: ssd と同じ条件です。
成功の目安は、nginx-required の NODE 列が nginx-selector と同じ 1 つ目のワーカーの名前になっていることです。
DoesNotExist で避ける
以下は disktype のラベルを持たないノードを求める Pod のマニフェストです。required の Pod の operator を DoesNotExist にし、values を外しました。DoesNotExist では values を空にしなければならないからです。nginx-avoid.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: nginx-avoid
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: DoesNotExist
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent参考:Assigning Pods to Nodes | Kubernetes、ResourceSlice | Kubernetes API Reference
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f nginx-avoid.yaml --context kind-multi-node
pod/nginx-avoid が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には nginx-avoid の行が書き足されていき、NODE 列は 2 つ目のワーカーになり、やがて Running になるはずです。disktype のラベルを持たないノードはコントロールプレーンのノードと 2 つ目のワーカーですが、コントロールプレーンのノードには、この Pod が許容しない NoSchedule の Taint があるからです(第15回)。
参考:Assigning Pods to Nodes | Kubernetes、Taints and Tolerations | Kubernetes、Implementation details | Kubernetes
成功の目安は、nginx-avoid の NODE 列が 2 つ目のワーカーの名前になっていることです。
どのノードも持たないラベルを求める
ここでは、どのノードも持っていない disktype=hdd のラベルを、required と preferred でそれぞれ求めます。
以下は required で disktype が hdd のノードを求める Pod のマニフェストです。nginx-required.yaml から、名前と values の値だけを変えました。nginx-required-hdd.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: nginx-required-hdd
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- hdd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f nginx-required-hdd.yaml --context kind-multi-node
置けるノードが無くても、Pod を作ること自体は成功します。Pod はまず Pending で受け付けられ、ノードが決まるのを待つからです(第9回)。そのため、pod/nginx-required-hdd が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には nginx-required-hdd の行が現れますが、STATUS 列は Pending のままで、NODE 列にもノードの名前が入らない(<none> のような表示になる)と考えられます。required の規則を満たすノードが無く、スケジューラーが Pod をスケジュールできないからです。
参考:Assigning Pods to Nodes | Kubernetes、Kubernetes Scheduler | Kubernetes、Pod Lifecycle | Kubernetes
理由を確かめるため、2 つ目の端末で Pod を詳しく表示します。
$ kubectl describe pod nginx-required-hdd --context kind-multi-node
参考:kubectl describe | Kubernetes、Resource Management for Pods and Containers | Kubernetes
表示の末尾の Events: には「weight と、kube-scheduler の 2 段階」の節で見た FailedScheduling(From が default-scheduler)の行があると考えられます。メッセージはノードが Pod の条件に合わないことを表すものになると考えられますが、その例はこのメディアで確かめた範囲の公式ドキュメントにはありません。
成功の目安は、nginx-required-hdd の STATUS 列が Pending のままで、NODE 列にノードの名前が入っていないことです。
次に、以下は preferred で disktype が hdd のノードを好む Pod のマニフェストです。公式のタスクの例(pods/pod-nginx-preferred-affinity.yaml)から、名前を nginx-preferred-hdd に、values の値を hdd に変えました。nginx-preferred-hdd.yaml という名前で保存します。
apiVersion: v1
kind: Pod
metadata:
name: nginx-preferred-hdd
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disktype
operator: In
values:
- hdd
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f nginx-preferred-hdd.yaml --context kind-multi-node
pod/nginx-preferred-hdd が作られたことを知らせる 1 行が出るはずです。1 つ目の端末には nginx-preferred-hdd の行が書き足されていき、NODE 列にワーカーのどちらかの名前が入って、やがて Running になるはずです。preferred では、合うノードが無くてもスケジューラーは Pod をスケジュールするからです。コントロールプレーンのノードは nginx-avoid のときと同じく Taint で外れると考えられます。どちらのワーカーにも disktype=hdd が無いので、weight はどちらにも足されず、どちらのワーカーになるかは、資源にかかわる点数などのほかの点数で決まると考えられます。
参考:Assigning Pods to Nodes | Kubernetes、Assigning Pods to Nodes | Kubernetes
同じラベルを求めても、required の nginx-required-hdd は Pending のまま、preferred の nginx-preferred-hdd は動き出すはずです。この違いが required と preferred の違いです。成功の目安は、nginx-preferred-hdd の NODE 列にどちらかのワーカーの名前が入り、STATUS 列が Running になっていることです。
ラベルを足して、待っていた Pod を置かせる
Pod の affinity は、作ったあとは通常の更新では変えられません(注意点の表)。そこで nginx-required-hdd の条件はそのままにして、ノードの側にラベルを足し、条件に合うノードを作ります。2 つ目の端末で、2 つ目のワーカーにラベル disktype=hdd を付けます。
$ kubectl label nodes <2 つ目のワーカーの名前> disktype=hdd --context kind-multi-node
node/<2 つ目のワーカーの名前> labeled のような 1 行が出るはずです。1 つ目の端末では、nginx-required-hdd の行が書き足され、NODE 列に 2 つ目のワーカーの名前が入って、ContainerCreating を経て Running になるはずです。公式ドキュメントのとおり、置けるノードが無い Pod は、スケジューラーが置けるようになるまでスケジュールされないまま残ります。条件に合うノードができたので、そのノードに置かれると考えられます。ラベルを付けてから置かれるまでにかかる時間は、公式ドキュメントには書かれていません。
このとき、2 つ目のワーカーで動いている nginx-avoid は disktype のラベルを持たないノードを求めていたので、条件に合わなくなりました。それでも「Node Affinity の required と preferred」で見た IgnoredDuringExecution のとおり、nginx-avoid は動き続けるはずです。1 つ目の端末の nginx-avoid の行は Running のままで、Terminating の行は現れないと考えられます。
成功の目安は、nginx-required-hdd の NODE 列が 2 つ目のワーカーの名前になって Running になり、nginx-avoid が Running のままであることです。
nginx-preferred-hdd も、もともと 1 つ目のワーカーに置かれていたなら、disktype=hdd のノードができても 2 つ目のワーカーへは移らないはずです。公式ドキュメントによると、Pod がスケジュールされるのは、その一生で一度だけだからです。
1 つ目の端末の表示が落ち着いたら、2 つ目の端末で、ノードの名前と一緒に一覧します。
$ kubectl get pods -o wide --context kind-multi-node
Pod は 5 つ表示され、どれも STATUS 列が Running のはずです。NODE 列は次の表のようになると考えられます。
| Pod | NODE 列 | 理由 |
|---|---|---|
nginx-selector | 1 つ目のワーカー | nodeSelector の disktype: ssd に合うのは 1 つ目のワーカーだけ |
nginx-required | 1 つ目のワーカー | required の disktype In (ssd) に合うのは 1 つ目のワーカーだけ |
nginx-avoid | 2 つ目のワーカー | 置いたときはラベルが無かった。そのあとラベルを付けても動き続ける |
nginx-required-hdd | 2 つ目のワーカー | ラベルを付けたあとで、required に合うノードができた |
nginx-preferred-hdd | どちらかのワーカー | 置いたときは preferred に合うノードが無く、ほかの点数で決まった |
片付け
1 つ目の端末で Ctrl+C を押して、監視を止めます。
2 つ目の端末で、作った Pod をマニフェストを指定して削除します。
$ kubectl delete -f nginx-selector.yaml --context kind-multi-node
$ kubectl delete -f nginx-required.yaml --context kind-multi-node
$ kubectl delete -f nginx-avoid.yaml --context kind-multi-node
$ kubectl delete -f nginx-required-hdd.yaml --context kind-multi-node
$ kubectl delete -f nginx-preferred-hdd.yaml --context kind-multi-node
続けて、2 つのワーカーに付けた disktype のラベルを外します。公式ドキュメントの kubectl label の例のとおり、キーの後ろに - を付けると、そのキーのラベルが消されます。この形には --overwrite は要りません。
$ kubectl label nodes <1 つ目のワーカーの名前> disktype- --context kind-multi-node
$ kubectl label nodes <2 つ目のワーカーの名前> disktype- --context kind-multi-node
残っていないかを確かめます。
$ kubectl get pods --context kind-multi-node
$ kubectl get nodes -L disktype --context kind-multi-node
1 つ目のコマンドで Pod が Terminating で表示されていたら、少し待ってからもう一度実行します。準備のときと同じ、見つからないことを知らせる 1 行が出れば、Pod は削除できています。2 つ目のコマンドで DISKTYPE 列がどのノードでも空なら、ラベルも外せています。
最後に、クラスタを削除し、消えたことを確かめます。
$ kind delete cluster --name multi-node
$ kind get clusters
kind delete cluster は、存在しないクラスタを指定してもエラーを返しません。kind get clusters に multi-node が表示されなくなったことを確かめます。kubeconfig に kind-multi-node のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-multi-node で消します。
注意点
nodeSelector と Node Affinity でつまずきやすい点と、後から変えられない設定を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
条件に合うノードが無いと Pending のまま | nodeSelector と required は、合うノードができるまで Pod をスケジュールしない。理由は kubectl describe pod のイベントで確かめる |
| preferred は保証しない | weight はノードのほかの点数に足されるもので、合わないノードが選ばれることもある |
| 置いたあとでラベルを変えても、Pod は動き続ける | IgnoredDuringExecution の意味。API リファレンスは、追い出そうとするかもしれないし、しないかもしれない(may or may not)と書いている |
| Pod の条件は、作ったあとは通常の更新では変えられない | v1.37 時点では、nodeSelector と affinity は patch や replace で変えられるフィールドの一覧に無い(第8回)。scheduling gates のある Pod では条件を狭める変更だけができるが、この回では扱わない |
nodeSelector と nodeAffinity を両方書くと両方が要る | どちらか一方を満たすだけでは、Pod はスケジュールされない |
nodeName はスケジューラーを飛ばす | nodeSelector やアフィニティより優先される。特定のノードに置きたいだけなら、Node Affinity か nodeSelector を使う |
| 隔離に使うラベルのキー | kubelet が書き換えられないキーを選ぶ。Node authorizer(認可の仕組みの 1 つ。第40回)を使い、アドミッションコントローラー(第30回・第40回)の NodeRestriction を有効にすると、kubelet は node-restriction.kubernetes.io/ の接頭辞のラベルを付けたり変えたりできない |
Gt と Lt は整数だけ | 整数として読めない値を渡すと、Pod はスケジュールされない |
参考:Assigning Pods to Nodes | Kubernetes、Pod | Kubernetes API Reference、Pods | Kubernetes、Pod Scheduling Readiness | Kubernetes、Kubernetes Scheduler | Kubernetes、Assigning Pods to Nodes | Kubernetes、Admission Control in Kubernetes | Kubernetes
関連記事
この回で DoesNotExist の代わりに挙げた、ノードの側から Pod を遠ざける Taint と Toleration については、以下の記事で解説しています。
ほかの Pod のラベルをもとに Pod を分散させたりまとめたりする Pod 間のアンチアフィニティと、Pod Topology Spread Constraints については、以下の記事で解説しています。
Pod Anti-Affinity と Topology Spread の違いほかの Pod のラベルで置き場所を決める Pod 間のアフィニティ・アンチアフィニティと、ドメインごとの Pod の数の偏りを抑える Pod Topology Spread Constraints の違いを図で説明し、kind のマルチノードのクラスタで Pod をまとめる配置と分散させる配置を比べる手順を紹介します。