CloudNet@팀의 EKS 스터디 AEWS 3기에 작성된 자료를 토대로 작성합니다.
이번 포스팅에서는 eviction에 대해서 알아보도록 하겠습니다.
✅ CloudFormation으로 EKS 설치
eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium \
--node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.31 --ssh-access --external-dns-access --verbose 4
✅ Eviction이란?
목표:
Eviction의 종류에도 Kubelet이 주도적으로 수행하는 Node pressure Eviction에 대해서 자세히 알아보도록 하자.
✅ Node Pressure Eviction
- kubelet이 능동적으로 파드를 중단시키는 절차이다.
- memory, disk , PID 부족(pressure)에 의해서 발생한다. (압박조건)
- kubelet은 이전에 설정된 PodDisruptionBudget 값이나 파드의 terminationGracePeriodSeconds 값을 따르지 않는다
- 소프트 축출 임계값과 하드 축출 임계값 설정이 있다.
✅ Node Pressure Eviction 어떤 파드부터 제거 시킬까?
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo $N1, $N2
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no ec2-user@$N1 hostname
ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no ec2-user@$N2 hostname
##### 디스크 사용률을 발생시키는 30GB dd 테스트
dd if=/dev/zero of=/large_files_1 bs=1G count=30 oflag=direct &
[1] 13032
[root@ip-192-168-1-134 kubelet]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 1.9G 980K 1.9G 1% /run
tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
/dev/nvme0n1p1 30G 30G 833M 98% / ***********evcition Hard nodefs 10% >
"evictionHard": {
"memory.available": "100Mi",
"nodefs.available": "10%",
"nodefs.inodesFree": "5%"
}
(Ted@myeks:N/A) [root@myeks-host ~]# for i in $N1 $N2; do echo ">> node $i <<"; ssh ec2-user@$i sudo systemctl status kubelet; echo; done
>> node 192.168.1.134 <<
● kubelet.service - Kubernetes Kubelet
Loaded: loaded (/etc/systemd/system/kubelet.service; enabled; vendor preset: disabled)
Drop-In: /etc/systemd/system/kubelet.service.d
└─10-kubelet-args.conf, 30-kubelet-extra-args.conf
Active: active (running) since 화 2025-02-04 12:26:08 UTC; 28min ago
Docs: https://github.com/kubernetes/kubernetes
Process: 2907 ExecStartPre=/sbin/iptables -P FORWARD ACCEPT -w 5 (code=exited, status=0/SUCCESS)
Main PID: 2916 (kubelet)
Tasks: 13
Memory: 96.9M
CGroup: /runtime.slice/kubelet.service
└─2916 /usr/bin/kubelet --config /etc/kubernetes/kubelet/kubelet-config.json --kubeconfig /var/lib/kubelet/kubeconfig --container-runtime-endpoint unix:///run/containerd/containerd.sock --image-credential-provider-config /etc/eks/image-credential-provider/config.json --image-credential-provider-bin-dir /etc/eks/image-credential-provider --node-ip=192.168.1.134 --pod-infra-container-image=602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/eks/pause:3.5 --v=2 --hostname-override=ip-192-168-1-134.ap-northeast-2.compute.internal --cloud-provider=external --node-labels=eks.amazonaws.com/sourceLaunchTemplateVersion=1,alpha.eksctl.io/cluster-name=myeks,alpha.eksctl.io/nodegroup-name=myeks-nodegroup,eks.amazonaws.com/nodegroup-image=ami-09dfa8dbb0051bd89,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=myeks-nodegroup,eks.amazonaws.com/sourceLaunchTemplateId=lt-06cea5a86c32e6889 --max-pods=17
2월 04 12:54:45 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: E0204 12:54:45.188232 2916 eviction_manager.go:598] "Eviction manager: cannot evict a critical pod" pod="kube-system/aws-node-g5mn7"
2월 04 12:54:45 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: I0204 12:54:45.188247 2916 eviction_manager.go:427] "Eviction manager: unable to evict any pods from the node"
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: I0204 12:54:55.198106 2916 eviction_manager.go:369] "Eviction manager: attempting to reclaim" resourceName="ephemeral-storage"
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: I0204 12:54:55.198146 2916 container_gc.go:88] "Attempting to delete unused containers"
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: I0204 12:54:55.201007 2916 image_gc_manager.go:431] "Attempting to delete unused images"
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: I0204 12:54:55.212951 2916 eviction_manager.go:380] "Eviction manager: must evict pod(s) to reclaim" resourceName="ephemeral-storage"
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: I0204 12:54:55.213010 2916 eviction_manager.go:398] "Eviction manager: pods ranked for eviction" pods=["kube-system/kube-proxy-b65l8","kube-system/aws-node-g5mn7"]
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: E0204 12:54:55.213048 2916 eviction_manager.go:598] "Eviction manager: cannot evict a critical pod" pod="kube-system/kube-proxy-b65l8"
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: E0204 12:54:55.213067 2916 eviction_manager.go:598] "Eviction manager: cannot evict a critical pod" pod="kube-system/aws-node-g5mn7"
2월 04 12:54:55 ip-192-168-1-134.ap-northeast-2.compute.internal kubelet[2916]: I0204 12:54:55.213083 2916 eviction_manager.go:427] "Eviction manager: unable to evict any pods from the node"
- 가장 먼저 eviction은 안쓰는 container와 images 등을 삭제합니다. 하지만 그 행위로도 디스크 확보를 못했을 경우 다음 step으로 넘어갑니다.
- kubelet은 이전에 설정된 pods rankied for eviction 을 통해 가장 많이 리소스를 사용하고 있는 파드를 eviction 합니다.
-
- 파드의 자원 사용량이 요청량을 초과했는지 여부
- 파드 우선순위 : Best Effort ( request, limit 설정 안된 파드)가 제일 먼저 -> Burstable ( request는 있지만 limits 없음) -> Guaranteed ( 모두 설정된 파드)
- 파드의 자원 요청량 대비 자원 사용량
-
목표:
- 그런데 왜 ? 여기서 kube-proxy와 aws-node는 eviction 되지 않았을까요?
- aws-node는 데몬셋으로 배포 되며 특정 노드에 하나씩 존재해야 합니다.
- systen-node-critical 우선순위 클래스에 설정 되어있습니다.
(Ted@myeks:N/A) [root@myeks-host ~]# kubectl describe pods -n kube-system aws-node-g5mn7
Name: aws-node-g5mn7
Namespace: kube-system
Priority: 2000001000
Priority Class Name: system-node-critical
Service Account: aws-node
Node: ip-192-168-1-134.ap-northeast-2.compute.internal/192.168.1.134
✅ Node Pressure Eviction 임계값 설정은 어떻게? 어디에?
목표:
- 축출 신호
- 축출 임계값
- 모니터링 간격
✅ 노드 접속 후 kubelet config.json 체크
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo $N1, $N2
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no ec2-user@$N1 hostname
ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no ec2-user@$N2 hostname
ssh ec2-user@$N1
# 접속 후 kubelet config 확인
[root@ip-192-168-1-134 kubelet]# cat /etc/kubernetes/kubelet/kubelet-config.json
{
"kind": "KubeletConfiguration",
"apiVersion": "kubelet.config.k8s.io/v1beta1",
"address": "0.0.0.0",
"authentication": {
"anonymous": {
"enabled": false
},
"webhook": {
"cacheTTL": "2m0s",
"enabled": true
},
"x509": {
"clientCAFile": "/etc/kubernetes/pki/ca.crt"
}
},
"authorization": {
"mode": "Webhook",
"webhook": {
"cacheAuthorizedTTL": "5m0s",
"cacheUnauthorizedTTL": "30s"
}
},
"clusterDomain": "cluster.local",
"hairpinMode": "hairpin-veth",
"readOnlyPort": 0,
"cgroupDriver": "systemd",
"cgroupRoot": "/",
"featureGates": {
"RotateKubeletServerCertificate": true
},
"protectKernelDefaults": true,
"serializeImagePulls": false,
"serverTLSBootstrap": true,
"tlsCipherSuites": [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256"
],
"clusterDNS": [
"10.100.0.10"
],
"evictionHard": {
"memory.available": "100Mi",
"nodefs.available": "10%",
"nodefs.inodesFree": "5%"
},
"kubeReserved": {
"cpu": "70m",
"ephemeral-storage": "1Gi",
"memory": "442Mi"
},
"providerID": "aws:///ap-northeast-2a/i-0aa9e7c50f2f027e6",
"systemReservedCgroup": "/system",
"kubeReservedCgroup": "/runtime"
}
✅ 축출 신호 ( eviction Signal)
- nodefs 와 image fs의 차이점
목표:
nodefs: 노드의 파일 시스템이며, 로컬 디스크 볼륨, emptyDir, 로그 스토리지 등에 사용된다.
imagefs: 컨테이너 런타임이 컨테이너 이미지 및 컨테이너 쓰기 가능 레이어를 저장하는데 사용하는 선택적 파일 시스템이다.
나의 환경에서는 nodefs에 imagefs가 속한다.
- memory 계산 방식
목표:
memory.available 값은 free -m과 같은 도구가 아니라 cgroupfs로부터 도출된다.
이는 free -m이 컨테이너 안에서는 동작하지 않고, 또한 사용자가 node allocatable 기능을 사용하는 경우 자원 부족에 대한 결정은 루트 노드뿐만 아니라 cgroup 계층 구조의 최종 사용자 파드 부분에서도 지역적으로 이루어지기 때문에 중요하다. kubelet이 수행하는 스크립트는 memory.available을 계산하기 위해 수행하는 동일한 단계들을 재현한다.
kubelet은 메모리 압박 상황에서 메모리가 회수 가능하다고 가정하므로, inactive_file(즉, 비활성 LRU 목록의 파일 기반 메모리 바이트 수)을 계산에서 제외한다. 이는 즉 다시말하면, 캐시를 많이 사용하고 있는 IO 부하의 상황에서는 극단적인 상황이 발생할수 있다.
✅ 축출 임계값
- 축출 임계값의 기본값은 다음과 같다. 그렇다면 디스크 사용량 (nodefs.available을 테스트 해보자)
- 우선 어플리케이션을 하나 배포해두자.
# 터미널1 (모니터링)
watch -d 'kubectl get pod,svc'
# 수퍼마리오 디플로이먼트 배포
cat <<EOT > mario.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mario
labels:
app: mario
spec:
replicas: 5
selector:
matchLabels:
app: mario
template:
metadata:
labels:
app: mario
spec:
containers:
- name: mario
image: pengbai/docker-supermario
---
apiVersion: v1
kind: Service
metadata:
name: mario
spec:
selector:
app: mario
ports:
- port: 80
protocol: TCP
targetPort: 8080
type: LoadBalancer
EOT
kubectl apply -f mario.yaml
# 배포 확인 : CLB 배포 확인
kubectl get deploy,svc,ep mario
# 마리오 게임 접속 : CLB 주소로 웹 접속
kubectl get svc mario -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Maria URL = http://"$1 }'
- 다음과 같이 BestEffort로 설정되있는 mario 어플리케이션이 가장 먼저 eviction 되는것을 알수있다.
- 그리고 그 과정 전에 metric -server, coredns , kube-proxy, aws-node등 사용량이 큰 컴포넌트들에 대해 먼저 사용량 기반으로 eviction을 하려고 하지만, 해당 시스템 들은 안전하게 Priority Class가 system-node-critical로 걸려 있어서 시스템이 뭉개지는 불상사를 피했다.
Every 2.0s: kubectl get pod,svc Tue Feb 4 22:54:22 2025
NAME READY STATUS RESTARTS AGE
pod/mario-6d8c76fd8d-4mcql 0/1 Error 0 7m
pod/mario-6d8c76fd8d-6l59m 0/1 ContainerStatusUnknown 1 7m
pod/mario-6d8c76fd8d-hqrzg 1/1 Running 0 48s
pod/mario-6d8c76fd8d-j7958 0/1 Error 0 10m
pod/mario-6d8c76fd8d-ql4rt 1/1 Running 0 41s
pod/mario-6d8c76fd8d-qmcxg 1/1 Running 0 42s
pod/mario-6d8c76fd8d-rzjs4 0/1 Error 0 7m
pod/mario-6d8c76fd8d-vz8wp 0/1 Error 0 7m
pod/mario-6d8c76fd8d-x7rt6 1/1 Running 0 51s
pod/mario-6d8c76fd8d-zsmkk 1/1 Running 0 44s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT
(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/
TCP 94m
service/mario LoadBalancer 10.100.100.99 a19b06d9e29504ef5bc60b5a9ceb026c-1892025131.ap-northeast-2.elb.amazonaws.com 80:3
0843/TCP 10m
- 어쨋든 여러분의 어플리케이션은 지켰다.. ㄷㄷ
✅ 모니터링 간격
- Soft eviction thresholds
- 소프트 축출 임계값은 관리자가 설정하는 유예 시간(필수)과 함께 정의된다. kubelet은 유예 시간이 초과될 때까지 파드를 제거하지 않는다. 유예 시간이 지정되지 않으면 kubelet 시작 시 오류가 반환된다.
- Hard eviction thresholds
- 하드 축출 임계값에는 유예 시간이 없다. 하드 축출 임계값 조건이 충족되면, kubelet은 고갈된 자원을 회수하기 위해 파드를 유예 시간 없이 즉시 종료한다.
'DevOps' 카테고리의 다른 글
[AWS EKS] (3) EKS 스터디 1주차 ( diskio - CPU성능 관계 ) (0) | 2025.02.04 |
---|---|
[AWS EKS] (1) EKS 스터디 1주차 ( EKS 설치 및 사용 ) (0) | 2025.02.04 |
[AWS EKS] EKS IaC with Terraform (2) (0) | 2024.04.23 |
[AWS EKS] EKS IaC with Terraform (1) (0) | 2024.04.23 |
[AWS EKS] EKS CICD (0) | 2024.04.15 |