스터디/AEWS

[AEWS] 5주차 쿠버네티스 오토스케일링 이해하기 - 수직 확장 (VPA)

안녕유지 2025. 3. 9. 01:40
Cloudnet AWES 5주차 스터디를 진행하며 정리한 글입니다.

 

이번 포스팅에서는 쿠버네티스에서 리소스 확장 방식 중 Pod의 CPU 및 메모리 Request과 Limit을 자동으로 조정하여 성능을 최적화하는 방식인 VPA에 대해 소개하겠습니다.

 

VPA

VPA(Vertical Pod Autoscaler)Pod 개수는 유지한 채, 개별 Pod의 리소스를 동적으로 조정하는 방식입니다.

VPA는 HPA와 동시에 사용할 수 없으며, 해당 스케일링을 하게 된다면 pod를 재실행해야합니다.

 

VPA는

1) pod의 과거 사용량을 분석하여 최적의 CPU, Memory Request, Limits을 추천하고,

2) 실행 중인 pod의 리소스를 자동으로 조정할 수 있습니다.

 

VPA의 동작원리

 

Request 

  • 컨테이너 실행될 때 최소한으로 보장 받는 값입니다.
  • 스케줄러는 Request 값을 기준으로 노드에 컨테이너를 배치합니다.
  • 노드에 충분한 리소스가 없으면 해당 pod는 Pending되고, cluster autoscaler가 활성화된 경우에는 새로운 노드가 추가될 수도 있습니다.

Limit

  • 컨테이너가 사용할 수 있는 최대 값입니다.
  • 컨테이너가 Limit을 초과할 경우 CPU는 제한된 속도로 실행되고 Memory는 OOM으로 종료됩니다.

따라서, request 값이 크다면 불필요한 node 추가로 인해 비용이 증가되고, 너무 낮으면 한 노드에 과도한 pod가 배치되어 성능 저하 및 OOM이 발생할 수 있어서 적절한 request값을 설정하는 것이 중요합니다.

 

1) VPA는 실제 Pod에 CPU/Memory 사용량을 모니터링하면서 적절한 requests 값을 자동으로 설정하는데, VPA의 Recommender는 메트릭 서버로부터 Pod의 CPU 및 메모리 사용량 데이터를 수집합니다.

2) 수집된 데이터를 기반으로 각 컨테이너에 대한 최적의 리소스 요청 및 제한 값을 계산합니다. 이때, percentile 값을 사용하여 리소스 사용 패턴을 분석합니다.

3) Admission Controller와 Updater는 Recommender의 추천 값을 적용하여 새로운 Pod의 리소스를 설정하거나, 기존 Pod의 리소스를 조정합니다.

 

 

 

참고)

https://github.com/kubernetes/design-proposals-archive/blob/main/autoscaling/vertical-pod-autoscaler.md

 

design-proposals-archive/autoscaling/vertical-pod-autoscaler.md at main · kubernetes/design-proposals-archive

Archive of Kubernetes Design Proposals. Contribute to kubernetes/design-proposals-archive development by creating an account on GitHub.

github.com

 

https://devocean.sk.com/blog/techBoardDetail.do?ID=164786&boardType=techBlog&searchData=&page=&subIndex=%EC%B5%9C%EC%8B%A0+%EA%B8%B0%EC%88%A0+%EB%B8%94%EB%A1%9C%EA%B7%B8

 

Pod CPU/Memory 리소스 최적화하기 (VPA 및 Kubecost 추천로직 분석)

 

devocean.sk.com

 

 

VPA의 구성 요소

  • Recommends : Pod의 CPU 및 메모리 사용량을 모니터링하고 적절한 Request/Limit 값을 추천
  • Updater : VPA 정책에 따라 Pod의 리소스 값을 업데이트하고 필요하면 Pod를 재시작
  • Admission Controller : Recommender의 추천값을 기반으로 새로운 Pod가 생성될 때 적절한 리소스를 설정하도록 조정, 초기 리소스 할당 최적화

 

VPA의 모드

  • off : 리소스를 모니터링하고 추천값을 제공하지만 실제 변경은 하지 않음
  • auto : VPA가 직접 Pod의 리소스를 업데이트하고 필요하면 재시작
  • initial : 처음 생성될 때만 VPA가 요청값을 설정하고 이후에는 변경하지 않음

 

이제 VPA를 설치하고 실제로 리소스가 증가되어 작동하는 것을 확인해보도록 하겠습니다.

 

VPA 실습

VPA 레포지토리를 클론하고, 

# 현재 실습환경에서는 이미 받아두었습니다.
# 아래 내용은 저의 실습환경에 대한 내용이니, 필요하신 분들은 아래 git repository에서 VPA 설치는 각자 진행하셔서 
# 현재 코드블록은 스킵하시는 것을 추천드립니다.
git clone https://github.com/kubernetes/autoscaler.git 

# 설치에 필요한 openssl 1.0 제거 후 1.1.1 버전 이상 다운로드
openssl version
yum remove openssl -y
yum install openssl11 -y
openssl11 version

# 스크립트 파일 내에 openssl11 수정
sed -i 's/openssl/openssl11/g' ~/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh
git status
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
git add .
git commit -m "openssl version modify"

# VPA 배포
watch -d kubectl get pod -n kube-system
cat hack/vpa-up.sh
./hack/vpa-up.sh
sed -i 's/openssl/openssl11/g' ~/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh
./hack/vpa-up.sh

# VPA 배포 확인
kubectl get crd | grep autoscaling
kubectl get mutatingwebhookconfigurations vpa-webhook-config
kubectl get mutatingwebhookconfigurations vpa-webhook-config -o json | jq

 

hamster 예제를 배포합니다. 

초기 배포시에는 햄스터 'ㅅ' 파드는 CPU는 100M, Memory는 50Mi로 설정된 것을 알 수 있습니다.

하지만 시간이 흐를 수록, VPA updater가 Pod를 강제종료하고 (evict) 새로운 리소스 추천 값을 적용하기 위해 재배포한 이벤트 로그를 확인할 수 있습니다.

이때, 파드의 Request 값은 CPU 587m, Memory 262144k로 변경되었습니다.

# 예제 파일 확인 햄스터~ 'ㅅ'
cat examples/hamster.yaml
# This config creates a deployment with two pods, each requesting 100 millicores
# and trying to utilize slightly above 500 millicores (repeatedly using CPU for
# 0.5s and sleeping 0.5s).
# It also creates a corresponding Vertical Pod Autoscaler that adjusts the
# requests.
# Note that the update mode is left unset, so it defaults to "Auto" mode.
---
apiVersion: "autoscaling.k8s.io/v1"
kind: VerticalPodAutoscaler
metadata:
  name: hamster-vpa
spec:
  # recommenders field can be unset when using the default recommender.
  # When using an alternative recommender, the alternative recommender's name
  # can be specified as the following in a list.
  # recommenders:
  #   - name: 'alternative'
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: hamster
  resourcePolicy:
    containerPolicies:
      - containerName: '*'
        minAllowed:
          cpu: 100m
          memory: 50Mi
        maxAllowed:
          cpu: 1
          memory: 500Mi
        controlledResources: ["cpu", "memory"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hamster
spec:
  selector:
    matchLabels:
      app: hamster
  replicas: 2
  template:
    metadata:
      labels:
        app: hamster
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534 # nobody
      containers:
        - name: hamster
          image: registry.k8s.io/ubuntu-slim:0.14
          resources:
            requests:
              cpu: 100m
              memory: 50Mi
          command: ["/bin/sh"]
          args:
            - "-c"
            - "while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done"

# 예제 파일 배포 햄스터~ ''
kubectl apply -f examples/hamster.yaml 
verticalpodautoscaler.autoscaling.k8s.io/hamster-vpa created
deployment.apps/hamster created

# 배포 초기 상태 - 파드의 리소스 Requests가 처음 배포한대로 적게 잡혀 있음
Every 2.0s: kubectl top pod;echo ----------------------… yujiyeon-ui-MacBookPro.local: 01:23:21

NAME                       CPU(cores)   MEMORY(bytes)
hamster-598b78f579-b8t82   392m         0Mi
hamster-598b78f579-dzxsl   498m         0Mi
hamster-598b78f579-nbkzj   342m         0Mi
----------------------
    Requests:
      cpu:        100m
      memory:     262144k
--
    Requests:
      cpu:        100m
      memory:     50Mi
--
    Requests:
      cpu:        100m
      
      
      
# VPA에 의해 기존 파드 삭제되고 신규 파드가 생성
❯ kubectl get events --sort-by=".metadata.creationTimestamp" | grep VPA
109s        Normal   EvictedPod          verticalpodautoscaler/hamster-vpa   VPA Updater evicted Pod hamster-598b78f579-5ld7r to apply resource recommendation.
109s        Normal   EvictedByVPA        pod/hamster-598b78f579-5ld7r        Pod was evicted by VPA Updater to apply resource recommendation.
49s         Normal   EvictedByVPA        pod/hamster-598b78f579-dzxsl        Pod was evicted by VPA Updater to apply resource recommendation.
49s         Normal   EvictedPod          verticalpodautoscaler/hamster-vpa   VPA Updater evicted Pod hamster-598b78f579-dzxsl to apply resource recommendation.
      memory:     262144k
      
      
# 시간 흐른 후 - 파드의 리소스 Requests가 변화한 것을 알 수 있음
Every 2.0s: kubectl top pod;echo ----------------------… yujiyeon-ui-MacBookPro.local: 01:26:00
                                                                                  in 2.563s (0)
NAME                       CPU(cores)   MEMORY(bytes)
hamster-598b78f579-b8t82   389m         0Mi
hamster-598b78f579-ml7cc   493m         0Mi
----------------------
    Requests:
      cpu:        587m
      memory:     262144k
--
    Requests:
      cpu:        587m
      memory:     262144k

 

 

 

KRR

KRR은 Prometheus-based Kubernetes Resource Recommendations의 약자로, 실제 사용량 기준으로 Request와 Limit의 사용량을 추천합니다.

 

https://github.com/robusta-dev/krr#getting-started

 

GitHub - robusta-dev/krr: Prometheus-based Kubernetes Resource Recommendations

Prometheus-based Kubernetes Resource Recommendations - robusta-dev/krr

github.com

 

KRR과 VPA의 비교

Krr는 설치가 간편하고 즉시 리소스 추천을 제공할 수 있으며 확장성과 커스텀 가능성이 뛰어나다고 합니다.

표는 GPT가 생성하여 비교해주었습니다.

 

 

Mac 환경에서 krr을 설치하고 결과값을 확인해보겠습니다.

제 로컬 환경에서도 쿠버네티스 환경에 배포된 리소스들에 대한 추천값을 데이터화하여 확인할 수 있었습니다.

brew tap robusta-dev/homebrew-krr
brew install krr
krr --help

# 히스토리 데이터 기간, CPU Percentile, 메모리 버퍼 등을 커스터마이징하여 추천 설정 가능
krr simple
┏━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃        ┃            ┃              ┃      ┃          ┃             ┃              ┃          ┃              ┃            ┃             ┃ Memory       ┃ Memory      ┃
┃ Number ┃ Namespace  ┃ Name         ┃ Pods ┃ Old Pods ┃ Type        ┃ Container    ┃ CPU Diff ┃ CPU Requests ┃ CPU Limits ┃ Memory Diff ┃ Requests     ┃ Limits      ┃
┡━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│     1. │ monitoring │ kube-promet… │ 1    │ 0        │ Deployment  │ grafana-sc-… │ +10m     │ (+10m) unset │ unset      │ +112Mi      │ (+112Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ -> 10m       │            │             │ unset ->     │ 112Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 112Mi        │             │
│     2. │            │              │      │          │             │ grafana-sc-… │ +10m     │ (+10m) unset │ unset      │ +115Mi      │ (+115Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ -> 10m       │            │             │ unset ->     │ 115Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 115Mi        │             │
│     3. │            │              │      │          │             │ grafana      │ +12m     │ (+12m) unset │ unset      │ +113Mi      │ (+113Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ -> 12m       │            │             │ unset ->     │ 113Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 113Mi        │             │
├────────┼────────────┼──────────────┼──────┼──────────┼─────────────┼──────────────┼──────────┼──────────────┼────────────┼─────────────┼──────────────┼─────────────┤
│     4. │ monitoring │ kube-promet… │ 1    │ 0        │ Deployment  │ kube-state-… │ +10m     │ (+10m) unset │ unset      │ +100Mi      │ (+100Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ -> 10m       │            │             │ unset ->     │ 100Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 100Mi        │             │
├────────┼────────────┼──────────────┼──────┼──────────┼─────────────┼──────────────┼──────────┼──────────────┼────────────┼─────────────┼──────────────┼─────────────┤
│     5. │ monitoring │ kube-promet… │ 1    │ 0        │ Deployment  │ kube-promet… │ +10m     │ (+10m) unset │ unset      │ +100Mi      │ (+100Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ -> 10m       │            │             │ unset ->     │ 100Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 100Mi        │             │
├────────┼────────────┼──────────────┼──────┼──────────┼─────────────┼──────────────┼──────────┼──────────────┼────────────┼─────────────┼──────────────┼─────────────┤
│     6. │ monitoring │ prometheus-… │ 1    │ 8        │ Deployment  │ prometheus-… │ +170m    │ (+170m)      │ unset      │ +242Mi      │ (+242Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ unset ->     │            │             │ unset ->     │ 242Mi       │
│        │            │              │      │          │             │              │          │ 170m         │            │             │ 242Mi        │             │
├────────┼────────────┼──────────────┼──────┼──────────┼─────────────┼──────────────┼──────────┼──────────────┼────────────┼─────────────┼──────────────┼─────────────┤
│     7. │ monitoring │ prometheus-… │ 1    │ 0        │ StatefulSet │ prometheus   │ +28m     │ (+28m) unset │ unset      │ +561Mi      │ (+561Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ -> 28m       │            │             │ unset ->     │ 561Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 561Mi        │             │
│     8. │            │              │      │          │             │ config-relo… │ +10m     │ (+10m) unset │ unset      │ +100Mi      │ (+100Mi)     │ unset ->    │
│        │            │              │      │          │             │              │          │ -> 10m       │            │             │ unset ->     │ 100Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 100Mi        │             │
├────────┼────────────┼──────────────┼──────┼──────────┼─────────────┼──────────────┼──────────┼──────────────┼────────────┼─────────────┼──────────────┼─────────────┤
│     9. │ monitoring │ kube-promet… │ 3    │ 0        │ DaemonSet   │ node-export… │ +30m     │ (+10m) unset │ unset      │ +300Mi      │ (+100Mi)     │ unset ->    │
│        │            │              │      │          │             │              │ (3 pods) │ -> 10m       │            │ (3 pods)    │ unset ->     │ 100Mi       │
│        │            │              │      │          │             │              │          │              │            │             │ 100Mi        │             │
└────────┴────────────┴──────────────┴──────┴──────────┴─────────────┴──────────────┴──────────┴──────────────┴────────────┴─────────────┴──────────────┴─────────────┘


# 1달(720시간) 데이터 기준으로 CPU Peak 사용량의 90%와 메모리 Peak 사용량의 10% 여유를 설정하는 예시
krr simple --history_duration 720 --cpu_percentile 90 --memory_buffer_percentage 10