[CloudNeta] EKS 워크샵 스터디 (5) - EKS 디버깅 마스터 가이드 (1)
이번 게시글에서는 EKS 워크샵 스터디 제 5주차 내용을 작성합니다.
이번 주에는 EKS 디버깅 마스터 가이드를 토대로 학습 예정입니다.
이 글은 3부로 나누어집니다.
들어가며 - 체계적 디버깅의 이유
이번 글은 EKS를 디버깅 함에 있어, 전체 서비스의 어느 부분을 살펴봐야 할지 기술합니다.
저자의 6-layer 프레임워크와 프로덕션 패턴을 토대로 살펴봅시다.
본 문서는 1.32+ 버전에 대해 유효합니다.
체계적 디버깅이 필요한 이유?
체계적인 접근으로 디버깅 하는 것과 그렇지 않은 것은 아래와 같은 차이가 있습니다.
비체계적 접근
- MTTR(Mean time to repair) 평균 4시간
- 로그 무작위 검색
- 추측기반의 원인 파악, 같은 문제 발생 반복
체계적 접근
- MTTR 평균 30분
- 레이어별 순차 점검
- 결정 트리 기반 판단
- Runbook으로 재발 방지
핵심 원칙
먼저 아래 핵심원칙을 준수합니다.
- 증상 → 레이어 분류 (레이어 별 주요 포인트는 후술합니다)
- 스코프 판별(단일/다수)
- 결정 트리 따라가기 (후술합니다)
- 근본 원인 해결
위와 같은 접근법은 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:#fffTop-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분: 스코프 판별
- 최근 이벤트 확인 (전체 NS - 네임스페이스)
- Pod 상태 집계
- 노드별 비정상 Pod 분포
- 단일 Pod? 같은 노드? 같은 AZ?
- 어디서 문제가 발생했는지 명확한 파악 필요
- (E.g., EC2 물리장비의 메모리가 고장난 경우 (특정 랙의 메모리가 싹다내려감;) 사용자가 대응하기 어려움)
5분: 초동 대응
# 문제가 발생한 파드의 상세정보
$ k describe pod
# 이전 컨테이너 로그 확인
$ k logs --previous
# 리소스 사용량 확인
$ k top nodes
$ k top pods
Escalation Matrix
Severity 분류 기준과 대응 체계를 구축합니다.
SEV-1: 전체 서비스 중단 (즉시대응)
이 상황은 전원호출 필요. 15분 내 상태 공유 필요!
- 전체 사용자 영향(!!)
- 컨트롤 플레인 장애 / 다수 노드의
NotReady - 대응: 전원호출, 15분 내 상태 공유
- AWS Support: Severity Critical
SEV-2: 주요기능 장애 (30분 내 대응)
- 특정 서비스/기능 불가
- 다수 파드에
CrashLoop, 특정 AZ 장애 - 대응: 온콜 + 해당 팀, 30분 내 상태 공유
- AWS Support: Severity Urgent
SEV-3: 성능 저하 (4시간 내 대응)
- 응답 지연, 간헐적 에러
- 일부 파드 재시작 / HPA 미작동
- 대응: 온콜 담당자, 업무시간 내 해결
- AWS Support: Severity High
SEV-4: 경미한 이슈 (다음 스프린트)
- 사용자 영향 없음
- 로그 경고, 비효율적 설정
- 대응: 백로그 등록, 정기 점검 시 처리
- AWS Support: Severity Normal
I. 컨트롤 플레인 디버깅
이어서 컨트롤플레인 디버깅에 대해 살펴봅니다.
컨트롤 플레인의 주요 요소
EKS 컨트롤 플레인의 3대 구성요소이며, 바로 쉘로 붙는 등의 행위는 불가능합니다. 메트릭은 일부 볼 수 있을 뿐이죠.
API Server 로그와 Health 코드로 진단
API Server와 etcd 로그로 디버깅할 가능성 높음
API Server
- 모든 k8s의 진입점
- Authn/Authz 처리
- Admission Controller 실행
- 장애 시:
kubectl명령 실패, 전체 영향 - CloudWatch -
kube-apiserver-audit로그
etcd
- 클러스터 저장소
- AWS 관리형 (직접 접근 불가)
- 자동 백업 및 복구
- 장애 시: 리소스 생성/수정 실패
- EKS Health:
EtcdNotAvailable코드로 모니터링 가능
애드온
CoreDNS: 클러스터 내 DNS 해석kube-proxy:Service네트워킹- VPC CNI: Pod IP 할당
- EBS CNI: 볼륨 관리
- 장애 시: DNS/네트워크/스토리지 불가
ConfigMap은 용도에 맞게 씁시다
ConfigMap 에 수십-수백MB 짜리의 데이터를(JSON 바이너리를 그렇게....) 넣는(!) 경우가 있었다는 제보.
이러면 etcd 부하가 있을 수 있다.... 지양합시다!
API Server 접근 실패의 결정 트리
API Server에 접근 실패 시 아래와 같은 결정트리를 참고합니다.
- 401 - 인증 실패(IAM 자격증명 문제)
- 403 - 인가 실패(RBAC 권한 부족)
- 토큰 만료(SA 토큰 만료) - SDK 자동 갱신 필요
- (번외) 429 - standard tier 로 운영하다가 요청량이 적어지면 tier가 낮아짐. 그 때 이런 문제가 발생할 수 있음
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 아카이빙을 권장합니다
fields @timestamp, @message
| filter @logStream like /kube-apiserver-audit/
| filter responseStatus.code >= 400
| stats count() by responseStatus.code
| sort count desc
fields @timestamp, @message
| filter @logStream like /authenticator/
| filter @message like /error/
or @message like /denied/
| sort @timestamp desc
fields @timestamp, @message
| filter @logStream like /kube-apiserver-audit/
| filter responseStatus.code = 403
| stats count() by user.username
| sort count desc
fields @timestamp, @message
| filter @logStream like /kube-apiserver/
| filter @message like /throttle/
or @message like /rate limit/
| stats count() by bin(5m)
IRSA vs Pod Identity + 빈번한 함정
ServiceAccount → IAM Role 매핑 디버깅의 실수를 확인합니다.
- 핵심 차이는 스케일링의 차이
- 일반적으론 Pod Identity 기준으로 살펴봅니다.
| 구분 | 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 결정 트리
위 작업을 위해선 노드 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 항목을 참고합니다
예방조치
eks-node-viewer로 최소한의 모니터링 실시- Karpenter
ttlSecondsAfterEmpty설정 - 리소스 requests/limits를 서비스에 맞게 적절하게 설정
노드 모니터링 에이전트
노드 관리는 Auto Mode와 Standard 모드가 있습니다 이 둘에 대한 차이점을 살펴봅시다.
EKS Auto Mode
AWS 의 완전관리 서비스이므로 아래와 같이 처리할 수 있습니다.
- 노드 프로비저닝 자동화
kubelet/containerd관리 불필요- 단, SSH/SSM 접속 불가
- 노드 문제 시 자동 교체
- 디버깅:
kubectl debug node사용
Standard Mode (수동설정)
흔히 사용하는 풀 매니징 서비스입니다. 표준 쿠버네티스 + AWS특화 지식 및 관리능력이 요구됩니다.
- Managed Node Group / Karpenter
- SSM 접속으로 직접 디버깅 가능
kubelet/containerd로그 확인 가능- 커스텀 AMI 사용 가능
- 디버깅: SSM + journalctl
참고 - 공통 모니터링 도구
eks-node-viewer: 터미널 노드 리소스 시각화Container Insights: CloudWatch 기반 메트릭Prometheus+Grafana:PromQL기반의 커스텀 대시보드
EC2 레벨 진단 명령어
SSM 및 콘솔을 통한 노드 직접 디버깅 방안에 대해 소개합니다.
# SSM 접속
aws ssm start-session --target <instance-id>
# kubelet 상태 및 로그
systemctl status kubelet
journalctl -u kubelet -n 100 -f
# containerd 상태
systemctl status containerd
# 컨테이너 런타임 확인
crictl pods
crictl ps -a
# 디스크 사용량
df -h
# 메모리 사용량
free -m
# PID 개수
ps aux | wc -l
# 미사용 이미지 정리
crictl rmi --prune
# 노드 네트워크 확인
ip addr show
ip route show
# 로그 수집 스크립트 다운로드 및 실행
curl -O https://raw.githubusercontent.com/\
awslabs/amazon-eks-ami/master/\
log-collector-script/linux/eks-log-collector.sh
sudo bash eks-log-collector.sh
# S3에 직접 업로드
sudo bash eks-log-collector.sh \
--upload s3://my-bucket/
# 노드 디버깅 (호스트 /host에 마운트)
kubectl debug node/<node-name> \
-it --image=ubuntu
# 호스트 파일시스템 접근
chroot /host
# 네트워크 디버깅
kubectl debug node/<node-name> \
-it --image=nicolaka/netshoot
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:#fffCrashLoopBackOff 디버깅
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 인증 실패
- ECR 토큰은 12시간 만료임을 확인
- Cross-account ECR 접근 정책
- 노드 IAM Role에 ECR 권한 확인(특히
ecr:GetAuthorizationToken)
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 # 디렉토리
Reloader (stakater/reloader) 사용 시 ConfigMap/Secret 변경 감지 후 자동 rollout restart 이 가능합니다.
HPA 미작동 + Sidecar 순서 문제
배포 후 마주하는 오토스케일링과 사이드카 종료 순서 이슈에 대해 살펴봅니다.
HPA 미작동 진단
TARGETS: <unknown> 상태가 포착되면 아래 세 가지 원인을 점검합니다.
metrics-server설치여부 확인- 파드에 resource requests 미설정
- HPA
minReplicas> Deploymentreplicas로 설정된 경우
이 경우 아래 명령어로 확인해봅니다.
kubectl get hpa
kubectl top pods
# 이때 top pods 실패 시 metrics-server 문제입니다.
참고: 스케일다운 진동 방지
behavior.scaleDown.stabilizationWindowSeconds: 300
Sidecar 종료 순서 문제
Envoy/ADOT sidecar가 메인 앱보다 먼저 종료되는 경우가 있었습니다. 그러니 종료 시점에 "connection refused" 에러를 뜨며 구동이 안되는 경우를 의미합니다.
해당 기능으로 시작: sidecar 먼저 / 종료: 메인 먼저를 보장합니다.
initContainers:
- name: envoy
restartPolicy: Always # sidecar
containers:
- name: app # main
preStep 트릭
app: preStop sleep 5
envoy: preStop sleep 15
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"
추가로 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는 서로의 결과를 모름
- 경로 불일치 가능 - Probe
/healthzvs ALB/으로 설정될 수 있음 - 타이밍 차이 - Probe 빠름 (
10s) vs ALB 느림 (15–30s)
타이밍 매트릭스
기본 설정값 비교 - 불일치가 장애를 만드는 지점
| 설정 | 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. 체크 주기 차이 - ALB(
15s)가 K8s Probe(10s)보다 느리게 체크해서, K8s는 Ready인데 ALB는 아직 Unhealthy인 구간이 생깁니다. - #2. timeout 경계값 - Probe는
1stimeout으로 통과하지만 ALB5stimeout 안에 앱이 응답 못하면 ALB에서만 실패로 찍힙니다. DB 쿼리 지연 같은 상황에서 자주 터지는 패턴.
패턴 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가 발생합니다.
해결
deregistration_delay=15s(기본300s에서 축소)preStop:sleep 15(deregistration 대기)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회 성공 → 트래픽 시작 |
해결:
minReadySeconds: 30(ALB HC interval 15s×threshold 2)maxUnavailable: 1PodDisruptionBudget.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 체크리스트
- ALB HC 경로 =
readinessProbe경로 - Probe
timeout< ALB HCtimeout preStophook +SIGTERM핸들러terminationGrace>deregistration_delay+preStop_sleepminReadySeconds≥ALB HC interval × thresholdPodDisruptionBudget(minAvailable: 50%)- Security Group: ALB → Pod CIDR 허용
- 배치 API는 별도 Ingress로 분리
PVC 마운트 실패 패턴
- az 불일치(스케줄링 실패)
- 볼륨제한 초과
- Nitro에는 최대 128개(단, 타입별 상이)
- RWO(
ReadWriteOnce)- EBS는 RWO만 지원. 동시에 여러 노드에서 마운트 불가
- 멀티 어태치시 이슈
- 이건 EFS RWM(
ReadWriteMany)로 운영해야함
- detach 지연 (~6분)
- 이건 블록 디바이스라 싱크되지 않은 데이터가 메모리 청크에 올라가있어서
- 최대 6분걸림
- 그런 이유로 강제 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 진단 리스트
- GPU 인식부터 확인
- 스케줄링 확인
- vLLM OOM 확인하기
- NCCL Timeout 확인하기
AutoMode vs Standard Mode
커스텀하기는 빡세나 일부를 AWS가 관리한다. 근데 쿠버네티스 환경임.
근데 풀 커스터마이징하려고 쿠버네티스 쓰는거 아니었나..?
근데 평균적으로 그렇지 않으니 이 모드가 나온거였음.... 10%쓰는게 보통이고 빡세게 GPU 까지 다 끌어야 40% 쓴다고 함;
NodePool & NodeClaim
Karpenter의 NodePool 과 유사함
Karpenter
Spot 중단 & Drift 처리
- CRIU 라는게 있음
- 특정 프로그램의 실행과정을 덤프떠서 다른데서 이어서 실행할 수 있게 해주는 도구인가???????
Karpenter 로그 분석
핵심 로그 패턴과 CloudWatch Logs Insights 쿼리
Observability architecture
- 앱 레벨 메트릭/로그/트레이스
- kubernetes 이벤트/메트릭
- 노드 시스템 메트릭
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가지
- 체계적 디버깅과 계층별 접근을 합시다
- 선제적 observability 구성으로 문제 전에 감지합시다
- auto mode 도입 전 트레이드오프 파악을 필요로 합니다. GPU, 고성능 스토리지, Custom CNI가 필요하면 하이브리드 구성이 필수적입니다
- GPU/AI 워크로드는 별도 디버깅 스킬이 필요합니다
- 운영 자동화를 통해 Alert에서 Auto-Remediation으로 옮겨갑시다
해보고 블로깅해야 이직을 할 수 있다.
AWS DevOps Agent - EKS Troubleshooting Workshop