背景
ある案件で以下のような小規模な Kubernetes クラスタを運用していました。
- Kubernetes には hoge というアプリケーションをデプロイしている
- hoge アプリケーションを乗せる用のノードは2ノードのみで、各ノードに hoge アプリケーションの Pod を1つずつ配置
そのため、hoge アプリケーションを乗せているノードのどちらかで障害が発生してしまうと、1ノードに Pod が偏ってしまいます。ノードを復旧しても何もしなければ Pod は偏ったままであり、その状態で Pod が偏った側のノードで障害が発生した場合、もう片方のノードに Pod が移るまでダウンタイムが発生してしまうという課題がありました。
ノード復旧後に手動で kubectl rollout restart などを実行すればこの課題は解決できますが、手動でのオペレーションは実施漏れが生じる可能性も高いので、ノードが復旧されたら自動で Pod の再配置を行う仕組みを用意したく、Descheduler for Kubernetes (以下、Descheduler と表記) を用いた Pod の再配置方法について調査してみました。
Descheduler の概要
Pod をどのノードに配置するかを考えるのは Pod をデプロイするタイミングだけですが、クラスタの状況は時と共に変化します。現在起動中の Pod はもしかすると他のノード上で動かす方が適切かもしれません。Descheduler はそういった Pod を検知して evict し、他のノードへの再配置を促してくれます。
リポジトリ : https://github.com/kubernetes-sigs/descheduler
実行方法
Job や CronJob、Deployment として実行することができます。
Helm チャートや Kustomize の manifest が用意されているので、簡単にデプロイできます。
Strategy
Descheduler では、evict 対象とすべき Pod の条件を Strategy を用いて設定することができます。現在は下記の Strategy が用意されています。
1. RemoveDuplicates
(a) 概要
- ReplicaSet や StatefulSet や Job に紐づく Pod が同じノードに 2 Pod 以上乗っていた場合に evict する。
ただし、該当 Pod を乗せられるノードが他に存在しない場合は evict しない。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
excludeOwnerKinds | list(string) | evict 対象から除外したい Kind を指定できる。 |
namespaces | namespaces.include に Namespace を指定した場合、その Namespace 内の Pod だけが evict 対象となる。namespaces.exclude に Namespace を指定した場合、それ以外の Namespace 内の Pod が evict 対象となる。※ namespaces.include と namespaces.exclude の併用は不可 | |
thresholdPriority | int | Priority がこの値以下の Pod のみが evict 対象となる。 ※ thresholdPriorityClassName との併用は不可 |
thresholdPriorityClassName | string | Priority がこの PriorityClass の値以下の Pod のみが evict 対象となる。 ※ thresholdPriority との併用は不可 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: RemoveDuplicates: enabled: true params: removeDuplicates: excludeOwnerKinds: - "Job" namespaces: include: - "namespace1" - "namespace2"
2. LowNodeUtilization
(a) 概要
- リソース使用率が閾値 (targetThresholds) よりも高いノード上の Pod を evict して、リソース使用率が閾値 (thresholds) よりも低いノードへの再配置を促す。
- リソース使用率が閾値 (targetThresholds) よりも高いノードが存在しない、もしくはリソース使用率が閾値 (thresholds) よりも低いノードが存在しない場合は何もしない。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
thresholds | map(string:int) | CPU やメモリなどの使用率 (requests / allocatable) を指定する。 ノードの各リソースの使用率が指定した値を全て下回った場合、より多くの Pod をスケジューリングすべきノードとして認識される。 |
targetThresholds | map(string:int) | CPU やメモリなどの使用率 (requests / allocatable) を指定できる。 ノードの各リソースの使用率が指定した値のいずれかを上回った場合、Pod を evict すべきノードとして認識される。 |
useDeviationThresholds | bool | true の場合、thresholds や targetThresholds の値は全ノードの平均リソース使用率からどれぐらいずれているかを示す値として扱われる。 例) 平均 CPU 使用率が 50% で、 thresholds.cpu が 20、targetThresholds.cpu が 20 とすると、CPU 使用率が 30% を切ると thresholds の条件を満たし、 CPU 使用率が 70% を超えると targetThresholds の条件を満たすことになる。 |
numberOfNodes | int | thresholds の条件を満たすノードが numberOfNodes 以上の場合のみ、evict するようにできる。規模が大きいクラスタで、リソース効率の悪いノードが頻繁に生じる場合などに使うことを想定している。 デフォルト値は 0。 |
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: LowNodeUtilization: enabled: true params: nodeResourceUtilizationThresholds: thresholds: cpu: 30 memory: 30 targetThresholds: cpu: 70 memory: 70
3. HighNodeUtilization
(a) 概要
- リソース使用率が閾値 (thresholds) よりも低いノード上の Pod を evict して、Pod を一部のノードに寄せる。
※ scheduler の scoring strategy をMostAllocated
にする必要がある - ノードの auto-scaling と一緒に利用して、リソース効率の悪いノードを削減するのが狙い。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
thresholds | map(string:int) | 2(b)で説明済みなので省略。 |
numberOfNodes | int | 2(b)で説明済みなので省略。 |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: HighNodeUtilization: enabled: true params: nodeResourceUtilizationThresholds: thresholds: cpu: 20 memory: 20
4. RemovePodsViolatingInterPodAntiAffinity
(a) 概要
- Inter-pod Anti-affinity に違反している Pod を evict する。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
labelSelector | labelSelector で evict 対象の Pod を指定できる。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: RemovePodsViolatingInterPodAntiAffinity: enabled: true
5. RemovePodsViolatingNodeAffinity
(a) 概要
- Node Affinity に違反している Pod を evict する。
(requiredDuringSchedulingIgnoredDuringExecution
がrequiredDuringSchedulingRequiredDuringExecution
のようになる)
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
nodeAffinityType | list(string) | 指定した Node Affinity の type を持つ Pod を evict 対象とする。 |
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
labelSelector | 4(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: RemovePodsViolatingNodeAffinity: enabled: true params: nodeAffinityType: - "requiredDuringSchedulingIgnoredDuringExecution"
6. RemovePodsViolatingNodeTaints
(a) 概要
NoSchedule
の Taint に違反している Pod を evict する。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
excludedTaints | list(string) | 指定した Taint に関しては、evict 対象外にできる。 |
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
labelSelector | 4(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: RemovePodsViolatingNodeTaints: enabled: true params: excludedTaints: - foo=bar - baz
7. RemovePodsViolatingTopologySpreadConstraint
(a) 概要
- Topology Spread Constraint に違反している Pod を evict する。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
includeSoftConstraints | bool | デフォルトは hard constraint (DoNotSchedule) に違反している Pod が evict 対象だが、 true にすることで soft constraint (ScheduleAnyway) に違反している Pod も evict 対象となる |
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
labelSelector | 4(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: RemovePodsViolatingTopologySpreadConstraint: enabled: true params: includeSoftConstraints: false
8. RemovePodsHavingTooManyRestarts
(a) 概要
- restart を繰り返している Pod を evict する。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
podRestartThreshold | int | restart 回数の閾値。 この値以上 restart が続いていたら evict 対象とする。 |
includingInitContainers | bool | true の場合、init container の restart 回数も加算する。 |
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
labelSelector | 4(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: RemovePodsHavingTooManyRestarts: enabled: true params: podsHavingTooManyRestarts: podRestartThreshold: 10 includingInitContainers: true
9. PodLifeTime
(a) 概要
- 一定時間経過した Pod を evict する。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
maxPodLifeTimeSeconds | int | この時間以上経過した Pod を evict 対象とする。 |
states | list(string) | 指定した状態にマッチする Pod だけ evict 対象にできる。 指定できる値は Pod の phase (Running, Pending) やコンテナの waiting 時の state (PodInitializing, ContainerCreating)。 |
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
labelSelector | 4(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: PodLifeTime: enabled: true params: podLifeTime: maxPodLifeTimeSeconds: 600 states: - "Pending" - "Waiting" - "PodInitializing"
10. RemoveFailedPods
(a) 概要
- failed 状態の Pod を evict する。
(b) 利用可能なパラメータ
Parameter | Type | Description |
---|---|---|
minPodLifeTimeSeconds | uint | この時間以上経過した Pod を evict 対象とする。 |
reasons | list(string) | failed の reason で evict 対象を絞ることができる。 |
includingInitContainers | bool | true にすると、init containers の reason も考慮してくれる |
excludeOwnerKinds | list(string) | 1(b)で説明済みなので省略。 |
namespaces | 1(b)で説明済みなので省略。 | |
thresholdPriority | int | 1(b)で説明済みなので省略。 |
thresholdPriorityClassName | string | 1(b)で説明済みなので省略。 |
labelSelector | 4(b)で説明済みなので省略。 |
(c) サンプル
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" strategies: RemoveFailedPods: enabled: true params: failedPods: minPodLifetimeSeconds: 3600 reasons: - "NodeAffinity" includingInitContainers: true
Policy
次のような Pod はデフォルトでは evict されません。
- Priority Class に
system-cluster-critical
やsystem-node-critical
が設定されているような critical な Pod (後述の evictSystemCriticalPods を用いれば evict 対象にできます) - ReplicaSet (Deployment) や StatefulSet や Job に紐づかない Pod (後述の evictFailedBarePods を用いれば evict 対象にできます)
- DaemonSet に紐づく Pod
- Local Storage を利用している Pod (後述の evictLocalStoragePods を用いれば evict 対象にできます)
- PVC をもつ Pod (後述の ignorePvcPods を用いれば evict 対象にできます)
Policy を設定することで、evict 対象を拡大したり制御することができます。
Policy | Default | Description |
---|---|---|
evictSystemCriticalPods | false | true にすると、system 系の Pod も evict 対象となる。 |
evictFailedBarePods | false | true にすると、ReplicaSet や StatefulSet や Job などに紐づいていない素の Pod が failed な状態の時に evict 対象となる。 |
evictLocalStoragePods | false | true にすると、Local Storage を利用している Pod も evict 対象となる。 |
ignorePvcPods | false | true にすると、PVC を利用している Pod も evict 対象となる。 |
nodeSelector | nil | 指定したノード上の Pod のみが evict 対象となる。 |
maxNoOfPodsToEvictPerNode | nil | 各ノードから evict 可能な Pod 数の上限を定められる。 |
maxNoOfPodsToEvictPerNamespace | nil | 各 Namespace から evict 可能な Pod 数の上限を定められる。 |
apiVersion: "descheduler/v1alpha1" kind: "DeschedulerPolicy" evictSystemCriticalPods: true evictFailedBarePods: false evictLocalStoragePods: true ignorePvcPods: false strategies: ...
※ PodDisruptionBudget に違反する場合は evict されません。
ログに関して
特にオプションを指定せずに起動すると出力されるログが少なく、evict されることを期待していた Pod が evict されなかったときの原因調査に困ります。
... I0815 18:10:02.287092 1 pod_antiaffinity.go:81] "Processing node" node="node01" I0815 18:10:02.314177 1 pod_antiaffinity.go:81] "Processing node" node="node02" I0815 18:10:02.339769 1 duplicates.go:99] "Processing node" node="node01" I0815 18:10:02.372760 1 duplicates.go:99] "Processing node" node="node02" I0815 18:10:02.400773 1 descheduler.go:152] "Number of evicted pods" totalEvicted=0
そんな時には起動時のオプションに --v=4
を追加すると、どの Pod をチェックして、evict されなかった場合はなぜ evict されなかったのかも出力してくれます。
... I0815 18:10:02.287092 1 pod_antiaffinity.go:81] "Processing node" node="node01" I0815 18:10:02.314177 1 pod_antiaffinity.go:81] "Processing node" node="node02" I0815 18:10:02.339769 1 duplicates.go:99] "Processing node" node="node01" I0815 18:10:02.372570 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="fluentd/fluentd-nrwcc" checks="[pod is a DaemonSet pod, pod has system critical priority, pod has higher priority than sp ecified priority class threshold]" I0815 18:10:02.372616 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/anetd-98nt8" checks="[pod is a DaemonSet pod, pod has system critical priority, pod has higher priority than specified priority class threshold]" I0815 18:10:02.372633 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/gke-metrics-agent-t558m" checks="pod is a DaemonSet pod" I0815 18:10:02.372657 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/kube-proxy-wrqdr" checks="[pod is a DaemonSet pod, pod has system critical priority, pod has higher priority than specified priority class threshold]" I0815 18:10:02.372680 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/localpv-fvjw2" checks="pod is a DaemonSet pod" I0815 18:10:02.372696 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/node-exporter-p2jkz" checks="pod is a DaemonSet pod" I0815 18:10:02.372714 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/stackdriver-log-forwarder-8dx22" checks="pod is a DaemonSet pod" I0815 18:10:02.372760 1 duplicates.go:99] "Processing node" node="node02" I0815 18:10:02.400589 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="fluentd/fluentd-9kwm5" checks="[pod is a DaemonSet pod, pod has system critical priority, pod has higher priority than sp ecified priority class threshold]" I0815 18:10:02.400635 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/anetd-xnr7z" checks="[pod is a DaemonSet pod, pod has system critical priority, pod has higher priority than specified priority class threshold]" I0815 18:10:02.400653 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/coredns-59f77cdb89-hvptb" checks="[pod has system critical priority, pod has higher priority than specified p riority class threshold]" I0815 18:10:02.400668 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/gke-metrics-agent-wlf9q" checks="pod is a DaemonSet pod" I0815 18:10:02.400683 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/kube-proxy-gdnsh" checks="[pod is a DaemonSet pod, pod has system critical priority, pod has higher priority than specified priority class threshold]" I0815 18:10:02.400695 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/localpv-fvnh2" checks="pod is a DaemonSet pod" I0815 18:10:02.400711 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/node-exporter-5ts7j" checks="pod is a DaemonSet pod" I0815 18:10:02.400723 1 evictions.go:300] "Pod lacks an eviction annotation and fails the following checks" pod="kube-system/stackdriver-log-forwarder-l9gzx" checks="pod is a DaemonSet pod" I0815 18:10:02.400773 1 descheduler.go:152] "Number of evicted pods" totalEvicted=0
バージョンに関して
Descheduler のバージョンと、それに対応する Kubernetes のバージョンは下記の通りです。
Descheduler | supported Kubernetes version |
---|---|
v0.25 | v1.25 |
v0.24 | v1.24 |
v0.23 | v1.23 |
v0.22 | v1.22 |
v0.21 | v1.21 |
Kubernetes のバージョンをアップグレードした際には、Descheduler のアップグレードもお忘れずに。
まとめ
- Descheduler を用いることで、定期的に Pod の再配置を促すことができ、可用性の向上やノードのリソース効率の改善などに繋げることができます。
- ただし再配置を行う際は、evict してからスケジューリングという流れになり、瞬間的に Pod 数が減ることになるので、PodDisruptionBudget を活用するなどして、サービス影響が生じないように注意が必要です。