Skip to content

docker 교과서 Chapter 8

HEALTHCHECK#

도커 컨테이너는 프로세스가 종료되면 같이 종료가 되지만 Status 500과 같이 내부 상태가 정상적이지 않은 상황에 프로세스는 살아있는 경우 컨테이너는 이를 판단하지 못한다. 이를 위하여 HEALTHCHECK 옵션이 존재한다. 인자로 임의의 커맨드가 들어가며, curl을 사용하여 임의의 /health 엔드포인트에 GET 요청을 주기적으로 보내 컨테이너 상태를 파악하는 기능을 제공한다.

ch08/exercises/numbers/numbers-api/Dockerfile.v2

curl --fail 옵션은 응답결과가 정상이면 0을, 비정상인 경우 0이 아닌 값을 반환한다. 이 값을 도커가 healty, unhealty 상태로 인식하게 된다.

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/ .
$ docker ps

IMAGE                      COMMAND                  STATUS                  
diamol/ch08-numbers-api:v2 "dotnet /app/Numbers…"   Up 8 minutes (unhealthy)

이제 unhealty한 컨테이너를 inspect 하면 JSON 파일을 받아볼 수 있다. State.Health.Log 를 보면 ExitCode가 22이고 Output이 500 Internal Server Error 임을 확인할 수 있다.

$ docker container inspect $(docker container ls --last 1 --format "{{.ID}}")
[
    {
        "Id": "dea679c75a7211bfc19be8a0dd0569d8ee2577155a823806a20d0114f4b0eb7f",
        "Created": "2024-10-28T14:16:28.793534681Z",
        "Path": "dotnet",
        "Args": [
            "/app/Numbers.Api.dll"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 213315,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2024-10-28T14:16:28.862957107Z",
            "FinishedAt": "0001-01-01T00:00:00Z",
            "Health": {
                "Status": "unhealthy",
                "FailingStreak": 27,
                "Log": [
                    {
                        "Start": "2024-10-28T23:30:31.489796749+09:00",
                        "End": "2024-10-28T23:30:31.569449709+09:00",
                        "ExitCode": 22,
                        "Output": "  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\r  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0\ncurl: (22) The requested URL returned error: 500 Internal Server Error\n"
                    }
                ]
            }
        },
    ...
]

디펜던시 체크#

디펜던시 체크는 도커 컴포즈에서 배웠던 depends_on 과 비슷하면서도 사뭇 다르다. 도커 컴포즈는 하나의 서버에서 굴릴 컨테이너들을 관리할 목적이고 이 책에서 소개하는 디펜던시는 여러 서버에 걸쳐있는 클러스터에서의 의존성 체크를 하는 방법을 의미한다. 물론 아직 클러스터링을 배우지 않았지만 왜 필요한지, depends_on의 한계가 무엇인지는 알고 넘어가야 한다.

디펜던시 체크와 관련한 명령어는 따로 없다. 단지 이미지를 run할때 애플리케이션이 실행되지 않게 만드는 로직을 추가하면 된다. 어차피 쿠버네티스 같은 녀석이 죽은 컨테이너를 다시 실행시켜줄테니깐.

방법1. Dockerfile에서 직접 의존 컨테이너에 요청을 보내기#

만약 CMD 명령의 curl이 정상적으로 실행이 되었다면 && 우측에 있는 명령을 실행하고 컨테이너가 생성될 것이다. 만약 아니라면 컨테이너는 만들어지다 exit 될 것이다.

FROM diamol/dotnet-sdk AS builder

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

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

# app image
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/ .

방법2. 커스텀 유틸리티를 실행시키기#

실무 목적으로 curl을 사용하는 것은 권장되지 않는다. 왜냐하면 불필요하게 이미지 크기를 늘리고 외부 공격에 노출될 여지가 있기 때문이다. 컨테이너가 너무 많은 유틸리티를 가지고 있으면 해커가 컨테이너에 접근권한을 획득했을때 할 수 있는 도구도 많아지는 셈이다.

따라서, 이미지를 빌드할때 같은 기반 이미지를 공유하는 커스텀 유틸리티를 만들어 헬스체크와 디펜던시 체크에 활용하는 것이 유용하다.

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

# http check utility
FROM diamol/dotnet-sdk AS http-check-builder

WORKDIR /src
COPY src/Utilities.HttpCheck/Utilities.HttpCheck.csproj .
RUN dotnet restore

COPY src/Utilities.HttpCheck/ .
RUN dotnet publish -c Release -o /out Utilities.HttpCheck.csproj

# 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/ .
graph TD
    BuilderArtifact[Numbers.Api]
    HTTPCheckArtifact[Utilities.HttpCheck]
    FinalAppImage[ENTRYPOINT]
    HealthCheck[HEALTHCHECK]


    subgraph builder
        BuilderArtifact   
    end
    subgraph http-check-builder
        HTTPCheckArtifact
    end

    subgraph app
        BuilderArtifact --> HealthCheck
        HTTPCheckArtifact --> HealthCheck
        HealthCheck --> FinalAppImage
    end

애플리케이션과 같은 기반으로 커스텀 유틸리티를 만들면 좋은 점

  • 별도의 도구를 레이어에 포함시킬 필요가 없다.
  • 비단 디펜던시, 헬스체크만 하는 것이 아니라 DB 연결, 환경변수가 올바르게 갖추어져있는지 등을 미리 검사할 수 있다.

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

도커컴포즈에도 헬스체크를 수행할 수 있고 컨테이너를 재실행 시킬 수는 있다. 하지만 컨테이너를 종료시켰다 새 컨테이너로 실행시키는 일은 할 수 없다. 아래 컴포즈 파일 중 healthcheckrestart 부분을 유심히 보자.

numbers-web은 numbers-api에 의존하고 있지만 depends_on을 사용하지 않았다. 만약 numbers-web이 먼저 초기화 된다면 아마도 애플리케이션을 실행시키지 못할 것이다. 이때 restart: on-failure가 빛을 발휘하여 다시 numbers-web을 실행하게 된다. 그 사이 numbers-api가 초기화 되면 정상작동하는 것이다.

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

아래 페이지에 접속해 랜덤 숫자 네개를 받고나서부터는 unhealty 상태로 바뀌고 Api 서버에서 요청을 받을 수 없게된다. 그렇지만 도커 컴포즈도 unhealty 상태인 컨테이너를 갈아끼울 수는 없다.

Screenshot From 2024-10-29 00-55-19.png

CONTAINER ID   IMAGE                                COMMAND                  CREATED         STATUS                     PORTS                                       NAMES
cd01eda8d371   diamol/ch08-numbers-web:v3           "/bin/sh -c 'dotnet …"   2 minutes ago   Up 2 minutes (healthy)     0.0.0.0:8088->80/tcp, [::]:8088->80/tcp     numbers-numbers-web-1
f769c7eb04da   diamol/ch08-numbers-api:v3           "dotnet Numbers.Api.…"   2 minutes ago   Up 2 minutes (unhealthy)   0.0.0.0:8087->80/tcp, [::]:8087->80/tcp     numbers-numbers-api-1