【Kubernetes】Podの配置戦略まとめ

# はじめに

この記事では、KubernetesにおけるPodの配置戦略についてまとめています。具体的には以下の4点についてまとめました。

- nodeSelector
- nodeAffinity / nodeAntiAffinity
- podAffinity / podAntiAffinity
- Taints / Tolerations

# Version

Kubernetes v1.17.3

# Podの配置戦略

Kubernetes上でアプリケーションを稼働させるときに考慮すべきこととして冗長性があります。具体的には複数NodeでKubernetes clusterを構成している場合に、あるNodeに障害が起きた場合でもアプリケーションを停止させないことが重要です。このようなことを実現させるためにはPodをどのNodeに配置するのかが重要になってきます。アプリケーションが実行されているPodを複数起動している場合、各Podを別々のNode上で起動することでNode障害に耐えられるアプリケーションを構築することができます。

KubernetesではPodの配置戦略を設定する方法として、以下の4点が用意されています。

- nodeSelector
- nodeAffinity / nodeAntiAffinity
- podAffinity / podAntiAffinity
- Taints / Tolerations

それぞれについて解説していきます。

## nodeSelector

nodeSelectorはその名の通り「Podを配置したいNodeラベル」をキーバリュー形式で指定します。例えばcity: tokyoというラベルのあるNodeへ配置したい場合、以下のような指定をします。

```yml:nodeSelector.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    web: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: Always
  nodeSelector:
    city: tokyo
```

ちなみにNodeへのラベル付与の仕方は以下の通りです。

```shell
$ kubectl label nodes [Node名] city=tokyo
```

このnodeSelectorはいずれなくなるようです。 理由はこの後に説明するnodeAffinityで代替できるからとのことです。

## nodeAffinity / nodeAntiAffinity

nodeAffinityはnodeSelectorよりも柔軟なNode指定ができます。AntiAffinity や、この後に紹介するTaints / Tolerationsと維持する動作も表現可能です。

Node選択する際、「配置する時（DuringScheduling）の必須度合（requiredまたはpreferred）」と「既に配置されて実行されているPod（DuringExecution）の対応（IgnoreまたはRequired）」の組み合わせで4パターンの動作が考えられます。 どのような動作をさせたいかで選択します。

- **requiredDuringSchedulingIgnoredDuringExecution**  
Pod配置時は必須条件とするが、すでに配置されているPodに対しては無視する。
- **preferredDuringSchedulingIgnoredDuringExecution**  
Pod配置時にできるだけ配慮して配置する。すでに配置されているPodに対しては無視する。
- **requiredDuringSchedulingRequiredDuringExecution**  
Pod配置時に必須条件とし、すでに配置されているPodがある場合は退去させる。 Kubernetes v1.17.3時点では利用不可。 今後実装予定の機能。
- **~~preferredDuringSchedulingRequiredDuringExecution~~**  
組み合わせ上は存在するが機能としては存在しない。

例えば以下のような設定を記述することができます。

```yml:nodeAffinity.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    web: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: Always
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: country
            operator: In
            values:
            - japan
            - america
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: city
            operator: In
            values:
            - tokyo
```

ここで細かな設定項目について解説します。

matchExpressionsはkey、operator、valuesで構成されます。keyには条件にしたいNodeのラベル名、valuesには値を指定します。operatorに使える演算子は以下の通りです。

|演算子|意味|
|:--:|:---|
|In|keyと一致するラベルの値がvaluesに指定したリスト内に存在するかを指定できる。|
|NotIn|keyと一致するラベルの値がvaluesに指定したリスト内に存在しないかを指定できる。|
|Exists|keyに指定したラベルが存在するかを指定できる。|
|DoesNotExists|keyに指定したラベルが存在しないかを指定できる。|

preferredDuringSchedulingには上記の条件に加えてweightが1～100の範囲で指定できます。 このweightはNode選択時に利用します。各Nodeにおいてラベルマッチした条件のweightを加算し、最終的に合計weightの高いNodeにPodを配置します。

## podAffinity / podAntiAffinity

podAffinityおよびpodAntiAffinityはすでに動いているPodをもとにどこへPodを配置するかを定義するものになります。podAffinityは「指定されたラベルに一致するPodが動作しているNodeがもつtopologyKeyが同一のNodeにPodを配置させる」動作になります。podAntiAffinityは「topologyKey の範囲内に指定されたラベルに一致するPodが動作していなければ、PodをtopologyKey範囲内のNodeのいずれかにPodを配置する」動作になります。

```yml:podAffinity.yml
apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0
  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: failure-domain.beta.kubernetes.io/zone
```

topologyKeyはNodeのLabelを使った範囲の指定です。topologyKeyで指定されたKeyに対応するValueで一致したものが同一のトポロジーとして扱われます。意図的にすべてのNodeに対して付与しているラベルがないのであれば標準的に付与される以下のようなラベルを利用します。

- kubernetes.io/hostname
- node.kubernetes.io/instance-type
- topology.kubernetes.io/region
- topology.kubernetes.io/zone
- kubernetes.io/arch
- kubernetes.io/os

topology.kubernetes.io/region等はクラウドプロバイダーに依存するラベルとなります。

## Taints / Tolerations

Taint / TolerationsはNodeAffinityと逆の機能でNodeへPodをスケジュールさせないための機能です。

### Taints

NodeにTaintをつけることでその条件が受け入れられないPodはそのNodeにスケジュールされない。Taintは"kubectl taint"コマンドで追加する。表記については以下の通り。

```shell
<Key>=<Value>:<Effect>
```

KeyとValueについてはLabelと同様に任意の値をつける。Effectについては以下の3つの値を指定できる。

- NoSchedule  
このTaintが許容できない場合はそのNodeにスケジュールできない。
- PreferNoSchedule  
このTaintが許容できない場合はそのNode以外にスケジュールを試みる。
- NoExecute  
このTaintが許容できない場合はそのNode以外にスケジュールされ、実行されているPodも追い出される。

taintは以下のように設定します。

```shell
$ kubectl taint node node1 test-taint=hoge:NoSchedule
```

### Tolerations

PodがNodeに設定されているTaintを容認するかを指定する機能です。tolerationにマッチしたTaintを容認し、そのNodeにスケジュールできるようになります。このTolerationはPodの spec.tolerations にリストで指定する。指定するフィールドは以下のものです。

- key  
TaintのKeyを指定
- operator  
Valueに比較の種類を指定。 EqualとExistsが指定できる。デフォルトはEqual。
- value  
TaintのValueを指定。 operatorがExistsの場合、Valueを省略できる。
- effect  
TaintのEffectを指定。指定しなかった場合、全てのEffectにマッチする。
- tolerationSeconds  
NoExecuteを指定した際、追い出されるまでの時間を秒数で指定する。指定しなかった場合、追い出されないないようになる。0や負の数を指定した場合、即時に追い出される。

tolerationは以下のように設定します。

```yml:toleration.yml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-toleration
spec:
  containers:
  - name: nginx
    image: nginx:alpine
  terminationGracePeriodSeconds: 0
  tolerations:
  - key: test-taint
    value: hoge
    effect: NoSchedule
```

これによりtest-taint=hoge:NoScheduleというTaintを容認し、そのNodeでPodが起動できるようになる。