[Docker] 헬스 체크(health check)

▶︎ 실습 소스코드


▶︎ Health Check를 지원하는 도커 이미지 빌드하기

안정적인 운영을 위해 컨테이너에 Health check를 도입해보자!


‣ Health check (헬스 체크)

  • docker swarm이나 k8s의 경우 Container Platform 상에서 Application이 스스로 장애에서 회복하는 기능을 제공한다.
    • Platform이 container에서 실행 중인 Application 상태가 정상인지 확인하는 정보를 Image에 함께 패키징 함.
    • 비정상적인 Application이 발생하면 Container를 삭제하고 새 Container로 대체함.
  • Docker가 확인하는 것은 단순히 Process의 실행 여부이지, 정상/비정상 상태를 검사하지는 않음.
    • Process가 종료되었다면 Container도 종료되므로 기본적인 Health check가 이루어지기는 함.
  • Dockerfile에 명시하여 Docker Image에 상태 확인을 위한 로직을 추가해주기만 하면 된다.

‣ Health check 로직이 없는 경우

  • Container runtime은 Process 안에서 무슨 일이 일어나는지, Application의 정상 작동 여부를 판별할 수 없게된다.
docker run -d -it --name test -p 8080:80 diamol/ch08-numbers-api

# 아래 명령어를 계속 반복하면 4번째부터 요청이 실패된다.
curl localhost:8080/rng

/rng 경로로 여러번 요청을 시도하니 아래와 같이 요청이 실패되었다.

{"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1","title":"An error occured while processing your request.","status":500,"traceId":"|343bc9fc-4b5a2dad0196c6ef."}

그러나 컨테이너는 종료되지 않았다.
image

  • Application에 Status: 500 에러가 발생하여 동작하지 않게 되었음.
  • 그럼에도 container는 여전히 up인 상태로 정상 동작 중임.

‣ health check instruction

Dockerfile

HEALTHCHECK CMD curl --fail http://localhost/health
  • health check instruction: Docker가 컨테이너 안에서 실행하는 명령을 지정
    • 명령이 반환하는 상태코드로 Application 상태 판단
  • --fail: curl 명령이 상태 코드를 Docker에 전달
    • 성공하면 0, 실패하면 0 이외의 상태 코드 반환
    • 상태를 판단할 수 있다면 어떤 명령을 지정해도 무방
  • 도커는 해당 명령을 일정 시간 간격으로 수행하여, 일정 횟수 이상 실패 시 Container를 이상 상태로 간주

‣ health check가 반영된 컨테이너 실행

Dockerfile

FROM diamol/dotnet-sdk AS builder

WORKDIR /src
COPY src/Numbers.Api/Numbers.Api.csproj .
RUN dotnet restore

COPY src/Numbers.Api/ .
RUN dotnet publish -c Release -o /out Numbers.Api.csproj

# app image
FROM diamol/dotnet-aspnet

ENTRYPOINT ["dotnet", "/app/Numbers.Api.dll"]
HEALTHCHECK CMD curl --fail http://localhost/health

WORKDIR /app
COPY --from=builder /out/ .
# 끝에 .은 현재 작업 디렉토리를 의미. -f로 Dockerfile 경로를 명시
docker build --tag diamol/ch08-numbers-api:v2 -f ./numbers-api/Dockerfile.v2 .
docker run -d -it -p 8081:80 --name test diamol/ch08-numbers-api:v2

# 4번을 반복하면 마찬가지로 에러가 발생한다.
curl http://localhost:8081/rng

watch docker container ls

image
unhealthy로 변경되었음.

  • health check가 감지한 Container의 이상 상태는 Docker API를 통해 Docker에게 보고됨.
    • Docker가 이상 상태를 통보받고 Application 복구를 위한 조치를 취할 수도 있다.

‣ Container 상태 확인하기

docker container inspect $(docker container ls --last 1 --format '{{.ID}}') | grep -A 30 'State"'

image

  • 가장 최근의 health check 수행 결과가 저장되기 때문에 로그 열람이 가능하다.
    • Status: 현재 container 상태
    • FailingStreak: Container에 대해 연속으로 check 실패한 횟수. 기본값은 3번을 넘으면 비정상 상태로 간주한다.

🟠 Docker가 상태 이상의 Container를 다른 Container로 교체하지 않는 이유

  • Cluster 환경에서 동작하는 Docker swarm이나 Kubernetes와는 달리 Docker engine은 단일 서버에서 동작하기 때문에, Container 교체 작업을 안전하게 처리할 수 없다! (리소스 한계)
  • 이상이 생긴 Container를 중지하고 재시작할 수는 있지만, 그동안 Application은 동작하지 않는 문제가 발생한다.
  • 설령 완전히 같은 설정으로 Container를 실행한다 하더라도, 기존의 Container에 보관된 데이터가 유실되고 그 시간 동안 Application이 동작하지 않는다.
  • 즉, Docker는 할 수는 있지만 리스크가 너무 크기 때문에 그냥 냅두고 Health check만 반복하는 것이다.
  • 반면에 Cluster 환경에서는 Container를 추가로 실행할 여력이 항상 남아 있기 때문에 이상 상태를 보이는 Container를 그대로 두고, 대체 Container를 실행하여 Application 중단 시간 없이 회복이 가능하다.

▶︎ Dependency check가 적용된 컨테이너 실행하기

🔑 Cluster 환경에서 Container 재실행시 발생하는 문제점

  • Cluster 환경에서 Health check는 용이하지만 Dependency check는 어려움.
    • 서버가 한 대인 경우 Container 실행 순서를 보장할 수는 있지만, Cluster 환경은 서버가 여러 대이므로 그렇지 않음.
    • 이상이 생긴 Container 교체시 처음 실행할 때 처럼 Container의 Dependency를 고려하지 않음
  • 이로 인해 API는 실행되기도 전에 web application이 먼저 실행될 수도 있음

‣ API 없이 web Application만 실행하는 경우

# 기존 컨테이너 모두 삭제
docker rm -f $(docker ps -aq)

docker run -d -it --name test -p 8082:80 diamol/ch08-numbers-web

docker container ls

핵심 의존 관계를 만족하지 않았기 때문에 Application이 정상 작동하지 않는 상태 image


‣ Dependency check를 위한 명령어 수정

{% include codeHeader.html name=“Dockerfile” %}

FROM diamol/dotnet-aspnet

ENV RngApi:Url=http://numbers-api/rng

CMD curl --fail http://numbers-api/rng && \
    dotnet Numbers.Web.dll

WORKDIR /app
COPY --from=builder /out/ .

curl --fail http://~ 가 성공해야 && 뒤의 명령이 실행된다.


# api Container가 실행되지 않았으므로 반드시 실패한다.
docker container run -d -p 8084:80 diamol/ch08-numbers-web:v2

docker container ls --all
  • Dependency check는 Application 실행 전에 한 번만 실행되며, 필요한 요구 사항을 확인한다.
  • 별도의 Instruction으로 구현되는 것이 아니라, Application 실행 명령에 로직을 추가한다.
  • 만약 curl 명령의 반환값이 실패한다면 Container를 종료해버린다.

▶︎ Docker Compose에 Health & Dependency check 명시하기

🟢 Docker compose와 상태 이상의 container

  • Docker Compose 또한 Application 상태 이상 발생 시 이를 복원할 능력이 있지만 대체하지 않음. (단일 서버이므로)
  • 하지만 Dockerfile에 정의되지 않은 Health check를 추가하고, 종료된 Container를 재시작할 수 있다.

‣ Compose 파일에서 Health check 명시하기

healthcheck.yaml

version: "3.8"

services:
  numbers-api:
    image: diamol/ch08-numbers-api:v3
    ports:
      - "8087:80"
    healthcheck:
      interval: 5s
      timeout: 1s
      retries: 2
      start_period: 5s
    networks:
      - app-net

networks:
  app-net:
    driver: bridge
    external: false

Docker compose에서는 Health check 옵션을 더 세세하게 설정할 수 있다.

  • interval : Health check 실시 간격
  • timeout : 이 때까지 응답을 받지 못하면 실패로 간주
  • retries : Container 상태를 이상으로 간주할 때까지 필요한 연속 실패 횟수
  • start_perioid : Container 실행 후 처음 Health check를 실시하는 간격. (Application 시작하는 데 시간이 오래 걸리면 필요)
  • 일반적으로 설정값은 이상 발생을 파악하는 속도와 허용할 수 있는 장애 오탐지 빈도를 고려해 결정한다.
  • CPU와 메모리 자원이 필요하므로 운영 환경에서는 Health check 간격을 좀 더 길게 잡는 것이 좋다.

healthcheck-cmd.yaml

...

  numbers-web:
    image: diamol/ch08-numbers-web:v3
    restart: on-failure
    environment:
      - RngApi__Url=http://numbers-api/rng
    ports:
      - "8088:80"
    healthcheck:
      test: ["CMD", "dotnet", "Utilities.HttpCheck.dll", "-t", "150"]
      interval: 5s
      timeout: 1s
      retries: 2
      start_period: 10s
    networks:
      - app-net

...
  • test : Health check를 위해 실행하는 명령 (Image에서 Health check 정의 안해도 Compose 파일에서 가능하다.)
  • restart: “on-failure” 설정은 Container가 예기치 않게 종료되면 재시작한다.

web 컨테이너의 경우 api 컨테이너에 의존성을 갖도록 image가 만들어져 있지만

정작 Compose 파일에는 depends_on 설정을 하지 않아 Docker Compose가 컨테이너 실행 순서를 고려하지 않는다.

하지만 API 컨테이너가 실행되기도 전에 web 컨테이너가 먼저 실행되버리면 healthcheck의 test에 등록된 dependency check에 의해 종료된다.

그러나 웹 컨테이너의 경우 restart: always가 명시되어 있기에 종료되도 다시 실행된다.

결론적으로 API 컨테이너는 무사히 실행되며, Dependency check도 통과하여 Application이 정상적으로 동작할 것이다.

✒️ Compose 파일에 depends_on 설정을 하지 않은 이유

Docker compose가 dependency check를 할 수 있는 범위는 단일 서버에 국한된다.반면, 실제 운영 환경은 단일 서버에서 동작하지 않는다.

보통은 클러스터를 구축할 것이다.
이렇게 되면 운영 환경에서 Application이 실제 시작할 때 일어나는 상황을 예측하기가 힘들어진다.


‣ 실행해보기

docker-compose up -d --build

docker container ls

docker-compose logs numbers-web

image

  • Compose 파일에 의존관계를 설정하지 않아 web Container가 먼저 실행되었다.
  • API Container는 CREATED와 STATUS를 비교했을 때, 실행까지 약 3~4초가 소요되었다.
  • Web Container도 실행은 됐으나 10초 가량이 소요되었다.
  • Web Container 로그를 확인해보면 처음 HTTP 테스트가 응답 거부로 에러가 발생했었다.

관련 글

지식 공유2분 읽기

[Docker] 컨테이너(Container)에 할당되는 CPU 제한하기

개요 이번 게시글에서는 도커 컨테이너에서 CPU를 최적으로 활용하는 방법에 대해서 기록해보려고 한다. 호스트 CPU Core 정보 확인 먼저 컨테이너에 CPU를 최적으로 할당하기 위해서는, 현재 호스트 시스템의 CPU 정보에 대해서...

도커
지식 공유1분 읽기

[Docker] 배그 최고급 꾸러기 시뮬레이터 배포

개요 최고급 꾸러미에 대한 확률 체감을 위한 시뮬레이터 프로그램을 구현하고 도커로 배포하였음. 배포 로직은 AWS EC2 인스턴스에 도커를 사용하여 nginx proxy manager를 배포하고 리버스 프록시로 홈 서버에 있는 시뮬레...

도커