AWS 타겟 그룹 Unhealthy 알람 — EventBridge · Lambda

클라우드·인프라4분조회

AWS 로드밸런서(ALB)는 들어온 트래픽을 타겟 그룹(Target Group, 이하 TG)이라는 대상 묶음으로 넘깁니다. TG 는 각 대상(서버·파드)의 상태를 헬스체크로 계속 확인하고, 대상이 Unhealthy 로 빠지면 그쪽으로는 트래픽을 안 보냅니다.

그래서 “TG 가 5분 이상 Unhealthy 면 Slack 으로 알린다” 가 이번 요구사항이었습니다. 대상은 한 리전의 전체 TG 에서 쿠버네티스가 자동 생성하는 것들만 제외한 나머지입니다.

CloudWatch Alarm 이 정석이지만 쓰지 않았습니다.

CloudWatch Alarm — TG 하나당 알람 하나

TG 가 수십 개면 알람도 그만큼 생기는데, 여기서 세 가지가 걸립니다.

알람 개수만큼 관리 대상이 늘어나니, 임계값이나 알림 채널을 한 번 바꾸려면 전부 손봐야 합니다.

등록하는 순간 알림이 한꺼번에 나가는 것도 문제입니다. 정상인 TG 를 한 번에 올리면 [해결] 알림이 그 개수만큼 동시에 갑니다.

신규 TG 가 추가될 때마다 등록 스크립트를 수동으로 다시 돌려야 합니다. 안 돌리면 그 TG 는 감시망 밖에 남습니다.

EventBridge 2분 폴링 + Lambda 하나

Lambda 하나가 전체 TG 를 훑는 구조입니다. 매 실행마다 TG 목록을 새로 조회하니 신규 TG 가 자동으로 들어옵니다.

EventBridge Scheduler (*/2 * * * ? *)
    ↓ 2분마다 트리거
Lambda (Python 3.12)

① SSM 스냅샷 조회 (이전 상태 전체 로드)
② ELBv2 API → 전체 TG 조회 (k8s prefix 제외)
③ 각 TG의 describe_target_health
④ EC2 describe_instances 일괄 조회 (Hostname, IP)
⑤ Unhealthy 타겟 처리
⑥ 복구 감지 → chat.update로 [해결] 갱신 후 SSM 삭제

2분 폴링이라 5분 기준이어도 실제로는 최대 6분에 알람이 나갑니다.

상태 저장 — SSM Parameter Store

“5분 이상 지속”을 판정하려면 언제부터 Unhealthy 였는지를 기억해야 합니다. Lambda 는 실행 간 상태가 없으니 밖에 둬야 합니다.

키는 TG 이름과 타겟을 붙여 만들고, 생성·수정·삭제를 전부 Lambda 가 하기 때문에 사전에 만들어 둘 게 없습니다.

/tg-alarm/{TG이름}/{인스턴스ID}_{포트}

예시:
/tg-alarm/my-app-test-tg/i-0123456789abcdef0_80

SSM 허용 문자 규칙에 맞춰 특수문자는 - 로 치환합니다.

저장하는 값은 이렇습니다.

필드 용도
first_unhealthy_at 최초 감지 시각(UTC). 5분 경과 판단 기준
alerted Slack 발송 여부. 중복 발송 방지
slack_ts Slack 메시지 타임스탬프. chat.update 에 사용
tg_name / lb_names TG 이름, 연결된 LB 이름
hostname / ip / instance_id / port 알림 본문에 넣을 타겟 정보
reason AWS 헬스체크 실패 원인 코드 (Target.Timeout 등)

상태 변화는 세 단계입니다.

T+0분   최초 감지  → SSM 생성     (alerted=False, slack_ts=null)
T+6분   5분 경과   → SSM 업데이트 (alerted=True,  slack_ts 저장)
T+10분  복구 감지  → SSM 삭제

현재 상태는 콘솔에서 바로 보입니다.

aws ssm get-parameters-by-path \
  --path /tg-alarm \
  --recursive \
  --region ap-northeast-2

5분 이내 복구 — 알림 생략

alerted 가 판단 기준입니다.

복구를 감지했을 때 alerted=True 면 이미 알람을 보낸 건이므로 chat.update 로 원래 메시지를 [해결] 로 바꾸고 SSM 키를 지웁니다. 새 메시지를 쌓지 않아서 채널이 깨끗하게 유지됩니다.

alerted=False 면 5분을 못 채우고 복구된 건입니다. Slack 없이 SSM 키만 지웁니다. 일시적인 장애가 채널 노이즈로 남지 않습니다.

복구 후 재발생하면 SSM 키가 없는 상태이므로 최초 감지와 똑같이 다시 시작합니다.

버그의 원인 — 스냅샷 읽는 시점

처음에는 Unhealthy 처리 루프를 돌면서 SSM 을 읽고 썼고, 그 다음에 복구 감지 루프를 돌렸습니다.

# 잘못된 순서 (버그 발생)
unhealthy 처리 루프 실행
    → SSM에 새 키 생성
ssm_get_all() 호출
    → 방금 생성한 키도 포함됨
복구 루프에서 해당 키를 복구로 잘못 처리
    → SSM 삭제 → 재발생 알람 영원히 안 옴

키가 지워지니 다음 사이클에서 first_unhealthy_at 부터 다시 시작하고, 5분이 영원히 안 채워집니다. 계속 Unhealthy 인데 알람이 한 번도 안 나갑니다.

고친 건 순서 하나입니다.

# 올바른 순서 (채택)
prev_ssm_states = ssm_get_all()   ← 맨 처음에 스냅샷
unhealthy 처리 루프 → prev_ssm_states 기준으로 판단
복구 루프          → prev_ssm_states 기준으로 비교

증상이 “알람이 안 온다”라서 발송 로직을 들여다보기 쉬운데, 원인은 삭제 쪽에 있었습니다.

EC2 정보 일괄 조회

Unhealthy 타겟의 호스트명과 사설 IP 를 알림에 넣으려면 EC2 조회가 필요합니다. TG 를 돌면서 타겟마다 부르면 호출 수가 타겟 수만큼 늘어납니다.

# 비효율적인 방식
for tg in tgs:
    for target in targets:
        ec2.describe_instances(InstanceIds=[target])  # TG마다 API 호출

# 채택 방식
all_instance_ids = [모든 unhealthy 타겟 ID 수집]
ec2.describe_instances(InstanceIds=all_instance_ids)  # 단 1회 호출

예약된 동시성 1

2분 주기인데 이전 실행이 아직 안 끝났으면 다음 실행이 겹칩니다. 두 실행이 같은 SSM 키를 보고 각자 Slack 을 쏘면 알림이 두 번 갑니다.

예약된 동시성을 1로 두면 겹치는 실행이 throttle 됩니다.

  1. 불러오는 중