▶︎ 실습 소스코드
▶︎ 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."}
그러나 컨테이너는 종료되지 않았다.
- 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 /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
unhealthy
로 변경되었음.
- health check가 감지한 Container의 이상 상태는 Docker API를 통해 Docker에게 보고됨.
- Docker가 이상 상태를 통보받고 Application 복구를 위한 조치를 취할 수도 있다.
‣ Container 상태 확인하기
docker container inspect $(docker container ls --last 1 --format '{{.ID}}') | grep -A 30 'State"'
- 가장 최근의 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이 정상 작동하지 않는 상태
‣ 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 /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
- Compose 파일에 의존관계를 설정하지 않아 web Container가 먼저 실행되었다.
- API Container는 CREATED와 STATUS를 비교했을 때, 실행까지 약 3~4초가 소요되었다.
- Web Container도 실행은 됐으나 10초 가량이 소요되었다.
- Web Container 로그를 확인해보면 처음 HTTP 테스트가 응답 거부로 에러가 발생했었다.