[도커 30강] 30강. 최종 미니 프로젝트: Docker 기반 운영형 파이썬 서비스
여기까지 오시느라 정말 고생 많았습니다. 이번 30강은 앞에서 배운 내용을 한 번에 묶어보는 최종 미니 프로젝트입니다. 목표는 단순히 “컨테이너가 뜨는 것”이 아니라, 실제 운영을 고려한 파이썬 서비스 구조를 Docker 기반으로 설계하고 검증하는 것입니다.
이번 실습에서는 FastAPI 기반 API 서비스, Postgres, Redis를 compose로 묶고, 개발/운영 설정 분리, 헬스체크, 리소스 제한, 로그 확인, 롤백 가능한 이미지 태그 전략까지 한 번에 다룹니다. 핵심은 화려한 기능보다 재현 가능성, 장애 대응 가능성, 팀 협업 가능성입니다.
핵심 개념
최종 프로젝트에서 가장 중요한 원칙은 “한 번 띄우는 데모”가 아니라 “여러 번 안전하게 배포 가능한 구조”입니다. 도커를 쓰는 이유는 실행 환경 통일이지만, 실무에서는 여기에 더해 운영 규칙(로그, 헬스체크, 자원 제한, 버전 추적)이 붙어야 가치가 생깁니다.
첫째, 서비스 경계를 명확히 나눕니다. API 앱 컨테이너, 데이터베이스 컨테이너, 캐시 컨테이너를 분리하고 네트워크로 연결하면 장애 범위를 줄이고 교체가 쉬워집니다.
둘째, 환경별 설정을 분리합니다. 개발 환경에서는 빠른 피드백(바인드 마운트, debug 로그)이 중요하고, 운영 환경에서는 불변 이미지, 최소 권한, 안정성(헬스체크/재기동 정책)이 중요합니다.
셋째, 배포 단위를 “코드”가 아니라 “이미지 태그”로 고정합니다. Git SHA 기반 태그를 쓰면 문제가 생겼을 때 정확히 어느 버전으로 돌아가야 하는지 명확해집니다.
넷째, 운영형 서비스는 관찰 가능해야 합니다. 컨테이너 로그, 헬스엔드포인트, restart 횟수, 리소스 사용량을 표준화해 두면 장애 대응 속도가 크게 빨라집니다.
다섯째, 문서화된 실행 절차가 필요합니다. 누가 실행해도 같은 결과가 나오는 명령 순서와 체크리스트가 있어야 팀 단위 운영이 가능합니다.
기본 사용
예제 1) 프로젝트 뼈대와 Dockerfile/compose 구성
mkdir -p docker100-final/app
cd docker100-final
cat > app/main.py <<'PY'
from fastapi import FastAPI
import os
app = FastAPI()
@app.get('/health')
def health():
return {
"status": "ok",
"service": "docker100-final",
"env": os.getenv("APP_ENV", "dev")
}
PY
cat > app/requirements.txt <<'REQ'
fastapi==0.115.0
uvicorn[standard]==0.30.6
psycopg2-binary==2.9.9
redis==5.0.8
REQ
설명:
- 가장 먼저 앱 골격을 만듭니다. 실무에서도 “헬스체크 가능한 최소 앱”을 우선 완성하면 인프라 검증이 쉬워집니다.
/health엔드포인트는 모니터링/배포 검증의 기준점 역할을 합니다.
cat > Dockerfile <<'DOCKER'
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
APP_ENV=prod
WORKDIR /app
RUN useradd -m -u 10001 appuser
COPY app/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt
COPY app /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
DOCKER
설명:
- non-root 유저로 실행해 기본 보안을 확보합니다.
- 불필요한 캐시를 줄이고 stdout 로그가 즉시 보이도록 환경변수를 설정했습니다.
예제 2) 운영형 compose 작성과 기동
cat > compose.yml <<'YAML'
services:
api:
build:
context: .
dockerfile: Dockerfile
image: devlab/docker100-final:${APP_TAG:-local}
container_name: docker100_api
ports:
- "8000:8000"
environment:
APP_ENV: "prod"
DATABASE_URL: "postgresql://app:app123@db:5432/appdb"
REDIS_URL: "redis://cache:6379/0"
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8000/health"]
interval: 15s
timeout: 3s
retries: 5
restart: unless-stopped
deploy:
resources:
limits:
cpus: "1.0"
memory: "512M"
db:
image: postgres:16
container_name: docker100_db
environment:
POSTGRES_DB: appdb
POSTGRES_USER: app
POSTGRES_PASSWORD: app123
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 10s
timeout: 3s
retries: 10
cache:
image: redis:7-alpine
container_name: docker100_cache
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
volumes:
db_data:
redis_data:
YAML
docker compose up -d --build
설명:
- API/DB/캐시를 분리해 서비스 경계를 분명히 했습니다.
- 헬스체크, 재기동 정책, 리소스 제한을 기본값으로 포함해 “운영형” 구성을 갖췄습니다.
- 볼륨을 통해 DB/Redis 데이터 영속성을 확보합니다.
예제 3) 검증/운영 점검/롤백 시나리오
# 기동 상태/헬스 확인
docker compose ps
curl -fsS http://127.0.0.1:8000/health
# 로그/리소스 관찰
docker compose logs api --tail 100
docker stats --no-stream docker100_api docker100_db docker100_cache
# 태그 기반 재배포
APP_TAG=20260217-a1b2c3 docker compose up -d --build api
# 문제 시 직전 태그 롤백
APP_TAG=20260216-z9y8x7 docker compose up -d api
설명:
- 배포 후 즉시 헬스와 로그를 확인해 정상 여부를 빠르게 판단합니다.
- 태그 기반 재배포/롤백 흐름을 미리 연습해두면 실제 장애 시 대응 시간이 줄어듭니다.
자주 하는 실수
실수 1) compose에 비밀번호/시크릿 하드코딩
- 원인: 빠른 테스트를 위해 민감 정보를 파일에 직접 작성한 뒤 그대로 운영에 사용.
- 해결: 운영에서는
.env분리, 비밀값 관리 도구(시크릿 매니저), 최소 권한 계정 사용을 기본 규칙으로 삼습니다.
실수 2) 개발 설정을 운영에 그대로 반영
- 원인: 바인드 마운트, debug 모드, auto-reload를 편해서 운영에도 유지.
- 해결:
compose.dev.yml과compose.prod.yml를 분리하고, 운영은 불변 이미지 중심으로 배포합니다.
실수 3) 헬스체크 없이 “컨테이너가 떠 있으면 정상”으로 판단
- 원인:
docker ps만 보고 서비스 정상이라 착각. - 해결: 실제 서비스 엔드포인트 기준 헬스체크를 넣고, 실패 시 재기동/알림 정책을 연결합니다.
실수 4) 태그 전략 부재로 롤백 실패
- 원인:
latest만 사용해서 직전 정상 버전 식별이 불가능. - 해결: Git SHA 또는 빌드 번호 기반 불변 태그를 운영 표준으로 고정합니다.
실무 패턴
실무에서 운영형 도커 서비스의 완성도는 “기동 성공”이 아니라 “운영 중 예측 가능성”으로 평가됩니다. 예측 가능성을 높이려면 세 가지를 고정해야 합니다.
첫 번째는 구성의 고정입니다. 어떤 환경에서든 같은 Dockerfile과 compose 규칙이 적용되어야 하며, 환경 차이는 변수로만 제어해야 합니다. 개인 PC에서는 되는데 서버에서 실패하는 대부분의 문제는 이 고정이 깨졌을 때 발생합니다.
두 번째는 배포의 고정입니다. 배포 파이프라인은 항상 같은 단계를 밟아야 합니다. 이미지 빌드 → 취약점/테스트 검증 → 태그 부여 → 레지스트리 푸시 → 배포 → 헬스 확인 → 모니터링. 이 단계가 자동화되어 있을수록 사람 실수는 줄고, 장애 복구는 빨라집니다.
세 번째는 운영 관찰의 고정입니다. 로그 포맷, 헬스체크 기준, 메트릭 수집 방식, 장애 대응 명령어를 팀 공통으로 통일해야 합니다. 장애가 났을 때 각자 다른 방식으로 확인하면 상황 파악에 시간이 오래 걸립니다.
팀 협업 관점에서는 “운영 체크리스트”를 저장소에 포함하는 것이 매우 중요합니다. 예를 들어 배포 전 체크(마이그레이션 영향, env diff, 태그 확인), 배포 직후 체크(health, error rate, latency), 장애 시 체크(logs/inspect/events) 같은 절차를 문서화하고, 실제 커맨드까지 적어두면 신입/주니어도 같은 품질로 운영할 수 있습니다.
또한 파이썬 서비스에서는 애플리케이션 코드와 컨테이너 설정을 분리해서 관리하는 습관이 좋습니다. 코드 PR과 인프라 PR을 함께 검토하되, 배포 영향도를 명시하면 리뷰 품질이 올라갑니다. Docker는 도구일 뿐이고, 결국 팀의 운영 규율이 품질을 결정합니다.
마지막으로, “완벽한 인프라”보다 “복구 가능한 인프라”가 현실적으로 더 중요합니다. 장애를 완전히 없앨 수는 없지만, 장애가 났을 때 5분 내 영향 축소, 30분 내 원인 가설 도출, 24시간 내 재발 방지 조치까지 연결되면 서비스 신뢰도는 크게 올라갑니다. 이번 30강의 목표가 바로 그 기반을 만드는 것입니다.
오늘의 결론
한 줄 요약: 운영형 Docker 서비스의 핵심은 컨테이너 실행 자체가 아니라, 재현 가능한 배포·관찰 가능한 운영·빠른 롤백이 가능한 구조를 팀 규칙으로 고정하는 데 있다.
연습문제
- 이번 강의의 FastAPI+Postgres+Redis 구성을 기준으로
compose.dev.yml과compose.prod.yml를 분리하고, 차이점을 표로 정리해보세요. - API 컨테이너에 의도적으로 오류를 넣어 재기동 루프를 만든 뒤,
logs/inspect/events/stats로 원인을 찾고 복구 절차를 10단계 이내로 문서화해보세요. - SHA 태그 기반 배포/롤백 스크립트(
deploy.sh,rollback.sh)를 작성해 스테이징에서 실제로 1회씩 검증해보세요.
이전 강의 정답
29강 연습문제 정답 요약:
- 장애 대응의 첫 단계는 정답 찾기가 아니라 영향 축소입니다. 즉시 롤백 가능한 태그 전략과 실행 명령 세트를 준비해두는 것이 핵심입니다.
docker inspect의ExitCode,OOMKilled,RestartCount는 재기동 루프 진단의 기본 지표이며, 로그와 함께 반드시 확인해야 합니다.- 로그를 보기 전에 컨테이너를 삭제하면 중요한 단서가 사라집니다. 삭제 전 수집(로그/이벤트/설정 스냅샷) 절차를 체크리스트화해야 재발 방지 품질이 올라갑니다.
실습 환경/재현 정보
- OS: macOS Sequoia 또는 Ubuntu 22.04 이상
- Docker 버전: Docker Engine 24+/25+, Docker Compose v2
- 언어/런타임: Python 3.12, FastAPI, Uvicorn
- 실행 순서:
- 프로젝트 폴더/앱 코드 생성
- Dockerfile 작성 및 non-root 실행 구성
- compose로 API/DB/Redis 연결 및 볼륨 설정
docker compose up -d --build로 기동- health/log/stats 검증
- 태그 기반 재배포/롤백 리허설
- 재현 체크:
/health응답이 안정적으로 반환되는가- DB/Redis 컨테이너 재시작 후 API가 자동 복구되는가
- 재배포/롤백 시 서비스 중단 시간이 최소화되는가
- 팀원이 문서만 보고 동일하게 재현 가능한가