클라우드 네이티브 환경에서 컨테이너 오케스트레이션을 담당하는 Amazon EKS는 현대적인 애플리케이션 배포의 핵심 인프라입니다. 하지만 클러스터 규모가 커지고 워크로드가 증가하면서 다양한 리소스 부족 문제에 직면하게 됩니다.
"어제까지 잘 돌아가던 서비스가 갑자기 Pod가 생성되지 않는다면?"
실제 운영 환경에서 자주 발생하는 이런 상황들을 해결하기 위해, 이 글에서는 가장 빈번하게 마주치는 세 가지 핵심 리소스 부족 문제와 그 실전 해결방안을 단계별로 살펴보겠습니다.
각 문제별로 근본 원인부터 구체적인 해결책, 그리고 사전 예방 방법까지 실무에서 바로 적용할 수 있는 가이드를 제공합니다.
실제 에러 메시지:
Warning FailedCreatePodSandBox pod/my-app-xxx Failed to create pod sandbox:
rpc error: code = Unknown desc = failed to set up sandbox container:
networkPlugin cni failed to set up pod "my-app-xxx_default" network:
add cmd: failed to assign an IP address to container
문제 진단 방법:
# 1. 노드별 IP 사용량 확인
kubectl get nodes -o custom-columns=NAME:.metadata.name,PODS:.status.capacity.pods,ALLOCATABLE:.status.allocatable.pods
# 2. VPC CNI 로그 확인
kubectl logs -n kube-system -l k8s-app=aws-node --tail=100
# 3. ENI 사용 현황 확인
aws ec2 describe-network-interfaces --filters "Name=description,Values=aws-K8S-*"
EKS 클러스터에서 Pod 수가 증가하면서 IP 주소가 부족해지는 현상이 발생합니다. 이는 VPC CNI의 기본 설정으로 인해 각 노드가 할당받을 수 있는 IP 개수가 제한되기 때문입니다.
먼저 VPC CNI의 핵심 설정값들이 무엇을 의미하는지 알아보겠습니다:
주요 설정값 설명:
warm_ip_target
: 각 노드에서 미리 확보해둘 여유 IP 개수 (기본값: 1)minimum_ip_target
: 노드당 최소 보장 IP 개수 (기본값: ENI당 IP 개수)max_eni
: 노드당 최대 ENI(Elastic Network Interface) 개수warm_eni_target
: 미리 준비해둘 여유 ENI 개수 (기본값: 1)apiVersion: v1
kind: ConfigMap
metadata:
name: amazon-vpc-cni
namespace: kube-system
data:
# Pod 생성 시 IP 부족으로 인한 지연을 방지하기 위해 미리 확보할 IP 개수
warm_ip_target: "10" # 기본값: 1
# 노드당 최소한 보장할 IP 개수 (갑작스런 트래픽 증가 대비)
minimum_ip_target: "5"
# 노드당 최대 ENI 개수 제한 (인스턴스 타입별로 상한 존재)
max_eni: "4"
# 새로운 ENI 할당 시 미리 준비해둘 개수
warm_eni_target: "1"
설정 효과 분석:
warm_ip_target
을 10으로 설정하면 Pod 생성 시 IP 할당 지연이 크게 감소Prefix Delegation이란?
기존 방식에서는 각 ENI에 개별 IP 주소를 할당했지만, Prefix Delegation은 /28
서브넷 블록(16개 IP)을 한 번에 할당하여 IP 효율성을 극대화하는 기술입니다.
효과 비교:
apiVersion: v1
kind: ConfigMap
metadata:
name: amazon-vpc-cni
namespace: kube-system
data:
# Prefix Delegation 기능 활성화
enable_prefix_delegation: "true"
# 미리 할당받을 prefix 개수 (각 prefix = 16개 IP)
warm_prefix_target: "1"
# prefix 내에서 미리 확보할 IP 개수
warm_ip_target: "5"
# prefix가 고갈되기 전 새 prefix를 요청할 임계점
minimum_ip_target: "3"
주의사항:
보안이나 IP 범위 분리가 필요한 경우:
apiVersion: v1
kind: ConfigMap
metadata:
name: amazon-vpc-cni
namespace: kube-system
data:
aws_vpc_k8s_cni_custom_network_cfg: "true"
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
name: pod-subnet-config
spec:
subnet: subnet-xxxxxxxxx
securityGroups:
- sg-xxxxxxxxx
실제 에러 메시지:
Warning FailedScheduling pod/my-app-xxx 0/3 nodes are available:
3 Insufficient cpu, 2 Insufficient memory.
문제 진단 방법:
# 1. 클러스터 전체 리소스 현황
kubectl top nodes
# 2. 특정 노드의 상세 리소스 정보
kubectl describe node <node-name>
# 3. Pending Pod 상세 정보 확인
kubectl describe pod <pod-name>
# 4. 리소스 사용률이 높은 Pod 찾기
kubectl top pods --all-namespaces --sort-by=cpu
kubectl top pods --all-namespaces --sort-by=memory
Pod 수가 증가하면서 노드의 CPU/메모리 리소스가 부족해져 Pod가 Pending 상태로 남는 문제가 발생합니다.
Kubernetes 1.33부터는 Pod를 재시작하지 않고도 리소스를 조정할 수 있습니다:
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
# 1.33에서 추가된 필드
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired
- resourceName: memory
restartPolicy: NotRequired
리소스 조정 명령:
kubectl patch pod example-pod --patch='
{
"spec": {
"containers": [{
"name": "app",
"resources": {
"requests": {"cpu": "200m", "memory": "256Mi"},
"limits": {"cpu": "400m", "memory": "512Mi"}
}
}]
}
}'
Kyverno란?
Kyverno는 Kubernetes 네이티브 정책 엔진으로, YAML로 정책을 정의하여 클러스터 리소스를 관리할 수 있는 도구입니다. 복잡한 스크립트 언어 대신 친숙한 Kubernetes 문법을 사용하여 정책을 작성할 수 있습니다.
Kyverno의 주요 기능:
Right-sizing이 필요한 이유:
많은 개발팀이 리소스 요청값을 과도하게 설정하거나 아예 설정하지 않아 클러스터 자원 낭비나 성능 문제가 발생합니다. Kyverno를 통해 이를 체계적으로 관리할 수 있습니다.
리소스 요구사항 검증 정책:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-pod-resources
spec:
validationFailureAction: enforce
background: true
rules:
- name: validate-resources
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Resource requests and limits are required"
pattern:
spec:
containers:
- name: "*"
resources:
requests:
memory: "?*"
cpu: "?*"
limits:
memory: "?*"
cpu: "?*"
자동 리소스 할당 정책:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-resources
spec:
background: true
rules:
- name: add-default-resources
match:
any:
- resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "*"
resources:
requests:
+(memory): "128Mi"
+(cpu): "100m"
limits:
+(memory): "256Mi"
+(cpu): "200m"
과도한 리소스 요청 제한:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: limit-resources
spec:
validationFailureAction: enforce
rules:
- name: check-cpu-limit
match:
any:
- resources:
kinds:
- Pod
validate:
message: "CPU limit cannot exceed 2 cores"
pattern:
spec:
containers:
- name: "*"
resources:
limits:
cpu: "<=2000m"
VPA 설치 및 구성:
# VPA 설치
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler/
./hack/vpa-install.sh
VPA 정책 설정:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: my-app-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
updatePolicy:
updateMode: "Auto" # Auto: 자동 적용, Off: 권장값만 제공, Initial: 최초 생성시만
resourcePolicy:
containerPolicies:
- containerName: app
maxAllowed:
cpu: 1
memory: 500Mi
minAllowed:
cpu: 100m
memory: 50Mi
# 리소스 조정 시 적용할 배율
controlledResources: ["cpu", "memory"]
VPA 적용 전후 비교 시나리오:
적용 전 상황:
# 개발자가 임의로 설정한 리소스 (과도하게 할당)
resources:
requests:
cpu: "1000m" # 실제 사용량: 200m
memory: "2Gi" # 실제 사용량: 512Mi
limits:
cpu: "2000m"
memory: "4Gi"
VPA 분석 결과 확인:
kubectl describe vpa my-app-vpa
VPA 권장값 예시:
Recommendation:
Container Recommendations:
Container Name: app
Lower Bound:
Cpu: 150m
Memory: 256Mi
Target:
Cpu: 250m # 실제 사용량 기반 권장값
Memory: 512Mi # 실제 사용량 기반 권장값
Upper Bound:
Cpu: 400m
Memory: 1Gi
적용 후 결과:
# VPA가 자동 조정한 리소스 (효율적 할당)
resources:
requests:
cpu: "250m" # 75% 절약
memory: "512Mi" # 75% 절약
limits:
cpu: "400m" # 80% 절약
memory: "1Gi" # 75% 절약
효과 분석:
실제 에러 메시지:
# CloudWatch 이벤트 또는 EC2 콘솔에서 확인 가능
EC2 Auto Scaling group activity:
Status: Failed
StatusReason: We currently do not have sufficient m5.large capacity
in the Availability Zone you requested (us-west-2a).
Our system will be working on provisioning additional capacity.
# Karpenter 사용 시
Warning FailedScheduling pod/my-app-xxx
no nodes available to schedule pod,
insufficient capacity for instance type m5.large in zone us-west-2a
문제 진단 방법:
# 1. Auto Scaling Group 활동 히스토리 확인
aws autoscaling describe-scaling-activities --auto-scaling-group-name <asg-name>
# 2. Spot Instance 가격 및 가용성 확인
aws ec2 describe-spot-price-history --instance-types m5.large --availability-zone us-west-2a
# 3. Karpenter 노드 프로비저닝 로그 확인 (Karpenter 사용 시)
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter
# 4. 클러스터 오토스케일러 로그 확인 (CA 사용 시)
kubectl logs -n kube-system -l app=cluster-autoscaler
주요 문제 상황:
전략 설명:
Spot Instance의 가용성을 높이기 위해서는 다양한 인스턴스 패밀리와 크기를 조합해야 합니다. 한 가지 타입에 의존하면 해당 타입의 용량이 부족할 때 전체 시스템이 영향을 받습니다.
Karpenter 설정 (권장):
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: diversified-spot-nodepool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
# 다양한 인스턴스 패밀리 포함
- key: node.kubernetes.io/instance-type
operator: In
values:
# 범용 인스턴스 (M 시리즈)
- "m5.large"
- "m5.xlarge"
- "m5a.large"
- "m5a.xlarge"
- "m5n.large"
- "m5n.xlarge"
# 컴퓨팅 최적화 (C 시리즈)
- "c5.large"
- "c5.xlarge"
- "c5a.large"
- "c5a.xlarge"
- "c5n.large"
- "c5n.xlarge"
# 메모리 최적화 (R 시리즈)
- "r5.large"
- "r5.xlarge"
# 다중 AZ 지원
- key: topology.kubernetes.io/zone
operator: In
values:
- "us-west-2a"
- "us-west-2b"
- "us-west-2c"
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
name: default
# 중단 정책 설정
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
expireAfter: 2160h # 90일
Auto Scaling Group 설정:
# ASG 사용 시 Launch Template 설정
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-autoscaler-status
namespace: kube-system
data:
nodes.max: "100"
# 다양한 인스턴스 타입 지원을 위한 MixedInstancesPolicy 활용
Spot Instance 중단 신호 처리:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: spot-interrupt-handler
namespace: kube-system
spec:
selector:
matchLabels:
app: spot-interrupt-handler
template:
metadata:
labels:
app: spot-interrupt-handler
spec:
serviceAccount: spot-interrupt-handler
hostNetwork: true
containers:
- name: spot-interrupt-handler
image: amazon/aws-node-termination-handler:v1.19.0
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: SPOT_INTERRUPT_THRESHOLD
value: "120" # 2분 전 미리 알림
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
volumes:
- name: proc
hostPath:
path: /proc
tolerations:
- operator: Exists
Pod Disruption Budget 설정:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
spec:
minAvailable: 2 # 최소 2개 Pod는 항상 유지
selector:
matchLabels:
app: my-app
{
"InstancesDistribution": {
"OnDemandAllocationStrategy": "prioritized",
"OnDemandBaseCapacity": 2,
"OnDemandPercentageAboveBaseCapacity": 20,
"SpotAllocationStrategy": "diversified",
"SpotInstancePools": 4,
"SpotMaxPrice": "0.50"
},
"LaunchTemplate": {
"LaunchTemplateSpecification": {
"LaunchTemplateId": "lt-xxxxxxxxx",
"Version": "$Latest"
},
"Overrides": [
{
"InstanceType": "m5.large",
"WeightedCapacity": 1
},
{
"InstanceType": "m5.xlarge",
"WeightedCapacity": 2
},
{
"InstanceType": "c5.large",
"WeightedCapacity": 1
}
]
}
}
CloudWatch 기반 Spot Instance 모니터링:
# CloudWatch 알람 설정 (AWS CLI)
aws cloudwatch put-metric-alarm \
--alarm-name "SpotInstanceTermination" \
--alarm-description "Spot instance termination detected" \
--metric-name "SpotInstanceTerminating" \
--namespace "AWS/EC2Spot" \
--statistic "Sum" \
--period 300 \
--threshold 1 \
--comparison-operator "GreaterThanOrEqualToThreshold" \
--evaluation-periods 1 \
--alarm-actions "arn:aws:sns:us-west-2:123456789012:spot-alerts"
Prometheus + Grafana 모니터링:
apiVersion: v1
kind: ConfigMap
metadata:
name: spot-monitoring-rules
namespace: monitoring
data:
spot-rules.yml: |
groups:
- name: spot-instances
rules:
# Spot Instance 중단 예측
- alert: SpotInstanceInterruptionWarning
expr: aws_spot_instance_interruption_warning > 0
for: 30s
labels:
severity: warning
annotations:
summary: "Spot instance interruption warning"
description: "Spot instance {{ $labels.instance_id }} will be terminated in 2 minutes"
# 노드 용량 부족
- alert: InsufficientSpotCapacity
expr: increase(kube_node_status_condition{condition="Ready",status="false"}[10m]) > 2
for: 2m
labels:
severity: critical
annotations:
summary: "Multiple spot instances unavailable"
description: "{{ $value }} nodes became unavailable in the last 10 minutes"
# 클러스터 오토스케일러 실패
- alert: ClusterAutoscalerFailure
expr: increase(cluster_autoscaler_failed_scale_ups_total[15m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "Cluster autoscaler failed to scale up"
description: "Autoscaler failed to provision nodes: {{ $labels.reason }}"
Slack 알림 통합:
apiVersion: v1
kind: ConfigMap
metadata:
name: alertmanager-config
data:
alertmanager.yml: |
global:
slack_api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'spot-alerts'
receivers:
- name: 'spot-alerts'
slack_configs:
- channel: '#eks-alerts'
title: 'EKS Spot Instance Alert'
text: |
{{ range .Alerts }}
*Alert:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
*Severity:* {{ .Labels.severity }}
{{ end }}
핵심 메트릭 모니터링:
# 일일 점검 스크립트 예시
#!/bin/bash
echo "=== EKS 클러스터 리소스 현황 체크 ==="
# 1. 노드별 Pod 밀도 확인
echo "1. 노드별 Pod 사용률:"
kubectl get nodes -o custom-columns=NAME:.metadata.name,PODS:.status.capacity.pods,ALLOCATABLE:.status.allocatable.pods | awk 'NR==1 || $2/$3 > 0.8'
# 2. IP 주소 사용률 확인
echo "2. VPC CNI IP 사용 현황:"
kubectl get pods -n kube-system -l k8s-app=aws-node -o jsonpath='{range .items[*]}{.spec.nodeName}{"\t"}{.status.containerStatuses[0].restartCount}{"\n"}{end}'
# 3. 리소스 사용률 Top 10
echo "3. CPU 사용률 상위 Pod:"
kubectl top pods --all-namespaces --sort-by=cpu | head -10
echo "4. 메모리 사용률 상위 Pod:"
kubectl top pods --all-namespaces --sort-by=memory | head -10
# 5. Pending Pod 확인
echo "5. Pending 상태 Pod:"
kubectl get pods --all-namespaces --field-selector=status.phase=Pending
예측적 용량 계획:
Spot/On-Demand 비율 최적화:
# 권장 비율: Spot 70%, On-Demand 30%
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: cost-optimized-nodepool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
# Spot Instance 우선, 부족 시 On-Demand로 대체
nodeClassRef:
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
name: default
# 비용 최적화를 위한 빠른 스케일 다운
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 10s
리소스 Right-sizing 자동화:
# VPA 권장값 적용 스크립트
#!/bin/bash
for deployment in $(kubectl get deployments --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}'); do
namespace=$(echo $deployment | cut -d' ' -f1)
name=$(echo $deployment | cut -d' ' -f2)
# VPA 권장값 확인
recommendation=$(kubectl get vpa ${name}-vpa -n $namespace -o jsonpath='{.status.recommendation.containerRecommendations[0].target}' 2>/dev/null)
if [ ! -z "$recommendation" ]; then
echo "Deployment $namespace/$name 리소스 조정 권장: $recommendation"
fi
done
Multi-AZ 배포 전략:
# Pod Anti-Affinity를 활용한 분산 배치
apiVersion: apps/v1
kind: Deployment
metadata:
name: high-availability-app
spec:
replicas: 6
template:
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- high-availability-app
topologyKey: topology.kubernetes.io/zone
- weight: 50
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- high-availability-app
topologyKey: kubernetes.io/hostname
자동 장애 복구 메커니즘:
# Health Check 및 자동 재시작
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
EKS 클러스터 운영에서 리소스 부족 문제는 피할 수 없는 도전과제입니다. 하지만 이 글에서 살펴본 것처럼, 각 문제의 근본 원인을 이해하고 적절한 해결책을 적용한다면 충분히 극복할 수 있습니다.
핵심 포인트 요약:
운영 철학:
특히 Kubernetes 1.33의 in-place pod resize 기능은 게임 체인저가 될 것입니다. Pod 재시작 없이 리소스를 조정할 수 있다는 것은 제로 다운타임 운영의 새로운 가능성을 열어줍니다.
1단계: 현재 상태 진단 (30분)
2단계: 긴급 대응 (1시간)
3단계: 중장기 개선 (1주일)
4단계: 운영 체계 구축 (1개월)
기술적 지표:
비즈니스 지표:
앞으로도 지속적인 모니터링과 용량 계획을 통해 안정적이고 비용 효율적인 EKS 클러스터를 운영하시길 바라며, 이 가이드가 여러분의 클러스터 운영에 실질적인 도움이 되기를 희망합니다.
궁금한 점이나 추가적인 도움이 필요하시면 언제든 댓글로 남겨주세요! 🚀
📚 참고 자료: