K8s Descheduler 리밸런싱

K8s Descheduler란?

Kubernetes 스케줄러는 Pod를 최초 배치할 때만 동작한다. 노드가 추가되거나, 리소스 사용 패턴이 변하거나, Taint/Label이 바뀌어도 이미 실행 중인 Pod는 재배치되지 않는다. 이 문제를 해결하는 것이 Descheduler다.

Descheduler는 실행 중인 Pod를 정책 기반으로 축출(evict)하여, 기본 스케줄러가 더 나은 노드에 재배치하도록 유도한다. 클러스터 리밸런싱, 노드 활용률 균등화, 정책 위반 Pod 정리 등에 필수적인 운영 도구다.

왜 Descheduler가 필요한가?

실제 운영 환경에서 흔히 발생하는 시나리오들이다:

  • 노드 스케일 아웃 후 불균형: 새 노드가 추가되었지만 기존 Pod는 이동하지 않아 새 노드가 비어 있다
  • Taint/Toleration 변경: 노드에 Taint를 추가했지만 기존 Pod는 Toleration 없이 계속 실행된다
  • Anti-Affinity 위반: 스케줄링 시점에는 문제없었지만, 이후 변경으로 Anti-Affinity 규칙을 위반하는 상태가 된다
  • 리소스 핫스팟: 특정 노드에 리소스 사용이 집중되어 성능 저하가 발생한다

설치 및 기본 구성

Helm으로 간단히 설치할 수 있다:

helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm repo update

helm install descheduler descheduler/descheduler 
  --namespace kube-system 
  --set schedule="*/5 * * * *"

기본적으로 CronJob으로 배포되며, 지정된 주기마다 정책을 평가하고 Pod를 축출한다. v0.28+부터는 Deployment 모드도 지원하여 지속적 모니터링이 가능하다.

핵심 Eviction 전략 6가지

Descheduler는 플러그인 기반 전략을 제공한다. 각 전략의 동작 원리와 설정법을 살펴보자.

1. RemoveDuplicates

동일 노드에서 같은 ReplicaSet/Deployment의 Pod가 여러 개 실행될 때, 중복 Pod를 축출하여 가용성을 높인다.

apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: default
    pluginConfig:
      - name: RemoveDuplicates
        args:
          excludeOwnerKinds:
            - "DaemonSet"
          namespaces:
            include:
              - "production"
              - "staging"
    plugins:
      balance:
        enabled:
          - RemoveDuplicates

운영 팁: DaemonSet은 반드시 제외하라. 노드당 1개가 의도된 배치이므로 중복으로 판단하면 안 된다.

2. LowNodeUtilization

노드 간 리소스 사용률 불균형을 해소한다. thresholds 이하인 노드를 “저활용”, targetThresholds 이상인 노드를 “과활용”으로 분류하고, 과활용 노드에서 Pod를 축출한다.

- name: LowNodeUtilization
  args:
    thresholds:
      cpu: 20
      memory: 20
      pods: 15
    targetThresholds:
      cpu: 50
      memory: 50
      pods: 40
    # 실제 사용량 기반 (requests 대신)
    useDeviationThresholds: false
    evictableNamespaces:
      include:
        - "default"
        - "production"

CPU 20% 미만 노드가 있고 50% 이상 노드가 있을 때, 50% 이상 노드의 Pod를 축출하여 균형을 맞춘다.

3. HighNodeUtilization

LowNodeUtilization의 반대 전략이다. 저활용 노드의 Pod를 축출하여 다른 노드로 통합(bin-packing)한다. 클라우드 환경에서 노드 수를 줄여 비용을 절감할 때 유용하다.

- name: HighNodeUtilization
  args:
    thresholds:
      cpu: 20
      memory: 20
    evictableNamespaces:
      include:
        - "default"

주의: Cluster Autoscaler와 함께 사용하면, 저활용 노드의 Pod를 이동시킨 후 빈 노드가 자동 축소되어 비용 최적화가 극대화된다.

4. RemovePodsViolatingNodeAffinity

노드의 Label이 변경되어 requiredDuringSchedulingIgnoredDuringExecution 규칙을 위반하게 된 Pod를 축출한다.

- name: RemovePodsViolatingNodeAffinity
  args:
    nodeAffinityType:
      - requiredDuringSchedulingIgnoredDuringExecution

“IgnoredDuringExecution”이라는 이름처럼 Kubernetes가 기본적으로 무시하는 위반을 Descheduler가 감지하고 교정한다.

5. RemovePodsViolatingInterPodAntiAffinity

Pod 간 Anti-Affinity 규칙을 위반하는 Pod를 축출한다. 노드 장애 후 복구 시 같은 노드에 Pod가 몰리는 상황에서 특히 유용하다.

- name: RemovePodsViolatingInterPodAntiAffinity

6. RemovePodsViolatingTopologySpreadConstraint

TopologySpreadConstraints의 maxSkew를 위반하는 Pod를 축출한다.

- name: RemovePodsViolatingTopologySpreadConstraint
  args:
    constraints:
      - DoNotSchedule
      - ScheduleAnyway

실전 운영 설정: 프로덕션 DeschedulerPolicy

실제 프로덕션에서 사용할 수 있는 종합 정책이다:

apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: production
    pluginConfig:
      - name: DefaultEvictor
        args:
          # PDB 존중 (필수!)
          ignorePvcPods: false
          evictLocalStoragePods: false
          # 시스템 네임스페이스 보호
          evictSystemCriticalPods: false
          # 축출 비율 제한
          maxNoOfPodsToEvictPerNode: 3
          maxNoOfPodsToEvictPerNamespace: 5
      - name: LowNodeUtilization
        args:
          thresholds:
            cpu: 25
            memory: 25
          targetThresholds:
            cpu: 60
            memory: 60
      - name: RemoveDuplicates
        args:
          excludeOwnerKinds:
            - "DaemonSet"
      - name: RemovePodsViolatingNodeAffinity
        args:
          nodeAffinityType:
            - requiredDuringSchedulingIgnoredDuringExecution
      - name: RemovePodsViolatingTopologySpreadConstraint
        args:
          constraints:
            - DoNotSchedule
    plugins:
      balance:
        enabled:
          - LowNodeUtilization
          - RemoveDuplicates
          - RemovePodsViolatingTopologySpreadConstraint
      deschedule:
        enabled:
          - RemovePodsViolatingNodeAffinity

PDB와의 연동: 안전한 축출

Descheduler는 PodDisruptionBudget(PDB)를 존중한다. PDB가 설정된 경우, minAvailable/maxUnavailable 제약을 초과하는 축출은 발생하지 않는다.

# PDB 설정 예시 - Descheduler 축출 시에도 적용됨
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: api-server

반드시 중요 워크로드에는 PDB를 설정하라. Descheduler가 한 번에 모든 Pod를 축출하여 서비스 중단이 발생하는 것을 방지한다.

DefaultEvictor 세부 설정

모든 전략에 공통 적용되는 축출 필터 설정이다:

파라미터 기본값 설명
evictLocalStoragePods false emptyDir 사용 Pod 축출 여부
evictSystemCriticalPods false system-critical Pod 축출 여부
ignorePvcPods false PVC 마운트 Pod 무시 여부
maxNoOfPodsToEvictPerNode 무제한 노드당 최대 축출 Pod 수
maxNoOfPodsToEvictPerNamespace 무제한 네임스페이스당 최대 축출 Pod 수
minReplicas 0 이 수 이하의 replica는 축출하지 않음

Deployment 모드 vs CronJob 모드

v0.28부터 두 가지 실행 모드를 지원한다:

# CronJob 모드 (기본) - 주기적 실행
helm install descheduler descheduler/descheduler 
  --set schedule="*/10 * * * *"

# Deployment 모드 - 지속 실행 + 간격 제어
helm install descheduler descheduler/descheduler 
  --set kind=Deployment 
  --set deschedulingInterval=5m

CronJob 모드는 리소스를 아끼고 싶을 때, Deployment 모드는 실시간에 가까운 리밸런싱이 필요할 때 사용한다.

모니터링: 메트릭 연동

Descheduler는 Prometheus 메트릭을 노출한다. Prometheus + Grafana와 연동하여 축출 현황을 추적하자.

# 주요 메트릭
descheduler_pods_evicted{result="success",strategy="LowNodeUtilization"}
descheduler_pods_evicted{result="error",strategy="RemoveDuplicates"}
descheduler_build_info

# Grafana PromQL 예시
# 전략별 축출 Pod 수 (5분 단위)
sum by (strategy) (
  increase(descheduler_pods_evicted{result="success"}[5m])
)

# 축출 실패율 알림
sum(rate(descheduler_pods_evicted{result="error"}[10m])) /
sum(rate(descheduler_pods_evicted[10m])) > 0.3

Cluster Autoscaler와 조합

Descheduler + Cluster Autoscaler(또는 Karpenter)의 조합은 비용 최적화의 핵심이다:

  1. HighNodeUtilization 전략으로 저활용 노드의 Pod를 다른 노드로 통합
  2. 비게 된 노드를 Cluster Autoscaler가 자동 축소
  3. 클라우드 비용 절감
# HighNodeUtilization + Cluster Autoscaler 조합
- name: HighNodeUtilization
  args:
    thresholds:
      cpu: 30    # CPU 30% 미만 노드의 Pod 축출
      memory: 30
    numberOfNodes: 1  # 최소 1개 저활용 노드가 있을 때만 동작

운영 주의사항과 베스트 프랙티스

  • PDB 필수 설정: 모든 프로덕션 워크로드에 PDB를 설정하라. Descheduler의 축출은 PDB를 존중하지만, PDB가 없으면 모든 Pod가 한 번에 축출될 수 있다
  • 점진적 도입: maxNoOfPodsToEvictPerNode를 낮게 시작하고 (2~3), 안정성을 확인한 후 늘려라
  • Dry-run 먼저: --dry-run 플래그로 어떤 Pod가 축출 대상인지 먼저 확인하라
  • 네임스페이스 제한: evictableNamespaces로 대상을 명확히 지정하라. kube-system은 제외하는 것이 안전하다
  • StatefulSet 주의: PVC가 있는 StatefulSet Pod는 축출 후 동일 노드가 아니면 재스케줄링이 실패할 수 있다. ignorePvcPods: true를 고려하라
  • Spot 인스턴스 환경: Spot 노드 축소 시 Descheduler가 미리 Pod를 이동시켜 놓으면, 강제 종료 시 영향을 최소화할 수 있다

정리

Descheduler는 Kubernetes 클러스터의 런타임 리밸런싱 도구다. 기본 스케줄러가 “최초 배치”만 담당하는 한계를 보완하여, 운영 중 발생하는 불균형·정책 위반을 자동으로 교정한다. PDB와 함께 사용하면 서비스 안정성을 유지하면서도 리소스 효율을 극대화할 수 있다. Cluster Autoscaler/Karpenter와 조합하면 비용 최적화까지 달성할 수 있는 필수 운영 도구다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux