Pod 수준 리소스 관리자

Pod 수준 리소스 관리자 (Pod-level resource managers)

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

이 기능에 대한 자세한 정보

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

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

기존 리소스 관리자(Topology, CPU, Memory)에 대한 Pod 수준 리소스 지원은 이들을 확장해 pod 수준 리소스 스펙을 처리해요. PodLevelResourcesPodLevelResourceManagers 기능 게이트로 활성화되면 리소스 관리자는 할당 결정의 기반으로 .spec.resources를 직접 사용할 수 있어요. 이는 엄격하게 컨테이너별 할당 모델에서 Pod 중심 모델로 진화한 거예요. 이 파티셔닝 체계는 특히 성능에 민감한 워크로드에 대해 더 유연하고 강력한 리소스 관리 모델을 도입해요. Pod의 일부 컨테이너는 전용의 NUMA 정렬 리소스를 받고, 다른 컨테이너는 pod 수준 공유 풀의 남은 리소스를 공유하는 하이브리드 할당 모델을 정의할 수 있게 해 줘요.

kubelet 리소스 관리자를 pod 수준 리소스로 설정하고 할당 동작을 직접 관찰하는 연습을 하려면, "kubelet 리소스 관리자에서 pod 수준 리소스 사용" 튜토리얼을 따라 해 보세요.

pod 수준 리소스 관리자를 이해하려면 전통적인 컨테이너 중심 모델과 대조해 보는 것이 도움이 돼요. 이전에는 kubelet 리소스 할당이 엄격히 전부 아니면 전무였어요. 워크로드에 전용 NUMA 정렬 리소스를 받으려면 Pod의 모든 컨테이너가 Guaranteed여야 했어요(CPU와 메모리 모두 request가 limit와 같게 지정).

Pod 수준 리소스 관리자는 .spec.resources를 사용해 구성된 Topology Manager 범위에 따라 유연한 파티셔닝을 가능하게 해요.

  • pod 범위: kubelet은 .spec.resources에 기반해 전체 Pod에 대해 단일 Pod 버블(bubble)을 할당하고 NUMA 정렬해요. 전용 할당을 요청하는 컨테이너는 이 Pod 버블 안에서 전용 슬라이스를 잘라내고, 다른 모든 컨테이너는 pod 격리 공유 풀에서 남은 버블 용량을 공유해요.
  • container 범위: 하이브리드 할당 모델을 활성화해요. kubelet은 개별 컨테이너가 Node의 할당 가능 풀에서 직접 전용 NUMA 정렬 리소스를 받도록 허용하면서, Pod의 .spec.resources 상한선을 사용해 집합 소비를 제한해요. 이렇게 하면 Pod의 모든 컨테이너가 Guaranteed일 필요 없이 사이드카가 일반 Node 공유 풀에서 실행될 수 있어요.

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

용어집 (Glossary)

Pod 수준 리소스 스펙(Pod level resources specification) .spec.resources에 Pod 수준으로 정의된 리소스 예산으로, 전체 Pod에 대한 집합적인 requests와 limits를 지정해요.

Guaranteed 컨테이너 CPU와 Memory 모두에 대해 requests가 limits와 같은 리소스를 지정하는 컨테이너예요(전용 CPU 할당은 양의 정수 값을 요구). 기존 kubelet 동작과 일관되게, 이는 컨테이너가 리소스 관리자로부터 전용 리소스 할당을 받을 자격을 갖게 해요.

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

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

pod 수준 리소스 관리자 작동 방식

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

Topology 관리자의 pod 범위와 pod 수준 리소스

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

결과로 생성된 NUMA 정렬 리소스 풀은 다음과 같이 분할돼요.

  1. 전용 슬라이스: Guaranteed 리소스(CPU와 메모리 모두 request가 limits와 같고 CPU request가 양의 정수)를 지정하는 컨테이너는 Pod의 전체 할당에서 전용 슬라이스를 받아요.
  2. Pod 공유 풀: 남은 리소스는 전용 할당을 받지 않는 Pod의 다른 모든 컨테이너를 위한 공유 풀을 형성해요. 이 풀의 컨테이너들은 서로 리소스를 공유하지만, 전용 슬라이스와 일반 노드 전체 공유 풀로부터는 엄격히 격리돼요.

표준 init 컨테이너가 실행을 완료하면 그 리소스는 Node의 리소스 풀로 돌아가는 대신 Per-Pod 재사용 가능 집합에 들어간다는 점에 주의하세요. 순차적으로 실행되므로 이후 앱 컨테이너가 이 리소스를 재사용할 수 있어요(자신의 전용 슬라이스 또는 공유 풀용으로).

이를 통해 전용 리소스가 필요한 컨테이너(예: 고성능 기본 애플리케이션)와 그렇지 않은 컨테이너(예: 로깅이나 모니터링용 사이드카)를 단일 NUMA 정렬 Pod 안에서 함께 위치시킬 수 있어요.

Topology Manager 범위가 pod이고 Pod의 총 예산이 4 CPU인 다음 Pod 스펙의 컨테이너를 고려해 보세요. main-app이 전용 2 CPU 슬라이스를 요청하고, 사이드카가 Pod의 공유 풀로 남은 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 관리자의 pod 범위로 pod 수준 리소스를 사용할 때 알아야 할 몇 가지 중요한 고려 사항이 있어요.

빈 공유 풀 제한: 이 구성은 공유 풀이 필요한 컨테이너가 있는데 빈 Pod 공유 풀을 만들게 되는 Pod 스펙을 허용하지 않아요. 모든 Guaranteed 컨테이너의 리소스 요청 합이 정확히 총 리소스 예산과 같고 공유 풀이 필요한 다른 컨테이너가 하나 이상 있으면, kubelet은 어드미션 시 해당 Pod를 거부해요.

예를 들어 다음 Pod는 pod 수준 예산으로 4 CPU를 요청해요. main-app이 전용 3 CPU를, metrics-sidecar가 전용 1 CPU를 요구해요. logging-sidecar를 위한 공유 풀에 0 CPU가 남으므로 kubelet은 이 Pod를 거부해요(메모리에도 동일한 검증이 적용돼요).

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
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
    resources:
      requests:
        cpu: "1"
        memory: "1Gi"
      limits:
        cpu: "1"
        memory: "1Gi"
  - name: logging-sidecar
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
  containers:
  - name: main-app
    image: registry.k8s.io/pause:3.9
    resources:
      requests:
        cpu: "3"
        memory: "3Gi"
      limits:
        cpu: "3"
        memory: "3Gi"

낭비되는 리소스: pod 범위를 사용할 때 과할당된 리소스(모든 컨테이너 request 합이 pod 수준 예산보다 작고 공유 풀 컨테이너가 없거나, 또는 공유 풀 컨테이너가 남은 양을 완전히 사용하지 못하는 경우)는 Pod에 할당되고 예약된 채로 남아, Pod 실행 내내 사실상 낭비돼요.

영구 풀: Pod의 총 리소스 풀(NUMA 정렬과 총 예약 용량)은 영구적이에요. 공유 풀 컨테이너가 크래시하고 재시작해도 Pod의 전체 리소스 예약은 Node에 안전하게 고정돼 있어요. Node는 전체 Pod가 종료될 때만 리소스를 일반 풀에 돌려줘요.

Topology 관리자의 container 범위와 pod 수준 리소스

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

전체 Pod가 Guaranteed QoS 클래스를 달성하면(pod 수준 .spec.resources에 적절한 값을 지정해), 컨테이너를 혼합할 수 있어요.

  • 자체 Guaranteed requests를 가진 컨테이너는 전용 NUMA 정렬 리소스를 받아요.
  • Pod에서 Guaranteed requests를 지정하지 않은 다른 컨테이너는 Node의 공유 풀에서 실행돼요.
  • 모든 컨테이너의 집합 리소스 소비는 여전히 Pod의 .spec.resources limits로 강제돼요.

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

Topology Manager 범위가 container이고 Pod가 인프라 사이드카와 두 개의 애플리케이션 워커가 있는 워크로드를 나타내며 총 예산이 4 CPU인 다음 Pod 스펙의 컨테이너를 고려해 보세요. infrastructure-sidecar가 전용 NUMA 정렬 2 CPU 슬라이스를 받고, 두 애플리케이션 워커(worker-1, worker-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.
  resources:
    requests:
      cpu: "4"
      memory: "4Gi"
    limits:
      cpu: "4"
      memory: "4Gi"
  initContainers:
  - name: infrastructure-sidecar
    image: registry.k8s.io/pause:3.9
    restartPolicy: Always
    resources:
      requests:
        cpu: "2"
        memory: "2Gi"
      limits:
        cpu: "2"
        memory: "2Gi"
  containers:
  - name: worker-1
    image: registry.k8s.io/pause:3.9
  - name: worker-2
    image: registry.k8s.io/pause:3.9

CPU 쿼터 (CFS)

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

  • 전용 컨테이너: 전용 CPU 슬라이스를 가진 컨테이너는 CPU CFS 쿼터 시행이 비활성화되어 Linux 스케줄러에 의한 스로틀링 없이 실행될 수 있어요.
  • Pod 공유 풀 컨테이너: Pod 공유 풀의 컨테이너는 CPU CFS 쿼터가 활성화되어 남은 Pod 예산을 초과해 소비하지 않도록 보장하고 전용 컨테이너를 간섭하지 못하게 해요.

영구 풀과 재시작

Pod의 총 리소스 풀(NUMA 정렬과 총 예약 용량)은 영구적이에요. Pod 공유 풀의 컨테이너가 크래시하고 재시작해도 Pod의 전체 리소스 예약은 Node에 안전하게 고정돼 있어요. Node는 전체 Pod가 종료될 때만 리소스를 일반 풀에 돌려줘요.

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

Kubernetes 1.36에서 PodLevelResourceManagers를 활성화하면 내부 kubelet 상태 체크포인트 파일(cpu_manager_state, memory_manager_state)이 오래된 kubelet 버전이 로드할 수 없는 형식으로 갱신돼요. 활발하게 사용한 후 1.36 kubelet을 다운그레이드하면 오래된 kubelet이 시작에 실패해요. 노드를 드레인하고 이 체크포인트 파일을 삭제한 다음 kubelet을 재시작해야 해요.

Kubernetes 1.37에서는 체크포인트 파일이 다운그레이드 중 시작 실패를 방지하기 위해 순방향 호환 형식을 사용해요. 다만 1.36 kubelet 버전은 활성 pod 수준 리소스 할당을 복원하지는 않아요. 체크포인트 형식과 복구에 대한 전체 세부 사항은 Pod 수준 리소스 관리자 참조를 참고하세요.

관측성과 메트릭

PodLevelResourceManagers 기능 게이트로 활성화되는 다음 kubelet 메트릭을 사용해 컨테이너 수준과 pod 수준 할당 모두에 걸쳐 리소스 관리자의 동작과 상태를 모니터링할 수 있어요.

  • resource_manager_allocations_total: 관리자가 수행한 전용 리소스 할당의 총 수를 세요. source 레이블("pod" 또는 "node")이 노드 수준 풀에서 가져온 할당인지 사전 할당된 pod 수준 풀에서 가져온 할당인지 구분해요.
  • resource_manager_allocation_errors_total: 전용 리소스 할당 중 발생한 오류를 세며, 의도된 할당 소스("pod" 또는 "node")로 구분해요.
  • resource_manager_container_assignments: 특정 유형의 리소스 할당을 받을 컨테이너의 누적 수를 추적해요. assignment_type 레이블("node_exclusive", "pod_exclusive", "pod_shared")이 전용 리소스(노드 또는 pod 풀에서)로 실행되는 컨테이너 수와 pod 수준 공유 풀의 컨테이너 수에 대한 가시성을 제공해요.

PodResources API

Kubernetes 1.37에서 PodLevelResourceManagers가 활성화되면 kubelet의 노드 로컬 PodResources gRPC API가 pod 수준 리소스 할당을 포함해요. 노드 로컬 모니터링 에이전트와 장치 플러그인은 컨테이너 수준 할당을 이중으로 계산하지 않으면서 최상위 Pod 할당(cpu_ids, memory)을 쿼리할 수 있어요.

전체 API 스키마, 필드 마스크, 범위별 보고 표에 대한 자세한 내용은 Pod 수준 리소스 관리자 참조를 참고하세요.

제한 사항과 주의 사항

  • 이 기능은 정적 CPU Manager 정책과 정적 Memory Manager 정책에서만 구현돼요. Memory Manager에는 BestEffort 정책이 지원되지 않는다는 점에 주의하세요.
  • 이 기능은 Linux 노드에서만 지원돼요. Windows 노드에서는 리소스 관리자가 pod 수준 할당에 대해 no-op으로 작동해요.

더 알아보기

  • Pod 수준 CPU와 메모리 리소스를 할당하는 방법을 배워 보세요.
  • kubelet 리소스 관리자에서 pod 수준 리소스 사용 튜토리얼을 따라 해 보세요.
  • Node 리소스 관리자에 대해 읽어 보세요.