📕 목차
1. Health check를 지원하는 Docker Image 빌드
2. Dependency check가 적용된 Container 실행
3. Custom Utility
4. Docker compose에 Health & Dependency checky 정의
5. 복원력 있는 Application
6. 연습 문제
1. Health check를 지원하는 Docker Image 빌드
이전까지 Docker Image로 Application을 패키징하고, 실행하고, Docker compose로 여러 Container를 동시에 실행하는 것까지 했다.
이제는 운영 환경에 맞게 Application을 다듬을 차례다.
📌 Health check
- docker swarm이나 kubernetes는 Container Platform 상에서 Application이 스스로 이상에서 회복하는 기능을 제공한다.
- Platform이 Container에서 실행 중인 Application 상태가 정상인지 확인하는 정보를 Image에 함께 패키징한다.
- 비정상적인 Application이 발생하면 Container를 삭제하고 새 Container로 대체한다.
- Docker가 확인하는 것은 단순히 Process의 실행 여부이지, 정상/비정상 상태를 검사하지는 않는다.
- Process가 종료되었다면 Container도 종료되므로 기본적인 Health check가 이루어지긴 한다.
- Dockerfile에 명시하여 Docker Iamge에 상태 확인을 위한 로직을 추가하기만 하면 된다.
📌 health check 로직이 없는 경우
💡 Container Runtime은 Process 안에서 무슨 일이 일어나는지, Application 정상 작동 여부를 알 수 없다.
docker container run -d -p 8080:80 diamol/ch08-numbers-api
# 계속 반복하면 4번째부터 실패한다.
curl http://localhost:8080/rng
docker container ls
- Application이 500 에러가 발생하여 동작하지 않게 되었다.
- 그럼에도 Container는 여전히 Up으로 나온다. 안에 있는 process의 상태도 여전히 실행중이다.
📌 health check Instruction
HEALTHCHECK CMD curl --fail http://localhost/health
- Health check Instruction : Docker가 Container 안에서 실행하는 명령을 지정 (명령이 반환하는 상태 코드로 Application 상태 판단)
- --fail : curl 명령이 상태 코드를 Docker에 전달한다.
- 성공하면 0, 실패하면 0 이외의 숫자 반환
- 상태를 판단할 수 있다면 어떤 명령을 지정해도 무방하다.
- Docker는 해당 명령을 일정 시간 간격으로 수행하여, 일정 획수 이상 실패 시 Container를 이상 상태로 간주한다.
📌 health check가 반영된 Container 실행
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/ .
# 끝에 .은 현재 작업 디렉토리를 의미. -f로 Image 경로를 명시해줬는데도 빼먹으면 오류가 난다
docker image build -t diamol/ch08-numbers-api:v2 -f .\numbers-api\Dockerfile.v2 .
docker container run -d -p 8081:80 diamol/ch08-numbers-api:v2
docker container ls
# 4번을 반복하면 마찬가지로 에러가 발생한다.
curl http://localhost:8081/rng
docker container ls
- Health check가 감지한 Container의 이상 상태는 Docker API를 통해 Docker에게 보고된다.
- 원한다면 Docker가 이상 상태를 통보받고 Application 복구를 위한 조치를 취할 수도 있다.
📌 Container 상태 확인하기
docker container inspect $(docker container ls --last 1 --format '{{.ID}}')
- 가장 최근의 Health check 수행 결가 저장되어 있어서 결과를 열람할 수 있다.
- Status : 현재 Container 상태
- FailingStreak : Container에 대해 연속으로 check 실패한 횟수. 기본값은 3번을 넘으면 비정상 상태로 간주한다.
✒️ Docker가 상태 이상의 Container를 다른 Container로 교체하지 않는 이유
Cluster 환경에서 동작하는 Docker swarm이나 Kubernetes와는 달리 Docker engine은 단일 서버에서 동작하기 때문에, Container 교체 작업을 안전하게 처리할 수 없기 때문이다. (리소스 한계)
1. 이상이 생긴 Container를 중지하고 재시작할 수는 있지만, 그동안 Application은 동작하지 않는다.
2. 설령 완전히 같은 설정으로 Container를 실행한다 하더라도, 기존의 Container에 보관된 데이터가 유실되고 그 시간 동안 Application이 동작하지 않는다.
즉, Docker는 할 수는 있지만 리스크가 너무 크기 때문에 그냥 냅두고 Health check만 반복하는 것이다.
반면에 Cluster 환경에서는 Container를 추가로 실행할 여력이 항상 남아 있기 때문에 이상 상태를 보이는 Container를 그대로 두고, 대체 Container를 실행하여 Application 중단 시간 없이 회복이 가능하다.
2. Dependency check가 적용된 Container 실행
📌 Cluster 환경에서 Container 재실행 문제점
- Cluster 환경에서 Health check는 용이하지만 Dependency check는 어렵다
- 서버가 한 대면 Container 실행 순서를 보장할 수 있지만, Cluster 환경은 서버가 여러 대이므로 그렇지 않다.
- 이상이 생긴 Container를 교체할 때, 처음 실행할 때처럼 Container의 Dependency를 고려하지도 않는다.
이로인해 API는 실행하기도 전에 Web Application이 먼저 실행될 수도 있다.
📌 API 없이 web Application만 실행 시
# 기존 Container 모두 제거
docker container rm -f $(docker container ls -aq)
docker container run -d -p 8082:80 diamol/ch08-numbers-web
docker container ls
- 핵심 의존 관계를 만족하지 않아 Application이 정상 작동하지 않는다.
📌 Dependence check를 위한 명령어 수정
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/ .
- "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를 종료해버린다.
💡 문제가 생겼을 때는 Container 종료되는 것이 이상을 빠르게 파악하는데 도움이 된다.
3. Custom Utility
📌 curl 명령어 보안 취약점
- Health & Dependency check를 갖추었다면 Container Platform 환경에 적합한 Application이라 할 수 있다.
- 하지만 curl은 너무 많은 Server 정보를 노출하므로 보안 정책상 이유로 Image에 포함시킬 수 없다.
- 따라서 curl과 같으면서 최소한의 기능을 하는 Custom Utility를 사용하는 것이 낫다.
📌 Custom Utility 장점
- Custom Utility를 실행할 때도 Application과 같은 도구를 사용하므로 Image에 추가적인 SW를 포함시킬 필요가 없다.
- Shell script로 표현하기 힘든 체크 로직을 적용할 수 있다. (리눅스, 윈도우 양쪽에서 사용할 Cross platform Image라면 더욱 유용하다)
- Application과 같은 설정으로 대상 URL을 여러 곳에 반복 정의, 혹은 수정 누락을 방지할 수 있다.
- Application과 같은 Library 환경에서 Container 실행 전 확인이 필요한 모든 사항을 검증할 수 있다.
- 다양한 상황에서 동작이 가능하다.
✒️ Image에 SW를 포함시킬 필요가 없는 이유
이 장점들이 이해가 안 가서 좀 더 찾아보니 이랬다.
Custom Utility는 Image에 포함되지 않고, Application Container가 실행되는 Host PC에서 실행되는 외부 도구다.
따라서 Host 환경에서 Image를 실행하는 동안에만 사용되며, Host PC의 리소스를 활용하여 작동한다.
📌 Custom Utility를 적용하여 실행하기
아래에서 Custom Utility로 쓰일 프로그램은 프로그래밍 언어로 작성된 소스 코드다.
curl 명령을 전부 Custom Utility를 실행하는 명령으로 바꾸었다.
Container Platform 마다 Health & Dependency check 정의 및 실행 방법에는 차이가 있다.
하지만 모든 로직을 Test Utility에 포함시키면 Docker compose부터 Kubernetes 까지 어떤 환경에서도 동작시킬 수 있는 이식성이 향상된다.
1️⃣ Health Check
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/ .
docker container rm -f $(docker container ls -aq)
docker container run -d -p 8080:80 --health-interval 5s diamol/ch08-numbers-api:v3
docker container ls
# 연속 4번 실행 - 반드시 오류
curl http://localhost:8080/rng
docker container ls
2️⃣ Dependency Check
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/ .
docker container run -d -p 8081:80 diamol/ch08-numbers-web:v3
docker container ls --all
4. Docker compose에 Health & Dependency check 정의
📌 Docker compose와 상태 이상의 Container
- Docker compose 또한 Application 상태 이상 시, 어느 정도 복원할 능력이 있지만 대체하지 않는다. (단일 서버이므로)
- 하지만 Dockerfile에 정의되지 않은 Health check를 추가하고, 종료된 Container를 재시작할 수 있다.
📌 Compose 파일에서 Health check
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
- Docker compose에서는 Health check 옵션을 더 세세하게 설정할 수 있다.
- interval : Health check 실시 간격
- timeout : 이 때까지 응답을 받지 못하면 실패로 간주
- retries : Container 상태를 이상으로 간주할 때까지 필요한 연속 실패 횟수
- start_perioid : Container 실행 후 처음 Health check를 실시하는 간격. (Application 시작하는 데 시간이 오래 걸리면 필요)
- 일반적으로 설정값은 이상 발생을 파악하는 속도와 허용할 수 있는 장애 오탐지 빈도를 고려해 결정한다.
- CPU와 메모리 자원이 필요하므로 운영 환경에서는 Health check 간격을 좀 더 길게 잡는 것이 좋다.
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 Image는 api Image에 의존성을 갖도록 Image가 만들어져 있지만,
정작 Compose 파일에는 depends_on 설정을 하지 않아 Docker compose가 Container 실행 순서를 고려하지 않는다.
하지만 API Container가 실행되기 전에 Web Container가 실행되면 healthcheck의 test에 등록한 dependency check에 의해 종료된다. (그런데 restart 옵션으로 인해 다시 실행..)
결론적으로 API Container는 무사히 실행될 것이고, Dependency check도 통과하여 Application이 동작할 것이다.
✒️ Compose 파일에 depends_on 설정을 하지 않은 이유
Docker compose가 dependency check를 할 수 있는 범위는 단일 서버에 국한된다.
반면, 실제 운영 환경은 단일 서버에서 동작하지 않는다. 보통은 클러스터를 구축할 것이다.
이렇게 되면 운영 환경에서 Application이 실제 시작할 때 일어나는 상황을 예측하기가 힘들어진다.
📌 실행해보기
docker-compose up -d
docker container ls
docker container logs numbers-numbers-web-1
- Compose 파일에 의존관계를 설정하지 않아 web Container가 먼저 실행되었다.
- API Container는 CREATED와 STATUS를 비교했을 때, 실행까지 약 3~4초가 소요되었다.
- (사진에는 나오지 않지만) Web Container도 실행은 됐으나 10초 가량이 소요되었다.
- Web Container 로그를 확인해보면 처음 HTTP 테스트가 응답 거부로 에러가 발생했었다.
5. 복원력 있는 Application
📌 Application의 자기 수복
- 의존 관계를 시작 순서에 따라 모델링하는 방법은 현명하지 않다.
- 운영 환경에서는 서버가 한 대 뿐일 거라는 가정이 없다. (한 대라면 상관 없다.)
- 20여 개의 API가 모두 실행되어야만, 나머지 50여 개의 Web Application이 실행될 수 있다면 비효율적일 것이다.
- Health & Dependency check를 도입하면 처음부터 플랫폼이 실행 순서를 보장하게 할 필요가 없다. 그냥 빨리 Container를 재실행하면 그만이다.
- Application의 자기 수복이란 일시적인 오류를 플랫폼이 해소해주는 것이다.
- 버그를 수정하지 않고도 Container를 대체해버리는 것으로도 Application은 계속 동작할 수 있다.
📌 주의 사항
- Health check
- 주기적으로 자주 실행되며, CPU를 사용하므로 시스템에 부하를 주는 내용이면 안 된다.
- 자원을 너무 많이 소모하지 않으면서 Application이 실질적으로 동작 중인지 검증 가능한 핵심만 테스트해야 한다.
- Dependency check
- Application 시작 시에만 실행되므로 리소스에 크게 신경 쓸 필요는 없다.
- Test 대상이 빠짐없이 정황하도록 주의하라
- 누락된 의존 관계가 있고, 플랫폼이 해결하지 못한다면 Application에도 문제가 발생한다.
6. 연습 문제
- Application 시작 시 메모리가 충분한지 확인하고, 부족한 경우 Container 종료
- Application 실행 중 5초 간격으로 최대치를 초과해 메모리를 사용하는지 확인하고, 초과한다면 상태 이상
- 테스트 로직은 memory-check.js 스크립트에 있고, Dockerfile에서 테스트 스크립트를 활용하라
Dockerfile.solution 내용을 뒤져보면 이렇다.
FROM diamol/node
ENV MAX_ALLOCATION_MB=4096 \
LOOP_ALLOCATION_MB=512 \
LOOP_INTERVAL_MS=2000
CMD node memory-check.js && \
node memory-hog.js
HEALTHCHECK --interval=5s \
CMD node memory-check.js
WORKDIR /app
COPY src/ .
어...혹시 이걸 작성하라는 거였나? ㅋㅋ..
docker image build -t diamol/ch08-lab:solution -f Dockerfile.solution .
docker container run diamol/ch08-lab:solution
docker container ls