이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Docker 기반 Spring 서비스 장애에서 어떤 순서로 증거를 모아야 하는가?
  • image, container, env, mount, network, log 문제를 어떻게 분리하는가?
  • 디버깅 중 어떤 명령이 증거를 지우거나 장애를 키울 수 있는가?

개요

Docker 장애 디버깅은 container를 재시작하는 일이 아니다.

장애 중에는 먼저 image, container status, logs, env, mount, network, health, resource를 나누어 본다.

같은 API가 안 된다도 image build 문제, env 누락, volume 권한, port mapping, DB network, JVM memory limit 중 하나일 수 있다.

디버깅 순서

처음에는 상태를 바꾸지 않는 명령으로 시작한다.

docker ps -a
docker logs --tail 200 order-api
docker inspect order-api
docker events --since 10m

그 다음 증상을 축으로 나눈다.

  • container가 떠 있는가?
  • exit code는 무엇인가?
  • restart count가 증가하는가?
  • logs에 Spring startup failure가 있는가?
  • env key가 존재하는가?
  • mount가 기대 경로에 붙었는가?
  • port가 publish되었는가?
  • container 내부 health는 성공하는가?

Image 문제 확인

image 문제는 실행 전 artifact가 잘못된 경우다.

docker inspect order-api --format '{{.Image}}'
docker image inspect registry.example.com/order-api:3f21a9c

확인할 것:

  • release note의 tag와 실제 image가 일치하는가?
  • digest가 인스턴스마다 같은가?
  • base image 변경이 있었는가?
  • entrypoint가 올바른가?
  • jar가 image 안에 존재하는가?

image 문제라면 같은 image를 쓰는 다른 환경에서도 재현될 가능성이 있다.

Container 실행 문제 확인

container 실행 문제는 image는 맞지만 실행 옵션이 틀린 경우다.

docker inspect order-api --format '{{json .Config.Env}}'
docker inspect order-api --format '{{json .HostConfig.Binds}}'
docker inspect order-api --format '{{json .NetworkSettings.Ports}}'

확인할 것:

  • SPRING_PROFILES_ACTIVE가 맞는가?
  • DB URL과 Secret key가 있는가?
  • memory limit이 너무 낮지 않은가?
  • restart policy가 반복 재시작을 만들고 있지 않은가?
  • mount 경로와 port mapping이 기대와 같은가?

Secret 값은 그대로 출력하거나 공유하지 않는다.

Log와 Health 확인

Spring 로그와 Docker 상태를 함께 본다.

docker logs --since 10m order-api
docker exec order-api curl -fsS http://localhost:8080/actuator/health

health 실패는 process 종료와 다르다.

process가 살아 있어도 DB, Redis, external API, disk permission 때문에 health가 실패할 수 있다.

반대로 health는 성공하지만 핵심 API가 실패할 수도 있다.

그래서 smoke test와 application log를 함께 본다.

Network와 Port 확인

접속 장애에서는 container 내부, host, proxy 순서로 본다.

docker exec order-api ss -ltnp
docker exec order-api curl -fsS http://localhost:8080/actuator/health
curl -fsS http://127.0.0.1:8080/actuator/health
docker network inspect <network>

container 내부 health는 성공하지만 host curl이 실패하면 port publish를 본다.

host는 성공하지만 외부가 실패하면 Nginx, firewall, security group을 본다.

Nginx 502인데 Spring log에 요청이 없으면 upstream 경로를 의심한다.

Volume과 파일 확인

파일 관련 장애는 mount를 확인한다.

docker inspect order-api --format '{{json .Mounts}}'
docker exec order-api ls -ld /data/uploads /var/log/order-api

확인할 것:

  • upload 경로가 mount되어 있는가?
  • log 경로 owner가 실행 사용자와 맞는가?
  • DB data volume을 실수로 새 volume에 붙였는가?
  • host disk가 full인가?

volume 문제에서 container를 삭제하면 증거와 데이터가 더 사라질 수 있다.

Resource 확인

Docker resource limit과 host resource를 함께 본다.

docker stats --no-stream
docker inspect order-api --format '{{json .HostConfig.Memory}}'
dmesg -T | grep -i 'killed process'

container가 exit code 137로 종료됐다면 OOM kill을 의심한다.

JVM heap과 container memory limit이 맞지 않으면 GC 지연이나 OOM이 발생할 수 있다.

Spring Boot image에서는 -XX:MaxRAMPercentage 같은 JVM option도 같이 확인한다.

운영 체크리스트

  • 디버깅 전 container logs와 inspect 결과를 저장했는가?
  • image tag/digest와 release note가 일치하는가?
  • env, mount, network, port를 따로 확인했는가?
  • health check와 smoke test를 구분했는가?
  • restart count와 exit code를 확인했는가?
  • resource limit과 OOM 신호를 봤는가?
  • 변경 명령 실행 전 영향과 rollback을 정했는가?

장애 신호

  • container가 반복 재시작하고 logs가 같은 오류를 반복한다.
  • docker ps에는 Up인데 health는 unhealthy다.
  • container 내부 health는 성공하지만 host port 접근은 실패한다.
  • env key 누락으로 Spring startup이 실패한다.
  • mount 누락으로 업로드 파일이 사라진다.
  • exit code 137과 OOM kill 로그가 같이 보인다.
  • docker system prune 후 필요한 image나 volume이 사라졌다.

안전한 완화 조치

원인 확인 전 docker rm -f, docker system prune, docker compose down -v를 실행하지 않는다.

먼저 증거를 저장한다.

문제가 image라면 이전 digest로 새 container를 띄우는 방안을 검토한다.

문제가 env라면 한 인스턴스에서 env 수정과 smoke test를 먼저 확인한다.

문제가 network라면 전체 firewall 변경보다 port mapping과 upstream을 좁혀 본다.

문제가 volume이라면 백업 없이 삭제하지 않는다.

인프라 담당자와 공유할 자료

서비스: order-api
container: order-api
image tag/digest: 3f21a9c / sha256:...
status: Restarting, exit code 137
logs: JVM OOM before shutdown
memory limit: 512m
recent change: MaxRAMPercentage 85로 변경
요청: host OOM log와 container memory limit 변경 이력 확인

Docker 문제는 container id, image, env/mount/network, logs, resource를 한 세트로 공유한다.

실전 팁

  • docker inspect를 읽을 줄 알아야 Docker 장애가 빨라진다.
  • logs, inspect, events를 저장한 뒤 변경 명령을 실행한다.
  • health 성공과 사용자 API 성공을 구분한다.
  • exec로 container 내부에서 본 localhost와 host localhost를 구분한다.
  • prune 명령은 장애 중 금지 명령에 가깝게 다룬다.

위험 신호!

  • 장애가 나면 바로 docker restart부터 한다.
  • docker rm -f로 container를 지우고 나서 원인을 찾는다.
  • docker compose down -v를 의미 없이 실행한다.
  • Secret 값이 담긴 env 전체를 채팅에 붙여넣는다.
  • Nginx 502를 Spring 500과 같은 문제로 본다.

확인 질문

확인 질문

  • Docker 장애에서 docker inspect가 중요한 이유는 무엇인가?
    • image, env, mount, network, restart policy, resource limit처럼 실행 당시 계약을 확인할 수 있기 때문이다.
  • docker compose down -v를 조심해야 하는 이유는 무엇인가?
    • container뿐 아니라 volume까지 삭제해 DB 데이터나 업로드 파일 같은 보존 데이터를 잃을 수 있기 때문이다.
  • container 내부 health는 성공하지만 외부 요청이 실패하면 무엇을 봐야 하는가?
    • host port publish, Nginx upstream, firewall/security group, 외부 DNS와 TLS 경로를 확인해야 한다.

참고 문서