이번 포스트에서는 EKS의 오토스케일링(Autoscaling) 옵션을 살펴보겠습니다.
기본적인 쿠버네티스 환경의 스케일링 옵션을 전반적으로 살펴보겠습니다. 이후 EKS의 오토스케일링 옵션을 살펴보고, 이를 실습을 통해 확인 해보도록 하겠습니다. 마지막으로 AKS의 오토스케일링 옵션을 EKS와 비교해 보겠습니다.
글을 작성하는 과정에서 분량이 너무 길어져, Part1에서는 HPA, KEDA, VPA까지의 내용을 다루고 Part2에서 Cluster Autoscaler 부터 이어서 설명하도록 하겠습니다.
목차
EKS의 오토스케일링 Part1
- 쿠버네티스 환경의 스케일링
- EKS의 오토스케일링 개요
- 실습 환경 생성
- HPA(Horizontal Pod Autoscaler)
- KEDA(Kubernetes Event-driven Autoscaler)
- VPA(Vertical Pod Autoscaler)
EKS의 오토스케일링 Part2 (https://a-person.tistory.com/39)
- CA(Cluster Autoscaler)
- Karpenter
- AKS의 오토스케일링
- 오토스케일링에 대한 주의사항
1. 쿠버네티스 환경의 스케일링
쿠버네티스 패턴(책만, 2020)에서는 쿠버네티스 환경의 애플리케이션의 스케일링 레벨을 아래와 같이 설명하고 있습니다.
출처: 빌긴 이브리암/롤란트 후스, 안승규/서한배, 책만, 2020, 272.
먼저 애플리케이션 튜닝이 필요합니다. 쿠버네티스 환경이라고 할지라도 애플리케이션 자체가 최대한의 성능을 사용하도록 동작해야 합니다. 애플리케이션에 할당된 리소스를 증가시키거나 복제본을 증가시키는 것은 부차적인 일입니다.
두번째는 수직 파드 오토스케일러(VPA, Vertical Pod Autoscaler)입니다. 파드의 리소스가 부족할 때 설정된 리소스(CPU/MEM) 자체를 증가시키는 방식입니다. 쿠버네티스 환경에서는 request와 limit을 지정할 수 있고, 이 값이 변경됩니다.
세번째는 수평 파드 오토스케일러(HPA, Horizontal Pod Autoscaler)입니다. 파드의 리소스가 임계치 이상 사용되면, 동일한 파드의 복제본을 증가시키는 방식입니다.
네번째는 VPA나 HPA로 애플리케이션이 용량이나 갯수가 증가했을 때, 노드의 할당 가능한 자원(Allocatable resource)을 모두 소진하면 파드가 스케줄링 불가능(Unschedurable Pod)한 상황이 발생할 수 있습니다. 이때는 노드 자체를 증가시켜야 합니다. 이것을 클러스터 오토스케일러(CA, Cluster Autoscaler)에서 지원합니다.
쿠버네티스 환경은 애플리케이션의 스케일링을 하기 위해서 위와 같은 기법을 사용할 수 있으며, 이후 EKS에서는 이들을 어떤식으로 활용할 수 있는지 살펴보겠습니다.
일반적으로 애플리케이션 튜닝은 쿠버네티스에 국한되지 않은 별개의 영역이므로 설명에 제외하도록 하겠습니다.
2. EKS의 오토스케일링 개요
EKS에서는 설명한 HPA, VPA, CA를 모두 지원하고 있으며, Karpenter이라는 노드 오토스케일링을 방식을 추가로 제공하고 있습니다. 또한 그림에는 없지만 KEDA를 통해서 이벤트를 통해 HPA를 확장할 수 있는 방식도 있습니다.
출처: https://www.youtube.com/watch?v=jLuVZX6WQsw
HPA는 쿠버네티스 환경에서 기본으로 제공되기 때문에 별도의 설치 과정 없이 hpa 오브젝트를 생성하여 사용할 수 있습니다.
EKS에서는 KEDA, VPA, CA, Karpenter는 helm이나 yaml을 배포하는 방식으로 사용자가 설치 및 구성해야 합니다. 애드온과 같은 방식이 아니라 오픈 소스를 배포하므로 직접 라이프사이클을 관리하며, 제공되는 모든 옵션을 활용할 수 있습니다.
컨트롤러에 해당하는 컴포넌트가 데이터 플레인에 직접 배포되기 때문에 동작 과정을 이해할 수 있습니다. 다만 이 또한 데이터 플레인의 리소스를 사용한다는 점과 사용자 책임의 관리가 필요한 점을 유의해야 합니다.
참고로 AKS의 KEDA, VPA, CA, Karpenter는 애드온이나 기능으로 제공되기 때문에 Managed의 영역으로 이해할 수 있습니다.
3. 실습 환경 생성
실습 환경은 아래와 같습니다.
CloudFormation을 바탕으로 실습 환경을 구성하도록 하겠습니다.
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-5week.yaml
# 변수 지정
CLUSTER_NAME=myeks
SSHKEYNAME=<SSH 키 페이 이름>
MYACCESSKEY=<IAM Uesr 액세스 키>
MYSECRETKEY=<IAM Uesr 시크릿 키>
# CloudFormation 스택 배포
aws cloudformation deploy --template-file myeks-5week.yaml --stack-name $CLUSTER_NAME --parameter-overrides KeyName=$SSHKEYNAME SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 MyIamUserAccessKeyID=$MYACCESSKEY MyIamUserSecretAccessKey=$MYSECRETKEY ClusterBaseName=$CLUSTER_NAME --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text
# EC2 접속
ssh -i ~/.ssh/ekskey.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
CloudFormation에서 운영서버를 배포하고, 이후 EKS 까지 배포를 하게 되어 있습니다. 대략 15~20분 가량이 소요됩니다.
그리고 생성된 EKS를 확인하고 kubeconfig을 받습니다.
# 변수 지정
CLUSTER_NAME=myeks
# 클러스터 확인
eksctl get cluster
# kubeconfig 생성
aws sts get-caller-identity --query Arn
aws eks update-kubeconfig --name $CLUSTER_NAME --user-alias <위 출력된 자격증명 사용자>
# 클러스터 기본 확인
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -A
NAME STATUS ROLES AGE VERSION INSTANCE-TYPE CAPACITYTYPE ZONE
ip-192-168-1-87.ap-northeast-2.compute.internal Ready <none> 3m12s v1.31.5-eks-5d632ec t3.medium ON_DEMAND ap-northeast-2a
ip-192-168-2-195.ap-northeast-2.compute.internal Ready <none> 3m9s v1.31.5-eks-5d632ec t3.medium ON_DEMAND ap-northeast-2b
ip-192-168-3-136.ap-northeast-2.compute.internal Ready <none> 3m8s v1.31.5-eks-5d632ec t3.medium ON_DEMAND ap-northeast-2c
kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system aws-node-b7vsh 2/2 Running 0 3m16s
kube-system aws-node-fnrj8 2/2 Running 0 3m13s
kube-system aws-node-ltkxn 2/2 Running 0 3m12s
kube-system coredns-86f5954566-96j8r 1/1 Running 0 9m10s
kube-system coredns-86f5954566-j6dvp 1/1 Running 0 9m10s
kube-system ebs-csi-controller-549bf6879f-6h7jg 6/6 Running 0 49s
kube-system ebs-csi-controller-549bf6879f-p2gml 6/6 Running 0 49s
kube-system ebs-csi-node-bm94h 3/3 Running 0 49s
kube-system ebs-csi-node-h5ntq 3/3 Running 0 49s
kube-system ebs-csi-node-n7s2s 3/3 Running 0 49s
kube-system kube-proxy-brf4v 1/1 Running 0 3m16s
kube-system kube-proxy-x7sbw 1/1 Running 0 3m13s
kube-system kube-proxy-zm6ht 1/1 Running 0 3m12s
kube-system metrics-server-6bf5998d9c-bzxn6 1/1 Running 0 9m9s
kube-system metrics-server-6bf5998d9c-zs5mg 1/1 Running 0 9m10s
그리고 이후 실습에 활용하기 위한 일부 컴포넌트들을 설치합니다.
# 환경 변수
CERT_ARN=$(aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text)
MyDomain=aperson.link # 각자 자신의 도메인 이름 입력
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "$MyDomain." --query "HostedZones[0].Id" --output text)
# AWS LoadBalancerController
helm repo add eks https://aws.github.io/eks-charts
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
--set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
# ExternalDNS
echo $MyDomain
curl -s https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml | MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst | kubectl apply -f -
# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=ClusterIP --set env.TZ="Asia/Seoul" --namespace kube-system
# kubeopsview 용 Ingress 설정 : group 설정으로 1대의 ALB를 여러개의 ingress 에서 공용 사용
echo $CERT_ARN
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/load-balancer-name: $CLUSTER_NAME-ingress-alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/target-type: ip
labels:
app.kubernetes.io/name: kubeopsview
name: kubeopsview
namespace: kube-system
spec:
ingressClassName: alb
rules:
- host: kubeopsview.$MyDomain
http:
paths:
- backend:
service:
name: kube-ops-view
port:
number: 8080 # name: http
path: /
pathType: Prefix
EOF
Prometheus와 Grafana도 설치를 진행합니다.
# repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성 : PV/PVC(AWS EBS) 삭제에 불편하여 PV/PVC 미사용 하도록 수정
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
scrapeInterval: "15s"
evaluationInterval: "15s"
podMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelectorNilUsesHelmValues: false
retention: 5d
retentionSize: "10GiB"
# Enable vertical pod autoscaler support for prometheus-operator
verticalPodAutoscaler:
enabled: true
ingress:
enabled: true
ingressClassName: alb
hosts:
- prometheus.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: xxx # Grafana 패스워드
defaultDashboardsEnabled: false
ingress:
enabled: true
ingressClassName: alb
hosts:
- grafana.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
kube-state-metrics:
rbac:
extraRules:
- apiGroups: ["autoscaling.k8s.io"]
resources: ["verticalpodautoscalers"]
verbs: ["list", "watch"]
customResourceState:
enabled: true
config:
kind: CustomResourceStateMetrics
spec:
resources:
- groupVersionKind:
group: autoscaling.k8s.io
kind: "VerticalPodAutoscaler"
version: "v1"
labelsFromPath:
verticalpodautoscaler: [metadata, name]
namespace: [metadata, namespace]
target_api_version: [apiVersion]
target_kind: [spec, targetRef, kind]
target_name: [spec, targetRef, name]
metrics:
- name: "vpa_containerrecommendations_target"
help: "VPA container recommendations for memory."
each:
type: Gauge
gauge:
path: [status, recommendation, containerRecommendations]
valueFrom: [target, memory]
labelsFromPath:
container: [containerName]
commonLabels:
resource: "memory"
unit: "byte"
- name: "vpa_containerrecommendations_target"
help: "VPA container recommendations for cpu."
each:
type: Gauge
gauge:
path: [status, recommendation, containerRecommendations]
valueFrom: [target, cpu]
labelsFromPath:
container: [containerName]
commonLabels:
resource: "cpu"
unit: "core"
selfMonitor:
enabled: true
alertmanager:
enabled: false
defaultRules:
create: false
kubeControllerManager:
enabled: false
kubeEtcd:
enabled: false
kubeScheduler:
enabled: false
prometheus-windows-exporter:
prometheus:
monitor:
enabled: false
EOT
cat monitor-values.yaml
# helm 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 69.3.1 \
-f monitor-values.yaml --create-namespace --namespace monitoring
# helm 확인
helm get values -n monitoring kube-prometheus-stack
# PV 사용하지 않음
kubectl get pv,pvc -A
# 프로메테우스 웹 접속
echo -e "https://prometheus.$MyDomain"
# 그라파나 웹 접속
echo -e "https://grafana.$MyDomain"
# TargetGroup binding 확인
kubectl get targetgroupbindings.elbv2.k8s.aws -A
NAMESPACE NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE AGE
kube-system k8s-kubesyst-kubeopsv-b2ecfd420f kube-ops-view 8080 ip 2m54s
monitoring k8s-monitori-kubeprom-40399c957e kube-prometheus-stack-grafana 80 ip 45s
monitoring k8s-monitori-kubeprom-826f25cbb8 kube-prometheus-stack-prometheus 9090 ip 45s
4. HPA(Horizontal Pod Autoscaler)
HPA는 지정된 워크로드의 특정 메트릭이 임계치를 초과하는 경우 복제본(Replicas)를 증가시킵니다.
출처: https://www.youtube.com/watch?v=jLuVZX6WQsw
HPA는 쿠버네티스 환경에서 기본으로 제공되기 때문에 어떤 환경에 있는 쿠버네티스에서도 사용이 가능합니다. 주의할 점은 HPA가 metrics-server에서 제공되는 core system metrics에 의해서 판단하게 되므로, metrics-server가 정상적이지 않으면 HPA가 동작하지 않는 점만 기억하시면 됩니다.
HPA는 EKS 특화된 기술이 아니기 때문에 EKS 환경의 실습은 진행하지 않으며, 다음 절에서 KEDA를 통해 HPA를 사용하는 사례를 통해 살펴보도록 하겠습니다.
HPA 실습
HPA에 대한 실습은 쿠버네티스 공식 문서를 참고하실 수 있습니다.
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/
1) 샘플 애플리케이션
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
2) HPA 정의
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
3) Load 발생
# 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"
이후 kubectl get hpa php-apache --watch
를 통해 메트릭의 증가와 복제본의 증가를 확인하실 수 있습니다.
HPA의 메트릭 확장
HPA는 Metric API를 통해서 값을 수집하는데, 이는 쿠버네티스에서 기본 제공되는 API가 아니며, 이를 노출하기 위한 metrics-server가 필요합니다. 또한 HPA는 이미 정의된 Resource Metric에 의해서 스케일링을 판단하는데 이는 metrics-server에 의해서 기본적으로 제공됩니다.
이러한 Metrics API를 확장하기 위해서 Custom Metric, External Metric을 사용할 수 있습니다. 보통 Prometheus를 통해서 추가 메트릭을 수집하고, Prometheus에서 수집된 메트릭을 기반으로 Prometheus Adapter이 Custom Metric API Server의 역할을 해 Custom Metric와 External Metric을 노출합니다.
출처: https://itnext.io/autoscaling-apps-on-kubernetes-with-the-horizontal-pod-autoscaler-798750ab7847
이후 살펴볼 KEDA 또한 자체적으로 Metrics API Server를 가지고 External Metircs를 노출하게 됩니다.
5. KEDA(Kubernetes Event-driven Autoscaler)
HPA는 metrics-server에 의해서 수집된 CPU, Memory와 같은 메트릭을 기반으로 스케일링을 결정합니다. 이러한 리소스 기반이 아닌 다른 메트릭을 참조하여 HPA를 동작하도 도와주는 컴포넌트가 KEDA(Kubernetes Event-driven Autoscaler)입니다.
KEDA는 다양한 Event Source로 부터 발생하는 이벤트를 기반으로 스케일링 여부를 결정할 수 있습니다.
출처: https://www.youtube.com/watch?v=jLuVZX6WQsw
EKS에서는 helm을 통해서 KEDA를 설치할 수 있습니다. (해당 실습에서 prometheus를 통해 모니터링을 하는 부분이 포함되어 있어, 사전 Prometheus가 설치되어 있어야 합니다)
# 설치 전 기존 metrics-server 제공 Metris API 확인
kubectl get --raw "/apis/metrics.k8s.io" | jq
{
"kind": "APIGroup",
"apiVersion": "v1",
"name": "metrics.k8s.io",
"versions": [
{
"groupVersion": "metrics.k8s.io/v1beta1",
"version": "v1beta1"
}
],
"preferredVersion": {
"groupVersion": "metrics.k8s.io/v1beta1",
"version": "v1beta1"
}
}
# external metrics는 없음
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq
Error from server (NotFound): the server could not find the requested resource
# KEDA 설치
cat <<EOT > keda-values.yaml
metricsServer:
useHostNetwork: true
prometheus:
metricServer:
enabled: true
port: 9022
portName: metrics
path: /metrics
serviceMonitor:
# Enables ServiceMonitor creation for the Prometheus Operator
enabled: true
podMonitor:
# Enables PodMonitor creation for the Prometheus Operator
enabled: true
operator:
enabled: true
port: 8080
serviceMonitor:
# Enables ServiceMonitor creation for the Prometheus Operator
enabled: true
podMonitor:
# Enables PodMonitor creation for the Prometheus Operator
enabled: true
webhooks:
enabled: true
port: 8020
serviceMonitor:
# Enables ServiceMonitor creation for the Prometheus webhooks
enabled: true
EOT
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --version 2.16.0 --namespace keda --create-namespace -f keda-values.yaml
# apiservice가 생성된 것을 알 수 있습니다.
kubectl get apiservice v1beta1.external.metrics.k8s.io -o yaml
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
annotations:
meta.helm.sh/release-name: keda
meta.helm.sh/release-namespace: keda
creationTimestamp: "2025-03-06T13:23:54Z"
labels:
app.kubernetes.io/component: operator
app.kubernetes.io/instance: keda
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: v1beta1.external.metrics.k8s.io
app.kubernetes.io/part-of: keda-operator
app.kubernetes.io/version: 2.16.0
helm.sh/chart: keda-2.16.0
name: v1beta1.external.metrics.k8s.io
resourceVersion: "7353"
uid: 26d3a3b1-7487-4086-84f9-1fd3105aa89d
spec:
caBundle: <생략>
group: external.metrics.k8s.io
groupPriorityMinimum: 100
service:
name: keda-operator-metrics-apiserver
namespace: keda
port: 443
version: v1beta1
versionPriority: 100
status:
conditions:
- lastTransitionTime: "2025-03-06T13:24:25Z"
message: all checks passed
reason: Passed
status: "True"
type: Available
# 설치 후 KEDA Metrics Server의해 노출된 External Metrics를 확인합니다.
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "external.metrics.k8s.io/v1beta1",
"resources": [
{
"name": "externalmetrics",
"singularName": "",
"namespaced": true,
"kind": "ExternalMetricValueList",
"verbs": [
"get"
]
}
]
}
KEDA를 설치한 이후 생성된 파드를 살펴보면 KEDA의 구성요소를 알 수 있는데, 각 Agent, Metrics, Admission Webhook의 역할을 합니다.
$ kubectl get pod -n keda
NAME READY STATUS RESTARTS AGE
pod/keda-admission-webhooks-86cffccbf5-nq7kw 1/1 Running 0 4m11s
pod/keda-operator-6bdffdc78-zrhmg 1/1 Running 1 (4m ago) 4m11s
pod/keda-operator-metrics-apiserver-74d844d769-2rbqk 1/1 Running 0 4m11s
공식 문서를 보면 각 컴포넌트에 해당하는 역할을 확인하실 수 있습니다.
https://keda.sh/docs/2.10/concepts/#how-keda-works
- Agent — KEDA activates and deactivates Kubernetes Deployments to scale to and from zero on no events. This is one of the primary roles of the
keda-operator
container that runs when you install KEDA. - Metrics — KEDA acts as a Kubernetes metrics server that exposes rich event data like queue length or stream lag to the Horizontal Pod Autoscaler to drive scale out. It is up to the Deployment to consume the events directly from the source. This preserves rich event integration and enables gestures like completing or abandoning queue messages to work out of the box. The metric serving is the primary role of the
keda-operator-metrics-apiserver
container that runs when you install KEDA. - Admission Webhooks - Automatically validate resource changes to prevent misconfiguration and enforce best practices by using an admission controller. As an example, it will prevent multiple ScaledObjects to target the same scale target.
그리고 생성된 CRD를 확인해보면 아래와 같은 CRD가 생성된 것을 알 수 있습니다.
$ kubectl get crd | grep keda
cloudeventsources.eventing.keda.sh 2025-03-06T13:23:51Z
clustercloudeventsources.eventing.keda.sh 2025-03-06T13:23:51Z
clustertriggerauthentications.keda.sh 2025-03-06T13:23:51Z
scaledjobs.keda.sh 2025-03-06T13:23:53Z
scaledobjects.keda.sh 2025-03-06T13:23:51Z
triggerauthentications.keda.sh 2025-03-06T13:23:51Z
이 중 ScaledObjects
이 중요한 역할을 합니다. 이는 Event Source(예를 들어, Rabbit MQ)와 쿠버네티스 리소스(예를 들어, Deployment) 간의 의도하는 맵핑(desired mapping)을 나타냅니다.
아래 실습에 사용되는 명세를 살펴보면, cron을 바탕으로 트리거triggers
되며, php-apache라는 Deployment를 대상 지정scaleTargetRef
하고, spec
에 HPA 오브젝트에 필요한 값이나 스케일링 속성을 지정합니다.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: php-apache-cron-scaled
spec:
minReplicaCount: 0
maxReplicaCount: 2
pollingInterval: 30
cooldownPeriod: 300
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
triggers:
- type: cron
metadata:
timezone: Asia/Seoul
start: 00,15,30,45 * * * *
end: 05,20,35,50 * * * *
desiredReplicas: "1"
참고로 scaledObject를 생성하면 HPA도 같이 생성이 됩니다. 아래에서 다시 살펴보겠습니다.
이제 샘플 애플리케이션과 ScaledObject를 생성합니다.
# keda 네임스페이스에 디플로이먼트 생성
cat << EOF > php-apache.yaml
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
EOF
kubectl apply -f php-apache.yaml -n keda
kubectl get pod -n keda
...
php-apache-d87b7ff46-bbp8c 0/1 ContainerCreating 0 3s
# ScaledObject 정책 생성 : cron
cat <<EOT > keda-cron.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: php-apache-cron-scaled
spec:
minReplicaCount: 0
maxReplicaCount: 2 # Specifies the maximum number of replicas to scale up to (defaults to 100).
pollingInterval: 30 # Specifies how often KEDA should check for scaling events
cooldownPeriod: 300 # Specifies the cool-down period in seconds after a scaling event
scaleTargetRef: # Identifies the Kubernetes deployment or other resource that should be scaled.
apiVersion: apps/v1
kind: Deployment
name: php-apache
triggers: # Defines the specific configuration for your chosen scaler, including any required parameters or settings
- type: cron
metadata:
timezone: Asia/Seoul
start: 00,15,30,45 * * * *
end: 05,20,35,50 * * * *
desiredReplicas: "1"
EOT
kubectl apply -f keda-cron.yaml -n keda
# 모니터링
kubectl get ScaledObject,hpa,pod -n keda
kubectl get ScaledObject -n keda -w
# HPA 확인 -> external 유형으로 생성되어 있음
kubectl get hpa -o jsonpath="{.items[0].spec}" -n keda | jq
{
"maxReplicas": 2,
"metrics": [
{
"external": {
"metric": {
"name": "s0-cron-Asia-Seoul-00,15,30,45xxxx-05,20,35,50xxxx",
"selector": {
"matchLabels": {
"scaledobject.keda.sh/name": "php-apache-cron-scaled"
}
}
},
"target": {
"averageValue": "1",
"type": "AverageValue"
}
},
"type": "External"
}
],
"minReplicas": 1,
"scaleTargetRef": {
"apiVersion": "apps/v1",
"kind": "Deployment",
"name": "php-apache"
}
}
이 예시는 cron을 통해서 00, 15, 30, 45분에 desiredReplicas
에 지정한 1개의 파드가 증가하고, 이후 05, 20, 35, 40에 minReplicaCount
에 지정된 0개로 파드가 줄어 듭니다. (이 예제에서 maxReplicaCount
는 큰 의미가 없지만 생성되는 HPA 오브젝트에서 사용되기 때문에 작성이 필요함)
결과를 아래와 같이 확인할 수 있습니다. 다만 테스트를 해보면 Cron으로 정의한 45분(start), 50분(end)에 정시에 트리거가 되는 것은 아닌 것으로 확인되기 때문에 정확도에 대해서는 기대치를 다소 낮춰야 할 것 같습니다.
# 이전 시점
Thu Mar 6 22:44:53 KST 2025
NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE FALLBACK PAUSED TRIGGERS AUTHENTICATIONS AGE
scaledobject.keda.sh/php-apache-cron-scaled apps/v1.Deployment php-apache 0 2 True False False Unknown 12m
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
horizontalpodautoscaler.autoscaling/keda-hpa-php-apache-cron-scaled Deployment/php-apache <unknown>/1 (avg) 1 2 0 12m
NAME READY STATUS RESTARTS AGE
pod/keda-admission-webhooks-86cffccbf5-nq7kw 1/1 Running 0 21m
pod/keda-operator-6bdffdc78-zrhmg 1/1 Running 1 (20m ago) 21m
pod/keda-operator-metrics-apiserver-74d844d769-2rbqk 1/1 Running 0 21m
# 45분 이후
Thu Mar 6 22:45:29 KST 2025
NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE FALLBACK PAUSED TRIGGERS AUTHENTICATIONS AGE
scaledobject.keda.sh/php-apache-cron-scaled apps/v1.Deployment php-apache 0 2 True True False Unknown 12m
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
horizontalpodautoscaler.autoscaling/keda-hpa-php-apache-cron-scaled Deployment/php-apache <unknown>/1 (avg) 1 2 0 12m
NAME READY STATUS RESTARTS AGE
pod/keda-admission-webhooks-86cffccbf5-nq7kw 1/1 Running 0 21m
pod/keda-operator-6bdffdc78-zrhmg 1/1 Running 1 (21m ago) 21m
pod/keda-operator-metrics-apiserver-74d844d769-2rbqk 1/1 Running 0 21m
pod/php-apache-d87b7ff46-gblgb 1/1 Running 0 9s
# ScaledObject 의 ACTIVE 상태가 45분 시점 True로 변경됨
kubectl get ScaledObject -n keda -w
NAME SCALETARGETKIND SCALETARGETNAME MIN MAX READY ACTIVE FALLBACK PAUSED TRIGGERS AUTHENTICATIONS AGE
php-apache-cron-scaled apps/v1.Deployment php-apache 0 2 True False False Unknown 2m30s
php-apache-cron-scaled apps/v1.Deployment php-apache 0 2 True False False Unknown 7m
php-apache-cron-scaled apps/v1.Deployment php-apache 0 2 True False False Unknown 12m
php-apache-cron-scaled apps/v1.Deployment php-apache 0 2 True True False Unknown 12m
php-apache-cron-scaled apps/v1.Deployment php-apache 0 2 True True False Unknown 13m
...
# 40분 쯤에 ScaleTarget을 minReplicaCount로 변경 함
kubectl logs -f -n keda keda-operator-6bdffdc78-zrhmg
...
2025-03-06T13:39:52Z INFO scaleexecutor Successfully set ScaleTarget replicas count to ScaledObject minReplicaCount {"scaledobject.Name": "php-apache-cron-scaled", "scaledObject.Namespace": "keda", "scaleTarget.Name": "php-apache", "Original Replicas Count": 1, "New Replicas Count": 0}
# 45분 쯤에 keda-operator에서 ScaleTarget을 업데이트 함
kubectl logs -f -n keda keda-operator-6bdffdc78-zrhmg
...
2025-03-06T13:45:22Z INFO scaleexecutor Successfully updated ScaleTarget {"scaledobject.Name": "php-apache-cron-scaled", "scaledObject.Namespace": "keda", "scaleTarget.Name": "php-apache", "Original Replicas Count": 0, "New Replicas Count": 1}
# 54분에 ScaleTarget을 minReplicaCount로 변경 함
2025-03-06T13:54:52Z INFO scaleexecutor Successfully set ScaleTarget replicas count to ScaledObject minReplicaCount {"scaledobject.Name": "php-apache-cron-scaled", "scaledObject.Namespace": "keda", "scaleTarget.Name": "php-apache", "Original Replicas Count": 1, "New Replicas Count": 0}
참고로 Grafana에서 아래의 json으로 대시보드를 만들어 모니터링 할 수 있습니다.
https://github.com/kedacore/keda/blob/main/config/grafana/keda-dashboard.json
실습을 마무리하고 생성된 리소스를 삭제합니다.
# KEDA 및 deployment 등 삭제
kubectl delete ScaledObject -n keda php-apache-cron-scaled && kubectl delete deploy php-apache -n keda && helm uninstall keda -n keda
kubectl delete namespace keda
6. VPA(Vertical Pod Autoscaler)
VPA는 대상이 되는 리소스의 과거 사용률을 기반으로 대상의 컨테이너 스펙의 request와 limits 자체를 변경하는 오토스케일러 입니다.
VPA의 동작과정을 아래에서 설명하고 있습니다. 단, 아래 설명은 Update Mode: Auto이므로 파드가 재생성되는 과정까지를 설명하고 있습니다.
출처: https://www.youtube.com/watch?v=jLuVZX6WQsw
그림에서 표시된 바와 같이 VPA에는 아래와 같은 주요 컴포넌트가 있습니다.
참고: https://github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/docs/components.md
- Recommender - monitors the current and past resource consumption and, based on it, provides recommended values for the containers' cpu and memory requests.
- Updater - checks which of the managed pods have correct resources set and, if not, kills them so that they can be recreated by their controllers with the updated requests.
- Admission Controller - sets the correct resource requests on new pods (either just created or recreated by their controller due to Updater's activity).
VPA에서 추천값을 계산하는 방식은 과거 사용률을 바탕으로 기준값을 정하고 여기에 Margin을 더하여 결정합니다. 상세한 내용은 아래 링크를 참고 부탁드립니다.
https://devocean.sk.com/blog/techBoardDetail.do?ID=164786
또한 VPA에는 updateMode를 설정할 수 있는데 Auto(Default), Recreate, Initial, Off 입니다.
이해하기로는 현 시점 Auto와 Recreate은 동일하게 동작을 합니다. 즉 생성되는 파드와 실행 중인 파드를 변경하고, 필요하면 현재 실행 중인 파드를 재시작 합니다. 다만 이후 in-place resource resize(Kubernetes 1.27, Alpha)가 가능해지면, Auto 모드에서는 재시작을 하지않고, 현재 파드의 리소스를 수정만하는 방식으로 사용될 수 있습니다.
Initial은 파드가 생성되는 시점에만 추천 값을 적용하는 모드이고, Off는 추천 값을 VPA 오브젝트를 통해서 확인만 가능한 모드입니다.
상세한 설명은 아래 문서를 참고 부탁드립니다.
https://learn.microsoft.com/en-us/azure/aks/vertical-pod-autoscaler#vpa-object-operation-modes
이제 VPA를 설치하도록 하겠습니다.
# VPA 코드 다운로드
git clone https://github.com/kubernetes/autoscaler.git
# 만약 openssl 이 1.1.1 이하 버전인 경우, 1.1.1 이상 버전으로 업그레이드 필요함
openssl version
OpenSSL 1.0.2k-fips 26 Jan 2017
# (필요한 경우) 1.0 제거
yum remove openssl -y
# (필요한 경우) openssl 1.1.1 이상 버전 확인
yum install openssl11 -y
# 스크립트 파일 내에 openssl11 수정 (commit 까지 해야 수행됨)
cd autoscaler/vertical-pod-autoscaler/
sed -i 's/openssl/openssl11/g' 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"
# Deploy the Vertical Pod Autoscaler to your cluster with the following command.
watch -d kubectl get pod -n kube-system
./hack/vpa-up.sh
# (필요한 경우) openssl 관련 에러가 발생한다면 재실행!
sed -i 's/openssl/openssl11/g' pkg/admission-controller/gencerts.sh
./hack/vpa-up.sh
# 설치 후 확인
kubectl get pod -n kube-system |grep vpa
vpa-admission-controller-659c978dcd-zwn24 1/1 Running 0 106s
vpa-recommender-9bb6d98b7-gjqc7 1/1 Running 0 112s
vpa-updater-68db47986b-jqnnh 1/1 Running 0 116s
# mutating webhook을 통해서 파드 생성 시점 설정을 변경함
kubectl get mutatingwebhookconfigurations vpa-webhook-config
NAME WEBHOOKS AGE
vpa-webhook-config 1 101s
kubectl get mutatingwebhookconfigurations vpa-webhook-config -o json | jq
{
"apiVersion": "admissionregistration.k8s.io/v1",
"kind": "MutatingWebhookConfiguration",
"metadata": {
"creationTimestamp": "2025-03-06T14:14:31Z",
"generation": 1,
"name": "vpa-webhook-config",
"resourceVersion": "22754",
"uid": "03b88fcf-c1ff-4079-b33c-38c998829d50"
},
"webhooks": [
{
"admissionReviewVersions": [
"v1"
],
"clientConfig": {
"caBundle": "<생략>",
"service": {
"name": "vpa-webhook",
"namespace": "kube-system",
"port": 443
}
},
"failurePolicy": "Ignore",
"matchPolicy": "Equivalent",
"name": "vpa.k8s.io",
"namespaceSelector": {
"matchExpressions": [
{
"key": "kubernetes.io/metadata.name",
"operator": "NotIn",
"values": [
""
]
}
]
},
"objectSelector": {},
"reinvocationPolicy": "Never",
"rules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"pods"
],
"scope": "*"
},
{
"apiGroups": [
"autoscaling.k8s.io"
],
"apiVersions": [
"*"
],
"operations": [
"CREATE",
"UPDATE"
],
"resources": [
"verticalpodautoscalers"
],
"scope": "*"
}
],
"sideEffects": "None",
"timeoutSeconds": 30
}
]
}
설치를 마치면 VPA와 관련된 파드와 CRD를 확인하실 수 있습니다.
kubectl get crd | grep autoscaling
verticalpodautoscalercheckpoints.autoscaling.k8s.io 2025-03-06T14:13:55Z
verticalpodautoscalers.autoscaling.k8s.io 2025-03-06T14:13:55Z
아래와 같이 샘플 예제를 배포하여 VPA를 테스트 하겠습니다.
# 공식 예제 배포 (autoscaler/vertical-pod-autoscaler 위치에서 수행)
kubectl apply -f examples/hamster.yaml && kubectl get vpa -w
verticalpodautoscaler.autoscaling.k8s.io/hamster-vpa created
deployment.apps/hamster created
NAME MODE CPU MEM PROVIDED AGE
hamster-vpa Auto 3s
hamster-vpa Auto 511m 262144k True 44s # VPA의 추천 값
# 파드 리소스 Requestes 확인
kubectl describe pod | grep Requests: -A2
Requests:
cpu: 100m
memory: 50Mi
--
Requests:
cpu: 100m
memory: 50Mi
# VPA에 의해 기존 파드 삭제되고 신규 파드가 생성됨
kubectl get events --sort-by=".metadata.creationTimestamp" | grep VPA
19s Normal EvictedByVPA pod/hamster-598b78f579-8gjfh Pod was evicted by VPA Updater to apply resource recommendation.
19s Normal EvictedPod verticalpodautoscaler/hamster-vpa VPA Updater evicted Pod hamster-598b78f579-8gjfh to apply resource recommendation.
# 파드 리소스 Requestes 가 변경 됨
kubectl describe pod | grep Requests: -A2
Requests:
cpu: 100m
memory: 50Mi
--
Requests:
cpu: 100m
memory: 50Mi
--
Requests:
cpu: 511m
memory: 262144k
실습을 마무리하고 아래와 같이 리소스를 삭제하겠습니다.
kubectl delete -f examples/hamster.yaml && ./hack/vpa-down.sh
마무리
생각보다 길이 길어져서 오토스케일링 개요, HPA, KEDA, VPA 까지만 이번 포스트에서 작성하고, CA, Karpenter, AKS의 오토스케일링, 스케일링 주의사항은 다음 포스트에서 이어서 작성하겠습니다.
'EKS' 카테고리의 다른 글
[6] EKS의 Security - EKS 인증/인가와 Pod IAM 권한 할당 (0) | 2025.03.16 |
---|---|
[5-2] EKS의 오토스케일링 Part2 (0) | 2025.03.07 |
[4] EKS의 모니터링과 로깅 (0) | 2025.03.01 |
[3-2] EKS 노드 그룹 (0) | 2025.02.23 |
[3-1] EKS 스토리지 옵션 (0) | 2025.02.23 |