HorizontalPodAutoscaler 워크스루

HorizontalPodAutoscaler 워크스루

애플리케이션에 부하가 몰리면 파드 수를 자동으로 늘리고, 부하가 줄면 다시 줄여주는 컨트롤러가 필요할 때가 많죠. HorizontalPodAutoscaler(HPA)가 정확히 그 역할을 해요. 이 페이지에서는 예제 웹 앱으로 HPA를 실제로 켜고, 부하를 걸어서 autoscaler가 어떻게 반응하는지, 그리고 CPU 외에 다양한 메트릭으로 확장하는 방법까지 차근차근 살펴볼게요.

출처: HorizontalPodAutoscaler Walkthrough — Kubernetes 공식 문서

본문

HorizontalPodAutoscaler는 Deployment나 StatefulSet 같은 워크로드 리소스를 자동으로 업데이트해서, 수요에 맞춰 워크로드를 스케일링하는 걸 목표로 해요.

수평 스케일링(horizontal scaling)은 부하가 늘면 대응 방식으로 파드를 더 많이 배치하는 거예요. 이는 이미 실행 중인 파드에 리소스(예: 메모리나 CPU)를 더 할당하는 수직 스케일링(vertical scaling)과는 달라요. 부하가 줄어서 파드 수가 설정한 최솟값보다 많아지면, HorizontalPodAutoscaler는 워크로드 리소스(Deployment, StatefulSet 같은 것)에 다시 줄이라고 지시해요.

이 문서는 예제 웹 앱에 대해 HorizontalPodAutoscaler를 켜서 스케일을 자동 관리하게 만드는 과정을 보여줘요. 예제 워크로드는 몇몇 PHP 코드를 실행하는 Apache httpd예요.

사전 준비

이 워크스루를 진행하려면 Metrics Server가 배포·구성된 클러스터가 필요해요. Kubernetes Metrics Server는 클러스터의 kubelet에서 리소스 메트릭을 수집하고, APIService를 통해 Kubernetes API로 그 메트릭을 노출해서 메트릭 값을 나타내는 새 종류의 리소스를 추가해요.

Metrics Server를 배포하는 방법은 metrics-server 문서를 참고하세요. 아직 더 오래된 쿠버네티스 릴리스를 쓰고 있다면 해당 릴리스에 맞는 문서 버전을 봐야 해요. minikube를 실행 중이라면 다음 명령으로 metrics-server를 켜요.

minikube addons enable metrics-server

php-apache 서버 실행하고 노출하기

HorizontalPodAutoscaler를 시연하려면 먼저 hpa-example 이미지를 사용하는 컨테이너를 실행하는 Deployment를 시작하고, 아래 매니페스트를 사용해 Service로 노출할게요.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

다음 명령으로 실행해요.

kubectl apply -f https://k8s.io/examples/application/php-apache.yaml
deployment.apps/php-apache created
service/php-apache created

HorizontalPodAutoscaler 만들기

서버가 실행 중이니 kubectl로 autoscaler를 만들어요. kubectl autoscale 서브커맨드가 이 작업을 도와줘요. 잠시 후 실행할 명령은, 첫 단계에서 만든 php-apache Deployment가 제어하는 파드의 레플리카를 1개와 10개 사이로 유지하는 HorizontalPodAutoscaler를 만들어요.

간단히 말하면, HPA controller는 평균 CPU 사용률이 모든 파드에 걸쳐 50%가 되도록 레플리카 수를 늘렸다 줄였다 해요(Deployment를 업데이트해서). Deployment는 그러면 ReplicaSet을 갱신하고 — 쿠버네티스에서 모든 Deployment가 이렇게 동작해요 — ReplicaSet은 .spec 변경에 맞춰 파드를 추가하거나 제거해요.

각 파드가 kubectl run으로 200 milli-core를 요청하므로, 이는 평균 CPU 사용량 100 milli-core를 뜻해요. 알고리즘에 대한 자세한 내용은 Algorithm details를 참고하세요.

HorizontalPodAutoscaler를 만들어요.

kubectl autoscale deployment php-apache --cpu=50% --min=1 --max=10
horizontalpodautoscaler.autoscaling/php-apache autoscaled

방금 만든 HorizontalPodAutoscaler의 현재 상태는 다음으로 확인할 수 있어요.

# You can use "hpa" or "horizontalpodautoscaler"; either name works OK.
kubectl get hpa

출력은 대략 이렇게 나와요.

NAME         REFERENCE                     TARGET    MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   0% / 50%  1         10        1          18s

(다른 이름의 HorizontalPodAutoscaler가 보여도 기존에 존재하던 것이라 대개 문제가 되지 않아요.) 현재 CPU 소비는 서버로 요청을 보내는 클라이언트가 없어서 0%예요(TARGET 컬럼은 해당 Deployment가 제어하는 모든 파드의 평균을 보여줘요).

부하 증가시키기

이제 autoscaler가 증가한 부하에 어떻게 반응하는지 볼게요. 이를 위해 클라이언트 역할을 하는 다른 파드를 시작해요. 클라이언트 파드 안의 컨테이너는 무한 루프로 php-apache 서비스에 쿼리를 보내요.

# Run this in a separate terminal
# so that the load generation continues and you can carry on with the rest of the steps
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"

이제 다음을 실행해요.

# type Ctrl+C to end the watch when you're ready
kubectl get hpa php-apache --watch

1분쯤 지나면 더 높은 CPU 부하가 보일 거예요. 예를 들면:

NAME         REFERENCE                     TARGET      MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   305% / 50%  1         10        1          3m

그리고 나서 레플리카가 더 늘어나요. 예를 들어:

NAME         REFERENCE                     TARGET      MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   305% / 50%  1         10        7          3m

여기서 CPU 소비가 request의 305%까지 올라갔어요. 그 결과 Deployment가 7개 레플리카로 조정됐죠.

kubectl get deployment php-apache

레플리카 수가 HorizontalPodAutoscaler의 숫자와 일치하는 걸 볼 수 있어요.

NAME         READY   UP-TO-DATE   AVAILABLE   AGE
php-apache   7/7      7           7           19m

참고 레플리카 수가 안정되려면 몇 분 걸릴 수 있어요. 부하 양은 어떤 식으로도 통제되지 않아서 최종 레플리카 수가 이 예제와 달라질 수 있어요.

부하 생성 중단하기

예제를 마치려면 부하 전송을 멈춰요. busybox 이미지를 실행하는 파드를 만든 터미널에서 <Ctrl> + C를 입력해 부하 생성을 종료해요. 그런 다음 (1분쯤 후) 결과 상태를 확인해요.

# type Ctrl+C to end the watch when you're ready
kubectl get hpa php-apache --watch

출력은 대략 이렇게 나와요.

NAME         REFERENCE                     TARGET       MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache/scale   0% / 50%     1         10        1          11m

Deployment도 스케일다운됐음을 보여줘요.

kubectl get deployment php-apache
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
php-apache   1/1     1            1           27m

CPU 사용률이 0으로 떨어지자 HPA가 자동으로 레플리카 수를 1로 줄였어요. 레플리카 오토스케일링은 몇 분 걸릴 수 있어요.

여러 메트릭과 커스텀 메트릭으로 오토스케일링

autoscaling/v2 API 버전을 사용하면 php-apache Deployment를 오토스케일링할 때 추가 메트릭을 사용할 수 있어요. 먼저 HorizontalPodAutoscaler의 YAML을 autoscaling/v2 형태로 가져와요.

kubectl get hpa php-apache -o yaml > /tmp/hpa-v2.yaml

/tmp/hpa-v2.yaml 파일을 편집기로 열면 대략 이런 YAML이 보일 거예요.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
status:
  observedGeneration: 1
  lastScaleTime: <some-time>
  currentReplicas: 1
  desiredReplicas: 1
  currentMetrics:
  - type: Resource
    resource:
      name: cpu
      current:
        averageUtilization: 0
        averageValue: 0

targetCPUUtilizationPercentage 필드가 metrics라는 배열로 바뀐 걸 볼 수 있어요. CPU 사용률 메트릭은 파드 컨테이너에 지정된 리소스의 백분율로 나타나므로 리소스 메트릭이라고 해요. CPU 외의 다른 리소스 메트릭도 지정할 수 있어요. 기본적으로 CPU 외에 지원되는 유일한 리소스 메트릭은 memory예요. 이 리소스들은 클러스터마다 이름이 바뀌지 않고, metrics.k8s.io API를 사용할 수 있는 한 항상 사용 가능해야 해요.

요청 값의 백분율 대신 직접 값을 사용해 리소스 메트릭을 지정할 수도 있어요. 이때는 target.typeUtilization 대신 AverageValue로 하고, target.averageUtilization 대신 target.averageValue 필드를 설정해요.

  metrics:
  - type: Resource
    resource:
      name: memory
      target:
        type: AverageValue
        averageValue: 500Mi

메트릭에는 다른 두 유형도 있는데 둘 다 커스텀 메트릭으로 간주돼요. 바로 파드 메트릭과 오브젝트 메트릭이에요. 이 메트릭들은 클러스터에 특정한 이름을 가질 수 있고, 더 고급스러운 클러스터 모니터링 구성이 필요해요.

첫 번째 대체 메트릭 유형은 파드 메트릭이에요. 이 메트릭들은 파드를 설명하고, 파드들에 걸쳐 평균을 낸 뒤 대상 값과 비교해서 레플리카 수를 결정해요. 리소스 메트릭과 비슷하게 동작하지만, AverageValue라는 target 유형만 지원한다는 점이 달라요.

파드 메트릭은 이렇게 메트릭 블록으로 지정돼요.

type: Pods
pods:
  metric:
    name: packets-per-second
  target:
    type: AverageValue
    averageValue: 1k

두 번째 대체 메트릭 유형은 오브젝트 메트릭이에요. 이 메트릭들은 파드를 설명하는 대신 같은 네임스페이스에 있는 다른 오브젝트를 설명해요. 메트릭을 오브젝트에서 가져올 필요는 없고, 그저 오브젝트를 설명할 뿐이에요. 오브젝트 메트릭은 ValueAverageValuetarget 유형을 모두 지원해요. Value를 쓰면 대상이 API에서 반환된 메트릭과 직접 비교되고, AverageValue를 쓰면 커스텀 메트릭 API가 반환한 값을 파드 수로 나눈 뒤 대상과 비교해요. 다음 예제는 requests-per-second 메트릭의 YAML 표현이에요.

type: Object
object:
  metric:
    name: requests-per-second
  describedObject:
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    name: main-route
  target:
    type: Value
    value: 2k

이런 메트릭 블록을 여러 개 제공하면 HorizontalPodAutoscaler가 각 메트릭을 차례로 고려해요. 각 메트릭마다 제안된 레플리카 수를 계산하고, 그중 가장 높은 레플리카 수를 선택해요.

예를 들어 네트워크 트래픽에 대한 메트릭을 수집하는 모니터링 시스템이 있다면 kubectl edit으로 위 정의를 이렇게 업데이트할 수 있어요.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
  - type: Pods
    pods:
      metric:
        name: packets-per-second
      target:
        type: AverageValue
        averageValue: 1k
  - type: Object
    object:
      metric:
        name: requests-per-second
      describedObject:
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        name: main-route
      target:
        type: Value
        value: 10k
status:
  observedGeneration: 1
  lastScaleTime: <some-time>
  currentReplicas: 1
  desiredReplicas: 1
  currentMetrics:
  - type: Resource
    resource:
      name: cpu
    current:
      averageUtilization: 0
      averageValue: 0
  - type: Object
    object:
      metric:
        name: requests-per-second
      describedObject:
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        name: main-route
      current:
        value: 10k

그리고 나면 HorizontalPodAutoscaler는 각 파드가 요청한 CPU의 약 50%를 소비하고, 초당 1000 패킷을 처리하며, main-route Ingress 뒤의 모든 파드가 초당 총 10000 요청을 처리하도록 만들려고 할 거예요.

더 구체적인 메트릭으로 오토스케일링

많은 메트릭 파이프라인이 메트릭을 이름으로 또는 _라벨_이라는 추가 설명자 집합으로 기술하게 해줘요. 비리소스 메트릭 유형(파드, 오브젝트, 그리고 아래 설명하는 external) 전부에 대해, 메트릭 파이프라인에 전달되는 추가 라벨 셀렉터를 지정할 수 있어요. 예를 들어 verb 라벨을 가진 http_requests 메트릭을 수집한다면, GET 요청에 대해서만 스케일링하도록 다음 메트릭 블록을 지정할 수 있어요.

type: Object
object:
  metric:
    name: http_requests
    selector: {matchLabels: {verb: GET}}

이 셀렉터는 쿠버네티스 라벨 셀렉터 전체와 같은 문법을 사용해요. 모니터링 파이프라인이 이름과 셀렉터가 여러 시리즈와 일치할 때 여러 시리즈를 단일 값으로 어떻게 합칠지는 파이프라인에 달려 있어요. 셀렉터는 부가적이며, 대상 오브젝트가 아닌 오브젝트를 설명하는 메트릭(Pods 유형에서는 대상 파드, Object 유형에서는 기술된 오브젝트)을 선택할 수 없어요.

쿠버네티스 오브젝트와 무관한 메트릭으로 오토스케일링

쿠버네티스에서 실행되는 애플리케이션은 클러스터 안 어떤 오브젝트와도 명확한 관계가 없는 메트릭, 예를 들어 쿠버네티스 네임스페이스와 직접 상관관계가 없는 호스팅 서비스를 설명하는 메트릭을 기준으로 오토스케일링해야 할 수도 있어요. 쿠버네티스 1.10 이상에서는 external 메트릭으로 이 사용 사례를 다룰 수 있어요.

external 메트릭을 쓰려면 모니터링 시스템에 대한 지식이 필요해요. 설정은 커스텀 메트릭을 쓸 때와 비슷해요. external 메트릭을 쓰면 모니터링 시스템에서 사용할 수 있는 어떤 메트릭을 기준으로도 클러스터를 오토스케일링할 수 있어요. 위와 같이 nameselector가 있는 metric 블록을 제공하고 Object 대신 External 메트릭 유형을 써요. metricSelector에 여러 타임 시리즈가 일치하면 HorizontalPodAutoscaler는 그 값들의 합을 사용해요. external 메트릭은 ValueAverageValue 대상 유형을 모두 지원하는데, Object 유형을 쓸 때와 똑같이 동작해요.

예를 들어 애플리케이션이 호스팅된 큐 서비스의 태스크를 처리한다면, HorizontalPodAutoscaler 매니페스트에 다음 섹션을 추가해서 처리 대기 중인 태스크 30개당 워커 하나가 필요하다고 지정할 수 있어요.

- type: External
  external:
    metric:
      name: queue_messages_ready
      selector:
        matchLabels:
          queue: "worker_tasks"
    target:
      type: AverageValue
      averageValue: 30

가능하면 커스텀 메트릭 대상 유형을 external 메트릭보다 선호해요. 커스텀 메트릭 API는 클러스터 관리자가 보안을 잡기 더 쉬우니까요. external 메트릭 API는 잠재적으로 모든 메트릭에 접근을 허용하므로, 관리자는 노출할 때 주의해야 해요.

부록: Horizontal Pod Autoscaler 상태 조건

autoscaling/v2 형태의 HorizontalPodAutoscaler를 사용하면 쿠버네티스가 설정한 상태 조건(status conditions)을 볼 수 있어요. 이 상태 조건들은 HorizontalPodAutoscaler가 스케일할 수 있는지, 그리고 현재 어떤 방식으로든 제한되어 있는지를 나타내요.

이 조건들은 status.conditions 필드에 나타나요. HorizontalPodAutoscaler에 영향을 주는 조건을 보려면 kubectl describe hpa를 쓸 수 있어요.

kubectl describe hpa cm-test
Name:                           cm-test
Namespace:                      prom
Labels:                         <none>
Annotations:                    <none>
CreationTimestamp:              Fri, 16 Jun 2017 18:09:22 +0000
Reference:                      ReplicationController/cm-test
Metrics:                        ( current / target )
  "http_requests" on pods:      66m / 500m
Min replicas:                   1
Max replicas:                   4
ReplicationController pods:     1 current / 1 desired
Conditions:
  Type                  Status  Reason                  Message
  ----                  ------  ------                  -------
  AbleToScale           True    ReadyForNewScale        the last scale time was sufficiently old as to warrant a new scale
  ScalingActive         True    ValidMetricFound        the HPA was able to successfully calculate a replica count from pods metric http_requests
  ScalingLimited        False   DesiredWithinRange      the desired replica count is within the acceptable range
Events:

이 HorizontalPodAutoscaler에서 건강한 상태의 여러 조건을 볼 수 있어요. 첫 번째인 AbleToScale은 HPA가 스케일을 가져오고 업데이트할 수 있는지, 그리고 백오프 관련 조건이 스케일링을 막는지 여부를 나타내요. 두 번째인 ScalingActive는 HPA가 활성화되어 있는지(대상의 레플리카 수가 0이 아닌지) 그리고 원하는 스케일을 계산할 수 있는지 여부를 나타내요. False라면 보통 메트릭을 가져오는 데 문제가 있다는 뜻이에요. 마지막으로 ScalingLimited는 원하는 스케일이 HorizontalPodAutoscaler의 최댓값이나 최솟값에 의해 제한됐음을 나타내요. 이는 HorizontalPodAutoscaler의 최소·최대 레플리카 제약을 올리거나 내릴 필요가 있을 수 있다는 신호예요.

수량(Quantity)

HorizontalPodAutoscaler와 메트릭 API의 모든 메트릭은 쿠버네티스에서 quantity라고 부르는 특별한 정수 표기법으로 지정돼요. 예를 들어 10500m이라는 수량은 십진 표기법으로 10.5로 써요. 메트릭 API는 가능하면 접미사 없는 정수를 반환하고, 그 외에는 일반적으로 milli 단위의 수량을 반환해요. 즉 메트릭 값이 십진 표기법으로 쓸 때 11500m 또는 11.5 사이에서 오르내리는 걸 볼 수 있어요.

다른 가능한 시나리오

autoscaler를 선언적으로 만들기

kubectl autoscale 명령으로 HorizontalPodAutoscaler를 절차적으로 만들지 않고, 다음 매니페스트를 사용해 선언적으로 만들 수도 있어요.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50

그리고 나서 다음 명령을 실행해 autoscaler를 만들어요.

kubectl create -f https://k8s.io/examples/application/hpa/php-apache.yaml
horizontalpodautoscaler.autoscaling/php-apache created

더 알아보기