Descheduler for Kubernetes で Pod の再配置

Ryoma Aoki

2022.12.13

背景

ある案件で以下のような小規模な 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) 利用可能なパラメータ

ParameterTypeDescription
excludeOwnerKindslist(string)evict 対象から除外したい Kind を指定できる。
namespacesnamespaces.include に Namespace を指定した場合、その Namespace 内の Pod だけが evict 対象となる。
namespaces.exclude に Namespace を指定した場合、それ以外の Namespace 内の Pod が evict 対象となる。
namespaces.includenamespaces.exclude の併用は不可
thresholdPriorityintPriority がこの値以下の Pod のみが evict 対象となる。
thresholdPriorityClassName との併用は不可
thresholdPriorityClassName stringPriority がこの 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) 利用可能なパラメータ

ParameterTypeDescription
thresholdsmap(string:int)CPU やメモリなどの使用率 (requests / allocatable) を指定する。
ノードの各リソースの使用率が指定した値を全て下回った場合、より多くの Pod をスケジューリングすべきノードとして認識される。
targetThresholdsmap(string:int)CPU やメモリなどの使用率 (requests / allocatable) を指定できる。
ノードの各リソースの使用率が指定した値のいずれかを上回った場合、Pod を evict すべきノードとして認識される。
useDeviationThresholdsbooltrue の場合、thresholdstargetThresholds の値は
全ノードの平均リソース使用率からどれぐらいずれているかを示す値として扱われる。
例) 平均 CPU 使用率が 50% で、thresholds.cpu が 20、targetThresholds.cpu が 20 とすると、
CPU 使用率が 30% を切ると thresholds の条件を満たし、
CPU 使用率が 70% を超えると targetThresholds の条件を満たすことになる。
numberOfNodesintthresholds の条件を満たすノードが numberOfNodes 以上の場合のみ、evict するようにできる。
規模が大きいクラスタで、リソース効率の悪いノードが頻繁に生じる場合などに使うことを想定している。
デフォルト値は 0。
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassName string1(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) 利用可能なパラメータ

ParameterTypeDescription
thresholdsmap(string:int)2(b)で説明済みなので省略。
numberOfNodesint2(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassName string1(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) 利用可能なパラメータ

ParameterTypeDescription
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassNamestring1(b)で説明済みなので省略。
labelSelectorlabelSelector で evict 対象の Pod を指定できる。

(c) サンプル

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  RemovePodsViolatingInterPodAntiAffinity:
    enabled: true

5. RemovePodsViolatingNodeAffinity

(a) 概要

  • Node Affinity に違反している Pod を evict する。
    (requiredDuringSchedulingIgnoredDuringExecutionrequiredDuringSchedulingRequiredDuringExecution のようになる)

(b) 利用可能なパラメータ

ParameterTypeDescription
nodeAffinityTypelist(string)指定した Node Affinity の type を持つ Pod を evict 対象とする。
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassNamestring1(b)で説明済みなので省略。
labelSelector4(b)で説明済みなので省略。

(c) サンプル

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  RemovePodsViolatingNodeAffinity:
    enabled: true
    params:
      nodeAffinityType:
      - "requiredDuringSchedulingIgnoredDuringExecution"

6. RemovePodsViolatingNodeTaints

(a) 概要

  • NoSchedule の Taint に違反している Pod を evict する。

(b) 利用可能なパラメータ

ParameterTypeDescription
excludedTaintslist(string)指定した Taint に関しては、evict 対象外にできる。
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassNamestring1(b)で説明済みなので省略。
labelSelector4(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) 利用可能なパラメータ

ParameterTypeDescription
includeSoftConstraintsboolデフォルトは hard constraint (DoNotSchedule) に違反している Pod が evict 対象だが、
true にすることで soft constraint (ScheduleAnyway) に違反している Pod も evict 対象となる
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassName string1(b)で説明済みなので省略。
labelSelector4(b)で説明済みなので省略。

(c) サンプル

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  RemovePodsViolatingTopologySpreadConstraint:
    enabled: true
    params:
      includeSoftConstraints: false

8. RemovePodsHavingTooManyRestarts

(a) 概要

  • restart を繰り返している Pod を evict する。

(b) 利用可能なパラメータ

ParameterTypeDescription
podRestartThresholdintrestart 回数の閾値。
この値以上 restart が続いていたら evict 対象とする。
includingInitContainersbooltrue の場合、init container の restart 回数も加算する。
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassNamestring1(b)で説明済みなので省略。
labelSelector4(b)で説明済みなので省略。

(c) サンプル

apiVersion: "descheduler/v1alpha1"
kind: "DeschedulerPolicy"
strategies:
  RemovePodsHavingTooManyRestarts:
    enabled: true
    params:
      podsHavingTooManyRestarts:
        podRestartThreshold: 10
        includingInitContainers: true

9. PodLifeTime

(a) 概要

  • 一定時間経過した Pod を evict する。

(b) 利用可能なパラメータ

ParameterTypeDescription
maxPodLifeTimeSecondsintこの時間以上経過した Pod を evict 対象とする。
stateslist(string)指定した状態にマッチする Pod だけ evict 対象にできる。
指定できる値は Pod の phase (Running, Pending) やコンテナの waiting 時の state (PodInitializing, ContainerCreating)。
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassName string1(b)で説明済みなので省略。
labelSelector4(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) 利用可能なパラメータ

ParameterTypeDescription
minPodLifeTimeSecondsuintこの時間以上経過した Pod を evict 対象とする。
reasonslist(string)failed の reason で evict 対象を絞ることができる。
includingInitContainersbooltrue にすると、init containers の reason も考慮してくれる
excludeOwnerKindslist(string)1(b)で説明済みなので省略。
namespaces1(b)で説明済みなので省略。
thresholdPriorityint1(b)で説明済みなので省略。
thresholdPriorityClassNamestring1(b)で説明済みなので省略。
labelSelector4(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-criticalsystem-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 対象を拡大したり制御することができます。

PolicyDefaultDescription
evictSystemCriticalPodsfalsetrue にすると、system 系の Pod も evict 対象となる。
evictFailedBarePodsfalsetrue にすると、ReplicaSet や StatefulSet や Job などに紐づいていない素の Pod が failed な状態の時に evict 対象となる。
evictLocalStoragePodsfalsetrue にすると、Local Storage を利用している Pod も evict 対象となる。
ignorePvcPodsfalsetrue にすると、PVC を利用している Pod も evict 対象となる。
nodeSelectornil指定したノード上の Pod のみが evict 対象となる。
maxNoOfPodsToEvictPerNodenil各ノードから 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 のバージョンは下記の通りです。

Deschedulersupported Kubernetes version
v0.25v1.25
v0.24v1.24
v0.23v1.23
v0.22v1.22
v0.21v1.21

Kubernetes のバージョンをアップグレードした際には、Descheduler のアップグレードもお忘れずに。

まとめ

  • Descheduler を用いることで、定期的に Pod の再配置を促すことができ、可用性の向上やノードのリソース効率の改善などに繋げることができます。
  • ただし再配置を行う際は、evict してからスケジューリングという流れになり、瞬間的に Pod 数が減ることになるので、PodDisruptionBudget を活用するなどして、サービス影響が生じないように注意が必要です。

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ