책 "도커 교과서"의 "8장 헬스 체크와 디펜던시 체크로 애플리케이션의 신뢰성 확보하기"를 기반으로 정리한 포스트 입니다.
핵심 주제
운영 환경에서는 컨테이너 플랫폼인 도커 스웜, 쿠버네티스 등을 사용하여 애플리케이션을 실행한다. 이런 플랫폼은 애플리케이션의 상태를 자동으로 모니터링하고 문제가 발생하면 복구하는 기능을 제공한다.
애플리케이션의 상태 체크 정보는 컨테이너 이미지에 포함되어 플랫폼이 이를 활용하여 컨테이너의 상태를 파악한다. 만약 애플리케이션이 정상적으로 작동하지 않는 것으로 판단되면, 플랫폼은 해당 컨테이너를 제거하고 새로운 컨테이너를 배포하게 된다.
도커의 기본 상태 점검
: 특정 프로세스의 실행 상태를 체크
- 도커는 컨테이너를 실행할 때 애플리케이션의 기본 상태를 점검한다.
- 프로세스가 종료되면, 해당 컨테이너도 종료 상태가 됩니다.
- 애플리케이션 실행 파일, 자바나 닷넷 같은 런타임, 혹은 셸 스크립트 등 특정 프로세스의 실행 상태를 체크한다.
- 프로세스가 비정상 종료되었는지, 컨테이너가 종료되었는지 등 기본적인 애플리케이션 상태를 파악할 수 있다.
- 특히 클러스터 환경에서는 플랫폼이 종료된 컨테이너를 재시작하거나 새 컨테이너로 교체하는 작업을 자동으로 수행한다.
- 그러나 프로세스의 실행 상태만을 보장하는 것이지 애플리케이션의 전체적인 건강 상태를 보장하는 것은 아니다.
- ex. 웹 애플리케이션의 처리 용량을 초과하는 요청이 들어왔을 때, 애플리케이션은 503 'Service Unavailable' 오류를 반환한다. 컨테이너 내의 프로세스는 여전히 실행 중이므로 도커는 컨테이너 상태를 정상으로 판단하게 된다.
- 따라서, 기본적인 프로세스 체크 이상의 헬스 체크와 디펜던시 체크가 필요하다.
예시 1 - 무작위 숫자를 반환하는 REST API (도커의 기본 상태 점검만 동작)
# API 컨테이너를 실행한다.
docker container run -d -p 8080:80 diamol/ch08-numbers-api
# API를 세 번 호출한다 - 각 호출마다 무작위 숫자가 반환된다.
curl http://localhost:8080/rng
curl http://localhost:8080/rng
curl http://localhost:8080/rng
# 네 번쨰부터 API 호출이 실패한다.
curl http://localhost:8080/rng
# 컨테이너의 상태를 확인한다.
docker container ls
컨테이너의 상태는 여전히 UP(실행 중)이다. 즉, 프로세스의 상태가 실핼 중이다. 도커 입장에서는 애플리케이션에 문제가 없다고 판단하기 때문이다.
헬스 체크 적용
: 실행 중인 애플리케이션의 상태 확인
- Dockerfile에서 HEALTHCHECK 인스트럭션을 사용하면 컨테이너에서 실행 중인 애플리케이션의 상태를 확인할 수 있다.
- HEALTHCHECK 인스트럭션은 도커에게 컨테이너 내에서 실행할 명령을 지정하며, 이 명령의 반환 상태 코드를 통해 애플리케이션의 상태를 판단한다.
- 도커는 지정된 시간 간격으로 이 명령을 수행하고, 정상 상태 코드가 반환되면 컨테이너를 정상 상태로, 일정 횟수 이상의 실패 상태가 반환되면 이상 상태로 판단한다.
- 기본값은 30초 간격으로 연속 3회 이상 실패하면 애플리케이션이 이상 상태로 판단한다.
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"]
WORKDIR /app
COPY --from=builder /out/ .
- RUN
- 이미지 빌드 시점에 셸 명령을 실행
- dotnet restore
- 프로젝트의 종속성을 복원
- -c Release
- 릴리즈 구성으로 빌드
- -o /out
- /out 디렉토리에 출력
- ENTRYPOINT
- 컨테이너가 시작될 때 실행할 명령을 설정
- --fail 옵션
- curl이 전달받은 상태 코드를 도커에 전달(성공 0, 실패 0 이외의 숫자)
# -f 옵션을 붙여 Dockerfile 스크립트 파일의 경로를 지정한다.
docker image build -t diamol/ch08-numbers-api:v2 -f ./numbers-api/Dockerfile.v2 .
# 버전 v2 이미지로 API 컨테이너를 실행하라
docker container run -d -p 8081:80 diamol/ch08-numbers-api:v2
# 30초 정도 기다린 다음 컨테이너 목록을 확인한다.
docker container ls
# API를 네 번 호출한다. 처음 세번은 무작위 숫자를 반환하고 네 번째는 실패한다.
curl http://localhost:8081/rng
curl http://localhost:8081/rng
curl http://localhost:8081/rng
curl http://localhost:8081/rng
# 애플리케이션이 이상 상태에 빠졌다. 90초를 기다려 도커가 이상 상태를 감지하는지 확인한다. -> unhealthy
docker container ls
# 가장 최근에 컨테이너의 상태 출력 (JSON 타입)
docker container inspect $(docker container ls --last 1 --format '{{.ID}}')
- Docker는 컨테이너의 이상 상태를 감지할 경우, 이 사실을 사용자에게 알려주는 역할만 한다.
- Docker 엔진은 단일 서버에서 동작하므로 특정 컨테이너에 이상이 발생했을 때 직접 재시작한다면 일시적으로 애플리케이션이 중단된다.
- 그 이유는 사용자에게 서비스 중단, 데이터 유실 같은 문제를 방지하기 위해서이다.
- Docker의 헬스 체크 기능은 클러스터 환경에서 특히 유용하다.
- 클러스터
- 여러 대의 서버가 모여 하나의 컴퓨팅 리소스 풀을 형성하는 구조
- Docker Swarm이나 쿠버네티스와 같은 컨테이너 오케스트레이션 도구를 통해 관리된다.
- 클러스터 환경은 여분의 컨테이너가 있어, 이상 상태가 감지된 컨테이너를 그대로 유지하면서 새로운 컨테이너를 실행하여, 애플리케이션의 서비스 중단 없이 상태를 회복할 수 있다.
- 클러스터
디펜던시 체크 적용
: 컨테이너 간 의존 관계 고려
- 분산 애플리케이션
- 여러 개의 서로 다른 컨테이너에서 동작하는 애플리케이션이다.
- 한 애플리케이션의 정상적인 작동이 다른 애플리케이션의 준비 상태에 의존할 수 있다.
- 즉, 애플리케이션의 초기 실행 시 혹은 이상 상태에 빠진 컨테이너를 교체할 때 문제가 발생할 수 있다.
- 단일 서버
- Docker가 컨테이너의 실행 순서를 직접 관리할 수 있다.
- Docker 명령을 사용하므로 ..(당연)
- 클러스터 환경
- 여러 대의 서버가 동시에 동작하므로 순차적 실행을 보장할 수 없다.
참고로 Docker Compose의 depends_on 옵션은 컨테이너가 시작되는 순서에 대해서만 보장해준다.
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/ .
- CMD 인스트럭션을 통해, 애플리케이션을 실행하기 전에 curl 명령어를 사용하여 API가 정상적으로 작동하고 있는지 확인한다.
- API가 정상적으로 동작할 경우, curl 명령이 성공하고 .NET Core 애플리케이션을 실행한다.
- API가 사용할 수 없는 경우, curl 명령은 실패하고, .NET Core 애플리케이션 실행 명령이 실행되지 않는다. 즉, 컨테이너는 종료된다.
- 클러스터 환경에서는 한 컨테이너가 실패하면 컨테이너 플랫폼이 자동으로 새로운 컨테이너를 생성하여 대체한다.
커스텀 유틸리티
실무에서는 보안 정책상의 이유로 이미지에 curl을 포함시킬 수 없다. 대신에 애플리케이션과 같은 언어로 구현된 별도의 커스텀 유틸리티를 사용하는 것이 좋다.
애플리케이션과 같은 언어로 구현된 커스텀 유틸리티의 장점
- 이미지에 추가적인 소프트웨어가 필요하지 않는다.
- 해당 유틸리티를 실행하는 데 추가적인 소프트웨어나 도구를 이미지에 포함시키지 않아도 된다.
- 예: 애플리케이션을 Java로 작성했다면, 커스텀 유틸리티도 Java로 작성하여 동일한 Java 실행 환경에서 동작시킬 수 있다.
- 복잡한 체크 로직을 구현할 수 있다.
- 쉘 스크립트와 같은 간단한 스크립트 언어로는 표현하기 어려운 복잡한 로직을 구현할 수 있다.
- 예: 재시도 횟수, 분기 처리 등을 구현할 수 있다.
- 설정을 재사용하여 애플리케이션과 유틸리티 사이에 일관성을 유지할 수 있다.
- 대상 URL과 같은 설정 정보를 여러 위치에 반복하여 작성하거나, 수정 시 빠뜨리는 등의 문제할 수 있다.
- 다양한 체크가 가능하다.
- 데이터베이스 접속 가능성, 인증서 파일의 존재 등 컨테이너 실행 전에 확인이 필요한 다양한 요소들을 체크할 수 있다.
- 다양한 상황에 대응할 수 있다.
- 애플리케이션과 같은 언어로 작성된 유틸리티는 애플리케이션과 유사한 방식으로 다양한 상황에 대응하게 된다.
- 따라서 유틸리티의 안정성과 신뢰성이 높아진다.
- 이미지의 이식성이 크게 향상된다.
- 다양한 컨테이너 플랫폼(ex. Docker Compose, Docker Swarm, Kubernetes 등)은 헬스 체크와 디펜던시 체크를 정의하고 실행하는 방법이 다르다.
- 그러나 커스텀 테스트 유틸리티를 사용하면, 이러한 체크 로직을 모두 해당 유틸리티에 포함시킬 수 있다.
- 즉, 통일된 체크 로직을 실행할 수 있으므로, 애플리케이션을 다양한 플랫폼에 쉽게 이식할 수 있다.
예시 1 - 애플리케이션과 HTTP 테스트 유틸리티를 분리해서 빌드하는 멀티 스테이드 빌드 과정
- Builder 단계
- SDK 이미지(.NET Core SDK 이미지)를 기반으로 실제 애플리케이션을 빌드한다. SDK 이미지에는 애플리케이션을 빌드하는 데 필요한 모든 도구와 라이브러리가 포함되어 있다.
- Check-builder 단계
- .NET Core SDK 이미지를 기반으로 HTTP 테스트 유틸리티를 빌드한다. 이 유틸리티는 컨테이너의 상태를 체크하는데 사용된다.
- 마지막 단계
- 애플리케이션과 HTTP 테스트 유틸리티를 최종 이미지에 복사한다. HEALTHCHECK 지시문을 사용해 HTTP 테스트 유틸리티를 실행하고, 이미지의 ENTRYPOINT는 실제 애플리케이션을 실행하도록 설정한다.
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/ .
위 Dockerfile을 보면 curl 명령 대신 .NET Core로 구현된 HTTP 테스트 유틸리티를 사용하는 것을 알 수 있다. 이렇게 하면 애플리케이션과 같은 환경에서 HTTP 통신 테스트를 할 수 있으므로 더욱 신뢰성 있는 체크가 가능하다.
도커 컴포즈에 헬스 체크와 디펜던시 체크 정의
- Docker Compose는 이상을 감지하면 해당 컨테이너를 복구하지만, 이상이 발생한 컨테이너를 새로운 컨테이너로 대체하는 기능은 없다.
- 이는 단일 서버에서 애플리케이션을 실행하고 있을 경우, 장애가 발생했을 떄 해당 컨테이너를 복구하기 어렵게 한다.
- ex. 컨테이너가 오류로 인해 종료되었을 경우, Docker Compose는 해당 컨테이너를 자동으로 재시작하지 않음
- 이런 제한을 해결하기 위해 헬스 체크를 사용할 수 있다.
Docker Compose의 전체 코드는 아래와 같다.
version: "3.7"
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
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
networks:
app-net:
external:
name: nat
부분 1
numbers-api:
image: diamol/ch08-numbers-api:v3
ports:
- "8087:80"
healthcheck:
interval: 5s
timeout: 1s
retries: 2
start_period: 5s
networks:
- app-net
- interval: 헬스 체크를 수행하는 주기이다. 해당 예시에서는 5초 간격으로 수행한다.
- timeout: 응답을 기다리는 제한 시간이다. 해당 예시에서는 1초 내에 응답이 없으면 실패로 간주된다.
- retries: 컨테이너 상태를 이상으로 간주하기 위한 연속 실패 횟수이다.
- start_period: 컨테이너 실행 후 첫 헬스 체크를 실시하는 시간이다. 애플리케이션을 시작하는 데 시간이 오래 걸리는 경우 필요하다.
부분 2
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: 컨테이너가 예기치 않게 종료되면 자동으로 다시 시작된다.
- healthcheck: 컨테이너의 상태를 주기적으로 확인하고, 비정상일 경우 대응할 수 있다.
- test: 헬스 체크를 위한 명령
- interval, timeout, retires, start_period 등
- depends_on 설정이 없으므로 API 서비스가 준비되지 않은 상태에서 웹 애플리케이션 서비스가 시작되어, 웹 애플리케이션 서비스의 헬스 체크가 실패할 수 있다.
- 이 경우, restart: on-failure 설정에 의해 다시 시작된다.
- API 서비스도 결국 실행되므로 웹 애플리케이션 서비스는 정상적으로 동작한다.
주의해야 할 점
헬스 체크가 주기적으로 실행되므로, 부하를 많이 주는 테스트는 피하고, 애플리케이션의 핵심 기능을 체크하는 것이 중요하다. 반면에 디펜던시 체크는 애플리케이션 시작 시에만 실행되므로, 테스트에 소모되는 리소스에 대해 크게 걱정하지 않아도 된다.
'DevOps > Docker' 카테고리의 다른 글
[Docker] 컨테이너 모니터링 (0) | 2023.07.28 |
---|---|
[Docker] 도커 컴포즈와 분산 애플리케이션 (0) | 2023.07.18 |
[Docker] 도커 볼륨과 마운트 (0) | 2023.07.18 |
[Docker] 도커 레지스트리와 도커 허브: 이미지 공유와 관리 (0) | 2023.07.18 |
[Docker] 멀티 스테이지와 애플리케이션 빌드 (0) | 2023.07.09 |