MLOps 기반 운영 자동화 과정 | 차시 8~12
1. Dockerfile 작성 (설계도 작성)
↓
2. docker build (이미지 생성)
↓
3. docker run (컨테이너 실행)
↓
4. 컨테이너 안에서 앱 동작
↓
5. docker stop (컨테이너 정지)
# Docker Hub에서 공식 Python 이미지 가져오기
docker pull python:3.10-slim
# 가져온 이미지 확인
docker images
# 출력 예시:
# REPOSITORY TAG IMAGE ID SIZE
# python 3.10-slim a2c7e8f3d4b1 150MB
3.10-slim, latest 등)
# 1. Hello World 컨테이너
docker run hello-world
# 2. Python 컨테이너에서 명령 실행
docker run python:3.10-slim python --version
# 출력: Python 3.10.x
# 3. 컨테이너 안에 들어가보기 (인터랙티브 모드)
docker run -it python:3.10-slim bash
# 컨테이너 내부에서:
python -c "print('Hello from Docker!')"
pip list
exit
| 명령어 | 설명 | 예시 |
|---|---|---|
docker pull | 이미지 다운로드 | docker pull python:3.10-slim |
docker images | 로컬 이미지 목록 | docker images |
docker run | 컨테이너 실행 | docker run -it python:3.10-slim bash |
docker ps | 실행 중인 컨테이너 | docker ps |
docker ps -a | 모든 컨테이너 (정지 포함) | docker ps -a |
docker stop | 컨테이너 정지 | docker stop [컨테이너ID] |
docker rm | 컨테이너 삭제 | docker rm [컨테이너ID] |
docker rmi | 이미지 삭제 | docker rmi python:3.10-slim |
# 1. 백그라운드로 Nginx 컨테이너 실행
docker run -d --name my-nginx -p 8080:80 nginx
# 2. 실행 중인 컨테이너 확인
docker ps
# CONTAINER ID IMAGE ... PORTS NAMES
# a1b2c3d4e5f6 nginx ... 0.0.0.0:8080->80/tcp my-nginx
# 3. 컨테이너 로그 확인
docker logs my-nginx
# 4. 컨테이너 정지
docker stop my-nginx
# 5. 컨테이너 삭제
docker rm my-nginx
docker pull python:3.10-slim 실행docker images로 이미지 확인docker run -it python:3.10-slim bash로 접속pip install fastapi 실행exit로 나온 후 다시 접속 — fastapi가 있나요?docker ps -a로 정지된 컨테이너 확인docker rm으로 정리
fastapi
uvicorn
scikit-learn
pandas
joblib
버전 미지정 → 설치 시점마다 다른 버전
→ "어제는 됐는데 오늘 안 돼요"
fastapi==0.115.0
uvicorn==0.30.0
scikit-learn==1.5.1
pandas==2.2.2
joblib==1.4.2
numpy==1.26.4
버전 고정 → 언제나 동일한 환경
→ 재현 가능한 빌드
# 방법 1: 현재 환경의 모든 패키지 (비추천 — 불필요한 것까지 포함)
pip freeze > requirements.txt # conda 환경 활성화 상태에서 실행
# 방법 2: 필요한 것만 직접 작성 (추천)
# requirements.txt 파일을 직접 편집
# 방법 3: 현재 설치된 버전 확인 후 직접 작성
pip show fastapi # 버전 확인
pip show uvicorn
pip show scikit-learn
# service-a-loan/requirements.txt
# 웹 프레임워크
fastapi==0.115.0
uvicorn==0.30.0
# ML 라이브러리
scikit-learn==1.5.1
pandas==2.2.2
joblib==1.4.2
numpy==1.26.4
# 유틸리티
pydantic==2.9.0
#)으로 카테고리를 구분하면 관리하기 좋습니다
Dockerfile (확장자 없음)| 명령어 | 역할 | 예시 |
|---|---|---|
FROM | 베이스 이미지 지정 | FROM python:3.10-slim |
WORKDIR | 작업 디렉토리 설정 | WORKDIR /app |
COPY | 파일 복사 (호스트→이미지) | COPY . /app |
RUN | 빌드 시 명령 실행 | RUN pip install -r requirements.txt |
CMD | 컨테이너 시작 명령 | CMD ["uvicorn", "app.main:app"] |
ENTRYPOINT | 고정 실행 명령 | ENTRYPOINT ["python"] |
EXPOSE | 포트 문서화 | EXPOSE 8000 |
ENV | 환경변수 설정 | ENV PORT=8000 |
# service-a-loan/Dockerfile
# 1. 베이스 이미지: Python 3.10 경량 버전
FROM python:3.10-slim
# 2. 작업 디렉토리 설정
WORKDIR /app
# 3. 의존성 파일 먼저 복사 (캐시 최적화)
COPY requirements.txt .
# 4. 의존성 설치
RUN pip install --no-cache-dir -r requirements.txt
# 5. 소스 코드 복사
COPY . .
# 6. 포트 문서화
EXPOSE 8000
# 7. 서버 실행 명령
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
COPY . .
RUN pip install -r requirements.txt
코드 1줄만 바꿔도
pip install 전체 재실행 (수 분)
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
requirements.txt 안 바뀌면
pip install 단계 캐시 사용 (수 초)
CMD ["uvicorn", "app.main:app",
"--host", "0.0.0.0",
"--port", "8000"]
docker run 시 덮어쓸 수 있음
ENTRYPOINT ["uvicorn"]
CMD ["app.main:app",
"--host", "0.0.0.0"]
docker run ... bash로 쉽게 진입 가능
| 이미지 | 크기 | 특징 | 용도 |
|---|---|---|---|
python:3.10 | ~900MB | 풀 버전, 빌드 도구 포함 | 빌드 복잡한 패키지 |
python:3.10-slim | ~150MB | 경량 Debian 기반 | 대부분의 ML 서비스 (추천) |
python:3.10-alpine | ~50MB | 초경량 Alpine 기반 | pip 호환성 문제 가능 |
service-a-loan/ 디렉토리에 Dockerfile 파일 생성python:3.10-slim 베이스 이미지 사용WORKDIR /app 설정requirements.txt 먼저 복사 후 pip installapp.main:app 실행하는 CMD 작성
# service-a-loan 디렉토리에서 실행
cd service-a-loan
# 이미지 빌드 (-t: 태그 지정, .: 현재 디렉토리)
docker build -t loan-api:v1 .
# 빌드 과정 출력 예시:
# [1/5] FROM python:3.10-slim
# [2/5] WORKDIR /app
# [3/5] COPY requirements.txt .
# [4/5] RUN pip install --no-cache-dir -r requirements.txt
# [5/5] COPY . .
# => exporting to image
# => naming to docker.io/library/loan-api:v1
# 빌드된 이미지 확인
docker images
# 기본 실행
docker run loan-api:v1
# 포트 매핑과 함께 실행
docker run -p 8000:8000 loan-api:v1
# 백그라운드 실행 + 이름 지정
docker run -d --name loan-service -p 8000:8000 loan-api:v1
# 실행 확인
docker ps
-p는 컨테이너의 포트를 호스트로 연결하는 것입니다.
http://localhost:9000으로 API 호출 가능
# 1. 컨테이너 실행 (백그라운드)
docker run -d --name loan-service -p 8000:8000 loan-api:v1
# 2. 헬스체크
curl http://localhost:8000/health
# {"status": "healthy", "model_loaded": true}
# 3. 예측 요청
curl -X POST http://localhost:8000/predict \
-H "Content-Type: application/json" \
-d '{
"annual_income": 5000,
"employment_years": 3,
"loan_amount": 2000,
"debt_ratio": 35.5,
"credit_grade": 2,
"has_mortgage": 1,
"num_credit_lines": 4
}'
# 4. Swagger UI 확인
# 브라우저에서 http://localhost:8000/docs 접속
# 로그 확인
docker logs loan-service
# 실시간 로그 (tail -f 처럼)
docker logs -f loan-service
# 실행 중인 컨테이너에 접속
docker exec -it loan-service bash
# 컨테이너 안에서 확인
ls -la
cat requirements.txt
python -c "import sklearn; print(sklearn.__version__)"
exit
# 컨테이너 정지 & 삭제
docker stop loan-service
docker rm loan-service
# service-a-loan/.dockerignore
# 가상환경 (절대 이미지에 넣지 않기!)
venv/
.venv/
__pycache__/
# Git
.git/
.gitignore
# IDE
.vscode/
.idea/
# 데이터 (학습 데이터는 이미지에 불필요할 수 있음)
data/
# 테스트
tests/
# Docker 관련
Dockerfile
docker-compose.yml
.dockerignore
# 기타
*.pyc
*.pyo
.env
.env 파일은 반드시 .dockerignore에 추가하세요!
# 전체 흐름 한눈에
# 1. 이미지 빌드
docker build -t loan-api:v1 .
# 2. 이미지 크기 확인
docker images loan-api
# REPOSITORY TAG IMAGE ID SIZE
# loan-api v1 abc123def456 350MB
# 3. 컨테이너 실행
docker run -d --name loan-service -p 8000:8000 loan-api:v1
# 4. 동작 확인
curl http://localhost:8000/health
# 5. 정리
docker stop loan-service && docker rm loan-service
.dockerignore 파일 작성docker build -t loan-api:v1 . 실행docker images로 이미지 크기 확인docker run -d --name loan-service -p 8000:8000 loan-api:v1http://localhost:8000/docs 접속하여 API 테스트docker logs loan-service로 로그 확인docker exec -it loan-service bash로 컨테이너 내부 확인docker stop loan-service && docker rm loan-service
| 옵션 | 설명 | 예시 |
|---|---|---|
-d | 백그라운드 실행 | docker run -d ... |
-p | 포트 매핑 | -p 8000:8000 |
--name | 컨테이너 이름 | --name my-app |
-e | 환경변수 전달 | -e DB_HOST=localhost |
-v | 볼륨 마운트 | -v ./data:/app/data |
--rm | 종료 시 자동 삭제 | docker run --rm ... |
-it | 인터랙티브 터미널 | docker run -it ... bash |
# service-b-review/requirements.txt
# 웹 프레임워크
fastapi==0.115.0
uvicorn==0.30.0
# Gemini API
google-genai>=1.0.0
# 유틸리티
pydantic==2.9.0
google-genai가 추가됨
# service-b-review/Dockerfile
# 베이스 이미지
FROM python:3.10-slim
# 작업 디렉토리
WORKDIR /app
# 의존성 설치 (캐시 최적화)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 코드 복사
COPY . .
# 포트 문서화
EXPOSE 8000
# 서버 실행
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
ENV GEMINI_API_KEY=abc123 ← 이렇게 하면 안 됩니다!
# 방법 1: -e 옵션으로 직접 전달
docker run -d --name review-service \
-p 8001:8000 \
-e GEMINI_API_KEY="your-api-key-here" \
review-api:v1
# 방법 2: --env-file로 파일에서 읽기 (추천)
# .env 파일 내용:
# GEMINI_API_KEY=your-api-key-here
docker run -d --name review-service \
-p 8001:8000 \
--env-file .env \
review-api:v1
# 환경변수 확인
docker exec review-service env | grep GEMINI
# Dockerfile에 API 키 하드코딩
ENV GEMINI_API_KEY=sk-abc123
# 코드에 직접 작성
api_key = "sk-abc123"
이미지에 키가 포함 → 보안 사고
# 코드에서 환경변수 읽기
import os
api_key = os.environ.get(
"GEMINI_API_KEY"
)
if not api_key:
raise ValueError(
"GEMINI_API_KEY 필요"
)
실행 시 -e로 주입 → 안전
# 1. 빌드
cd service-b-review
docker build -t review-api:v1 .
# 2. 실행 (API 키 주입)
docker run -d --name review-service \
-p 8001:8000 \
-e GEMINI_API_KEY="your-api-key-here" \
review-api:v1
# 3. 헬스체크
curl http://localhost:8001/health
# 4. 리뷰 분석 테스트
curl -X POST http://localhost:8001/analyze \
-H "Content-Type: application/json" \
-d '{"review_text": "배송이 정말 빨라서 좋았습니다!"}'
# 5. Swagger UI 확인
# http://localhost:8001/docs
# 서비스 A: 포트 8000
docker run -d --name loan-service \
-p 8000:8000 \
loan-api:v1
# 서비스 B: 포트 8001
docker run -d --name review-service \
-p 8001:8000 \
-e GEMINI_API_KEY="your-key" \
review-api:v1
# 두 서비스 모두 확인
docker ps
# CONTAINER ID IMAGE PORTS NAMES
# abc123... loan-api:v1 0.0.0.0:8000->8000/tcp loan-service
# def456... review-api:v1 0.0.0.0:8001->8000/tcp review-service
# 각각 테스트
curl http://localhost:8000/health # 서비스 A
curl http://localhost:8001/health # 서비스 B
service-b-review/requirements.txt 작성 (버전 고정)service-b-review/Dockerfile 작성service-b-review/.dockerignore 작성docker build -t review-api:v1 .로 빌드-e GEMINI_API_KEY=...와 함께 docker run/analyze 테스트| 항목 | 확인 |
|---|---|
| requirements.txt 버전 고정 완료 | ☐ |
| Dockerfile 작성 완료 | ☐ |
| .dockerignore 작성 완료 | ☐ |
| docker build 성공 | ☐ |
| docker run + 환경변수 주입 성공 | ☐ |
| /health 응답 확인 | ☐ |
| /analyze 정상 동작 확인 | ☐ |
| 서비스 A와 동시 실행 확인 | ☐ |
# 상황: 컨테이너는 실행되었는데 접속이 안 됨
$ curl http://localhost:8000/health
curl: (7) Failed to connect to localhost port 8000: Connection refused
| 원인 | 확인 방법 | 해결 |
|---|---|---|
-p 옵션 누락 | docker ps에서 PORTS 열 확인 | -p 8000:8000 추가 |
| uvicorn host가 127.0.0.1 | docker logs로 확인 | --host 0.0.0.0 필수 |
| 포트 번호 불일치 | CMD와 -p 옵션 비교 | 포트 번호 일치시키기 |
# 1. 포트 매핑 확인
docker ps
# PORTS 열이 비어있으면 -p 옵션이 누락된 것
# 2. uvicorn 바인딩 주소 확인
docker logs loan-service
# INFO: Uvicorn running on http://127.0.0.1:8000
# ↑ 이게 문제! 0.0.0.0이어야 함
# 3. Dockerfile CMD 수정
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# ↑ 반드시 0.0.0.0
# 4. 다시 빌드 & 실행
docker stop loan-service && docker rm loan-service
docker build -t loan-api:v1 .
docker run -d --name loan-service -p 8000:8000 loan-api:v1
# 상황: 이미 사용 중인 포트에 다시 바인딩하려 할 때
$ docker run -d -p 8000:8000 --name loan-2 loan-api:v1
Error: Bind for 0.0.0.0:8000 failed: port is already allocated
# 해결 1: 다른 포트 사용
docker run -d -p 8080:8000 --name loan-2 loan-api:v1
# 해결 2: 기존 컨테이너 정지 후 실행
docker stop loan-service && docker rm loan-service
docker run -d -p 8000:8000 --name loan-service loan-api:v1
# 해결 3: 어떤 프로세스가 포트를 쓰고 있는지 확인
# Linux/Mac
lsof -i :8000
# Windows
netstat -ano | findstr :8000
# 상황: 컨테이너 실행 시 바로 종료됨
$ docker logs loan-service
Traceback (most recent call last):
File "/app/app/main.py", line 3, in <module>
from app.model import LoanModel
File "/app/app/model.py", line 1, in <module>
import joblib
ModuleNotFoundError: No module named 'joblib'
joblib이 빠져있음!
# 1. requirements.txt에 joblib 추가
echo "joblib==1.4.2" >> requirements.txt
# 2. 다시 빌드 & 실행
docker build -t loan-api:v1 .
docker run -d --name loan-service -p 8000:8000 loan-api:v1
# 상황: 모델 로딩 실패
$ docker logs loan-service
모델 파일 없음: models/loan_model.pkl
# 또는
FileNotFoundError: [Errno 2] No such file or directory: 'models/loan_model.pkl'
# 컨테이너 내부 확인
docker exec -it loan-service bash
ls -la models/
# 파일이 없거나 디렉토리 자체가 없음
python train.py 먼저 실행!
# 상황: 서비스 B 실행 시 에러
$ docker logs review-service
ValueError: GEMINI_API_KEY 환경변수를 설정하세요
# 원인: -e 옵션으로 API 키를 전달하지 않음
# 해결: 환경변수 전달
docker stop review-service && docker rm review-service
# 방법 1: -e 옵션
docker run -d --name review-service \
-p 8001:8000 \
-e GEMINI_API_KEY="your-actual-key" \
review-api:v1
# 방법 2: .env 파일
docker run -d --name review-service \
-p 8001:8000 \
--env-file .env \
review-api:v1
# 상황: docker build 중 pip install 실패
Step 4/7 : RUN pip install --no-cache-dir -r requirements.txt
ERROR: Could not find a version that satisfies the requirement
scikit-learn==999.0.0
# 또는 C 라이브러리 빌드 에러
error: command 'gcc' failed: No such file or directory
# 1. 존재하는 버전으로 수정
# pip install scikit-learn== 로 가용 버전 확인
# 2. C 빌드 도구 필요 시 (slim 이미지에서)
FROM python:3.10-slim
RUN apt-get update && apt-get install -y \
gcc \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
| 상황 | 명령어 |
|---|---|
| 컨테이너 상태 확인 | docker ps -a |
| 에러 로그 확인 | docker logs [이름] |
| 실시간 로그 | docker logs -f [이름] |
| 컨테이너 내부 접속 | docker exec -it [이름] bash |
| 파일 구조 확인 | docker exec [이름] ls -la /app |
| 환경변수 확인 | docker exec [이름] env |
| 설치된 패키지 확인 | docker exec [이름] pip list |
| 이미지 레이어 확인 | docker history [이미지명] |
| 컨테이너 상세 정보 | docker inspect [이름] |
docker run -d loan-api:v1을 실행했는데
curl http://localhost:8000/health가 안 됩니다.docker ps에 컨테이너가 안 보입니다.
docker ps -a에는 STATUS가 "Exited (1)"입니다./analyze 호출 시
500 에러가 발생합니다.
원인: -p 옵션 누락
해결: -p 8000:8000 추가
원인: 앱 시작 시 에러로 종료
해결: docker logs [이름]으로 에러 확인 후 수정
원인: 호스트 포트 충돌
해결: -p 8000:8000 / -p 8001:8000으로 호스트 포트 분리
원인: GEMINI_API_KEY 미설정 또는 잘못된 키
해결: -e GEMINI_API_KEY=... 확인, docker logs로 에러 메시지 확인
docker ps -a → 컨테이너가 실행 중인가? 종료되었나?docker logs [이름] → 에러 메시지가 있나?docker exec -it [이름] bash → 내부 파일/환경 확인-p) 확인 → PORTS 열에 표시되는가?-e) 확인 → docker exec [이름] env-e, --env-file)