EKS 클러스터 리소스 부족 문제 해결 가이드

목차

들어가며

Amazon EKS는 클라우드 네이티브 환경에서 컨테이너 오케스트레이션을 담당하는, 애플리케이션 배포의 핵심 인프라입니다. 하지만 클러스터 규모가 커지고 워크로드가 늘면 다양한 리소스 부족 문제를 겪게 됩니다.

"어제까지 잘 돌아가던 서비스가 갑자기 Pod가 생성되지 않는다면?"

이 글에서는 실제 운영 환경에서 가장 자주 마주치는 세 가지 리소스 부족 문제와 해결 방안을 단계별로 살펴보겠습니다.

문제마다 근본 원인, 구체적인 해결책, 사전 예방 방법 순서로 정리했습니다.

1. Pod IP 부족 문제 해결

문제 상황 및 증상

실제 에러 메시지:

코드
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

문제 진단 방법:

bash
# 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 개수가 제한되기 때문입니다.

해결 방안

1.1 WARM_IP_TARGET 설정 조정

먼저 VPC CNI의 핵심 설정값이 무엇을 뜻하는지 보겠습니다.

주요 설정값 설명:

yaml
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"

설정 효과 분석:

1.2 Prefix Delegation 활성화

Prefix Delegation이란?
기존 방식에서는 각 ENI에 개별 IP 주소를 할당했지만, Prefix Delegation은 /28 서브넷 블록(16개 IP)을 한 번에 할당해 IP를 더 효율적으로 쓰는 방식입니다.

효과 비교:

yaml
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"

주의사항:

1.3 Custom Networking 구성

보안이나 IP 범위 분리가 필요한 경우에 씁니다.

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: amazon-vpc-cni
  namespace: kube-system
data:
  aws_vpc_k8s_cni_custom_network_cfg: "true"
yaml
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
  name: pod-subnet-config
spec:
  subnet: subnet-xxxxxxxxx
  securityGroups:
    - sg-xxxxxxxxx

1.4 초기 설계 시 고려사항

2. Resource Limits/Requests 부족 문제

문제 상황 및 증상

실제 에러 메시지:

코드
Warning  FailedScheduling  pod/my-app-xxx  0/3 nodes are available: 
3 Insufficient cpu, 2 Insufficient memory.

문제 진단 방법:

bash
# 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 In-Place Pod Resize (Beta)

Kubernetes 1.33부터는 Pod를 재시작하지 않고도 리소스를 조정할 수 있습니다.

yaml
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

리소스 조정 명령:

bash
kubectl patch pod example-pod --patch='
{
  "spec": {
    "containers": [{
      "name": "app",
      "resources": {
        "requests": {"cpu": "200m", "memory": "256Mi"},
        "limits": {"cpu": "400m", "memory": "512Mi"}
      }
    }]
  }
}'

1.33 이전 버전에서의 해결방안

2.1 Kyverno를 활용한 Right-sizing

Kyverno란?
Kyverno는 Kubernetes 네이티브 정책 엔진으로, YAML로 정책을 정의하여 클러스터 리소스를 관리할 수 있는 도구입니다. 복잡한 스크립트 언어 대신 친숙한 Kubernetes 문법을 사용하여 정책을 작성할 수 있습니다.

Kyverno의 주요 기능:

Right-sizing이 필요한 이유:
많은 개발팀이 리소스 요청값을 과도하게 설정하거나 아예 설정하지 않아 클러스터 자원 낭비나 성능 문제가 발생합니다. Kyverno를 쓰면 이를 정책으로 관리할 수 있습니다.

리소스 요구사항 검증 정책:

yaml
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: "?*"

자동 리소스 할당 정책:

yaml
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"

과도한 리소스 요청 제한:

yaml
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"

2.2 Vertical Pod Autoscaler (VPA) 활용

VPA 설치 및 구성:

bash
# VPA 설치
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler/
./hack/vpa-install.sh

VPA 정책 설정:

yaml
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 적용 전후 비교 시나리오:

적용 전 상황:

yaml
# 개발자가 임의로 설정한 리소스 (과도하게 할당)
resources:
  requests:
    cpu: "1000m"      # 실제 사용량: 200m
    memory: "2Gi"     # 실제 사용량: 512Mi
  limits:
    cpu: "2000m"
    memory: "4Gi"

VPA 분석 결과 확인:

bash
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

적용 후 결과:

yaml
# VPA가 자동 조정한 리소스 (효율적 할당)
resources:
  requests:
    cpu: "250m"       # 75% 절약
    memory: "512Mi"   # 75% 절약
  limits:
    cpu: "400m"       # 80% 절약
    memory: "1Gi"     # 75% 절약

효과 분석:

3. Spot Instance 용량 부족 문제

문제 상황 및 증상

실제 에러 메시지:

코드
# 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

문제 진단 방법:

bash
# 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

주요 문제 상황:

해결 방안

3.1 다양한 인스턴스 타입 사용

전략 설명:
Spot Instance의 가용성을 높이기 위해서는 다양한 인스턴스 패밀리와 크기를 조합해야 합니다. 한 가지 타입에 의존하면 해당 타입의 용량이 부족할 때 전체 시스템이 영향을 받습니다.

Karpenter 설정 (권장):

yaml
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 설정:

yaml
# ASG 사용 시 Launch Template 설정
apiVersion: v1
kind: ConfigMap
metadata:
  name: cluster-autoscaler-status
  namespace: kube-system
data:
  nodes.max: "100"
  # 다양한 인스턴스 타입 지원을 위한 MixedInstancesPolicy 활용

3.2 Spot Instance 중단 대응 전략

Spot Instance 중단 신호 처리:

yaml
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 설정:

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: my-app-pdb
spec:
  minAvailable: 2  # 최소 2개 Pod는 항상 유지
  selector:
    matchLabels:
      app: my-app

3.3 Mixed Instance Policy (ASG 사용 시)

json
{
  "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
      }
    ]
  }
}

3.4 실시간 모니터링 및 알림 시스템

CloudWatch 기반 Spot Instance 모니터링:

yaml
# 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 모니터링:

yaml
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 알림 통합:

yaml
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 }}

실전 운영 가이드 및 모범 사례

1. 사전 예방적 모니터링 체계

핵심 메트릭 모니터링:

bash
# 일일 점검 스크립트 예시
#!/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

예측적 용량 계획:

2. 비용 최적화 전략

Spot/On-Demand 비율 최적화:

yaml
# 권장 비율: 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 자동화:

bash
# 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

3. 고가용성 및 장애 복구

Multi-AZ 배포 전략:

yaml
# 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

자동 장애 복구 메커니즘:

yaml
# 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 클러스터를 운영하다 보면 리소스 부족 문제는 피하기 어렵습니다. 하지만 이 글에서 살펴본 것처럼, 각 문제의 근본 원인을 이해하고 적절한 해결책을 적용한다면 충분히 극복할 수 있습니다.

핵심 포인트 요약:

  1. Pod IP 부족은 VPC CNI 설정 최적화와 Prefix Delegation으로 해결
  2. 리소스 부족은 Kubernetes 1.33의 새 기능과 Kyverno/VPA를 활용한 관리로 대응
  3. Spot Instance 이슈는 다양화 전략과 적극적인 모니터링으로 안정성 확보

운영 철학:

특히 Kubernetes 1.33의 in-place pod resize 기능은 운영 방식을 크게 바꿀 것입니다. Pod 재시작 없이 리소스를 조정할 수 있으면 제로 다운타임 운영에 한층 가까워집니다.

체크리스트: 리소스 부족 문제 해결 로드맵

즉시 실행할 수 있는 작업

1단계: 현재 상태 진단 (30분)

2단계: 긴급 대응 (1시간)

3단계: 중장기 개선 (1주일)

4단계: 운영 체계 구축 (1개월)

성공 지표

기술적 지표:

비즈니스 지표:

지속적인 모니터링과 용량 계획으로 안정적이고 비용 효율적인 EKS 클러스터를 운영하는 데 이 가이드가 도움이 되길 바랍니다.


참고 자료:

도식

100%

끌어서 이동, 두 손가락이나 휠로 확대·축소. 키보드: 화살표 이동, + − 확대·축소, 0 맞춤, Esc 닫기. 같은 내용은 그림 아래 “그림 관계를 글로 읽기”에 있다.