AEWS 5주차 - EKS Troubleshooting
CloudNet 팀의 2026년 AWS EKS Workshop Study 4기 5주차 과제 작성내용입니다.
EKS ELB 스케일 다운 시 트래픽 손실 문제와 해결
AWS 공식 블로그 글 How to rapidly scale your application with ALB on EKS (without losing traffic)의 내용을 정리하였습니다.
K8s Probe와 ALB Health Check의 독립성
K8s Probe와 ALB Health Check는 서로의 결과를 공유하지 않고 완전히 독립적으로 동작합니다.
- K8s Probe: kubelet이 노드 내부에서 Pod으로 직접 요청 (localhost)
- ALB Health Check: AWS 인프라의 외부 스펙에서 Pod IP로 요청 (네트워크 레벨)
4가지 Health Check 메커니즘 비교표 (출처: Link)
| 구분 | K8s Probe | ALB Health Check | NLB Health Check | Ingress-NGINX |
|---|---|---|---|---|
| 실행 주체 | kubelet | AWS ALB | AWS 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/Ingress | Service annotation | Ingress annotation |
AWS ALB는 기본 설정으로 15초 간격으로 헬스체크를 수행하며, 2~3회 연속 실패 시 Target을 unhealthy로 판정합니다. 이후 연결 draining (기본 300초)을 시작하여 진행 중인 요청을 마친 후 Target을 제거(deregister)합니다.
그러나 K8s가 Pod를 종료할 때는 이러한 ALB의 draining 과정을 기다리지 않습니다. 따라서 Health Check 불일치로 인해 아래와 같은 4가지 전형적인 장애 패턴이 발생합니다:
| 에러 코드 | 원인 | 발생 상황 |
|---|---|---|
| 503 Service Unavailable | Probe는 성공이나 ALB HC는 실패 | 경로 불일치, Security Group 차단 등 |
| 502 Bad Gateway | Graceful Shutdown 타이밍 불일치 | 종료 중인(SIGTERM) Pod에 트래픽 전송 |
| 일시적 503 | Rolling Update 중 새 Pod 미준비 | ALB HC 통과 전 트래픽 수신 |
| 504 Timeout | Ingress/ALB timeout과 백엔드 불일치 | 파일 업로드, 대용량 배치 API 처리 시 Timeout 발생 |
표 출처: Link
Scale-In Flow (문제 시나리오)
EKS에서 HPA는 아래 순서로 Pod를 Scale-In 합니다.
문제 분석:
HPA에 의해 스케일 다운이 결정되면, kubelet은 즉시 SIGTERM 신호를 Pod에 전송합니다. 하지만 이 시점에서 ALB는 아직 이 Pod가 종료될 것임을 모릅니다.
ALB Controller가 Pod의 상태 변화를 감지하고 Target Group에서 deregister하는 데는 수 초에서 수십 초의 지연이 발생합니다. 이 지연 구간 동안 ALB는 이미 종료된 Pod로 트래픽을 계속 전달하며, 이 요청들은 502 Bad Gateway 또는 504 Timeout 에러로 응답됩니다.
근본 원인:
- ALB와 K8s 간 상태 동기화 메커니즘 부재
- ALB의 기본 헬스체크 간격(15초)이 너무 길어서 Pod 종료 신호를 빠르게 감지하지 못함
- Pod가 graceful shutdown 처리를 시작하기도 전에 ALB 요청을 받음
해결 방법
AWS 블로그에서는 아래 세 가지 방법을 제시합니다.
- 전용 헬스체크 엔드포인트 — Pod 종료 신호 시 ALB에 전달
- ALB Health Check Interval 조정 — 상태 변화를 빠르게 감지
- PreStop hook + Readiness Probe 연동 — SIGTERM 전에 ALB deregister 완료 대기
상태확인 전용 엔드포인트 설정
비즈니스 로직과 분리된 상태확인 전용 엔드포인트(/health)를 만들어, Pod가 종료 중일 때만 이 엔드포인트가 500를 반환하도록 구현합니다.
동작 원리:
1. 정상 상태: /health → HTTP 200 반환
2. 종료 신호 수신: /health → HTTP 500 반환
# Python Signal 핸들러를 사용한 상태확인 기능 간단한 구현 예시
import signal
import os
shutdown_requested = False
def signal_handler(sig, frame):
global shutdown_requested
shutdown_requested = True # 종료 신호 플래그 설정
print("Shutdown signal received")
signal.signal(signal.SIGTERM, signal_handler)
def health(request):
if shutdown_requested:
return HttpResponse("shutting down", status=500) # Sigterm 수신 시 ALB에 unhealthy 신호
return HttpResponse("healthy", status=200)
이 방식으로 /health가 5xx를 반환하면, ALB는 unhealthy threshold를 넘기는 즉시 Pod를 unhealthy로 판정하고 draining을 시작합니다.
ALB Health Check Interval 조정
ALB와 readiness probe 모두 상태확인 전용 엔드포인트를 바라보도록 설정하고, ALB가 Pod의 상태 변화를 빠르게 감지할 수 있도록 헬스체크 간격을 기본값 15초에서 3초로 조정합니다.
# Kubernetes Ingress spec 예시
kind: Ingress
metadata:
name: order-api-ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/healthcheck-path: /health # 비즈니스 경로와 분리
alb.ingress.kubernetes.io/success-codes: '200-301'
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '3' # 기본 15초 → 3초 단축
alb.ingress.kubernetes.io/healthy-threshold-count: '2' # unhealthy 2회 감지 시 deregister
alb.ingress.kubernetes.io/unhealthy-threshold-count: '2'
왜 간격을 3초로 설정하나?
- 기본 15초는 Pod 종료 시 최대 15초까지 대기
- 3초로 설정하면 Pod 종료 신호 후 3-6초 내 ALB가 unhealthy를 감지
- readinessProbe의 주기(아래 1.3.3)와 동일하게 맞춰야 함
PreStop hook + Readiness Probe 설정
PreStop hook은 SIGTERM 신호 전에 kubelet이 실행합니다. 이를 이용해 ALB에게 먼저 상태를 알린 후, SIGTERM 처리를 진행합니다.
# Kubernetes deployment spec 예시
spec:
terminationGracePeriodSeconds: 150 # preStop(120초) 완료 대기 시간 설정
containers:
- name: order-api
lifecycle:
preStop:
exec:
command:
- "/bin/sh"
- "-c"
# Step 1: 헬스체크 상태 변조 → /health가 5xx 반환
# Step 2: ALB가 unhealthy 감지하고 draining 시작할 때까지 대기 (120초)
- "sed -i 's/healthy/unhealthy/g' /usr/src/app/health.py && sleep 120"
readinessProbe:
httpGet:
path: /health # ALB와 동일한 상태확인 엔드포인트 사용‼️
port: 8080
initialDelaySeconds: 3
periodSeconds: 3 # ALB healthcheck-interval(3초)와 동일
successThreshold: 1
failureThreshold: 1 # 1회 실패 시 즉시 Endpoints에서 제거
흐름 설명:
- HPA: 스케일인 결정 → 종료할 Pod 선택
- kubelet: PreStop hook 실행 (SIGTERM 전!)
/health파일 변조 → 다음 요청부터 5xx 반환
- ALB: 3초 간격 헬스체크에서 5xx 감지 → 즉시 unhealthy 판정
- ALB Controller: Target deregister 시작 (Connection Draining 300초 기본값)
- 진행 중인 연결만 유지, 신규 요청은 차단
- 120초 후: kubelet이 SIGTERM 전송
- 이 시점엔 ALB가 이미 새 요청을 받지 않음
- Pod: 진행 중이던 요청만 처리 후 graceful shutdown
PreStop 순서
kubelet의 Pod 종료 순서는:
- PreStop hook 실행 (있으면)
- SIGTERM 신호 전송
- terminationGracePeriodSeconds 대기
- 강제 SIGKILL
PreStop hook에서 충분한 시간(120초)을 sleep하면, 그동안 ALB의 connection draining이 완료됩니다.
개선된 Scale-In Flow
위의 3가지 모두 적용 후, Scale-In 발생 시 Pod가 먼저 죽지 않고, ALB가 먼저 인지하고 트래픽을 차단합니다.
(healthcheck) participant ALB_ctrl as ALB
Controller participant endpoints as Endpoints HPA->>kubelet: 종료 대상 Pod 선택 rect rgb(200, 220, 255) Note over kubelet,Pod: Phase 1: PreStop Hook 실행 (SIGTERM 전) kubelet->>Pod: preStop hook 실행 Pod->>Pod: /health 상태 플래그 변경 end rect rgb(255, 240, 200) Note over readiness,ALB_HC: Phase 2: 상태 변화 감지 (병렬 실행) readiness->>Pod: /health 요청 → 5xx readiness->>readiness: failureThreshold 도달 readiness->>endpoints: Pod 제거 요청 par ALB 동시 감지 ALB_HC->>Pod: healthcheck-interval(3s) 요청 → 5xx ALB_HC->>ALB_HC: 2회 연속 실패 감지 ALB_HC->>ALB_ctrl: unhealthy 보고 end end rect rgb(200, 255, 220) Note over ALB_ctrl,Pod: Phase 3: Connection Draining (신규 요청 차단) ALB_ctrl->>ALB_ctrl: deregisterTargets 호출 ALB_ctrl->>ALB_ctrl: Connection Draining 시작 (300s 기본값) ALB_ctrl->>Pod: 신규 요청 ❌
기존 연결만 유지 ✅ end Pod->>Pod: preStop sleep (120s) rect rgb(255, 200, 200) Note over kubelet,Pod: Phase 4: Graceful Shutdown 진행 kubelet->>Pod: SIGTERM 신호 Pod->>Pod: 진행 중 요청 완료
(새 요청 없음) Pod->>Pod: DB 연결 정리, 캐시 flush 등 Pod->>Pod: 종료 완료 ✅ end
주의사항
terminationGracePeriodSeconds
PreStop hook의 sleep 시간이 terminationGracePeriodSeconds보다 길면, kubelet이 SIGKILL로 Pod를 강제 종료합니다.
ALB가 대상을 제외하기 전에 Pod가 먼저 죽으면 5xx 에러가 다시 발생하게 되므로 terminationGracePeriodSeconds 값을 반드시 확인해야 합니다.
spec:
terminationGracePeriodSeconds: 150 # preStop(120초) + 여유(30초)
containers:
- name: order-api
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sed -i 's/healthy/unhealthy/g' /app/health.py && sleep 120"]
헬스체크 interval과 readinessProbe 주기 동기화
ALB와 kubelet이 동시에 상태를 감지하도록 주기를 맞춰야 합니다.
| 컴포넌트 | 설정값 | 설명 |
|---|---|---|
| ALB healthcheck-interval | 3초 | alb.ingress.kubernetes.io/healthcheck-interval-seconds: '3' |
| kubelet readiness probe | 3초 | periodSeconds: 3 |
| 목표 | 동일 | 둘 다 동시에 상태 감지 |
간격이 크면 deregister가 늦어지고, 그 동안 Pod 종료 상황을 ALB가 놓칠 수 있습니다.
요약
| 구성 요소 | 역할 | 설정값 |
|---|---|---|
| 전용 /health 엔드포인트 | Pod 종료 의사를 ALB에 전달 | 종료 시 5xx 반환 |
| preStop hook | SIGTERM 전에 /health → 5xx 전환 + ALB deregister 대기 | sleep 120 |
| readinessProbe | kubelet도 동시에 상태 감지, Endpoints에서 제거 | path: /health, periodSeconds: 3 |
| healthcheck-interval | ALB가 상태 변화를 빠르게 감지 (기본 15초 → 3초) | 3 |
| terminationGracePeriodSeconds | preStop sleep보다 크게 설정하여 강제 kill 방지 | 150 |
| Ingress annotation | ALB가 비즈니스 경로 대신 /health만 체크 | alb.ingress.kubernetes.io/healthcheck-path: /health |
ALB 트래픽 손실 방지 체크리스트
애플리케이션 코드
-
/health엔드포인트 구현 (비즈니스 로직과 분리) - SIGTERM/SIGINT 핸들러에서 shutdown flag 설정 →
/health반환값 변경 - graceful shutdown 로직 구현 (pending request 처리, DB 연결 정리 등)
Pod Spec (Deployment/StatefulSet)
-
terminationGracePeriodSeconds: 150설정 (preStop 120s보다 여유있게 설정) -
preStophook:sleep 120 -
readinessProbe: path=/health(ALB 헬스체크 경로와 일치), periodSeconds=3
Ingress 사용 시 Annotation 설정
-
alb.ingress.kubernetes.io/healthcheck-path: /health(상태확인 엔드포인트 사용) -
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '3'
참고 자료
스터디 자료
AWS 공식 문서
- AWS 블로그: How to rapidly scale your application with ALB on EKS without losing traffic
- 504/502 에러 완전 제거 및 감소 결과 포함 Order API 예제
- AWS Load Balancer Controller - Health Check Annotations
- 모든 healthcheck 관련 annotation 상세 설명
- AWS Load Balancer Controller - Target Group Attributes
- deregistration-delay, stickiness 등 TG 설정
- aws-samples/app-health-with-aws-load-balancer-controller
- Python, Node.js 등 다양한 언어의 graceful shutdown 구현 예제