KubernetesのNode AffinityとInternal Pod Affinityを使ってPodを高度スケジューリングする
はじめに
Kubernetesには高度スケジューリングを行えるAPIが用意されていあるので、今回はまとめようと思います。
どれもlabelを使って認識するのでその事前知識が必要です。
アジェンダ
Affinity, Anti-Affinityとは
Affinity, Anti-Affinityとは
PodかNodeにスケジュールされるのに対して、どのNodeにPodを配置するかや、どのPodをNodeが配置を許可するかのルール
を示したものです。
公式記事は以下のようになっています。
Assigning Pods to Nodes - Kubernetes
これからいくつかのセクションで分けて解説します。
2つの種類
以下のようにAffinityには2つの種類があります。
- Node Affinity, Anti-Affinity
- Internal Pod Affinity
Node Affinity, Anti-Affinity
Node Affinityは
どのNodeにPodが配置されるかのノードのラベルで識別したもの
を示したものです。高度なnodeSelectorです。
指定方法はnodeSelectorTerms
というものがあって、以下のように設定します。
nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/e2e-az-name operator: In values: - e2e-az1 - e2e-az2
また、Node Anti-Affinityはその逆です。
名前の通り、どのNodeに!?を選ぶようなものです。
ここで出てきたoperator
についてはあとに解説します。
またこの中でも2つの種類があります。
項目 | 説明 |
---|---|
requiredDuringSchedulingIgnoredDuringExecution |
指定したNodeにスケジュールされることを強制する |
preferredDuringSchedulingIgnoredDuringExecution |
指定したNodeにスケジュールされることを優先的にする。weight によって1~100 で優先度を設定する。 |
です。
またどちらも語尾についているIgnoredDuringExecution
は、ランタイムでラベルのノードが変更された時などはPodは終了しないことを意味しています。
以下のようにspec.affinity
に設定します。
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/e2e-az-name operator: In values: - e2e-az1 - e2e-az2 preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: another-node-label-key operator: In values: - another-node-label-value
また、Node-AntiAffintiyではtopologyKey
を指定することができますが、あとで解説します。
Internal Pod Affinity, Anti-Affinity
Node Affinityの逆で、Internal Pod Affinityとは公式ドキュメントでは
NodeがPodのラベルを識別してPodを受け入れるかを指定する
ルールと書かれています。たとえば特定のPodが動いていた時は受け入れないっといったようなことができるようになります。
ですが、実質的には
Pod同士のルールを定めてPodをスケジューリングするものです。
注意点としては、大きなクラスタになるとスケジューリングコストがかかります。
また、指定の方法は二つ種類があって、これはNode Affinityと同様です。
項目 | 説明 |
---|---|
requiredDuringSchedulingIgnoredDuringExecution |
指定したNodeにスケジュールされることを強制する |
preferredDuringSchedulingIgnoredDuringExecution |
指定したNodeにスケジュールされることを優先的にする |
以下のように設定します。
spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: security operator: In values: - S1 topologyKey: failure-domain.beta.kubernetes.io/zone podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: security operator: In values: - S2 topologyKey: kubernetes.io/hostname containers: - name: with-pod-affinity image: k8s.gcr.io/pause:2.0
ここで意味するところは
- podAffinityについては
- 新しいPodはもし
security=S1
に対して同じゾーンで一つ以上あればPodはそのNodeにスケジュールされる
- 新しいPodはもし
- podAntiAffinityについては
- 新しいPodはもし
security=S2
に対して同じゾーンで一つ以上あればPodはそのNodeにスケジュールされない
- 新しいPodはもし
という意味である。
ビルトインNode Label
ノードにはあらかじめ自動的にラベル付けされるものがあって、それも指定することができます。
現在では以下のようなラベルがあります。
- kubernetes.io/hostname
- failure-domain.beta.kubernetes.io/zone
- failure-domain.beta.kubernetes.io/region
- beta.kubernetes.io/instance-type
- beta.kubernetes.io/os
- beta.kubernetes.io/arch
またGoogle Cloud Platformだと以下のようにすればプリエンティティブノードにスケジューリングされることを防ぐことができます。
nodeSelectorTerms: - matchExpressions: - key: cloud.google.com/gke-preemptible operator: DoesNotExist
operator
以下のようなoperator
の指定方法があります。
operator | 意味 |
---|---|
In |
ラベルの値が指定したValueのいずれかに一致する |
NotIn |
ラベルの値が指定したValueのいずれにも一しない時 |
Exists |
ラベルが存在する時 |
DoesNotExist |
ラベルが存在しない時 |
Gt |
ラベルの値が指定した値よりも大きい時 |
Lt |
ラベルの値が指定した値よりも小さい時 |
topologyKey
podAffintiyなどを結果的にどのレベルで一緒に配置するのというものです。
operator | 意味 |
---|---|
failure-domain.beta.kubernetes.io/zone |
同じ値をもつPodと同じゾーンにスケジュール |
kubernetes.io/hostname |
同じ値をもつPodの同じノードにスケジュール |
Affinityの今後
現在はまだベータ版ですが、以下の機能が実装されることが予定されています。
requiredDuringSchedulingRequiredDuringExecution
: これによってすでにあるPod
は削除されて、Affinityにあうように再スケジュールされる。
検証
準備
変数設定
何回も何回も指定するのは面倒なので以下のように設定します。
$CLUSTER_NAME=hoge
クラスタ作成
以下のようにクラスタを作成します。
Nodeの数ですがデフォルトは3ですが、せっかくのスケジューリングの実証なので6
にします。
Nodeのスペックは高くなくていいので、以下のg1-small
に指定します。
gcloud container clusters create- $CLUSTER_NAME --region=asia-northeast1-b --num-nodes=6 --machine-type=g1-small
正しくクラスタが構築できたか確認してください。
gcloud container clusters describe $CLUSTER_NAME --region $(gcloud config get-value compute/region)
そしてkubectl
でアクセスできるようにcredentialを取得します。
gcloud container clusters get-credentials $CLUSTER_NAME --region $(gcloud config get-value compute/region)
ここでNodeを確認してください。
kubectl get nodes NAME STATUS ROLES AGE VERSION gke-affinitiy-test-default-pool-42bc3e7f-4v8r Ready <none> 19m v1.9.7-gke.6 gke-affinitiy-test-default-pool-42bc3e7f-f7f9 Ready <none> 19m v1.9.7-gke.6 gke-affinitiy-test-default-pool-42bc3e7f-j50r Ready <none> 19m v1.9.7-gke.6 gke-affinitiy-test-default-pool-42bc3e7f-n1m5 Ready <none> 19m v1.9.7-gke.6 gke-affinitiy-test-default-pool-42bc3e7f-n31r Ready <none> 19m v1.9.7-gke.6 gke-affinitiy-test-default-pool-42bc3e7f-rh20 Ready <none> 19m v1.9.7-gke.6
オプション説明
Num Nodes
以下のオプションでNodeの個数を指定することができます。
--num-nodes=NUM_NODES; default=3]
Machine type
どのインスタンスのタイプ化かを指定します。
[--machine-type=MACHINE_TYPE, -m MACHINE_TYPE]
スタンダートなものと、小さなものは以下の一覧から選べます。
Machine Types | Compute Engine Documentation | Google Cloud
Nodeラベル
何かnode全体にラベルを付けたければ以下のオプションがあります。
[--node-labels=[NODE_LABEL,…]
Podに使うコンテナイメージ用意
今回はスケジューリングを見るので特に作らなくて大丈夫です。
以下の公式DockerImageを使おうと思います。
- nginx:1.15.4-alpine
- node:8.12.0-alpine
実際にやっていく
何も指定なし
以下のように何も指定しません。
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx labels: app: nginx spec: replicas: 8 selector: matchLabels: app: nginx-pod template: metadata: labels: app: nginx-pod spec: containers: - name: nginx image: nginx-1.15.4-alpine ports: - containerPort: 80
以下のようになっています。
kubectl get pods nginx-566d7d7fb9-2tdtx 1/1 Running 0 1m nginx-566d7d7fb9-99wrx 1/1 Running 0 24m nginx-566d7d7fb9-bptf5 1/1 Running 0 1m nginx-566d7d7fb9-dl6tt 1/1 Running 0 1m nginx-566d7d7fb9-md988 1/1 Running 0 24m nginx-566d7d7fb9-xcvnp 1/1 Running 0 24m nginx-566d7d7fb9-xh2jp 1/1 Running 0 24m nginx-566d7d7fb9-xz6rj 1/1 Running 0 24m
どのようにNodeにスケジューリングされたかというと以下のようになっています。
以下のコマンドでスケールされることができます。
kubectl scale --replicas=20 deploy/nginx
結果
1つはどこも配置されて、あるところは2つになっている
ここで確率的に一つも配置されないケースもありえるので、再度試してみてください。
nodeSelector
次にspec.nodeSelector
でビルドインラベルが以下のようなものを指定しようと思います。
kubernetes.io/hostname
がgke-affinitiy-test-default-pool-42bc3e7f-4v8r
のもの。
よって以下をdeploymentに付け足します。
nodeSelector: kubernetes.io/hostname: gke-affinitiy-test-default-pool-42bc3e7f-4v8r
そしてデプロイします。
結果
リソースが限界に対しているのでひとつはスケジューリングできなかったですが他はうまくいってます。
Node Affinity
新しいラベルをNodeごとに割り当てるのは面倒なので、以下のようにします。
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - gke-affinitiy-test-default-pool-42bc3e7f-4v8r - gke-affinitiy-test-default-pool-42bc3e7f-f7f9
結果
狙った通り、二つに入れられています。
Internal Pod Affinity
これは2つ目のイメージも使います。
一つ目のreplica
数を以下の用に変更します。
replica: 1
また、ラベルも付与しておきます。
nginx: prod
2つめのdeploymentは以下のようにします。
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: node labels: app: node spec: replicas: 1 selector: matchLabels: app: node-pod template: metadata: labels: app: node-pod spec: containers: - name: node image: node:8.12.0-alpine ports: - containerPort: 5000 affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: nginx operator: In values: - prod topologyKey: kubernetes.io/hostname
ユースケースとしてはnginx-pod
と一緒のnodeにnode-pod
を入れたい時です。
結果
以下のように一致しています。
クリーンアップ
検証用に使ったリソースを解放します。
まず、deployment
を消してください。
kubectl delete deploy nginx kubectl delete deploy node
そしてクラスタを消してください。
gcloud container cluster $CLUSTER_NAME -- region $(gcloud config get-value computer/region)
以上です。
まとめ
Pod同士の配置は意識したことがなかったのでいい勉強になりました。
付録
NodeSelectorとは
NodeSelectorとは、もっとも簡単なNode Affinityのことです。
ラベルによってPodを配置するNodeを選ぶことができる。
以下のようにspec.nodeSelector
につけるだけです。
spec: nodeSelector: disktype: ssd
それで一致するラベルがあればスケジュールしてくれます。
ラベルをCLIから付ける方法
以下のコマンドでノードも付けることができます。
kubectl label nodes <node-name> <label-key>=<label-value>
Podも同様に付けることはできますが、.yamlなどのファイルにつけて宣言的であることを保証するためにCLIで付けることは推奨しません。
Resource Quota
たとえばクラスタ作成のときに--num-nodes
は10
とするとクラスタを作成することができなく、以下のエラーがでます。
ERROR: (gcloud.container.clusters.create) ResponseError: code=403, message= (1) insufficient regional quota to satisfy request: resource "CPUS": request requires '10.0' and is short '2.0'. project has a quota of '8.0' with '8.0' available (2) insufficient regional quota to satisfy request: resource "IN_USE_ADDRESSES": request requires '10.0' and is short '2.0'. project has a quota of '8.0' with '8.0' available.
これはResource Quota=リソースの分け前と呼ばれるもので、この場合だと作成できるノード数はリージョンによって指定されています。
追加で要求するときはIAMと管理
-> 割り当て
から編集することができます。
事前に知りたい時は以下のコマンドを走らせてください。
gcloud compute project-info describe --project $(gcloud config get-value project)