컨테이너에 할당된 CPU·메모리 리소스 크기 조정하기

컨테이너에 할당된 CPU·메모리 리소스 크기 조정하기 (Resize CPU and Memory Resources assigned to Containers)

이 페이지는 파드를 다시 만들지 않고 컨테이너에 할당된 CPU와 메모리 리소스 요청 및 한도를 변경하는 방법을 설명해요.

전통적으로 파드의 리소스 요구 사항을 변경하려면 기존 파드를 삭제하고 대체 파드를 만들어야 했으며, 종종 워크로드 컨트롤러로 관리됐어요. In-place Pod Resize는 실행 중인 파드의 컨테이너 CPU/메모리 할당을 변경하면서 애플리케이션 중단을 피할 수 있게 해줘요. 파드 리소스 크기를 조정하는 과정은 파드에 할당된 CPU·메모리 리소스 크기 조정하기에서 다뤄요.

핵심 개념 (Key Concepts):

  • 원하는 리소스 (Desired Resources): 컨테이너의 spec.containers[*].resources는 컨테이너의 원하는 리소스를 나타내며 CPU와 메모리에 대해 변경 가능해요.
  • 실제 리소스 (Actual Resources): status.containerStatuses[*].resources 필드는 실행 중인 컨테이너에 현재 구성된 리소스를 반영해요. 시작되지 않았거나 재시작된 컨테이너의 경우 다음 시작 시 할당된 리소스를 반영해요.
  • 크기 조정 트리거 (Triggering a Resize): 파드의 사양에서 원하는 requests와 limits를 업데이트해 크기 조정을 요청할 수 있어요. 이것은 일반적으로 파드의 resize 하위 리소스를 대상으로 kubectl patch, kubectl apply, kubectl edit을 사용해 수행돼요. 원하는 리소스가 할당된 리소스와 일치하지 않으면 Kubelet이 컨테이너 크기를 조정하려고 시도해요.
  • 할당된 리소스 (Allocated Resources, 고급): status.containerStatuses[*].allocatedResources 필드는 Kubelet이 확인한 리소스 값을 추적하며, 주로 내부 스케줄링 로직에 사용돼요. 대부분의 모니터링·검증 목적에는 status.containerStatuses[*].resources에 집중해요.

노드에 보류 중이거나 불완전한 크기 조정(아래 파드 크기 조정 상태 참고)이 있는 파드가 있으면 스케줄러는 스케줄링 결정을 내릴 때 컨테이너의 원하는 요청, 할당된 요청, status의 실제 요청의 최대값을 사용해요.

출처: 문서

본문

시작하기 전에 (Before you begin)

Kubernetes 클러스터가 있어야 하고 kubectl 명령줄 도구가 클러스터와 통신하도록 구성되어 있어야 해요. 이 튜토리얼은 컨트롤 플레인 호스트로 작동하지 않는 노드가 최소 두 개 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube를 이용해 만들거나, 아래 Kubernetes 플레이그라운드 중 하나를 사용할 수 있어요.

버전을 확인하려면 kubectl version을 입력해요.

InPlacePodVerticalScaling 기능 게이트가 제어 플레인과 클러스터의 모든 노드에 대해 활성화되어야 해요.

지연된 크기 조정 요청에 대한 자동 스케줄러 선점을 활성화하려면 InPlacePodVerticalScalingSchedulerPreemption 기능 게이트도 제어 플레인에 대해 활성화되어야 해요.

메모리 지원(medium: Memory) emptyDir 볼륨을 동적으로 크기 조정하려면 InPlacePodVerticalScalingMemoryBackedVolumes 기능 게이트도 제어 플레인과 노드에 대해 활성화되어야 하고, 기본 노드가 cgroup v2를 실행 중이어야 해요.

--subresource=resize 플래그를 사용하려면 kubectl 클라이언트 버전이 최소 v1.32여야 해요.

파드 크기 조정 상태 (Pod resize status)

Kubelet은 크기 조정 요청의 상태를 나타내도록 파드의 status 조건을 업데이트해요:

  • type: PodResizePending: Kubelet이 요청을 즉시 승인할 수 없음. message 필드가 이유를 설명해줘요. reason: Infeasible: 요청된 크기 조정이 현재 노드에서 불가능함(노드가 가진 것보다 더 많은 리소스를 요청하는 경우 등). reason: Deferred: 요청된 크기 조정이 현재는 불가능하지만 나중에 가능해질 수 있음(예: 다른 파드가 제거된 경우). Kubelet은 크기 조정을 다시 시도해요.
  • type: PodResizeInProgress: Kubelet이 크기 조정을 수락하고 리소스를 할당했지만 변경 사항이 아직 적용 중임. 이것은 보통 짧지만 리소스 유형과 런타임 동작에 따라 더 오래 걸릴 수 있어요. 작동 중 오류는 message 필드에(reason: Error와 함께) 보고돼요.

지연된 크기 조정이 재시도되고 선점되는 방식 (How deferred resizes are retried and preempted)

요청된 크기 조정이 Deferred(지연됨)이면 kubelet은 주기적으로 크기 조정을 다시 시도해요. 예를 들어 다른 파드가 제거되거나 축소될 때요.

<div class="feature-state-notice feature-alpha" title="Feature Gate: InPlacePodVerticalScalingSchedulerPreemption">
          <span class="feature-state-name">Feature state:</span>
          <span class="feature-state-details">

             <span class="feature-state-stage">Alpha</span> since Kubernetes v1.37; disabled by default
           </span>
        </div>

        <div class="feature-alpha">

            <details>
            <summary>More information about this feature</summary>
            <p>To use this feature, you (or a cluster administrator) will need to enable the <a href="/docs/reference/command-line-tools-reference/feature-gates/#InPlacePodVerticalScalingSchedulerPreemption"><tt>InPlacePodVerticalScalingSchedulerPreemption</tt></a> feature gate for all relevant components in your cluster.</p>
            </details>
            </div>

기능 게이트 활성화 또는 비활성화에서 더 많은 정보를 확인해요.

InPlacePodVerticalScalingSchedulerPreemption 기능 게이트가 활성화되면 kube-scheduler가 Deferred 크기 조정 상태의 파드를 감시해요. 노드에 더 높은 우선순위 파드의 크기 조정 요청을 충족할 충분한 용량이 없으면, 스케줄러는 해당 노드에서 더 낮은 우선순위 파드를 선점(축출)해 필요한 CPU 또는 메모리 용량을 확보할 수 있어요. 자세한 내용은 인플레이스 파드 크기 조정을 위한 선점을 참고해요.

지연된 크기 조정이 여러 개 있으면 다음 우선순위에 따라 재시도돼요:

  • 더 높은 우선순위(PriorityClass 기반)의 파드가 먼저 크기 조정 요청을 재시도해요.
  • 두 파드의 우선순위가 같으면 guaranteed 파드의 크기 조정이 burstable 파드의 크기 조정보다 먼저 재시도돼요.
  • 다른 모든 것이 같으면 Deferred 상태에 더 오래 있었던 파드가 우선순위를 받아요.

더 높은 우선순위의 크기 조정이 pending으로 표시된다고 해서 나머지 대기 중인 크기 조정이 시도되는 것을 막지는 않아요. 더 높은 우선순위의 크기 조정이 다시 지연되더라도 남은 모든 대기 중인 크기 조정은 여전히 재시도돼요.

observedGeneration 필드 활용하기 (Leveraging observedGeneration Fields)

  • 최상위 status.observedGeneration 필드는 kubelet이 인지한 최신 파드 사양에 해당하는 metadata.generation을 보여줘요. 이것을 사용해 kubelet이 처리한 가장 최근 크기 조정 요청을 결정할 수 있어요.
  • PodResizeInProgress 조건에서 conditions[].observedGeneration 필드는 현재 진행 중인 크기 조정이 시작되었을 때의 podSpec의 metadata.generation을 나타내요.
  • PodResizePending 조건에서 conditions[].observedGeneration 필드는 대기 중인 크기 조정의 할당이 마지막으로 시도되었을 때 podSpec의 metadata.generation을 나타내요.

컨테이너 크기 조정 정책 (Container resize policies)

컨테이너 사양에서 resizePolicy를 설정해 크기 조정 시 컨테이너를 재시작할지 제어할 수 있어요. 이것은 리소스 유형(CPU 또는 메모리)에 따라 세밀한 제어를 허용해요.

resizePolicy:
    - resourceName: cpu
      restartPolicy: NotRequired
    - resourceName: memory
      restartPolicy: RestartContainer
  • NotRequired: (기본값) 컨테이너를 재시작하지 않고 실행 중인 컨테이너에 리소스 변경을 적용해요.
  • RestartContainer: 새 리소스 값을 적용하기 위해 컨테이너를 재시작해요. 많은 애플리케이션과 런타임이 메모리 할당을 동적으로 조정할 수 없기 때문에 메모리 변경에는 종종 필요해요.

리소스에 대해 resizePolicy[*].restartPolicy가 지정되지 않으면 기본값 NotRequired로 설정돼요.

예시 시나리오 (Example Scenario)

CPU에 restartPolicy: NotRequired, 메모리에 restartPolicy: RestartContainer로 구성된 컨테이너를 고려해요.

  • CPU 리소스만 변경되면 컨테이너가 인플레이스로 크기 조정돼요.
  • 메모리 리소스만 변경되면 컨테이너가 재시작돼요.
  • CPU와 메모리 리소스가 동시에 변경되면 컨테이너가 재시작돼요(메모리 정책 때문에).

메모리 지원 emptyDir 볼륨 크기 조정 (Resizing memory-backed emptyDir volumes)

InPlacePodVerticalScalingMemoryBackedVolumes 기능 게이트가 활성화되면 파드 /resize 하위 리소스가 컨테이너를 재시작하거나 파드를 다시 만들지 않고도 실행 중인 파드에서 메모리 지원(medium: Memory) emptyDir 볼륨의 sizeLimit 업데이트를 지원해요.

볼륨의 sizeLimit가 /resize 하위 리소스를 통해 업데이트되면 Kubelet이 컨테이너 중단 없이 기본 tmpfs 마운트를 동적으로 업데이트하면서, 메모리 부족 오류나 잘못된 축출 트리거를 안전하게 방지해요.

메모리 지원 emptyDir 볼륨의 크기를 조정하려면 파드의 resize 하위 리소스를 대상으로 spec.volumes[].emptyDir.sizeLimit을 업데이트해요:

kubectl patch pod <pod-name> --subresource resize --patch \
  '{"spec":{"volumes":[{"name":"cache-volume", "emptyDir":{"sizeLimit":"200Mi"}}]}}'

파드의 status 조건(PodResizePending과 PodResizeInProgress)을 사용해 크기 조정 진행 상황을 모니터링할 수 있어요. 완료되면 실행 중인 컨테이너에서 실제 볼륨 용량을 확인할 수 있어요(예: kubectl execdf -h를 실행).

제한 사항 (Limitations)

Kubernetes 1.37에서 파드 리소스를 인플레이스로 크기 조정하는 데는 다음 제한이 있어요:

  • 리소스 유형 (Resource Types): CPU와 메모리 리소스만 크기 조정할 수 있어요.
  • 메모리 감소 (Memory Decrease): 메모리 크기 조정 재시작 정책이 NotRequired(또는 미지정)이면 kubelet은 메모리 한도를 줄일 때 oom-kill을 방지하기 위해 최선을 다하지만 어떤 보장도 하지 않아요. 컨테이너 메모리 한도를 낮추기 전에 메모리 사용량이 요청된 한도를 초과하면 크기 조정이 건너뛰어지고 상태가 "In Progress"로 남아요. 이것은 검사 후 메모리 사용량이 급증할 수 있는 경쟁 조건에 여전히 노출되기 때문에 최선의 노력으로 간주돼요.
  • QoS 클래스 (QoS Class): 파드의 원래 QoS(서비스 품질) 클래스(Guaranteed, Burstable, BestEffort)는 생성 시 결정되며 크기 조정으로 변경할 수 없어요. 크기 조정된 리소스 값은 여전히 원래 QoS 클래스의 규칙을 따라야 해요: Guaranteed: 크기 조정 후에도 CPU와 메모리 모두에 대해 요청이 계속 한도와 같아야 해요. Burstable: CPU와 메모리 모두에 대해 요청과 한도가 동시에 같아질 수 없어요(그러면 Guaranteed로 바뀌기 때문). BestEffort: 리소스 요구 사항(requests 또는 limits)을 추가할 수 없어요(그러면 Burstable 또는 Guaranteed로 바뀌기 때문).
  • 컨테이너 유형 (Container Types): 재시작 불가능한 init 컨테이너임시 컨테이너는 크기 조정할 수 없어요. 사이드카 컨테이너는 크기 조정할 수 있어요.
  • 리소스 제거 (Resource Removal): 리소스 요청과 한도는 한 번 설정되면 완전히 제거할 수 없고, 다른 값으로만 변경할 수 있어요.
  • 운영체제 (Operating System): Windows 파드는 인플레이스 크기 조정을 지원하지 않아요.
  • 노드 정책 (Node Policies): 정적 CPU 또는 메모리 매니저 정책으로 관리되는 파드는 인플레이스로 크기 조정할 수 없어요.
  • 스왑 (Swap): 스왑 메모리를 사용하는 파드는 메모리에 대한 resizePolicy가 RestartContainer가 아니면 메모리 요청의 크기를 조정할 수 없어요.
  • 메모리 지원 볼륨 크기 조정 (Memory-Backed Volume Resizing): 메모리 지원(medium: Memory) emptyDir 볼륨을 인플레이스로 크기 조정하려면 cgroup v2를 실행하는 노드가 필요해요. cgroup v1 노드에서는 인플레이스 볼륨 크기 조정 요청이 불가능(infeasible)으로 거부돼요. 디스크 지원 emptyDir 볼륨과 영구 볼륨은 파드 /resize 하위 리소스로 크기 조정할 수 없어요.

이러한 제한은 향후 Kubernetes 버전에서 완화될 수 있어요.

네임스페이스 만들기 (Create a namespace)

이 연습에서 만드는 리소스를 클러스터의 나머지 부분과 격리하도록 네임스페이스를 만들어요.

kubectl create namespace qos-example

예시 1: 재시작 없이 CPU 크기 조정 (Example 1: Resizing CPU without restart)

먼저 인플레이스 CPU 크기 조정과 재시작 요구 메모리 크기 조정을 위해 설계된 파드를 만들어요.

apiVersion: v1
kind: Pod
metadata:
  name: resize-demo
  namespace: qos-example
spec:
  containers:
  - name: pause
    image: registry.k8s.io/pause:3.8
    resizePolicy:
    - resourceName: cpu
      restartPolicy: NotRequired # Default, but explicit here
    - resourceName: memory
      restartPolicy: RestartContainer
    resources:
      limits:
        memory: "200Mi"
        cpu: "700m"
      requests:
        memory: "200Mi"
        cpu: "700m"

파드를 만들어요:

kubectl create -f pod-resize.yaml -n qos-example

이 파드는 Guaranteed QoS 클래스로 시작해요. 초기 상태를 확인해요:

# Wait a moment for the pod to be running
kubectl get pod resize-demo --output=yaml -n qos-example

spec.containers[0].resourcesstatus.containerStatuses[0].resources를 관찰해요. 이것들은 매니페스트(700m CPU, 200Mi 메모리)와 일치해야 해요. status.containerStatuses[0].restartCount에 주목해요(0이어야 함).

이제 CPU 요청과 한도를 800m으로 늘려요. --subresource resize 명령줄 인자로 kubectl patch를 사용해요.

kubectl patch pod resize-demo -n qos-example --subresource resize --patch \
  '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"cpu":"800m"}, "limits":{"cpu":"800m"}}}]}}'

# Alternative methods:
# kubectl -n qos-example edit pod resize-demo --subresource resize
# kubectl -n qos-example apply -f <updated-manifest> --subresource resize --server-side

패치 후 파드 상태를 다시 확인해요:

kubectl get pod resize-demo --output=yaml --namespace=qos-example

다음을 볼 수 있어요:

  • spec.containers[0].resources가 이제 cpu: 800m을 보여줌.
  • status.containerStatuses[0].resourcescpu: 800m을 보여주며, 노드에서 크기 조정이 성공했음을 나타냄.
  • status.containerStatuses[0].restartCount0으로 유지되며, CPU resizePolicy가 NotRequired이기 때문.

예시 2: 재시작으로 메모리 크기 조정 (Example 2: Resizing memory with restart)

이제 같은 파드의 메모리를 300Mi로 늘려 크기 조정해요. 메모리 resizePolicy가 RestartContainer이므로 컨테이너가 재시작될 것으로 예상해요.

kubectl patch pod resize-demo -n qos-example --subresource resize --patch \
  '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"memory":"300Mi"}, "limits":{"memory":"300Mi"}}}]}}'

패치 직후 파드 상태를 확인해요:

kubectl get pod resize-demo --output=yaml --namespace=qos-example

이제 다음을 관찰해야 해요:

  • spec.containers[0].resourcesmemory: 300Mi를 보여줌.
  • status.containerStatuses[0].resourcesmemory: 300Mi를 보여줌.
  • status.containerStatuses[0].restartCount1(이전에 재시작이 있었다면 그 이상)로 증가하며, 메모리 변경을 적용하기 위해 컨테이너가 재시작되었음을 나타냄.

문제 해결: 불가능한 크기 조정 요청 (Troubleshooting: Infeasible resize request)

다음으로, 노드 용량을 초과할 가능성이 높은 비현실적인 CPU 양(예: 1000 전체 코어, 밀리코어를 위해 "1000m" 대신 "1000"으로 작성)을 요청해요.

# Attempt to patch with an excessively large CPU request
kubectl patch pod resize-demo -n qos-example --subresource resize --patch \
  '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"cpu":"1000"}, "limits":{"cpu":"1000"}}}]}}'

파드의 세부 정보를 조회해요:

kubectl get pod resize-demo --output=yaml --namespace=qos-example

문제를 나타내는 변경 사항을 볼 수 있어요:

  • spec.containers[0].resources가 원하는 상태(cpu: "1000")를 반영함.
  • type: PodResizePendingreason: Infeasible인 조건이 파드에 추가됨.
  • 조건의 message가 이유를 설명함(Node didn't have enough capacity: cpu, requested: 800000, capacity: ...).
  • 결정적으로 status.containerStatuses[0].resources는 여전히 이전 값을(cpu: 800m, memory: 300Mi) 보여주는데, 불가능한 크기 조정이 Kubelet에 의해 적용되지 않았기 때문.
  • restartCount는 이 실패한 시도 때문에 변경되지 않음.

이것을 고치려면 실행 가능한 리소스 값으로 파드를 다시 패치해야 해요.

정리하기 (Clean up)

네임스페이스를 삭제해요. 이렇게 하면 이 작업을 위해 만든 모든 파드가 삭제돼요:

kubectl delete namespace qos-example

다음 단계 (What's next)

애플리케이션 개발자용 (For application developers)

클러스터 관리자용 (For cluster administrators)

더 알아보기 (Learn more)