들어가며
이번 세미나에서는 Kubernetes Autoscaling의 개념과 Autoscaling을 통해 리소스를 더 효율적으로 관리하기 위한 몇 가지 오픈소스를 알게 되었다. 또한 EKS 기반 클러스터에서 Autoscaling 실습을 진행하며 실무에 어떤 식으로 적용해야 할지에 대한 감을 잡을 수 있었다.
Kubernetes Autoscaling
Kubernetes 클러스터에서 애플리케이션 또는 워크로드의 수요에 따라 자동으로 리소스를 조정하는 기능이다. 이 기능은 클러스터 내에서 실행 중인 파드의 수를 동적으로 조절하여 애플리케이션의 가용성과 성능을 최적화한다.
참고 - 링크
Workload 방식은 실행 중인 Pod의 갯수를 늘리는 HPA(Horizontal Pod Autoscailing) 방식과 실행 중인 Pod의 리소스를 높이는 VPA(Vertical Pod Autoscaling) 방식이 있다.
그리고 현재 클러스터에서 이러한 Scaling을 적용할 인프라 리소스가 부족하거나, 과도하게 리소스가 남을 경우 kubernetes 클러스터 자체를 Autoscaling 하기도 한다.
먼저 Workload 기준의 Scaling 방식 부터 살펴보도록 한다. 아래의 그림은 VPA, HPA의 동작 방식을 대조하기 위한 도식이다.
참고 - 링크
HPA(Horizontal Pod Autoscaler = Pod scale out/in) - Workload Scaling
HPA를 수행하는 절차는 다음과 같다.
Metrics Server는 Kubernetes 클러스터 내에서 실행 중인 각 노드의 kubelet에 요청을 보내고, 포드에서 실행 중인 컨테이너의 리소스 사용량 메트릭을 수집한다. 이러한 메트릭에는 CPU 사용량, 메모리 사용량, 디스크 I/O, 네트워크 트래픽 등이 포함된다.
kube-controller-manager의 HPA controller는 일정한 주기(default = 15초)로 kube-apiserver를 통해 Metrics Server로부터 메트릭 데이터를 Pull 방식으로 수집한다. 이렇게 수집된 메트릭 데이터를 기반으로 HPA controller HorizontalPodAutoscaler(HPA)에 명시된 스펙과 비교하며 감시한다.
HPA controller는 HPA에 지정된 조건과 설정을 기반으로 현재 상태를 확인하고, 파드의 수를 조정하기 위한 결정을 내린다. 예를 들어, HPA 리소스에 명시된 정보를 기반으로 CPU 사용량이 특정 임계값을 초과하는 경우 파드 수를 증가시킬 수 있다.
HPA controller는 파드 수를 업데이트하기 위해 Deployment 스펙을 조정하고, 이를 통해 Deployment controller는 새로운 파드 수를 반영하는 ReplicaSet를 업데이트 하도록 지시하여 실제 파드 수를 조정한다.
VPA(Vertical Pod Autoscaler = Pod scale up/down) - Workload Scaling
VPA를 수행하기 위해 동작하는 요소들이 많기 때문에 우선 각 요소가 하는 역할에 대해서 살펴 보도록한다. 각각의 요소들의 설명은 다음과 같다.
- Metrics Server: Kubernetes 클러스터 내의 모든 노드와 파드의 자원 사용량에 대한 정보를 수집하여 API를 통해 제공하는 컴포넌트
- Kube-apiserver: Kubernetes 클러스터의 API 서버로 RESTful API를 통해 클러스터의 상태 정보를 제공하고, 사용자의 명령을 받아 클러스터의 상태를 변경하는 역할을 수행함
- VerticalPodAutoscaler (VPA): VPA는 파드의 CPU 및 메모리 요구사항을 자동으로 조정함. VPA는 런타임에 파드의 리소스 요구사항을 변경하여 파드가 사용하는 자원과 실제 필요한 자원 간의 불일치를 해결
- VPA recommender: VPA 구성 요소 중 하나로, 파드의 리소스 사용 히스토리를 기반으로 새로운 리소스 요청과 리밋을 추천함
- VPA admission plugin: 파드가 생성되거나 업데이트 될 때 VPA recommender로부터 받은 추천 사항을 바탕으로 파드의 자원 요구사항을 업데이트.
- VPA updater: 이미 실행중인 파드의 자원 요구사항을 recommender의 추천에 맞게 업데이트함. 이때, 파드는 변경된 리소스를 기반으로 재시작됨
VPA를 수행하는 절차는 다음과 같다.
Metrics Server는 클러스터 내의 모든 노드와 파드의 리소스 사용 정보를 수집한다.
VPA recommender는 kube-apiserver를 통해 Metrics Server의 정보를 사용하여 파드의 리소스 사용량에 대한 패턴을 학습한다.
다음으로 VPA recommender는 학습된 패턴에 기반하여 파드에 대한 새로운 리소스 요구사항을 추천하며, 이 추천 정보는 VPA(VerticalPodAutoscaler)에 저장된다.
VPA admission plug-in과 VPA updater는 VPA의 업데이트된 추천 정보를 수집한다.
Pod가 새로 생성되거나 업데이트될 때, VPA admission plugin은 VPA로부터 업데이트된 추천 사항을 조회하고 Pod의 리소스 요구사항을 업데이트하여 실행시킨다.
실행 중인 Pod의 경우 VPA updater가 VPA recommender의 최신 추천을 반영하기 위해 필요한 경우 파드를 재시작한다. 재시작된 Pod는 VPA admission plugin에 의해 새로운 리소스 요구사항으로 생성된다.
CAS 또는 CA(Cluster Autoscaling)
Cluster Autoscaler는 워크로드에 따라 클러스터의 크기를 동적으로 조정하는 컴포넌트다. 노드를 증설하는 형태이므로 Cloud 환경에서 주로 사용된다.
클러스터 내에 파드가 리로스 부족으로 충분히 스케줄 되지 못하게 되면 워커 노드를 추가하여 클러스터를 확장한다. 반대로, 클러스터에 여유가 있는 경우 사용되지 않는 노드를 삭제한다.
CA는 주로 아래 세 가지 상황에서 동작한다.
- 어떤 파드도 스케쥴되지 못하고 있을 때: 이 경우, CA는 충분한 자원을 가진 노드를 추가하여 이 파드가 스케줄 될 수 있게
- 여러 노드가 충분히 활용되지 않고 있을 때: 이 경우, CA는 이 노드들의 파드를 다른 노드로 옮기고, 이 노드들을 삭제함
- 노드 그룹이 최소 또는 최대 크기 제한에 도달했을 때: 이 경우, CA는 노드 그룹의 크기가 이 제한을 넘지 않도록 함
EKS에서 동작하는 CA의 메커니즘은 다음과 같다.
- Cluster Autoscale 동작을 하기 위한 cluster-autoscaler 파드(디플로이먼트)를 배치한다.
- Cluster Autoscaler(CA)는 리소스 부족으로 인해 pending 상태인 파드가 존재할 경우, 워커 노드를 스케일 아웃한다.
- 특정 시간을 간격으로 사용률을 확인하여 스케일 인/아웃을 수행한다.(AWS에서는 Auto Scaling Group(ASG)을 사용하여 Cluster Autoscaler를 적용한다.)
참고 - 클라우드 네이티브를 위한 쿠버네티스 실전 프로젝트 - 아이자와 고지
Cluster Autoscaler사용 시 주의해야 할 점은, 실제 부하가 작지만 Pod의 request값이 큰 경우 스케일링이 일어난다는 것과, request 값은 낮지만 limit 설정이 없거나 limit 설정이 아주 높은 경우 노드에 오버 커밋 상태가 발생할 수 있다는 것이다. 그러므로 request와 limit 설정이 중요하다.
기본설정
지난 세미나때와 마찬가지로 리소스를 확인하기 위해 그라파나와 kubeopsview를 설치했고, 노드의 리소스 사용량을 터미널에서 확인할 수 있는 eks-node-viewer라는 툴을 설치했다. eks-node-viewer는 노드별 할당 가능한 리소스와 요청 request에 대한 리소스를 간편하게 확인할 수 있다.
사용한 Grafana dashboard는 다음과 같다.
Kubernetes / Views / Global - 15757 | 링크
1 Kubernetes All-in-one Cluster Monitoring KR - 17900 | 링크
Node Exporter for Prometheus Dashboard based on 11074 - 15172 | 링크
Kubernetes / Horizontal Pod Autoscaler - 17125 | 링크
kubeopsview
다음과 같이 설치한다.
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 env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"
EKS Node viewer
다음과 같이 설치한다.
# go 설치
yum install -y go
# EKS Node Viewer 설치 : 현재 ec2 spec에서는 설치에 다소 시간이 소요됨 = 2분 이상
go install github.com/awslabs/eks-node-viewer/cmd/eks-node-viewer@latest
# bin 확인 및 사용
tree ~/go/bin
cd ~/go/bin
./eks-node-viewer
# 옵션 설정을 통해 다양한 매트릭을 확인할 수 있음
./eks-node-viewer --resources cpu,memory
HPA(Horizontal Pod Autoscaler)
실습 - HPA
HPA의 동작을 확인하기 위해 부하를 발생시킬 수 있는 php 페이지를 배포하는 웹애플리케이션을 구동시킨다.
# Run and expose php-apache server
curl -s -O https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/php-apache.yaml
cat php-apache.yaml | yh
kubectl apply -f php-apache.yaml
# 확인
kubectl exec -it deploy/php-apache -- cat /var/www/html/index.php
...
# 모니터링 : 터미널2개 사용
watch -d 'kubectl get hpa,pod;echo;kubectl top pod;echo;kubectl top node'
kubectl exec -it deploy/php-apache -- top
# 접속
PODIP=$(kubectl get pod -l run=php-apache -o jsonpath={.items[0].status.podIP})
curl -s $PODIP; echo
정상적으로 Pod가 배포되면 다음과 같이 새로 생성된 Pod를 확인할 수 있다.
Pod의 소스코드를 확인해 보면 CPU 부하를 발생시키는 상황을 연출하기 위한 코드가 들어있다.
다음으로는 CPU의 사용량이 특정 수치에 넘어갈 경우 정의된 범위 안에서 Pod를 오토 스케일링 할 수 있도록 설정한다.
# Deployment의 Pod의 요청량이 200m 일 때 50%넘으면 1대에서 10대 사이로 오토스케일링 하도록 설정
kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
kubectl get hpa php-apache -o yaml | kubectl neat | yh
HPA 정책 내용을 확인해 보면 이전에 세팅한 대로 maxReplicas와 averageUtilization 값이 잘 설정되어 있음을 확인할 수 있다.
지속적으로 부하를 발생시키는 페이지를 호출하면서 클러스터의 리소스 사용량을 확인해 보았다.
# 지속적으로 웹페이지 호출
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" ;
가장 먼저 눈에 띈 건 node에 배포된 Deployment의 cpu가 67%의 사용율을 보인 것이다.
대시보드 상에서도 cpu 사용량이 급증하는 것이 확인되었다.
그로 인해 kubeopsview에서 pod가 추가적으로 생성되었다.
이런 식으로 점진적으로 Pod의 Replicas가 계속 생성되다가, Autoscaling으로 묶인 Pod들의 평균 CPU 사용률이 50% 미만 이면 더 이상 증설되지 않는다.
실행 중인 프로세스를 중지하고 부하발생 상황을 해소하면, 그림과 같이 CPU 사용률이 0%(50% 이하)가 된다.
5분 정도 지나면 Replicas 가 1로 줄어든다. (편안..)
이제 실습 자원을 삭제한다.
kubectl delete deploy,svc,hpa,pod --all
KEDA(Kubernetes based Event Driven Autoscaler)
기본적인 HPA는 CPU, Metric 등에 따라 스케일링을 지원한다. KEDA는 여러 가지 오픈소스의 이벤트들을 기반으로 오토스케일링을 할 수 있다.
KEDA의 핵심적인 역할을 하는 것은 scaler로써 다양한 소프트웨어의 이벤트를 감지할 수 있으며 이를 통해 스케일 조정이 가능하도록 한다.
KEDA 홈페이지에 다양한 스케일러들이 소개되어 있다. - 링크
실습 - KEDA를 이용하여 Cron Scaler를 이용하여 오토스케일링을 테스트
다음과 같이 KEDA를 설치하고, Cron Scaler를 통한 KEDA 동작을 테스트한다.
# 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: 8080
serviceMonitor:
# Enables ServiceMonitor creation for the Prometheus webhooks
enabled: true
EOT
kubectl create namespace keda
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda --version 2.10.2 --namespace keda -f keda-values.yaml
# KEDA 설치 확인
kubectl get-all -n keda
kubectl get all -n keda
kubectl get crd | grep keda
# keda 네임스페이스에 디플로이먼트 생성
kubectl apply -f php-apache.yaml -n keda
kubectl get pod -n keda
# ScaledObject 정책 생성 : cron
cat <<EOT > keda-cron.yaml
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"
EOT
kubectl apply -f keda-cron.yaml -n keda
# 그라파나 대시보드 추가
# 모니터링
watch -d 'kubectl get ScaledObject,hpa,pod -n keda'
kubectl get ScaledObject -w
# 확인
kubectl get ScaledObject,hpa,pod -n keda
kubectl get hpa -o jsonpath={.items[0].spec} -n keda | jq
...
"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"
}
설치를 통해 특정 시간대 별로 Pod를 실행시키고 종료시키는 Scaler가 동작하기 시작한다.
설정된 시간대로 Pod가 생성 삭제 되는 것을 확인할 수 있다. - 대시보드 | 리소스
kubectl delete -f keda-cron.yaml -n keda && kubectl delete deploy php-apache -n keda && helm uninstall keda -n keda
kubectl delete namespace keda
특정시간대에 트래픽이 많이 발생하거나 기타 스케줄링이 필요한 경우 활용할 수 있을 것으로 보인다.
VPA(Vertical Pod Autoscaler)
VPA는 pod resouces.request를 최적값으로 수정한다. 주의할 것은 HPA와 같이 사용이 불가능하고, 수정할 때는 위에서 설명했던 것처럼 Pod가 종료된다는 것이다.
실습 - VPA
VPA는 kube-controller에 포함된 기능이 아니므로, 다음과 같이 스크립트를 통해 클러스터에 직접 설치해주어야 한다.
VPA를 사용하기 위해서는 Openssl 1.1.1 이상의 버전이 설치되어야 한다. 그래서 현재 클러스터의 OpenSSL 버전을 확인하고 1.1.1 이상으로 설치한다.
# openssl 버전 확인
openssl version
# 스크립트파일내에 openssl11 수정
sed -i 's/openssl/openssl11/g' ~/autoscaler/vertical-pod-autoscaler/pkg/admission-controller/gencerts.sh
# Deploy the Vertical Pod Autoscaler to your cluster with the following command.
watch -d 'kubectl get pod -n kube-system | grep vpa'
cat hack/vpa-up.sh
./hack/vpa-up.sh
kubectl get crd | grep autoscaling
다음과 같이 VPA 관련 3개의 Pod(by deployment)가 생성된 것을 확인할 수 있다.
이제 Pod 두대를 배포한다.
이제 곧 있으면 Pod들이 업데이트된다. VPA recommender가 스펙을 권고한다.
잠시 후 기존의 Pod가 삭제되고 권고된 스펙의 새로운 Pod가 생성된다.
Cluster Autoscaler
실습 - CA
CA 사용 시 EKS 노드에 태그가 포함되어 있어야 동작하기 때문에 주의해야 한다.
우선 EKS에 설정된 ASG(Autoscaling) 정보를 확인해 보면 초기에 최대, 최소, 원하는 개수가 3대로 설정되어 있다.
aws autoscaling describe-auto-scaling-groups \
--query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" \
--output table
실습을 위해 MAX Size를 6개로 수정한다.
# 수정
export ASG_NAME=$(aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].AutoScalingGroupName" --output text)
aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 3 --desired-capacity 3 --max-size 6
# 확인
aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" --output table
다음으로 CA를 배포한다.
curl 우선 -s -O https://raw.githubusercontent.com/kubernetes/autoscaler/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-autodiscover.yaml
sed -i "s/<YOUR CLUSTER NAME>/$CLUSTER_NAME/g" cluster-autoscaler-autodiscover.yaml
kubectl apply -f cluster-autoscaler-autodiscover.yaml
다음으로 500m의 CPU를 요청하는 Pod 15대를 요청한다.
cat <<EoF> nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-to-scaleout
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
service: nginx
app: nginx
spec:
containers:
- image: nginx
name: nginx-to-scaleout
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 500m
memory: 512Mi
EoF
kubectl apply -f nginx.yaml
kubectl get deployment/nginx-to-scaleout
# Scale our ReplicaSet
# Let’s scale out the replicaset to 15
kubectl scale --replicas=15 deployment/nginx-to-scaleout && date
배포하는 Nginx Pod의 request CPU는 500m다. Pod에 배포할 수 있는 리소스가 500m * 15를 만족해야 하는데, 현재 클러스터 구성 상 해당 스펙을 감당할 수 없다. 그렇기 때문에 클러스터의 Worker Node를 늘리게 된다.
결과적으로 다음과 같이 새로운 인스턴스들이 추가되면서 NotReady → Ready 상태로 변경되는 것을 확인할 수 있다.
ASG를 확인해 보면 다음과 같이 Desired 가 5로 변경된 것을 확인할 수 있다.
이제 Pod들을 삭제한다.
kubectl delete -f nginx.yaml && date
10분 정도 지나게 되면 다음과 같이 증설된 노드들이 종료된다.
CPA(Cluster Proportional Autoscaler)
CPA는 노드 수 증가에 비례하여 성능 처리가 필요한 애플리케이션(컨테이너/파드)을 수평으로 자동 확장하기 위해 사용하는 Autoscaler다.
예를 들어 CoreDNS와 같은 Pod가 기본적으로 2개 정도 생성되어 있는 상황이라고 가정하면, Worker Node 개수가 엄청나게 증가하면 CoreDNS 호출에 대한 부하가 발생하게 된다.
이럴 경우 Worker Node 수가 확장됨에 따라 CoreDNS Pod 역시 수평으로 자동 확장 되어야 한다.
이러한 요구사항을 해결하기 위한 것이 CPA다.
실습 - CPA를 이용한 Autoscaling 테스트
우선 helm charts로 CPA를 설치한다.
helm repo add cluster-proportional-autoscaler https://kubernetes-sigs.github.io/cluster-proportional-autoscaler
# CPA규칙을 설정하고 helm차트를 릴리즈 필요
helm upgrade --install cluster-proportional-autoscaler cluster-proportional-autoscaler/cluster-proportional-autoscaler
짜잔.. 설치하게 되면 다음과 같이 cluster-proportinal-autoscaler가 존재하지 않는다는 에러를 발생시킨다. CPA를 사용하기 위해서는 Deployment를 먼저 만들어서 CPA 규칙을 설정하고 Helm Chart를 release 해야 한다.
# nginx 디플로이먼트 배포
cat <<EOT > cpa-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
resources:
limits:
cpu: "100m"
memory: "64Mi"
requests:
cpu: "100m"
memory: "64Mi"
ports:
- containerPort: 80
EOT
kubectl apply -f cpa-nginx.yaml
# CPA 규칙 설정
cat <<EOF > cpa-values.yaml
config:
ladder:
nodesToReplicas:
- [1, 1]
- [2, 2]
- [3, 3]
- [4, 3]
- [5, 5]
options:
namespace: default
target: "deployment/nginx-deployment"
EOF
다음과 같이 디플로이먼트를 배포하게 되면 이제 CPA 설치가 가능해진다.
다음으로는 노드를 5개로 증가시킨다.
export ASG_NAME=$(aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].AutoScalingGroupName" --output text)
aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 5 --desired-capacity 5 --max-size 5
aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" --output table
Worker Node를 추가시키면 다음과 같이 Pod가 Worker Node 수만큼 증가하는 것을 확인할 수 있다.
다음으로 Worker Node를 4개로 축소해 보았다.
aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${ASG_NAME} --min-size 4 --desired-capacity 4 --max-size 4
aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[? Tags[? (Key=='eks:cluster-name') && Value=='myeks']].[AutoScalingGroupName, MinSize, MaxSize,DesiredCapacity]" --output table
다음과 같이 Node 개수를 변경시키면 CPA 규칙에 따라서 3개의 Pod가 존재하게 된다.
실습 - Karpenter
Karpenter는 Kubernetes 클러스터에서 자동으로 스케줄링되는 컨테이너 팟(Pod) 리소스를 관리하기 위한 오픈 소스 프로젝트로써, 대규모 컨테이너 오케스트레이션 시스템으로, 애플리케이션을 여러 컨테이너로 분할하고 이를 클러스터에 배포하고 관리할 수 있도록 도와준다.
참고 - 링크
이번 실습은 bastion에서 직접 EKS 클러스터를 만드는 형태로 진행되었다. 다음과 같이 최초 실습 환경을 구축했다.
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/karpenter-preconfig.yaml
# CloudFormation 스택 배포
예시) aws cloudformation deploy --template-file karpenter-preconfig.yaml --stack-name myeks2 --parameter-overrides KeyName='~~' SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 MyIamUserAccessKeyID=... MyIamUserSecretAccessKey='...' ClusterBaseName=myeks2 --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks2 --query 'Stacks[*].Outputs[0].OutputValue' --output text
# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/~~.pem ec2-user@$(aws cloudformation describe-stacks --stack-na
# 네트워크 대역 확인
# IP 주소 확인 : 172.30.0.0/16 VPC 대역에서 172.30.1.0/24 대역을 사용 중
ip -br -c addr
172.30.x.x 대역의 Public VPC 하나만 있다. 예외 상황을 최소화하여 공식 도큐먼트의 실습 환경과 흡사하게 사용하기 위함이다.
다음으로 클러스터를 생성 시킨다. 카펜터 생성시 Autodiscovery를 위한 karpenter.sh/discovery 태그를 확인하게 된다.
# 환경변수 정보 확인
export | egrep 'ACCOUNT|AWS_|CLUSTER' | egrep -v 'SECRET|KEY'
# 환경변수 설정
export KARPENTER_VERSION=v0.27.5 # 카펜터 버전
export TEMPOUT=$(mktemp)
echo $KARPENTER_VERSION $CLUSTER_NAME $AWS_DEFAULT_REGION $AWS_ACCOUNT_ID $TEMPOUT
# CloudFormation 스택으로 IAM Policy, Role, EC2 Instance Profile 생성 : 3분 정도 소요
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}"
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
iam:
withAddonPolicies:
externalDNS: true #External DNS를 사용하기 때문에 true로 설정한다.
EOF
설치가 꽤 오래 걸린다. 설치가 끝나면 nodegroup, 클러스터 정보, IAM 정보를 를 확인한다.
# eks 배포 확인
eksctl get cluster
eksctl get nodegroup --cluster $CLUSTER_NAME
eksctl get iamidentitymapping --cluster $CLUSTER_NAME
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
eksctl get addon --cluster $CLUSTER_NAME
다음으로 Kubernetes 클러스터 구성 정보를 확인한다. 확인해 보면 mapRoles에 role/KarpenterNodeRole-myeks2 가 등록 된 것을 확인할 수 있는데, 이것은 Karpenter가 생성한 클러스터에 권한을 주기 위한 용도이다.
kubectl cluster-info
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -n kube-system -owide
kubectl describe cm -n kube-system aws-auth
다음으로는 Karpender를 설치하기 위한 인증 정보를 변수형태로 만든다.
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
다음으로 아래의 스크립트를 통해 karpenter를 설치한다.
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
# 확인
kubectl get-all -n karpenter
kubectl get all -n karpenter
kubectl get cm -n karpenter karpenter-global-settings -o jsonpath={.data} | jq
kubectl get crd | grep karpenter
다음으로 kube-ops-view를 설치한다. 이때 접속의 편의를 위해 ExternalDNS도 함께 설치한다.
# ExternalDNS
MyDomain=devlos.click
echo "export MyDomain=devlos.click" >> /etc/profile
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | 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 env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"
다음으로 프로비저너와 노드템플릿이라는 정책을 만든다.
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"] #스팟 인스턴스 타입으로 생성한다.
limits:
resources:
cpu: 1000
providerRef:
name: default
ttlSecondsAfterEmpty: 30 #데몬셋 외에 아무 Pod가 없을때 자동으로 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
# 확인
kubectl get awsnodetemplates,provisioners
다음으로 Karpenter 정보를 모니터링을 위해 프로메테우스와 그라파나를 설치한다. 여기서 정책에 대한 정보를 확인할 수 있다.
# 미친듯이 빠르게 생성되었다가 종료되는 파드를 연출한다.
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 5
kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
스팟 인스턴스에 Pod가 뜬 것을 확인할 수 있다. Deployment를 삭제하면 30초 정도 있다가 노드가 삭제된다.
kubectl delete deployment inflate
kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
비용에 따라 자동으로 타이트하게 인프라를 사용할 수 있도록 지원한다. 하지만 데몬셋이 설치되는데 1~2분 정도 걸림.
Consolidation을 사용하면 엄청나게 빠른 속도로 적절한 노드를 찾아서 파드가 배포된다. Consolidation는 커다란 컨테이너에 소량의 여러 화물을 혼합해 적재하는 일을 뜻한다.
kubectl delete provisioners default
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: default
spec:
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
#
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
kubectl logs -f -n karpenter -l app.kubernetes.io/name=karpenter -c controller
# 인스턴스 확인
# This changes the total memory request for this deployment to around 12Gi,
# which when adjusted to account for the roughly 600Mi reserved for the kubelet on each node means that this will fit on 2 instances of type m5.large:
kubectl get node -l type=karpenter
kubectl get node --label-columns=eks.amazonaws.com/capacityType,karpenter.sh/capacity-type
kubectl get node --label-columns=node.kubernetes.io/instance-type,topology.kubernetes.io/zone
# Next, scale the number of replicas back down to 5:
kubectl scale deployment inflate --replicas 5
이제 스케일을 줄여서 적절하게 인스턴스로 동작하는지 확인할 수 있다.
# replicas를 12에서 5로 줄인다.
kubectl scale deployment inflate --replicas 5
최적의 환경을 유지한다.
하나로 줄이면 노드를 줄여서 다음과 같이 효율이 좋은 인스턴스로 정리한다.
마지막으로 다음과 같이 실습 리소스를 정리한다.
kubectl delete svc -n monitoring grafana
helm uninstall -n kube-system kube-ops-view
helm uninstall karpenter --namespace karpenter
aws ec2 describe-launch-templates --filters Name=tag:karpenter.k8s.aws/cluster,Values=${CLUSTER_NAME} |
jq -r ".LaunchTemplates[].LaunchTemplateName" |
xargs -I{} aws ec2 delete-launch-template --launch-template-name {}
# 클러스터 삭제
eksctl delete cluster --name "${CLUSTER_NAME}"
aws cloudformation delete-stack --stack-name "Karpenter-${CLUSTER_NAME}"
aws cloudformation delete-stack --stack-name ${CLUSTER_NAME}
마치며
이번 세미나에서는 Kubernetes 환경의 autoscaling 방식에 대해서 배웠다. 애매모호하게 이해하고 있던 메커니즘을 세미나를 통해서 좀 더 명확히 알 수 있게 되어 좋았고, 실제와 비슷한 여러 가지 시나리오를 통해 실습을 해보며 실무에 어떤 식으로 적용할지 많은 고민을 할 수 있었다. 세미나의 실습과정을 사내 팀원들과 공유하며 실제 사업에 적용하여 고객 만족을 높일 수 있는 방법들에 대해 이야기할 수 있게 되니, 기술 스터디를 통해 빠듯한 시간을 보내는 것이 의미 있고 유익하다는 것을 체감할 수 있었다.
'클라우드 컴퓨팅 & NoSQL > [AEWS] Amazon EKS 워크숍 스터디' 카테고리의 다른 글
[7주차] AEWS Amazon EKS 워크숍 스터디 (23.06.04) (0) | 2023.06.09 |
---|---|
[6주차] AEWS Amazon EKS 워크숍 스터디 (23.05.28) (3) | 2023.06.02 |
[4주차] AEWS Amazon EKS 워크숍 스터디 (23.05.14) (0) | 2023.05.20 |
[3주차] AEWS Amazon EKS 워크숍 스터디 (23.05.07) (2) | 2023.05.14 |
[2주차] AEWS Amazon EKS 워크숍 스터디 (23.04.30) (0) | 2023.05.07 |