플로렌스라는 개발자

Docker healthcheck와 depends_on 이해하기

NOTE

Docker Engine 27.4.0, Docker Compose 2.39.2 기준으로 작성되었습니다.

Healthcheck의 역할은 간단하다. 지정한 Healthcheck 명령을 실행하고 종료 코드(Exit Code)에 따라 상태를 구분한다. 종료 코드 0은 성공, 나머지는 실패로 처리된다. 이 상태는 컨테이너의 플래그로 지정된다.

Healthcheck 살펴보기

Docker Compose에서의 Healthcheck 문법은 아래와 같다.

services:
  redis:
    image: redis:8
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 3s
      timeout: 5s
      retries: 1
      start_period: 30s
      start_interval: 3s

다음은 Healthcheck 옵션이다.

옵션설명필수기본값
intervaltest 실행 주기N30s
timeouttest 실행 시간 제한N30s
start_period컨테이너가 시작되고 나서 설정된 시간 동안 test 실패 시 실패 횟수가 증가하지 않는다. test가 성공하면 해당 옵션은 꺼진다. 자세한 건 아래의 Healthcheck 흐름 참고.N0s
start_interval1시작 기간 중 test 실행 주기N5s
retries얼마나 연속적으로 실패를 허용할 것인지에 대한 옵션. 값이 5면 연속적으로 5번 실패 시 unhealthy상태로 판단한다.N3

CAUTION

interval이 너무 짧으면 리소스 부담이 있을 수 있으니 적절히 조절해서 사용해야 합니다.

Healthcheck 흐름

옵션마다 컨테이너 실행 후 Healthcheck가 어떤 흐름으로 동작하는지 알아본다.

기본값

    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      #interval: 30s
      #timeout: 30s
      #retries: 3
      #start_period: 0s
      #start_interval: 5s

흐름에서 괄호 안에 있는 것은 docker container inspect ... 명령 실행 시 나오는 State.Health.Status값이다.
성공시 흐름

0초: 컨테이너 시작(starting)
...
30초: 1차 test 실행(healthy)
...
60초: 2차 test 실행(healthy)

실패시 흐름

0초: 컨테이너 시작(starting)
...
30초: 1차 test 실행(starting)
...
60초: 2차 test 실행(starting)
...
90초: 3차 test 실행(unhealthy)

예제 1

healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 5s
  timeout: 5s
  retries: 3
  start_period: 30s
  start_interval: 3s

성공시 흐름

0초: 컨테이너 시작(starting)
...
3초: 1차 test 실행(healthy)
...
8초: 2차 test 실행(healthy)
...
13초: 3차 test 실행(healthy)

여기서 주목해야 할 것은 1차 test 실행과 2차 test 실행 간격이 5초라는 것이다. start_period가 30초로 설정되어 있지만 처음 1차 test가 성공하고 나서 interval 주기로 동작하게 된다. 또한 1차 test 이후에 실패 시 실패 카운트가 증가할 것이다.

실패시 흐름

0초: 컨테이너 시작(starting)
...
3초: 1차 test 실행(starting)
...
6초: 2차 test 실행(starting)
...
9초: 3차 test 실행(starting)
...
12초: 4차 test 실행(starting)
.........
30초: test 실행(starting)
...
35초: test 실행(starting)
...
40초: test 실행(starting)
...
45초: test 실행(unhealthy)

그래서 Healthcheck로 뭘 할 수 있나?

단독으로 특별한 기능을 하지 않는다. 하지만 아래처럼 활용할 수 있다.

  • 플래그 값을 참조하는 모니터링 툴을 사용
  • 반복적으로 플래그 값을 검사해서 unhealthy면 재시작하는 스크립트를 작성[^2]
  • Docker Compose depends_on과 함께 사용

또한 healthcheck와 restart정책은 서로 연관이 없다.

depends_on 살펴보기

Docker Compose의 depends_on은 서비스 시작, 중지 순서를 제어할 수 있다. 아래의 Docker Compose 스크립트를 예시로 들면

services:
  server:
    image: hello-world:latest
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:18
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=admin
      - POSTGRES_DB=app

  redis:
    image: redis:8

먼저 server 서비스(hello-world:latest)는 postgres, redis 서비스에 의존하고 있다고 가정한다. 그러므로 이 서비스들의 시작 순서가

  1. postgres 컨테이너와 redis 컨테이너가 시작됨
  2. server 컨테이너 시작

으로 동작해야 할 것이다. 만약 depends_on을 지정하지 않았다면 세 서비스 모두 거의 동시에 시작될 것이다.

depends_on의 함정

depends_on은 시작 순서를 제어하는 것이지 컨테이너의 프로세스가 정상적으로 시작되었고, 요청을 받아들일(혹은 보낼) 준비되었다는 게 아니다. 이러한 특징 때문에 depends_on이 지정되지 않았더라도 postgres, redis가 프로세스 실행시간이 시간이 짧고 server는 길다면 정상적으로 동작할 것이다.

만약 postgres 컨테이너가 실행되었지만, 프로세스 실행이 오래 걸려 준비가 안 된 상태라고 가정하자. 이 경우 server 앱이 정상적으로 동작하지 않을 것이다.

이 문제를 해결해 주는 것이 depends_on.conditionhealthcheck다.

depends_on.conditionhealthcheck로 문제 해결하기

depends_on.condition은 해당 서비스가 어떤 상태가 되었을 때 시작할 것인지에 대한 조건이다. depends_on.condition에서 사용할 수 있는 값은 다음 세 가지 값이다.

  • service_started: 서비스가 시작됨(기본값)
  • service_healthy: healthcheck로 인한 컨테이너 플래그가 healthy
  • service_completed_successfully: 서비스가 성공적으로 종료되었음(보통 마이그레이션같은 일회성 서비스에서 사용됨)

여기서 service_healthy를 사용해 문제를 해결할 수 있다.

services:
  server:
    image: hello-world:latest
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  postgres:
    image: postgres:18
    command: ["bash", "-c", "sleep 10 && exec docker-entrypoint.sh postgres"] # 10초 지연 후 서버 시작
    environment:
      - POSTGRES_USER=admin
      - POSTGRES_PASSWORD=admin
      - POSTGRES_DB=app
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
      interval: 1m
      timeout: 5s
      retries: 3
      start_period: 30s
      start_interval: 3s

  redis:
    image: redis:8
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 1m
      timeout: 5s
      retries: 3
      start_period: 30s
      start_interval: 3s

postgresredis의 healthcheck로 컨테이너가 준비되었는지 확인하고 healthy 플래그를 설정한다. server는 이 플래그 기반으로 둘 다 healthy가 되어야 컨테이너가 시작된다.

docker compose up -d.gif
docker compose up -d 실행

TIP

Healthcheck가 depends_on에서만 쓰인다면 interval은 길게 하고 start_period와 start_interval을 설정하세요.

각주

  1. Docker Compose 2.20.2부터 사용 가능

Logo

Plorence

주로 TypeScript와 C#을 사용하며 겪었던 문제나 잘 알려지지 않은 내용들을 다루는 기술 블로그입니다.

틀린 내용/오타 수정 제안