본문 바로가기
스터디/docker 스터디

헬스 체크와 디펜던시 체크로 애플리케이션의 신뢰성 확보하기

by xladmt 2025. 7. 4.

0. 들어가며

도커 컨테이너에서 실행 중인 애플리케이션 상태가 정상적으로 동작하는지 확인하는 것은 중요하다. 도커에서도 헬스 체크와 디펜던시 체크 기능을 통해 애플리케이션이 비정상 상태라는 것을 알 수 있다. 플랫폼이 제공하는 기능을 활용하기 위해 필요한 정보를 컨테이너 이미지에 추가하는 방법을 알아보자. 

- 헬스 체크 : 서버의 상태를 주기적으로 확인하여 서버의 정상 작동 여부를 판단하는 과정
- 디펜던시 체크 : 애플리케이션이 실행되기 전에 필요한 외부 리소스나 서비스, 즉 의존성이 정상적으로 작동하는지 확인하는 과정

 

1. 헬스 체크를 지원하는 도커 이미지 빌드하기

먼저, 헬스 체크 로직이 없는 경우부터 살펴보자. 다음은 무작위 숫자를 반환하는 간단한 REST API이다. 이 애플리케이션에는 버그가 있어 세 번 API를 호출하고 나면 비정상 상태에 빠지며 그 이후의 호출은 실패한다.

# 1. API 컨테이너를 실행한다.
docker container run -d -p 8080:80 diamol/ch08-numbers-api

# 2. API를 세 번 호출한다.(각 호출마다 무작위 숫자가 반환된다.)
curl http://localhost:8080/rng
curl http://localhost:8080/rng
curl http://localhost:8080/rng

# 3. 네 번째부터 API 호출이 실패한다.
curl http://localhost:8080/rng

# 4. 컨테이너의 상태를 확인한다.
docker container ls

실행 결과

컨테이너의 상태를 확인해보면 API 호출을 실패(HTTP 500, Internal Server Error)임에도 불구하고 여전히 Up 상태임을 알 수 있다. 컨테이너 안에서 동작하는 프로세스의 상태도 역시 실행 중이다. 도커의 입장에서는 애플리케이션에 문제가 없다. 컨테이너 런타임은 프로세스 안에서 무슨 일이 일어나는지, 애플리케이션이 정상적으로 동작 중인지 알 방법이 없다. 그렇기 때문에 헬스체크가 필요하다.

 

Dockerfile에서 HEALTHCHECK 인스트럭션을 보자. 컨테이너 런타임은 이 인스트럭션에 정의된 정보를 이용해 컨테이너에서 동작 중인 애플리케이션의 상태가 정상인지 확인할 수 있다. 이 명령어가 반환되는 상태 코드가 정상이면 컨테이너도 정상으로 간주되지만, 상태 코드가 연속으로 일정 횟수 이상 실패로 나오면 해당 컨테이너를 이상 상태로 간주한다.

Dockerfile 스크립트의 HEALTHCHECK 인스트럭션

  • --fail 옵션 : curl이 전달받은 상태 코드를 도커에 전달한다. 요청이 성공하면 curl이 0을 반환하고 실패하면 0 이외의 숫자를 반환하는데, 도커는 0을 헬스 체크 정상, 0 이외의 값을 비정상으로 간주한다.

 

자, 그럼 헬스체크가 잘 동작하는지 확인해보자. 헬스 체크 기능을 갖춘 애플리케이션을 실행해보겠다.

# Dockerfile 스크립트가 있는 파일로 이동
cd ./ch08/exercises/numbers

# -f 옵션을 붙여 Dockerfile 스크립트 파일의 경로를 지정
docker image build -t diamol/ch08-numbers-api:v2 0f ./numbers-api/Dockerfile.v2

# 버전 v2 이미지로 API 컨테이너 실행
docker container run -d -p 8081:80 diamol/ch08-numbers-api:v2

# 30초 정도 기다린 후 컨테이너 목록 확인
docker container ls

# API 4번 호출
curl http://localhost:8081/rng
curl http://localhost:8081/rng
curl http://localhost:8081/rng
curl http://localhost:8081/rng

# 애플리케이션이 이상 상태에 빠졌다. 90초(도커가 이상 상태 감지 시간) 후 상태를 확인해보자.
docker container ls

unhealthy 상태

애플리케이션에 이상이 발생하니 컨테이너의 상태가 아직 실행 중이지만 unhealthy로 나타났다. 컨테이너 이상 상태는 도커 API를 통해 보고 된다. 따라서 컨테이너를 실행 중인 플랫폼도 컨테이너의 이상 상태를 통보받고 애플리케이션을 복구하기 위한 조치를 취할 수 있다. 

 

그리고 가장 최근의 헬스 체크 수행 결과도 저장돼 있어 컨테이너의 상태를 조사할 때 이 결과를 열람할 수 있다. 

docker container inspect $(docker container ls --last 1 --format '{{.ID}}')

헬스 체크 수행 결과

  • Health : 현재의 헬스 체크 상태
  • FailingStreak : 연속 실패한 횟수
  • Log : 가장 최근에 수행한 헬스 체크의 정보

헬스 체크 명령에서 HTTP 상태 코드 500이 나오면 실패가 되는데, 헬스 체크가 연속으로 여섯 번 실패하면 컨테이너의 상태가 unhealthy로 바뀐다.

 

왜 이상 상태에 있는 컨테이너를 재시작하거나 다른 컨테이너로 교체하지 않을까?

위의 헬스 체크 수행 결과를 보면, 애플리케이션이 이상 상태임에도 컨테이너의 상태는 여전히 실행 중(Running)이라고 나온다. 그럼에도 재시작하거나 다른 컨테이너로 교체되지 않는 이유가 있다.

그 이유는, 도커가 이런 작업을 안전하게 처리할 수 없기 때문이다. 도커 엔진은 단일 서버에서 동작하는데, 이상이 생긴 컨테이너를 도커가 중지하고 재시작할 수는 있지만 그 시간 동안에는 애플리케이션이 동작하지 않는다. 만약 그렇게 하게 된다면, 컨테이너에 보관된 데이터가 유실되고 그 시간 동안 애플리케이션도 동작하지 않는다. 도커 입장에서 직접 컨테이너를 교체하게 된다면 상황을 더 악화시키지 않을 것이라는 보장이 없으므로, 이상 상태 발생을 통보만 할 뿐 컨테이너는 그대로 두는 것이다. 물론 헬스 체크도 계속 수행한다. 만약, 일시적인 실패였다면, 컨테이너의 상태가 다시 정상(healthy)으로 돌아간다.

 

2. 디펜던시 체크가 적용된 컨테이너 실행하기

헬스 체크는 여러 개의 서버로 구성된 클러스터는 일부 컨테이너가 비정상 상태가 되더라도 새 컨테이너를 실행해 상태를 복구할 수 있다. 그러나 여러 컨테이너에 나뉘어 실행되는 분산 애플리케이션은 이와는 또 다른 문제를 겪을 수 있다. 이상이 생긴 컨테이너를 교체할 때는 처음 애플리케이션을 실행할 때 처럼 컨테이너 간 의존 관계 (dependency)를 고려하지 않기 때문이다.

 

그렇다면, 의존 관계를 검증하지 않는 애플리케이션일 경우를 살펴보자.

# 실행중인 모든 컨테이너를 제거해 동작 중인 API 컨테이너가 없게 한다.
docker container rm -f $(docker container ls -aq)

# 웹 애플리케이션 실행 후, 웹 브라우저에서 접근해보자.
docker container run -d -p 8082:80 diamol/ch08-numbers-web

# 컨테이너 확인
docker container ls

 웹 브라우저에서 http://localhost:8082에 접근하면 버튼이 제대로 동작하지 않는다.

API를 사용할 수 없어 제대로 동작하지 않음.

컨테이너 상태는 정상인데 핵심 의존 관계를 만족하지 안항 애플리케이션이 정상적으로 동작하지 않는 상황이다. 의존 관계를 만족하는지 점검하는 디펜던시 체크 기능도 도커 이미지에 추가할 수 있다. 디펜던시 체크는 애플리케이션 실행 전에 필요한 요구 사항을 확인하는 기능으로, 주기적으로 실행하는 헬스 체크와 실행 시점이 다르다. 모든 요구 사항이 확인되면 디펜던시 체크가 성공하고 애플리케이션이 실행된다. 반대로 만족하지 못하는 요구 사항이 있다면 디펜던시 체크가 실패해 애플리케이션이 실행되지 않는다.

 

디펜던시 체크는 헬스 체크처럼 별도의 인스트럭션으로 도커에 구현된 것은 아니고 애플리케이션 실행 명령에 로직을 추가하는 방법으로 구현한다. 아래는 API 사용 가능 여부를 확인하기 위해 기반 이미지에 포함된 유틸리티인 curl을 사용한다.

API 사용 가능 여부를 확인하기 위해 curl을 사용한 디펜던시 체크

  • CMD 인스트럭션에 정의된 명령은 컨테이너를 실행할 때 실행된다.
  • 이 명령은 API에 HTTP 요청을 보내 API가 사용 가능한지 확인하다.
  • &&는 && 앞에 오는 명령이 성공하면 뒤에 오는 명령을 실행한다.

 

3. 애플리케이션 체크를 위한 커스텀 유틸리티 만들기

curl은 웹 애플리케이션이나 API를 테스트하는 데 매우 유용하지만 몇 가지 단점이 있다. 첫 번째로, 보안 정책상의 이유로 이미지에 curl을 포함시킬 수 없다. 두 번째는 도커 이미지에는 애플리케이션을 구동하는 데 필요한 최소한의 내용만 들어가야 한다. 사용하지 않을 도구를 추가해 봤자 이미지의 크기만 증가하고 외부 공격에 노출될 여지가 있다. 그렇기 때문에, 실제 애플리케이션 체크에는 애플리케이션과 같은 언어로 구현된 별도의 커스텀 유틸리티를 사용하는 것이 낫다.

 

3-1. 애플리케이션과 같은 언어로 구현된 커스텀 유틸리티의 장점

  • 커스텀 유틸리티를 실행할 때도 애플리케이션과 같은 도구를 사용하므로 이미지에 추가적인 소프트웨어를 포함시킬 필요가 없다.
  • 재시도 횟수나 분기 등 셸 스크립트로는 표현하기 까다로운 복잡한 체크 로직을 적용할 수 있다. 특히 리눅스와 윈도우 양쪽에서 사용할 크로스 플랫폼 이미지라면 더욱 유용하다.
  • 애플리케이션과 같은 설정을 사용해 대상 URL을 여러 곳에 반복 정의하거나 수정에서 누락시키는 일을 방지할 수 있다.
  • 애플리케이션과 같은 라이브러리 환경에서 데이터베이스 접속이나 인증서 파일의 존재 유무 등 컨테이너 실행 전에 확인이 필요한 모든 사항을 검증할 수 있다.

 

3-2. 커스텀 유틸리티 만들기

아래는 HEALTHCHECK 인스트럭션에서 curl 대신 닷넷 코어로 구현된 테스트 유틸리티이다.

커스텀 유틸리티로 헬스 체크에서 curl 대체하기

실행 방법은 이전 방식과 동일하다. API를 v3 버전의 이미로 실행하고 결과를 확인하면 헬스 체크가 정상 작동하여 컨테이너 상태가 unhealthy로 나올 것이다.

 

디펜던시 체크에도 같은 유틸리티를 사용했다.

디펜던시 체크에도 커스텀 유틸리티 사용하기

  • -t : 유틸리티가 요청에 대한 응답을 기다릴 제한 시간 설정
  • -c : 애플리케이션과 같은 설정 파일을 읽어 그 설정대로 대상 URL 지정
# 버전 v3를 실행하기. API가 없으므로 컨테이너가 바로 종료된다.
docker container run -d -p 8081:80 diamol/ch08-numbers-web:v3

# 컨테이너 확인
docker container ls --all

종료됨

 

4. 도커 컴포즈에 헬스 체크와 디펜던시 체크 정의하기

도커 컴포즈에는 애플리케이션의 상태에 이상이 생겼을 때 어느 정도 복원할 수 있는 기능이 있다. 그러나 도커 컴포즈도 이상이 생긴 컨테이너를 새 컨테이너로 대체하지 않는다. 단일 서버에서 애플리케이션을 실행 중이라면 더 심각한 장애를 일으킬 수 있기 때문이다. 하지만 종료된 컨테이너를 재시작하거나 이미지에 정의되지 않은 헬스 체크를 추가할 수는 있다.

 

4-1. 도커 컴포즈 파일에서의 헬스 체크 옵션

  • interval : 헬스 체크 실시 간격
  • timeout : 그때까지 응답을 받지 못하면 실패로 간주하는 제한 시간
  • retries : 컨테이너 상태를 이상으로 간주할 때까지 필요한 연속 실패 횟수
  • start_period : 컨테이너 실행 수 첫 헬스 체크를 실시하는 시간 간격

실제 설정값은 애플리케이션의 이상 발생을 파악하는 속도와 허용할 수 있는 장애 오탐지 빈도를 고려해 결정한다. 헬스 체크를 실시하는 데도 CPU와 메모리 자원이 필요하므로 운영 환경에서는 자원을 고려하여 설정해야 한다.

 

도커 컴포즈 파일

  • restart: on-failure : 컨테이너가 예기치 않게 종료되면 컨테이너를 재시작한다.
  • depends_on 설정이 없으므로 도커 컴포즈는 컨테이너를 어떤 순서로든 실행 할 수 있다.
    • API 컨테이너의 준비가 끝나기 전에 웹 애플리케이션 컨테이너가 실행되면 디펜던시 체크가 실패해 웹 컨테이너가 종료된다. 하지만 종료시 재시작하는 기능이 있으므로 기존 컨테이너가 재시작된다. 결국 API 컨테이너도 실행되어 마지막에는 디펜던시 체크도 성공해 애플리케이션이 제대로 동작한다. 
    • depends_on 설정을 사용하지 않는 이유는, 도커 컴포즈가 디펜던시 체크를 할 수 있는 범위가 단일 서버로 제한되기 때문이다. 

 

5. 헬스 체크와 디펜던시 체크로 복원력 있는 애플리케이션을 만들 수 있는 이유

디펜던시 체크와 헬스 체크를 도입하면 처음부터 플랫폼이 실행 순서를 보장하게 할 필요가 없다. 일부 컨테이너가 의존 관계를 만족하지 못한 상태라면 재실행되거나 다른 컨테이너로 교체될 것이다. 이런 방법이면 대규모 애플리케이션의 경우 완전 동작 상태가 되는 데 몇 분 정도가 걸린다. 하지만 그동안에도 애플리케이션이 동작하며 요청을 처리할 수 있다. 

애플리케이션 자기 수복이란?
일시적인 오류를 플랫폼이 해소해 주는 것이다. 애플리케이션에 메모리 누수를 일으키는 까다로운 버스가 있더라도 플랫폼에서 해당 컨테이너를 메모리를 잃지 않은 새 컨테이너로 대체하면 된다. 버그를 수정한 것은 아니지만 애플리케이션은 계속 동작할 수 있다.

 

하지만 헬스 체크와 디펜던시 체크에 주의가 필요하다. 헬스 체크는 주기적으로 자주 실행되므로, 시스쳄에 부하를 주는 내용이어서는 안된다. 자원을 너무 많이 소모하지 않으면서 애플리케이션이 실질적으로 동작 중인지 검증할 수 있는 핵심적인 부분을 테스트해야 한다. 디펜던시 체크는 애플리케이션 시작 시에만 실행된다. 그러므로 리소스에는 크게 신경쓰지 않아도 되지만 테스트 대상이 빠짐없이 정확하도록 주의해야 한다.

 

[참고]

도커 교과서 8장(엘튼 스톤맨 지음, 심효섭 옮김)