기술공부/개발_코드

가상환경도 부족하다. 재현성을 위해 Docker로 띄우는 배포 패턴, 환경관리 모범 사례

넹넹선생님 2025. 11. 20. 10:00
728x90
반응형

0. 목표 정의

  • “내 컴에선 되는데?”를 없애기 위해
    코드 + OS + 라이브러리 + 설정을 전부 Docker 이미지로 고정한다.
  • 로컬 / 테스트 서버 / 운영 서버 어디서든
    docker compose up 한 방에 동일한 환경을 띄우는 것을 목표로 한다.

1. 기본 원칙

  1. 하나의 서비스 = 하나의 이미지
    • FastAPI, 백엔드, 워커, DB 등은 각각 컨테이너로 분리.
  2. 이미지는 불변(Immutable)
    • 컨테이너 안에 직접 pip install 하지 않고,
      항상 Dockerfile 변경 → docker build로 새 이미지 생성.
  3. 컨테이너는 휘발성
    • 컨테이너 안에 중요한 데이터를 저장하지 않는다.
    • 데이터는 반드시 볼륨/외부 DB에 저장.
  4. 환경 차이는 “환경 변수”로 제어
    • 코드/이미지는 같고, 환경별(dev/stage/prod) 설정만 env로 분리.

2. 예시 프로젝트 구조

예를 들어 FastAPI + PostgreSQL 서비스라고 하면:

myapp/
 ├─ app/
 │   ├─ __init__.py
 │   ├─ main.py
 │   └─ config.py
 ├─ requirements.txt
 ├─ Dockerfile
 ├─ docker-compose.yml
 ├─ .dockerignore
 ├─ .env           # 공용 개발 환경설정 (운영용은 별도 관리)
 └─ README.md

3. Dockerfile 설계 예시 (Python / FastAPI 기준)

# 1) 베이스 이미지 선택 (슬림, 재현성 위해 태그 고정)
FROM python:3.11-slim AS base

# 2) 시스템 패키지 설치 (필요할 때만 최소한으로)
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
 && rm -rf /var/lib/apt/lists/*

# 3) 작업 디렉토리
WORKDIR /app

# 4) 의존성 파일만 먼저 복사 → 캐시 활용
COPY requirements.txt .

# 5) 패키지 설치 (가상환경 없이 컨테이너 자체를 가상환경처럼 사용)
RUN pip install --no-cache-dir -r requirements.txt

# 6) 앱 코드 복사
COPY app ./app

# 7) 환경 변수 (예: uvicorn host/port는 나중에 override 가능)
ENV PYTHONUNBUFFERED=1

# 8) 실행 커맨드
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

포인트:

  • 베이스 이미지 태그는 반드시 고정 (python:3.11-slim) → 재현성 강화.
  • requirements.txt 먼저 복사해서 의존성 설치 캐시 최대 활용.
  • 컨테이너 자체가 격리된 환경이라 venv를 추가로 만들지 않아도 됨(단순화).

4. .dockerignore 설정

빌드 시不要 파일이 이미지에 포함되지 않도록:

.git
__pycache__
*.pyc
*.pyo
*.pyd
.env
.venv
tests
notebooks
  • .env는 외부에서 주입하는 게 원칙 (이미지 안에 비번을 넣지 않는다).
  • 로컬 가상환경, 캐시 폴더 등도 제외.

5. docker-compose로 “여러 서비스” 한 번에 띄우기

예: FastAPI + PostgreSQL

# docker-compose.yml
version: "3.9"

services:
  api:
    build: .
    container_name: myapp-api
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - db

  db:
    image: postgres:16
    container_name: myapp-db
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: mydb
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

운영 시에는 .env 대신 KMS/Secret Manager 또는 별도의 .env.prod를 사용하도록 권장.


6. 환경 변수 관리 모범 사례

  1. 코드에서 하드코딩 금지
# app/config.py
import os

class Settings:
    db_url: str = os.getenv("DATABASE_URL", "postgresql://myuser:mypassword@db:5432/mydb")
    debug: bool = os.getenv("DEBUG", "false").lower() == "true"

settings = Settings()
  1. 환경별 .env 분리 (개발용)
# .env (dev)
DEBUG=true
DATABASE_URL=postgresql://myuser:mypassword@db:5432/mydb

운영에서는:

  • .env 파일을 Git에 올리지 않는다.
  • 서버/오케스트레이션(Kubernetes, ECS 등)에서 환경 변수로 직접 주입.

7. 재현성을 위한 버전/이미지 관리

  1. 이미지 태그 규칙 정하기

예:

  • myapp-api:1.0.0
  • myapp-api:1.0.1-hotfix
  • myapp-api:latest는 개발/로컬용으로만 사용.
  1. Git 태그와 Docker 태그를 맞춘다
  • Git 태그 v1.0.0 → Docker 태그 myapp-api:1.0.0
  • 특정 버전에 버그가 생겨도 정확히 어떤 이미지인지 추적 가능.
  1. 빌드 명령 예시
# 로컬 빌드
docker build -t myapp-api:1.0.0 .

# 실행
docker run --rm -p 8000:8000 --env-file .env myapp-api:1.0.0

8. 개발/스테이징/운영 환경 분리 패턴

  1. 공통 compose 파일: docker-compose.yml
  2. 환경별 오버라이드 파일:
    • docker-compose.dev.yml
    • docker-compose.prod.yml

예시:

# docker-compose.dev.yml
services:
  api:
    environment:
      DEBUG: "true"
    volumes:
      - .:/app  # 코드 변경 시 바로 반영 (개발 모드)

운영:

# docker-compose.prod.yml
services:
  api:
    image: myregistry/myapp-api:1.0.0
    restart: always
    environment:
      DEBUG: "false"

실행:

# 개발
docker compose -f docker-compose.yml -f docker-compose.dev.yml up

# 운영
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

9. CI/CD와 연계한 모범 배포 플로우 예시

  1. Git main 브랜치에 머지 → CI가 작동:
    • 테스트 실행 (pytest 등)
    • Docker 이미지 빌드
    • 이미지 태깅 (예: myapp-api:gitsha 또는 myapp-api:1.2.3)
    • 이미지 레지스트리(ECR, GCR, GHCR 등)에 push
  2. 배포 서버/오케스트레이터에서:
    • 새 태그의 이미지 pull
    • docker compose 혹은 Kubernetes manifest 업데이트
    • 무중단 배포(blue-green, rolling 등) 적용

핵심: 운영 서버에서 빌드하지 않고, 검증된 이미지만 배포
→ 개발/테스트에서 돌린 것과 완전히 동일한 이미지가 운영까지 그대로 간다.


10. Docker 환경관리 모범 사례 정리

  • Dockerfile:
    • 베이스 이미지 태그를 고정한다.
    • 필요 최소한의 OS 패키지만 설치한다.
    • 의존성 설치를 위한 레이어(예: requirements.txt)를 위로 올려 캐시를 활용한다.
    • 앱 실행은 CMD 또는 ENTRYPOINT로 명확히 지정한다.
  • 보안/시크릿:
    • 비밀번호/토큰을 Dockerfile에 쓰지 않는다.
    • .env나 오케스트레이터의 Secret 기능을 사용한다.
    • .env는 .gitignore에 반드시 포함한다.
  • 로깅:
    • 컨테이너 내부에서 파일로 로그 쓰기보다는 stdout/stderr로 출력.
    • 로그 수집기는 호스트/플랫폼에서 수집.
  • 상태 관리:
    • 컨테이너는 언제든지 삭제/재시작 가능하다는 전제로 설계한다.
    • DB, 파일 저장소, 캐시는 외부 서비스(볼륨, S3, RDS 등)에 둔다.
  • 문서화:
    • README.md에 “Docker로 실행하는 방법”을 항상 포함:
      • 빌드 방법
      • 환경 변수 목록
      • docker compose로 올리는 명령
728x90
반응형