티스토리 뷰

쿠버네티스는 컨테이너 오케스트레이션 도구입니다. 컨테이너는 쿠버네티스에서 파드(pod)라는 개념으로 감싸집니다. 하나의 파드는 한 개 이상의 컨테이너를 가질 수 있습니다. 즉, 애플리케이션 컨테이너 한 개와 그 옆에 사이드카 패턴으로 다른 컨테이너를 붙여서 하나의 파드로 운영하는 것이 가능합니다.

https://www.oreilly.com/library/view/designing-distributed-systems/9781491983638/ch02.html

 

각설하고, 이번 글에서는 파드를 수평 확장(Scale-out)하는 방법인 HPA(Horizontal Pod Autoscaling)노드를 수평 확장하는 카펜터(Karpenter)를 엮어서 개념을 이야기해 보도록 하겠습니다. 언제나와 마찬가지로 글에 잘못된 부분이 있거나 토론이 필요한 부분이 있다면 댓글로 의견 부탁드립니다 :-)


 

# HPA(Horizontal Pod Autoscaling)

Deployment로 파드의 replica를 조정해서 사용하는 것이 아마도 쿠버네티스 사용의 첫 단계일 것이다. 여기까지 설정이 됐다면 바로 다음 단계인 HPA(Horizontal Pod Autoscaling)를 적용하게 된다. HPA는 아래 이미지와 같이 파드의 개수를 지정된 범위 내에서 자동으로 확장/축소해 준다. 

https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#how-does-a-horizontalpodautoscaler-work

 

테라폼 코드로 HPA 설정을 보면 아래와 같다. 

# https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/horizontal_pod_autoscaler
resource "kubernetes_horizontal_pod_autoscaler" "example" {
  metadata {
    name = "terraform-example"
  }

  spec {
    max_replicas = 10
    min_replicas = 8

    scale_target_ref {
      kind = "Deployment"
      name = "MyApp"
    }
  }
}

 

주목할 것은 min_replicasmax_replicas 부분이다. 이는 오토스케일러가 설정 가능한 하한 값과 상한 값이다(최솟값/최댓값). 그리고 설정의 아래쪽으로 내려가보면 metric을 설정할 수 있는데 그 부분은 아래 코드를 살펴보자. 위에 spec과 같은 레벨(indent)로 작성된 코드이다.

metric {
  type = "Resource"
  resource {
    name = "cpu"
    target {
      type                = "Utilization"
      average_utilization = 60
    }
  }
}

 

코드 내용을 해석해 보면 CPU 평균 사용량을 60%를 맞추기 위해 오토스케일러가 동작한다는 설정이다. Resource 대신 Object, Pods, External 설정도 가능하고, average_utilization 대신 average_value 설정도 가능하다. 

average_utilization의 경우 평균 사용량을 계산해서 백분율로 조건을 거는 설정이고,  average_value는 고정된 수치로 지정하는 설정이다. 예를 들어 파드가 2개 사용되고 있는데 average_utilization가 위와 같이 60으로 설정되어 있다면, 두 개 파드의 CPU 사용량 평균이 60% 인지 검사할 것이다. 그리고 그 이상인 경우 오토스케일러는 파드를 한 개 더 띄워서 CPU 평균 사용량을 낮추게 될 것이다. 첫 번째 파드의 사용량이 80이고 두 번째 파드의 사용량이 30이라면 평균은 55 이니까 파드가 확장되지 않는다. 만약 첫 번째 파드가 100이고 두 번째 파드는 30이라면? 평균은 65이므로, 파드를 3개로 확장해서 평균 사용량을 43으로 낮출 거다. 한편 average_value는 고정된 값을 사용하기 때문에 CPU 유닛을 지정할 수 있다. 250m, 500m, 1, 2처럼 말이다. 이 설정은 CPU 메트릭 외에 메모리 메트릭에서도 동일하게 사용 가능하다. 

HPA 리소스가 정상적으로 배포되었다면 kubectl 명령어를 통해 아래처럼 확인 가능하다.

$ kubectl get hpa
NAME       REFERENCE             TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
test-app   Deployment/test-app   16%/60%    8         10        8          6h2m

 

위에 16%에 대한 근거를 확인하려면 클러스터에 아래와 같이 메트릭 서버를 설치해야 한다.

# https://github.com/kubernetes-sigs/metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

 

메트릭 서버가 설치 됐다면 아래 명령어로 pod의 사용량을 볼 수 있다. pod 대신 node를 입력해서 노드의 사용량을 확인하는 것도 가능하다. ( 아래 데이터는 임의로 만들어진 데이터이므로 평균이 16%가 아님을 참고 )

$ kubectl top pod
NAME                        CPU(cores)   MEMORY(bytes)
test-app-55dcb7bb4b-9vslh   3m           92Mi
test-app-55dcb7bb4b-9w65h   1050m        439Mi
test-app-55dcb7bb4b-fslhs   3m           138Mi
test-app-55dcb7bb4b-hvsml   3m           222Mi
test-app-55dcb7bb4b-kppcr   4m           115Mi
test-app-55dcb7bb4b-ljhd4   516m         90Mi
test-app-55dcb7bb4b-vkmsf   2m           267Mi
test-app-55dcb7bb4b-vkmsf   2m           267Mi

 

자, 여기 예제에서는 최소/최대 파드 개수가 8 ~ 10으로 차이가 크지 않았지만 만약 8 ~ 800처럼 간격이 크다면 어떻게 될까? 오토스케일러는 파드를 새로 띄울 노드를 선택해야 하는데 쿠버네티스 클러스터에 충분히 많은 노드가 붙어있지 않은 경우라면 파드 개수를 무한정 늘릴 수는 없을 거다. 이때 필요한 게 노드의 수평 확장인데 카펜터(Karpenter)가 등장한다.

 

# 카펜터(Karpenter)

만약 CSP(Cloud Service Provider: AWS, GCP, Azure 같은)에서 제공하는 매니지드 쿠버네티스를 사용한다면 노드를 수평 확장하는 방법이 따로 존재하겠지만 이 글에서는 다루지는 않는다. 예로 AWS의 경우 Cluster Autoscaler가 있지만 일반적으로 카펜터(Karpenter)가 더 빠르게 노드를 수평 확장한다. kube-scheduler를 거치지 않고 노드를 띄우기 때문에 Just-in-time에 노드를 확장 가능하다고 이야기한다. 그래도 (약) 1분은 걸린다. 한편 다른 CSP를 이야기하기에는 글의 주제를 벗어난다. 

https://karpenter.sh/

 

카펜터 콘셉트는 크게 노드 템플릿(Node Template)프로비저너(Provisioner)로 구분된다. 노드 템플릿은 인스턴스에 붙이는 설정으로 이해하면 쉽다. 예를 들어 서브넷이나 보안 그룹, 인스턴스 프로필 등을 설정할 수 있다. 좀 더 자세한 내용은 공식 문서를 참고하기 바란다.

apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: default
spec:
  subnetSelector: { ... }        # required, discovers tagged subnets to attach to instances
  securityGroupSelector: { ... } # required, discovers tagged security groups to attach to instances
  instanceProfile: "..."         # optional, overrides the node's identity from global settings
  amiFamily: "..."               # optional, resolves a default ami and userdata
  amiSelector: { ... }           # optional, discovers tagged amis to override the amiFamily's default
  userData: "..."                # optional, overrides autogenerated userdata with a merge semantic
  tags: { ... }                  # optional, propagates tags to underlying EC2 resources
  metadataOptions: { ... }       # optional, configures IMDS for the instance
  blockDeviceMappings: [ ... ]   # optional, configures storage devices for the instance
  detailedMonitoring: "..."      # optional, configures detailed monitoring for the instance
status:
  subnets: { ... }               # resolved subnets
  securityGroups: { ... }        # resolved security groups
  amis: { ... }                  # resolved AMIs

 

한편 프로비저너는 Karpenter가 생성하는 노드에서 실행할 수 있는 파드를 제한하거나 노드가 생성될 때 특정 인스턴스 타입, capacity 타입, 컴퓨터 아키텍처(CPU 타입)를 제한할 수 있게 해 준다. 또한 노드에 만료 조건을 줘서 빈 노드를 제거하거나 일정 시간마다 오래된 노드를 버리고 새로운 노드로 갈아치울 수 있다. 노드를 굳이 왜 새것으로 갈아야 하느냐고 하면, 이렇게 하면 인스턴스에 보안패치를 최신으로 유지하는데 도움이 되며 쿠버네티스 버전 관리로부터 데브옵스 엔지니어 혹은 사이트 신뢰성 엔지니어(SRE)가 좀 더 자유로워지기 때문이다. 프로비저너에 대한 더 자세한 설명은 공식 문서를 참고하도록 하자.

카펜터는 클러스터에 노드 중에 더 이상 파드를 배치할 수 있는 곳이 없다면 새로운 노드를 띄우게 된다. 이때 노드를 선택하는 최초 기준은 Deployment에 requests 설정이다.

  spec {
    container {
      image = "nginx:1.21.6"
      name  = "example"

      resources {
        limits = {
          cpu    = "0.5"
          memory = "512Mi"
        }
        requests = {
          cpu    = "250m"
          memory = "50Mi"
        }
      }
    }
  }

requests 설정은 너무 작아도 문제고 너무 커도 문제다. 너무 큰 경우 카펜터가 적합한 노드를 찾기 어려울 것이고 너무 작은 경우 앱을 배포하자마자 OOM(Out of Memory)을 맞거나 다른 애플리케이션과 리소스를 차지하기 위해 치열한 경합을 하게 될지도 모른다. 그렇기에 requests는 애플리케이션 용도에 맞게 적당한 값을 선택하는 것이 중요하다. ( OOM 조건은 limit 설정이지만 여기 예시는 request와 limit에 간극이 적다고 가정했다 )

이야기가 좀 돌아왔지만 결국 HPA와 카펜터는 쌍으로 움직여야 한다. HPA에 설정한 max_replicas 설정이 정상적으로 동작할 수 있도록 하기 위해서는 노드 확장이 필수일 테니까 말이다.

 

# 마무리

이번 글에서는 HPA와 카펜터의 관계에 대해 알아보았다. HPA와 카펜터는 우리의 애플리케이션이 빠르게 확장 가능하도록 해주는 도구이다. 많은 분들이 아시는 것처럼, HPA와 카펜터는 어쩌면 (이제는) 너무 쉬운 개념일지도 모른다. 그럼에도 이 글을 정리하는 이유는, 이해하기 쉬운 언어로 HPA와 카펜터에 대해 설명함으로써, 아마도 누군가에게 도움이 될 수 있기 때문이다. 카펜터 하나만 갖고도 한 시간 발표는 훌쩍 가능할 정도로 복잡한 개념들이 많이 내포되어 있는데 시간 되는 대로 차근차근 정리해 보도록 하겠다. 누군가에게는 도움이 되겠지 :-) 

 

댓글
최근에 올라온 글
최근에 달린 댓글
글 보관함
Total
Today
Yesterday