컨테이너 기초
“내 컴퓨터에서는 되는데요?”라는 말은 컨테이너가 등장한 이유를 한 문장으로 설명합니다. 컨테이너는 애플리케이션과 그 실행에 필요한 모든 의존성을 하나의 패키지로 묶어, 어떤 환경에서든 동일하게 실행되도록 보장합니다. ML 모델을 학습할 때, 서빙할 때, 파이프라인을 구성할 때 — 컨테이너는 재현성의 기본 단위입니다.학습 목표
- 컨테이너와 VM의 근본적 아키텍처 차이를 이해하고 적절한 상황에서 선택할 수 있다
- Dockerfile을 작성하여 재현 가능한 이미지를 빌드하고 최적화할 수 있다
- docker-compose로 다중 서비스 환경을 정의하고 운영할 수 있다
- GPU 컨테이너 환경을 구성하여 ML 워크로드를 실행할 수 있다
왜 중요한가
AI/ML 엔지니어링에서 가장 빈번하게 발생하는 문제는 환경 불일치입니다. Python 버전, CUDA 버전, 라이브러리 의존성이 조금만 달라져도 모델 학습 결과가 달라지거나 아예 실행이 불가능합니다. 컨테이너는 이 문제를 근본적으로 해결합니다.docker run 한 줄이면 누구나 동일한 환경에서 동일한 결과를 얻을 수 있습니다.
LLMOps 파이프라인에서 컨테이너는 모든 단계의 기반입니다.
데이터 전처리, 모델 학습, 하이퍼파라미터 튜닝, 모델 서빙, A/B 테스트 — 각 단계가 독립적인 컨테이너로 실행되며, Kubernetes가 이를 오케스트레이션합니다.
컨테이너 vs VM 아키텍처 비교
| 비교 항목 | VM | Container |
|---|---|---|
| 격리 수준 | 하드웨어 수준 (강력) | 프로세스 수준 (namespace/cgroup) |
| 시작 시간 | 수십 초 ~ 수 분 | 수 밀리초 ~ 수 초 |
| 이미지 크기 | GB 단위 (Guest OS 포함) | MB 단위 (레이어 공유) |
| 리소스 오버헤드 | 높음 (Guest OS 메모리) | 낮음 (커널 공유) |
| 밀도 | 호스트당 수십 개 | 호스트당 수백 개 |
| 보안 격리 | 강력 (하이퍼바이저) | 상대적 약함 (커널 공유) |
| 사용 사례 | 멀티 OS, 강한 격리 필요 | 마이크로서비스, CI/CD, ML |
컨테이너는 VM을 대체하는 것이 아닙니다. VM 위에서 컨테이너를 실행하는 구조가 클라우드 환경의 표준입니다.
AWS EC2(VM) 위에서 Docker 컨테이너를 실행하는 것이 대표적인 예입니다.
Docker 아키텍처
Docker는 클라이언트-서버 아키텍처로 동작합니다.- Docker CLI: 사용자가 명령어를 입력하는 인터페이스
- Docker Daemon (dockerd): 이미지 빌드, 컨테이너 관리, 네트워크/볼륨 처리
- containerd: 컨테이너 라이프사이클 관리 (OCI 표준)
- runc: 실제 컨테이너 프로세스 생성 (Linux namespace/cgroup)
이미지 레이어 구조
Docker 이미지는 읽기 전용 레이어의 스택입니다. Union File System(OverlayFS)이 이를 하나의 파일시스템으로 합쳐 보여줍니다.Dockerfile 지시어
| 지시어 | 설명 | 예시 |
|---|---|---|
FROM | 베이스 이미지 지정 | FROM python:3.12-slim |
RUN | 빌드 시 명령어 실행 | RUN pip install torch |
COPY | 호스트 파일을 이미지에 복사 | COPY . /app |
ADD | COPY + URL 다운로드 + tar 자동 해제 | ADD data.tar.gz /data |
WORKDIR | 작업 디렉터리 설정 | WORKDIR /app |
EXPOSE | 문서용 포트 선언 (바인딩 아님) | EXPOSE 8000 |
ENV | 환경 변수 설정 (이미지에 영구 기록) | ENV PYTHONUNBUFFERED=1 |
ARG | 빌드 시 변수 (런타임 미노출) | ARG CUDA_VERSION=12.4 |
CMD | 컨테이너 기본 실행 명령 (오버라이드 가능) | CMD ["python", "app.py"] |
ENTRYPOINT | 컨테이너 진입점 (CMD가 인자로 전달) | ENTRYPOINT ["uvicorn"] |
USER | 실행 사용자 변경 | USER appuser |
HEALTHCHECK | 컨테이너 상태 점검 | HEALTHCHECK CMD curl -f http://localhost:8000/health |
LABEL | 이미지 메타데이터 | LABEL version="1.0" |
VOLUME | 마운트 포인트 선언 | VOLUME /data |
SHELL | 기본 셸 변경 | SHELL ["/bin/bash", "-c"] |
Dockerfile 예시: ML 서빙 환경
멀티스테이지 빌드
멀티스테이지 빌드는 빌드에 필요한 도구와 실제 실행에 필요한 파일을 분리하여 최종 이미지 크기를 극적으로 줄입니다.GPU 이미지 멀티스테이지 빌드
이미지 최적화
.dockerignore
최적화 전략
| 전략 | 효과 | 방법 |
|---|---|---|
| slim/alpine 베이스 | 이미지 크기 50-80% 감소 | python:3.12-slim 사용 |
| 레이어 캐시 순서 | 빌드 시간 단축 | 변경 빈도 낮은 레이어를 위에 배치 |
--no-cache-dir | 불필요한 pip 캐시 제거 | pip install --no-cache-dir |
| apt 캐시 제거 | 시스템 패키지 캐시 제거 | rm -rf /var/lib/apt/lists/* |
| 멀티스테이지 | 빌드 도구 제외 | 빌드/런타임 스테이지 분리 |
.dockerignore | 불필요한 파일 제외 | 빌드 컨텍스트 최소화 |
볼륨과 데이터 관리
| 유형 | 명령어 예시 | 특징 | 사용 사례 |
|---|---|---|---|
| Bind Mount | -v /host/path:/container/path | 호스트 디렉터리 직접 마운트 | 개발 중 소스코드, 데이터셋 |
| Named Volume | -v mydata:/data | Docker가 관리하는 볼륨 | 데이터베이스, 모델 체크포인트 |
| tmpfs | --tmpfs /tmp | 메모리 기반 임시 저장소 | 민감 정보, 임시 캐시 |
:ro 플래그는 읽기 전용 마운트를 의미합니다. 학습 데이터는 컨테이너 내부에서 변경될 필요가 없으므로 :ro를 사용하면 실수로 인한 데이터 손상을 방지할 수 있습니다.docker-compose: 다중 서비스 관리
GPU Docker: NVIDIA Container Toolkit
GPU를 컨테이너에서 사용하려면 NVIDIA Container Toolkit이 필요합니다.실무 명령어 레퍼런스
| 명령어 | 설명 | 예시 |
|---|---|---|
docker build | 이미지 빌드 | docker build -t myapp:v1 . |
docker run | 컨테이너 실행 | docker run -d -p 8000:8000 myapp:v1 |
docker exec | 실행 중 컨테이너에 명령 | docker exec -it <id> bash |
docker logs | 로그 확인 | docker logs -f --tail 100 <id> |
docker ps | 실행 중 컨테이너 목록 | docker ps -a |
docker stop | 컨테이너 중지 | docker stop <id> |
docker rm | 컨테이너 삭제 | docker rm -f <id> |
docker images | 이미지 목록 | docker images --format "table {{.Repository}}\t{{.Size}}" |
docker rmi | 이미지 삭제 | docker rmi myapp:v1 |
docker pull | 이미지 다운로드 | docker pull python:3.12-slim |
docker push | 이미지 업로드 | docker push registry/myapp:v1 |
docker tag | 이미지 태그 | docker tag myapp:v1 registry/myapp:v1 |
docker inspect | 상세 정보 확인 | docker inspect <id> |
docker system prune | 미사용 리소스 정리 | docker system prune -af --volumes |
docker stats | 리소스 사용량 모니터링 | docker stats --no-stream |
docker cp | 파일 복사 | docker cp <id>:/app/model.pt ./ |
docker network ls | 네트워크 목록 | docker network ls |
docker volume ls | 볼륨 목록 | docker volume ls |
docker compose up | 스택 시작 | docker compose up -d |
docker compose down | 스택 중지 및 정리 | docker compose down -v |
컨테이너 보안
| 영역 | 실천 사항 | 방법 |
|---|---|---|
| 비root 실행 | 컨테이너 내 root 권한 제거 | USER appuser 지시어 |
| 이미지 스캐닝 | 알려진 취약점 탐지 | trivy image myapp:v1 |
| 시크릿 관리 | 환경 변수로 비밀키 주입 | Docker Secrets, 외부 vault |
| 최소 권한 | 불필요한 capabilities 제거 | --cap-drop ALL --cap-add NET_BIND_SERVICE |
| 읽기 전용 | 파일시스템 변경 방지 | --read-only --tmpfs /tmp |
| 네트워크 격리 | 서비스 간 통신 제한 | 별도 Docker 네트워크 구성 |
AI/ML에서 컨테이너가 중요한 이유
| ML 워크플로 단계 | 컨테이너 활용 |
|---|---|
| 데이터 전처리 | 재현 가능한 ETL 파이프라인 |
| 모델 학습 | GPU 컨테이너로 환경 고정 |
| 실험 추적 | MLflow 컨테이너 + 볼륨 영속성 |
| 모델 서빙 | FastAPI/vLLM 컨테이너 배포 |
| A/B 테스트 | 버전별 컨테이너 동시 운영 |
| CI/CD | 이미지 빌드 → 테스트 → 배포 자동화 |
Docker Desktop vs Docker Engine 차이
Docker Desktop vs Docker Engine 차이
Docker Desktop은 macOS/Windows용 GUI 도구로 내부에 Linux VM을 포함합니다.
Docker Engine은 Linux 네이티브 데몬으로, 서버 환경에서 직접 실행됩니다.
ML 워크로드는 대부분 Linux 서버에서 Docker Engine을 사용합니다.
개발 환경에서는 Docker Desktop이 편리하지만, GPU 패스스루는 Linux Docker Engine에서만 네이티브로 지원됩니다.
이미지 태그 전략: latest를 쓰면 안 되는 이유
이미지 태그 전략: latest를 쓰면 안 되는 이유
latest 태그는 ‘가장 최근’을 의미하지 않습니다. 단지 태그를 지정하지 않았을 때의 기본값일 뿐입니다.
프로덕션에서는 반드시 구체적인 버전 태그(python:3.12.7-slim, nvidia/cuda:12.4.1-runtime-ubuntu22.04)를 사용하세요.
Semantic Versioning이나 Git SHA 기반 태그가 재현성을 보장합니다.컨테이너 안에서 nvidia-smi가 안 보일 때
컨테이너 안에서 nvidia-smi가 안 보일 때
- NVIDIA 드라이버가 호스트에 설치되어 있는지 확인:
nvidia-smi - NVIDIA Container Toolkit이 설치되어 있는지 확인:
nvidia-ctk --version - Docker 데몬을 재시작했는지 확인:
sudo systemctl restart docker --gpus all또는--gpus '"device=0"'플래그를 정확히 사용했는지 확인- docker-compose의 경우
deploy.resources.reservations.devices설정 확인
멀티플랫폼 빌드 (ARM vs x86)
멀티플랫폼 빌드 (ARM vs x86)
Apple Silicon(M1/M2/M3)에서 빌드한 이미지는 x86 서버에서 실행되지 않을 수 있습니다.
docker buildx를 사용하면 멀티플랫폼 이미지를 생성할 수 있습니다:
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:v1 --push .
ML 서빙 환경이 x86 Linux라면, 개발 환경이 ARM이라도 반드시 --platform linux/amd64를 지정하세요.docker-compose v1 vs v2
docker-compose v1 vs v2
docker-compose(하이픈)는 v1으로 Python 기반이며 별도 설치가 필요했습니다.
docker compose(공백)는 v2로 Go 기반이며 Docker CLI 플러그인으로 내장됩니다.
v1은 2023년에 EOL되었으므로, 항상 docker compose(v2)를 사용하세요.
version 필드도 v2에서는 선택사항입니다.컨테이너 로그 관리와 디스크 풀 방지
컨테이너 로그 관리와 디스크 풀 방지
컨테이너 로그가 디스크를 가득 채우는 것은 흔한 운영 문제입니다.
Docker 데몬 설정(이렇게 하면 컨테이너당 최대 30MB(10MB x 3 파일)로 로그가 제한됩니다.
/etc/docker/daemon.json)에 로그 크기 제한을 추가하세요:체크리스트
- Docker가 설치되어 있고
docker run hello-world가 정상 실행되는가 - 컨테이너와 VM의 아키텍처 차이를 설명할 수 있는가
- Dockerfile을 작성하여 이미지를 빌드할 수 있는가
- 멀티스테이지 빌드로 이미지 크기를 최적화할 수 있는가
- bind mount와 named volume의 차이를 이해하고 있는가
- docker-compose로 다중 서비스를 정의하고 실행할 수 있는가
-
--gpus all플래그로 GPU 컨테이너를 실행할 수 있는가 -
.dockerignore로 빌드 컨텍스트를 최적화하고 있는가 - 컨테이너를 비root 사용자로 실행하는 방법을 알고 있는가
-
docker logs,docker exec,docker stats명령어를 활용할 수 있는가

