EKS Autoscaling
EKS Cluster의 Node 수와 Pod 수를 자동으로 조정하여 서비스에 사용되는 자원을 최적화하는 기능이다.
개인적으로 이런 기능이 클라우드와 Kubernetes를 쓰는 주된 이유라고 생각한다.
- 서비스의 요청에 따라 성능과 비용을 최적화 할 수 있다.
- Node나 Pod에 장애가 발생한 경우 자동으로 복구해 서비스 가용성을 높일 수 있다.
- 이런 작업들이 자동으로 이루어지기 때문에 관리와 운영 편의성을 높일 수 있다.
다양한 방법을 통해서 Autoscaling을 제공하는데 대표적인 방법은 아래와 같다.
HPA(Horizontal Pod Autoscaler)와 VPA(Vertical Pod Autoscaler)
- HPA는 Pod의 CPU 사용량, 메모리 사용량 또는 요청 수와 같은 지표를 사용하여 Pod의 수를 조절하는 방법이다.
- VPA는 Pod의 CPU 사용량과 메모리 사용량을 조정하여 Pod의 성능을 향상시키는 방법이다.
KEDA(Kubernetes based Event Driven Autoscaler)
- Kubernetes용 이벤트 기반 Autoscaler이며, 서비스 특성을 반영한 확장이 가능하다.
- HPA에 비해 더 정교한 Autoscaling이 가능하다.
- 예를들어 HPA는 Resource(CPU, Memory 등) 기반의 Autoscaling으로 지정한 지표가 일정량을 넘으면 Pod를 증가시키는 단순한 동작인데, 만약 해당 지표가 서비스 지연과 연관이 없을 경우 Pod 증가가 의미가 없어지는 경우가 발생한다.
- 하지만 KEDA는 Pod 증가가 필요한 이벤트를 지정해서 해당 이벤트가 발생할 때 증가시킬 수 있기때문에 더 정교한 Autoscaling이라고 볼 수 있다.
- 하지만 사용과 구성이 비교적 더 복잡하다.
- Github : https://github.com/kedacore/keda
CPA(Cluster Proportional Autoscaler)
- Cluster의 Node 수와 Pod 수의 비율을 유지해주는 Autoscaler로, Node가 추가로 생성할 때 지정한 Pod를 같이 추가해 확장해준다.
- 각 Node에 필수로 존재해야하는 Pod가 있거나, Node에 안정적으로 분배되어야 하는 서비스가 있을 경우 사용하기 좋다.
- 실습 참고자료 : https://www.eksworkshop.com/docs/autoscaling/workloads/cluster-proportional-autoscaler/
- Github : https://github.com/kubernetes-sigs/cluster-proportional-autoscaler
Cluster Autoscaler(CA)
- EKS Cluster의 Node 수를 확장하거나 축소해서 최적화 하는 방법이다.
- AWS의 경우 EC2 AutoScaling Group을 통해서 동작한다.
- 클라우드 서비스의 경우 이 Node의 확장과 축소에 의해 비용이 크게 결정된다.
- 다만 Node의 확장과 축소에 보통 분 단위로 시간이 소요된다.
이렇게 Node의 확장과 축소에 시간이 걸리는 데는 아래와 같은 문제가 있다.
EKS의 경우 AWS EC2 AutoScaling Group에 의존하다보니 EKS가 가진 정보와 AutoScaling Group이 가진 정보를 동기화 하거나 불일치하는 경우로 인해서 다양한 문제가 발생한다.
- Node 확장/축소하는 Autoscaling 동작이 느리기 때문에 이로 인한 비용적 손실이 크게 발생한다.
- Node의 자원 사용량을 최적화해서 사용하기 어렵다. (확장과 축소가 필요한 자원 사용량의 적절한 수치를 알아내기 어렵다.)
- 원하지 않는 Autoscaling 동작이 발생하는 경우가 존재한다. (사용량에 여유가 있을 때 Node 확장이 일어나거나, Node에 부하가 발생했을 때 Node 확장이 동작하지 않는 경우 등)
Karpenter
위의 Cluster Autoscaler(CA)가 가진 단점을 극복하기 위해 AWS에서 Karpenter라는 오픈소스를 개발하였다.
AWS에서 개발했지만 Kubernetes용으로 개발된 오픈소스이다.
기존 AutoScaler와 AutoScaling Group을 통해서 복잡한 정보 교환을 하며 동작했던 과정을 제거하고, Karpender가 바로 요청을 받아 분석하고 처리해줌으로써 빠른 Provisioning이 가능해졌다.
- Kubernetes용 Node Autoscaler이며,
- 아직은 AWS EKS에서만 지원된다.
(Azure에서도 지원해달라는 요청사항 : https://github.com/Azure/AKS/issues/2712) - Github : https://github.com/aws/karpenter
Karpenter는 크게 4가지 동작으로 구분된다.
- Watching : unschedulable 상태인 Pod를 주기적으로 확인
- Evaluating : Pod를 scheduling하기 위해 제약 사항을 평가(자원 요청량, Node Selector 등)
- Provisioning : Pod의 요구사항을 충족하는 Node에 Pod 배포
- Removing : 필요없어진 Node를 삭제
Karpenter 사용해보기
https://karpenter.sh/v0.27.5/getting-started/getting-started-with-karpenter/ 에서 제공하는 스크립트를 통해 실습해볼 수 있다.
Karpenter 설치에 필요한 사전 준비
# 스크립트 사용을 위한 환경변수 설정
export KARPENTER_VERSION=v0.27.5 # 사용할 Karpeter버전 가능한 최신버전을 사용하자
export CLUSTER_NAME="${USER}-karpenter-demo" # 사용할 클러스터 이름
export AWS_DEFAULT_REGION="ap-northeast-2" # 사용할 Region
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export TEMPOUT=$(mktemp) # 임시로 사용할 디렉토리 경로
# Cloudformation으로 Karpenter에서 필요한 IAM 권한과 NodeInstanceProfile 등을 생성한다.
curl -fsSL https://karpenter.sh/"${KARPENTER_VERSION}"/getting-started/getting-started-with-karpenter/cloudformation.yaml > $TEMPOUT \
&& aws cloudformation deploy \
--stack-name "Karpenter-${CLUSTER_NAME}" \
--template-file "${TEMPOUT}" \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides "ClusterName=${CLUSTER_NAME}"
자동 실행되는 yaml 파일을 살펴보면 몇가지 재밌는 점이 있는데, SQS 권한을 요청하는 것과 제품 가격정보에 대한 권한을 요청하는 것이다. 이는 SQS를 통해 Node Interrupt에 대한 이벤트 처리를 하는 것(https://karpenter.sh/preview/concepts/deprovisioning/#interruption)과 Karpenter가 가격정보를 토대로 Node를 평가해 적절한 Node를 Provisioning을 해주기 때문이다.
...
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEKS_CNI_Policy"
- !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEKSWorkerNodePolicy"
- !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
- !Sub "arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore"
KarpenterControllerPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Sub "KarpenterControllerPolicy-${ClusterName}"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Resource: "*"
Action:
# Write Operations
- ec2:CreateFleet
...
- ec2:TerminateInstances
# Read Operations
- ec2:DescribeAvailabilityZones
...
- ec2:DescribeSubnets
- pricing:GetProducts # 가격정보를 가져오는 권한을 요청한다
- ssm:GetParameter
...
KarpenterInterruptionQueue:
Type: AWS::SQS::Queue # SQS 관련 권한을 요청한다
Properties:
QueueName: !Sub "${ClusterName}"
MessageRetentionPeriod: 300
SqsManagedSseEnabled: true
...
EKS Cluster 생성
기존 EKS Cluster 생성과 조금 다른점은 metadata의 tag 정보로 karpenter.sh/discovery: ${CLUSTER_NAME} 가 붙는 부분이다.
Karpenter는 Node 시작을 찾기위해 securityGroupSelector와 subnetSelector를 사용하는데 이때 해당 tag를 사용한다.
eksctl create cluster -f - <<EOF
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: ${CLUSTER_NAME}
region: ${AWS_DEFAULT_REGION}
version: "1.24"
tags:
karpenter.sh/discovery: ${CLUSTER_NAME}
iam:
withOIDC: true
serviceAccounts:
- metadata:
name: karpenter
namespace: karpenter
roleName: ${CLUSTER_NAME}-karpenter
attachPolicyARNs:
- arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}
roleOnly: true
iamIdentityMappings:
- arn: "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}"
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
managedNodeGroups:
- instanceType: m5.large
amiFamily: AmazonLinux2
name: ${CLUSTER_NAME}-ng
desiredCapacity: 2
minSize: 1
maxSize: 10
## Optionally run on fargate
# fargateProfiles:
# - name: karpenter
# selectors:
# - namespace: karpenter
EOF
Karpenter 설치
# Karpenter 설치를 위한 환경변수 설정
export CLUSTER_ENDPOINT="$(aws eks describe-cluster --name ${CLUSTER_NAME} --query "cluster.endpoint" --output text)"
export KARPENTER_IAM_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/${CLUSTER_NAME}-karpenter"\
echo $CLUSTER_ENDPOINT $KARPENTER_IAM_ROLE_ARN
# EC2 Spot Fleet 사용을 위한 권한 재확인
# service-linked-role을 이미 생성했으므로 에러가 나는 것이 정상
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com || true
# public ECR에 인증없이 이미지를 가져오기 위해 혹시 모를 docker 인증 logout
docker logout public.ecr.aws
# Karpenter 설치
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter --version ${KARPENTER_VERSION} --namespace karpenter --create-namespace \
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=${KARPENTER_IAM_ROLE_ARN} \
--set settings.aws.clusterName=${CLUSTER_NAME} \
--set settings.aws.defaultInstanceProfile=KarpenterNodeInstanceProfile-${CLUSTER_NAME} \
--set settings.aws.interruptionQueueName=${CLUSTER_NAME} \
--set controller.resources.requests.cpu=1 \
--set controller.resources.requests.memory=1Gi \
--set controller.resources.limits.cpu=1 \
--set controller.resources.limits.memory=1Gi \
--wait
# Provisioner 생성
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"] # "on-demand" 도 가능
limits:
resources:
cpu: 1000
providerRef:
name: default
ttlSecondsAfterEmpty: 30 # 비어있는 Node를 판단하는 시간
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
name: default
spec:
subnetSelector:
karpenter.sh/discovery: ${CLUSTER_NAME}
securityGroupSelector:
karpenter.sh/discovery: ${CLUSTER_NAME}
EOF
# 설치 확인
(test_admin@myeks2:N/A) [root@myeks2-bastion-EC2 bin]# kubectl get awsnodetemplates,provisioners
NAME AGE
awsnodetemplate.karpenter.k8s.aws/default 9s
NAME AGE
provisioner.karpenter.sh/default 9s
Karpenter Provisioner 옵션 : https://karpenter.sh/preview/concepts/provisioners/
Karpenter 동작 확인
Pod 1개당 최소 CPU 1을 사용하는 앱을 배포한다.
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
spec:
replicas: 0
selector:
matchLabels:
app: inflate
template:
metadata:
labels:
app: inflate
spec:
terminationGracePeriodSeconds: 0
# 정상 종료 동작이 수행되는 기간(Grace Period) 설정
# 만약 설정한 시간안에 정상종료가 되지 않으면 Pod에 SIGKILL 시그널을 보내서 프로세스를 강제종료한다
containers:
- name: inflate
image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
resources:
requests:
cpu: 1
EOF
배포 후 replicas 0인 상태에서의 Node 모니터링
# replicas 5로 변경 후 Karpenter 동작 확인
kubectl scale deployment inflate --replicas 5
kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
할당 가능한 용량을 확인해야하는 Spot인데도 1분도 되지않아 Node가 준비되었고 몇 초 후에 바로 Ready 상태로 변경되었다.
Spot의 경우 Karpenter가 현재 사용할 수 있는 용량 중에 가장 저렴한 용량으로 선택해준다고 한다.
(몇번 테스트를 해봤는데 추측으로는 m5.large의 On-Demand기 때문에 가장 유사한 가격대로 설정해주는 것 같았다.)
배포한 app을 삭제 후 Node가 내려가는 시간 테스트를 해보면 DaemonSet 외에 Pod가 없어진지 30초 후에 삭제된 것을 확인할 수 있다.
Karpenter Consolidation 기능 테스트
Kubernetes 클러스터에서 Node를 자동으로 합쳐주는 기능으로, 자원의 여유가 있는 Node를 모니터링하고 가능한 경우 Node를 합쳐서 비용절감 효과를 가져다 준다.
# 기존 Provisioner를 삭제하고 재배포
kubectl delete provisioners default
# Consolidation을 활성화한 Provisioner로 배포
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
consolidation: # Consolidation 기능 활성화
enabled: true
labels:
type: karpenter
limits:
resources:
cpu: 1000
memory: 1000Gi
providerRef:
name: default
requirements:
- key: karpenter.sh/capacity-type
operator: In
values:
- on-demand
- key: node.kubernetes.io/instance-type
operator: In
values:
- c5.large
- m5.large
- m5.xlarge
EOF
# 앱 배포 후 Replicas 증가
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: inflate
spec:
replicas: 0
selector:
matchLabels:
app: inflate
template:
metadata:
labels:
app: inflate
spec:
terminationGracePeriodSeconds: 0
containers:
- name: inflate
image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
resources:
requests:
cpu: 1
EOF
kubectl scale deployment inflate --replicas 12
배포 후 초기 상태
Replicas 5로 변경 후 모니터링하면 서비스하는 Pod가 없는 3번째 Node는 가장 먼저 삭제되고 5개의 노드가 된다.
Node 삭제 후 조금 더 기다리면 한개의 Node가 더 없어지는 것을 볼 수 있는데 Pod가 옮겨져서 하나의 Node로 합쳐지는 것을 확인할 수 있다.
Replicas 1로 변경 후 최종 Node를 확인해보면 기존 m5.large 2개에 마지막 추가된 Node는 c5.large로 선택해 배포한 것을 확인할 수 있다.
참고자료
- AWS Karpenter Github : https://github.com/aws/karpenter
- Scaling Kubernetes with Karpenter: Advanced Scheduling with Pod Affinity and Volume Topology Awareness : https://aws.amazon.com/ko/blogs/containers/scaling-kubernetes-with-karpenter-advanced-scheduling-with-pod-affinity-and-volume-topology-awareness/
- [데브옵스] 오픈 소스 Karpenter를 활용한 Amazon EKS 확장 운영 전략 | 신재현, 무신사 : https://youtu.be/FPlCVVrCD64
- Karpenter 소개 – 오픈 소스 고성능 Kubernetes 클러스터 오토스케일러 : https://aws.amazon.com/ko/blogs/korea/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/
- Getting Started with Karpenter : https://karpenter.sh/v0.27.5/getting-started/getting-started-with-karpenter/