[k8s]PodDisruptionBudgetを使ってみる

やってみただけのメモ的な記事であり、以下の Qiita の記事のほうが分かりやすいかと思います。

[Kubernetes: 複数の Node を安全に停止する (kubectl drain + PodDisruptionBudget)](https://qiita.com/tkusumi/items/946b0f31931d21a78058)


# 環境

```bash
$kubectl version
Client Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.3", GitCommit:"2bba0127d85d5a46ab4b778548be28623b32d0b0", GitTreeState:"clean", BuildDate:"2018-05-28T20:03:09Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"11+", GitVersion:"v1.11.8-eks-7c34c0", GitCommit:"7c34c0d2f2d0f11f397d55a46945193a0e22d8f3", GitTreeState:"clean", BuildDate:"2019-03-01T22:49:39Z", GoVersion:"go1.10.8", Compiler:"gc", Platform:"linux/amd64"}

# Node は3つ
$kubectl get nodes
NAME                                               STATUS    ROLES     AGE       VERSION
ip-172-31-22-177.ap-northeast-1.compute.internal   Ready     <none>    65d       v1.11.5
ip-172-31-28-5.ap-northeast-1.compute.internal     Ready     <none>    66d       v1.11.5
ip-172-31-4-131.ap-northeast-1.compute.internal    Ready     <none>    66d       v1.11.5
```

# 参考

公式だと以下あたりが参考になりそう

+ [Disruptions](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/)
+ [Specifying a Disruption Budget for your Application](https://kubernetes.io/docs/tasks/run-application/configure-pdb/)

以下も参考にさせていただきましたというかこちらに記載頂いた内容を検証してみました

+ [Kubernetes: 複数の Node を安全に停止する (kubectl drain + PodDisruptionBudget)](https://qiita.com/tkusumi/items/946b0f31931d21a78058)

# 概要

+ `kubectl drain` によって対象 Node から Pod を退去でき、以降も Pod がスケジューリングされないように出来る
+ 注意点として上記だけでは対象 Node 上で起動している Pod は一度に evicted され、一度に退去させられる可能性がある。例えば ReplicaSet 2 で drain 対象の Node に 2つの Pod が起動していた場合、両方の Pod に対して evicted され、一つも Pod が起動していない時間帯がある可能性が出来てしまう
+ PodDisruptionBudget(PDB)を定義することで上記を防ぐことが出来る

# やってみる

以下の内容をそのまま利用させて頂きます。

[Kubernetes: 複数の Node を安全に停止する (kubectl drain + PodDisruptionBudget)](https://qiita.com/tkusumi/items/946b0f31931d21a78058)の記事に記載がある内容を試させて頂きます。

上記では`initialDelaySeconds`を使って Pod が Running になるまでの時間が掛かるようにしていました。これは確かに便利そう。

```myapp-dep.yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 2 # レプリカ数の設定
  template:
    metadata:
      labels:
        run: myapp
    spec:
      containers:
      - image: nginx:1.13
        name: myapp
        readinessProbe:
          httpGet:
            path: /
            port: 80
initialDelaySeconds: 120 # ready まで 2 分かかる設定

```

また、PDB(PodDisruptionBudget)では `maxUnavailable: 1`を指定。これによって最大無効であってもよい Pod 数を示す。その為、ReplicaSet が 2の場合、一度に2つとも無効になることはない。

```myapp-pdb.yaml 
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: myapp
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
run: myapp
```

ということで試す。

まずはデプロイ。

```bash
# 2つの　Podをデプロイ
$kubectl apply -f myapp-dep.yaml
deployment.extensions "myapp" created

# READY になるまで少し時間が掛かる
$ kubectl get pod -l run=myapp
NAME                     READY     STATUS    RESTARTS   AGE
myapp-79ddcf65f8-hkvr8   0/1       Running   0          2m
myapp-79ddcf65f8-ssnc8   0/1       Running   0          2m

# 2分ほど経過するといずれも READY になる
$kubectl get pod -l run=myapp
NAME                     READY     STATUS    RESTARTS   AGE
myapp-79ddcf65f8-hkvr8   1/1       Running   0          2m
myapp-79ddcf65f8-ssnc8   1/1       Running   0          2m

# それぞれ以下の Node で起動している
$ kubectl get pod myapp-79ddcf65f8-hkvr8 -o=jsonpath={..spec.nodeName}
ip-172-31-28-5.ap-northeast-1.compute.internal


$kubectl get pod myapp-79ddcf65f8-ssnc8 -o=jsonpath='{.spec.nodeName}'
ip-172-31-4-131.ap-northeast-1.compute.internal

# 以下で見たほうがスマートだった...
$ kubectl get pods -o wide -l run=myapp
NAME                     READY     STATUS    RESTARTS   AGE       IP              NODE
myapp-79ddcf65f8-hkvr8   1/1       Running   0          17m       172.31.21.107   ip-172-31-28-5.ap-northeast-1.compute.internal
myapp-79ddcf65f8-ssnc8   1/1       Running   0          17m       172.31.0.239    ip-172-31-4-131.ap-northeast-1.compute.internal
```

2つの Node に Pod が配置された。
次に PDB も追加。

```
# 追加
$kubectl apply -f myapp-pdb.yaml

# PDB の作成を確認
$kubectl get pdb
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
myapp     N/A             1                 1                     30s

# 詳細を確認。現在の設定の場合、少なくとも1つの Pod は生きているように制御される
$kubectl get pdb -o yaml myapp
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"policy/v1beta1","kind":"PodDisruptionBudget","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"maxUnavailable":1,"selector":{"matchLabels":{"run":"myapp"}}}}
  creationTimestamp: 2019-05-07T00:09:43Z
  generation: 1
  name: myapp
  namespace: default
  resourceVersion: "20681705"
  selfLink: /apis/policy/v1beta1/namespaces/default/poddisruptionbudgets/myapp
  uid: 6a3083c6-705c-11e9-8fa9-068bb854a108
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      run: myapp
status:
  currentHealthy: 2
  desiredHealthy: 1
  disruptedPods: null
  disruptionsAllowed: 1
  expectedPods: 2
  observedGeneration: 1

```

OK.
準備ができたので対象 Node をそれぞれ drain する。

```bash
$ kubectl drain --ignore-daemonsets --force ip-172-31-28-5.ap-northeast-1.compute.internal

$ kubectl drain --ignore-daemonsets --force ip-172-31-4-131.ap-northeast-1.compute.internal

```


`-w`オプション(watch)を付与して監視

```bash
$kubectl get rs -w -l run=myapp

# ReplicaSet によって 起動されるが READY になるまでに2分必要なので READY は1
NAME               DESIRED   CURRENT   READY     AGE
myapp-79ddcf65f8   2         2         1         24m

# READY が２になる
myapp-79ddcf65f8   2         2         2         25m

# 上記によってもう一つの Pod も evicted 出来る
myapp-79ddcf65f8   2         1         1         25m

# 起動しているが READY  までには2分掛かるので待ち
myapp-79ddcf65f8   2         2         1         25m

# 2つとも READY になった
myapp-79ddcf65f8   2         2         2         38m

```

ということで実際の結果としても一 PDB を使うことで一斉に Pod が削除されることはなく、想定通り一つずつ削除された。