목록으로

Programming Notes

Kubernetes v1.36: Memory QoS를 통한 계층형 메모리 보호

SIG Node를 대표하여, Kubernetes v1.36에서 업데이트된 Memory QoS 기능(알파)을 발표하게 되어 기쁩니다. Memory QoS는 cgroup v2 메모리 컨트롤러를 사용하여 커널이 컨테이너 메모리를 어떻게 처리해야 할지에 대해 더 나은 가이드를 제공합니다. 이 기능은 v1.22에서 처음 도입되었고 v1.27에서 업데이트되었습니다. Kubernetes v1.36에서는 선택적(opt-in) 메모리 예약, QoS 클래스별 계층형 보호, 관찰 가능성 지표(metrics), 그리고 memory.high에 대한 커널 버전 경고 기능을 도입합니다.

v1.36의 새로운 기능

memoryReservationPolicy를 통한 선택적 메모리 예약

v1.36에서는 스로틀링(throttling)과 예약(reservation)이 분리되었습니다. 기능 게이트(Feature Gate)를 활성화하면 memory.high 스로틀링이 켜지지만(kubelet은 memoryThrottlingFactor를 기반으로 memory.high를 설정하며, 기본값은 0.9입니다), 메모리 예약은 이제 별도의 kubelet 설정 필드에 의해 제어됩니다.

  • None (기본값): memory.min이나 memory.low가 작성되지 않습니다. memory.high를 통한 스로틀링은 여전히 작동합니다.
  • TieredReservation: kubelet이 포드의 QoS 클래스에 따라 계층화된 메모리 보호 설정을 작성합니다.

Guaranteed 포드는 memory.min을 통해 강력한 보호를 받습니다. 예를 들어, 512 MiB의 메모리를 요청하는 Guaranteed 포드는 다음과 같은 결과를 생성합니다.

$ cat /sys/fs/cgroup/kubepods.slice/kubepods-pod6a4f2e3b_1c9d_4a5e_8f7b_2d3e4f5a6b7c.slice/memory.min
536870912

커널은 어떠한 상황에서도 이 메모리를 회수(reclaim)하지 않습니다. 만약 이 보장을 준수할 수 없다면, 다른 프로세스에 OOM 킬러를 호출하여 페이지를 확보합니다.

Burstable 포드는 memory.low를 통해 부드러운 보호를 받습니다. 동일하게 512 MiB를 요청하는 Burstable 포드의 경우:

$ cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8b3c7d2e_4f5a_6b7c_9d1e_3f4a5b6c7d8e.slice/memory.low
536870912

커널은 일반적인 압박 상황에서는 이 메모리 회수를 피하지만, 시스템 전체의 OOM을 피하기 위한 유일한 대안인 경우에는 이를 회수할 수 있습니다.

BestEffort 포드는 memory.min이나 memory.low를 모두 받지 않습니다. 이들의 메모리는 완전히 회수 가능한 상태로 유지됩니다.

v1.27 동작과의 비교

이전 버전에서는 MemoryQoS 기능 게이트를 활성화하면 메모리 요청(request)이 있는 모든 컨테이너에 즉시 memory.min이 설정되었습니다. memory.min은 메모리 압박에 관계없이 커널이 회수하지 않는 강력한 예약 방식입니다.

예를 들어, 8 GiB RAM이 있는 노드에서 Burstable 포드의 요청 합계가 7 GiB인 경우를 가정해 보겠습니다. 이전 버전에서는 그 7 GiB가 memory.min으로 잠겨버려 커널, 시스템 데몬 또는 BestEffort 워크로드를 위한 여유 공간이 거의 남지 않게 되고, 시스템 전체의 OOM 킬 링 위험이 높아졌습니다.

v1.36의 계층형 예약 방식을 사용하면, 이러한 Burstable 요청은 memory.min 대신 memory.low에 매핑됩니다. 일반적인 압박 하에서 커널은 여전히 해당 메모리를 보호하지만, 극심한 압박 상황에서는 시스템 전체의 OOM을 방지하기 위해 일부를 회수할 수 있습니다. 오직 Guaranteed 포드만이 memory.min을 사용하므로, 강제적인 예약 수치를 낮게 유지할 수 있습니다.

v1.36의 memoryReservationPolicy를 사용하면 먼저 스로틀링을 활성화하여 워크로드 동작을 관찰한 다음, 노드에 충분한 여유 공간이 있을 때 예약을 선택적으로 적용(opt-in)할 수 있습니다.

관찰 가능성 지표 (Observability metrics)

kubelet의 /metrics 엔드포인트에 두 가지 알파 단계 지표가 노출됩니다.

지표(Metric) 설명
kubelet_memory_qos_node_memory_min_bytes 모든 Guaranteed 포드에 설정된 memory.min의 총합
kubelet_memory_qos_node_memory_low_bytes 모든 Burstable 포드에 설정된 memory.low의 총합

이 지표들은 용량 계획(capacity planning)에 유용합니다. 만약 kubelet_memory_qos_node_memory_min_bytes가 노드의 물리 메모리에 근접하고 있다면, 강력한 예약(hard reservation) 공간이 부족해지고 있다는 것을 알 수 있습니다.

$ curl -sk https://localhost:10250/metrics | grep memory_qos
# HELP kubelet_memory_qos_node_memory_min_bytes [ALPHA] Total memory.min in bytes for Guaranteed pods
kubelet_memory_qos_node_memory_min_bytes 5.36870912e+08
# HELP kubelet_memory_qos_node_memory_low_bytes [ALPHA] Total memory.low in bytes for Burstable pods
kubelet_memory_qos_node_memory_low_bytes 2.147483648e+09

커널 버전 체크

5.9 미만의 커널에서는 memory.high 스로틀링이 커널 라이브락(livelock) 이슈를 유발할 수 있습니다. 이 버그는 커널 5.9에서 수정되었습니다. v1.36에서는 기능 게이트가 활성화될 때 kubelet이 시작 시 커널 버전을 확인하고, 5.9 미만일 경우 경고 로그를 남깁니다. 기능 자체는 계속 작동하며, 이 경고는 정보 제공용일 뿐 실행을 차단하지는 않습니다.

Kubernetes가 Memory QoS를 cgroup v2에 매핑하는 방법

Memory QoS는 네 가지 cgroup v2 메모리 컨트롤러 인터페이스를 사용합니다.

  • memory.max: 메모리 하드 제한(hard limit) — 이전 버전과 동일하게 유지됩니다.
  • memory.min: 강력한 메모리 보호 — TieredReservation 설정 시 Guaranteed 포드에만 설정됩니다.
  • memory.low: 부드러운 메모리 보호 — TieredReservation 설정 시 Burstable 포드에 설정됩니다.
  • memory.high: 메모리 스로틀링 임계값 — 이전 버전과 동일하게 유지됩니다.

다음 표는 memoryReservationPolicy: TieredReservation이 구성되었을 때 Kubernetes 컨테이너 리소스가 cgroup v2 인터페이스에 어떻게 매핑되는지 보여줍니다. 기본값인 memoryReservationPolicy: None을 사용하면 memory.min이나 memory.low 값은 설정되지 않습니다.

QoS 클래스 memory.min memory.low memory.high memory.max
Guaranteed requests.memory로 설정
(강력한 보호)
설정 안 됨 설정 안 됨
(request == limit 이므로 스로틀링이 무의미함)
limits.memory로 설정
Burstable 설정 안 됨 requests.memory로 설정
(부드러운 보호)
스로틀링 팩터가 포함된
공식에 따라 계산됨
limits.memory로 설정
(지정된 경우)
BestEffort 설정 안 됨 설정 안 됨 노드 할당 가능(Allocatable) 메모리를
기반으로 계산됨
설정 안 됨

Cgroup 계층 구조 (Cgroup hierarchy)

cgroup v2에서는 부모 cgroup의 메모리 보호 수치가 자식들의 보호 수치 합계보다 크거나 같아야 합니다. kubelet은 kubepods 루트 cgroup의 memory.min을 모든 Guaranteed 및 Burstable 포드 메모리 요청의 합계로 설정하고, burstable QoS cgroup의 memory.low를 모든 Burstable 포드 메모리 요청의 합계로 설정하여 이를 유지합니다. 이렇게 함으로써 커널이 컨테이너 레벨과 포드 레벨의 보호 값을 올바르게 적용할 수 있도록 합니다.

kubelet은 runc libcontainer 라이브러리를 사용하여 포드 레벨 및 QoS 클래스 cgroup을 직접 관리하며, 컨테이너 레벨 cgroup은 컨테이너 런타임(containerd 또는 CRI-O)에 의해 관리됩니다.

어떻게 사용할 수 있나요?

사전 요구 사항

  1. Kubernetes v1.36 이상
  2. cgroup v2가 활성화된 Linux. 커널 5.9 이상을 권장합니다. 이전 버전에서도 작동하지만 라이브락 이슈가 발생할 수 있습니다. mount | grep cgroup2 명령어로 cgroup v2 활성 여부를 확인할 수 있습니다.
  3. cgroup v2를 지원하는 컨테이너 런타임 (containerd 1.6+, CRI-O 1.22+)

설정

계층형 보호를 포함한 Memory QoS를 활성화하려면 다음과 같이 설정합니다.

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  MemoryQoS: true
memoryReservationPolicy: TieredReservation # 옵션: None (기본값), TieredReservation
memoryThrottlingFactor: 0.9 # 선택 사항: 기본값은 0.9

메모리 보호 없이 memory.high 스로틀링만 원하는 경우, memoryReservationPolicy를 생략하거나 None으로 설정하십시오.

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  MemoryQoS: true
memoryReservationPolicy: None  # 이것이 기본값입니다

더 자세히 알아보기

참여하기

이 기능은 SIG Node에서 주도하고 있습니다. 기여에 관심이 있거나 의견이 있으시다면 Slack (#sig-node), 메일링 리스트, 또는 정기적인 SIG Node 회의를 통해 저희를 찾으실 수 있습니다. 버그는 kubernetes/kubernetes에, 개선 제안은 kubernetes/enhancements에 제출해 주세요.