Published on

AWS ALB 502/504 폭증 시 TargetGroup 헬스체크 진단

Authors

운영 중인 서비스에서 AWS ALB가 갑자기 502 또는 504를 대량으로 뿜기 시작하면, 대부분의 팀은 애플리케이션 로그부터 뒤집니다. 하지만 ALB 계열 장애는 Target Group 헬스체크가 어떻게 실패하고 있는지를 먼저 구조적으로 확인해야, 불필요한 추측을 줄이고 복구 시간을 단축할 수 있습니다.

이 글은 “ALB 오류 폭증” 상황에서 Target Group 헬스체크를 중심으로 원인을 분류하고, CloudWatch 지표와 로그로 사실을 확인하며, 설정과 애플리케이션을 함께 튜닝하는 절차를 제공합니다.

관련해서 쿠버네티스 환경이라면 liveness/readiness와 얽혀 증상이 증폭되기도 합니다. EKS에서 프로브 실패로 재시작이 반복되는 케이스는 아래 글도 함께 보면 원인 분리가 빨라집니다.

1) 먼저 용어 정리: 502와 504는 무엇이 다른가

ALB에서 흔히 보는 두 코드는 “원인이 다르다”기보다, ALB가 백엔드(Target)와 통신하는 과정에서 어디서 실패했는지를 나타냅니다.

  • 502 Bad Gateway
    • ALB가 타깃에서 유효하지 않은 응답을 받았거나(프로토콜/헤더/바디), 연결 과정에서 실패했거나, 타깃이 연결을 끊는 등 “게이트웨이로서 정상 처리 불가” 상황
  • 504 Gateway Timeout
    • ALB가 타깃에 요청을 전달했지만 정해진 시간 내 응답을 못 받음

여기서 중요한 포인트는, 두 코드 모두 타깃이 불안정하거나 헬스체크가 흔들릴 때 급격히 증가할 수 있다는 점입니다. 특히 헬스체크가 실패하면 ALB는 타깃을 라우팅 대상에서 제외하고, 남은 타깃에 부하가 몰리면서 지연이 커져 504가 연쇄적으로 늘어나는 패턴이 자주 나옵니다.

2) “헬스체크 실패”가 곧 “장애 원인”은 아니다

Target Group에서 타깃이 unhealthy로 바뀌는 것은 “증상”인 경우가 많습니다. 아래 중 하나일 수 있습니다.

  1. 애플리케이션이 실제로 죽거나 스레드/커넥션 고갈로 응답을 못함
  2. 의존성(DB, Redis, 외부 API) 지연으로 헬스 엔드포인트가 느려짐
  3. 네트워크/보안그룹/라우팅 문제로 헬스체크 트래픽이 차단됨
  4. 헬스체크 설정 자체가 과도하게 엄격하거나(타임아웃 짧음) 엔드포인트가 부적절함
  5. 배포 중 연결 드레이닝, 포트 리스닝 지연, 워밍업 미고려

따라서 진단 순서는 “헬스체크가 실패했다”에서 끝나면 안 되고, 왜 실패했는지를 지표와 로그로 확정해야 합니다.

3) 10분 안에 하는 1차 분류 체크리스트

장애 대응 초기에 아래를 빠르게 확인하면 방향이 잡힙니다.

3-1. Target Group 상태 변화 타임라인

  • 언제부터 unhealthy가 늘었는가
  • 특정 AZ에만 집중되는가
  • 특정 인스턴스/파드만 반복적으로 빠졌다 들어오는가(플래핑)

플래핑이 보이면 헬스체크 설정 또는 워밍업 문제일 가능성이 큽니다.

3-2. CloudWatch 지표로 “ALB가 못 받은 것”과 “타깃이 못 처리한 것” 분리

ALB/TargetGroup에서 자주 보는 지표는 다음입니다.

  • HTTPCode_ELB_5XX_Count
    • ALB 자체가 반환한 5xx
  • HTTPCode_Target_5XX_Count
    • 타깃이 반환한 5xx
  • TargetResponseTime
    • 타깃 응답 시간(지연 급증 여부)
  • HealthyHostCount / UnHealthyHostCount
    • 헬스 상태 변화
  • RequestCount
    • 트래픽 급증 여부

해석 팁:

  • HTTPCode_ELB_5XX_Count만 치솟고 HTTPCode_Target_5XX_Count는 낮다
    • 연결/타임아웃/프로토콜/네트워크 레벨 의심
  • HTTPCode_Target_5XX_Count도 함께 증가
    • 애플리케이션 내부 오류 가능성 높음
  • HealthyHostCount가 줄면서 TargetResponseTime이 같이 상승
    • 타깃 수 감소로 부하 집중, 큐잉/스레드 고갈 가능성

3-3. ALB 액세스 로그로 “실제로 무엇이 느린지” 확인

ALB 액세스 로그를 S3에 켜두었다면, 장애 구간의 레코드에서 다음 필드를 봅니다.

  • elb_status_code (예: 502, 504)
  • target_status_code (타깃이 응답했다면 값이 존재)
  • request_processing_time
  • target_processing_time
  • response_processing_time

특히 target_processing_time이 길게 늘어졌다면 타깃 처리 지연입니다. 반면 target_status_code-로 비어 있고 elb_status_code504라면, 타깃과의 연결/응답 자체가 성립하지 못했을 가능성이 큽니다.

4) Target Group 헬스체크 설정: 실패를 만드는 흔한 조합

헬스체크는 단순히 “살아있나”가 아니라 ALB가 타깃을 라우팅 풀에서 넣고 빼는 스위치입니다. 설정이 공격적이면 타깃이 흔들릴 때 장애가 증폭됩니다.

4-1. Path 선택: / 대신 전용 엔드포인트를 만들기

헬스체크를 /에 걸어두면 다음 문제가 생깁니다.

  • 인증/리다이렉트/템플릿 렌더링 등 불필요한 로직 실행
  • DB 조회가 섞여 의존성 지연이 헬스체크 실패로 직결
  • 캐시 미스나 콜드 스타트가 그대로 반영

권장:

  • /healthz 또는 /livez 같은 가벼운 엔드포인트
  • DB까지 확인하는 /readyz는 별도로 두고, ALB 헬스체크는 최소 요건만 확인

예: Node.js(Express)에서 최소 헬스 엔드포인트

import express from 'express';

const app = express();

app.get('/healthz', (req, res) => {
  // 외부 의존성 체크는 여기서 하지 않는 편이 안전
  res.status(200).type('text/plain').send('ok');
});

app.listen(process.env.PORT || 3000);

4-2. Timeout, Interval, Threshold의 현실적인 조합

헬스체크 파라미터는 서로 영향을 줍니다.

  • Timeout: 한 번의 체크를 얼마나 기다릴지
  • Interval: 체크를 얼마나 자주 할지
  • HealthyThresholdCount: 몇 번 연속 성공해야 healthy
  • UnhealthyThresholdCount: 몇 번 연속 실패해야 unhealthy

장애 시 흔한 문제:

  • Timeout이 너무 짧아 GC/스파이크에 바로 실패
  • Interval이 너무 짧아 타깃이 바쁠 때 헬스체크 트래픽이 추가 부하가 됨
  • UnhealthyThresholdCount가 너무 낮아 순간 스파이크에도 타깃이 빠짐

실무적으로는 “장애 전파를 늦추되, 죽은 타깃은 빠르게 빼는” 균형이 필요합니다. 예를 들어 짧은 순간 스파이크가 잦은 워크로드라면 UnhealthyThresholdCount를 올리고, 배포 직후 워밍업이 필요하면 HealthyThresholdCount도 함께 조정합니다.

4-3. Success codes: 200-399를 무턱대고 쓰지 말기

헬스체크 성공 코드를 200-399로 넓히면, 리다이렉트나 예상치 못한 응답도 성공으로 처리될 수 있습니다.

  • 인증 미들웨어가 302로 로그인 페이지로 보내는데도 healthy로 유지
  • 실제로는 서비스 기능이 망가졌는데 헬스체크는 통과

가능하면 200 고정이 안전합니다. 예외적으로 블루/그린 전환 중에 의도적으로 302를 활용하는 등 특별한 설계가 있다면 그때만 넓힙니다.

5) “헬스체크는 통과하는데 502/504” 패턴 진단

헬스체크가 정상인데도 사용자 요청이 실패한다면, 헬스 엔드포인트와 실제 트래픽 경로가 다르기 때문입니다.

5-1. 헬스체크는 가볍고, 실제 요청은 무겁다

  • /healthz200을 즉시 반환
  • 실제 API는 DB 커넥션 풀 고갈, 락 경합, 외부 API 지연

이 경우 지표는 보통 이렇게 나옵니다.

  • HealthyHostCount는 정상
  • TargetResponseTime 증가
  • HTTPCode_Target_5XX_Count 또는 504 증가

해결은 헬스체크 튜닝이 아니라 병목 리소스(스레드/커넥션/쿼리/외부 호출) 개선입니다. 외부 API 지연이 원인이라면 재시도/백오프 전략도 함께 봐야 합니다.

5-2. keep-alive, idle timeout, 커넥션 재사용 문제

ALB와 타깃 간 커넥션 재사용에서 문제가 생기면 간헐적 502가 발생할 수 있습니다. 예를 들어 타깃이 keep-alive 커넥션을 먼저 닫았는데 ALB가 재사용하려다 실패하는 류의 증상입니다.

점검 포인트:

  • 타깃 웹서버(Nginx, Envoy, 애플리케이션 서버)의 keep-alive 설정
  • ALB의 idle_timeout(로드 밸런서 속성)
  • 타깃의 커넥션 타임아웃이 ALB보다 짧아 “중간에 끊김”이 생기지 않는지

Nginx 예시(개념용)

http {
  keepalive_timeout  65s;
  send_timeout       60s;
}

핵심은 “양쪽 타임아웃 관계”입니다. 타깃이 너무 빨리 끊으면 ALB는 재사용 시도 중 오류를 낼 수 있습니다.

6) “헬스체크가 실패해서 타깃이 줄고, 그게 또 장애를 키우는” 패턴

가장 흔한 장애 증폭 루프는 다음입니다.

  1. 일시적 지연(배포, GC, DB 슬로우) 발생
  2. 헬스체크 연속 실패로 타깃이 unhealthy
  3. 남은 타깃으로 트래픽 집중
  4. 지연이 더 커져 추가 타깃도 헬스체크 실패
  5. 502/504 폭증

이때는 “헬스체크를 완화”하는 것만으로 임시 복구가 되기도 하지만, 근본 원인은 따로 있습니다.

6-1. 배포/스케일 이벤트와 헬스체크 워밍업

  • 새 인스턴스/파드가 뜨자마자 헬스체크가 들어오고, 준비가 덜 되어 실패
  • 애플리케이션이 포트는 열었지만 라우팅/캐시/마이그레이션 등 준비가 끝나지 않음

대응:

  • 애플리케이션에 “준비 완료 전에는 헬스체크 실패”를 명시적으로 반환
  • 오토스케일/배포 시 등록 지연 또는 워밍업 시간을 고려

쿠버네티스라면 readinessProbe로 트래픽 유입을 제어하고, ALB(Ingress)와의 연계를 점검해야 합니다.

6-2. Deregistration delay(드레이닝) 미스매치

타깃이 제거되는 동안 기존 커넥션을 얼마나 유지할지(드레이닝) 설정이 짧거나, 애플리케이션이 종료 시그널을 받자마자 즉시 종료하면 진행 중 요청이 끊기며 502가 늘 수 있습니다.

애플리케이션 종료 훅에서 graceful shutdown을 구현하세요.

예: Node.js graceful shutdown

import http from 'http';

const server = http.createServer((req, res) => {
  res.end('ok');
});

server.listen(3000);

process.on('SIGTERM', () => {
  // 새 연결은 받지 않고 기존 요청을 정리
  server.close(() => {
    process.exit(0);
  });

  // 최악의 경우를 위한 강제 종료 타이머
  setTimeout(() => process.exit(1), 30_000).unref();
});

7) 네트워크/보안그룹/라우팅: “헬스체크만” 막히는 케이스

의외로 자주 나오는 실수는 “서비스 트래픽은 되는데 헬스체크만 안 됨” 또는 그 반대입니다.

7-1. 보안그룹 인바운드 규칙

  • ALB 보안그룹에서 타깃 보안그룹으로 인바운드 허용이 되어야 함
  • 타깃이 인스턴스라면 인스턴스 SG, 파드라면 노드 SG 또는 CNI 정책까지 영향

점검 방법:

  • 타깃 인바운드에 source가 ALB SG로 열려 있는지
  • 헬스체크 포트가 실제 서비스 포트와 다르면 해당 포트도 열려 있는지

7-2. AZ 불일치와 cross-zone

  • ALB가 붙은 서브넷(AZ)과 타깃이 있는 AZ 구성이 어긋나면 특정 AZ에서만 실패가 발생할 수 있습니다.
  • cross-zone 로드밸런싱 설정에 따라 트래픽 분산이 달라져 “한쪽만 과부하”가 될 수도 있습니다.

8) 실전 진단 절차: 원인 확정까지의 7단계

아래 순서대로 하면 “헬스체크가 왜 실패했는지”를 재현 가능한 증거로 남길 수 있습니다.

8-1. 장애 구간 확정

  • 장애 시작/종료 시각
  • 배포, 스케일, 설정 변경, DB 작업 등 이벤트 타임라인

8-2. Target Group 이벤트와 상태 스냅샷

  • 어떤 타깃이 언제 unhealthy가 되었는지
  • 실패 사유(콘솔에 표시되는 reason)

8-3. CloudWatch 지표 5종 세트 비교

  • HTTPCode_ELB_5XX_Count
  • HTTPCode_Target_5XX_Count
  • TargetResponseTime
  • HealthyHostCount
  • RequestCount

이 조합만으로도 “타깃 지연”인지 “연결 실패”인지 1차 분리가 됩니다.

8-4. ALB 액세스 로그 샘플링

  • 504 레코드에서 target_processing_time이 긴지
  • target_status_code가 비어 있는지

8-5. 타깃에서 동시성/리소스 고갈 확인

  • CPU 100%가 아니어도 스레드/이벤트루프/커넥션풀이 먼저 죽습니다.
  • Java라면 GC pause, thread pool, DB pool
  • Node.js라면 event loop lag, max sockets, upstream timeout

8-6. 헬스 엔드포인트 자체의 비용 측정

헬스체크가 DB를 때리거나 외부 API를 호출하고 있지 않은지 확인합니다.

예: 헬스 요청 로그를 별도 태그로 남기기(Express)

app.use((req, res, next) => {
  if (req.path === '/healthz') {
    console.log('healthcheck', { ua: req.headers['user-agent'] });
  }
  next();
});

ALB 헬스체크의 User-Agent는 환경에 따라 다를 수 있으니, 경로 기반으로 먼저 분리하는 편이 안전합니다.

8-7. 설정 튜닝은 “증거 기반”으로

  • 타깃이 일시적으로 느려지는 게 원인이라면: Timeout/UnhealthyThresholdCount 완화 + 근본 병목 개선
  • 연결이 끊기는 게 원인이라면: keep-alive/idle timeout/드레이닝/SG 점검
  • 배포 직후만 문제라면: readiness/워밍업/드레이닝 정합성

9) 튜닝 가이드: 많이 쓰는 권장 패턴

9-1. 헬스체크 엔드포인트 설계 패턴

  • /healthz는 프로세스가 요청을 처리할 수 있는지만 확인(가벼움)
  • /readyz는 의존성(DB 등)까지 확인(무거움)
  • ALB는 /healthz를 사용하고, 내부 오케스트레이션(Kubernetes readiness 등)은 /readyz를 사용

9-2. 장애 확산을 막는 운영 팁

  • 타깃 수가 적을수록 헬스체크 실패의 파급이 큼: 최소 타깃 수를 확보
  • 오토스케일은 “CPU만” 보지 말고 지연/큐 길이/에러율도 고려
  • 배포 시에는 드레이닝과 graceful shutdown을 반드시 맞추기

10) 마무리: 결론은 “헬스체크를 고치면 끝”이 아니다

ALB 502/504 폭증은 표면적으로는 로드밸런서 문제처럼 보여도, 실제로는 Target Group의 헬스체크가 타깃의 불안정성을 얼마나 빠르게 증폭시키는지가 핵심 변수인 경우가 많습니다.

진단의 요령은 간단합니다.

  • 지표로 “ALB가 실패한 것”과 “타깃이 실패한 것”을 먼저 분리
  • 액세스 로그로 타임아웃/지연/연결 실패를 확정
  • 헬스체크 엔드포인트와 파라미터를 현실적으로 조정
  • 배포/드레이닝/워밍업/리소스 고갈 같은 근본 원인을 함께 제거

이 과정을 한 번 템플릿화해두면, 다음에 502/504가 쏟아져도 당황하지 않고 10분 안에 원인 후보를 절반 이하로 줄일 수 있습니다.