Kubernetes 의 Autoscaling
Tags: kubernetes
Published: 2022-03-15 08:10:33

## 들어가며
Kubernetes를 사용하는 큰 이유중 하나가 autoscaling 때문이다. 하지만 autoscaling을 제대로 사용하기 위해서는 resource request와 limit 설정 및 Horizontal Pod Autoscaler (HPA), Cluster Autoscaler (CA) 의 설정이 반드시 필요하다. 
최근 서비스 오픈을 위해 performance testing을 하며 Kubernetes의 Autoscaling을 사용하면서 알게 된 내용을 정리하였다.  

## 1. Kubernetes 의 Autoscaling 
Kubernetes 에서 autoscaling 을 할 수 있는 방법은 세가지가 있다. VPA (Vertical Pod Autoscaler), HPA, (Horizontal Pod Autoscaler), CA (Cluster Autoscaler)를 사용하여 변화하는 request 에 따라 autoscale 이 가능하다. 보통 운영에서는 VPA를 이용하여 적절한 resource request / limit 값을 찾는데 활용하고, 실제로 사용하는 방법은 HPA 와 CA이다. (VPA 관련 내용은 [Kubernetes 의 Resources 설정을 위한 VPA 및 Goldilocks](https://dev.to/airoasis/kubernetes-yi-resources-seoljeongeul-wihan-vpa-mic-goldilocks-3503) 참고).

여기서 HPA의 역할은 CPU나 Memory 의 사용량에 따라 Pod를 scale-out 하는 용도로 사용하고, HPA로 인해 새로운 Pod들이 생겨나서 더이상 available 한 node 로는 scheduling을 못하게 되는 상황에 Node를 scale-out 하기 위해 CA가 사용된다. 

## 2. Kubernetes Resouce Request 와 Limit 설정
사실 request, limit 설정의 정답은 없다. 하지만 cpu 와 memory 의 특성에 의해 **cpu 는 request만 설정하고, memory는 request와 limit을 동일하게 설정하는것을 추천한다**. 

이렇게 설정하는 이유는 다음과 같다. cpu는 compressible resource라 여러 pod가 cpu를 서로 사용하려고 할 때에 서비스는 단지 throttling 되어 처리시간이 좀 더 걸리지만 memory는 incompressible resource라 memory 가 부족하면 OOM(out of memory)으로 Pod가 종료되고 새롭게 scheduling 되어야 한다. 따라서 memory 의 request를 낮게 설정하여 여유롭지 않은 node 에 배치되었는데 Java와 같이 올라갈때 memory 가 많이 필요한 서비스의 경우 해당 node 에서 memory가 부족하여 올라가지도 못하고 바로 종료되는 상황이 발생할 수 있다. 

그리고 중요한 점은 운영환경의 **Kubernetes에 배포되는 모든 서비스의 request / limit 설정을 해주어야 한다**는 것이다. (개발중에는 설정하지 않아도 문제가 되지는 않는다) 이를 설정하지 않으면 Pod는 Node의 자원 전체를 사용할 수 있다는 의미와 동일하다. 결국 kubernetes는 정확한 scheduling 을 할 수가 없고, 특정 node 에 많은 pod가 배치될 수 있고, resource 부족이 생기는 경우 설정이 없는 pod들을 eviction 대상으로 보고 많은 pod들이 재배치되는 문제가 생기게 된다. 

결국 autoscaling 은 request / limit 설정 없이는 무용지물이다. 

## 3. HPA 설정
HPA 설정에서 중요한건 다음 두가지 이다
- **🛑 HPA를 사용하면 deployment 의 replicas 설정은 반드시 반드시 제거해야 한다.**
- **🛑 HPA 가 scale-out 하는 기준은 resource limit 이 아닌 request 이다**

### Replicas 설정 제거
관련 내용은 [Migrating Deployments and StatefulSets to horizontal autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#migrating-deployments-and-statefulsets-to-horizontal-autoscaling) 에 자세히 설명되어 있다. 하지만 페이지의 거의 끝부분에 있어서 놓치고 지나치기 쉽다...

HPA 도입 이전에는 deployment 의 `replicas` 설정으로 pod의 갯수를 설정한다. 하지만 HPA를 도입하면 반드시 **deployment 의 `replicas` 설정을 제거하고 HPA의 `mixReplicas`, `maxReplicas` 설정으로 대신하여야 한다**. 

만약 두군데 모두 설정이 있으면 kubernetes 는 예를 들어 배포와 같이 `kubectl apply -f deployment.yaml` command 를 사용하게 되는 경우 무조건 deployment 의 replicas 설정으로 회귀하도록 한다. 

예를 들어 deployment의 replicas 설정이 2이고 HPA로 인해 pod가 10개까지 늘어난 상황에서 서비스가 되고 있는데 이때 배포가 일어나면 갑자기 pod는 2개로 줄어들었다가 다시 HPA로 인해 필요한 pod들이 생기는 상황이 발생한다. 

또한 Argo CD를 사용하는 경우 pod가 2개가 아닌 경우 계속 `OutOfSync`를 보게 될 것이다... 

### HPA 의 scale-out 기준
많은 경우 HPA의 scale-out 기준이 resouce limit으로 착각하는 경우가 있다. 하지만 HPA의 동작은 **resource request**기준이다. 
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: main-server
  labels:
    app: main-server
spec:
  selector:
    matchLabels:
      app: main-server
  template:
    metadata:
      labels:
        app: main-server
    spec:
      containers:
      - name: main-server
        image: main-server:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 1
            memory: 1.5Gi
          limits:
            memory: 1.5Gi
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: main-server-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: main-server
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 90
```
예를 들어 위와 같은 설정에서 HPA는 CPU 가 900밀리코어일때 scale-out을 시작한다. 참고로 여기서 `averageUtilization` 값은 100을 넘어선 값도 설정이 가능하다. 

## 4. Pod Disruption Budget (PDB)
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: main-server-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: main-server
```
PDB는 운영에서 반드시 필요한 설정이다. Pod는 항상 설정된 replica의 수 만큼 유지되지만 시스템 관리로 인해 특정 node를 다운 시켜야 하는 경우, 또는 cluster autoscaler 가 node의 수를 줄이는 경우 등과 같은 이유로 pod의 수가 줄어들어야 하는 경우가 있다.

이런 경우 PDB를 통해 최소한 운영 가능한 pod의 비율/개수를 정하거나 최대 서비스 가능하지 않은 pod의 비율/개수를 정하여 서비스의 안정성을 보장한다. 여기에서는 최소 1개의 pod가 항상 보장되게 설정하였다. 결국 node가 scale down 되어야 하는 상황에서 최소 보장되어야 하는 PDB 설정을 만족하지 못하는 해당 node는 다운되지 않고 기다리다가 만족하는 상황이 되면 그때 다운되어진다.

## 마치며
운영환경에서 제대로 autoscaling을 사용하기는 쉽지않다... 각 서비스에 따라 resource request/limit 설정을 달리 하여야 하고, HPA가 동작할때 새로 생기는 pod 가 ready 될 때까지 견딜 수 있도록 적절한 target 을 정하는 것, 그리고 새로운 pod 가 생성되고 termination 될 때 request의 유실이 없도록 application 및 kuernetes 의 설정이 필요하다. 

한번에 이 모든걸 완벽하게 해결하기 보다는 운영을 하면서 문제점을 하나씩 해결해 나가는 방법이 적절한거 같다. 

Java 서비스의 경우 [Kubernetes 를 위한 Spring Boot 개발 (feat. 무중단 배포/운영)](https://dev.to/airoasis/kubernetes-reul-wihan-spring-boot-gaebal-feat-mujungdan-baepounyeong-2k0o) 와 [Spring Boot 서비스를 위한 Kubernetes 설정](https://dev.to/airoasis/spring-boot-seobiseureul-wihan-kubernetes-seoljeong-3d72) 를 참고하면 좋을거 같다.