← 목록

ECS Fargate 컨테이너 Health Check 기반 자동 재시작 구현

ECS에서 컨테이너는 Running인데 내부 앱이 죽는 문제를 Task Definition Health Check로 해결하고, 장애 시 자동 재시작을 구현하는 방법

2024-11-22AWSDevOpsDockerECS

문제 정의

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 1

ECS는 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가 해당 컨테이너 자동 재시작

파라미터 설명

파라미터값설명
commandcurl -f http://localhost:8080/health || exit 1헬스체크 명령어
interval30초체크 간격
timeout5초응답 대기 시간
retries3회실패 허용 횟수
startPeriod60초앱 기동 시간 (이 기간 내 실패는 무시)

애플리케이션 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"

필요 조건:


트러블슈팅

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. 테스트: 앱 내부 장애 시 자동 재시작 확인

정리


배운 점

컨테이너가 살아 있다는 말과 서비스가 동작한다는 말은 다르다. 프로세스는 떠 있는데 안에서 아무 일도 못 하는 상태를 오케스트레이터는 정상으로 본다. 상태를 판단하는 주체가 누구인지, 그 주체가 무엇을 근거로 판단하는지 확인하지 않으면 감시는 형식만 남는다.

startPeriod를 넉넉히 잡는 일도 생각보다 중요했다. 기동이 느린 애플리케이션에 짧은 유예를 주면 뜨는 도중에 unhealthy로 죽고, 죽으면 다시 뜨는 순환에 빠진다. 자동 복구는 잘못 설정하면 자동 장애가 된다.

컨테이너가 언제든 교체될 수 있다는 전제는 AWS Health Dashboard 모니터링에서 확인한 플랫폼 패치 사례와도 이어진다. 죽는 걸 막는 설계보다, 죽어도 되는 설계가 Fargate에서는 더 현실적이었다.


이어 읽기