[Docker 시리즈 9편] 헬스체크와 재시작 정책으로 컨테이너 지키기

English version

8편에서 포트·네트워크 구조를 파악했다면, 이제 컨테이너가 실제로 요청을 처리할 수 있는지 확인하는 방법을 배울 차례입니다. Healthcheck와 재시작 정책은 서로 다른 장애 상황을 다루는데, 초보자일수록 두 개념을 자주 헷갈립니다. 이 글에서는 nginx와 MySQL 예제로 그 차이를 분명하게 정리합니다.

이 글의 흐름

  1. Dockerfile 수준 Healthcheck가 하는 일
  2. Compose 수준 Healthcheck로 환경에 맞게 조정하기
  3. restart 정책으로 자동 복구 이해하기
  4. nginx·MySQL 컨테이너에서 직접 실습하기
  5. 초보자가 먼저 익혀야 할 운영 감각 정리

읽기 카드

  • 예상 소요 시간: 15분
  • 사전 준비: curl/wget 사용 경험, docker compose 기초
  • 읽고 나면: 헬스체크 결과를 보고 재시작 정책을 조정할 수 있습니다.

각 기능이 해결하는 문제 먼저 보기

문법보다 먼저, 두 기능이 잡아내는 장애 상황을 나눠서 보겠습니다.

  • 헬스체크는 컨테이너 프로세스는 살아 있지만 서비스 응답이 깨졌을 때 사용합니다.
  • 재시작 정책은 메인 프로세스가 종료되었을 때 Docker가 자동으로 다시 띄우게 하고 싶을 때 사용합니다.

즉, 두 기능은 경쟁 관계가 아니라 보완 관계입니다.

  • 헬스체크는 조용한 장애를 잡습니다. 예: nginx 프로세스는 살아 있지만 HTTP 응답이 오지 않는 경우
  • 재시작 정책은 크래시를 잡습니다. 예: MySQL 프로세스가 오류 코드와 함께 종료되는 경우

중요한 단서도 하나 있습니다. standalone Docker나 일반 Docker Compose에서는 unhealthy 상태가 주로 정보 표시 역할을 합니다. Docker가 상태는 바꾸지만, 헬스체크 실패만으로 컨테이너를 자동 재시작하지는 않습니다. 반면 Swarm이나 Kubernetes 같은 오케스트레이터는 이 신호를 보고 교체나 재배치를 할 수 있습니다.

Dockerfile 수준 헬스체크

Healthcheck는 이미지 자체에 "이 컨테이너가 정상인지 이렇게 확인해 줘"라는 규칙을 적어 놓는 기능입니다. 예를 들어 nginx 이미지를 커스터마이징한다면 아래처럼 적을 수 있습니다.

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://127.0.0.1/ || exit 1
  • interval은 Docker가 검사를 얼마나 자주 실행하는지 뜻합니다.
  • timeout은 검사 명령이 끝나기를 얼마나 오래 기다릴지 뜻합니다.
  • retries는 몇 번 연속 실패해야 상태가 unhealthy로 바뀌는지 뜻합니다.
  • start-period는 서비스가 아직 시작 중일 때 초기 실패를 계산에서 빼도록 합니다.

검사 명령 자체도 컨테이너 내부에서 실제로 실행 가능한 것이어야 합니다. 여기서 wget --spider는 페이지 본문을 다운로드하지 않고 응답 가능 여부만 확인합니다. 뒤의 || exit 1은 명령이 실패하면 "헬스체크도 실패로 처리하라"는 뜻입니다.

이렇게 Dockerfile에 넣으면 해당 이미지로 컨테이너를 띄우는 모든 환경에서 기본 규칙으로 따라옵니다. nginx 말고도 MySQL이라면 mysqladmin ping -h 127.0.0.1 같은 명령을 넣어 DB가 응답하는지 확인할 수 있습니다.

검사가 retries 횟수만큼 연속으로 실패하면 컨테이너 상태는 unhealthy로 바뀌지만, 메인 프로세스 자체는 계속 실행됩니다. 이후 검사가 한 번이라도 성공하면 상태는 다시 healthy로 돌아옵니다.

Compose 수준 헬스체크

Compose에서는 서비스마다 더 자세한 설정을 덮어쓸 수 있습니다.

services:
  web:
    image: my-nginx:latest
    ports:
      - "8080:80"
    healthcheck:
      test: ["CMD", "wget", "--spider", "http://127.0.0.1" ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Dockerfile에 적어 둔 기본 규칙을 가져오되, Compose 레벨에서 timeout이나 start_period 같은 값을 환경에 맞게 더 넉넉하게 줄 수 있습니다. 같은 이미지라도 실행 환경이 달라지면 시작 시간과 응답 시간이 달라질 수 있기 때문입니다.

예를 들어 노트북에서는 빨리 켜지던 이미지가 실제 서버에서는 초기 데이터 로딩 때문에 더 오래 걸릴 수 있습니다. 서비스는 결국 정상인데 시작 직후 검사만 자꾸 실패한다면 start_period를 늘리고, 검사 명령이 늦게 끝나기만 한다면 timeout을 늘려 보세요.

restart: unless-stopped가 하는 일

restart 정책은 컨테이너가 갑자기 꺼질 때 어떻게 대응할지를 정합니다. 가장 자주 쓰는 값은 아래와 같습니다.

  • no: 자동으로 다시 시작하지 않습니다.
  • on-failure: 프로세스가 비정상 종료코드로 끝났을 때만 재시작합니다.
  • unless-stopped: 사용자가 docker compose stop으로 멈추지 않는 한 계속 자동 재시작합니다.
  • always: 어떤 이유로 꺼져도 무조건 다시 시작합니다.

상황에 따라 고르는 기준을 같이 기억해 두면 좋습니다.

  • no는 디버깅용 실습에서 좋습니다. 실패 후 컨테이너가 그대로 멈춰 있어야 원인을 보기 쉽기 때문입니다.
  • on-failure는 일시적인 오류로 프로세스가 죽을 수 있을 때 제한적인 자동 복구가 필요할 때 어울립니다.
  • unless-stopped는 장시간 실행해야 하는 서비스가 크래시 후 다시 살아나야 하지만, 사용자가 직접 멈춘 상태는 존중해야 할 때 적합합니다.
  • always는 거의 어떤 종료 상황에서도 다시 띄우고 싶은 서비스에만 신중하게 사용합니다.

핵심 구분은 그대로입니다. 헬스체크는 서비스 상태를 보고하고, 재시작 정책은 프로세스 종료에 반응합니다. 따라서 plain Docker에서는 unhealthy가 되었다고 자동 재시작되지 않습니다. 상태 신호를 바탕으로 자동 조치를 원한다면 그 신호를 이해하는 오케스트레이터가 필요합니다.

nginx와 MySQL로 실습해 보기

직접 손을 움직여 보면 차이가 더 분명하게 보입니다. 예전에 만든 실습용 컨테이너가 남아 있다면 먼저 정리하세요.

docker rm -f web db 2>/dev/null || true

그다음 아래 두 가지 미션을 진행해 보세요.

  1. nginx 컨테이너

    docker run -d --name web --health-cmd "wget --spider -q http://127.0.0.1 || exit 1" \
      --health-interval=10s --health-start-period=5s --restart unless-stopped nginx:alpine
    docker ps --format "table {{.Names}}\t{{.Status}}"
    docker exec web nginx -s quit
    docker ps --format "table {{.Names}}\t{{.Status}}"
    

    nginx 프로세스를 일부러 종료하면 메인 프로세스가 꺼지고, 이번에는 재시작 정책이 컨테이너를 다시 띄웁니다. 이후 헬스체크가 통과하면서 STATUS가 Restarting, Up ... (health: starting), Up ... (healthy) 같은 순서로 바뀌는 과정을 볼 수 있습니다.

  2. MySQL 컨테이너

    docker run -d --name db -e MYSQL_ROOT_PASSWORD=pass1234 \
      --health-cmd 'mysqladmin ping -h 127.0.0.1 -ppass1234 || exit 1' \
      --health-start-period=30s \
      --health-interval=20s --restart unless-stopped mysql:8
    docker ps --format "table {{.Names}}\t{{.Status}}"
    docker logs -f db --tail 20
    

    초기 기동 중에는 start-period 덕분에 초반 실패를 바로 계산하지 않고, 이후에는 ping 명령이 DB가 응답 중인지 알려 줍니다. 실제 운영에서는 이런 식으로 비밀번호를 명령줄에 직접 넣지 않는 편이 좋고, 여기서는 로컬 실습이라서 단순화한 예시라고 이해하면 됩니다.

실습하면서 헬스 데이터 자체도 직접 확인해 보세요.

docker inspect --format '{{json .State.Health}}' web

여기에는 Status, FailingStreak, 최근 검사 로그 같은 정보가 담깁니다. 이 출력을 보면 "프로세스가 죽은 상황"과 "프로세스는 살아 있지만 서비스 검사가 실패한 상황"을 더 쉽게 구분할 수 있습니다.

초보자가 먼저 익혀야 할 운영 감각 정리

여기서 꼭 기억할 것은 두 가지입니다.

  • 헬스체크는 정상인지 표시합니다.
  • 재시작 정책은 프로세스가 종료되었을 때 다시 일어나게 합니다.

즉, 둘은 비슷해 보여도 컨테이너 생명주기의 다른 지점을 담당합니다. 그래서 실제 서비스에서는 두 기능을 함께 쓰는 경우가 많습니다. 다음 글에서는 이렇게 살아 있는 컨테이너가 실제 웹 요청을 Nginx로 어떻게 처리하는지 살펴봅니다.

💬 댓글

이 글에 대한 의견을 남겨주세요