문제 정의
ECS Fargate 환경에서 컨테이너는 Running 상태인데 내부 애플리케이션이 죽는 문제가 발생했다.
컨테이너 상태: Running ✅
애플리케이션 상태: Dead ❌
→ ECS는 컨테이너가 살아있다고 판단
→ 서비스 장애 발생해도 감지 못함
→ 자동 재시작 안 됨
컨테이너 상태 ≠ 애플리케이션 상태라는 것이 핵심 문제다.
근본 원인
Dockerfile에 HEALTHCHECK를 정의해도 ECS는 이를 자동으로 사용하지 않는다.
# Dockerfile에 있어도 ECS가 무시함
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1ECS는 Task Definition에 정의된 healthCheck만 인식한다.
해결: Task Definition에 healthCheck 추가
Health Check 설정
{
"containerDefinitions": [
{
"name": "app",
"image": "[ecr-repo]/[image]:[tag]",
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 60
}
}
]
}동작 방식
30초마다 /health API 호출
↓
응답 실패 시 retry (최대 3회)
↓
3회 연속 실패 → unhealthy 판정
↓
ECS가 해당 컨테이너 자동 재시작
파라미터 설명
| 파라미터 | 값 | 설명 |
|---|---|---|
command | curl -f http://localhost:8080/health || exit 1 | 헬스체크 명령어 |
interval | 30초 | 체크 간격 |
timeout | 5초 | 응답 대기 시간 |
retries | 3회 | 실패 허용 횟수 |
startPeriod | 60초 | 앱 기동 시간 (이 기간 내 실패는 무시) |
애플리케이션 Health Endpoint 준비
Spring Boot Actuator 사용
# application.yml
management:
endpoints:
web:
exposure:
include: health, info/actuator/health 엔드포인트가 자동으로 제공된다.
# 정상 응답
curl http://localhost:8080/actuator/health
# {"status":"UP"}Health Check 명령어를 Actuator에 맞게 수정:
{
"command": ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"]
}커스텀 Health Endpoint
Actuator를 사용하지 않는 경우 간단한 엔드포인트를 직접 구현한다.
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<String> health() {
return ResponseEntity.ok("OK");
}
}Docker 이미지 준비
Alpine 기반 이미지에는 curl이 없으므로 반드시 설치해야 한다.
FROM eclipse-temurin:17-jre-alpine
# curl 설치 (Health Check에 필요)
RUN apk update && apk add --no-cache curl
COPY target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
curl이 없으면 Health Check 명령어가 실패하여 컨테이너가 계속 unhealthy로 판정된다.
주요 설정 포인트
startPeriod (가장 중요)
앱 기동 시간을 충분히 고려해야 한다. Spring Boot 앱은 기동에 30~60초 이상 걸릴 수 있다.
startPeriod: 10초 (기본값)
→ 앱이 아직 뜨는 중에 Health Check 실패
→ unhealthy 오판 → 재시작 루프
startPeriod: 60초 (권장)
→ 60초 동안은 실패해도 무시
→ 앱 기동 완료 후부터 정상 체크
retries
너무 작으면 일시적 지연에도 재시작된다. 3회가 적절하다.
dependsOn (멀티 컨테이너)
사이드카 패턴 등에서 컨테이너 간 실행 순서를 Health Check 기반으로 제어할 수 있다.
{
"dependsOn": [
{
"containerName": "sidecar",
"condition": "HEALTHY"
}
]
}ECS Exec (운영 디버깅)
실행 중인 컨테이너에 직접 접속하여 디버깅할 수 있다.
aws ecs execute-command \
--cluster [cluster-name] \
--task [task-id] \
--container app \
--interactive \
--command "/bin/sh"필요 조건:
- Task Role에
AmazonSSMManagedInstanceCore정책 연결 - Task Definition에
enableExecuteCommand: true설정 - Service 생성/업데이트 시
--enable-execute-command플래그
트러블슈팅
Health Check 계속 실패
원인 1: curl이 없음
→ Dockerfile에 curl 설치 추가
원인 2: startPeriod 부족
→ 앱 기동 시간보다 길게 설정 (60~120초)
원인 3: Health Endpoint 경로 불일치
→ /health vs /actuator/health 확인
ECS는 정상인데 앱이 죽음
원인: Task Definition에 healthCheck 미설정
→ ECS는 프로세스 존재 여부만 확인
→ 앱 레벨 상태는 체크하지 않음
→ healthCheck 추가로 해결
전체 구성 흐름
1. 앱에 /health 엔드포인트 구현 (또는 Actuator 사용)
2. Dockerfile에 curl 포함
3. Docker 이미지 빌드 → ECR Push
4. Task Definition에 healthCheck 설정 (startPeriod 충분히)
5. ECS Service 배포
6. 테스트: 앱 내부 장애 시 자동 재시작 확인
정리
- ECS는 Docker HEALTHCHECK를 무시함 → Task Definition에 직접 설정 필요
/health엔드포인트 +curl기반 Health Check로 앱 상태 감지startPeriod를 앱 기동 시간보다 충분히 길게 설정 (오판 방지)- unhealthy 판정 시 ECS가 자동으로 컨테이너 재시작
- ECS Exec로 실행 중 컨테이너 디버깅 가능
배운 점
컨테이너가 살아 있다는 말과 서비스가 동작한다는 말은 다르다. 프로세스는 떠 있는데 안에서 아무 일도 못 하는 상태를 오케스트레이터는 정상으로 본다. 상태를 판단하는 주체가 누구인지, 그 주체가 무엇을 근거로 판단하는지 확인하지 않으면 감시는 형식만 남는다.
startPeriod를 넉넉히 잡는 일도 생각보다 중요했다. 기동이 느린 애플리케이션에 짧은 유예를 주면 뜨는 도중에 unhealthy로 죽고, 죽으면 다시 뜨는 순환에 빠진다. 자동 복구는 잘못 설정하면 자동 장애가 된다.
컨테이너가 언제든 교체될 수 있다는 전제는 AWS Health Dashboard 모니터링에서 확인한 플랫폼 패치 사례와도 이어진다. 죽는 걸 막는 설계보다, 죽어도 되는 설계가 Fargate에서는 더 현실적이었다.
이어 읽기
- ECS Task STOPPED 상태 감지 및 Slack 알람 구축: 재시작이 반복될 때 사유를 확인하는 방법
- ECS Fargate OOM 방지: 메모리 설정 최적화: 재시작의 원인이 메모리였던 경우