[도커 30강] 26강. 배포 전 점검 체크리스트 자동화
실서비스 배포 직전에 가장 많이 터지는 문제는 "어려운 기술"이 아니라 "빠뜨린 기본 점검"에서 나옵니다. 예를 들어 이미지 태그를 latest로만 올려서 롤백 포인트를 잃거나, 헬스체크는 정의했는데 실제 엔드포인트가 바뀌어 무의미해지는 식입니다. 그래서 팀 규모가 커질수록 배포 품질은 개인 역량보다 체크리스트의 자동화 수준으로 결정됩니다.
이번 강의에서는 사람이 머리로 기억하던 배포 전 점검 항목을 스크립트로 고정하는 방법을 다룹니다. 목표는 단순합니다. "배포 전 5분 점검"을 누구나 같은 품질로 수행하게 만드는 것입니다. 그리고 실패하면 배포를 멈추는 기준(게이트)까지 포함해, 실무에서 바로 적용 가능한 형태로 정리하겠습니다.
핵심 개념
- 체크리스트는 문서만으로는 오래 못 갑니다. 자동 실행 가능한 검사 스크립트가 되어야 팀이 바뀌어도 품질이 유지됩니다.
- 배포 전 점검은 크게 4축으로 나눕니다: 이미지 무결성(빌드/태그), 런타임 설정(환경변수/시크릿), 가용성(헬스체크/리소스), 운영성(로그/관측/롤백 포인트).
- "문제가 있으면 알림" 수준에서 끝내지 말고, 배포 파이프라인에서 fail fast(즉시 실패) 하도록 설계해야 사고를 줄일 수 있습니다.
- 체크리스트는 많을수록 좋은 게 아닙니다. 핵심 위험을 막는 항목부터 작은 집합으로 시작해, 장애 회고를 통해 항목을 점진적으로 늘리는 방식이 현실적입니다.
- 자동화된 점검 결과는 텍스트 로그로만 남기지 말고, 배포 이력(커밋 SHA, 이미지 다이제스트, 검사 결과)과 함께 보관해야 사후 분석이 쉬워집니다.
기본 사용
예제 1) 배포 전 점검 스크립트 뼈대 만들기
mkdir -p ~/docker100/lesson26/scripts && cd ~/docker100/lesson26
cat > scripts/predeploy_check.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
echo "[1/5] compose 문법 검사"
docker compose -f compose.prod.yaml config >/dev/null
echo "[2/5] 필수 환경변수 검사"
required_vars=(APP_ENV DB_HOST DB_USER DB_PASSWORD IMAGE_TAG)
for v in "${required_vars[@]}"; do
if [[ -z "${!v:-}" ]]; then
echo "ERROR: missing env var -> $v"
exit 1
fi
done
echo "[3/5] 이미지 태그 규칙 검사(latest 금지)"
if [[ "${IMAGE_TAG}" == "latest" ]]; then
echo "ERROR: IMAGE_TAG=latest is not allowed"
exit 1
fi
echo "[4/5] 헬스체크 경로 검사"
if ! grep -q "/health" compose.prod.yaml; then
echo "ERROR: healthcheck endpoint not found"
exit 1
fi
echo "[5/5] 완료"
echo "OK: predeploy checks passed"
BASH
chmod +x scripts/predeploy_check.sh
설명:
set -euo pipefail은 체크 스크립트의 기본 안전장치입니다. 한 단계라도 실패하면 즉시 종료되어 잘못된 배포를 막습니다.- 환경변수 검사는 "비어 있음"도 실패로 처리해야 합니다. 키만 존재하고 값이 빈 경우가 실무에서 자주 발생합니다.
latest금지는 롤백 가능성을 확보하기 위한 최소 규칙입니다. 태그는 최소한v2026.02.17-<sha>처럼 추적 가능해야 합니다.
예제 2) 이미지/보안/런타임 항목 확장하기
cat > scripts/predeploy_deep_check.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
IMAGE_REF="${IMAGE_REF:?IMAGE_REF is required}"
echo "[A] 이미지 pull 가능 여부"
docker pull "$IMAGE_REF" >/dev/null
echo "[B] 컨테이너 사용자 검사(non-root 권장)"
USER_FIELD=$(docker image inspect "$IMAGE_REF" --format '{{.Config.User}}')
if [[ -z "$USER_FIELD" || "$USER_FIELD" == "0" || "$USER_FIELD" == "root" ]]; then
echo "ERROR: image runs as root user"
exit 1
fi
echo "[C] 읽기 전용 루트파일시스템 설정 확인"
if ! grep -q "read_only: true" compose.prod.yaml; then
echo "WARN: read_only not enabled (권장)"
fi
echo "[D] 메모리 제한 확인"
if ! grep -q "mem_limit:" compose.prod.yaml; then
echo "ERROR: mem_limit is missing"
exit 1
fi
echo "[E] 재시작 정책 확인"
if ! grep -Eq "restart:\s*(always|unless-stopped)" compose.prod.yaml; then
echo "ERROR: restart policy is missing"
exit 1
fi
echo "OK: deep predeploy checks passed"
BASH
chmod +x scripts/predeploy_deep_check.sh
설명:
- 점검 항목은 "배포 실패를 막는 하드 체크"와 "개선 권고(경고)"를 분리해야 운영이 편합니다.
- non-root 실행 여부, 메모리 제한, 재시작 정책은 장애 전파를 줄이는 핵심 항목입니다.
- 실제 팀에서는 여기서 더 나아가 이미지 취약점 스캔(예: Trivy) 결과 임계치도 게이트에 포함합니다.
예제 3) CI 파이프라인에서 점검을 게이트로 연결하기
cat > .github/workflows/predeploy-gate.yml <<'YAML'
name: predeploy-gate
on:
workflow_dispatch:
push:
branches: [ "main" ]
jobs:
gate:
runs-on: ubuntu-latest
env:
APP_ENV: production
DB_HOST: prod-db.internal
DB_USER: app
DB_PASSWORD: dummy-for-ci
IMAGE_TAG: v2026.02.17-a1b2c3d
IMAGE_REF: ghcr.io/acme/myapp:v2026.02.17-a1b2c3d
steps:
- uses: actions/checkout@v4
- name: Docker version
run: docker version
- name: Basic predeploy check
run: bash scripts/predeploy_check.sh
- name: Deep predeploy check
run: bash scripts/predeploy_deep_check.sh
YAML
git add scripts .github/workflows/predeploy-gate.yml
git status
설명:
- 배포 직전 수동으로 한 번만 점검하면 누락이 다시 생깁니다.
main진입 시마다 자동 점검해 기준을 강제해야 합니다. - CI에서 실패하면 "다음 단계 배포" 자체가 진행되지 않도록 연결하는 게 핵심입니다.
- 실제 비밀번호를 CI에 직접 넣지 말고, 시크릿 매니저/GitHub Secrets를 사용하세요. 위 예시는 구조 설명용입니다.
예제 4) 배포 전 최종 리허설(스모크 테스트) 포함
cat > scripts/predeploy_smoke.sh <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
STACK_NAME="smoke"
COMPOSE_FILE="compose.prod.yaml"
# 임시 스택 기동
docker compose -p "$STACK_NAME" -f "$COMPOSE_FILE" up -d --wait
# 핵심 엔드포인트 점검
curl -fsS http://localhost:8080/health >/dev/null
curl -fsS http://localhost:8080/ready >/dev/null
# 로그 에러 문자열 간단 점검
if docker compose -p "$STACK_NAME" -f "$COMPOSE_FILE" logs | grep -qi "exception\|fatal\|panic"; then
echo "ERROR: suspicious log pattern detected"
docker compose -p "$STACK_NAME" -f "$COMPOSE_FILE" down -v
exit 1
fi
# 정리
docker compose -p "$STACK_NAME" -f "$COMPOSE_FILE" down -v
echo "OK: smoke test passed"
BASH
chmod +x scripts/predeploy_smoke.sh
bash scripts/predeploy_smoke.sh
설명:
- 단순 정적 검사만으로는 런타임 문제를 못 잡습니다. 최소한의 스모크 테스트를 같이 돌려야 배포 직후 장애를 줄일 수 있습니다.
--wait를 사용하면 서비스 준비 상태를 기다린 뒤 검사를 진행하므로, 기동 타이밍 이슈를 줄일 수 있습니다.- 스모크 테스트는 짧고 확실한 것부터 시작하세요. 핵심 URL 2~3개만 안정적으로 검증해도 효과가 큽니다.
자주 하는 실수
실수 1) 체크리스트가 위키 문서로만 존재
- 원인: 작성은 쉽지만 실행 강제력이 없어서, 바쁜 날엔 생략됩니다.
- 해결: 문서를 스크립트로 옮기고 CI 게이트에 연결해 "건너뛰기 불가" 상태를 만드세요.
실수 2) 모든 항목을 동일한 중요도로 처리
- 원인: 체크리스트가 길어질수록 팀이 피로해지고, 결국 형식적 확인만 하게 됩니다.
- 해결: 배포 중단이 필요한 하드 체크와 경고성 항목을 분리하세요. 중단 규칙은 적고 강하게 유지합니다.
실수 3) latest 태그/수동 이미지 선택을 허용
- 원인: 빠른 테스트 습관이 운영까지 이어집니다.
- 해결: 커밋 SHA 기반 고정 태그만 허용하고, 배포 기록에 이미지 다이제스트를 남기세요.
실수 4) 점검 통과 기록을 남기지 않음
- 원인: "통과했으니 됐다"로 끝내고 증적을 저장하지 않습니다.
- 해결: 체크 결과를 아티팩트로 저장하고 릴리스 노트에 링크해, 장애 시 즉시 추적 가능하게 만드세요.
실무 패턴
실무에서는 배포 점검을 보통 3단계로 구성합니다. 첫 번째는 정적 검사(Compose 유효성, 필수 env, 태그 정책), 두 번째는 이미지/런타임 검사(non-root, 리소스 제한, 재시작 정책), 세 번째는 짧은 스모크 테스트(헬스/레디니스/핵심 API)입니다. 이 3단계를 모두 통과했을 때만 프로덕션 배포 버튼이 열리도록 구성하면, "아는 사람만 조심해서 배포"하는 문화에서 "시스템이 기본 실수를 막는 문화"로 바뀝니다.
또한 팀 단위로 운영하려면 점검 항목 소유권이 필요합니다. 예를 들어 플랫폼팀은 배포 공통 게이트를 관리하고, 서비스팀은 서비스별 스모크 시나리오를 관리하는 식으로 분리하면 유지보수가 쉬워집니다. 이때 중요한 것은 항목 수를 늘리는 것이 아니라, 실제 장애를 줄인 항목을 유지하는 것입니다. 장애 회고에서 반복 등장한 원인만 체크리스트에 추가하면 점검 품질이 계속 좋아집니다.
관측 측면에서는 "왜 실패했는지"를 즉시 알 수 있어야 합니다. 실패 로그를 장황하게 쌓기보다, 원인과 수정 힌트를 한 줄로 제공하는 것이 효과적입니다. 예: ERROR: IMAGE_TAG=latest is not allowed (use immutable tag, e.g. vYYYY.MM.DD-<sha>). 이런 메시지는 신입도 바로 대응할 수 있게 해줍니다.
마지막으로, 자동화는 예외 처리까지 포함해야 완성됩니다. 긴급 핫픽스 상황에서 체크를 임시 완화할 수는 있지만, 누가 언제 어떤 근거로 우회했는지 반드시 남겨야 합니다. "예외 없는 규칙"보다 "예외가 기록되는 규칙"이 실제 운영에서는 더 안전합니다.
오늘의 결론
한 줄 요약: 배포 품질은 사람의 기억력이 아니라, 실패 시 즉시 멈추는 자동 체크리스트 게이트로 만든다.
연습문제
- 현재 서비스 기준으로 "배포 중단이 필요한 하드 체크 5개"를 정의하고,
predeploy_check.sh에 반영해보세요. - latest 태그 금지, non-root 강제, 메모리 제한 필수 규칙을 CI에서 실패 처리하도록 구성해보세요.
- 스모크 테스트에 핵심 API 2개를 추가하고, 실패 시 자동 롤백(또는 배포 중단) 흐름까지 문서화해보세요.
이전 강의 정답
25강 연습문제 해설:
- 논리 백업 스크립트의 정답 패턴은
서비스명+시각+버전파일명으로 추적성을 확보하는 것입니다. 예:appdb_20260217_2300_v1.dump. - 백업의 완료 기준은 파일 생성이 아니라 복구 검증입니다. 별도 복구 DB(
appdb_restore)에pg_restore후 핵심 테이블 row 수/샘플 조회를 통과해야 "성공"입니다. - 장애 시나리오별 RPO/RTO를 수치로 선언해야 운영 의사결정이 빨라집니다. 예를 들어 실수 삭제는 RPO 1시간/RTO 30분, 호스트 장애는 RPO 24시간/RTO 2시간처럼 목표를 먼저 정하고 절차를 맞추는 접근이 정답입니다.
실습 환경/재현 정보
- OS: macOS 15+ 또는 Ubuntu 22.04+
- Docker 버전: Docker Engine/Desktop 25.x 이상
- 실행 순서:
compose.prod.yaml준비 및 필수 env 정의scripts/predeploy_check.sh로 기본 하드 체크 실행scripts/predeploy_deep_check.sh로 이미지/런타임 심화 점검scripts/predeploy_smoke.sh로 임시 스택 기동 후 스모크 테스트- CI 게이트에 연결해 main 병합/배포 전 자동 강제
- 재현 체크:
- 필수 환경변수 누락 시 즉시 실패하는가?
latest태그 사용 시 배포가 중단되는가?- non-root/메모리 제한/재시작 정책 누락을 탐지하는가?
- 스모크 테스트 실패 시 정리(down -v) 후 실패 반환하는가?
- 점검 결과와 배포 이력이 함께 보관되는가?