Pod QoS 클래스

Pod QoS 클래스 (Pod Quality of Service Classes)

이 페이지는 Kubernetes의 QoS(Quality of Service, 서비스 품질) 클래스를 소개하고, Kubernetes가 그 Pod의 컨테이너에 지정한 리소스 제약의 결과로 각 Pod에 QoS 클래스를 어떻게 부여하는지 설명합니다. Kubernetes는 노드에 사용 가능한 리소스가 충분하지 않을 때 어떤 Pod를 축출할지 결정하기 위해 이 분류에 의존합니다.

출처: 문서

본문

QoS 클래스 (Quality of Service classes)

Kubernetes는 실행하는 Pod를 분류하고 각 Pod를 특정 QoS(Quality of Service) 클래스에 할당합니다. Kubernetes는 이 분류를 사용해 서로 다른 Pod가 어떻게 처리되는지에 영향을 줍니다. Kubernetes는 그 Pod의 컨테이너 리소스 요청과, 그 요청이 리소스 limit과 어떻게 관련되는지에 기반해 분류합니다. 이것을 QoS(Quality of Service) 클래스라고 해요. Kubernetes는 구성 컨테이너의 리소스 requests와 limits를 기반으로 모든 Pod에 QoS 클래스를 부여합니다. QoS 클래스는 Kubernetes가 노드 압력(Node Pressure)을 겪는 노드에서 어떤 Pod를 축출할지 결정하는 데 사용됩니다. 가능한 QoS 클래스는 Guaranteed, Burstable, BestEffort입니다. 노드가 리소스가 부족해지면 Kubernetes는 먼저 그 노드에서 실행 중인 BestEffort Pod를 축출하고, 그다음 Burstable, 마지막으로 Guaranteed Pod를 축출합니다. 이 축출이 리소스 압력 때문일 때는 리소스 요청을 초과하는 Pod만 축출 후보입니다.

Guaranteed

Guaranteed Pod는 가장 엄격한 리소스 limit을 가지며 축출에 직면할 가능성이 가장 낮습니다. 자신의 limit을 초과하거나 노드에서 선점할 수 있는 더 낮은 우선순위 Pod가 없을 때까지 죽지 않는다는 보장을 받습니다. 지정된 limit을 넘어서는 리소스를 획득할 수는 없습니다. 이러한 Pod는 정적 CPU 관리 정책을 사용해 전용 CPU를 활용할 수도 있습니다.

기준 (Criteria)

Pod에 QoS 클래스 Guaranteed가 부여되려면:

  • Pod의 모든 컨테이너가 0보다 큰 메모리 limit과 메모리 request를 가져야 합니다.
  • Pod의 모든 컨테이너에 대해 메모리 limit이 메모리 request와 같아야 합니다.
  • Pod의 모든 컨테이너가 0보다 큰 CPU limit과 CPU request를 가져야 합니다.
  • Pod의 모든 컨테이너에 대해 CPU limit이 CPU request와 같아야 합니다.

대신 Pod가 Pod 수준 리소스를 사용한다면:

  • Pod가 Pod 수준 메모리 limit과 메모리 request를 가져야 하며, 그 값이 같아야 합니다.
  • Pod가 Pod 수준 CPU limit과 CPU request를 가져야 하며, 그 값이 같아야 합니다.

Burstable

Burstable Pod는 request에 기반한 하한 리소스 보장을 일부 가지지만, 특정 limit을 요구하지는 않습니다. limit이 지정되지 않으면 노드의 용량과 동일한 limit으로 기본 설정되어, 리소스가 사용 가능하면 Pod가 리소스를 유연하게 늘릴 수 있게 해줍니다. 노드 리소스 압력으로 인한 Pod 축출의 경우, 이 Pod들은 모든 BestEffort Pod가 축출된 후에만 축출됩니다. Burstable Pod는 limit이나 request가 없는 컨테이너를 포함할 수 있으므로, Burstable인 Pod는 노드 리소스를 얼마든지 사용하려고 시도할 수 있어요.

기준 (Criteria)

Pod는 다음 조건이 성립하면 QoS 클래스 Burstable이 부여됩니다:

  • Pod가 QoS 클래스 Guaranteed의 기준을 충족하지 않습니다.
  • Pod의 최소 하나의 컨테이너가 메모리 또는 CPU request나 limit을 가지거나, Pod가 Pod 수준 메모리 또는 CPU request나 limit을 가집니다.

BestEffort

BestEffort QoS 클래스의 Pod는 다른 QoS 클래스의 Pod에 특별히 할당되지 않은 노드 리소스를 사용할 수 있습니다. 예를 들어 kubelet이 사용할 수 있는 16 CPU 코어가 있는 노드에서 Guaranteed Pod에 4 CPU 코어를 할당했다면, BestEffort QoS 클래스의 Pod는 나머지 12 CPU 코어를 얼마든지 사용하려고 할 수 있어요.

kubelet은 노드가 리소스 압박 상태에 오면 BestEffort Pod를 축출하는 것을 선호합니다.

기준 (Criteria)

Pod는 Guaranteed 또는 Burstable 기준을 충족하지 않으면 BestEffort QoS 클래스를 가집니다. 다시 말해, Pod의 어떤 컨테이너도 메모리 limit이나 메모리 request를 가지지 않고, 어떤 컨테이너도 CPU limit이나 CPU request를 가지지 않으며, Pod가 어떤 Pod 수준 메모리나 CPU limit이나 requests도 가지지 않을 때만 BestEffort입니다. Pod의 컨테이너는 다른 리소스(CPU나 메모리가 아닌)를 요청할 수 있으며 여전히 BestEffort로 분류됩니다.

cgroup v2를 사용한 메모리 QoS (Memory QoS with cgroup v2)

메모리 QoS는 cgroup v2의 메모리 컨트롤러를 사용해 Kubernetes에서 메모리 스로틀링(throttling)과 보호를 관리합니다. Pod의 QoS 클래스를 사용해 어떤 cgroup 설정을 적용할지 결정하지만, Pod가 어떻게 분류되는지는 바꾸지 않습니다.

기본 kubelet 구성은 메모리 스로틀링이나 메모리 보호를 활성화하지 않습니다. 어느 한 동작을 활성화하려면 각각 memoryThrottlingFactor 또는 memoryReservationPolicy를 구성하세요.

메모리 스로틀링 (Memory throttling)

메모리 스로틀링은 kubelet 구성 필드 memoryThrottlingFactor로 제어됩니다. 기본값은 nil이며, 이는 kubelet이 memory.high를 설정하지 않는다는 뜻입니다. 메모리 스로틀링을 활성화하려면 memoryThrottlingFactor를 0보다 크고 1보다 작거나 같은 값으로 설정하세요.

Burstable Pod의 경우 kubelet은 워크로드가 하드 limit(memory.max)에 도달하기 전에 메모리 할당을 스로틀링하기 위해 memory.high를 사용합니다. 컨테이너의 스로틀링 임계값은 다음과 같이 계산됩니다:

memory.high = requests + memoryThrottlingFactor * (limits - requests)

예를 들어 memoryThrottlingFactor0.9로 설정하면, request가 256 MiB이고 limit이 1 GiB인 컨테이너의 memory.high는 대략 947 MiB로 설정됩니다. Burstable 컨테이너에 메모리 limit이 없으면 kubelet은 limit 대신 노드 할당 가능 메모리(node allocatable memory)를 사용합니다.

메모리 request나 limit이 없는 BestEffort 컨테이너의 경우, kubelet은 request를 0으로 하고 limit 대신 노드 할당 가능 메모리를 사용해 memory.high를 계산합니다. Guaranteed 컨테이너는 request가 limit과 같으므로 memory.high를 받지 않습니다.

메모리 예약 구성 (Configuring memory reservation)

메모리 예약은 kubelet 구성 필드 memoryReservationPolicy로 제어됩니다:

  • None (기본값): kubelet은 컨테이너와 Pod에 memory.min이나 memory.low를 설정하지 않습니다. 커널이 메모리를 하드-잠금하지 않습니다.
  • TieredReservation: kubelet은 Pod의 QoS 클래스에 따라 계층적 메모리 보호를 설정합니다:
    • Guaranteed Pod: memory.min이 메모리 requests로 설정됩니다. 커널은 어떤 상황에서도 이 메모리를 회수하지 않습니다.
    • Burstable Pod: memory.low가 메모리 requests로 설정됩니다. 커널은 이 메모리를 우선적으로 유지하지만 극심한 압박에서는 회수할 수 있습니다.
    • BestEffort Pod: 메모리 보호가 설정되지 않습니다.

예를 들어 다음 kubelet 구성은 0.9의 요인으로 메모리 스로틀링을 활성화하고 계층적 메모리 보호를 활성화합니다:

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
memoryThrottlingFactor: 0.9
memoryReservationPolicy: TieredReservation

두 필드를 독립적으로 구성할 수 있어요. memoryThrottlingFactor를 생략해 kubelet이 memory.high를 설정하지 않게 하거나, memoryReservationPolicy: None을 설정해 메모리 보호를 비활성화하세요.

cgroup v2 메모리 보호는 계층적이므로 kubelet은 상위 cgroup도 구성합니다. Guaranteed 및 Burstable Pod의 메모리 requests의 합으로 kubepods 루트 cgroup에 memory.min을 설정합니다. Burstable Pod의 메모리 requests의 합으로 kubepods 루트 cgroup과 Burstable QoS cgroup에 memory.low를 설정합니다. 이 상위 cgroup 범위가 없으면 Pod별 및 컨테이너별 보호가 효과가 없습니다.

주의: Guaranteed Pod의 경우 메모리 requests가 메모리 limits와 같습니다. 따라서 memoryReservationPolicy: TieredReservation에서는 memory.minmemory.max와 같습니다. 큰 페이지 캐시를 사용하는 워크로드의 경우, 커널이 cgroup이 memory.max에 도달하기 전에 충분한 페이지 캐시를 회수하지 못할 수 있으며, 이는 OOM kill로 이어질 수 있습니다. 페이지 캐시에 충분한 여유(headroom)가 포함되도록 메모리 limit의 크기를 정하세요.

메모리 QoS 비활성화 또는 롤백 (Disabling or rolling back Memory QoS)

메모리 QoS를 비활성화하려면 MemoryQoS 기능 게이트를 false로 설정하고, memoryReservationPolicy가 설정되지 않았거나 None으로 설정되었는지 확인한 후 kubelet을 재시작하세요. 시작 시 kubelet은 kubepods 루트 cgroup의 memory.minmemory.low, Burstable QoS cgroup의 memory.low를 0으로 재설정합니다. Pod 수준 및 컨테이너 수준 memory.minmemory.low 값은 남을 수 있지만, 해당 상위 보호가 0이므로 효과가 없습니다.

메모리 QoS가 비활성화되면 kubelet은 컨테이너 런타임이 새, 재시작된, 또는 제자리 리사이즈된 컨테이너에 리소스 구성을 적용할 때마다 memory.high를 max로 설정합니다. 재시작되거나 리사이즈되지 않는 이미 실행 중인 컨테이너는 다음 재시작이나 제자리 리사이즈까지 이전 memory.high 값을 유지할 수 있습니다.

시스템 요구 사항 (System requirements)

메모리 QoS는 cgroup v2가 있는 Linux가 필요합니다. 구형 커널의 memory.high 스로틀링이 알려진 livelock 버그를 촉발할 수 있으므로 커널 5.9 이상을 권장합니다. 구형 커널에서 MemoryQoS 기능 게이트가 활성화되면 kubelet은 시작 시 경고를 기록합니다.

일부 동작은 QoS 클래스와 무관합니다 (Some behavior is independent of QoS class)

특정 동작은 Kubernetes가 부여한 QoS 클래스와 무관합니다. 예를 들어:

  • 리소스 limit을 초과하는 모든 컨테이너는 그 Pod의 다른 컨테이너에 영향을 주지 않고 kubelet에 의해 죽고 재시작됩니다.
  • 컨테이너가 리소스 request를 초과하고 실행 중인 노드가 리소스 압박에 직면하면, 그 Pod는 축출 후보가 됩니다. 이 경우 그 Pod의 모든 컨테이너가 종료됩니다. Kubernetes는 보통 다른 노드에 교체 Pod를 만들 수 있습니다.
  • Pod의 리소스 request는 구성 컨테이너의 리소스 requests의 합과 같고, Pod의 리소스 limit은 구성 컨테이너의 리소스 limits의 합과 같습니다.
  • kube-scheduler는 어떤 Pod를 선점할지 선택할 때 QoS 클래스를 고려하지 않습니다. 선점은 클러스터에 정의한 모든 Pod를 실행할 충분한 리소스가 없을 때 발생할 수 있어요.
  • QoS 클래스는 Pod가 생성될 때 결정되며 Pod의 수명 동안 변경되지 않습니다. 나중에 다른 QoS 클래스를 초래할 제자리 리사이즈를 시도하면 그 리사이즈는 admission에 의해 거부됩니다.

다음 단계 (What's next)

더 알아보기 (Learn more)