메모리 매니저

메모리 매니저 (Memory Manager)

메모리 매니저는 NUMA(Non-Uniform Memory Access)가 있는 시스템에서 메모리와 hugepages를 관리하는 kubelet 컴포넌트예요.

최적의 성능을 얻으려면 CPU 격리, 메모리, 디바이스 지역성(locality)과 관련된 최적화가 필요해요. 쿠버네티스에서 메모리 매니저는 Guaranteed QoS 클래스의 파드에 대해 보장된 메모리(및 hugepages) 할당을 제공해요.

출처: 문서

본문

시작하기 전에 (Before you begin)

쿠버네티스 클러스터가 필요하고, kubectl 명령줄 도구가 클러스터와 통신하도록 구성돼 있어야 해요. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 두 개 이상 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube로 만들거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있어요.

  • iximiuz Labs
  • Killercoda
  • KodeKloud

쿠버네티스 서버가 최소 v1.32 버전이어야 해요. 버전을 확인하려면 kubectl version을 입력하세요. 더 오래된 버전의 쿠버네티스를 실행 중이라면 실제 실행 중인 버전의 문서를 참고하세요.

리소스 정렬 사전 요구 사항 (Resource alignment prerequisites)

Pod 스펙에서 메모리 리소스를 다른 요청 리소스와 정렬하려면:

  • 노드에 CPU Manager가 활성화되고 적절한 CPU Manager 정책이 구성돼 있어야 해요. CPU 관리 정책 제어를 참고하세요.
  • 노드에 Topology Manager가 활성화되고 적절한 Topology Manager 정책이 구성돼 있어야 해요. Topology 관리 정책 제어를 참고하세요.

Windows 지원 (Windows support)

기능 상태: Kubernetes v1.32부터 Alpha; 기본적으로 비활성화.

이 기능을 사용하려면 WindowsCPUAndMemoryAffinity 기능 게이트를 활성화해야 하고, 컨테이너 런타임의 지원이 필요해요. Windows에서는 NoneBestEffort 정책만 지원돼요.

메모리 매니저는 어떻게 동작하나요? (How does the Memory Manager operate?)

Linux 노드에서 메모리 매니저는 Guaranteed QoS 클래스의 파드에 보장된 메모리(및 hugepages) 할당을 제공해요. 메모리 매니저를 즉시 작동시키려면 "메모리 매니저 구성" 섹션의 지침을 따른 뒤, "Pod를 Guaranteed QoS 클래스에 배치하기" 섹션에서 설명한 대로 Guaranteed Pod를 준비하고 배포하세요.

메모리 매니저는 힌트 제공자(hint provider)이며, Topology Manager에 토폴로지 힌트를 제공해요. 그러면 Topology Manager가 이 토폴로지 힌트에 따라 요청된 리소스를 정렬해요. Linux에서는 파드에 cgroups(특히 cpuset.mems)도 강제해요. 파드 승인과 배포 과정에 관한 전체 흐름도는 아래에 나와 있어요.

이 과정 동안 메모리 매니저는 보장된 메모리 할당을 관리하기 위해 [Node Map과 Memory Maps]에 저장된 내부 카운터를 업데이트해요.

노드 관리자가 kubelet에 reservedMemory를 구성하면(예약 메모리 구성 섹션) 메모리 매니저는 kubelet 시작 중에 활성화돼요. 이 경우 kubelet은 이 예약을 반영하도록 노드 맵을 업데이트해요.

Static 정책이 구성되면 노드에 예약 메모리를 구성해야 해요(예: kubelet 구성의 reservedMemory 구성 필드 사용).

메모리 매니저 운영의 맥락에서 중요한 주제는 NUMA 그룹 관리예요. 파드의 메모리 요청이 단일 NUMA 노드 용량을 초과할 때마다 메모리 매니저는 여러 NUMA 노드로 구성되고 확장된 메모리 용량을 가진 그룹을 만들려고 시도해요.

메모리 매니저 구성 (Memory Manager configuration)

다른 매니저가 이미 구성돼 있어야 해요(리소스 정렬 사전 요구 사항 참고). kubelet 구성에 memoryManagerPolicy 구성 필드를 선택한 정책 이름으로 설정하세요.

선택적으로 시스템이나 kubelet 프로세스에 일부 메모리를 예약해 노드 안정성을 높일 수 있어요(예약 메모리 구성 섹션).

정책 (Policies)

쿠버네티스의 메모리 매니저는 세 가지 정책을 제공해요. kubelet 구성의 memoryManagerPolicy 구성 필드로 정책을 선택할 수 있어요. Kubernetes 1.37에서 사용 가능한 값은:

  • None (기본값)
  • Static (Linux 전용)
  • BestEffort (Windows 전용)
None 정책

이것은 기본 정책이며 메모리 할당에 어떤 식으로도 영향을 주지 않아요. 메모리 매니저가 전혀 없는 것처럼 동작해요. None 정책은 기본 토폴로지 힌트를 반환해요. 이 특별한 힌트는 힌트 제공자(이 경우 메모리 매니저)가 어떤 리소스에 대한 NUMA 어피니티 선호도가 없음을 나타내요.

Static 정책

기능 상태: Kubernetes v1.32부터 Stable; 기본적으로 활성화.

이 정책은 Linux에서만 지원돼요. Guaranteed 파드의 경우 Static 메모리 매니저 정책은 메모리가 보장될 수 있는 NUMA 노드 집합에 관한 토폴로지 힌트를 반환하고, 내부 [NodeMap] 객체를 업데이트해 메모리를 예약해요. BestEffort 또는 Burstable 파드의 경우 Static 메모리 매니저 정책은 보장된 메모리에 대한 요청이 없으므로 기본 토폴로지 힌트를 보내고 내부 [NodeMap] 객체에 메모리를 예약하지 않아요. 이 정책은 Linux에서만 지원돼요.

BestEffort 정책

기능 상태: Kubernetes v1.32부터 Alpha; 기본적으로 비활성화.

이 정책은 Windows에서만 지원돼요. Windows에서 NUMA 노드 할당은 Linux와 다르게 동작해요. 메모리 접근이 특정 NUMA 노드에서만 오도록 보장하는 메커니즘이 없어요. 대신 Windows 운영체제 스케줄러가 CPU 할당에 따라 가장 최적의 NUMA 노드를 선택해요. Windows 스케줄러가 최적이라고 판단하면 Windows가 다른 NUMA 노드를 사용할 수도 있어요.

이 정책은 내부 노드 맵을 통해 사용 가능한 메모리와 요청된 메모리 양을 추적해요. 메모리 매니저는 리소스 할당 전에 NUMA 노드에 충분한 메모리가 사용 가능하도록 하는 데 최선을 다해요. 즉 대부분의 경우 메모리 할당이 지정된 대로 동작해야 해요.

예약 메모리 구성 (Reserved memory configuration)

관리자로서 노드의 총 예약 메모리 양을 구성할 수 있어요. 이 미리 구성된 값은 파드에 사용 가능한 실제 노드 할당 가능 메모리 양을 계산하는 데 사용돼요.

쿠버네티스 스케줄러는 파드 스케줄링을 최적화하기 위해 할당 가능 메모리 정보를 통합해요. 노드 할당 가능 메커니즘은 노드 관리자가 kubelet이나 운영체제 프로세스를 위해 K8s 노드 시스템 리소스를 예약해 노드 안정성을 보장하는 데 흔히 사용돼요.

관련 kubelet 설정에는 kubeReserved, systemReserved, reservedMemory가 있어요. reservedMemory 설정은 총 예약 메모리를 나누어 여러 NUMA 노드에 걸쳐 할당할 수 있게 해 줘요.

NUMA 노드당 다른 메모리 유형의 메모리 예약 목록을 쉼표로 구분해 지정할 수 있어요. 세미콜론을 구분자로 사용해 여러 NUMA 노드에 걸친 예약도 지정할 수 있어요.

메모리 매니저는 이 예약 메모리를 컨테이너 워크로드 실행에 사용하지 않아요.

예를 들어 "NUMA0"이라는 NUMA 노드에 10GiB의 메모리를 사용할 수 있고, reservedMemory로 NUMA0에 1Gi(메모리)를 예약하도록 구성했다면, 메모리 매니저는 파드에 9GiB만 사용 가능하다고 가정해요.

이 매개변수는 생략할 수 있지만, 모든 NUMA 노드의 예약 메모리 양이 노드 할당 가능 메모리 양과 같아야 한다는 것을 알아야 해요.

최소 하나의 노드 할당 가능 매개변수가 0이 아니면 최소 하나의 NUMA 노드에 대해 reservedMemory를 지정해야 해요. 실제로 evictionHard 임계값은 기본적으로 100Mi와 같으므로, Static 정책을 사용한다면 reservedMemory를 지정하는 것이 필수예요.

메모리 매니저 예약 메모리 구문 (Memory manager reserved memory syntax)

kubelet의 reservedMemory 구성 설정 예시는 다음과 같아요.

# 예시 1
reservedMemory:
- numaNode: 0 # NUMA 노드 인덱스
  limits:
    memory: "1Gi" # 바이트 수량
- numaNode: 1
  limits:
    memory: "2Gi" # 바이트 수량
# 예시 2
reservedMemory:
- numaNode: 0
  limits:
    "memory": "512Gi"
- numaNode: 1
  limits:
    "memory": "512Gi"
    "hugepages-1Gi": "2Gi" # Linux에서만 관련

NUMA 메모리 예약에 대한 제약 (Constraints on NUMA memory reservation)

reservedMemory 값을 지정할 때 이것은 적용 중인 kubeReservedsystemReserved 값과, evictionHard의 일부로 설정하는 memory.available 설정과 호환되어야 해요.

Σ (reservedMemory[i]) = kubeReserved + systemReserved + evictionHard.memory.available

여기서 i는 NUMA 노드의 인덱스예요. 위 공식을 따르지 않으면 메모리 매니저가 시작 시 오류를 보여줘요.

즉, 위 예시 1은 기존 메모리(type=memory)에 대해 쿠버네티스가 총 3GiB를 예약한다는 것을 보여줘요.

reservedMemory[0] + reservedMemory[1] (type=memory) = 1GiB + 2GiB = 3GiB

노드 할당 가능 구성과 관련된 kubelet 구성 설정 예시:

kubeReserved: { cpu: "500m", memory: "50Mi" } # CPU 반 개, 메모리 50MiB
systemReserved: { cpu: "500m", memory: "256Mi" } # CPU 반 개, 메모리 256MiB

참고: 기본 하드 퇴거 임계값은 0이 아니라 100MiB예요. reservedMemory로 예약하는 메모리 양을 그 하드 퇴거 임계값만큼 늘리는 것을 기억하세요. 그렇지 않으면 kubelet이 메모리 매니저를 시작하지 않고 오류를 표시해요.

reservedMemory를 사용하는 올바른 구성 예시:

# 이 스니펫은 evictionHard의 기본값에 의존
memoryManagerPolicy: Static
kubeReserved: { cpu: "4", memory: "4Gi" }
systemReserved: { cpu: "1", memory: "1Gi" }
reservedMemory:
- numaNode: 0
  limits:
    memory: "3Gi"
- numaNode: 1
  limits:
    memory: "2148Mi" # 3GiB 마이너스 100MiB

피해야 할 구성 (Configurations to avoid)

다음 구성을 피하세요.

  • 중복: 같은 NUMA 노드나 메모리 유형이지만 다른 값으로
  • 어떤 메모리 유형에 대해서도 제한(limit)을 0으로 설정
  • 머신 하드웨어에 존재하지 않는 NUMA 노드 ID
  • memoryhugepages-<size>(특정 <size>의 hugepages도 존재해야 함)와 다른 메모리 유형 이름

Pod를 Guaranteed QoS 클래스에 배치하기 (Placing a Pod in the Guaranteed QoS class)

선택한 정책이 None이 아닌 경우 메모리 매니저는 Guaranteed QoS 클래스에 있는 파드를 식별해요. 메모리 매니저는 각 Guaranteed 파드에 대해 Topology Manager에 특정 토폴로지 힌트를 제공해요. Guaranteed가 아닌 다른 QoS 클래스의 파드에 대해서는 메모리 매니저가 Topology Manager에 기본 토폴로지 힌트를 제공해요.

다음 파드 매니페스트 발췌는 파드를 Guaranteed QoS 클래스에 할당해요.

requestslimits와 같은 경우, 정수 CPU를 가진 Pod는 Guaranteed QoS 클래스에서 실행돼요.

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "2"
        example.com/device: "1"

또한 requestslimits와 같은 경우 CPU를 공유하는 파드도 Guaranteed QoS 클래스에서 실행돼요.

spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      limits:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"
      requests:
        memory: "200Mi"
        cpu: "300m"
        example.com/device: "1"

Pod가 Guaranteed QoS 클래스에 속하려면 CPU와 메모리 요청이 모두 지정돼야 한다는 점을 주의하세요.

다음 단계 (What's next)

  • 토폴로지 관리 문제 해결 읽기
  • 메모리 매니저에 대한 KEP(쿠버네티스 개선 제안) 읽기
  • 파드 레벨 리소스 매니저에 대해 읽기

더 알아보기 (Learn more)