[도커 30강] 05강. 컨테이너 로그/상태 점검과 문제 진단 기본기
컨테이너를 실행하는 것보다 더 중요한 건, 문제가 생겼을 때 빨리 원인을 좁히는 능력입니다. 실무에서 장애를 길게 끄는 팀과 짧게 끝내는 팀의 차이는 화려한 도구가 아니라, docker logs, docker ps, docker inspect, docker stats 같은 기본 점검 루틴을 얼마나 일관되게 쓰느냐에서 갈립니다.
이번 강의에서는 “컨테이너가 안 떠요”, “떠 있는데 접속이 안 돼요”, “갑자기 느려졌어요”, “가끔 죽어요” 같은 흔한 상황을 기준으로, 로그/상태를 읽는 순서와 판단 기준을 잡아보겠습니다. 핵심은 명령어를 많이 아는 게 아니라, 같은 순서로 재현 가능하게 진단하는 습관입니다.
핵심 개념
- 로그(
docker logs)는 애플리케이션이 남긴 메시지이고, 상태(docker ps,docker inspect)는 런타임이 보는 컨테이너 사실 정보입니다. 둘을 분리해서 봐야 오진이 줄어듭니다. - 장애 진단의 첫 단계는 “실행 중인지”가 아니라 “왜 현재 상태가 되었는지”를 확인하는 것입니다.
Exited (1)인지Restarting인지에 따라 접근이 달라집니다. - 한 번의 명령으로 결론 내리지 말고, 상태 → 로그 → 설정값(inspect) → 리소스(stats) 순서로 좁혀야 재현성과 설명력이 생깁니다.
- 디버깅 시간의 대부분은 정보 수집 누락 때문에 낭비됩니다. 그래서 팀 단위로 최소 진단 체크리스트를 고정하는 게 중요합니다.
기본 사용
예제 1) 컨테이너 상태를 먼저 분류하기
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Ports}}"
설명:
docker ps만 쓰면 실행 중인 컨테이너만 보여서, 이미 죽은 원인을 놓칠 수 있습니다.-a를 붙여 종료/재시작/생성 상태를 같이 봐야 전체 그림이 잡힙니다.- 실무에서는 Name을 기준으로 보고서를 쓰기 쉬우니
--format으로 컬럼을 고정하는 습관이 좋습니다.
진단 포인트:
Exited (0): 정상 종료일 수도 있음(배치/원샷 작업 확인 필요)Exited (1)또는 비정상 코드: 애플리케이션 오류 가능성 큼Restarting: 헬스체크/프로세스 크래시 반복 가능성Up ... (healthy|unhealthy): 헬스체크 상태와 실제 트래픽 상태를 함께 봐야 함
예제 2) 로그를 시간 축으로 읽기
docker logs --tail 200 --timestamps my-app
docker logs -f --since 10m my-app
설명:
--tail은 최근 로그만 추려서 초기 분석 속도를 높입니다.--timestamps를 붙여 이벤트 시점 비교(배포 시점/모니터링 알람 시점) 정확도를 높입니다.-f는 실시간 추적,--since는 최근 구간만 집중 분석할 때 유용합니다.
초보가 헷갈리는 포인트:
- 로그가 없다고 “문제 없음”은 아닙니다. 앱이 시작도 못 했거나 stdout/stderr가 아닌 파일로만 로그를 쓰는 구조일 수 있습니다.
- 로그에 에러가 보여도 근본 원인은 환경변수, 볼륨, 포트, 권한일 수 있습니다. 반드시
inspect로 설정값을 확인하세요.
예제 3) 컨테이너 설정/종료 원인 확인
docker inspect my-app
docker inspect -f '{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}' my-app
docker inspect -f 'Restart={{.HostConfig.RestartPolicy.Name}} Env={{json .Config.Env}}' my-app
설명:
inspect는 컨테이너의 사실 기록입니다. 로그가 주장이라면 inspect는 계약서에 가깝습니다.ExitCode,OOMKilled,Error를 보면 “앱 자체 오류인지, 메모리 부족인지, 런타임 오류인지”를 빠르게 구분할 수 있습니다.- 재시작 정책(
always,unless-stopped,on-failure)은 장애 체감 방식에 큰 영향을 줍니다. 무한 재시작이면 로그가 계속 밀려 분석이 어려워질 수 있습니다.
예제 4) 리소스 병목 감지하기
docker stats --no-stream
설명:
- CPU/메모리/네트워크/블록 I/O를 한 번에 요약해서 봅니다.
- 느림 이슈가 있을 때 코드 문제만 의심하지 말고 리소스 압박 여부를 먼저 확인하세요.
- 메모리가 치솟고 OOMKilled가 반복되면 앱 튜닝 이전에 메모리 제한/캐시 정책/워크로드 패턴을 같이 점검해야 합니다.
예제 5) “접속 안 됨”을 3단계로 분해하기
# 1) 컨테이너 내부 포트 리슨 여부
docker exec -it my-app sh -c "ss -lntp || netstat -lntp"
# 2) 호스트 포트 매핑 확인
docker port my-app
# 3) 호스트에서 실제 접근 확인
curl -i http://localhost:8080
설명:
- 내부 프로세스가 안 떠 있으면 포트 매핑이 있어도 당연히 실패합니다.
- 포트 매핑이 없으면 앱이 잘 떠 있어도 외부 접근은 실패합니다.
- 즉, “앱 문제/도커 설정 문제/호스트 네트워크 문제”를 층위별로 나눠서 봐야 원인이 빨리 좁혀집니다.
자주 하는 실수
실수 1) 로그 한 줄만 보고 원인을 단정
- 원인: 에러 메시지 키워드만 보고 라이브러리 버그라고 결론 내림.
- 해결: 같은 시점의
inspect상태(ExitCode, OOMKilled, Error)와 리소스 지표를 반드시 교차 확인합니다.
실수 2) docker ps만 보고 “컨테이너가 없다”고 판단
- 원인: 실행 중인 컨테이너만 조회해서 이미 죽은 컨테이너를 놓침.
- 해결: 장애 분석 기본 명령은 항상
docker ps -a로 시작합니다.
실수 3) 재시작 루프에서 -f 로그만 멍하니 봄
- 원인: 재시작 정책/헬스체크 실패 원인을 확인하지 않고 실시간 로그만 추적.
- 해결:
inspect에서 RestartPolicy, Health 상태를 먼저 보고, 그다음 로그를 시계열로 읽습니다.
실수 4) 로컬에서는 되는데 서버에서만 실패하는데 환경변수 미검증
- 원인:
.env누락, 변수명 오타, 값 포맷 차이(JSON 문자열/숫자 등) 미확인. - 해결:
docker inspect -f '{{json .Config.Env}}'로 실제 주입된 값을 확인하고, 운영 비밀값은 마스킹된 방식으로 점검합니다.
실수 5) “느리다”를 앱 코드 탓으로만 돌림
- 원인: 컨테이너 메모리 제한, CPU 쿼터, 디스크 I/O 병목을 확인하지 않음.
- 해결:
docker stats --no-stream와 호스트 리소스 지표를 함께 확인해 병목 지점을 먼저 분류합니다.
실무 패턴
현장에서 효과가 큰 방법은 “장애 때 뭘 먼저 치는지”를 사람마다 다르게 두지 않는 것입니다. 아래는 팀에서 바로 적용 가능한 최소 진단 루틴입니다.
- 상태 스냅샷 고정
docker ps -a결과를 먼저 캡처하고, 문제 컨테이너 이름/상태/종료코드를 기록합니다.- 이 단계를 건너뛰면 분석 중 컨테이너가 재기동되어 원래 상태를 잃어버릴 수 있습니다.
- 로그 수집 범위 표준화
- 기본값을
--tail 200 --timestamps로 정해, 팀원 간 같은 근거를 봅니다. - 장애 시간이 특정되면
--since를 붙여 잡음을 줄입니다.
- inspect 핵심 필드만 추출
- 전체 JSON은 길기 때문에 초기 대응에서는
State/RestartPolicy/Env/Mounts/Ports만 우선 확인합니다. - 특히
OOMKilled,ExitCode,Health는 장애 보고서의 핵심 근거가 됩니다.
- 재현 가능한 결론 문장 작성
- “아마 메모리 문제” 대신 “OOMKilled=true, 메모리 사용률 95% 이상 반복, ExitCode 137”처럼 근거 기반으로 기록합니다.
- 이후 개선 작업(리밋 상향, 캐시 조정, 워커 수 조절)이 추적 가능해집니다.
- 운영 체크 명령 세트 스크립트화
- 팀 공용 스크립트(예:
./scripts/diag-container.sh <name>)로 상태/로그/inspect/stats를 한 번에 수집하면 야간 대응 품질이 크게 올라갑니다. - 중요한 건 자동화 자체보다, 사람마다 다른 대응 습관을 줄이는 것입니다.
오늘의 결론
한 줄 요약: 도커 장애 대응은 감이 아니라 순서다 — 상태 확인, 로그 확인, 설정 확인, 리소스 확인의 4단계를 고정하면 해결 속도가 빨라진다.
컨테이너 진단에서 가장 비싼 비용은 명령어를 몰라서가 아니라, 올바른 순서를 지키지 않아 같은 실수를 반복하는 데서 나옵니다. 오늘 배운 루틴을 그대로 템플릿처럼 쓰면, 초보자도 “문제를 설명할 수 있는 사람”으로 빠르게 성장할 수 있습니다.
연습문제
- 일부러 잘못된 환경변수를 넣어 컨테이너를 실행한 뒤,
docker ps -a와docker logs --tail 100으로 실패 원인을 찾아보세요. 원인을 한 문장으로 정리해보세요. - 재시작 정책(
--restart on-failure)을 적용한 컨테이너를 만들어 재시작 루프를 관찰해보세요.inspect에서 어떤 필드로 루프를 확인했는지 적어보세요. - 메모리를 많이 쓰는 작업을 실행한 뒤
docker stats --no-stream와inspect의OOMKilled를 비교해보세요. 두 정보가 어떻게 연결되는지 설명해보세요.
이전 강의 정답
4강 연습문제 해설:
-it --rm으로alpine셸에 들어가 파일을 만들고exit하면,--rm때문에 컨테이너 객체가 자동 삭제되어docker ps -a에서 남지 않습니다.nginx:alpine을-d -p 8082:80으로 실행하면curl http://localhost:8082접속이 가능하지만,-p없이 실행하면 컨테이너 내부 서비스가 떠 있어도 호스트에서 직접 접근할 수 없습니다.- 로컬 폴더를
-v로 마운트하면 파일 수정이 즉시 반영되어, 컨테이너 재빌드/재시작 없이도 정적 콘텐츠 변경을 확인할 수 있습니다.
실습 환경/재현 정보
- OS: macOS 15+ 또는 Ubuntu 22.04+
- Docker 버전: Docker Engine 25.x+ (Docker Desktop 최신 안정 버전 포함)
- 사용 이미지 예시:
nginx:alpine,alpine:3.20, 실습용 앱 이미지 - 실행 순서:
docker ps -a로 상태 스냅샷 확보docker logs --tail 200 --timestamps <name>로 로그 확인docker inspect핵심 필드(State/ExitCode/OOMKilled/RestartPolicy/Env) 점검docker stats --no-stream로 리소스 병목 여부 확인- 필요 시
docker exec,docker port,curl로 접근 경로 분해 진단
- 재현 체크:
- 실패 상황에서 상태/로그/설정/리소스를 각각 분리해 설명 가능한가?
- 원인 결론을 명령 결과(근거)와 함께 적을 수 있는가?
- 같은 문제를 팀원이 동일한 루틴으로 다시 확인할 수 있는가?