[CloudNeta] EKS 워크샵 스터디 (5) - EKS 디버깅 마스터 가이드 (1)

이번 게시글에서는 EKS 워크샵 스터디 제 5주차 내용을 작성합니다.

이번 주에는 EKS 디버깅 마스터 가이드를 토대로 학습 예정입니다.

이 글은 3부로 나누어집니다.

  1. 5주차 - EKS 디버깅 마스터 가이드 (1) (현재 보고계신 글)
  2. 5주차 - EKS 디버깅 마스터 가이드 (2)
  3. 5주차 - EKS 디버깅 마스터 가이드 (3)

들어가며 - 체계적 디버깅의 이유

이번 글은 EKS를 디버깅 함에 있어, 전체 서비스의 어느 부분을 살펴봐야 할지 기술합니다.

저자의 6-layer 프레임워크와 프로덕션 패턴을 토대로 살펴봅시다.

k8s 버전에 대해

본 문서는 1.32+ 버전에 대해 유효합니다.

체계적 디버깅이 필요한 이유?

체계적인 접근으로 디버깅 하는 것과 그렇지 않은 것은 아래와 같은 차이가 있습니다.

비체계적 접근

체계적 접근

핵심 원칙

먼저 아래 핵심원칙을 준수합니다.

  1. 증상 → 레이어 분류 (레이어 별 주요 포인트는 후술합니다)
  2. 스코프 판별(단일/다수)
  3. 결정 트리 따라가기 (후술합니다)
  4. 근본 원인 해결

위와 같은 접근법은 MTTR을 8배 가량[1] 단축시켜주며, "체계적 프레임워크 구성"과 "반복 훈련"에 기반합니다.

디버깅 6-layer 프레임워크

본 게시글은 디버깅 6-layer 프레임워크를 소개하며 컨트롤플레인부터 하위레벨까지 순서를 매깁니다.

모든 구성요소는 독립적으로 디버깅 가능하지만, 상위 레이어의 장애가 하위 레이어로 전파될 수 있음에 유의합니다.

flowchart TB
    CP["🎛️ 컨트롤 플레인
API Server · etcd · Add-on"] NODE["🖥️ 노드
kubelet · containerd · Karpenter"] NET["🌐 네트워크
VPC CNI · DNS · NetworkPolicy"] WL["📦 워크로드
Pod · Probe · HPA · Deployment"] STOR["💾 스토리지
EBS CSI · EFS CSI · PV/PVC"] OBS["📊 옵저버빌리티
메트릭 · 로그 · 알림 · 대시보드"] CP --> NODE --> NET --> WL --> STOR --> OBS style CP fill:#4286f4,stroke:#2a6acf,color:#fff style NODE fill:#ff9900,stroke:#cc7a00,color:#fff style NET fill:#fbbc04,stroke:#c99603,color:#000 style WL fill:#ff4444,stroke:#cc3636,color:#fff style STOR fill:#9333ea,stroke:#7623bf,color:#fff style OBS fill:#10b981,stroke:#0d8a63,color:#fff

Top-down vs Bottom-up 접근

상황에 따라 적합한 디버깅방향을 선택합니다. 디버깅 방향으로는 증상을 빠르게 해결하는 탑다운 방식과, 예방 차원의 바텀업 방식이 있습니다.

프로덕션 사고상황은 탑다운을 권장

증상 파악 후, 해당 레이어의 디버깅 섹션으로 이동하여 즉각 대응이 필요

구분 Top-down(증상 → 원인) Bottom-up (인프라 → 앱)
시작점 사용자 보고 증상 or 알림 컨트롤 플레인부터 순차점검
적절한 상황 프로덕션 인시던트, 장애 대응 예방적 점검, 마이그레이션 후 검증
장점 빠른 장애해결, 사용자 영향 최소화 숨은 문제 사전 발견, 전체 상태 파악
단점 근본원인을 놓칠 수 있음 시간 소요가 큼
소요시간 분 단위(긴급 대응) 시간 단위(정기 점검)
권장 대상 SRE 온콜 엔지니어 플랫폼 팀, 클러스터 관리자

첫 5분 간의 체크리스트

사고 발생 시 체크리스트에 대해 살펴봅시다. 첫 5분에서 가장 중요한 것은 문제의 범위(scope) 판별과 초동 대응입니다.

즉시 진행

# 클러스터 상태 확인
$ aws eks describe-cluster

# 노드 상태 확인
$ kubectl get nodes

# 비정상 Pod 확인
$ kubectl get pods -A | grep -v Running

2분: 스코프 판별

  1. 최근 이벤트 확인 (전체 NS - 네임스페이스)
  2. Pod 상태 집계
  3. 노드별 비정상 Pod 분포
  4. 단일 Pod? 같은 노드? 같은 AZ?
    • 어디서 문제가 발생했는지 명확한 파악 필요
    • (E.g., EC2 물리장비의 메모리가 고장난 경우 (특정 랙의 메모리가 싹다내려감;) 사용자가 대응하기 어려움)

5분: 초동 대응

# 문제가 발생한 파드의 상세정보
$ k describe pod

# 이전 컨테이너 로그 확인
$ k logs --previous

# 리소스 사용량 확인
$ k top nodes
$ k top pods

Escalation Matrix

Severity 분류 기준과 대응 체계를 구축합니다.

SEV-1: 전체 서비스 중단 (즉시대응)

이 상황은 전원호출 필요. 15분 내 상태 공유 필요!

SEV-2: 주요기능 장애 (30분 내 대응)

SEV-3: 성능 저하 (4시간 내 대응)

SEV-4: 경미한 이슈 (다음 스프린트)


I. 컨트롤 플레인 디버깅

이어서 컨트롤플레인 디버깅에 대해 살펴봅니다.

컨트롤 플레인의 주요 요소

EKS 컨트롤 플레인의 3대 구성요소이며, 바로 쉘로 붙는 등의 행위는 불가능합니다. 메트릭은 일부 볼 수 있을 뿐이죠.

EKS 컨트롤 플레인은 AWS 관리형

API Server 로그와 Health 코드로 진단
API Server와 etcd 로그로 디버깅할 가능성 높음

API Server

etcd

애드온

API Server 접근 실패의 결정 트리

API Server에 접근 실패 시 아래와 같은 결정트리를 참고합니다.

flowchart TB
    START([API 접근 실패])
    D1[HTTP 401?]
    D2[HTTP 403?]
    D3[토큰 만료?]
    A1[aws-auth / Access Entry 확인]
    A2["RBAC 권한 확인 (can-i)"]
    A3[SDK 버전 업그레이드]
    A4[SG / VPC 엔드포인트 확인]

    START --> D1
    D1 -->|Yes| A1
    D1 -->|No| D2
    D2 -->|Yes| A2
    D2 -->|No| D3
    D3 -->|Yes| A3
    D3 -->|No| A4

    style START fill:#4286f4,stroke:#2a6acf,color:#fff
    style D1 fill:#fbbc04,stroke:#c99603,color:#000
    style D2 fill:#fbbc04,stroke:#c99603,color:#000
    style D3 fill:#fbbc04,stroke:#c99603,color:#000
    style A1 fill:#10b981,stroke:#0d8a63,color:#fff
    style A2 fill:#10b981,stroke:#0d8a63,color:#fff
    style A3 fill:#10b981,stroke:#0d8a63,color:#fff
    style A4 fill:#10b981,stroke:#0d8a63,color:#fff

컨트롤 플레인 로그 분석

CloudWatch Logs Insights 핵심 쿼리 목록[2]를 살펴봅니다.

필수 로그관련

audit, authenticator 로그는 프로덕션에서 항시 활성화를 권장하며
hot phase는 2주 정도, 나머지는 S3 아카이빙을 권장합니다

IRSA vs Pod Identity + 빈번한 함정

ServiceAccount → IAM Role 매핑 디버깅의 실수를 확인합니다.

구분 IRSA Pod Identity
설정 방식 SA annotation + OIDC Trust Policy CLI 한 줄 (Association)
Cross-account OIDC Provider 공유 필요 간단 (Role Trust만)
디버깅 SA annotation, Trust Policy, OIDC 모두 확인 Association 존재 여부만 확인
권장 대상 기존 워크로드 신규 워크로드
진단 명령

kubectl exec pod -- env | grep AWS

함정 (1) Role ARN 오타 주의

SA annotation의 Role ARN 오타 - 에러 메시지 없이 기본 권한으로 동작하므로 이를 주의깊게 살펴봅니다.

함정 (2) Trust Policy의 ns/sa 불일치

이경우 AssumeRole 이 실패합니다.

함정 (3) Pod Identity 함정

파드 재시작이 필수입니다. Association은 파드 생성 시점에 적용되므로, 이 경우 파드 재시작을 해봅시다.

II. 노드 디버깅

노드 디버깅을 위해 순차적으로 원인을 좁혀가는 진단 흐름을 살펴봅시다.

Node NotReady 결정 트리

SSM 접속 필요

위 작업을 위해선 노드 IAM Role에 AmazonSSMManagedInstanceCore 정책이 필수입니다.

flowchart TB
    START([Node NotReady])
    D1[EC2 상태 확인]
    D2[kubelet 상태 확인]
    D3[containerd 상태 확인]
    D4[리소스 압박 확인]
    A1[인스턴스 재시작/교체]
    A2[systemctl restart kubelet]
    A3[systemctl restart containerd]
    A4[Disk/Memory/PID 조치]
    A5[SG / NACL / VPC 점검]

    START --> D1
    D1 -->|Stopped| A1
    D1 -->|Running| D2
    D2 -->|Not Running| A2
    D2 -->|Running| D3
    D3 -->|Not Running| A3
    D3 -->|Running| D4
    D4 -->|Pressure| A4
    D4 -->|정상| A5

    style START fill:#ff4444,stroke:#cc3636,color:#fff
    style D1 fill:#fbbc04,stroke:#c99603,color:#000
    style D2 fill:#fbbc04,stroke:#c99603,color:#000
    style D3 fill:#fbbc04,stroke:#c99603,color:#000
    style D4 fill:#fbbc04,stroke:#c99603,color:#000
    style A1 fill:#10b981,stroke:#0d8a63,color:#fff
    style A2 fill:#10b981,stroke:#0d8a63,color:#fff
    style A3 fill:#10b981,stroke:#0d8a63,color:#fff
    style A4 fill:#10b981,stroke:#0d8a63,color:#fff
    style A5 fill:#10b981,stroke:#0d8a63,color:#fff

할 수 있는 선 까지 해보고, 이쪽의 문제가 아니라면 사실 기다리는게 전부.... 라는 의견이 있었습니다.

노드 리소스 압박 진단

DiskPressure / MemoryPressure / PIDPressure 에 대해 살펴봅시다.

Condition 임계값 및 진단 명령어 (SSM) 해결 방법
DiskPressure 임계값: 사용 가능 디스크 < 10%진단: df -h crictl rmi --prune (미사용 이미지 정리)
MemoryPressure 임계값: 사용 가능 메모리 < 100Mi진단: free -m 저우선순위 Pod 축출 또는 노드 교체
PIDPressure 임계값: 사용 가능 PID < 5%진단: ps aux | wc -l kernel.pid_max 증가, PID leak 컨테이너 재시작

EKS Node Monitoring Agent 를 참조합니다.

진단 시작

$ k describe node <node-name>

# `Conditions` 섹션에서 True인 Pressure 항목을 참고합니다

예방조치

  1. eks-node-viewer 로 최소한의 모니터링 실시
  2. Karpenter ttlSecondsAfterEmpty 설정
  3. 리소스 requests/limits를 서비스에 맞게 적절하게 설정

노드 모니터링 에이전트

노드 관리는 Auto Mode와 Standard 모드가 있습니다 이 둘에 대한 차이점을 살펴봅시다.

EKS Auto Mode

AWS 의 완전관리 서비스이므로 아래와 같이 처리할 수 있습니다.

Standard Mode (수동설정)

흔히 사용하는 풀 매니징 서비스입니다. 표준 쿠버네티스 + AWS특화 지식 및 관리능력이 요구됩니다.

참고 - 공통 모니터링 도구

EC2 레벨 진단 명령어

SSM 및 콘솔을 통한 노드 직접 디버깅 방안에 대해 소개합니다.

III. 파드 디버깅

이어서 파드 디버깅에 대해 살펴봅시다.

파드 상태별 디버깅 플로우

kubectl get pods 결과에서 시작하는 진단맵을 살펴봅시다.

flowchart TB
    START([Pod 이상 감지])
    CHECK[Pod 상태 확인]

    S1[CrashLoopBackOff]
    S2[ImagePullBackOff]
    S3[OOMKilled]
    S4[Pending]
    S5[Running 0/1 Ready]
    S6[Terminating]

    A1[로그 / CMD / config / OOM 확인]
    A2[ECR 인증 / 레지스트리 / 태그]
    A3[limits 증가 / 메모리 분석]
    A4[리소스 / affinity / PVC]
    A5[readinessProbe 점검]
    A6[Finalizer / preStop 확인]

    START --> CHECK
    CHECK --> S1 --> A1
    CHECK --> S2 --> A2
    CHECK --> S3 --> A3
    CHECK --> S4 --> A4
    CHECK --> S5 --> A5
    CHECK --> S6 --> A6

    style START fill:#ff4444,stroke:#cc3636,color:#fff
    style CHECK fill:#fbbc04,stroke:#c99603,color:#000
    style S1 fill:#ff4444,stroke:#cc3636,color:#fff
    style S2 fill:#fbbc04,stroke:#c99603,color:#000
    style S3 fill:#ff4444,stroke:#cc3636,color:#fff
    style S4 fill:#fbbc04,stroke:#c99603,color:#000
    style S5 fill:#fbbc04,stroke:#c99603,color:#000
    style S6 fill:#fbbc04,stroke:#c99603,color:#000
    style A1 fill:#10b981,stroke:#0d8a63,color:#fff
    style A2 fill:#10b981,stroke:#0d8a63,color:#fff
    style A3 fill:#10b981,stroke:#0d8a63,color:#fff
    style A4 fill:#10b981,stroke:#0d8a63,color:#fff
    style A5 fill:#10b981,stroke:#0d8a63,color:#fff
    style A6 fill:#10b981,stroke:#0d8a63,color:#fff

CrashLoopBackOff 디버깅

4가지 주요 원인과 진단 방법에 대해 살펴봅시다.

앱이 깨지는경우

uncaught exception, panic, segfault 등의 케이스를 의미합니다.
로그에 표기되는 스택트레이스를 확인하여 리포트합니다.

CMD/ENTRYPOINT 오류

잘못된 실행명령어나 경로가 기재되어있는지 Dockerfile을 확인합니다

Config 이 잘못 들어간 경우

환경변수나 설정파일 등이 마운트되지 않은 경우일 수 있으므로, ConfigMap, Secret 설정을 확인합니다

OOMKilled 의 경우

메모리 limits 초과로 강제종료되었을 수 있으니 종료 사유를 파악하고 limits 를 조정합니다.
상세한 내용은 후술합니다.

CrashLoopBackOff 진단 주요 명령어

해당 에러를 진단하기 위한 방안을 설명합니다.

# 이전 (크래시된) 컨테이너 로그 확인
kubectl logs <pod-name> -n <namespace> --previous

# Pod 이벤트 확인 (OOMKilled 여부)
kubectl describe pod <pod-name> -n <namespace>

# 멀티컨테이너 Pod에서 특정 컨테이너 로그
kubectl logs <pod-name> -c <container-name> --previous

# 컨테이너 종료 사유 확인
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState}'

OOMKilled 심층 분석

OOMKilled는 컨테이너 limits 가 초과되어 파드의 배포를 중단하는 것입니다.
노드의 OOM은 requests 합계가 노드의 용량 이상을 초과하는 것입니다.

이를 지정하기 위해 쿠버네티스의 requests와 limits를 다시 확인해봅시다. 둘의 차이는 실제사용량/실제피크로 이해하고 지정해주면 됩니다.

requests vs limits

구분 requests limits
의미 최소 보장 메모리 (스케줄링 기준) 최대 사용 가능 메모리 (초과 시 OOMKill)
설정 권장 실제 사용량 기반 (p50) 실제 피크 기반 (p99 + 20%)
미설정 시 Best Effort QoS (가장 먼저 축출) 무제한 사용 → 노드 OOM 위험
QoS 영향 Guaranteed = requests == limits Burstable = requests < limits

언어별 메모리 관리 주의사항

주요 언어별 메모리 관리 주의사항은 아래와 같습니다.

언어/런타임 핵심 설정 주의사항
JVM (Java/Kotlin) -Xmx = limits의 75% Off-heap, Metaspace 별도 고려. limits 512Mi면 -Xmx384m
Go GOMEMLIMIT 설정 1.19+ 소프트 메모리 제한 지원. GC 임계값 조정
Node.js --max-old-space-size 기본 V8 힙 ~1.5GB. limits에 맞춰 명시 설정
Python 메모리 누수 프로파일링 tracemalloc / memory_profiler 사용

ImagePullBackOff 디버깅

아래 세 가지 주요 원인이 있습니다.

ECR 인증 실패

Private Registry

imagePullSecrets 미설정
Secret의 docker-server URL 오류
인증 정보 만료(kubectl create secret docker-registry 로 확인하기)

이미지 태그 이슈

존재하지 않는 태그를 사용한 경우
:latest 태그 덮어쓰기로 인한 문제
멀티 아키텍처 이미지 불일치
ECR 배포 시 가급적 immutable 태그 사용 권장

ImagePullBackOff 진단 주요 명령어

해당 에러를 진단하기 위한 방안을 설명합니다.

# 이벤트에서 상세 에러 메시지 확인
kubectl describe pod <pod-name> | grep -A 5 "Events:"

# ECR 인증 테스트
aws ecr get-login-password --region <region> | \
  docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com

# imagePullSecrets 확인
kubectl get pod <pod-name> -o jsonpath='{.spec.imagePullSecrets}'

# 이미지 존재 여부 확인
aws ecr describe-images --repository-name <repo> --image-ids imageTag=<tag>

'배포했는데 안 됩니다' 의 6가지 패턴

파드는 Running 상태인데 서비스가 정상작동하지 않는 경우를 살펴봅시다.

Probe 실패 루프

Running 0/1 Ready 상태의 경우.

not ready 상태가 오래가면 디버깅이 필요합니다.
이럴 때는 path 불일치, initialDelay가 모자라서 상태가 제대로 뜨지 않는지, graceful shutdown에 필요한 timeout이 모자란지 확인합니다.
아래에서 후술합니다.

ConfigMap/Secret 미반영

설정 변경이 적용되지 않는 경우.

subPath, envFrom 은 자동 업데이트가 되지 않습니다.
아래에서 후술합니다.

HPA 미반영

TARGETS: Unknown 상태의 경우.

metrics-server 미설치, requests 설정에 대해 살펴봅니다.
아래에서 후술합니다.

Sidecar 순서문제

종료 시 요청이 유실되는 경우.

이 경우 1.29 버전 이상의 Native Sidecar 로 해결할 수 있습니다.
아래에서 후술합니다.

타임존 이슈

로그 시간 불일치(UTC).

TZ 환경변수값을 어떻게 기재할 것인지, tzdata 패키지는 어떻게 관리할 것인지 살펴봅니다.
아래에서 후술합니다.

RequestQuota 초과

네임스페이스 리소스 한도초과(exceeded quota)에러 입니다.
아래에서 후술합니다.

probe 실패 루프

Running 0/1 Ready는 트래픽을 받지 못하는 상태입니다. 이런 경우를 디버깅하기 위해 아래 결정 트리를 살펴봅시다.

권장 Probe 설정(E.g., Spring Boot)

startupProbe:          # 앱 시작 완료 확인
  httpGet:
    path: /actuator/health
    port: 8080
  failureThreshold: 30   # 최대 300초 대기
  periodSeconds: 10
readinessProbe:        # 트래픽 수신 준비
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  periodSeconds: 5
  timeoutSeconds: 3
livenessProbe:         # 데드락 감지 (외부 의존성 제외!)
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 5

ConfigMap / Secret 변경 미반영

마운트 방식에 따라 자동 업데이트 여부가 다릅니다

마운트 방식 자동 업데이트 반영 시간 비고
volumeMount (일반) ✅ 자동 1–2분 (kubelet sync) 권장 방식
volumeMount + subPath ❌ 안 됨 N/A Pod 재시작 필수
envFrom / env ❌ 안 됨 N/A Pod 재시작 필수

subPath 사용 시 주의

# 자동 업데이트 불가
volumeMounts:
  - name: config
    mountPath: /etc/app/config.yaml
    subPath: config.yaml  # 문제!

권장: 디렉토리 마운트

# 자동 업데이트 가능
volumeMounts:
  - name: config
    mountPath: /etc/app  # 디렉토리
Note

Reloader (stakater/reloader) 사용 시 ConfigMap/Secret 변경 감지 후 자동 rollout restart 이 가능합니다.

HPA 미작동 + Sidecar 순서 문제

배포 후 마주하는 오토스케일링과 사이드카 종료 순서 이슈에 대해 살펴봅니다.

HPA 미작동 진단

TARGETS: <unknown> 상태가 포착되면 아래 세 가지 원인을 점검합니다.

  1. metrics-server 설치여부 확인
  2. 파드에 resource requests 미설정
  3. HPA minReplicas > Deployment replicas 로 설정된 경우

이 경우 아래 명령어로 확인해봅니다.

kubectl get hpa
kubectl top pods
# 이때 top pods 실패 시 metrics-server 문제입니다.

참고: 스케일다운 진동 방지
behavior.scaleDown.stabilizationWindowSeconds: 300

Sidecar 종료 순서 문제

Envoy/ADOT sidecar가 메인 앱보다 먼저 종료되는 경우가 있었습니다. 그러니 종료 시점에 "connection refused" 에러를 뜨며 구동이 안되는 경우를 의미합니다.

k8s 1.29+ 의 네이티브 사이드카 기능

해당 기능으로 시작: sidecar 먼저 / 종료: 메인 먼저를 보장합니다.

initContainers:
- name: envoy
  restartPolicy: Always  # sidecar
containers:
- name: app              # main

ResoureeQuota + 타임존 이슈 대응

ResourceQuota

Namespace 리소스 한도 초과 시 파드생성이 차단됩니다.

Error: exceeded quota: compute-quota

진단: kubectl describe resourcequota -n ns
확인: LimitRange도 함께 점검합니다.

ResourceQuota 설정 예시

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: production
spec:
  hard:
    requests.cpu: "100"        # 총 CPU requests 한도
    requests.memory: "200Gi"   # 총 메모리 requests 한도
    limits.cpu: "200"          # 총 CPU limits 한도
    limits.memory: "400Gi"     # 총 메모리 limits 한도
    pods: "100"                # 최대 Pod 수

Timezone/Locale 이슈

컨테이너의 기본 UTC와 로그의 시간이 불일치하는 경우가 있습니다. 이 경우 컨테이너 설정, 각 언어별 타임존을 기재하여 해결합니다.

env:
- name: TZ
  value: "Asia/Seoul"
# JVM 추가:
- name: JAVA_OPTS
  value: "-Duser.timezone=Asia/Seoul"
Alpine/Distroless 같은 경량 이미지의 경우

추가로 tzinfo 를 설치해야 대응이 가능합니다.

Rollout Strategy 비교

maxUnavailable / maxSurge 조합에 따른 배포 동작의 차이점을 살펴봅시다.

전략 maxUnavailable maxSurge 동작 적합한 상황
안전 우선 0 1 새 Pod 먼저 생성 후 구 Pod 제거 무중단 필수, 여유 리소스 있음
균형 1 1 1개씩 교체 (기본값) 대부분의 워크로드
빠른 배포 25% 25% 동시 교체 (Deployment 기본) 빠른 롤아웃 필요
최소 리소스 1 0 구 Pod 먼저 제거 후 새 Pod 생성 리소스 제한적, 순간 중단 허용
대규모 10% 30% 빠른 스케일업 + 점진적 제거 대규모 Deployment (100+ Pods)

minReadySeconds

새 Pod가 Ready 상태가 된 뒤 추가로 대기하는 시간. ALB Health Check 통과를 기다리는 데 사용합니다.

권장값

ALB HC interval × threshold = 30s

progressDeadlineSeconds

롤아웃 진행 제한 시간 (기본 600초). 초과 시 Deployment 상태가 ProgressDeadlineExceeded로 바뀝니다.

# 롤백
kubectl rollout undo deployment/<name>

IV. 헬스체크 불일치

k8s Probe와 LB Healch 체크 살펴보기

K8s Probe와 LB Health Check의 독립적 실행이 만드는 장애에 대해 살펴보겠습니다.

증상 원인 트리거
503 Service Unavailable Probe 성공인데 ALB HC 실패 경로 불일치, SG 차단
502 Bad Gateway Graceful Shutdown 타이밍 불일치 종료 중 Pod에 트래픽 전송
일시적 503 Rolling Update 중 새 Pod 미준비 ALB HC 통과 전 트래픽
504 Timeout Ingress timeout과 백엔드 불일치 파일 업로드, 배치 API

실제 사고 시나리오

K8s Probe ALB Health Check
설정 readinessProbe: /healthz / (기본값)
판정 Pod Ready (Endpoints OK) Unhealthy (404 반환)

영향: 사용자에게 503 에러 → MTTR 2시간 (원인 찾기 어려움)

4가지 헬스체크 메커니즘 비교

각각 독립적으로 실행되는 서로 다른 체크방안 입니다.

구분 K8s Probe ALB HC NLB HC Ingress-NGINX
실행 주체 kubelet ALB NLB nginx process
체크 위치 Pod 내부 (노드) 외부 → Pod IP 외부 → Pod IP L7 프록시 내부
실패 시 동작 Endpoints 제거 TG deregister TG deregister upstream 제거 + 재시도
체크 방식 HTTP / TCP / exec HTTP(S) TCP or HTTP 실제 트래픽 결과
설정 위치 Pod spec Service annotation Service annotation Ingress annotation

핵심 포인트

Probe의 경로와 LB HC의 경로는 반드시 같게, 그리고 LB HC가 Probe보다 더 관대하게 (interval x threshold가 Probe보다 커야) 설정합니다.

이 한 줄을 어기면 배포할 때마다 일시적 503이 반복되고, 회수 안 되는 커넥션이 남고, 원인 찾기가 어려워서 MTTR이 길어집니다.

타이밍 매트릭스

기본 설정값 비교 - 불일치가 장애를 만드는 지점

설정 K8s Probe ALB HC NLB HC Ingress-NGINX
기본 interval 10s 15s 30s - (실제 트래픽)
기본 timeout 1s 5s 6s 60s (proxy_read_timeout)
실패 threshold 3 2 (unhealthy) 3 -
성공 threshold 1 2 (healthy) 3 -
체크 주체 kubelet ALB NLB nginx
실패 시 동작 Endpoints 제거 TG deregister TG deregister upstream 제거
체크 경로 /healthz / 또는 커스텀 TCP or HTTP 실제 요청 경로

불일치 위험 두 가지

패턴 1: Probe OK 상태지만 ALB 503

파드가 Ready 상태인데 사용자에게 503이 보이는 경우입니다. 가장 흔한 원인은 readinessProbe path (/healthz)가 ALB HC path (/ 기본값)과 다른 경우입니다.

flowchart TB
    START([503 발생])
    D1[Pod Ready 확인]
    D2[Endpoints 존재?]
    D3[TG Target Healthy?]
    D4[HC Path 일치?]
    D5[Timeout 설정?]
    D6[Security Group]

    A1[Probe 설정 수정]
    A2[healthcheck-path 통일]
    A3[timeout 증가]
    A4[SG 인바운드 추가]

    START --> D1
    D1 -->|Not Ready| A1
    D1 -->|Ready| D2
    D2 -->|없음| A1
    D2 -->|존재| D3
    D3 -->|unhealthy| D4
    D3 -->|healthy| D6
    D4 -->|No| A2
    D4 -.->|Yes| D5
    D5 --> A3
    D6 --> A4

    style START fill:#ff4444,stroke:#cc3636,color:#fff
    style D1 fill:#fbbc04,stroke:#c99603,color:#000
    style D2 fill:#fbbc04,stroke:#c99603,color:#000
    style D3 fill:#fbbc04,stroke:#c99603,color:#000
    style D4 fill:#fbbc04,stroke:#c99603,color:#000
    style D5 fill:#fbbc04,stroke:#c99603,color:#000
    style D6 fill:#fbbc04,stroke:#c99603,color:#000
    style A1 fill:#10b981,stroke:#0d8a63,color:#fff
    style A2 fill:#10b981,stroke:#0d8a63,color:#fff
    style A3 fill:#10b981,stroke:#0d8a63,color:#fff
    style A4 fill:#10b981,stroke:#0d8a63,color:#fff

패턴 2: Graceful Shutdown 시 502

파드 종료와 ALB deregistration 타이밍이 불일치하는 경우입니다.

시간 파드 측 ALB 측
T+0s Pod Terminating 시작 deregistration 시작
T+0s~ preStop hook 실행 connection draining...
T+0s~ SIGTERM 전송 connection draining...
T+1s 앱 프로세스 종료 여전히 draining 중 → ⚠️ 502 발생
T+grace SIGKILL (grace 만료 시) draining...
T+300s - deregistration 완료

문제

앱이 T+1s에 이미 종료되지만, ALB는 T+300s(기본값)까지 connection draining 중. 이 간격에서 502 Bad Gateway가 발생합니다.

해결

  1. deregistration_delay = 15s (기본 300s에서 축소)
  2. preStop: sleep 15 (deregistration 대기)
  3. SIGTERM 핸들러 구현 (graceful shutdown)

종료 시퀀스 공식

그렇다보니 terminationGracePeriodSeconds 계산법은 보통 아래와 같이 측정됩니다:

terminationGracePeriodSeconds
  = deregistration_delay + preStop_sleep + app_shutdown_buffer
  = 15s                  + 15s           + 10s
  = 40s

권장 YAML 설정

spec:
  terminationGracePeriodSeconds: 40
  containers:
    - name: app
      lifecycle:
        preStop:
          exec:
            command:
              - /bin/sh
              - -c
              - |
                # ALB deregistration 감지 대기
                sleep 15
---
# Service annotation
metadata:
  annotations:
    alb.ingress.kubernetes.io/target-group-attributes: |
      deregistration_delay.timeout_seconds=15

패턴 3: Rolling Update 503 + NLB Local

최소 앱이 뜨기까지의 시간을 벌어주고, 그다음에 바로 올라오면 스위치되도록 하는 것이 중요합니다.

healthcheck 실패로 인한 불균형을 제어할 필요가 있습니다. 어느 한쪽에 치우치지 않고 균형있게 두는게 중요합니다.
고객의 요구와 앱 구동시간, 앱 준비시간 등등에 따라 다르니까 잘 확인해두는 편이 좋습니다.

시간 상태
T+10s K8s Ready (Probe 성공) → 구 Pod 트래픽 중지
T+10~30s ALB HC 아직 미통과 = 503
T+30s ALB HC 2회 성공 → 트래픽 시작

해결:

  1. minReadySeconds: 30 (ALB HC interval 15s × threshold 2)
  2. maxUnavailable: 1
  3. PodDisruptionBudget.minAvailable: 50%

패턴 4: NLB + externalTrafficPolicy: Local — 트래픽 불균형

노드 Pod 분포 HC 결과 트래픽
Node 1 Pod A, Pod B ✅ 성공 2배 수신
Node 2 (없음) ❌ 실패 → TG 제거 0
Node 3 Pod C ✅ 성공 1배

정책 선택 기준:

정책 선택 기준
Local Client IP 보존 필요 + 노드당 최소 1 Pod 보장 가능
Cluster 균등 분배 우선 + X-Forwarded-For 사용 가능

헬스체크의 Best Practices

워크로드 유형 별로 설정을 살펴보는 편이 좋겠습니다.

워크로드 유형 readiness period ALB HC interval minReadySeconds terminationGrace
Stateless API 5s 15s 30s 40s
웹 프론트엔드 5s 15s 30s 40s
배치 워커 10s 30s 60s 120s
Long-lived 연결 10s 30s 60s 300s
gRPC 서비스 5s (grpc) 15s (HTTP) 30s 40s

배포 전 Health Check 체크리스트

  1. ALB HC 경로 = readinessProbe 경로
  2. Probe timeout < ALB HC timeout
  3. preStop hook + SIGTERM 핸들러
  4. terminationGrace > deregistration_delay + preStop_sleep
  5. minReadySecondsALB HC interval × threshold
  6. PodDisruptionBudget (minAvailable: 50%)
  7. Security Group: ALB → Pod CIDR 허용
  8. 배치 API는 별도 Ingress로 분리

PVC 마운트 실패 패턴

  1. az 불일치(스케줄링 실패)
  2. 볼륨제한 초과
    1. Nitro에는 최대 128개(단, 타입별 상이)
  3. RWO(ReadWriteOnce)
    1. EBS는 RWO만 지원. 동시에 여러 노드에서 마운트 불가
    2. 멀티 어태치시 이슈
    3. 이건 EFS RWM(ReadWriteMany)로 운영해야함
  4. detach 지연 (~6분)
    1. 이건 블록 디바이스라 싱크되지 않은 데이터가 메모리 청크에 올라가있어서
    2. 최대 6분걸림
    3. 그런 이유로 강제 detach 하면 데이터가 빠그러진다

스토리지 성능 최적화

IOPS 가 높은것들을 막 쓰지 않기 (알아보고 쓰기)
온라인으로 볼륨 확장은 가능한데, 축소는 불가능하다(!) - 프로덕션에서 하지말고, 조심스럽게 작업할 것(잘못하면 데이터가 날아간다!!!!!!!!!!!!!!!!!!)
고아 볼륨 정리 - finalizer

auto mode의 스토리지 제약

내장 EBS드라이버임
이 노드에는 DaemonSet 이 올라가지 못함
VPC CNI, EBS CSI가 구동되고있음

GPU/AI

CUDA XID 패턴

커널 dmesg에 나옴. NVIDIA monitor 참고해서 디버깅 필요

NCCL Timeout 디버깅

게시글 참고

vLLM 디버깅

요새 뭐만하면 AI 발표가 대부분이다... 이쪽이 되게 핫하다
vLLM의 memory allocator를 k8s 에서 바로 굴리는 느낌으로 받아들이는 느낌이 있다.

vLLM 파라미터 튜닝을 구동하면서 테스트

GPU Operator 컴포넌트 구조

오퍼레이터 패턴 이해하기

정리 - GPU 진단 리스트

  1. GPU 인식부터 확인
  2. 스케줄링 확인
  3. vLLM OOM 확인하기
  4. NCCL Timeout 확인하기

AutoMode vs Standard Mode

커스텀하기는 빡세나 일부를 AWS가 관리한다. 근데 쿠버네티스 환경임.
근데 풀 커스터마이징하려고 쿠버네티스 쓰는거 아니었나..?

근데 평균적으로 그렇지 않으니 이 모드가 나온거였음.... 10%쓰는게 보통이고 빡세게 GPU 까지 다 끌어야 40% 쓴다고 함;

NodePool & NodeClaim

Karpenter의 NodePool 과 유사함

Karpenter

Spot 중단 & Drift 처리

Karpenter 로그 분석

핵심 로그 패턴과 CloudWatch Logs Insights 쿼리

Observability architecture

container insights & application signals

말씀은 해주시나 기능이 비싸면 못쓴다...
초기 운영때는 이걸로 잘 쓰다가 점차 조직이 커지면서 OTel 기반으로 가는게 좋을지도..

4 patterns for incident detection

임계값 기반
이상 탐지
복합 알람
로그 메트릭 필터

L1, L2, L3, L4가 있고

기본적으로 수동 모니터링을 하다가, 임계값 + 로그필터로 확인하다가
이상탐지 및 복합 알람까지 설정하다가, 자동감지 및 자동복구까지 되도록 하기

알림 최적화 & alert fatigue 방지

너무 알람뜨지않게 하기

p1 부터 p4로 단계화하고, p3/p4만 슬랙에, p1/p2는 PagerDuty(별도 서드파티 시스템같음)에.

Quick references

Top 10 에러 빠른 참조표

Wrap-up

핵심 교훈 5가지

  1. 체계적 디버깅과 계층별 접근을 합시다
  2. 선제적 observability 구성으로 문제 전에 감지합시다
  3. auto mode 도입 전 트레이드오프 파악을 필요로 합니다. GPU, 고성능 스토리지, Custom CNI가 필요하면 하이브리드 구성이 필수적입니다
  4. GPU/AI 워크로드는 별도 디버깅 스킬이 필요합니다
  5. 운영 자동화를 통해 Alert에서 Auto-Remediation으로 옮겨갑시다

해보고 블로깅해야 이직을 할 수 있다.
AWS DevOps Agent - EKS Troubleshooting Workshop


  1. 슬라이드 2페이지 참고 ↩︎

  2. 슬라이드 9페이지 참고 ↩︎