Kubernetes: Kubernetes Descheduler とは

kubernetes-incubator で開発が進められている、[Kubernetes Descheduler](https://github.com/kubernetes-incubator/descheduler) という Pod の再スケジューリングを行うためのコンポーネントについて調べてみました。[v0.6.0](https://github.com/kubernetes-incubator/descheduler/releases/tag/v0.6.0) 時点での情報をもとに記載しています。

## Kubernetes Descheduler とは
Kubernetes のスケジューラは Pod の作成時にしかスケジューリングを行わないため、長期的に見ると下記のような課題があります。

- ノードのリソース使用率に偏りがでてしまう
- スケジュール後に taints, label などが変更されても、スケジュール済みの Pod には反映されない
- 新しくノードが追加されても、その時点では Pod は分散されない

Kubernetes Descheduler はスケジュール済みの Pod 削除 (evict) し再スケジュールさせることでこの問題を解決します。Descheduler はあくまで Pod の削除のみを行い、再スケジュールは既存のスケジューラ( kube-scheduler ) が行います。

なおこの記事の記載時点(0.6.0)では開発中のため API の予告ない変更などがありえるため、プロダクション利用はまだ想定されていないようです[^1]。Descheduler は [sig-scheduling](https://github.com/kubernetes/community/tree/master/sig-scheduling) のサブプロジェクトとなっており、[SIG の議事録](https://docs.google.com/document/d/13mwye7nvrmV11q9_Eg77z-1w3X7Q1GTbslpml4J7F3A/edit)を見ると 2018/6/21 の meeting にて kubernetes 1.12 で標準コンポーネントに昇格させるというプランが記載されています。

## デモ

下記は descheduler を実行した際の Pod の動きになります。3 ノードの構成で一番右のノードは事前に `kubectl drain` で Pod を退避させ、その後に schedulable に設定しています。descheduler を実行すると他のノードから Pod が削除され、空いている右のノードに Pod が再配置されることが確認できます。

3 つの紫の四角がノード、緑の四角が Pod を表しています。可視化には [kube-ops-view](https://github.com/hjacobs/kube-ops-view) を利用しています。


![descheduler.gif](https://qiita-image-store.s3.amazonaws.com/0/100377/e42ee205-8858-d064-9f45-3235c3d618f0.gif)

Kubernetes Descheduler をビルドし下記のようにローカル環境から Kubernetes クラスタに対して実行しました。Descheduler は Pod の再配置(削除)が一回終わると終了します。

```
$ descheduler --kubeconfig ~/.kube/config --policy-config-file ./policy.yaml -v 4
```

policy.yaml は下記の設定実行しました。上記デモでは DeschedulerPolicy (後述) だけを設定しています。

```yaml
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "RemoveDuplicates":
     enabled: true
```


## Descheduler のポリシー

Descheduler は複数のストラテジーを組み合わせて、削除する Pod のポリシーを設定ファイルで定義します。現時点では下記の 4 つのストラテジーがあります。

### RemoveDuplicates

同一の ReplicaSet , ReplicationController から作られた Pod が、ひとつのノードに複数配置されていた場合に Pod を削除します。kube-scheduler にはノードやゾーンに対して分散させる `SelectorSpreadPriority` という priority がデフォルトで入っているため、Pod がノードに対して再度分散するようになります。

例えば下記のように同一 ReplicaSet に属する Pod (App1) が Node 1 に複数存在する場合に削除を行い、Node 3 に Pod が再配置されるように動作します。Node 3 に障害があって復旧した場合などにこの最初の状況がありえます。

```
# Initial state

| App1 |  | App1 |  |      |
| App1 |  |      |  |      |
|      |  |      |  |      |
|      |  |      |  |      |
|      |  |      |  |      |
+------+  +------+  +------+
  Node1     Node2     Node3


# After descheduling

| App1 |  | App1 |  | App1 |
|      |  |      |  |      |
|      |  |      |  |      |
|      |  |      |  |      |
|      |  |      |  |      |
+------+  +------+  +------+
  Node1     Node2     Node3
```


### LowNodeUtilization
リソースの利用率が高いノードの Pod をリソース利用率が低いノードに再スケジュールをすることを目的としたストラテジーです。リソースの使用率は CPU、RAM、Pod 数を使って閾値を定義します。Pod の削除となるノードのリソースの使用率の下限は `targetThresholds`、Pod の移動先となるノードの利用率の上限は `thresholds`で設定します。二つの閾値の間のノードは処理に影響しません。

リソースの計算には Pod の Resource Request の値 (`spec.containers[].resources.requests`) が設定されている必要があります。基本的な動作としては、リソースが空いているノードの空きリソースの合計を計算し、その分だけリソースが高いノードから Pod を削除するという動きになります。

```yaml
apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  "LowNodeUtilization":
     enabled: true
     params:
       nodeResourceUtilizationThresholds:
         thresholds:
           "cpu" : 20
           "memory": 20
           "pods": 20
         targetThresholds:
           "cpu" : 50
           "memory": 50
           "pods": 50
```

### RemovePodsViolatingInterPodAntiAffinity

[Pod 間の AntiAffinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) に反している Pod を削除します。Pod 間の AntiAffinity とは特定のラベルを持った Pod 同士を一緒のノードにスケジューリングさせたくないという指定です。AntiAffinity はスケジューリング時点でしか考慮されないため、何らかの理由でスケジュール後にこのルールを満たさなくなった Pod が存在した場合に削除を行います。

### RemovePodsViolatingNodeAffinity

[Node の AntiAffinity](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) に反している Pod を削除します。Node の Affinity は特定の種類のノードに対して Pod をスケジューリングさせるための機能です。ノードの指定にはラベルを利用するため、ラベルの付け替えによって現時点ではこのルールを満たしていない状況があります。

## 実行方法
[README.md](https://github.com/kubernetes-incubator/descheduler) ではkubeconfig を指定してクラスタ外から実行する方法と、クラスタ内で Job を使って実行する方法が紹介されています。バイナリを実行すると一回該当する Pod を削除して終了するので、現時点では実行するタイミングは各自で制御する必要があるようです。

## Pod の退避
Pod の退避(削除) は下記のルールで行われます。Pod は eviction の仕組みを使って退避されるため、PodDisruptionBudget の設定も考慮されます。

- ` scheduler.alpha.kubernetes.io/critical-pod` のアノテーションのついている Critical Pod は対象外
- ReplicaSet、Job などで管理されていない Pod は再作成されないため対象外
- DaemonSet が作成した Pod は対象外
- Local Storage を利用したPod は対象外
- Pod の [QosClass](https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/) にBestEffort を指定したものは Bustrable、Guaranteed より先に退避させられる

## まとめ
Kubernetes Descheduler を使用することで、Pod の再スケジュールを行うことができます。0.6.0 の時点で基本的な機能は実装されていますが、開発中のためにプロダクション利用はまだ想定されていないようです[^1]。 [sig-scheduling](https://github.com/kubernetes/community/tree/master/sig-scheduling) にて Kubernetes v1.12 で標準コンポーネントに昇格させるプランもあるようなので、これからの動きに期待しています。

## 参考
- [GitHub - kubernetes-incubator/descheduler: Descheduler for Kubernetes](https://github.com/kubernetes-incubator/descheduler)
- [Meet a Kubernetes Descheduler](https://akomljen.com/meet-a-kubernetes-descheduler/)
- https://qiita.com/tkusumi/items/58fdadbe4053812cb44e

[^1]: https://github.com/kubernetes-incubator/descheduler/tree/v0.6.0#note