k8s での Pod のスケジューリングについて確認した時のメモ(Taints and Tolerations)

メモ。
以下の続きです。

+ [k8s での Pod のスケジューリングについて確認した時のメモ(nodeSelector まで)](https://qiita.com/toshihirock/items/0f38aea8d069d434d81d)
+ [k8s での Pod のスケジューリングについて確認した時のメモ(NodeAffinity)](https://qiita.com/toshihirock/items/e4105e2a78ca5067b1fe)
+ [k8s での Pod のスケジューリングについて確認した時のメモ(Inter-Pod Affinity/Anti-Affinity)](https://qiita.com/toshihirock/items/1c0523571216f56f9c25)

# まとめ

+ Taints and Tolerations　はノードに対して汚れ（Taints）をつけておき、それを許容（Tolerations）できる Pod のみスケジューリングを許可する
+ ノードへの taints の付与・削除は `kubectl taint` で可能
+ Effect には PreferNoSchedule/NoSchedule/NoExecute の3つがある
+ NoExecute を使うことで既にスケジューリング済みの Pod を taint が付与されたノードから排除する事もできる

# 環境

以下の記事と同じ。

[k8s での Pod のスケジューリングについて確認した時のメモ(nodeSelector まで)](https://qiita.com/toshihirock/items/0f38aea8d069d434d81d)

# Taints and Tolerations

以下を参考確認

+ [Taints and Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)
+ [ノード taint によるスケジューリングの制御](https://cloud.google.com/kubernetes-engine/docs/how-to/node-taints?hl=ja)
+ [EKS Kubernetes Podの配置管理](http://blog.father.gedow.net/2019/10/10/eks-kubernetes-pod-positioning/)

今まで確認したように k8s のスケジューリングという意味では以下が使えた

+ nodeSelector
+ Node Affinity/Anti-Affinity
+ Inter-Pod Affinity/Anti-Affinity

これらは **Pod のスケジューリング時に特定の ノードを選択する** スケジューリングポリシーだった。
一方、今回実施する Taints 及び Tolerations は逆の考え方となる。
具体的には **ノードに対して汚れ（Taints）をつけておき、それを許容（Tolerations）できる Pod のみスケジューリングを許可する** というものである。
個人的に以下のブログの記述が分かりやすい&頭に残る

[EKS Kubernetes Podの配置管理](http://blog.father.gedow.net/2019/10/10/eks-kubernetes-pod-positioning/)

>まずNode側としては、Taintsを付与することで、いったん全てのPodスケジュールを拒否するような状態になります。Taint = 汚染、汚れ なので、『アタイは汚れちまったのさ、こないでおくれっ』というイメージです。一部のNodeに付与した場合、Podはそこ以外に起動するし、全てのNodeに付与した場合はどこにもPodを起動できなくなります。
>それに対し、Pod側として Tolerations を付与することでNodeに起動できるようになります。Toleration = 寛容、容認 なので、『そんなオメェでも、オレァかまわねぇよっ』と歌舞いていくスタイルです。

# 検証

## 検証に利用する Node

以下の通り2つの Node が配置された環境を利用する。
デフォルトで付与されてるラベルは以下の通り

```bash
$k get nodes -o json|jq ".items[]|.metadata.labels"
{
  "alpha.eksctl.io/cluster-name": "test-cluster",
  "alpha.eksctl.io/instance-id": "i-0282a2ef8130cbff1",
  "alpha.eksctl.io/nodegroup-name": "standard-workers",
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/instance-type": "t3.medium",
  "beta.kubernetes.io/os": "linux",
  "failure-domain.beta.kubernetes.io/region": "ap-northeast-1",
  "failure-domain.beta.kubernetes.io/zone": "ap-northeast-1c",
  "kubernetes.io/arch": "amd64",
  "kubernetes.io/hostname": "ip-192-168-48-33.ap-northeast-1.compute.internal",
  "kubernetes.io/os": "linux"
}
{
  "alpha.eksctl.io/cluster-name": "test-cluster",
  "alpha.eksctl.io/instance-id": "i-03a676eba6a541672",
  "alpha.eksctl.io/nodegroup-name": "standard-workers",
  "beta.kubernetes.io/arch": "amd64",
  "beta.kubernetes.io/instance-type": "t3.medium",
  "beta.kubernetes.io/os": "linux",
  "failure-domain.beta.kubernetes.io/region": "ap-northeast-1",
  "failure-domain.beta.kubernetes.io/zone": "ap-northeast-1a",
  "kubernetes.io/arch": "amd64",
  "kubernetes.io/hostname": "ip-192-168-74-169.ap-northeast-1.compute.internal",
  "kubernetes.io/os": "linux"
}
```

上記の通り、Node は ap-northeast-1c 及び ap-northeast-1d に起動されている

## Taints の付与・削除

Taints を付与する場合「Key=Value:Effect」の書式で指定可能。
Key 及び Value は任意の値を設定して良いが、Effect は以下の3つからいずれかを指定する。

+ PreferNoSchedule:可能な限りスケジューリングしない
+ NoSchedule:スケジューリングしない(既にスケジューリングしている Pod はそのまま)
+ NoExecute:実行を許可しない(既にスケジューリングされている Pod は停止)

今まで確認した nodeSelector、Node Affinity/Anti-Affinity、Inter-Pod Affinity/Anti-Affinity との違いという意味だと **NoExecute** があるという点がある。
nodeSelector、Node Affinity/Anti-Affinity、Inter-Pod Affinity/Anti-Affinity はあくまで **スケジューリング時のみ考慮される** というのもであった。
つまり、後からスケーリングポリシーに合致しなくなっても Pod はノード上に起動し続けることが出来た。
一方、**NoExecute** を使うことで後から条件に合致しなくなった場合にノード上に起動する Pod を停止させることができる。

試しにノードに追加する。

```bash
# taint を追加
$ k taint node ip-192-168-48-33.ap-northeast-1.compute.internal example-key=true:NoSchedule
node/ip-192-168-48-33.ap-northeast-1.compute.internal tainted

# 確認.指定したノードには設定されており、もう一方にはない
$k describe node ip-192-168-48-33.ap-northeast-1.compute.internal |grep Taints
Taints:             example-key=true:NoSchedule

$k describe nodes |grep Taints
Taints:             example-key=true:NoSchedule
Taints:             <none>
```

なお、削除する際には `kubectl taint nodes node1 key:<Effect>-` という書式で可能。

```bash
# taints の削除
$k taint node ip-192-168-48-33.ap-northeast-1.compute.internal example-key:NoSchedule-
node/ip-192-168-48-33.ap-northeast-1.compute.internal untainted

# 確認
$k describe node ip-192-168-48-33.ap-northeast-1.compute.internal |grep Taints
Taints:             <none>
```

## tolerations を付与した Pod の起動

Pod を作成する際に付与されている taints(汚れ) を toleration(許容)する動きを確認する。
まず、ノード側に taints を付与する。

```bash
# 特定ラベルを持つノード全てに taint を付与
$k taint node -l beta.kubernetes.io/os=linux example-key=true:NoSchedule

# 2つのノードのいずれにも taint が付与された
$k describe nodes |grep Taints
Taints:             example-key=true:NoSchedule
Taints:             example-key=true:NoSchedule
```

次に Pod を作成する。
まずは `tolerations` を指定せず、作成

```pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
```

これを apply する。

```bash
# apply
$ k apply -f pod.yaml
pod/nginx created

# Pendig のまま
$k get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          54s

# 詳細を確認
$k describe pod nginx
（一部省略)

Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s

Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  54s (x4 over 104s)  default-scheduler  0/2 nodes ar
e available: 2 node(s) had taints that the pod didn't tolerate.
```

Events を見るとスケジューリングに失敗した点が確認出来ており、ノードには taint があるがこの Pod には toleration がないのでスケジューリング出来ない、という事が分かる。

次に toleration がある Pod を作成する。

```
apiVersion: v1
kind: Pod
metadata:
  name: nginx-with-toleration
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"
```

```bash
# apply
$k apply -f pod-with-toleration.yaml
pod/nginx-with-toleration created

# nginx-with-toleration は起動している
k get pods
NAME                    READY   STATUS    RESTARTS   AGE
nginx                   0/1     Pending   0          5m37s
nginx-with-toleration   1/1     Running   0          19s

# 詳細を見てみる
$k describe pods nginx-with-toleration
（一部省略)

Tolerations:     example-key:NoSchedule
                 node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age    From
        Message
  ----    ------     ----   ----
        -------
  Normal  Scheduled  2m13s  default-scheduler
        Successfully assigned default/nginx-with-toleration to ip-192-168-74-169
.ap-northeast-1.compute.internal
  Normal  Pulling    2m12s  kubelet, ip-192-168-74-169.ap-northeast-1.compute.internal  Pulling image "nginx"
  Normal  Pulled     2m5s   kubelet, ip-192-168-74-169.ap-northeast-1.compute.internal  Successfully pulled image "nginx"
  Normal  Created    2m5s   kubelet, ip-192-168-74-169.ap-northeast-1.compute.internal  Created container nginx
  Normal  Started    2m4s   kubelet, ip-192-168-74-169.ap-northeast-1.compute.internal  Started container nginx

```

`describe` すると **example-key:NoSchedule** が Tolerations に追加されているのが分かる。
また、実際には **ip-192-168-74-169
.ap-northeast-1.compute.internal** にスケジューリングされ、起動ができている状況。

## NoExecute を使ってみる

NoExecute　を使ってあとから条件に合致しなくなるような場合の挙動を確認する。
具体的には後から taints を付与する場合を検証する。

まず、本記事を上から進めているとノードに taints が付与されているので削除する。

```bash
# taints の削除
$k taint node -l beta.kubernetes.io/os=linux example-key:NoSchedule-
node/ip-192-168-48-33.ap-northeast-1.compute.internal untainted
node/ip-192-168-74-169.ap-northeast-1.compute.internal untainted

# 確認
$k describe nodes |grep Taints
Taints:             <none>
Taints:             <none>
```

先程の tolerations がない Pod を再利用する
一応再掲

```pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
```


こちらを作成

```bash
$k apply -f pod.yaml

# 確認
$k get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          4s

# ノード「ip-192-168-74-169.ap-northeast-1.compute.internal」に起動している
$ k describe pod nginx |grep Node
Node:         ip-192-168-74-169.ap-northeast-1.compute.internal/192.168.74.169
Node-Selectors:  <none>
```

OK.
次にこの状態で taints を追加する。
ただし、最初は **NoSchedule** を指定する。

```bash
$k taint node -l beta.kubernetes.io/os=linux example-key=true:NoSchedule

# 確認.付与されている
$k describe node |grep Taints
Taints:             example-key=true:NoSchedule
Taints:             example-key=true:NoSchedule

# Pod は起動したまま
$k get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          104s
```

これは **NoSchedule** であり、あくまでスケジューリング時のみ考慮されるということで想定通り。
次に付与する taints を **NoExecute** にする。

```bash
$k taint node -l beta.kubernetes.io/os=linux example-key=true:NoExecute
node/ip-192-168-48-33.ap-northeast-1.compute.internal tainted
node/ip-192-168-74-169.ap-northeast-1.compute.internal tainted

# taint の effect が変わった
$k describe node |grep Taints
Taints:             example-key=true:NoExecute
Taints:             example-key=true:NoExecute

# pod はなくなった
$k get pods
No resources found in default namespace.
```

恐らく、`ReplicaSet` や `Deployment` を使えば継続的に起動を試みると思われるが、今回は `Pod` だったので削除された、という状況と思われる。
いずれにせよスケジューリング以外でも **NoExecute** を使うことで Pod を排除する事ができた。