컨테이너가 시작되자마자 종료되거나, 갑자기 재시작되거나, 응답이 없습니다. 로그도 안 보이고 에러 메시지도 없을 때, 어디서부터 확인해야 할까요?

디버깅의 기본 흐름

PLAINTEXT
컨테이너 문제 발생

  ├── 1. 상태 확인: docker ps -a

  ├── 2. 로그 확인: docker logs

  ├── 3. 상세 정보: docker inspect

  ├── 4. 리소스 확인: docker stats

  ├── 5. 내부 진입: docker exec

  └── 6. 이벤트 확인: docker events

1단계: 컨테이너 상태 확인

BASH
# 실행 중인 컨테이너
docker ps

# 종료된 컨테이너 포함
docker ps -a

# 출력 예시
# CONTAINER ID  IMAGE       STATUS                      PORTS   NAMES
# abc123        myapp       Up 2 hours                  8080    api
# def456        myworker    Exited (137) 5 minutes ago          worker
# ghi789        mydb        Exited (1) 10 minutes ago           db

종료 코드(Exit Code) 분석

코드의미
0정상 종료
1일반적인 애플리케이션 에러
126명령어 실행 불가 (퍼미션 문제)
127명령어를 찾을 수 없음
128+N시그널 N에 의한 종료
137128+9(SIGKILL) — OOM Killer 또는 docker kill
139128+11(SIGSEGV) — 세그멘테이션 폴트
143128+15(SIGTERM) — docker stop의 정상 종료
BASH
# 종료 코드 확인
docker inspect --format '{{.State.ExitCode}}' mycontainer
# 137

# OOM Killed 여부 확인
docker inspect --format '{{.State.OOMKilled}}' mycontainer
# true

2단계: 로그 확인

BASH
# 전체 로그
docker logs mycontainer

# 실시간 로그 follow
docker logs -f mycontainer

# 최근 100줄
docker logs --tail 100 mycontainer

# 타임스탬프 포함
docker logs -t mycontainer

# 특정 시간 이후
docker logs --since "2026-03-19T10:00:00" mycontainer
docker logs --since 30m mycontainer

# 특정 기간
docker logs --since 1h --until 30m mycontainer

로그가 안 보일 때

BASH
# 로그 드라이버 확인
docker inspect --format '{{.HostConfig.LogConfig.Type}}' mycontainer
# json-file, syslog, journald, none 등

# json-file이 아니면 docker logs로 볼 수 없음
# 로그 파일 직접 확인
cat /var/lib/docker/containers/<container-id>/<container-id>-json.log

애플리케이션이 stdout/stderr가 아닌 파일에 로그를 남기는 경우:

BASH
# 컨테이너 내부의 로그 파일 직접 확인
docker exec mycontainer cat /var/log/app/error.log

# 또는 로그 파일 복사
docker cp mycontainer:/var/log/app/error.log ./error.log

3단계: 상세 정보 확인 (docker inspect)

BASH
# 전체 정보 (JSON)
docker inspect mycontainer | jq

# 자주 사용하는 필드들
# 상태 정보
docker inspect --format '{{json .State}}' mycontainer | jq
# {
#   "Status": "exited",
#   "Running": false,
#   "Pid": 0,
#   "ExitCode": 137,
#   "Error": "",
#   "StartedAt": "2026-03-19T10:00:00Z",
#   "FinishedAt": "2026-03-19T10:30:00Z",
#   "OOMKilled": true

네트워크 설정을 이어서 정의합니다.

BASH
# }

# IP 주소
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mycontainer

# 마운트된 볼륨
docker inspect --format '{{json .Mounts}}' mycontainer | jq

# 환경변수
docker inspect --format '{{json .Config.Env}}' mycontainer | jq

# 포트 매핑
docker inspect --format '{{json .NetworkSettings.Ports}}' mycontainer | jq

# 헬스체크 상태
docker inspect --format '{{json .State.Health}}' mycontainer | jq

4단계: 리소스 모니터링

BASH
# 실시간 리소스 사용량
docker stats

# 특정 컨테이너만
docker stats mycontainer

# CONTAINER  CPU %  MEM USAGE / LIMIT  MEM %  NET I/O       BLOCK I/O
# mycontainer 45.2% 384MiB / 512MiB    75.0%  12.5MB / 8MB  50MB / 20MB

# 한 번만 출력 (스크립팅용)
docker stats --no-stream mycontainer

메모리 문제 진단

BASH
# 메모리 제한과 현재 사용량
docker inspect --format '{{.HostConfig.Memory}}' mycontainer
# 536870912 (512MB)

docker stats --no-stream --format "{{.MemUsage}}" mycontainer
# 480MiB / 512MiB ← 거의 한계에 도달

# cgroup에서 직접 확인 (호스트에서)
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytes

5단계: 컨테이너 내부 진입

실행 중인 컨테이너

BASH
# 셸로 접속
docker exec -it mycontainer /bin/sh
# 또는 bash가 있으면
docker exec -it mycontainer /bin/bash

# 특정 사용자로 실행
docker exec -it --user root mycontainer /bin/sh

# 일회성 명령 실행
docker exec mycontainer cat /etc/hosts
docker exec mycontainer env
docker exec mycontainer ps aux
docker exec mycontainer netstat -tlnp

종료된 컨테이너 디버깅

BASH
# 종료된 컨테이너에서 파일 복사
docker cp mycontainer:/var/log/app.log ./app.log
docker cp mycontainer:/app/config.json ./config.json

# 같은 이미지로 새 컨테이너를 만들어 디버깅
docker run -it --entrypoint /bin/sh myapp:latest

# 종료된 컨테이너를 커밋하여 이미지로 만들기
docker commit mycontainer debug-image
docker run -it --entrypoint /bin/sh debug-image

디버그 컨테이너 (sidecar)

최소 이미지(distroless 등)에는 셸이 없어서 exec로 접속할 수 없습니다. 이때 디버그 컨테이너를 사용합니다.

BASH
# 같은 네트워크에 디버그 컨테이너 연결
docker run -it --rm \
    --network container:mycontainer \
    nicolaka/netshoot \
    bash

# 내부에서 네트워크 디버깅
curl http://localhost:8080/health
tcpdump -i eth0
nslookup target-service

6단계: 이벤트 확인

BASH
# 실시간 Docker 이벤트 모니터링
docker events

# 특정 컨테이너의 이벤트
docker events --filter container=mycontainer

# 특정 이벤트 타입 필터
docker events --filter event=die
docker events --filter event=oom

# 시간 범위
docker events --since "2026-03-19T10:00:00" --until "2026-03-19T11:00:00"

이벤트 예시:

PLAINTEXT
2026-03-19T10:30:00 container die abc123 (exitCode=137, image=myapp)
2026-03-19T10:30:00 container oom abc123 (image=myapp)

자주 발생하는 문제와 해결

컨테이너가 시작 즉시 종료

BASH
# 원인 1: CMD/ENTRYPOINT가 포그라운드가 아님
# 나쁜 예
CMD nginx  # 백그라운드로 실행되어 즉시 종료
# 좋은 예
CMD ["nginx", "-g", "daemon off;"]

# 원인 2: 설정 파일 오류
docker logs mycontainer
# nginx: [emerg] unknown directive "servr" in /etc/nginx/nginx.conf:1

# 원인 3: 의존 서비스 미연결
docker logs mycontainer
# Error: connect ECONNREFUSED 172.17.0.2:5432

OOMKilled (메모리 초과)

BASH
# 확인
docker inspect --format '{{.State.OOMKilled}}' mycontainer
# true

# 해결 1: 메모리 제한 늘리기
docker run --memory 1g myapp

# 해결 2: 메모리 사용 패턴 분석
docker stats mycontainer  # 시간에 따른 메모리 증가 패턴 확인

# 해결 3: JVM 등 런타임 메모리 설정 조정
docker run --memory 512m -e JAVA_OPTS="-Xmx384m -Xms256m" myjava-app

네트워크 연결 실패

BASH
# DNS 해석 확인
docker exec mycontainer nslookup target-service

# 네트워크 연결 확인
docker exec mycontainer wget -qO- http://target-service:8080/health

# 같은 네트워크에 있는지 확인
docker network inspect mynetwork | jq '.[0].Containers'

# 포트가 열려있는지 확인
docker exec mycontainer nc -zv target-service 8080

퍼미션 거부

BASH
# 로그 확인
docker logs mycontainer
# Permission denied: '/app/data/file.txt'

# 컨테이너 내부 파일 소유자 확인
docker exec mycontainer ls -la /app/data/

# 해결: 소유권 수정 또는 올바른 사용자로 실행
docker run --user $(id -u):$(id -g) -v $(pwd)/data:/app/data myapp

디스크 공간 부족

BASH
# Docker 디스크 사용량 확인
docker system df
docker system df -v

# 사용하지 않는 리소스 정리
docker system prune        # 중지된 컨테이너, 미사용 네트워크, dangling 이미지
docker system prune -a     # 사용하지 않는 모든 이미지 포함
docker volume prune        # 미사용 볼륨
docker builder prune       # 빌드 캐시

디버깅 명령어 치트시트

BASH
# 상태 확인
docker ps -a
docker inspect <container>
docker stats

# 로그
docker logs -f --tail 100 <container>
docker logs --since 10m <container>

# 내부 접근
docker exec -it <container> /bin/sh
docker cp <container>:/path/to/file ./local-file

네트워크 설정을 이어서 정의합니다.

BASH

# 네트워크
docker network inspect <network>
docker exec <container> nslookup <target>

# 이벤트
docker events --filter container=<container>

# 정리
docker system df
docker system prune -a

주의할 점

1. 컨테이너가 재시작되면 이전 로그가 사라질 수 있다

--restart=always로 설정된 컨테이너가 죽고 다시 시작되면, 기본 json-file 로그 드라이버에서는 이전 실행의 로그가 유지되지만 일부 로그 드라이버에서는 유실됩니다. 또한 docker logs로 보는 것은 현재 컨테이너의 로그뿐이므로, 반복적으로 죽는 원인을 찾으려면 docker inspect로 이전 종료 상태(ExitCode, OOMKilled)를 먼저 확인하세요.

2. Exit Code 137이 항상 OOM Kill은 아니다

137은 SIGKILL(128+9)을 의미하는데, OOM Kill 외에도 docker stop(10초 후 SIGKILL)이나 docker kill, Kubernetes의 eviction에서도 137이 나옵니다. docker inspect에서 OOMKilled: true인지 확인하고, dmesg에서 OOM 관련 커널 로그를 찾아야 정확한 원인을 파악할 수 있습니다.

3. 최소 이미지(alpine, distroless)에는 디버깅 도구가 없어서 문제 진단이 어렵다

curl, wget, netstat, ss 같은 기본 도구가 없는 이미지에서는 네트워크 문제를 진단할 수 없습니다. docker exec로 들어가도 할 수 있는 것이 없습니다. nicolaka/netshoot 같은 디버그 컨테이너를 같은 네트워크에 붙이거나, Docker 25+의 docker debug 명령어를 활용하세요.

정리

  • 디버깅은 상태 → 로그 → inspect → stats → exec → events 순서로 접근합니다.
  • ** 종료 코드 **를 확인하면 문제의 종류를 빠르게 파악할 수 있습니다. 137은 OOM, 143은 정상 종료 시그널입니다.
  • docker inspect 의 State 필드에서 OOMKilled, ExitCode, Error 등 핵심 정보를 얻습니다.
  • distroless 같은 최소 이미지에서는 nicolaka/netshoot 같은 디버그 컨테이너를 sidecar로 붙여서 진단합니다.
  • 종료된 컨테이너에서는 docker cp로 파일을 꺼내거나, docker commit으로 이미지로 만들어 분석합니다.
댓글 로딩 중...