kubelet 리소스 관리자와 함께 파드 레벨 리소스 사용하기
kubelet 리소스 관리자와 함께 파드 레벨 리소스 사용하기 (Use Pod-Level Resources with kubelet Resource Managers)
이 기능을 사용하려면 (또는) 클러스터 관리자가 클러스터의 모든 관련 컴포넌트에 대해 PodLevelResourceManagers 기능 게이트를 활성화해야 해요.
기능 게이트 활성화 또는 비활성화에 대한 자세한 내용은 기능 게이트 활성화/비활성화를 참조하세요.
이 튜토리얼은 kubelet의 리소스 관리자(topology, CPU, memory)가 파드 레벨 리소스 스펙을 지원하도록 구성하는 방법을 보여 줘요. 일부 컨테이너는 배타적이고 NUMA 정렬된 인프라 리소스를 받고, 다른 컨테이너는 파드 레벨 공유 풀에서 나머지 리소스를 공유하는 혼합 할당 모델을 정의할 수 있어요.
이 기능의 개념에 대해 더 배우려면 파드 레벨 리소스 관리자 개념 페이지를 읽어보세요.
출처: 문서
본문
목표 (Objectives)
- 파드 레벨 리소스를 지원하도록 kubelet 리소스 관리자(CPU, memory, topology)를 구성하기.
- 배타적 및 공유 컨테이너가 혼합된 단일 NUMA 정렬을 달성하기 위해 파드 Topology Manager 범위로 워크로드를 배포하기.
- CPU와 메모리 리소스가 배타적 컨테이너와 파드 공유 풀 사이에 어떻게 분할되는지 검사하고 확인하기.
- 파드 레벨 공유 풀이 비게 될 때의 승인 거부 규칙 이해하기.
- 혼합 컨테이너 할당과 함께 컨테이너(container) Topology Manager 범위를 사용해 워크로드를 배포하기.
시작하기 전에
쿠버네티스 클러스터가 있어야 하고, kubectl 명령줄 도구가 클러스터와 통신하도록 구성되어 있어야 해요. 이 튜토리얼을 제어 플레인 호스트로 작동하지 않는 최소 두 개의 노드가 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube로 만들거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있어요:
- iximiuz Labs
- Killercoda
- KodeKloud
버전을 확인하려면 kubectl version을 입력하세요. 쿠버네티스 서버는 v1.36 이상이어야 해요.
이 튜토리얼을 완료하려면 다음이 필요해요:
- Linux 워커 노드가 있는 쿠버네티스 클러스터(파드 레벨 리소스 관리자는 Windows 노드에서 지원되지 않음).
- NUMA 토폴로지가 있는 워커 노드가 적어도 하나(정렬을 관찰하려면 여러 NUMA 노드가 바람직함).
- 워커 노드에서 kubelet 구성을 수정하고 kubelet 서비스를 재시작할 수 있는 관리 접근 권한(root 또는
sudo). - 네임스페이스와 파드를 만들 수 있는 권한이 있는 kubectl 접근.
제어 플레인과 워커 노드 모두에 대해 다음 기능 게이트가 활성화되어 있는지 확인해요:
PodLevelResourcesPodLevelResourceManagers
네임스페이스 만들기
이 튜토리얼에서 만든 리소스가 클러스터의 나머지 부분과 격리되도록 네임스페이스를 만들어요:
kubectl create namespace plrm-tutorial
혼합 할당과 함께 pod 범위 사용하기
Topology Manager 범위가 pod로 설정되면 kubelet은 .spec.resources에 기반해 전체 파드에 대해 단일 NUMA 정렬을 수행해요. 결과적인 리소스 예산은 다음과 같이 분할돼요: Guaranteed 리소스를 요청하는 컨테이너는 배타적 슬라이스를 받고, 배타적 할당을 받지 않는 컨테이너는 파드 레벨 공유 풀에서 나머지 예산을 공유해요.
1단계: pod 범위용 kubelet 구성
이 동작을 활성화하려면 이러한 워크로드를 실행할 대상 워커 노드에서 kubelet을 필수 정책으로 구성해요. 해당 노드에 대한 kubelet 구성을 다음과 같이 업데이트할 수 있어요:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cpuManagerPolicy: "static"
memoryManagerPolicy: "Static"
topologyManagerScope: "pod"
topologyManagerPolicy: "single-numa-node"
topologyManagerPolicy에 대해 유효한 값은single-numa-node,restricted,best-effort예요. 파드 레벨 리소스 관리를 사용할 때는 다른 값을 지정할 수 없어요.
구성을 적용하려면 kubelet을 재시작해요. 예를 들어 systemd가 있는 Linux에서: systemctl restart kubelet.service.
2단계: 혼합 할당 파드 배포
다음 예시 파드 매니페스트를 고려해 봐요. 파드는 파드 레벨에서 총 4 CPU 예산(.spec.resources)을 요청해요. 파드 안에서:
main-app컨테이너는 2개의 전체 CPU 코어에 대한 배타적 할당을 요청해요 (requests = limits = 2 CPU).metrics-sidecar와logging-sidecar는 컨테이너 레벨 요청을 지정하지 않으므로, 그 두 사이드카 컨테이너는 파드 레벨 공유 풀에서 남는 CPU 코어 2개를 공유해요.
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"
매니페스트를 클러스터에 적용해요:
kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-pod-scope-mixed.yaml --namespace=plrm-tutorial
3단계: 리소스 분할 확인 및 검사
- 파드가 성공적으로 실행 중인지 확인해요:
kubectl get pod pod-scope-mixed --namespace=plrm-tutorial
- 백그라운드에서 무슨 일이 일어났는지 이해해요:
flowchart TD
subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
direction TB
C1["main-app<br/>(Exclusive: 2 CPUs, 2Gi Memory)"]
subgraph Pool["Pod Shared Pool: 2 CPUs, 2Gi Memory"]
C2["metrics-sidecar"]
C3["logging-sidecar"]
end
end
- 파드 정렬: Topology Manager가 4 CPU 파드 요청(
spec.resources)을 평가하고 전체 파드를 단일 NUMA 노드에 할당했어요. - 배타적 할당: CPU Manager가
main-app에 전용 2 CPU 슬라이스를 할당했어요.main-app에 대한 CPU CFS 쿼터 스로틀링이 비활성화되어, 그 배타적 코어에 스로틀링 없는 접근을 제공해요. - 파드 공유 풀: 나머지 2 CPU가 파드 레벨 공유 풀을 형성해요.
metrics-sidecar와logging-sidecar는 컨테이너 레벨 리소스(resources: {})를 지정하지 않으므로, CFS 쿼터 강제가 활성화된 이 파드 격리 공유 풀 안에서 실행돼요. 이 두 사이드카 사이에서는 공유되지만, 이 풀은 노드의 외부 워크로드로부터 격리되어, 사이드카에 전용 NUMA 위치성과 노드 레벨 리소스 경합으로부터의 보호를 제공해요.
빈 공유 풀 승인 제한 관찰하기
pod 범위를 사용할 때 kubelet 승인 제어는 공유 풀이 필요한 컨테이너가 있을 때 빈 파드 공유 풀을 초래하는 파드 스펙을 거부해요.
Guaranteed 컨테이너의 배타적 리소스 요청 합이 총 파드 레벨 예산과 같고, 적어도 하나의 다른 컨테이너가 공유 풀을 필요로 한다면, kubelet은 파드를 거부해요.
4단계: 잘못된 파드 매니페스트 검사
다음 매니페스트를 고려해 봐요. 파드는 총 4 CPU 예산을 요청해요. container-a는 배타적 1 CPU를 요청하고 container-b는 배타적 3 CPU를 요청해요(총 4 CPU). container-c는 배타적 리소스를 요청하지 않고 공유 풀이 필요하지만, 0 CPU가 남아요:
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"
5단계: 배포 시도와 거부 관찰
- 매니페스트를 적용해요:
kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-empty-shared-pool.yaml --namespace=plrm-tutorial
- 승인 오류를 관찰하려면 파드 이벤트를 검사해요:
kubectl describe pod empty-shared-pool --namespace=plrm-tutorial
공유 리소스가 필요한 컨테이너에 대해 파드 레벨 공유 풀이 비게 되므로 kubelet이 파드를 거부했음을 설명하는 이벤트 메시지를 주목해요:
Status: Failed
Reason: TopologyAffinityError
Message: Pod was rejected: Pod Scope pod with pod-level resources failed admission under pod-scope topology manager
flowchart TD
subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
direction TB
C1["container-a<br/>(Exclusive: 1 CPU, 1Gi Memory)"]
C2["container-b<br/>(Exclusive: 3 CPUs, 3Gi Memory)"]
subgraph Pool["Pod Shared Pool: 0 CPUs, 0Gi Memory"]
C3["container-c<br/>(Requires shared pool)"]
end
end
Pool --> Rejection["Admission Error: Pod Rejected!"]
style Rejection fill:#ffcccc,stroke:#ff0000,stroke-width:2px
혼합 할당과 함께 container 범위 사용하기
Topology Manager 범위를 container로 구성할 수도 있어요. 이 모드에서 kubelet은 각 컨테이너를 배타적 할당을 위해 개별적으로 평가하는 반면, .spec.resources의 전체 파드 예산은 여전히 QoS와 cgroup 한도 경계를 강제해요.
6단계: container 범위용 kubelet 구성
대상 워커 노드에서 container 범위에 대한 kubelet 구성을 업데이트해요:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cpuManagerPolicy: "static"
memoryManagerPolicy: "Static"
topologyManagerScope: "container"
topologyManagerPolicy: "single-numa-node"
구성을 적용하려면 kubelet을 재시작해요. 예를 들어 systemd가 있는 Linux에서: systemctl restart kubelet.service.
7단계: 컨테이너 범위 혼합 워크로드 배포
다음 예시 파드 매니페스트를 고려해 봐요. 파드는 총 4 CPU 예산을 가져요:
infrastructure-sidecar는 배타적 2 CPU 슬라이스를 요청해요 (requests = limits = 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. 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
매니페스트를 적용해요:
kubectl apply -f https://k8s.io/examples/pods/resource/pod-level-resource-managers-container-scope-mixed.yaml --namespace=plrm-tutorial
8단계: 배포 확인
- 파드가 실행 중인지 확인해요:
kubectl get pod container-scope-mixed --namespace=plrm-tutorial
- 백그라운드에서 무슨 일이 일어났는지 이해해요:
flowchart TD
subgraph Pod["Pod-Level Budget: 4 CPUs, 4Gi Memory"]
direction TB
C1["infrastructure-sidecar<br/>(Exclusive NUMA Slice: 2 CPUs, 2Gi Memory)"]
subgraph NodePool["Node Shared Pool: Pod-level limit"]
C3["worker-2"]
C2["worker-1"]
end
end
- 컨테이너 범위 정렬: container 범위에서 kubelet은 컨테이너를 개별적으로 평가해요.
infrastructure-sidecar는 노드의 할당 가능 풀에서 직접 배타적이고 NUMA 정렬된 2 CPU 슬라이스를 받아요. - 노드 공유 풀:
worker-1과worker-2는 컨테이너 레벨 리소스 요청(resources: {})을 지정하지 않으므로, container 범위에서 (파드 격리 풀이 아닌) 노드의 일반 공유 풀에서 실행돼요. - 파드 한도 강제: 모든 컨테이너에 걸친 총 CPU 소비는 파드 레벨 한도(
spec.resources.limits)에 의해 4 CPU로 제한된 채 유지돼요.
정리
이 튜토리얼 중에 만든 네임스페이스와 모든 예시 파드를 삭제해요:
kubectl delete namespace plrm-tutorial
더 알아보기 (Learn more)
- 개념 문서 읽기: 파드 레벨 리소스 관리자.
- 노드 레벨 리소스 관리자를 구성하는 방법 배우기: 노드에서 토폴로지 관리 정책 제어하기, 노드에서 CPU 관리 정책 제어하기, NUMA 인지 메모리 관리자 활용하기.
- 리소스를 할당하는 방법 배우기: 파드 레벨 CPU와 메모리 리소스 할당하기, 컨테이너와 파드에 CPU 리소스 할당하기, 컨테이너와 파드에 메모리 리소스 할당하기.