HPA
개요
워크로드들을 자동으로 스케일링해주는 오브젝트.[1]
지정된 조건에 따라 레플리카의 개수를 늘리거나 줄이는 동작을 알아서 해준다.
물론 데몬셋 같이 애초에 스케일링 안 되는 요소에는 스케일링을 하지 않는다.
이름에서 알 수 있듯이, 복제본을 늘리거나 줄이는 작업을 한다.
한 어플리케이션 자체의 리소스를 확장해주는 Vertical Pod Autoscaler와는 다르다.
작동 원리
오토스케일러 역시 컨트롤러를 통해 동작한다.
이 컨트롤러는 주기적으로 대상이 된 오브젝트의 상태를 감시한다.
(kube-controller-manager에 --horizontal-pod-autoscaler-sync-period
인자를 수정하여 기간(기본 15초)을 조정할 수 있다.)
컨트롤러는 스케일링할 scaleTargetRef
필드로 건드릴 워크로드(디플로이먼트, 스테이트풀셋)를 추적한다.
그리고 해당 워크로드의 라벨 셀렉터를 통해 파드들을 찾아낸 후, 지표를 수집한다.
얻어낸 후에는? 지표 계산을 통해 필요한 레플리카 수를 구한 뒤에 해당 워크로드에 스케일링 명령을 내린다!
참고로 스케일 명령 자체는 해당 오브젝트가 /scale
이라는 api에 요청을 보내는 것을 말한다.
그래서 어떤 커스텀 오브젝트라도 /scale
에 대한 서브리소스만 같이 만들어둔다면 똑같이 HPA의 대상으로 삼을 수 있다.
스케일링 알고리즘
그렇다면 얼마나 스케일링 해야 하는지는 어떻게 정하는가?
방식은 간단하다.
일단 현재 메트릭과 희망하는 메트릭을 보고 얼마나의 비율로 값이 다른지를 구한다(분수).
해당 비율은 희망하는 레플리카 개수와 현제 레플리카 개수의 비율과 같으므로 현재 레플리카 개수를 곱하면 늘어나거나, 줄어들어서 맞춰져야 할 레플리카 개수가 나오게 되는 것이다.
물론 레플리카는 정수이므로 올림 처리된다
그런데 여기에서 하나의 의문이 더 발생한다.
그렇다면 현재 메트릭 값이라는 것은 무엇인가?
가령 cpu 사용률이 메트릭이라고 쳐보자.
그렇다면 각 파드의 cpu 사용량을 일단 가져와서, 이를 합하고 현재의 개수로 나눈다(평균 구하기).
그럼 그게 현재 메트릭 값이다!
여기까지만 들으면 알고리즘은 자체는 굉장히 쉽다는 것을 알 수 있다.
그러나 실제 작업이 들어갈 때는 몇가지 추가 조건이 붙는다.
무시되는 파드
일단 fail 상태인 파드와 삭제되는 중인 파드는 계산에서 완전히 제외된다.
특수 고려되는 파드
메트릭이 수집되지 않고 있는 파드, 준비 상태가 아닌 파드에 대해서는 조금 특별하게 대응한다.
일단 이들을 포함하여 계산하기에 앞서 상태가 확인되는 파드들만 이용해서 계산을 진행하고, 그 다음 이 친구들을 반영한다.
아래에서 보겠지만 이는 일단 현재 레플리카를 늘려야 하는지, 줄여야 하는지를 선판단하기 위해서이다.
일단 파드의 준비 상태에 대해 not yet ready
상태를 매길 때가 있다.
이것은 시작되지 얼마 되지 않은 파드가 아직 제대로 요청들을 수행해낼 수 없어 다른 파드들이 부하를 받고 있는 상황일 때 과도하게 파드가 늘어나게 스케일링을 하는 것을 막기 위함이다.
파드가 시작된 이후, 준비 상태가 된지 얼마 안 됐다면 해당 파드를 not yet ready
로 간주한다(--horizontal-pod-autuscaler-initial-readiness-delay
인자 기본 30초).
다만 파드가 준비가 아니었다가 준비상태가 된 지 얼마 안 됐더라도 파드 자체가 시작된 지는 오래 됐다면(--horizontal-pod-autoscaler-cpu-initialization-period
인자 기본 5분), 이때는 바로 ready 상태로 반영한다.
그래서 이 not yet ready
파드를 어떻게 활용하는가?
이들에 대해서는 메트릭이 0퍼센트라고 가정하고 계산한다.
이를 통해 이 파드들이 현재 다른 파드들의 부하를 견딜 수 있다고 상정하며 불필요한 스케일링을 막는 것이다.
(이런 가정이 오히려 필요한 스케일링을 늦출 수도 있지 않을까?)
메트릭이 수집되고 있지 않는 파드에 대해서는 스케일링을 덜하게 만드는 방향으로 전제를 하고 계산한다.
즉 스케일 다운을 해야 하는 상황이라면 해당 파드가 100퍼센트의 자원을 쓰고 있다고 가정한다.
반대로 스케일 아웃을 해야한다면 해당 파드가 0퍼를 쓰고 있다고 가정한다.
이를 통해 스케일링이 최대한 덜 일어나도록 만들어버린다.
아무튼 이런 식으로 추가 계산을 해서 다시 필요한 레플리카 개수를 산출한다.
이로 인해 원래 늘어났어야 할 파드가 늘어나지 않는다던가 하는 상황이 충분히 나올 수 있다.
HPA는 꽤나 스케일링에 있어 보수적인 전략을 펼친다는 것을 알 수 있는데, 스케일링을 통해 늘어난 파드의 정보가 초기에 제대로 반영되지 않는 것을 상당히 신경 쓰기 위함인 것으로 해석된다.
그렇기에 HPA를 설정할 때는 가급적이면 넉넉하게 조건을 설정해주는 것이 안정적인 서비스를 구축하는데 도움을 줄 것이다.
여러 메트릭 조건이 걸렸다면
cpu도 기준이고 메모리도 기준 값으로, 복수 조건이 설정이 돼있을 수 있다.
이때는 각 값을 계산한 이후, 레플리카의 수를 가장 높게 설정하는 값을 기준으로 한다.
이미 스케일링이 필요한 상황이라면, 최대한 적극적으로 스케일링에 임한다는 것을 알 수 있다.
이중 어떤 값이 측정되지 않고 있다면 스케일 아웃을 하는 과정에서는 제외하고 계산에 임한다.
그러나 스케일 다운을 하는 경우에 측정되지 않는 값이 있다면 스케일 다운은 진행되지 않는다.
정상 서비스 운영에 대한 영향을 최소화하는 방향으로 스케일링을 진행하는 것이다.
이전 계산 고려 - 안정화 윈도우
마지막으로, 이전 계산 값도 스케일링에 고려한다!
합산을 한다던가 하는 것은 아니고, 스케일링이 들쑥날쑥 한 기준 시간마다 변동되는 것에 제약을 걸기 위함이다.
윈도우 기간(--horizontal-pod-autoscaler-downscale-stabilization
인자로 기본 5분)을 두고 이 기간 내의 시간 동안 가장 큰 값으로만 스케일링하도록 돼있다.
즉, 한번 워크로드의 레플리카가 크게 늘어난 후면 대충 5분간은 다시 떨어지지 않는다는 말이다.
이는 스케일 다운이 최대한 점진적으로, 급격하게 변동하는 메트릭값으로부터 안전하도록 하기 위한 조치다.
이러한 방식을 thrashing, flapping이라고 부른다.
사용 시 유의사항
kubectl 조작
kubectl 에서 autocale을 통해 간단하게 조작할 수 있다.
아주 간단하게, cpu가 평균 80퍼가 넘어갈 때 오토스케일링 되도록 만들었다.
메트릭 수집
어디에서 메트릭을 수집해서 스케일링을 진행하는가?
기본적으로 3가지의 api로 접근 가능한 메트릭이면 된다.
metrics.k8s.io
- 이건 메트릭 서버에서 노출해주는 값으로, 간단하게 세팅이 가능하다.
custom.metrics.k8s.io
- 위의 메트릭은 cpu, 메모리 사용량만 노출하므로추가적인 메트릭을 기준 삼고 싶을 때 커스텀 메트릭을 등록해서 사용한다.
external.metrics.k8s.io
- 여기에 클러스터 외부의 메트릭을 이용하고 싶을 때는 이 api에 메트릭을 등록해서 사용하면 된다.
이 api들은 kube-apiserver에 API Aggregation Layer으로 커스텀 api를 만들어 메트릭을 노출시키는 방식이다.
그리고 이 메트릭들을 사용할 때는 [[#metrics]]에서 pods, object, external
타입을 이용하면 된다.
k get --raw /apis/metrics.k8s.io
해당 메트릭들이 잘 노출되고 있는지 확인할 때는 이렇게 직접 api 경로를 명시해서 요청을 날려보면 된다.
k top pod
를 쓸 때, -v
옵션을 써서 세부 과정을 디버깅해보면 먼저 aggregation layer에서 그룹 디스커버리를 호출하고, 관련 api를 찾아서 요청이 진행되는 것을 확인할 수 있다.
위에처럼 그냥 --raw
를 사용하면 이 중간과정없이 바로 해당 메트릭 api로 요청을 쏘는 것을 볼 수 있다.
커스텀 메트릭을 이용하는 방법 중 유용한 게 또 프로메테우스다.
자세한 건 Prom-Adaptor를 참조하다.
스케일링 중단시키기
스케일링을 중단하고 싶다면 그냥 HPA 오브젝트를 없애면 된다.
근데 HPA 오브제트의 대상이 되는 워크로드에서 replica를 0으로 세팅해도 HPA의 조정이 중단된다.
측정 알고리즘 상 곱셈밖에 없기 때문에, 0이 되는 순간 그냥 계산이 안 돼버리는 것이다.
파일 관리 관련
워크로드 양식파일을 따로 관리하고 있다면, 해당 파일에서 replicas
필드를 없애는 것을 추천한다.
내 파일을 적용할 때마다 HPA가 고생할 수도 있다..
근데 심지어 윈도우 안정화 이슈로 변동이 빠르게 적용되지도 않을 것이다.
기존에 있던 놈에 갑자기 replicas 필드를 없애면 레플리카가 하나로 줄어들게 될 텐데, 이건 기본값이 잠시 세팅되는 거라 걱정하지 말자.
(그게 걱정되면 처음부터 HPA한테 스케일링을 맡겨라)
k apply -f test.yaml --server-side --field-manager={hpa이름} --validate=false
이런 식으로 서버 사이드로 관리권을 넘기는 방법도 있다는데, 이건 시도 안 해봤다.
양식 작성법
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hpa-deployment
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
기본적인 양식 작성법으로, 거의 위 명령어와 동일하다.[2]
먼저 스케일할 타겟을 지정하고, 최대와 최소값을 지정한다.
그 스케일의 기준과 각 행동을 지정하면 된다.
(참고로 아래 나올 다양한 방식들을 활용하기 위해서는 꼭 apiVersion을 v2로 쓰자)
scaleTargetRef
스케일링의 대상을 지정하는 필드이다.
보통은 디플로이먼트, 스테이트풀셋 정도가 대상이 되는데, 위에서도 언급했듯이 /scale
api가 구현된 어떤 오브젝트든 대상이 될 수 있다.
대표적으로는 Argo CD 롤아웃 정도가 있는 것 같다.
단 하나 유의할 만한 건 같은 네임스페이스의 오브젝트를 대상으로 하라는 것 정도.
metrics
이제 어떤 메트릭을 수집하고, 그 값의 기준을 어디에 둘 것인지를 보자.
이때 주의할 게 하나 있다.
메트릭에는 기본적으로 두 가지 타입의 값이 있다.
여기에 추가적으로 세 가지 유형의 커스텀 메트릭을 사용할 수 있다.
pods
- type: Pods
pods:
metric:
name: packets-per-second
target:
type: AverageValue
averageValue: 1k
파드의 다른 메트릭을 이용하고 싶다면 이렇게 Pods라고 써준다.[3]
유의할 점은 일단 해당 메트릭을 노출하는 다른 추가 세팅을 먼저 해줘야 한다는 것.
그래서 해당 세팅을 통해 metric.name
필드에 들어간 이름의 메트릭이 실제로 있어야만 한다.
그리고 여기에는 AverageValue만 들어갈 수 있다는 것.
사용률은 리소스 요청량을 분모로 하는 거니까, 어쩌면 이건 당연한데, 어쩌면 Dynamic Resource Allocation이 GA가 된다면 상황이 달라질 수도 있겠다.
object
- type: Object
object:
metric:
name: requests-per-second
describedObject:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: main-route
target:
type: Value
value: 10k
임의의 오브젝트의 지표를 이용해 스케일링을 지정하고 싶다면 이걸 활용해야 한다.
파드가 아닌 다른 오브젝트를 사용하는 만큼 사용할 오브젝트를 describedObject
로 명시한다.
autoscaling/v2
api 에서 사용가능한 방식이란 것을 참고하자.
관련 오브젝트에 대한 값이 metrics api로 노출만 되고 있고, metric
필드에 명시된 이름이 거기에 있다면 그것을 스케일의 기준으로 삼을 수 있게 된다.
이 친구는 Value, AverageValue가 가능하다.
type: Object
object:
metric:
name: http_requests
selector: {matchLabels: {verb: GET}}
이런 식으로, 오브젝트로 추적할 수 없는 내용의 지표를 담을 때 라벨 셀렉터를 사용할 수도 있다.
이때의 라벨은 프로메테우스 데이터 형식이나 각종 관측 가능성 툴에서 정의하는 메트릭의 라벨을 말하는 건데, 이것들을 쿠버네티스 라벨 셀렉터 형식으로 작성하면 된다.
external
- type: External
external:
metric:
name: queue_messages_ready
selector:
matchLabels:
queue: "worker_tasks"
target:
type: AverageValue
averageValue: 30
클러스터 내부의 오브젝트와 관련 없는 메트릭을 이용하는 방법도 존재한다.
위에까진 커스텀 메트릭이라 부르는데, 이놈은 외부 메트릭이라 부른다.
이 친구도 Value, AverageValue가 가능하다.
세팅하는 것이 복잡할 수 있고, 메트릭 세팅을 하는 것이 까다롭기에 사용하지 않는 것이 권장된다.
behavior
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
스케일링 시 어떻게 행동할지도 정할 수 있다(세팅하지 않을 시 적용되는 값은 위의 예시).
크게는 스케일 다운과 스케일 업을 할 때의 행동을 각각 지정할 수 있다.
여기에 [[#이전 계산 고려 - 안정화 윈도우]]에서 봤던 값을 커스텀하는 것도 가능하다.
여기에서 구체적으로 지정하는 게 무어냐, 어차피 위의 계산에 따라 스케일링될 값은 정해져있는데?
여기에서 지정하는 것은 스케일링될 때 변동성을 완화하는 설정이다.
policies:
- type: Percent
value: 100
periodSeconds: 15
이 건 15초 기준으로 스케일링될 때 100퍼센트까지만 허용한다는 것이다.
그 이상 스케일링돼야한다고 해도 15초이내로는 최대 100퍼센트까지만 가능한 것이다.
파드를 지정해서 아예 구체적인 숫자를 지정하는 것도 가능하다.
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: Max
복수의 정책이 들어간다면 가장 크게 변동을 허용하는 정책이 채택된다.
정책이 이렇게 세팅됐고 레플리카가 하나라 쳐보자.
근데 15개가 늘어나야 하는 상황이다.
그렇다면 일단 처음에는 아래 정책이 적용돼 4개가 늘어난다.
그러나 그 다음에는 위 기준이 적용될 때 5개가 늘어날 수 있으므로 위 정책이 적용된다.
(1 -> 1 + 4 -> 1+ 4 + 5)
최대 변동 정책으로 적용되는 게 싫다면 selectPolicy: Min
으로 하자.
selectPolicy: Disabled
라 하면 해당 방향의 정책들은 아예 적용되지 않는다.
그래서 스케일다운을 하기는 싫다면 이걸 설정해버리면 된다.
관련 문서
이름 | noteType | created |
---|---|---|
KEDA | knowledge | 2024-12-29 |
Prometheus-Adapter | knowledge | 2025-03-04 |
5W - HPA, KEDA를 활용한 파드 오토스케일링 | published | 2025-03-07 |