실행 중인 컨테이너 디버깅 전략
docker logs, exec, inspect, stats, top, nsenter, debug 컨테이너를 활용해 실행 중인 컨테이너 문제를 진단하는 방법을 상황별로 설명합니다.
지난 글에서 이미지 서명으로 공급망 보안을 강화하는 방법을 살펴봤다. 이번엔 반대편 — 이미 실행 중인 컨테이너에서 문제가 생겼을 때 어떻게 진단하는지를 다룬다.
컨테이너는 격리된 환경이라 호스트에서 직접 접근하기 어렵다. 하지만 Docker는 이를 위한 다양한 도구를 제공한다.
디버깅 도구 전체 지형도
1단계: 컨테이너 상태 확인
문제가 발생하면 먼저 컨테이너 상태부터 확인한다.
# 모든 컨테이너 상태 확인 (종료된 것 포함)
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# 종료 코드 확인
docker inspect myapp --format '{{.State.ExitCode}} {{.State.Error}}'
# 0: 정상 / 1: 애플리케이션 오류 / 137: OOM 또는 SIGKILL / 139: Segfault
# 재시작 횟수 확인 (CrashLoop 의심)
docker inspect myapp --format '{{.RestartCount}}'
종료 코드 137(= 128 + SIGKILL)은 OOM Killer가 프로세스를 죽였거나 docker kill이 호출됐을 때 나타난다.
2단계: 로그 분석
# 마지막 100줄 + 타임스탬프
docker logs --tail=100 --timestamps myapp
# 실시간 팔로우
docker logs -f myapp
# 에러만 필터
docker logs myapp 2>&1 | grep -i "error\|exception\|fatal"
# 특정 시간 이후 로그
docker logs --since="2026-05-23T10:00:00" myapp
# 이전 컨테이너 로그 (재시작된 경우)
docker logs --previous myapp 2>/dev/null || docker logs myapp
컨테이너가 이미 종료됐어도 로그 드라이버가 json-file인 경우 로그가 남아 있다.
3단계: 리소스 사용량
# 스냅샷 형태로 통계 조회
docker stats --no-stream myapp
# 실시간 모니터링
docker stats myapp
# 컨테이너 내 프로세스 목록
docker top myapp aux
# 출력에서 PID는 호스트 PID namespace 기준
MEM USAGE가 MEM LIMIT에 근접하면 OOM 위험 신호다.
4단계: 내부 진입 (exec)
# bash 또는 sh로 진입
docker exec -it myapp bash
docker exec -it myapp sh # bash 없을 때
# 단발성 명령 실행
docker exec myapp ps aux
docker exec myapp cat /etc/hosts
docker exec myapp env | sort
docker exec myapp ls -la /app
# 루트로 접속 (컨테이너가 non-root로 실행 중일 때)
docker exec -it --user root myapp bash
5단계: 상세 메타데이터 (inspect)
# 전체 정보 JSON
docker inspect myapp
# 환경변수 확인
docker inspect myapp --format '{{range .Config.Env}}{{println .}}{{end}}'
# IP 주소 및 네트워크
docker inspect myapp --format '{{.NetworkSettings.IPAddress}}'
docker inspect myapp --format '{{json .NetworkSettings.Networks}}' | jq .
# 마운트 정보
docker inspect myapp --format '{{json .Mounts}}' | jq .
# 헬스체크 결과
docker inspect myapp --format '{{json .State.Health}}' | jq .
셸 없는 컨테이너 디버깅: nsenter
distroless 이미지처럼 셸이 없는 컨테이너는 docker exec로 진입할 수 없다. nsenter를 사용하면 컨테이너의 네임스페이스에 호스트 도구를 들고 들어갈 수 있다.
# 컨테이너 PID 조회
PID=$(docker inspect --format '{{.State.Pid}}' myapp)
# 컨테이너 네임스페이스로 진입 (호스트에서 실행)
nsenter --target "$PID" --mount --uts --ipc --net --pid -- sh
# 네트워크 네임스페이스만 진입 (IP 확인용)
nsenter --target "$PID" --net -- ip addr show
nsenter --target "$PID" --net -- ss -tlnp
docker debug (Docker Desktop 4.27+)
Docker Desktop은 공식 docker debug 명령으로 임시 디버그 컨테이너를 주입한다. distroless/scratch 이미지도 디버깅 가능하다.
# 디버그 컨테이너 시작
docker debug myapp
# 특정 도구가 포함된 이미지로 디버깅
docker debug --image=nicolaka/netshoot myapp
Kubernetes에서 임시 컨테이너
Kubernetes 1.23+에서는 kubectl debug로 실행 중인 파드에 임시 컨테이너를 삽입할 수 있다.
# 임시 컨테이너 추가 (같은 네임스페이스 공유)
kubectl debug -it mypod \
--image=nicolaka/netshoot \
--target=myapp-container
# 파드 복사본 생성 (원본 유지하면서 디버깅)
kubectl debug mypod \
-it --image=ubuntu \
--copy-to=debug-pod \
--share-processes
nicolaka/netshoot은 네트워크 디버깅 도구(curl, dig, tcpdump, iperf3 등)를 포함한 유용한 디버그 이미지다.
파일시스템 스냅샷
실행 중인 컨테이너의 파일시스템을 호스트로 복사해 분석할 수 있다.
# 특정 파일 복사
docker cp myapp:/app/logs/error.log ./error.log
# 컨테이너 파일시스템 전체 덤프
docker export myapp | tar -xf - -C ./container-fs/
# 변경된 파일 확인 (이미지 대비 diff)
docker diff myapp
# A: Added / C: Changed / D: Deleted
지난 글: Cosign으로 Docker 이미지 서명하기
다음 글: 실패한 컨테이너 원인 분석하기
읽어주셔서 감사합니다. 😊