파드 레벨 리소스 관리자

파드 레벨 리소스 관리자 (Pod-level resource managers)

이 기능을 사용하려면 (또는) 클러스터 관리자가 클러스터의 모든 관련 컴포넌트에 대해 PodLevelResourceManagers 기능 게이트를 활성화해야 해요.

기능 게이트 활성화 또는 비활성화에 대한 자세한 내용은 기능 게이트 활성화/비활성화를 참조하세요.

기존 리소스 관리자(Topology, CPU, Memory)에 대한 파드 레벨 리소스 지원은 그것들을 파드 레벨 리소스 스펙을 처리하도록 확장해요. 활성화되면(PodLevelResourcesPodLevelResourceManagers 기능 게이트를 통해), 리소스 관리자는 할당 결정의 기초로 .spec.resources를 직접 사용할 수 있으며, 엄격히 컨테이너별 할당 모델에서 파드 중심(Pod-centric) 모델로 진화해요. 이 분할 방식은 특히 성능에 민감한 워크로드에 대해 더 유연하고 강력한 리소스 관리 모델을 도입해요. 파드의 일부 컨테이너가 배타적이고 NUMA 정렬된 리소스를 받고, 다른 컨테이너가 파드 레벨 공유 풀에서 나머지 리소스를 공유하는 혼합 할당 모델을 정의할 수 있게 해줘요.

kubelet 리소스 관리자를 파드 레벨 리소스로 설정하고 할당 동작을 직접 관찰하는 연습을 하려면 kubelet 리소스 관리자와 함께 파드 레벨 리소스 사용하기 튜토리얼을 따르세요.

파드 레벨 리소스 관리자를 이해하려면 전통적인 컨테이너 중심 모델과 대조하는 것이 도움이 돼요. 이전에 kubelet 리소스 할당은 엄격히 전부 또는 전무였어요: 워크로드를 위한 배타적 NUMA 정렬 리소스를 받으려면 파드의 모든 컨테이너가 Guaranteed여야 했어요(CPU와 메모리 모두에 대해 requests가 limits와 같은 것을 지정).

파드 레벨 리소스 관리자는 구성된 Topology Manager 범위에 따라 유연한 분할을 가능하게 하기 위해 .spec.resources를 사용해요:

  • pod 범위(pod scope): kubelet은 .spec.resources에 기반해 전체 파드에 대해 단일 파드 버블을 할당하고 NUMA 정렬해요. 배타적 할당을 요청하는 컨테이너는 이 파드 버블 내에서 전용 슬라이스를 잘라내고, 다른 모든 컨테이너는 파드 격리된 공유 풀에서 나머지 버블 용량을 공유해요.

  • container 범위(container scope): 혼합 할당 모델을 활성화해요. kubelet은 개별 컨테이너가 노드의 할당 가능 풀에서 직접 배타적이고 NUMA 정렬된 리소스를 받도록 허용하면서, 파드의 .spec.resources 상한을 사용해 집합적 소비를 제한해요 — 파드의 모든 컨테이너가 Guaranteed일 필요 없이 사이드카가 일반 노드 공유 풀에서 실행될 수 있게 해요.

표준 이닛 컨테이너와 재시작 가능한 이닛 컨테이너(사이드카) 모두 완전히 지원돼요. 그것들은 배타적 리소스 슬라이스를 받거나 파드의 공유 풀을 사용할 수 있으며, 파드 레벨 리소스 관리자는 그 수명 주기 규칙을 존중해요 (예: 표준 이닛 컨테이너용 재사용 가능 리소스 vs. 사이드카용 영구 예약).

출처: 문서

본문

용어집 (Glossary)

파드 레벨 리소스 스펙 (Pod level resources specification)

.spec.resources에서 파드 레벨로 정의된 리소스 예산으로, 전체 파드에 대한 집합적 requests와 limits를 지정해요.

Guaranteed 컨테이너

CPU와 Memory 모두에 대해 requests가 limits와 같은 리소스 요청을 지정하는 컨테이너(배타적 CPU 할당에는 양의 정수 값이 필요). 기존 kubelet 동작과 일관되게, 이로 인해 컨테이너가 리소스 관리자의 배타적 리소스 할당 대상이 될 수 있어요.

배타적 슬라이스 (Exclusive slice)

단일 컨테이너에만 할당된 전용 리소스 부분(예: 특정 CPU 또는 메모리 페이지)으로, 다른 컨테이너로부터의 격리를 보장해요.

파드 공유 풀 (Pod shared pool)

모든 배타적 슬라이스가 예약된 후 남는 파드의 할당된 리소스 부분 집합. 이 리소스는 배타적 할당을 받지 않는 파드의 모든 컨테이너가 공유해요. 이 풀의 컨테이너는 서로 리소스를 공유하지만, 배타적 슬라이스와 일반 노드 전체 공유 풀로부터는 엄격히 격리돼요.

파드 레벨 리소스 관리자의 작동 방식

CPU와 Memory 리소스 관리자는 구성된 Topology Manager 범위에 따라 다르게 작동해요.

Topology manager의 pod 범위와 파드 레벨 리소스

Topology Manager 범위가 pod로 설정되면, kubelet은 .spec.resources에 정의된 리소스 예산에 기반해 전체 파드에 대해 단일 NUMA 정렬을 수행해요.

결과적인 NUMA 정렬 리소스 풀은 다음과 같이 분할돼요:

  • 배타적 슬라이스: Guaranteed 리소스(CPU와 메모리 모두에 대해 requests가 limits와 같고, CPU 요청이 양의 정수)를 지정하는 컨테이너는 파드의 총 할당에서 배타적 슬라이스를 받아요.
  • 파드 공유 풀: 나머지 리소스는 배타적 할당을 받지 않는 파드의 모든 다른 컨테이너를 위한 공유 풀을 형성해요. 이 풀의 컨테이너는 서로 리소스를 공유하지만, 배타적 슬라이스와 일반 노드 전체 공유 풀로부터는 엄격히 격리돼요.

표준 이닛 컨테이너가 완료까지 실행되면, 그 리소스는 노드의 리소스 풀로 돌아가지 않고 파드당 재사용 집합에 들어간다는 점을 기억하세요. 순차적으로 실행되므로, 이후의 앱 컨테이너가 이러한 리소스를 재사용할 수 있어요 (자신의 배타적 슬라이스나 공유 풀을 위해).

이를 통해 배타적 리소스가 필요한 컨테이너(예: 고성능 주요 애플리케이션)를 그렇지 않은 컨테이너(예: 로깅이나 모니터링용 사이드카)와 같은 단일 NUMA 정렬 파드 안에 공동 배치할 수 있어요.

Topology Manager 범위가 pod이고 파드의 총 예산이 4 CPU인 다음 파드 스펙의 컨테이너를 고려해 봐요. main-app은 배타적 2 CPU 슬라이스를 요청하고, 사이드카는 파드 공유 풀에서 나머지 2 CPU를 공유해요:

apiVersion: v1
kind: Pod
metadata:
  name: pod-scope-mixed
  annotations:
    kubernetes.io/description: "A pod demonstrating pod-level scope where one container gets exclusive resources and others share the remaining pod resources in a shared pool."
spec:
  # At Pod level, the Pod has CPU request equal to limits and memory request
  # also equal to memory limits. The main-app container meets the requirements
  # for the Guaranteed QoS class at container level, and the sidecar containers
  # don't specify any resource request. Under pod scope, this means that the
  # kubelet could statically assign 4 CPUs to the overall Pod, of which 2 are
  # assigned exclusively to the main-app container, and the remaining 2 are
  # shared by the sidecars in the pod's shared pool.
  resources:
    requests:
      cpu: "4"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "4Gi"
  initContainers:
  - name: metrics-sidecar
    # Note: This is a placeholder image for demonstration purposes, not an
    # actual metrics helper.
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
  - name: logging-sidecar
    # Note: This is a placeholder image for demonstration purposes, not an
    # actual logging agent.
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
  containers:
  - name: main-app
    # Note: This is a placeholder image for demonstration purposes.
    image: registry.k8s.io/pause:3.9
    resources:
      requests:
        cpu: "2"
        memory: "2Gi"
      limits:
        cpu: "2"
        memory: "2Gi"

중요한 고려 사항:

Topology manager의 pod 범위와 함께 파드 레벨 리소스를 사용할 때 몇 가지 중요한 고려 사항이 있어요:

  • 빈 공유 풀 제한: 이 구성은 파드 공유 풀이 필요하지만 그 공유 풀이 비게 되는 파드 스펙은 허용하지 않아요. 모든 Guaranteed 컨테이너의 리소스 요청 합이 총 리소스 예산과 정확히 같고, 공유 풀이 필요한 다른 컨테이너가 적어도 하나 있다면, kubelet은 승인 시 파드를 거부해요. 예를 들어, 다음 파드는 파드 레벨 예산 4 CPU를 요청해요. main-app은 배타적 3 CPU를 요구하고 metrics-sidecar는 배타적 1 CPU를 요구해요. 공유 풀에 logging-sidecar를 위한 CPU가 0개 남으므로 kubelet은 이 파드를 거부해요 (메모리에도 같은 검증이 적용돼요):
apiVersion: v1
kind: Pod
metadata:
  name: empty-shared-pool
  annotations:
    kubernetes.io/description: "A pod demonstrating a configuration that is rejected because exclusive containers consume the entire pod resource budget, leaving no resources for the remaining container in the shared pool."
spec:
  # At Pod level, the Pod has CPU request equal to limits and memory request
  # also equal to memory limits. The main-app and metrics-sidecar containers
  # meet the requirements for the Guaranteed QoS class at container level, and
  # the logging-sidecar container doesn't specify any resource request. Because
  # the Guaranteed containers consume the entire pod resource budget,
  # leaving 0 CPUs for the shared pool required by logging-sidecar, this pod
  # will be rejected at admission.
  resources:
    requests:
      cpu: "4"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "4Gi"
  initContainers:
  - name: metrics-sidecar
    # Note: This is a placeholder image for demonstration purposes, not an
    # actual metrics helper.
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
    resources:
      requests:
        cpu: "1"
        memory: "1Gi"
      limits:
        cpu: "1"
        memory: "1Gi"
  - name: logging-sidecar
    # Note: This is a placeholder image for demonstration purposes, not an
    # actual logging agent.
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
  containers:
  - name: main-app
    # Note: This is a placeholder image for demonstration purposes.
    image: registry.k8s.io/pause:3.9
    resources:
      requests:
        cpu: "3"
        memory: "3Gi"
      limits:
        cpu: "3"
        memory: "3Gi"
  • 낭비되는 리소스: pod 범위를 사용할 때 초과 할당된 모든 리소스(총 컨테이너 요청 합이 파드 레벨 예산보다 적고 공유 풀 컨테이너가 없거나, 공유 풀 컨테이너가 나머지 양을 완전히 사용하지 않는 경우)는 파드에 할당되고 예약된 채로 남아, 전체 파드 실행 동안 효과적으로 낭비돼요.

  • 영구 풀: 파드의 총 리소스 풀(NUMA 정렬과 총 예약 용량)은 영구적이에요. 공유 풀 컨테이너가 충돌하고 재시작되면, 파드의 전체 리소스 예약은 노드에 안전하게 고정된 채로 남아요. 노드는 전체 파드가 종료될 때만 리소스를 일반 풀로 돌려줘요.

Topology manager의 container 범위와 파드 레벨 리소스

Topology Manager 범위가 container로 설정되면, kubelet은 배타적 할당을 위해 각 컨테이너를 개별적으로 평가해요.

전체 파드가 Guaranteed QoS 클래스를 달성하면(파드 레벨 .spec.resources에서 적절한 값을 지정), 컨테이너를 혼합할 수 있어요:

  • 자신의 Guaranteed 요청이 있는 컨테이너는 배타적 NUMA 정렬 리소스를 받아요.
  • Guaranteed 요청을 지정하지 않은 파드의 다른 컨테이너는 노드의 공유 풀에서 실행돼요.
  • 모든 컨테이너의 집합적 리소스 소비는 여전히 파드의 .spec.resources limits에 의해 강제돼요.

이 범위는 디바이스 접근을 위해 특정 NUMA 노드에 정렬되어야 하는 인프라 사이드카가 있고, 주요 워크로드는 일반 노드 공유 풀에서 실행될 수 있을 때 유용해요.

Topology Manager 범위가 container이고 파드가 인프라 사이드카와 두 애플리케이션 워커가 있는 워크로드를 나타내며 총 예산이 4 CPU인 다음 파드 스펙의 컨테이너를 고려해 봐요. infrastructure-sidecar는 배타적이고 NUMA 정렬된 2 CPU 슬라이스를 받아요. 두 애플리케이션 워커(worker-1worker-2)는 일반 노드 전체 공유 풀에서 실행돼요:

apiVersion: v1
kind: Pod
metadata:
  name: container-scope-mixed
  annotations:
    kubernetes.io/description: "A pod demonstrating container-level scope where one container gets exclusive resources and others run in the node's shared pool."
spec:
  # At Pod level, the Pod has CPU request equal to limits and memory request
  # also equal to memory limits. The infrastructure-sidecar container meets the
  # requirements for the Guaranteed QoS class at container level, and the worker
  # containers don't specify any resource request. Under container scope, the
  # kubelet evaluates containers individually for exclusive allocation. This
  # means the infrastructure-sidecar gets an exclusive 2 CPU slice, while the
  # worker containers run in the node's general shared pool, all while bounded
  # by the overall pod limits.
  resources:
    requests:
      cpu: "4"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "4Gi"
  initContainers:
  - name: infrastructure-sidecar
    # Note: This is a placeholder image for demonstration purposes, not an
    # actual infrastructure helper.
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
    resources:
      requests:
        cpu: "2"
        memory: "2Gi"
      limits:
        cpu: "2"
        memory: "2Gi"
  containers:
  - name: worker-1
    # Note: This is a placeholder image for demonstration purposes.
    image: registry.k8s.io/pause:3.9
  - name: worker-2
    # Note: This is a placeholder image for demonstration purposes.
    image: registry.k8s.io/pause:3.9

CPU 쿼터 (CFS)

파드 내에서 혼합 워크로드를 실행할 때 kubelet은 할당에 따라 격리를 다르게 강제해요:

  • 배타적 컨테이너: 배타적 CPU 슬라이스가 있는 컨테이너는 CPU CFS 쿼터 강제가 비활성화되어, Linux 스케줄러에 의한 스로틀링 없이 실행될 수 있어요.
  • 파드 공유 풀 컨테이너: 파드 공유 풀의 컨테이너는 CPU CFS 쿼터가 활성화되어, 남은 파드 예산보다 더 많이 소비하지 않고 배타적 컨테이너를 방해하지 않도록 보장해요.

영구 풀과 재시작

파드의 총 리소스 풀(NUMA 정렬과 총 예약 용량)은 영구적이에요. 파드 공유 풀의 컨테이너가 충돌하고 재시작되면, 파드의 전체 리소스 예약은 노드에 안전하게 고정된 채로 남아요. 노드는 전체 파드가 종료될 때만 리소스를 일반 풀로 돌려줘요.

kubelet 다운그레이드와 상태 체크포인트

쿠버네티스 1.36에서 PodLevelResourceManagers 활성화는 내부 kubelet 상태 체크포인트 파일(cpu_manager_statememory_manager_state)을 이전 kubelet 버전이 로드할 수 없는 형식으로 업데이트했어요. 활발히 사용한 후 1.36 kubelet을 다운그레이드하면, 이전 kubelet이 시작에 실패해요; 노드를 드레인하고, 이러한 체크포인트 파일을 삭제하고, kubelet을 재시작해야 해요.

쿠버네티스 1.37에서 체크포인트 파일은 다운그레이드 중 시작 실패를 방지하는 전방 호환 형식을 사용하지만, 1.36 kubelet 버전은 활성 파드 레벨 리소스 할당을 복원하지 않아요. 체크포인트 형식과 복구에 대한 완전한 세부 사항은 파드 레벨 리소스 관리자 참조를 참조하세요.

관찰 가능성과 지표 (Observability and metrics)

다음 kubelet 지표를 사용해 컨테이너 레벨과 파드 레벨 할당 모두에 걸쳐 리소스 관리자의 동작과 상태를 모니터링할 수 있어요(PodLevelResourceManagers 기능 게이트를 통해 활성화):

  • resource_manager_allocations_total: 관리자가 수행한 배타적 리소스 할당의 총 수를 셈. source 레이블("pod" 또는 "node")은 노드 레벨 풀에서 가져온 할당과 미리 할당된 파드 레벨 풀에서 가져온 할당을 구분.
  • resource_manager_allocation_errors_total: 배타적 리소스 할당 중 발생한 오류를 셈, 의도된 할당 소스("pod" 또는 "node")로 구분.
  • resource_manager_container_assignments: 특정 유형의 리소스 할당을 부여받을 컨테이너의 누적 수를 추적. assignment_type 레이블("node_exclusive", "pod_exclusive", "pod_shared")은 배타적 리소스(노드 또는 파드 풀에서)와 파드 레벨 공유 풀에서 실행되는 컨테이너가 각각 몇 개인지 보여줌.

PodResources API

쿠버네티스 1.37에서 kubelet의 노드 로컬 PodResources gRPC API는 PodLevelResourceManagers가 활성화되면 파드 레벨 리소스 할당을 포함해요. 노드 로컬 모니터링 에이전트와 디바이스 플러그인은 컨테이너 레벨 할당을 이중으로 계산하지 않으면서 최상위 파드 할당(cpu_idsmemory)을 쿼리할 수 있어요.

완전한 API 스키마, 필드 마스크, 범위별 보고 표에 대해서는 파드 레벨 리소스 관리자 참조를 참조하세요.

제한 사항과 주의 사항

  • 이 기능은 정적(static) CPU Manager 정책과 정적(Static) Memory Manager 정책에 대해서만 구현돼 있어요. Memory Manager의 BestEffort 정책은 지원되지 않는 점에 유의하세요.
  • 이 기능은 Linux 노드에서만 지원돼요. Windows 노드에서는 리소스 관리자가 파드 레벨 할당에 대해 무작동(no-op)으로 작동해요.

더 알아보기 (Learn more)

  • 파드 레벨 CPU와 메모리 리소스를 할당하는 방법 배우기.
  • kubelet 리소스 관리자와 함께 파드 레벨 리소스 사용하기 튜토리얼 따르기.
  • 노드 리소스 관리자 읽기.