컨테이너에서 실행 중인 애플리케이션을 운영 환경에 맞게 다듬는 일을 이제 할 것이다. 운영 환경에서는 도커 스웜이나 쿠버네티스 같은 컨테이너 플랫폼상에서 애플리케이션을 실행하게 될 텐데, 이들 플랫폼은 애플리케이션이 스스로 이상에서 회복할 수 있도록하는 기능을 제공한다.(실행 중인 것인 정상인지도 확인)
이번에는 플랫폼이 제공하는 기능을 활용하기 위해 필요한 정보를 컨테이너 이미지에 추가하는 방법을 알아본다.
도커는 컨테이너를 시작할 때마다 애플리케이션의 기본적인 상태를 확인한다. 여기서 기본적인 상태란 프로세스의 실행 상태이다. 컨테이너의 처리 용량을 뛰어넘는 수의 용청이 들어오면 오류를 내보내는데 컨테이너의 프로세스는 여전히 정상 실행 중이며 도커는 이로 인해 정상이라 판단한다. 도커는 실제로 정상인지 확인하기 위해 도커 이미지를 직접 넣을 수 있다. Dockerfile 스크립트에 상태 확인을 위한 로직을 추가하면 된다.
일단 문제를 먼저 체험해보자
docker container run -d -p 8080:80 diamol/ch08-numbers-api
curl http://localhost:8080/rng
curl http://localhost:8080/rng
curl http://localhost:8080/rng
curl http://localhost:8080/rng
docker container ls
컨테이너 목록을 확인하면 해당 컨테이너의 상태는 여전히 Up상태임을 알 수 있다.
FROM diamol/dotnet-aspnet
ENTRYPOINT ["dotnet", "/app/Numbers.Api.dll"]
HEALTHCHECK CMD curl --fail http://localhost/health
WORKDIR /app
COPY --from=builder /out/ .
이 중 HEALTHCHECK를 보자. 컨테이너 런타임은 이 인스럭션에 정의된 정보를 이용해 컨테이너에서 동작 중인 애플리케이션의 상태가 정상적인지 확인할 수 있다.
해당 인스트럭션에서 사용된 명령은 curl 명령으로, 호스트 컴퓨터에서 API 상태를 확인한 방법과 비슷하다. 다른 점은 컨테이너 내부에서 실행된다는 점이다. (URL/health는 버그가 발동했는지 확인하기 위한 또 다른 API 엔드포인트다. 버그 발동시 500(Internal Server Error), 정상 200(OK))
나머지 부분을 살펴보자.
닷넷 코어 어플리케이션이므로 ENTRYPOINT 인스트럭션에서 dotnet 명령을 실행한다. 따라서 도커가 애플리케이션 상태를 확인하기 위해 모니터링하는 프로세스도 dotnet이다. 헬스 체크 시에는 엔드포인트 /health로 HTTP 요청을 보내는데, --fail 옵션을 붙이면 curl이 전달받은 상태 코드를 도커에 전달한다. 성공하면 curl이 0을 반환하고 실패하면 그 외의 값을 반환한다.
이번엔 위의 스크립트로 새로운 버전의 이미지를 빌드할 것인데 build 부명령을 사용하는 방법을 먼저 알아보자.
일반적으로 Dockerfile 스크립트의 파일명은 Dockerfile이고, 도커는 이 이름을 가진 파일을 찾아 빌드를 시도한다. 이번엔 파일명이 다르고 위치한 경로도 다르다할 것이다. 이런 경우 먼저 위치와 파일명을 지정해야 한다.
docker image build -t diamol/ch08-numbers-api:v2 -f ./numbers-api/Dockerfile.v2 .
이제 헬스 체크 기능을 갖춘 애플리케이션을 실행할 수 있다. 여기에 더해 헬스 체크 간격과 애플리케이션의 상태를 이상으로 간주하는 누적 실패 횟수도 설정할 수 있다. 기본 값은 30초 간격, 3회 이상이다.
docker container run -d -p 8081:80 diamol/ch08-numbers-api:v2
unhealthy상태임을 알았지만 컨테이너는 여전히 실행 중이다. 컨테이너의 이상 상태는 도커 API를 통해 보고된다. 따라서 컨테이너를 실행 중인 플랫폼도 복구 조치를 할 수 있다. 헬스 체크가 숭행 중이라면 그 결과 역시 inspect 명령에서 볼 수 있다.
docker container inspect $(docker container ls --last 1 --format '{{.ID}}')
위의 캡쳐를 통해 14번 헬스 체크가 실패했다는 것을 알 수 있고 헬스 체크 로그도 볼 수 있다.
하지만 여전히 실행 중이라 나온다. 도커는 재시작이나 다른 컨테이너 교체를 안전하게 처리할 수 없기 때문이다. 시도하다 데이터가 날라가거나 애플리케이션이 중지되는 등의 문제가 발생할 수 있기 때문이다.
헬스 체크는 동작 중인 애플리케이션의 상태를 확인할 수 있는 수단이었다.
분산 애플리케이션에선 컨테이너를 교체할 때 의조솬계를 고려하지 않기 때문에 다른 문제를 겪을 수 있다.
docker container rm -f $(docker container ls -aq)
docker container run -d -p 8082:80 diamol/ch08-numbers-web
docker container ls
해당 예시는 컨테이너가 실행 중이고 애플리케이션이 정상임에도 제대로 동작하지 않는 예시이다.
API를 사용할 수 없는 상태이므로 웹 애프리케이션도 제대로 동작하지 않는다.
의존 관계를 만족하는지 점검하는 디펜던시 체크 기능도 도커 이미지에 추가할 수 있다. 디펜던시체크는 애플리케이션 실행 전에 필요한 요구 사항을 확인하는 기능으로, 실행 시점이 헬스 체크와 조금 다르다. 요구 사항 중 하나라도 만족하지 않으면 애플리케이션이 실행되지 않는다.
헬스 체크 처럼 인스트럭션이 아닌 디펜던시 체크는 애플리케이션 실행 명령에 로직을 추가하는 방법으로 구현된다.
# app image
FROM diamol/dotnet-aspnet
ENTRYPOINT ["dotnet", "Numbers.Api.dll"]
HEALTHCHECK CMD ["dotnet", "Utilities.HttpCheck.dll", "-u", "http://localhost/health"]
WORKDIR /app
COPY --from=http-check-builder /out/ .
COPY --from=builder /out/ .
이 인스트럭션을 보면 API에 HTTP 요청을 보내 API가 사용 가능한지 확인한다. 그 뒤에 &&를 사용하여 앞이 성공하면 뒤에 오는 명령을 실행한다.
docker container run -d -p 8084:80 diamol/ch08-numbers-web:v2
docker container ls --all
스크린샷을 보면 최근 것은 애초에 애플리케이션이 동작하지 않는다. 하지만 실행이 되지 않는 애플리케이션은 UP상태임을 알 수 있다.
흔히 가장 사용한 검증 방식은 curl을 사용한 가장 기본적인 HTTP 요청 테스트다.
curl은 웹 애플리케이션이나 API를 테스트하는 데 매우 유용한 도구다. 다만 보안 정책상의 이유로 이미지에 curl을 포함시킬 수 없기 때문이다. 이런 이유로 커스텀 유틸리티를 사용하는 것이 좋다.
애플리케이션과 같은 언어로 구현된 커스텀 유틸리티 장점은 다음과 같다.
FROM diamol/dotnet-aspnet
ENTRYPOINT ["dotnet", "Numbers.Api.dll"]
HEALTHCHECK CMD ["dotnet", "Utilities.HttpCheck.dll", "-u", "http://localhost/health"]
WORKDIR /app
COPY --from=http-check-builder /out/ .
COPY --from=builder /out/ .
여태의 스크립트와 차이점은 컨테이너 검사 중 출력되는 로그가 좀 적어진다는 정도다.(헬스 체크 수행 한 번에 한 줄의 결과만 출력) 따라서 API를 몇 번 호출하고 나면 이상 상태를 보고할 것이다.
docker container rm -f $(docker container ls -aq)
docker container run -d -p 8080:80 --health-interval 5s diamol/ch08-numbers-api:v3
curl http://localhost:8080/rng
curl http://localhost:8080/rng
curl http://localhost:8080/rng
curl http://localhost:8080/rng
docker container ls
# app image
FROM diamol/dotnet-aspnet
ENV RngApi__Url=http://numbers-api/rng
CMD dotnet Utilities.HttpCheck.dll -c RngApi:Url -t 900 && \
dotnet Numbers.Web.dll
WORKDIR /app
COPY --from=http-check-builder /out/ .
COPY --from=builder /out/ .
여기서 사용한 -t옵션은 유티리티가 요청에 대한 응답을 기다릴 제한 시간을 설정한 것이고, -c 옵션은 애플리케이션과 같은 설정 파일을 읽어 그 설정대로 대상 URL을 지정한 것이다.
docker container run -d -p 8081:80 diamol/ch08-numbers-web:v3
docker container ls --all
웹 애플리케이션 컨테이너의 디펜던스 체크가 실패해 컨테이너가 종료된 상태이다.
도커 컴포즈는 종료된 컨테이너를 재시작하거나 이미지에 정의되지 않은 헬스 체크를 추가할 수는 있다.
이하는 헬스 체크 옵션을 설정한 것이다.
numbers-api:
image: diamol/ch08-numbers-api:v3
ports:
- "8087:80"
healthcheck:
interval: 5s
timeout: 1s
retries: 2
start_period: 5s
networks:
- app-net
여기서
이하는 헬스 체크를 정의한 것이다.
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
해당 스크립트 내용 중 restart: on-failure이 있으므로 예기치 않게 종료되면 컨테이너를 재시작한다. 그러나 의존 관계를 정의한 depends_on이 없으므로 어떤 순서로든 실행할 수 있다.
docker-compose up -d
docker container ls
docker container logs numbers-numbers-web-1
depends_on 설정을 사용해 직접 디펜던시 체크를 하도록 하지 않는지 궁금한 독자도 있을 텐데 이유는 도커 컴포즈가 디펜던시 체크를 할 수 있는 범위가 단일 서버로 제한되기 때문이다.
디펜던시 체크와 헬스 체크를 도입하면 처음부터 플랫폼이 실행 순서를 보장하게 할 필요가 없기 때문이다.
다만 헬스 체크는 주기적으로 자주 실행 되므로, 시스템에 부하를 주는 내용이어서는 안된다. 자원을 너무 많이 소모하지 않으면 애플리케이션이 실질적으로 동작 중인지 검증할 수 있는 부분을 테스트해야 한다. 디펜던시 체크는 애플리케이션 시작 시에만 실행된다. 그러므로 테스트에 소모되는 리소스에 너무 크게 신경 쓸 필요는 없다.