[도커 30강] 29강. 운영 장애 대응: 롤백/재기동/원인분석

[도커 30강] 29강. 운영 장애 대응: 롤백/재기동/원인분석

운영 환경에서 Docker를 쓰기 시작하면 언젠가 반드시 마주치는 순간이 있습니다. "방금 배포했는데 응답 시간이 급격히 늘어났어요", "컨테이너가 계속 재시작해요", "CPU가 치솟는데 원인을 모르겠어요" 같은 장애 상황입니다. 이때 실무에서 중요한 건 영웅적인 감이 아니라, 정해진 순서로 빠르게 안정화하고 근거를 남기는 대응 체계입니다.

이번 강의에서는 운영 장애가 발생했을 때 많이 사용하는 세 가지 축, 즉 롤백(rollback), 재기동(restart), 원인분석(root cause analysis) 을 Docker 관점으로 정리합니다. 핵심은 단순히 컨테이너를 다시 띄우는 게 아니라, "서비스를 먼저 살리고, 같은 장애를 반복하지 않게 만드는 것"입니다.


핵심 개념

  • 장애 대응의 1순위는 원인 규명보다 서비스 영향 최소화(복구 시간 단축) 입니다. 즉, 먼저 안정화(롤백/우회)하고 이후 원인을 깊게 파는 것이 기본 순서입니다.
  • 롤백은 감으로 하는 작업이 아니라, 직전 정상 이미지 태그 또는 digest를 기준으로 재배포하는 절차여야 합니다.
  • 재기동은 치료제가 아니라 응급처치입니다. 재기동으로 일시 회복하더라도 반드시 로그, 이벤트, 리소스 지표를 모아 근본 원인을 확인해야 합니다.
  • 원인분석에서는 "무엇이 바뀌었는가"를 먼저 보세요. 코드 변경, 이미지 변경, 환경변수 변경, 외부 의존성(DB/Redis/API) 상태를 함께 점검해야 합니다.
  • 장애 대응 기록(시간, 조치, 결과)을 남기면 다음 사고 때 MTTR(평균 복구 시간)을 크게 줄일 수 있습니다.

기본 사용

예제 1) 직전 정상 이미지로 즉시 롤백

APP=myapp
IMAGE=ghcr.io/acme/myapp
PREV_SHA=8f3a4b29c1de

# 직전 정상 태그 pull
docker pull ${IMAGE}:${PREV_SHA}

# 기존 컨테이너 중지/교체
docker stop ${APP} || true
docker rm ${APP} || true
docker run -d --name ${APP} --restart unless-stopped -p 8080:8080 ${IMAGE}:${PREV_SHA}

# 즉시 상태 확인
docker ps --filter name=${APP}
curl -fsS http://127.0.0.1:8080/health

설명:

  • 장애 상황에서 "무엇을 고쳐서 다시 배포"보다 "직전 정상 상태로 되돌리기"가 훨씬 빠르고 안전합니다.
  • 롤백 기준은 latest가 아니라 검증된 SHA 태그나 digest여야 재현성과 추적성이 확보됩니다.
  • 롤백 후에는 헬스체크/핵심 API를 바로 확인해 사용자 영향이 해소됐는지 먼저 판단합니다.

예제 2) 재기동 루프와 종료 원인 확인

APP=myapp

# 최근 로그와 종료 이벤트 확인
docker logs --tail 200 ${APP}
docker inspect ${APP} --format 'ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}} FinishedAt={{.State.FinishedAt}}'

# 재기동 정책/횟수 확인
docker inspect ${APP} --format 'RestartPolicy={{json .HostConfig.RestartPolicy}} RestartCount={{.RestartCount}}'

# 이벤트 스트림으로 재시작 패턴 파악
docker events --since 20m --filter container=${APP}

설명:

  • 컨테이너가 재시작되는 이유는 앱 예외, 메모리 부족(OOM), 헬스체크 실패 등 다양합니다.
  • docker inspect에서 OOMKilled=true가 보이면 코드 버그보다 메모리 제한/누수 가능성을 우선 점검해야 합니다.
  • docker events는 "정확히 언제, 어떤 순서로" 문제가 반복되는지 파악하는 데 유용합니다.

예제 3) 리소스 병목 원인 좁히기

APP=myapp

# 실시간 리소스 사용량 확인
docker stats --no-stream ${APP}

# 컨테이너 내부 프로세스 확인
docker top ${APP}

# 애플리케이션 헬스/의존성 점검
docker exec -it ${APP} sh -lc 'env | sort | head -n 40'
docker exec -it ${APP} sh -lc 'wget -qO- http://127.0.0.1:8080/health || true'

설명:

  • CPU/메모리 급등이 관찰되면 앱 내부 워커 폭증, 스레드 교착, 무한 재시도 루프를 의심해야 합니다.
  • 장애는 애플리케이션 내부 원인만 있는 게 아닙니다. 잘못된 환경변수, 끊긴 DB 연결, DNS 문제도 자주 등장합니다.
  • 빠른 원인 축소를 위해 "컨테이너 자체", "앱 프로세스", "외부 의존성"을 분리해서 확인하세요.

예제 4) Compose 기반 서비스의 안전한 롤백/재기동

# 현재 버전 백업
cp docker-compose.yml docker-compose.yml.bak

# 롤백할 이미지 태그로 교체
# image: ghcr.io/acme/myapp:8f3a4b29c1de

# 서비스 재생성
docker compose pull app
docker compose up -d app

# 배포 결과 확인
docker compose ps
docker compose logs app --tail 150

설명:

  • Compose 환경에서도 핵심은 같습니다. "정상 태그로 되돌리고 즉시 상태 확인"입니다.
  • 전체 스택 재기동보다 장애 서비스만 선택적으로 재배포하면 영향 범위를 줄일 수 있습니다.
  • 롤백한 파일(docker-compose.yml) 변경 내역은 반드시 Git에 기록해 추적 가능하게 유지하세요.

자주 하는 실수

실수 1) 장애 중에 원인 분석부터 집착

  • 원인: 기술적으로 맞는 답을 빨리 찾고 싶어 복구보다 분석에 시간을 씀.
  • 해결: 타임박스를 두고 먼저 복구 조치(롤백/우회)를 실행한 뒤, 안정화 후 분석 단계로 넘어갑니다.

실수 2) latest 태그로 롤백 시도

  • 원인: 편해서 latest를 기준 이미지로 사용.
  • 해결: 운영 배포 이력에 SHA/digest를 남기고, 롤백 자동화 입력값도 불변 태그만 허용합니다.

실수 3) 재기동 성공을 완전 복구로 오해

  • 원인: 컨테이너가 살아났다는 사실에 안심하고 조사 중단.
  • 해결: 최소 15~30분 모니터링하며 에러율/지연시간/재시작 횟수를 추적해 재발 여부를 확인합니다.

실수 4) 로그 보존 없이 컨테이너 삭제

  • 원인: 급한 마음에 docker rm -f를 먼저 실행.
  • 해결: 삭제 전 로그/inspect/events를 반드시 수집하고 사고 티켓에 첨부합니다.

실무 패턴

실무에서는 장애 대응을 크게 세 단계로 나눕니다. 1단계 안정화, 2단계 원인 확인, 3단계 재발 방지입니다. 안정화 단계에서는 사용자 영향이 있는 지표(오류율, 응답 시간, 처리량)를 기준으로 롤백이나 트래픽 우회를 먼저 결정합니다. 이때 좋은 팀은 "누가 잘못했는지"보다 "얼마나 빨리 안전 상태로 되돌릴지"에 집중합니다.

원인 확인 단계에서는 반드시 시간축을 맞춥니다. 예를 들어 21:05 배포, 21:07 오류율 상승, 21:10 재시작 루프 시작 같은 식으로 사건 타임라인을 만듭니다. 타임라인이 없으면 로그를 많이 모아도 결론이 흔들립니다. Docker 환경에서는 docker events, 컨테이너 로그, 배포 시스템 이력(Git SHA, CI run id), 인프라 지표를 한 줄로 연결해야 정확한 분석이 가능합니다.

재발 방지 단계에서 자주 쓰는 패턴은 체크리스트화입니다. 예를 들면 "배포 전 smoke test 통과", "메모리 제한 검증", "헬스체크 실패 시 자동 롤백", "장애 시 10분 내 실행할 명령어 세트"를 문서와 스크립트로 고정합니다. 사람이 기억하는 대응은 언젠가 틀리지만, 스크립트와 절차는 팀 자산으로 축적됩니다.

또 하나 중요한 것은 커뮤니케이션입니다. 장애 대응 중에는 5~10분 간격으로 현재 상태(영향 범위, 임시 조치, 다음 액션)를 공유해야 합니다. 기술적으로는 잘 대응했는데도 공유가 없으면 팀 신뢰가 급격히 떨어집니다. 반대로 조치 근거를 투명하게 남기면 같은 장애가 와도 훨씬 침착하게 대응할 수 있습니다.

마지막으로, Docker 자체 설정도 예방적 관점에서 정비해두면 좋습니다. --restart 정책을 서비스 성격에 맞게 설정하고, 메모리/CPU 제한을 무제한으로 두지 않으며, 헬스체크를 실제 의존성 상태를 반영하도록 구성하세요. 장애는 "발생 후 대응"만이 아니라 "발생 전 설계"에서 이미 절반이 결정됩니다.

오늘의 결론

한 줄 요약: 운영 장애 대응의 핵심은 재기동 기술이 아니라, 불변 태그 기반 롤백과 근거 있는 원인분석을 빠르게 반복 가능한 절차로 만드는 것이다.

연습문제

  1. 현재 운영 중인 서비스 하나를 골라 직전 정상 SHA 태그 기준 롤백 절차를 문서화하고, 스테이징에서 실제로 1회 리허설해보세요.
  2. 장애 대응 체크 스크립트(logs, inspect, events, stats)를 1개 bash 파일로 만들고, 3분 안에 핵심 진단 정보가 나오도록 개선해보세요.
  3. 최근 배포 3건을 대상으로 "배포 시각-지표 변화-에러 발생" 타임라인을 만들어 보고, 사전 탐지 가능했던 신호를 찾아보세요.

이전 강의 정답

28강 연습문제 해설:

  • 이미지 태그는 latest 단독이 아니라 SHA + latest 병행이 기본입니다. 배포 입력은 SHA(또는 digest)로 고정해야 장애 시 정확한 롤백이 가능합니다.
  • 운영 배포 로그에는 최소한 image digest, git commit, 배포 시각이 남아야 하며, 이 세 가지가 있어야 문제 버전을 빠르게 특정할 수 있습니다.
  • 주기적인 --pull --no-cache 빌드는 캐시 편향을 줄이고 보안 패치 누락을 발견하는 데 효과적입니다. 평시 캐시 빌드와 비교해 차이 리포트를 남기면 품질 관리가 쉬워집니다.

실습 환경/재현 정보

  • OS: macOS Sequoia / Ubuntu 22.04+
  • Docker 버전: Docker Engine 24+ 또는 25+, Docker Compose v2
  • 실행 순서:
    1. 장애 상황 재현(의도적 잘못된 이미지 태그 또는 메모리 과부하)
    2. 롤백 명령 실행으로 서비스 복구
    3. logs/inspect/events/stats 수집
    4. 타임라인 작성 및 원인 가설 검증
    5. 재발 방지 체크리스트/자동화 반영
  • 재현 체크:
    • 롤백 후 헬스체크가 1분 내 정상화되는가?
    • 재시작 횟수(RestartCount)가 안정화 후 증가하지 않는가?
    • 수집한 로그와 이벤트만으로 장애 시점을 설명할 수 있는가?
    • 같은 유형 장애에 대해 실행 가능한 대응 문서가 준비되어 있는가?