Skip to content

Kubernetes/EKS 트러블슈팅 플레이북: 증상 → 진단 → 원인 → 조치

기존 출력의 기록 버전: Amazon EKS 1.36의 기존 출력 예시 — 컨트롤 플레인 v1.36.2-eks-bca9cf6, 플랫폼 버전 eks.9, Karpenter 1.4, VPC CNI v1.21, CoreDNS v1.14 마지막 검토: 2026년 9월 11일

< 이전: Zonal 클러스터 운영 전략 | 목차 >


새벽 3시에 알림을 받고 터미널을 열었을 때 필요한 것은 개념 설명이 아니라 지금 보이는 증상에서 다음에 칠 명령입니다. 이 문서는 개념이 아니라 증상에서 출발합니다. 각 증상마다 "무엇이 보이는가 → 무엇을 치는가 → 출력이 어떻게 나오는가 → 가장 흔한 원인은 무엇이고 어떻게 고치는가"를 한 덩어리로 묶었습니다.

아래 출력은 기존 문서에 기록된 2026년 9월 2일 환경의 예시와 공식 문서의 메시지를 설명하기 위한 것입니다. 이번 검토에서 해당 클러스터를 다시 실행하거나 원본 수집 로그를 재검증하지 않았습니다. 특히 Karpenter 호환성 표는 Kubernetes 1.36에 Karpenter 1.13 이상을 요구합니다. 기록된 1.4.0 조합을 지원되는 배포 구성으로 재사용하지 않습니다.

깊은 원인 분석(컨트롤 플레인 로그, CloudWatch Logs Insights 쿼리, 노드 조인 실패 8가지 원인 등)은 이미 EKS 문제 해결EKS 고급 디버깅에 있습니다. 이 문서는 그 앞단에서 어느 페이지로 들어가야 하는지를 30초 안에 결정하는 것이 목적이며, 해당 내용을 반복하지 않고 링크합니다.

목차

  1. 30초 요약: 증상 → 첫 명령 → 가장 흔한 원인
  2. 진단 결정 트리
  3. 증상별 플레이북
  4. kubectl 진단 치트시트
  5. 더 깊이 들어가기: 관련 문서
  6. 참고 자료

30초 요약: 증상 → 첫 명령 → 가장 흔한 원인

증상 칸을 클릭하면 아래 해당 플레이북 섹션으로 이동합니다.

증상 (kubectl get pods/nodes에서 보이는 것)첫 명령가장 흔한 원인
Pendingkubectl describe pod <pod> → Events의 FailedScheduling 메시지리소스 부족(Insufficient cpu/memory), toleration 누락, nodeSelector 불일치, PVC 미바인딩
ImagePullBackOff / ErrImagePullkubectl describe pod <pod>Failed to pull image태그 오타, 프라이빗 레지스트리 인증(imagePullSecrets/노드 IAM), ECR 리전·계정 불일치
CrashLoopBackOffkubectl logs <pod> --previous + lastState.terminated 확인앱 시작 실패(exit 1), OOMKilled(exit 137), liveness 프로브 실패, ConfigMap/Secret 누락
Running 인데 READY 0/1kubectl describe pod <pod>Readiness probe failedreadiness 프로브 경로/포트 오류, 의존 서비스 대기, 사이드카 미준비
Service로 요청이 안 감kubectl get endpointslices -l kubernetes.io/service-name=<svc>셀렉터 라벨 불일치, targetPort 오류, NetworkPolicy 차단, CoreDNS 장애
Node NotReadykubectl describe node <node> → Conditionskubelet 중단/네트워크 단절, DiskPressure, MemoryPressure, PIDPressure
PVC Pendingkubectl describe pvc <pvc> → EventsWaitForFirstConsumer(정상 대기), StorageClass 누락/오타, AZ 불일치
앱 로그에 AccessDenied (AWS API)kubectl get sa <sa> -o yaml + 자격 증명 공급자 주입 필드IRSA(IAM Roles for Service Accounts) 어노테이션/신뢰 정책 오류, Pod Identity association 누락, 파드 재시작 안 함
ContainerCreating에서 멈춤 + failed to assign an IP addresskubectl describe pod <pod>FailedCreatePodSandBox서브넷 IP 고갈, 노드 max-pods 도달, aws-node 비정상
Karpenter가 노드를 안 만듦kubectl get events -A --field-selector reason=FailedSchedulingNodePool limits 도달, requirements/taint 불일치, 인스턴스 타입 제한
Service 생성이 failed calling webhook으로 거부됨kubectl -n kube-system get endpointslices -l kubernetes.io/service-name=aws-load-balancer-webhook-service웹훅 Deployment 비정상(CrashLoop)인데 failurePolicy: Fail + 전체 네임스페이스 매치

진단 결정 트리

노드 배정 여부, 이미지·초기화 대기, 반복 종료, Pod Ready 조건, EndpointSlice와 네트워크를 구분하는 진단 결정 트리.

노드 배정 여부, 이미지·초기화 대기, 반복 종료, Pod Ready 조건, EndpointSlice와 네트워크를 구분하는 진단 결정 트리.전체 화면으로 열기 ↗

먼저 컨텍스트와 네임스페이스를 확인합니다. <pod>, <ns> 등은 실제 값으로 치환하는 자리표시자입니다. Pod phase와 컨테이너 상태는 다르므로 Running을 전부 제외하면 CrashLoop나 readiness 실패를 놓칠 수 있습니다. 아래는 완료된 파드를 제외하고 phase 또는 Ready 조건이 비정상인 파드를 조회합니다.

bash
# phase와 Ready 조건을 함께 확인
kubectl get pods -A -o json | jq -r '
  .items[]
  | select(.status.phase != "Succeeded")
  | select(.status.phase != "Running" or
      ([.status.conditions[]? | select(.type == "Ready" and .status == "True")] | length == 0))
  | [.metadata.namespace, .metadata.name, .status.phase,
     ([.status.initContainerStatuses[]?, .status.containerStatuses[]?
       | .state.waiting.reason // empty] | join(","))] | @tsv
'

# 최근 Warning 이벤트 (클러스터 전체, lastTimestamp 기준)
kubectl get events -A --field-selector type=Warning --sort-by=.lastTimestamp | tail -30

증상별 플레이북

1. Pod가 Pending에서 멈춤

증상: Pending은 스케줄 대기뿐 아니라 이미지 다운로드·초기화 대기도 포함합니다. .spec.nodeNamePodScheduled 조건을 먼저 확인합니다. 노드 미배정이면 스케줄러 이벤트를, 이미 배정됐다면 컨테이너 상태·마운트·CNI 이벤트를 확인합니다.

진단: 미스케줄 파드는 FailedScheduling에서 단서를 찾습니다. 이 요약은 실패 이유별 노드 수를 집계하므로 이유별 노드 집합이 서로 겹칠 수 있습니다.

bash
kubectl describe pod <pod> -n <ns> | sed -n '/^Events:/,$p'
Warning  FailedScheduling  default-scheduler  0/15 nodes are available: 1 Insufficient cpu, 1 Insufficient memory,
  6 node(s) didn't match Pod's node affinity/selector, 8 node(s) had untolerated taint(s).
  no new claims to deallocate, preemption: 0/15 nodes are available:
  1 No preemption victims found for incoming pod, 14 Preemption is not helpful for scheduling.

이 출력만으로 CPU와 메모리 부족이 반드시 같은 노드에서 발생했다거나 정확히 한 노드만 다른 제약을 통과했다고 단정하지 않습니다. 스케줄러 집계 코드는 각 노드의 여러 reason을 누적할 수 있습니다. 실제 노드의 라벨·taint·할당량을 대조합니다. DRA 관련 문구는 ResourceClaim 사용 여부와 함께 읽습니다.

원인과 조치:

메시지 조각원인조치
Insufficient cpu / Insufficient memory요청량이 남은 노드 용량보다 큼requests 현실화, 오토스케일러 확인(→ 10. Karpenter), kubectl describe nodeAllocated resources 확인
Too many pods설정된 노드 max-pods 도달; CNI IP 용량은 별도 확인9. ENI/IP 고갈
node(s) had untolerated taint(s)노드 taint에 대한 toleration 없음kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints[*].key로 taint 확인 후 toleration 추가 또는 NodePool 조정
node(s) didn't match Pod's node affinity/selectornodeSelector/affinity 라벨이 어느 노드에도 없음kubectl get nodes --show-labels로 라벨 확인. Karpenter라면 well-known 키인지, NodePool template labels/requirements에서 해당 값을 제공하는지 확인
pod has unbound immediate PersistentVolumeClaimsPVC가 Pending7. PVC Pending
node(s) had volume node affinity conflictPV(EBS)가 있는 AZ에 스케줄 가능한 노드가 없음PV의 nodeAffinity zone 확인 후 해당 AZ에 노드 확보
node(s) didn't match pod topology spread constraints / pod anti-affinity rules분산 제약을 만족하는 노드 없음필요한 노드 추가. topology spread는 가용성 목표를 검토한 후 ScheduleAnyway를 고려하고, pod anti-affinity는 required/preferred 규칙을 별도로 검토
이벤트가 전혀 없음스케줄러 자체 문제, 또는 schedulerName 오타kubectl get pod <pod> -o jsonpath='{.spec.schedulerName}' 확인

2. ImagePullBackOff / ErrImagePull

증상: STATUS가 ErrImagePull로 시작해 몇 번 재시도 후 ImagePullBackOff로 바뀝니다. kubelet의 pull 재시도 백오프는 최대 5분까지 늘어납니다.

진단:

bash
kubectl describe pod <pod> -n <ns> | grep -A2 -E "Failed to pull|Back-off pulling"
kubectl get pod <pod> -n <ns> -o jsonpath='{range .spec.containers[*]}{.name}{"\t"}{.image}{"\n"}{end}'
kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.imagePullSecrets}'
Warning  Failed   kubelet  Failed to pull image "123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/app:v1.2.3": ... not found
Warning  Failed   kubelet  Error: ErrImagePull
Normal   BackOff  kubelet  Back-off pulling image "123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/app:v1.2.3"
Warning  Failed   kubelet  Error: ImagePullBackOff

정상 pull은 Pulling image "..."Successfully pulled image "..." in 4.501s ... 이벤트 쌍으로 남고, 이미 있는 이미지는 Container image "..." already present on machine 으로 찍힙니다. 이 이벤트는 다운로드가 성공했다는 뜻입니다. 이미지 안의 실행 파일·아키텍처·설정 문제까지 배제하지는 않습니다.

원인과 조치:

Failed to pull image 뒤에 붙는 내용원인조치
not found / manifest unknown태그 오타, 아직 push 안 된 태그, 잘못된 리포지토리aws ecr describe-images --repository-name <repo> --image-ids imageTag=<tag>로 존재 확인
401 Unauthorized / no basic auth credentials프라이빗 레지스트리 인증 실패ECR이면 노드 IAM 역할에 AmazonEC2ContainerRegistryPullOnly(또는 ReadOnly), 외부 레지스트리면 imagePullSecrets 확인
다른 리전/계정의 ECR에서 pull 실패주소 차이 자체는 오류가 아님. 크로스 계정 정책·리전 endpoint 접근 확인ECR 리포지토리 정책에 pull 주체 추가
dial tcp ... i/o timeout프라이빗 서브넷에서 NAT/VPC 엔드포인트 없음com.amazonaws.<region>.ecr.api, ecr.dkr, S3 게이트웨이 엔드포인트 확인
toomanyrequestsDocker Hub rate limitECR pull-through cache로 미러링

노드 진단이 필요하면 먼저 kubelet 이벤트·로그와 이미지 pull에 사용되는 자격 증명 공급자를 확인합니다. crictl pull은 kubelet의 ECR credential provider나 Pod의 imagePullSecrets를 자동 재사용하지 않으므로 같은 인증 경로를 재현한다고 가정하지 않습니다. Fargate의 이미지 pull에는 Pod 실행 역할이 쓰이며 애플리케이션의 IRSA 역할과 다릅니다.

3. CrashLoopBackOff (exit 137 OOMKilled, 프로브 실패, 설정 오류)

증상: 컨테이너가 반복 종료하며 RESTARTS가 증가합니다. 일반적인 백오프는 10초부터 두 배씩 증가해 300초에 도달하지만 feature gate와 kubelet 설정에 따라 달라집니다. Pod lifecycle의 restart 정책을 확인합니다.

진단: 세 가지를 순서대로 봅니다 — 종료 이유와 exit code, 이전 컨테이너의 로그, Events.

bash
# (1) 왜 죽었는가: lastState.terminated
kubectl get pod <pod> -n <ns> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}restarts={.restartCount}{"\t"}reason={.lastState.terminated.reason}{"\t"}exit={.lastState.terminated.exitCode}{"\n"}{end}'

# (2) 죽기 직전 로그 (현재 컨테이너가 아니라 이전 컨테이너)
kubectl logs <pod> -n <ns> -c <container> --previous --tail=100

# (3) 프로브/킬 이벤트
kubectl describe pod <pod> -n <ns> | sed -n '/^Events:/,$p'

기존 문서의 출력 예시 — 메모리 limit 128Mi로 기록된 컨테이너:

    Last State:     Terminated
      Reason:       OOMKilled
      Exit Code:    137
      Started:      Mon, 31 Aug 2026 08:55:27 +0000
      Finished:     Tue, 01 Sep 2026 21:13:37 +0000
    Restart Count:  3

약 36시간 실행 후 종료됐다는 사실은 즉시 시작 실패와 구분하는 단서입니다. 이것만으로 메모리 누수를 확정하지 않습니다. 트래픽 급증·배치 작업·노드 OOM·limit 변경 등도 확인하고 메모리 시계열과 커널/cgroup 이벤트를 대조합니다.

exit code 읽는 법:

Exit CodeReason의미조치
0Completed프로세스가 정상 종료 — Deployment라면 앱이 포그라운드로 안 떠 있음장기 서비스는 엔트리포인트를 포그라운드로, 또는 Job으로 전환
1Error앱이 스스로 종료 (설정 오류, 의존 서비스 연결 실패)logs --previous에 스택트레이스가 있음
126Error셸 엔트리포인트에서 커맨드는 찾았지만 실행 불가 — 실행 권한 누락, 또는 셸이 cannot execute binary file: Exec format error를 낸 경우(아키텍처 불일치)Dockerfile에서 chmod +x; kubectl get nodes -L kubernetes.io/arch로 arm64/amd64 확인 후 멀티아치 이미지 사용
127Error셸 엔트리포인트에서 커맨드를 찾을 수 없음 — 경로 오타, 또는 최종 이미지 스테이지에 바이너리가 복사되지 않음command/args와 이미지 안의 실제 파일을 비교 (kubectl debug ... -- ls <path>)
137OOMKilledOOM으로 종료됨. 컨테이너 limit 또는 노드 메모리 압박을 구분limit 상향 또는 누수 수정. JVM은 -XX:MaxRAMPercentage 확인 → 리소스 최적화
137Errorlimit이 아닌 다른 이유의 SIGKILL — liveness 실패 후 terminationGracePeriodSeconds 안에 안 죽음preStop/graceful shutdown 점검
143ErrorSIGTERM을 받고 종료 (정상 롤링/축출 과정일 수 있음)반복되면 누가 죽이는지 Events 확인
  • 셸 없이 바이너리를 직접 실행하는 이미지라면 아키텍처 불일치는 exit 126으로 나오지 않습니다 — 컨테이너가 아예 시작되지 못하고 lastState.terminated에 Reason StartError, 메시지에 exec format error가 찍힙니다. 조치는 같습니다: 멀티아치 이미지, 또는 kubernetes.io/arch nodeSelector.

프로브 실패: 아래 이벤트는 liveness 검사 실패로 재시작했다는 뜻입니다. 경로·포트 오류뿐 아니라 실제 앱 장애, 과부하, 교착도 원인일 수 있습니다. 프로브를 완화하기 전에 응답과 앱 상태를 확인합니다.

Warning  Unhealthy  kubelet  Liveness probe failed: HTTP probe failed with statuscode: 503
Normal   Killing    kubelet  Container app failed liveness probe, will be restarted
  • 앱 기동이 느려서 죽는다면 liveness의 initialDelaySeconds를 늘리는 대신 startupProbe 를 추가합니다(startupProbe가 성공할 때까지 liveness는 시작되지 않음).
  • Readiness probe failed: dial tcp 10.0.2.45:8080: connect: connection refused처럼 TCP 거부라면 컨테이너 포트와 프로브 포트가 다른지 먼저 봅니다.

설정 참조 오류 — 정확히는 CrashLoop이 아니라 CreateContainerConfigError로 멈춥니다:

Warning  Failed  kubelet  Error: configmap "app-config" not found
Warning  Failed  kubelet  Error: secret "db-credentials" not found

kubectl get cm,secret -n <ns>로 이름·네임스페이스를 대조하면 끝납니다. 볼륨 마운트로 참조했다면 FailedMount 이벤트(MountVolume.SetUp failed for volume "cfg" : configmap "app-config" not found)로 나타납니다.

4. Running인데 READY가 아님 / Endpoints가 비어 있음

증상: STATUS는 Running인데 READY가 0/1(사이드카가 있으면 1/2). 일반적인 Service 라우팅에서는 not-ready endpoint가 제외됩니다. publishNotReadyAddresses·종료 중 endpoint·LB fail-open 등 설정은 별도 확인하며 사용자 증상은 프록시에 따라 오류 또는 타임아웃일 수 있습니다.

진단:

bash
kubectl describe pod <pod> -n <ns> | grep -E "Ready|Readiness probe"
kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<svc>

EndpointSlice의 주소 목록과 Ready 상태는 별개입니다. selector에 매칭된 not-ready 파드도 주소와 ready: false로 포함될 수 있습니다. 기본 표의 ENDPOINTS 열만으로 준비 상태를 판단하지 말고 ready, serving, terminating을 확인합니다.

bash
kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<svc> -o json | jq -r '
  .items[] as $slice | $slice.endpoints[]?
  | [$slice.metadata.name, (.addresses | join(",")), (.conditions | tojson)] | @tsv
'

ready 미설정은 API에서 unknown이며 소비자는 ready로 해석합니다. publishNotReadyAddresses: true는 Ready 필터링에 예외를 만들고, 종료 중인 serving endpoint 처리도 프록시에 따라 고려됩니다. selector가 없는 Service는 수동 관리 EndpointSlice 여부를 확인합니다. v1 Endpoints는 Kubernetes 1.33부터 deprecated이므로 신규 진단은 EndpointSlice를 사용합니다.

원인과 조치:

관찰원인조치
Events에 Readiness probe failed 반복프로브 경로/포트 오류, 앱이 아직 의존 서비스(DB 등) 대기 중프로브 대상 URL을 앱의 실제 헬스 엔드포인트로. 의존성 대기는 readiness에 두고 liveness에서는 빼기
Conditions에 Ready False, 사유가 ReadinessGatesNotReadyPod readiness gate 대기 — AWS Load Balancer Controller의 target-health.elbv2.k8s.aws/* 게이트가 대표적Target Group 헬스체크 실패 원인 확인 → AWS Load Balancer Controller
1/2 Running, 앱 컨테이너만 Ready사이드카(istio-proxy 등) 미준비 또는 사이드카가 앱보다 늦게 떠서 초기 연결 실패사이드카 로그 확인, 주입기·사이드카 버전이 지원하는 시작 순서와 readiness 설정 확인. native sidecar 전환만으로 Ready 실패가 해결되지는 않음
Ready인데도 EndpointSlice가 비어 있음Service 셀렉터가 파드 라벨과 불일치5. Service 접근 불가

5. Service에 접근이 안 됨

증상: 컨테이너는 1/1 Running인데 curl http://<svc>.<ns>.svc.cluster.local 이 타임아웃/거절, 또는 이름 풀이 실패.

진단은 세 층으로 나눕니다: (a) Service → 파드 매핑, (b) 네트워크 정책, (c) DNS.

bash
# (a) 셀렉터와 실제 라벨 대조
kubectl get svc <svc> -n <ns> -o jsonpath='{.spec.selector}{"\n"}{.spec.ports}{"\n"}'
kubectl get pods -n <ns> -l <key>=<value> -o wide
kubectl get endpointslices -n <ns> -l kubernetes.io/service-name=<svc>

# (b) 네임스페이스에 걸린 NetworkPolicy
kubectl get networkpolicies -n <ns>
kubectl describe networkpolicy <policy> -n <ns>

# (c) CoreDNS 상태와 로그
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
kubectl get cm -n kube-system coredns -o jsonpath='{.data.Corefile}'

원인과 조치:

관찰원인조치
셀렉터 {"app":"api"} 인데 파드 라벨은 app=api-server라벨 불일치 → EndpointSlice 비어 있음라벨/셀렉터 통일. Helm 차트에서 selectorLabelspodLabels가 갈라진 경우가 흔함
EndpointSlice에 IP는 있는데 connection refusedtargetPort가 컨테이너가 실제로 listen하는 포트와 다름kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.containers[*].ports}'와 대조. 앱이 127.0.0.1에만 바인딩된 경우도 같은 증상
특정 네임스페이스에서만 안 됨default-deny NetworkPolicy가 있고 ingress 허용 규칙 누락podSelector/namespaceSelector 확인. VPC CNI 네트워크 정책은 kubectl get policyendpoints -n <ns>로 실제 적용 상태 확인 → 네트워크 정책
nslookup <svc>NXDOMAIN다른 네임스페이스에서 짧은 이름 사용, 또는 CoreDNS 장애FQDN(<svc>.<ns>.svc.cluster.local) 사용. CoreDNS 파드가 Running인지, /etc/resolv.confnameserver가 kube-dns ClusterIP(이 클러스터는 172.20.0.10)인지 확인
외부 도메인 해석이 느림기본값 ndots:5 때문에 점이 5개 미만인 이름은 search 도메인(<ns>.svc.cluster.local, svc.cluster.local, cluster.local, 노드의 VPC 도메인)을 전부 먼저 시도한 뒤에야 절대 이름으로 질의외부 이름 끝에 .을 붙이거나 dnsConfig.options에 name: ndots, value: "2" 설정을 검토
NodePort/LB는 되는데 일부 노드로만 됨externalTrafficPolicy: Local인데 그 노드에 파드가 없음의도된 동작. 모든 노드 수신이 필요하면 client IP 보존·cross-node 트래픽 영향을 검토해 Cluster 고려

DNS를 파드 관점에서 재현하려면 임시 파드를 하나 띄웁니다: kubectl run -it --rm dns-test --image=busybox:1.36 --restart=Never -- nslookup kubernetes.default.svc.cluster.local. CoreDNS 개념과 Corefile 구성은 서비스와 네트워킹을 참고합니다.

NetworkPolicy는 목적지 ingress와 출발지 egress를 모두 확인합니다. containerPort 선언은 실제 listen 소켓을 만들지 않으므로 앱 로그나 소켓 상태를 확인합니다. NodeLocal DNSCache를 쓰면 resolv.conf의 nameserver가 kube-dns ClusterIP와 달라도 정상일 수 있습니다.

Auto Mode의 DNS 진단: 현재 Auto Mode는 노드 시스템 서비스인 CoreDNS를 사용하므로 순수 Auto Mode에서 kube-dns Pod/Service가 없다는 것만으로 장애라고 판단하지 않습니다. 혼합 클러스터의 일반 노드에는 CoreDNS Deployment가 필요합니다. 위 Pod/ConfigMap 명령은 Deployment 기반 DNS를 확인하는 절차이며, Auto Mode는 파드의 실제 이름 해석과 노드 DNS 로그·상위 resolver 접근을 함께 확인합니다.

6. Node NotReady / kubelet 압박 (DiskPressure, MemoryPressure, PIDPressure)

증상: kubectl get nodesNotReady가 보이거나, 노드는 Ready인데 파드가 Evicted되거나 새 파드가 node(s) had untolerated taint(s)로 그 노드를 피합니다.

진단:

bash
# 노드 컨디션 한 줄 요약
kubectl get nodes -o custom-columns='NAME:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,MEM:.status.conditions[?(@.type=="MemoryPressure")].status,DISK:.status.conditions[?(@.type=="DiskPressure")].status,PID:.status.conditions[?(@.type=="PIDPressure")].status'

# 컨디션의 reason까지
kubectl get node <node> -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" ("}{.reason}{")\n"}{end}'

# 노드가 자동으로 받은 taint
kubectl get node <node> -o jsonpath='{.spec.taints}'

정상 노드 출력 (EKS Node Monitoring Agent가 붙어 있으면 ContainerRuntimeReady/NetworkingReady/KernelReady/StorageReady 컨디션이 추가로 보입니다):

MemoryPressure=False (KubeletHasSufficientMemory)
DiskPressure=False (KubeletHasNoDiskPressure)
PIDPressure=False (KubeletHasSufficientPID)
Ready=True (KubeletReady)
ContainerRuntimeReady=True (ContainerRuntimeIsReady)
NetworkingReady=True (NetworkingIsReady)
KernelReady=True (KernelIsReady)
StorageReady=True (DiskIsReady)

원인과 조치:

컨디션 / reason자동 taint원인조치
Ready=Unknown (NodeStatusUnknown, "Kubelet stopped posting node status.")node.kubernetes.io/unreachablekubelet 프로세스 중단, 인스턴스 정지/네트워크 단절, API 서버 인증 실패EC2 인스턴스 상태 확인 → 노드가 응답하면 지원되는 로그 접근 방식 사용. kubelet이 죽으면 debug Pod도 실행되지 않을 수 있음
Ready=Falsenode.kubernetes.io/not-ready컨테이너 런타임 다운, CNI 미초기화(aws-node 비정상)kubectl get pods -n kube-system -l k8s-app=aws-node -o wide로 해당 노드의 aws-node 확인
DiskPressure=True (KubeletHasDiskPressure)node.kubernetes.io/disk-pressure이미지 캐시/컨테이너 로그가 루트 볼륨을 채움crictl rmi --prune, 로그 로테이션, 루트 EBS 확대. 파드는 The node was low on resource: ephemeral-storage 메시지로 Evicted
MemoryPressure=True (KubeletHasInsufficientMemory)node.kubernetes.io/memory-pressure실제 사용량이 예약을 초과하거나 시스템 예약 부족. limit만 지정하면 request도 기본 지정될 수 있음requests 설정 강제(LimitRange), kube-reserved/system-reserved 확인
PIDPressure=True (KubeletHasInsufficientPID)node.kubernetes.io/pid-pressure포크 폭주(스레드 누수)해당 파드 찾아 재시작, podPidsLimit 설정

아래는 호스트에 셸·journalctl·crictl이 설치된 일반 Linux 워커용 예제이며 privileged 디버그 Pod를 만듭니다. Bottlerocket/EKS Auto Mode의 불변 호스트에 같은 chroot 절차를 가정하지 않습니다. Auto Mode 진단의 debug container·콘솔 로그 절차를 사용합니다.

bash
kubectl debug node/<node> -it --image=busybox --profile=sysadmin -- chroot /host
# 들어간 뒤
journalctl -u kubelet --since "10 min ago" | tail -50
df -h /var/lib/containerd
crictl ps -a | head

kubectl get nodes에 노드가 아예 나타나지 않는 경우(조인 실패: IAM 역할/access entry, 서브넷 라우팅, 보안 그룹, AMI 불일치)는 별도 주제입니다 → EKS 고급 디버깅 — 노드 조인 실패 진단, EKS 문제 해결 — 노드 및 파드 문제. Karpenter 노드라면 10번의 NodeClaim 확인을 먼저 합니다.

7. PVC가 Pending

증상: kubectl get pvcPending, 이를 쓰는 파드는 pod has unbound immediate PersistentVolumeClaimsPending.

진단:

bash
kubectl get pvc -n <ns>
kubectl describe pvc <pvc> -n <ns> | sed -n '/^Events:/,$p'
kubectl get storageclass
kubectl get pods -n kube-system -l app=ebs-csi-node -o wide     # 해당 노드에 CSI 노드 플러그인이 있는가
NAME   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2    kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  145d
gp3    ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   76d

위 StorageClass 목록은 환경 예시입니다. Auto Mode의 ebs.csi.eks.amazonaws.com과 별도 EBS CSI의 ebs.csi.aws.com은 구분합니다. Auto Mode에서는 일반 ebs-csi-node DaemonSet이 없다는 이유만으로 장애라고 판단하지 않습니다. storageClassName: ""은 기본 클래스를 사용하지 않겠다는 명시적 요청이므로 필드 생략과도 다릅니다.

원인과 조치: describe pvc의 Events 메시지가 곧 진단입니다.

Events 메시지원인조치
WaitForFirstConsumer: waiting for first consumer to be created before binding정상. volumeBindingMode: WaitForFirstConsumer는 파드가 스케줄될 때까지 볼륨을 만들지 않음파드가 없어서 Pending이면 그대로 두면 됨. 파드도 Pending이면 파드 쪽 FailedScheduling을 봐야 함
FailedBinding: no persistent volumes available for this claim and no storage class is setstorageClassName을 안 썼고 기본 StorageClass도 없음PVC에 storageClassName: gp3 지정, 또는 SC에 storageclass.kubernetes.io/is-default-class: "true" 어노테이션
ProvisioningFailed: storageclass.storage.k8s.io "<name>" not foundStorageClass 이름 오타, 다른 클러스터에서 가져온 매니페스트kubectl get sc의 실제 이름으로 수정
ProvisioningFailed: error generating accessibility requirements: no topology key found for node <node>파드가 배정된 노드에 EBS CSI 노드 플러그인이 아직 등록되지 않음(CSINode에 드라이버 없음)kubectl get csinode <node>의 DRIVERS 열 확인, ebs-csi-node 데몬셋이 그 노드에 떠 있는지 확인
ProvisioningFailed + UnauthorizedOperation/AccessDeniedEBS CSI 컨트롤러의 IRSA/Pod Identity 권한 없음8. IRSA/Pod Identity — 대상은 ebs-csi-controller-sa
파드 쪽 node(s) had volume node affinity conflict기존 PV(EBS)는 AZ ap-northeast-2a에 있는데 스케줄 가능한 노드는 다른 AZEBS는 AZ를 못 넘음. kubectl get pv <pv> -o jsonpath='{.spec.nodeAffinity}'로 zone 확인 후 해당 AZ에 노드 확보(NodePool zone requirement 또는 nodeSelector)
파드 쪽 FailedAttachVolume: Multi-Attach error for volumeRWO 볼륨이 이전 노드에서 아직 detach 안 됨(노드 장애 후 StatefulSet 재스케줄)kubectl get volumeattachments로 stale attachment 확인. 노드가 사라졌으면 attachment가 정리될 때까지 수 분 대기

WaitForFirstConsumer, StorageClass, 동적 프로비저닝 개념은 스토리지에, EBS/EFS CSI 오류 패턴은 EKS 고급 디버깅 — 스토리지에 있습니다.

8. EKS: IRSA / Pod Identity AccessDenied

증상: 파드는 정상 Running인데 앱 로그에 AWS SDK 오류.

An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation:
  Not authorized to perform sts:AssumeRoleWithWebIdentity

또는 S3/DynamoDB 호출 자체가 ... is not authorized to perform: s3:GetObject 로 거부되는데, 거부된 주체가 서비스 계정 역할이 아니라 노드 IAM 역할(assumed-role/<node-role>/i-0abc...)인 경우. 이는 SDK가 노드 자격 증명을 선택했다는 단서입니다. 주입 누락 외에도 SDK 버전·공급자 우선순위·명시적 자격 증명을 확인합니다. IMDS 접근이 차단돼 있으면 노드 역할로 폴백하지 못합니다.

진단 — 어떤 방식을 쓰는지부터 확인합니다. 파드 환경 변수에 답이 있습니다.

bash
# 서비스 계정 어노테이션 (IRSA)
kubectl get sa <sa> -n <ns> -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}{"\n"}'

# 파드에 주입된 자격 증명 관련 env
kubectl get pod <pod> -n <ns> -o json | jq -r '
  (.spec.initContainers[]?, .spec.containers[]?) as $container
  | $container.env[]?
  | select(.name == "AWS_ROLE_ARN" or .name == "AWS_WEB_IDENTITY_TOKEN_FILE"
        or .name == "AWS_CONTAINER_CREDENTIALS_FULL_URI"
        or .name == "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE")
  | [$container.name, .name, (.value // "valueFrom")] | @tsv
'
주입된 env방식의미
AWS_ROLE_ARN=arn:aws:iam::...:role/<role> + AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/tokenIRSApod-identity-webhook이 주입. 없으면 SA 어노테이션이 파드 생성 이후에 붙었거나 SA 이름이 다름
AWS_CONTAINER_CREDENTIALS_FULL_URI + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILEEKS Pod Identityeks-pod-identity-agent169.254.170.23에서 자격 증명 제공. association이 있어야 주입됨
둘 다 없음다른 공급자 또는 자격 증명 없음아래 표 참고
bash
# Pod Identity: 에이전트와 association
kubectl get pods -n kube-system -l app.kubernetes.io/name=eks-pod-identity-agent
aws eks list-pod-identity-associations --cluster-name <cluster> --namespace <ns> --service-account <sa>

# IRSA: 신뢰 정책의 OIDC 조건
aws eks describe-cluster --name <cluster> --query 'cluster.identity.oidc.issuer' --output text
aws iam get-role --role-name <role> --query 'Role.AssumeRolePolicyDocument'

원인과 조치:

관찰원인조치
env 없음, SA 어노테이션은 있음생성 순서, 실제 serviceAccountName 또는 webhook 주입 설정 문제설정을 확인·수정한 뒤 해당 네임스페이스에서 rollout 영향 검토 후 재생성
env 없음, association도 없음Pod Identity association 미생성 또는 다른 SA/네임스페이스로 생성aws eks create-pod-identity-association ... 후 파드 재시작
Not authorized to perform sts:AssumeRoleWithWebIdentityIRSA 신뢰 정책의 Federated OIDC provider ARN 또는 sub 조건(system:serviceaccount:<ns>:<sa>)/aud(sts.amazonaws.com) 불일치신뢰 정책 수정. 클러스터를 재생성했다면 OIDC issuer가 바뀌어 provider도 새로 만들어야 함
Pod Identity인데 AssumeRole 거부신뢰 정책 Principal이 pods.eks.amazonaws.com이 아니거나 sts:TagSession 누락신뢰 정책에 sts:AssumeRole + sts:TagSession 모두 허용
env는 정상, 특정 API만 AccessDenied권한 정책, 리소스 정책, SCP, boundary, session/VPC endpoint 정책 등의 거부사용 주체와 요청 리소스를 확인하고 명시적 Deny까지 추적. CloudTrail data event는 수집 설정이 필요할 수 있음
Pod Identity env 있는데 SDK가 Unable to locate credentialsSDK가 너무 오래되어 컨테이너 자격 증명 공급자(FULL_URI)를 지원 안 함SDK 업그레이드 — 지원 최소 버전은 EKS 문서 참고

IRSA와 Pod Identity의 동작 원리·설정 방법은 EKS 보안 모범 사례EKS 보안에, 토큰 만료·webhook 이슈는 EKS 고급 디버깅 — 컨트롤 플레인 디버깅에 있습니다.

9. EKS: ENI/VPC CNI IP 고갈

증상: 파드가 ContainerCreating에서 멈추고, Events에 FailedCreatePodSandBox:

Warning  FailedCreatePodSandBox  kubelet  Failed to create pod sandbox: rpc error: code = Unknown desc =
  failed to setup network for sandbox "...": plugin type="aws-cni" name="aws-cni" failed (add):
  add cmd: failed to assign an IP address to container

스케줄 단계의 Too many pods는 kubelet의 파드 개수 상한에 도달했다는 뜻입니다. IP 부족과 연관될 수 있지만 동일 원인으로 단정하지 않습니다. FailedCreatePodSandBox의 CNI 오류는 노드 배정 이후 발생합니다.

진단:

bash
# 노드에 실제 설정된 파드 상한. secondary-IP 기본 계산과 prefix/custom networking 설정을 구분
kubectl get node <node> -o jsonpath='{.status.allocatable.pods}{"\n"}'
kubectl get pods -A --field-selector spec.nodeName=<node>,status.phase!=Succeeded,status.phase!=Failed --no-headers | wc -l

# aws-node 상태와 IPAM 설정
kubectl get pods -n kube-system -l k8s-app=aws-node -o wide
kubectl get ds -n kube-system aws-node -o jsonpath='{range .spec.template.spec.containers[?(@.name=="aws-node")].env[*]}{.name}={.value}{"\n"}{end}' | grep -E "PREFIX|WARM|MINIMUM|CUSTOM_NETWORK"

# 서브넷 잔여 IP
aws ec2 describe-subnets --subnet-ids <subnet-id> --query 'Subnets[].{id:SubnetId,az:AvailabilityZone,free:AvailableIpAddressCount}' --output table

IPv4 secondary-IP 모드에서 WARM_ENI_TARGET=1은 여분 ENI 용량을 유지하려는 기본 설정입니다. ENI마다 primary IP도 소비되며 m5.xlarge의 15 IPv4 주소 중 일반 파드용 secondary IP는 14개입니다. 양수 WARM_IP_TARGET/MINIMUM_IP_TARGET은 warm ENI 규칙보다 우선합니다. 예를 들어 warm=3, minimum=6이면 사용 중 1개일 때 총 6개·여분 5개가 필요할 수 있고, 사용 중 5개이면 총 8개·여분 3개를 목표로 합니다. 항상 여분이 정확히 3개라는 뜻이 아닙니다. IPAM 조정 시간·ENI 한계·prefix 단위 할당에 따라 실제 개수는 달라집니다.

원인과 조치:

관찰원인조치
서브넷 AvailableIpAddressCount가 한 자릿수서브넷 자체가 고갈. warm pool이 IP를 선점WARM_IP_TARGET/MINIMUM_IP_TARGET으로 warm pool 축소(위 설정처럼), 보조 CIDR(100.64.0.0/16 등) 추가 후 custom networking(ENIConfig), 장기적으로 IPv6
노드 파드 수 = allocatable pods설정된 파드 개수 상한. IP 용량과 별도 확인지원·서브넷 여유를 확인한 prefix delegation(ENABLE_PREFIX_DELEGATION=true, /28 prefix 단위 할당, 지원 인스턴스와 연속된 /28 여유 블록 필요) + max-pods 재계산, 또는 더 큰 인스턴스
aws-node가 해당 노드에서 CrashLoopBackOffCNI 자체 장애(IAM 정책 AmazonEKS_CNI_Policy 누락, 버전 불일치)kubectl logs -n kube-system <aws-node-pod> -c aws-node, 노드의 /var/log/aws-routed-eni/ipamd.log
Security Groups for Pods 사용 중 vpc.amazonaws.com/pod-eni 부족branch ENI 한계트렁크 ENI를 지원하는 인스턴스로, ENABLE_POD_ENI=true 확인

IPAM 동작(warm pool, prefix delegation, custom networking)은 VPC CNI — IP 주소 관리에, 단계별 IP 고갈 대응은 EKS 고급 디버깅 — 네트워킹 진단EKS 문제 해결 — VPC CNI 문제에 있습니다.

10. EKS: Karpenter가 노드를 만들지 않음

증상: 파드가 Pending인데 kubectl get nodeclaims에 새 NodeClaim이 생기지 않음. 기본 스케줄러의 FailedScheduling별도로 Karpenter가 같은 파드에 자기 이유를 이벤트로 남깁니다.

진단:

bash
# Karpenter가 남긴 이벤트 (source가 karpenter)
kubectl get events -n <ns> --field-selector involvedObject.name=<pod> -o custom-columns=REASON:.reason,SRC:.source.component,MSG:.message

# NodePool limits vs 현재 사용량
kubectl get nodepool -o custom-columns='NAME:.metadata.name,CPU_LIMIT:.spec.limits.cpu,CPU_USED:.status.resources.cpu,MEM_LIMIT:.spec.limits.memory,MEM_USED:.status.resources.memory,READY:.status.conditions[?(@.type=="Ready")].status'

# NodeClaim 진행 단계
kubectl get nodeclaims -o custom-columns='NAME:.metadata.name,TYPE:.metadata.labels.node\.kubernetes\.io/instance-type,LAUNCHED:.status.conditions[?(@.type=="Launched")].status,REGISTERED:.status.conditions[?(@.type=="Registered")].status,READY:.status.conditions[?(@.type=="Ready")].status'

kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter --tail=100

기존 문서의 Karpenter 이벤트 예시 (여러 NodePool의 탈락 이유):

FailedScheduling  karpenter  Failed to schedule pod, incompatible with nodepool "system",
  daemonset overhead={"cpu":"821m","memory":"1350Mi","pods":"10"}, incompatible requirements,
  label "nvidia.com/device-plugin.config" does not have known values;
  incompatible with nodepool "runner-arm", ..., did not tolerate workload-type=ci-runner:NoSchedule;
  all available instance types exceed limits for nodepool "graviton";
  incompatible with nodepool "gpu-ner", ..., incompatible requirements, key node.kubernetes.io/instance-type,
  node.kubernetes.io/instance-type In [g6e.4xlarge] not in node.kubernetes.io/instance-type In [g6.2xlarge g6.4xlarge g6.xlarge]

기존 예시는 CPU_LIMIT 8 / CPU_USED 8을 기록했지만, exceed limits가 반드시 현재 사용량=상한을 뜻하지는 않습니다. 남은 여유보다 모든 후보 인스턴스가 크면 상한 미만에서도 같은 오류가 납니다. Nominated도 성공 완료가 아니라 스케줄링 후보 지정이므로 NodeClaim의 Launched·Registered·Initialized·Ready와 실제 Pod 배정을 확인합니다.

원인과 조치:

메시지 조각원인조치
all available instance types exceed limits for nodepool "<np>"후보 인스턴스를 추가하면 NodePool limits 초과limit 상향, 또는 consolidation으로 유휴 노드 회수 확인
label "<key>" does not have known values파드가 요구한 custom label 값을 NodePool template labels/requirements에서 제공하지 않음NodePool spec.template.spec.requirements에 해당 키 추가(값 목록 포함)
did not tolerate <key>=<value>:NoScheduleNodePool taints에 대한 toleration 없음의도된 격리라면 다른 NodePool 사용, 아니면 toleration 추가
key node.kubernetes.io/instance-type, ... In [X] not in ... In [Y Z]파드가 요구한 인스턴스 타입이 NodePool 허용 목록에 없음둘 중 하나를 맞춤. 파드 쪽 requirement가 너무 좁은 경우가 많음
daemonset overhead={...}가 크고 Insufficient데몬셋 예약분을 뺀 뒤 남는 용량이 부족더 큰 인스턴스를 requirements에 포함
NodeClaim LAUNCHED=True, REGISTERED=False가 수 분 지속EC2는 떴는데 노드가 조인 못 함 (EC2NodeClass의 subnet/SG 셀렉터, 노드 IAM 역할 access entry, AMI)kubectl describe nodeclaim <name>의 Conditions/Events, EC2 콘솔의 시스템 로그
Karpenter 로그에 InsufficientInstanceCapacity해당 AZ/인스턴스 타입의 EC2 용량 없음 (ICE — Insufficient Capacity Error)인스턴스 타입·AZ·capacity-type(spot/on-demand) 범위 확대
이벤트 없음, Karpenter 로그도 조용파드가 Karpenter 대상이 아님 (nodeSelector가 MNG 라벨을 가리킴, 또는 Karpenter와 무관한 스케줄 제약)파드 spec에서 노드 관련 제약 전체를 다시 확인

NodePool/EC2NodeClass 구조와 상세 문제 해결은 Karpenter — 문제 해결EKS 고급 디버깅 — Karpenter 프로비저닝 문제에 있습니다.

11. 어떤 Service도 만들 수 없음: failed calling webhook

증상: 어느 네임스페이스에서든 — 로드밸런서와 아무 상관 없는 네임스페이스라도 — Service를 kubectl apply/create 하면 API 서버가 거부합니다. Service가 들어 있는 Deployment 배포, Helm 설치, ArgoCD sync가 그 자리에서 멈추는데, 기존 Service는 계속 정상 동작하므로 파드 상태만 보면 아무 이상이 없습니다.

Internal error occurred: failed calling webhook "mservice.elbv2.k8s.aws": failed to call webhook:
  ... no endpoints available for service "aws-load-balancer-webhook-service"

진단: 메시지 안에 웹훅 이름과 그 뒤의 Service 이름이 다 들어 있습니다. 웹훅 설정 → 웹훅 Service의 endpoints → 그 뒤의 Deployment 순으로 내려갑니다.

bash
# (1) 어떤 웹훅이 걸려 있고, 각각 실패 시 어떻게 동작하는가 (rules, namespaceSelector, objectSelector, failurePolicy)
kubectl get mutatingwebhookconfigurations,validatingwebhookconfigurations
kubectl get mutatingwebhookconfiguration aws-load-balancer-webhook -o jsonpath='{range .webhooks[*]}{.name}{"\t"}failurePolicy={.failurePolicy}{"\t"}ns={.namespaceSelector}{"\t"}obj={.objectSelector}{"\t"}{.rules[*].operations}{" "}{.rules[*].resources}{"\n"}{end}'

# (2) 웹훅 Service 뒤에 Ready 파드가 있는가
kubectl -n kube-system get endpointslices -l kubernetes.io/service-name=aws-load-balancer-webhook-service

# (3) 그 Deployment는 왜 죽는가
kubectl -n kube-system get pods -l app.kubernetes.io/name=aws-load-balancer-controller
kubectl -n kube-system logs deploy/aws-load-balancer-controller --previous

기존 문서에는 2026년 9월 2일 LBC v3.2.1의 반복 종료와 아래 로그가 기록되어 있습니다. 이번 검토에서는 당시 재시작 횟수·지속 시간을 재검증하지 않았으므로 일반적인 재현 결과로 인용하지 않습니다. 여기서 검증할 단서는 설치된 CRD와 컨트롤러가 요청하는 API 버전의 불일치입니다.

{"ts":"2026-09-02T07:54:42Z","logger":"setup","msg":"Disabling NLBGatewayAPI: missing required Gateway API CRDs","missing":["TLSRoute","TCPRoute","UDPRoute"]}
{"level":"error","logger":"controller-runtime.source.Kind","msg":"if kind is a CRD, it should be installed before calling Start","kind":"ListenerSet.gateway.networking.k8s.io","error":"no matches for kind \"ListenerSet\" in version \"gateway.networking.k8s.io/v1\""}
{"ts":"2026-09-02T07:57:00Z","level":"error","logger":"setup","msg":"problem running manager","error":"failed to wait for gateway.k8s.aws/alb caches to sync kind source: *v1.ListenerSet: timed out waiting for cache to be synced for Kind *v1.ListenerSet"}

ListenerSet.gateway.networking.k8s.io/v1을 찾지 못해 컨트롤러의 캐시 동기화가 실패한 로그입니다. Gateway API 1.5.0에서는 ListenerSet이 standard 채널에 포함됩니다. LBC v3.2.1 가이드의 요구 버전·standard CRD와 LBC 전용 CRD를 확인합니다. TCPRoute·UDPRoute 등 추가 API가 필요할 때만 해당 experimental 배포 요구를 검토합니다. 실제 웹훅의 rules·namespaceSelector·objectSelector·failurePolicy에 매칭되는 요청이 영향을 받습니다. 기존 LB 데이터 경로가 계속 동작할 수 있어도 컨트롤러 장애 중 타겟 갱신·신규 리소스 조정은 중단될 수 있습니다.

원인과 조치:

관찰원인조치
no endpoints available for service "aws-load-balancer-webhook-service"웹훅 Deployment에 Ready 파드가 0개 (CrashLoop, 스케줄 실패, replicas 0)컨트롤러를 먼저 살립니다 (아래 행). 복구 확인은 EndpointSlice의 주소·ready 조건 및 실제 webhook 요청으로
로그에 no matches for kind "ListenerSet"timed out waiting for cache to be synced컨트롤러 버전이 요구하는 Gateway API CRD가 미설치(a) 그 컨트롤러 버전이 요구하는 Gateway API CRD 설치 — Gateway API 1.5.0의 ListenerSet은 standard 채널, (b) CRD를 갖추기 전까지 Helm의 feature-gate 값으로 컨트롤러의 Gateway API 기능을 끈다 (정확한 gate 이름은 해당 버전의 values.yaml에서 확인), (c) 설치된 CRD와 맞는 컨트롤러 버전으로 고정
endpoints는 있는데 connection refused / context deadline exceeded / x509웹훅 포트로의 경로 차단(NetworkPolicy/보안 그룹), 인증서 만료·불일치API 서버 → 파드 웹훅 포트 경로, clientConfig.caBundle과 인증서 갱신 상태 확인
지금 당장 Service를 만들어야 함영향 범위를 이해한 의식적인 비상조치로만: mservice.elbv2.k8s.awsfailurePolicyIgnore로 패치. 그 사이 만든 Service는 컨트롤러의 mutation(기본 loadBalancerClass 주입)을 받지 못하므로, 복구 후 반드시 Fail로 되돌리고 그동안 만든 Service를 점검

하지 말아야 할 것: Service에 app.kubernetes.io/name=aws-load-balancer-controller 라벨을 붙여 objectSelector를 피해 가는 것. 웹훅은 통과하지만 그 Service는 해당 mutation을 받지 못하고, 라벨이 거짓말을 하게 됩니다. 이 selector는 컨트롤러 자신의 Service를 만들기 위한 예외일 뿐입니다.

Gateway API 기능을 사용하지 않는 동안 끄기로 결정했다면 v3.2.1 차트의 다음 values를 기존 설정에 병합하고 Helm diff를 검토합니다. 다른 Gateway 리소스를 운영 중인지 먼저 확인합니다.

yaml
controllerConfig:
  featureGates:
    ALBGatewayAPI: false
    NLBGatewayAPI: false

예방: webhook Deployment의 가용 복제본과 CrashLoop에 알림을 설정합니다. 예를 들어 kube-state-metrics의 아래 값이 5분 동안 참이면 알리도록 규칙을 구성합니다.

promql
kube_deployment_status_replicas_available{
  namespace="kube-system", deployment="aws-load-balancer-controller"
} < 1

메트릭이 수집되지 않으면 이 식도 결과가 없으므로 별도의 absent_over_time(...[5m]) 또는 scrape 장애 알림이 필요합니다. Deployment Available만으로 Service selector·인증서·네트워크 경로까지 보장하지는 않습니다. EndpointSlice 조건과 webhook 경로도 점검합니다. 복제본을 여러 AZ에 분산하고 PDB를 적용하면 일부 노드 장애에 도움이 되지만, 모든 복제본의 잘못된 설정을 해결해 주지는 않습니다.

기존 Pod 네트워크 벤치마크의 ClusterIP 측정 제외 사유는 해당 문서의 측정 범위로 남깁니다. 이번 검토에서 그 장애를 다시 실행하지는 않았습니다.


kubectl 진단 치트시트

조회 명령과 디버그 작업을 구분합니다. get/describe/logs/top은 조회이며, kubectl debug·kubectl run은 컨테이너 또는 Pod를 생성합니다. 복제 디버그 Pod의 라벨이 Service selector와 겹치지 않도록 확인하고, 작업 후 생성한 디버그 Pod를 정리합니다. ephemeral container는 해당 Pod에 기록이 남습니다.

bash
# ── 상태 스캔 ──────────────────────────────────────────────────────────
# 비정상 파드만
kubectl get pods -A -o json | jq -r '
  .items[]
  | select(.status.phase != "Succeeded")
  | select(.status.phase != "Running" or
      ([.status.conditions[]? | select(.type == "Ready" and .status == "True")] | length == 0))
  | [.metadata.namespace, .metadata.name, .status.phase,
     ([.status.initContainerStatuses[]?, .status.containerStatuses[]?
       | .state.waiting.reason // empty] | join(","))] | @tsv
'
# 재시작 횟수 오름차순 정렬이므로 tail을 거치면 가장 많이 재시작한 15개가 마지막에 나옴 + 마지막 종료 이유.
# 첫 번째 컨테이너([0])만 읽으므로 멀티 컨테이너 파드는 나머지 컨테이너를 따로 확인.
kubectl get pods -A --sort-by='.status.containerStatuses[0].restartCount' \
  -o custom-columns='NS:.metadata.namespace,NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount,REASON:.status.containerStatuses[0].lastState.terminated.reason' | tail -15
# 특정 노드의 파드
kubectl get pods -A --field-selector spec.nodeName=<node> -o wide
# 노드 컨디션 + zone + 인스턴스 타입
kubectl get nodes -o custom-columns='NAME:.metadata.name,READY:.status.conditions[?(@.type=="Ready")].status,DISK:.status.conditions[?(@.type=="DiskPressure")].status,MEM:.status.conditions[?(@.type=="MemoryPressure")].status,ZONE:.metadata.labels.topology\.kubernetes\.io/zone,TYPE:.metadata.labels.node\.kubernetes\.io/instance-type'

# ── 이벤트 ─────────────────────────────────────────────────────────────
kubectl get events -A --field-selector type=Warning --sort-by=.lastTimestamp | tail -30
kubectl get events -n <ns> --field-selector involvedObject.name=<pod>,reason=FailedScheduling
kubectl events -n <ns> --for pod/<pod> --watch          # 특정 객체 실시간 추적
kubectl events -A --types=Warning                       # kubectl events 서브커맨드 (1.26+)

# ── jsonpath로 딱 필요한 필드만 ───────────────────────────────────────
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[*].lastState.terminated}'
kubectl get pod <pod> -o jsonpath='{range .spec.containers[*]}{.name}{": "}{.resources}{"\n"}{end}'
kubectl get svc <svc> -o jsonpath='{.spec.selector}'
kubectl get sa <sa> -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
kubectl get pv <pv> -o jsonpath='{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions}'

# ── 로그 ───────────────────────────────────────────────────────────────
kubectl logs <pod> -c <container> --previous --tail=100   # 죽은 컨테이너의 로그
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50  # 라벨로 여러 파드
kubectl logs deploy/<name> --all-containers --since=10m

# ── 디버그 컨테이너 ────────────────────────────────────────────────────
# distroless 파드에 임시 컨테이너 붙이기 (target 런타임 지원 필요)
kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container>
# 셸이 있는 승인된 debug-image로 복제 (복제 라벨·볼륨 접근을 먼저 검토)
kubectl debug <pod> -it --copy-to=<pod>-debug --container=<container> --set-image=<container>=<debug-image> -- sh
# 노드 셸 (SSH 없이). --profile=sysadmin 은 privileged 컨테이너
kubectl debug node/<node> -it --image=busybox --profile=sysadmin -- chroot /host

# ── 리소스 사용량 (metrics-server 필요) ────────────────────────────────
kubectl top nodes
kubectl top pods -n <ns> --sort-by=memory
# metrics-server가 없으면: "error: Metrics API not available"

# ── 스키마 확인 ────────────────────────────────────────────────────────
kubectl explain pod.status.containerStatuses.lastState.terminated
kubectl explain nodepool.spec.limits        # CRD도 동작
kubectl api-resources | grep -E "karpenter|k8s.aws"

# ── 롤아웃 ─────────────────────────────────────────────────────────────
kubectl rollout status deploy/<name> -n <ns>
kubectl rollout history deploy/<name> -n <ns>

kubectl debug--profile 값은 legacy, general, baseline, restricted, netadmin, sysadmin이며(기본값은 kubectl 버전에 따라 legacy 또는 generalkubectl debug --help로 확인), restricted 정책을 적용한 네임스페이스에서는 --profile=restricted와 호환 이미지·보안 컨텍스트가 필요하며, 다른 admission 정책까지 통과한다는 보장은 없습니다. 노드 디버그의 privileged 작업은 별도 허용이 필요합니다.


더 깊이 들어가기: 관련 문서

이 플레이북은 "어디로 들어갈지"를 정하는 입구입니다. 원인이 좁혀졌으면 아래 문서로 이동합니다.

좁혀진 영역개념 문서심화 문제 해결
파드 라이프사이클, 프로브, 재시작 정책파드와 워크로드EKS 고급 디버깅 — 워크로드 디버깅
Service, EndpointSlice, CoreDNS, NetworkPolicy서비스와 네트워킹, 네트워크 정책EKS 문제 해결 — 네트워킹 문제
PV/PVC/StorageClass, EBS CSI스토리지EKS 문제 해결 — 스토리지 문제
노드 조인, kubelet, 리소스 압박클러스터 아키텍처EKS 문제 해결 — 노드 및 파드 문제
Karpenter NodePool/NodeClaimKarpenter스케일링 전략
VPC CNI IPAM, prefix delegation, custom networkingVPC CNIEKS 네트워킹 Part 3: 문제 해결
IRSA, Pod Identity, RBACEKS 보안 모범 사례, Kubernetes 인증 및 권한 부여EKS 문제 해결 — IAM 및 인증 문제
로그가 어디 있고 어떻게 찾는가Logging 개요관측성 분석
requests/limits, OOM, JVM 메모리리소스 최적화EKS 문제 해결 — 성능 문제
장애 대응 절차, 심각도, 첫 5분 체크리스트EKS 고급 디버깅 — 장애 대응 프레임워크

참고 자료

이 문서에 인용한 문자열과 경험 법칙의 근거가 되는 공식 문서입니다.

Kubernetes

Amazon EKS / AWS


< 이전: Zonal 클러스터 운영 전략 | 목차 >