본문 바로가기
빌드노트 검색

빌드노트

서버는 응답하는데 HEALTHCHECK는 curl을 찾지 못한다

HTTP 서버가 응답해도 검사에 쓰는 curl을 컨테이너 안에서 찾지 못하면 unhealthy가 된다. 적용된 Test와 State.Health.Log의 ExitCode·Output을 읽고, 실제 제공된 BusyBox wget으로 같은 내부 URL을 확인해 검사 실행 실패를 구분한다.

Docker 컨테이너의 HTTP 주소는 응답하는데 상태가 unhealthy라면, 앱 연결을 조사하기 전에 헬스체크 명령이 실행됐는지부터 확인한다. 검사에 쓰는 curl이 컨테이너 안에서 발견되지 않아도 헬스체크는 실패한다. 이 경우에는 HTTP 요청을 보내기도 전에 명령 실행이 막힌 것이므로, 그 결과만으로 앱이 응답하지 못한다고 판단할 수 없다.

HEALTHCHECK는 컨테이너 내부에서 명령을 실행한다. 호스트에 curl이 설치돼 있다는 사실은 컨테이너의 검사 조건을 충족하지 않는다. Docker가 보관하는 검사 출력과 실제 실행 도구를 함께 봐야 한다. HEALTHCHECK의 실행 위치와 진단 출력 (새 탭에서 열림)

앱 로그보다 검사 명령과 Output을 먼저 읽는다

다음 두 조회로 현재 컨테이너에 적용된 명령과 최근 검사 결과를 확인한다. Docker에 연결된 PowerShell에서 $container를 자신의 컨테이너 이름이나 ID로 바꾼다. 아래 예는 Linux 컨테이너를 대상으로 한다.

$container = 'my-app'
docker container inspect --format '{{json .Config.Healthcheck.Test}}' $container
docker container inspect --format '{{json .State.Health}}' $container

첫 조회의 Test는 무엇을 실행하는지, 둘째 조회의 Status와 Log는 그 명령이 어떤 결과를 냈는지 보여 준다. json은 선택한 객체를 JSON으로 출력하는 Docker 템플릿 함수다. 컨테이너를 찾지 못하거나 Docker 연결 오류가 나면 조회부터 바로잡는다. 헬스체크 설정이나 기록이 없다면 아래 오류와 같은 상황이라고 볼 수 없다. 컨테이너 inspect (새 탭에서 열림), 부분 객체의 JSON 출력 (새 탭에서 열림)

차이를 확인하기 위해 Alpine 3.22.7과 BusyBox 1.37.0으로 별도 실험을 했다. 컨테이너 안의 127.0.0.1:8080/health에 짧은 HTTP 응답기를 두고, 본문 ok를 돌려주게 했다. BusyBox nc로 만든 합성 서버이며 실제 업무 앱의 장애 기록은 아니다. 외부 네트워크와 호스트 포트 공개 없이 실행했다.

문제 조건에서는 없는 curl을 검사 명령으로 지정했다. 첫 조회 결과는 다음과 같았다.

["CMD-SHELL","curl -fsS http://127.0.0.1:8080/health"]

두 번째 조회의 Status는 unhealthy였다. Log의 최근 검사 한 건에서 시각을 제외한 두 필드만 발췌하면 다음과 같다.

{"ExitCode":127,"Output":"/bin/sh: curl: not found\n"}

이 출력에서는 셸이 curl을 찾지 못했다. HTTP 서버가 보낸 오류가 아니므로 포트나 응답 코드를 바꾸기 전에 실행 도구를 확인할 근거가 된다. 여기서 읽은 ExitCode는 State.Health.Log 안의 검사 종료 코드다. 메인 프로세스의 State.ExitCode와 혼동하지 않는다.

숫자 127만으로 모든 이미지의 원인을 단정할 수는 없다. 이번 값은 해당 Linux 셸에서 관찰한 결과다. 같은 없는 명령 뒤에 || exit 1을 붙여 따로 실행하자 출력은 여전히 curl: not found였지만 종료 코드는 1로 바뀌었다. 코드 1이라고 해서 HTTP 요청까지 진행됐다는 뜻도 아니다. Test와 Output을 함께 읽어야 하는 이유다.

같은 주소를 이미지에 있는 도구로 확인한다

다음 명령은 해당 컨테이너의 기본 실행 환경에서 curl과 BusyBox를 찾을 수 있는지 확인한다. /bin/sh가 있는 이미지에서 앞서 지정한 $container를 그대로 사용한다.

docker exec $container /bin/sh -c 'command -v curl; command -v busybox'

실험에서는 curl 경로가 나오지 않았고 /bin/busybox만 출력됐다. command -v가 경로를 반환하지 않으면 그 환경의 명령 검색으로 찾지 못했다는 뜻이다. 이미지에 도구가 빠졌는지, 설치 위치가 PATH에서 빠졌는지 구분해 확인한다. 경로가 나온다면 현재 Test에 지정된 경로와 스크립트가 실제 호출하는 명령까지 대조해야 한다.

이번 이미지에는 BusyBox의 wget 기능이 들어 있었다. 다음 명령으로 실패한 검사와 같은 컨테이너 안에서 같은 URL에 요청을 보냈다. 자신의 서비스에 적용할 때는 컨테이너 내부에서 쓰는 포트와 경로로 바꾼다. /bin/busybox와 그 안의 wget 기능이 없는 이미지에는 이 명령을 그대로 적용할 수 없다.

docker exec $container /bin/busybox wget -q -T 2 -O - http://127.0.0.1:8080/health

명령은 종료 코드 0으로 끝나고 ok를 출력했다. 응답 헤더를 출력하는 별도 실행에서도 HTTP/1.1 200 OK를 확인했다. curl 검사로 unhealthy가 된 컨테이너에서도 HTTP 서버는 응답하고 있었다. -T 2는 요청의 대기 시간을 제한하고, -O -는 받은 본문을 화면에 출력한다.

이 결과는 probe 실행 실패를 뒷받침한다. 반대로 실제 제공된 도구로도 연결이 거절되거나 HTTP 오류가 나온다면 도구 교체만으로 해결됐다고 볼 수 없다. 그때는 검사 주소와 포트, 서버가 준비됐는지, 해당 경로의 응답을 조사한다. 이 합성 실험은 실제 서비스의 인증이나 의존 DB 상태까지 확인한 것은 아니다.

docker exec는 실행 중인 컨테이너에 명령을 추가로 실행한다. 셸이 없는 최소 이미지라면 위 command -v 조회부터 사용할 수 없으므로, 그 이미지가 제공하는 실행 파일과 최종 이미지의 구성으로 확인해야 한다. 셸 조회 자체의 실패를 앱의 HTTP 장애로 해석하지 않는다. docker exec의 실행 조건 (새 탭에서 열림)

확인된 명령으로 HEALTHCHECK를 바꾼다

이미지에 필요한 검사 도구를 포함하거나, 이미지가 이미 제공하는 도구로 검사를 바꾼다. 이번처럼 BusyBox wget의 요청 성공을 확인했다면 Dockerfile의 검사 명령을 다음과 같이 바꿀 수 있다. /health는 앞에서 직접 확인한 주소이며, 응답 본문을 보던 -O -를 검사에서는 -O /dev/null로 바꿨다.

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD /bin/busybox wget -q -T 2 -O /dev/null http://127.0.0.1:8080/health

이 명령을 사용하는 별도 정상 비교 컨테이너도 실제로 실행했다. 같은 Alpine 이미지와 같은 응답기를 사용했고, 두 검사 모두 실험에서는 주기 1s, 제한 시간 2s, 연속 실패 기준 2로 설정했다. 없는 curl을 쓰는 쪽은 unhealthy였고, BusyBox wget을 쓰는 쪽은 healthy, 최근 검사 ExitCode=0, Output=""였다. 응답을 버리고 조용히 실행하도록 지정했으므로 정상 검사 출력이 비어 있는 것은 이 설정에서의 예상 결과다.

위 Dockerfile은 일반 적용을 위한 설정 예다. Dockerfile 자체를 빌드한 것은 아니며, 명령의 실제 실행은 --health-cmd로 비교했다. 검사 주기와 실패 기준은 서비스에 맞춰 정한다. 공식 문서는 검사 성공을 코드 0, 실패를 1로 설명하며 2는 예약 값으로 둔다. HEALTHCHECK 문법과 종료 코드 (새 탭에서 열림)

수정 후에는 새 이미지로 컨테이너를 재생성하고 처음의 두 조회를 다시 실행한다. Dockerfile을 편집한 사실만으로 현재 컨테이너의 검사 명령이 바뀌었다고 판단하지 않는다. 새 Test에 의도한 명령이 적용됐는지와 그 명령의 실제 검사 결과가 성공했는지까지 확인한다.

직접 확인한 환경은 Windows의 PowerShell 7.6.6, Docker Desktop 4.92.0의 Linux Engine 29.8.0이었다. 검증 범위는 도구를 찾지 못한 검사와 같은 내부 주소에 성공한 검사의 차이다. 다른 셸·exec 형식에서의 오류 표현, TLS·인증, 실제 앱 배포는 검증하지 않았다.

← 전체 글 보기