Module 2

Docker & 컨테이너화

MLOps 기반 운영 자동화 과정 | 차시 8~12

차시 8

Docker 개념 & 컨테이너 실행

이미지/컨테이너 차이, 기초 명령어

왜 Docker가 필요한가?

Docker 없이

  • "내 컴퓨터에서는 되는데..."
  • Python 버전 충돌
  • 라이브러리 의존성 지옥
  • OS마다 다른 설정
  • 배포할 때마다 수동 설치

Docker 사용

  • 어디서나 동일한 환경 보장
  • 의존성을 이미지에 포함
  • 한 줄 명령어로 실행
  • 개발 = 운영 환경
  • 팀 전체가 같은 환경 사용

Docker 핵심 개념

이미지(Image)
설계도/레시피 (읽기 전용)
Python 3.10
FastAPI
모델 파일
소스 코드
붕어빵 틀
컨테이너(Container)
실행 중인 앱 (읽기/쓰기)
Python 3.10
FastAPI
모델 파일
소스 코드
붕어빵
이미지 = 실행에 필요한 모든 것을 담은 패키지 (변하지 않음)
컨테이너 = 이미지를 기반으로 실행된 인스턴스 (실행/중지 가능)

VM vs Container

가상 머신 (VM)
App A
Guest OS
App B
Guest OS
Hypervisor
Host OS
Hardware
OS 전체 가상화 → 무겁고 느림
컨테이너 (Docker)
App A
Libs
App B
Libs
Docker Engine
Host OS
Hardware
커널 공유 → 가볍고 빠름

Docker 실행 흐름


1. Dockerfile 작성     (설계도 작성)
         ↓
2. docker build        (이미지 생성)
         ↓
3. docker run          (컨테이너 실행)
         ↓
4. 컨테이너 안에서 앱 동작
         ↓
5. docker stop         (컨테이너 정지)
          
또는 Docker Hub에서 이미 만들어진 이미지를 docker pull로 받아올 수도 있습니다

Docker Hub & 이미지 가져오기


# Docker Hub에서 공식 Python 이미지 가져오기
docker pull python:3.10-slim

# 가져온 이미지 확인
docker images

# 출력 예시:
# REPOSITORY   TAG          IMAGE ID       SIZE
# python       3.10-slim    a2c7e8f3d4b1   150MB
          
Docker Hub = 이미지 저장소 (GitHub의 이미지 버전)
태그(Tag) = 이미지의 버전 (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
          
-it: interactive + tty (터미널 접속)
exit 또는 Ctrl+D로 컨테이너 종료

기초 명령어 정리

명령어설명예시
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

컨테이너 생명주기

Created
Running
Paused
Stopped
Removed
docker create  →  docker start  →  docker pause  →  docker stop  →  docker rm

# 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
          
-d: 백그라운드(detached) 실행
--name: 컨테이너 이름 지정 (없으면 랜덤 이름)
-p 8080:80: 호스트 8080번 포트를 컨테이너 80번 포트에 연결

실습: Docker 기초 명령어

따라해보세요:

1. docker pull python:3.10-slim 실행
2. docker images로 이미지 확인
3. docker run -it python:3.10-slim bash로 접속
4. 컨테이너 안에서 pip install fastapi 실행
5. exit로 나온 후 다시 접속 — fastapi가 있나요?
6. docker ps -a로 정지된 컨테이너 확인
7. docker rm으로 정리
핵심 깨달음: 컨테이너 안에서 설치한 패키지는 컨테이너 삭제 시 사라집니다!
→ 그래서 Dockerfile에 설치 명령을 적어야 합니다

차시 9

requirements 관리 & Dockerfile 작성

서비스 A 기준 컨테이너 이미지 만들기

requirements.txt 왜 중요한가?

나쁜 예


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
              

버전 고정 → 언제나 동일한 환경
→ 재현 가능한 빌드

requirements.txt 만들기


# 방법 1: 현재 환경의 모든 패키지 (비추천 — 불필요한 것까지 포함)
pip freeze > requirements.txt   # conda 환경 활성화 상태에서 실행

# 방법 2: 필요한 것만 직접 작성 (추천)
# requirements.txt 파일을 직접 편집

# 방법 3: 현재 설치된 버전 확인 후 직접 작성
pip show fastapi     # 버전 확인
pip show uvicorn
pip show scikit-learn
          
pip freeze 주의사항:
- conda 환경 안에서 실행해야 깔끔합니다
- 글로벌 환경에서 하면 수십 개의 불필요한 패키지가 포함됩니다

서비스 A: requirements.txt


# 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이란?

  • Docker 이미지를 만들기 위한 설계도 (텍스트 파일)
  • 각 줄이 하나의 레이어(Layer)를 생성
  • 위에서 아래로 순차 실행
  • 파일명은 정확히 Dockerfile (확장자 없음)
Dockerfile
docker build
Docker Image
Container
(설계도)             (빌드 명령)            (이미지)              (실행)

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

서비스 A: Dockerfile 작성

FROM
python:3.10-slim
WORKDIR
/app
COPY
requirements.txt
RUN
pip install
COPY
. .
CMD
uvicorn

# 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"]
          

왜 requirements.txt를 먼저 복사하나?

비효율적 (캐시 X)


COPY . .
RUN pip install -r requirements.txt
              

코드 1줄만 바꿔도
pip install 전체 재실행 (수 분)

효율적 (캐시 O)


COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
              

requirements.txt 안 바뀌면
pip install 단계 캐시 사용 (수 초)

소스코드만 변경 시
FROM python:3.10-slim CACHED
WORKDIR /app CACHED
COPY requirements.txt . CACHED
RUN pip install ... CACHED
COPY . .   ← 변경! REBUILD
CMD ["uvicorn", ...] REBUILD
캐시 재사용
(빠름)
재빌드 필요
(느림)
변경된 레이어
아래의 모든 레이어가
재빌드됩니다

CMD vs ENTRYPOINT

CMD


CMD ["uvicorn", "app.main:app",
     "--host", "0.0.0.0",
     "--port", "8000"]
              
  • 기본 실행 명령
  • docker run 시 덮어쓸 수 있음
  • ML 서비스에서 주로 사용

ENTRYPOINT


ENTRYPOINT ["uvicorn"]
CMD ["app.main:app",
     "--host", "0.0.0.0"]
              
  • 항상 실행되는 고정 명령
  • CMD는 인자로 추가됨
  • CLI 도구에 적합
ML 서비스에서는 CMD를 추천합니다 — 디버깅 시 docker run ... bash로 쉽게 진입 가능

베이스 이미지 선택 가이드

이미지크기특징용도
python:3.10~900MB풀 버전, 빌드 도구 포함빌드 복잡한 패키지
python:3.10-slim~150MB경량 Debian 기반대부분의 ML 서비스 (추천)
python:3.10-alpine~50MB초경량 Alpine 기반pip 호환성 문제 가능
Alpine 주의: scikit-learn, pandas 등 C 확장 모듈이 있는 패키지는
Alpine에서 빌드 실패하거나 매우 오래 걸릴 수 있습니다. slim을 사용하세요!

실습: Dockerfile 작성해보기

서비스 A의 Dockerfile을 직접 작성해보세요:

1. service-a-loan/ 디렉토리에 Dockerfile 파일 생성
2. python:3.10-slim 베이스 이미지 사용
3. WORKDIR /app 설정
4. requirements.txt 먼저 복사 후 pip install
5. 전체 소스 코드 복사
6. uvicorn으로 app.main:app 실행하는 CMD 작성
7. 포트는 8000번 사용

차시 10

로컬 Docker 실행 테스트

서비스 A: build, run, 포트 매핑, .dockerignore

docker build: 이미지 만들기


# 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
          
-t loan-api:v1: 이미지 이름(loan-api)과 태그(v1) 지정
. (마지막 점): 빌드 컨텍스트 = 현재 디렉토리

docker run: 컨테이너 실행


# 기본 실행
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 없이 실행하면?
컨테이너는 실행되지만, 호스트에서 접속할 수 없습니다!
포트 매핑 -p는 컨테이너의 포트를 호스트로 연결하는 것입니다.

포트 매핑 이해하기

호스트 (내 PC)
:8000
:9000
-p 8000:8000
-p 9000:8000
컨테이너
:8000 (uvicorn)
:8000 (uvicorn)
docker run -p [호스트포트]:[컨테이너포트] 이미지명
-p 9000:8000 = 내 PC의 9000번 포트로 접속하면 컨테이너의 8000번 포트로 연결
http://localhost:9000으로 API 호출 가능

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
          

.dockerignore 파일


# service-a-loan/.dockerignore

# 가상환경 (절대 이미지에 넣지 않기!)
venv/
.venv/
__pycache__/

# Git
.git/
.gitignore

# IDE
.vscode/
.idea/

# 데이터 (학습 데이터는 이미지에 불필요할 수 있음)
data/

# 테스트
tests/

# Docker 관련
Dockerfile
docker-compose.yml
.dockerignore

# 기타
*.pyc
*.pyo
.env
          

.dockerignore 왜 필요한가?

.dockerignore 없이

  • venv/ 포함 → 이미지 +500MB
  • .git/ 포함 → 불필요한 히스토리
  • __pycache__/ 포함 → OS 호환성 문제
  • .env 포함 → 보안 위험!
  • 빌드 시간 증가

.dockerignore 사용

  • 필요한 파일만 이미지에 포함
  • 이미지 크기 최소화
  • 빌드 속도 향상
  • 보안 강화
  • 깔끔한 프로덕션 이미지
필수: .env 파일은 반드시 .dockerignore에 추가하세요!
API 키, 비밀번호 등이 이미지에 포함되면 큰 보안 사고로 이어집니다.

빌드 → 실행 전체 흐름


# 전체 흐름 한눈에

# 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
          

실습: 서비스 A Docker 실행

서비스 A를 Docker로 빌드하고 실행해보세요:

1. .dockerignore 파일 작성
2. docker build -t loan-api:v1 . 실행
3. docker images로 이미지 크기 확인
4. docker run -d --name loan-service -p 8000:8000 loan-api:v1
5. http://localhost:8000/docs 접속하여 API 테스트
6. docker logs loan-service로 로그 확인
7. docker exec -it loan-service bash로 컨테이너 내부 확인
8. 정리: docker stop loan-service && docker rm loan-service

자주 쓰는 docker run 옵션

옵션설명예시
-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

차시 11

서비스 B Docker화 실습

자율실습: 고객 리뷰 분석 API 컨테이너화

서비스 B Docker화 목표

서비스 A에서 배운 Docker 패턴을 서비스 B에 적용합니다.
핵심 차이점: 서비스 B는 Gemini API 키를 환경변수로 주입해야 합니다.

서비스 A (대출 심사)

  • 모델 파일(.pkl)을 이미지에 포함
  • 외부 API 호출 없음
  • 환경변수 불필요

서비스 B (리뷰 분석)

  • 모델 파일 없음 (Gemini API 사용)
  • 외부 API 호출 필요
  • API 키를 환경변수로 주입

서비스 B: requirements.txt


# service-b-review/requirements.txt

# 웹 프레임워크
fastapi==0.115.0
uvicorn==0.30.0

# Gemini API
google-genai>=1.0.0

# 유틸리티
pydantic==2.9.0
          
서비스 A와의 차이:
- scikit-learn, pandas, joblib, numpy가 없음 (모델 학습/추론 불필요)
- 대신 google-genai가 추가됨
- 이미지 크기가 훨씬 작아집니다!

서비스 B: Dockerfile 작성


# 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"]
          
주의: Dockerfile에 API 키를 절대 넣지 마세요!
ENV GEMINI_API_KEY=abc123 ← 이렇게 하면 안 됩니다!

환경변수로 API 키 주입 (-e 옵션)


# 방법 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로 주입 → 안전

서비스 B 빌드 & 실행


# 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
          

자율실습 과제

서비스 B를 Docker로 완성하세요:

1. service-b-review/requirements.txt 작성 (버전 고정)
2. service-b-review/Dockerfile 작성
3. service-b-review/.dockerignore 작성
4. docker build -t review-api:v1 .로 빌드
5. -e GEMINI_API_KEY=...와 함께 docker run
6. Swagger UI에서 /analyze 테스트
7. 서비스 A와 동시 실행하여 두 서비스가 함께 돌아가는지 확인

체크리스트

항목확인
requirements.txt 버전 고정 완료
Dockerfile 작성 완료
.dockerignore 작성 완료
docker build 성공
docker run + 환경변수 주입 성공
/health 응답 확인
/analyze 정상 동작 확인
서비스 A와 동시 실행 확인

차시 12

컨테이너 트러블슈팅 실습

포트, 모듈, 의존성 오류 해결하기

트러블슈팅이 중요한 이유

Docker를 쓰면서 마주치는 에러의 90%는 동일한 패턴입니다.
이번 차시에서 자주 발생하는 오류를 미리 경험하고 해결법을 익힙니다.

  • 포트 관련 오류 — 연결 거부, 포트 충돌
  • 모듈 import 오류 — ModuleNotFoundError
  • 의존성 오류 — 패키지 버전 충돌, 빌드 실패
  • 파일 경로 오류 — FileNotFoundError

오류 1: 포트 연결 거부


# 상황: 컨테이너는 실행되었는데 접속이 안 됨
$ 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.1docker logs로 확인--host 0.0.0.0 필수
포트 번호 불일치CMD와 -p 옵션 비교포트 번호 일치시키기

오류 1: 해결 과정 시연


# 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
          

오류 2: 포트 충돌


# 상황: 이미 사용 중인 포트에 다시 바인딩하려 할 때
$ 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
          

오류 3: ModuleNotFoundError


# 상황: 컨테이너 실행 시 바로 종료됨
$ 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'
          
원인: requirements.txt에 joblib이 빠져있음!
해결: requirements.txt에 추가 후 다시 빌드

# 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
          

오류 4: FileNotFoundError (모델 파일)


# 상황: 모델 로딩 실패
$ 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/
# 파일이 없거나 디렉토리 자체가 없음
              

해결 방법

  • .dockerignore에서 models/ 제거
  • 또는 빌드 전 모델 파일 생성 확인
  • python train.py 먼저 실행!

오류 5: 환경변수 누락 (서비스 B)


# 상황: 서비스 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
          

오류 6: 의존성 빌드 실패


# 상황: 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 [이름]

트러블슈팅 실습: 문제 시나리오

다음 시나리오의 원인을 찾고 해결하세요:

시나리오 1: docker run -d loan-api:v1을 실행했는데 curl http://localhost:8000/health가 안 됩니다.

시나리오 2: docker ps에 컨테이너가 안 보입니다. docker ps -a에는 STATUS가 "Exited (1)"입니다.

시나리오 3: 서비스 A는 8000번, 서비스 B도 8000번에서 실행하려 합니다. 어떻게 해야 할까요?

시나리오 4: 서비스 B가 실행은 되지만 /analyze 호출 시 500 에러가 발생합니다.

시나리오 해답

시나리오 1

원인: -p 옵션 누락
해결: -p 8000:8000 추가

시나리오 2

원인: 앱 시작 시 에러로 종료
해결: docker logs [이름]으로 에러 확인 후 수정

시나리오 3

원인: 호스트 포트 충돌
해결: -p 8000:8000 / -p 8001:8000으로 호스트 포트 분리

시나리오 4

원인: GEMINI_API_KEY 미설정 또는 잘못된 키
해결: -e GEMINI_API_KEY=... 확인, docker logs로 에러 메시지 확인

트러블슈팅 체크리스트

Docker 문제 발생 시 순서대로 확인하세요:
  1. docker ps -a → 컨테이너가 실행 중인가? 종료되었나?
  2. docker logs [이름] → 에러 메시지가 있나?
  3. docker exec -it [이름] bash → 내부 파일/환경 확인
  4. 포트 매핑(-p) 확인 → PORTS 열에 표시되는가?
  5. 환경변수(-e) 확인 → docker exec [이름] env
  6. .dockerignore 확인 → 필요한 파일이 제외되지 않았나?
  7. requirements.txt 확인 → 빠진 패키지가 없나?

Module 2 정리

  • Docker = 앱 + 환경을 하나의 패키지로 묶는 기술
  • 이미지 = 설계도 (읽기 전용), 컨테이너 = 실행 인스턴스
  • Dockerfile로 이미지를 정의: FROM, WORKDIR, COPY, RUN, CMD
  • requirements.txt는 반드시 버전 고정
  • 레이어 캐시: 의존성 파일을 먼저, 소스 코드를 나중에 복사
  • 환경변수로 민감 정보 관리 (-e, --env-file)
  • .dockerignore로 불필요한 파일 제외

다음 Module: 이 Docker 이미지를 GitHub Actions CI/CD로 자동 빌드하고
AWS에 배포합니다!