이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Compose healthcheck와 depends_on은 어떤 의존성 문제를 줄이고 무엇을 보장하지 못하는가?
  • MySQL, Redis, Spring API의 준비 상태를 어떻게 다르게 확인해야 하는가?
  • 로컬 환경에서 시작 순서 문제를 운영 장애 학습으로 연결하려면 무엇을 봐야 하는가?

개요

Compose에서 container가 시작됐다는 것과 애플리케이션이 요청을 처리할 준비가 됐다는 것은 다르다.

MySQL process가 떠도 connection을 받을 준비가 안 됐을 수 있다.

Redis가 시작됐어도 Spring이 Redis connection을 만드는 시점과 맞지 않을 수 있다.

depends_on과 healthcheck는 이런 문제를 줄이지만, 애플리케이션의 모든 readiness를 대신 보장하지는 않는다.

depends_on의 한계

단순 depends_on은 시작 순서를 지정한다.

services:
  api:
    depends_on:
      - mysql

이 설정은 mysql container를 먼저 시작하게 하지만, MySQL이 query를 받을 준비가 됐다는 뜻은 아니다.

Spring이 너무 빨리 뜨면 DB connection 실패로 종료될 수 있다.

그래서 DB healthcheck와 애플리케이션 재시도 설정이 함께 필요하다.

healthcheck 예시

MySQL healthcheck는 실제 접속 가능성을 확인해야 한다.

mysql:
  image: mysql:8.4
  environment:
    MYSQL_DATABASE: app
    MYSQL_USER: app
    MYSQL_PASSWORD: app-password
    MYSQL_ROOT_PASSWORD: root-password
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    interval: 5s
    timeout: 3s
    retries: 20

Redis는 redis-cli ping을 쓸 수 있다.

redis:
  image: redis:7.2
  healthcheck:
    test: ["CMD", "redis-cli", "ping"]
    interval: 5s
    timeout: 3s
    retries: 20

healthcheck 명령이 image 안에 존재하는지도 확인해야 한다.

service_healthy 조건

Compose에서는 health 상태를 조건으로 의존성을 걸 수 있다.

api:
  build: .
  depends_on:
    mysql:
      condition: service_healthy
    redis:
      condition: service_healthy

이렇게 하면 api 시작이 MySQL과 Redis health 상태를 기다릴 수 있다.

하지만 API 자체의 readiness까지 보장하지는 않는다.

Spring startup, migration, cache warm-up, 외부 API mock 준비는 별도 확인 대상이다.

Spring 재시도와 timeout

로컬 환경에서는 의존성 시작이 느릴 수 있다.

Spring datasource, Redis client, migration 도구가 실패를 어떻게 처리하는지 확인한다.

무한 재시도는 문제를 숨기고, 너무 짧은 timeout은 local startup을 불안정하게 만든다.

로컬에서도 운영과 비슷한 timeout 감각을 유지하되, 실패가 로그에 분명히 남게 해야 한다.

Actuator Health

Spring API의 readiness는 /actuator/health로 확인할 수 있다.

curl -fsS http://localhost:8080/actuator/health
docker compose logs --tail 100 api

Actuator health가 DB와 Redis 상태를 어떻게 반영하는지 알아야 한다.

로컬에서는 health가 DOWN인 이유를 dependency별로 확인하는 연습이 중요하다.

운영 체크리스트

  • depends_on이 단순 시작 순서인지 health 조건인지 구분했는가?
  • MySQL healthcheck가 실제 connection 가능성을 충분히 반영하는가?
  • Redis healthcheck 명령이 image 안에 존재하는가?
  • Spring startup 실패 로그가 명확히 남는가?
  • migration이 DB 준비 전에 실행되지 않는가?
  • Actuator health가 어떤 dependency를 포함하는지 알고 있는가?
  • healthcheck 실패 시 무작정 reset하지 않고 로그를 확인하는가?

장애 신호

  • mysql은 Up이지만 api는 DB connection 실패로 Exited 상태다.
  • healthcheck 명령 자체가 없어 계속 unhealthy로 표시된다.
  • depends_on을 넣었는데도 Spring migration이 실패한다.
  • Redis는 늦게 준비됐고 Spring cache 초기화가 실패했다.
  • health는 UP인데 실제 핵심 API는 migration 미완료로 실패한다.
  • 로컬 startup이 팀원마다 성공/실패를 반복한다.

안전한 완화 조치

시작 순서 문제가 있으면 down -v로 바로 지우지 않는다.

먼저 각 service 로그와 health 상태를 확인한다.

docker compose ps
docker compose logs --tail 100 mysql
docker compose logs --tail 100 api

DB 준비가 느린 문제라면 healthcheck retries와 Spring connection timeout을 조정한다.

migration 실패라면 schema 상태와 migration history를 확인한다.

헬스체크가 너무 약하면 더 실제 요청에 가까운 확인으로 바꾼다.

인프라 담당자와 공유할 자료

로컬 재현 증상: api container가 DB connection failure로 종료
mysql status: Up, health starting -> healthy까지 35초
api start: mysql healthy 전 migration 실행
Compose: depends_on condition 없음
수정 방향: mysql healthcheck 추가, api depends_on service_healthy 적용

로컬 healthcheck 문제는 운영 readiness 설계의 축소판으로 설명할 수 있다.

실전 팁

  • container Up과 service ready를 구분한다.
  • healthcheck는 너무 무겁지 않지만 실제 준비 상태에 가까워야 한다.
  • Spring 재시도는 실패를 숨기지 않게 로그와 timeout을 남긴다.
  • migration은 DB health와 별도 단계로 봐야 한다.
  • local startup 실패를 운영 readiness 문서 개선 자료로 쓴다.

위험 신호!

  • depends_on만으로 DB 준비가 보장된다고 믿는다.
  • healthcheck 명령이 image에 없는데 확인하지 않는다.
  • startup 실패를 해결하려고 무조건 sleep만 늘린다.
  • migration 실패와 DB health 실패를 구분하지 않는다.
  • health가 UP이면 핵심 API도 된다고 가정한다.

확인 질문

확인 질문

  • depends_on만으로 충분하지 않은 이유는 무엇인가?
    • container 시작 순서만 맞출 뿐 DB나 Redis가 실제 요청을 받을 준비 상태인지 보장하지 못하기 때문이다.
  • healthcheck가 계속 실패할 때 먼저 볼 것은 무엇인가?
    • healthcheck 명령이 image 안에 존재하는지, timeout/retries가 적절한지, service 로그에 실제 준비 실패가 있는지다.
  • 로컬 healthcheck 설계가 운영에 도움이 되는 이유는 무엇인가?
    • readiness와 dependency 준비 상태를 구분하는 연습이 운영 배포와 장애 대응 판단으로 이어지기 때문이다.

참고 문서