GPU 지원 Docker 컨테이너로 AI 서비스를 패키징하는 방법을 다루며, NVIDIA Container Toolkit 설정부터 멀티 스테이지 빌드까지 실전 기법을 소개합니다.
일반적인 웹 애플리케이션의 Docker 이미지는 수십~수백 MB 수준입니다. Node.js 애플리케이션은 약 100MB, Go 바이너리는 10MB 이하로도 패키징할 수 있습니다. 그러나 AI 서비스의 컨테이너는 근본적으로 다른 규모와 요구사항을 가집니다.
첫째, CUDA 런타임과 cuDNN 라이브러리가 필요합니다. NVIDIA CUDA 베이스 이미지 자체가 수 GB에 달합니다. 둘째, Python 런타임과 PyTorch, vLLM 등의 ML 프레임워크가 추가로 수 GB를 차지합니다. 셋째, 모델 가중치 파일은 수 GB에서 수백 GB에 이릅니다.
AI 서비스 Docker 이미지 크기 구성:
CUDA 베이스 이미지: 약 3-5 GB
Python + PyTorch: 약 3-5 GB
vLLM/TGI: 약 1-2 GB
모델 가중치 (8B, FP16): 약 16 GB
─────────────────────────────────
총 이미지 크기: 약 23-28 GB
(참고: 일반 웹 앱 이미지는 약 0.1-0.5 GB)이러한 크기 때문에 이미지 빌드, 전송, 저장에 상당한 시간과 비용이 소요됩니다. 효율적인 컨테이너화 전략이 필수적입니다.
GPU를 Docker 컨테이너 내에서 사용하려면 NVIDIA Container Toolkit이 호스트 시스템에 설치되어 있어야 합니다. 이 도구는 NVIDIA GPU 드라이버와 CUDA 라이브러리를 컨테이너에 노출시키는 역할을 합니다.
nvidia-smi+-----------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 NVIDIA A100-SXM4 On | 00000000:00:04.0 Off | 0 |
| N/A 32C P0 52W / 400W | 0MiB / 81920MiB | 0% Default |
+-------------------------------+----------------------+----------------------+# NVIDIA Container Toolkit 리포지토리 추가
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
# 설치
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
# Docker 런타임 설정
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart dockerdocker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi이 명령이 호스트와 동일한 nvidia-smi 출력을 보여주면 설정이 완료된 것입니다.
vLLM 공식 이미지를 베이스로 사용하는 것이 가장 간단한 방법입니다.
FROM vllm/vllm-openai:latest
# 환경 변수 설정
ENV MODEL_NAME="meta-llama/Llama-3.1-8B-Instruct"
ENV MAX_MODEL_LEN=4096
ENV GPU_MEMORY_UTILIZATION=0.90
# 헬스 체크 스크립트
COPY healthcheck.sh /healthcheck.sh
RUN chmod +x /healthcheck.sh
HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=3 \
CMD ["/healthcheck.sh"]
EXPOSE 8000
# 시작 스크립트
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]#!/bin/bash
set -e
exec vllm serve "$MODEL_NAME" \
--host 0.0.0.0 \
--port 8000 \
--max-model-len "$MAX_MODEL_LEN" \
--gpu-memory-utilization "$GPU_MEMORY_UTILIZATION" \
--dtype auto \
--disable-log-requests#!/bin/bash
curl -sf http://localhost:8000/health || exit 1모델 가중치를 Docker 이미지에 직접 포함하면 이미지가 거대해집니다. 대신 모델을 외부 스토리지에서 런타임에 로드하는 것이 일반적인 패턴입니다.
FROM vllm/vllm-openai:latest
# 모델은 볼륨 마운트 또는 S3에서 다운로드
ENV HF_HOME=/models
ENV TRANSFORMERS_CACHE=/models
COPY entrypoint-with-download.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8000
ENTRYPOINT ["/entrypoint.sh"]#!/bin/bash
set -e
# 모델이 로컬에 없으면 Hugging Face Hub에서 다운로드
if [ ! -d "/models/$MODEL_NAME" ]; then
echo "모델 다운로드 중: $MODEL_NAME"
huggingface-cli download "$MODEL_NAME" \
--local-dir "/models/$MODEL_NAME" \
--local-dir-use-symlinks False
fi
exec vllm serve "/models/$MODEL_NAME" \
--host 0.0.0.0 \
--port 8000 \
--max-model-len "$MAX_MODEL_LEN" \
--gpu-memory-utilization "$GPU_MEMORY_UTILIZATION" \
--dtype auto실행 시 모델 디렉토리를 볼륨으로 마운트합니다.
docker run --gpus all \
-v /data/models:/models \
-e MODEL_NAME=meta-llama/Llama-3.1-8B-Instruct \
-e MAX_MODEL_LEN=4096 \
-e GPU_MEMORY_UTILIZATION=0.90 \
-p 8000:8000 \
my-vllm-server:latest프로덕션 환경에서는 모델을 S3, GCS, 또는 공유 파일 시스템(EFS, Filestore)에 저장하고, 컨테이너 시작 시 로컬 NVMe SSD로 복사하는 패턴이 일반적입니다. NVMe에서 모델을 로드하면 네트워크 파일 시스템 대비 로딩 시간이 크게 단축됩니다.
커스텀 코드나 추가 의존성이 필요한 경우, 멀티 스테이지 빌드로 이미지 크기를 최적화합니다.
# 1단계: 의존성 설치
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 2단계: 런타임 이미지
FROM vllm/vllm-openai:latest
# 추가 의존성만 복사
COPY --from=builder /install /usr/local
# 커스텀 미들웨어, 전처리 코드 등
COPY src/ /app/src/
COPY configs/ /app/configs/
WORKDIR /app
EXPOSE 8000
ENTRYPOINT ["python", "-m", "src.server"]TGI는 공식 Docker 이미지를 직접 사용하는 것이 표준 패턴입니다.
docker run --gpus all --shm-size 1g \
-v /data/models:/data \
-p 8080:80 \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id meta-llama/Llama-3.1-8B-Instruct \
--max-input-tokens 4096 \
--max-total-tokens 8192TGI를 래핑하는 커스텀 이미지가 필요한 경우에는 다음과 같이 구성합니다.
FROM ghcr.io/huggingface/text-generation-inference:latest
# 커스텀 설정 파일
COPY tgi-config.json /config/tgi-config.json
# 헬스 체크
HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \
CMD curl -sf http://localhost:80/health || exit 1
# TGI 기본 포트는 80
EXPOSE 80개발 및 테스트를 위해 Docker Compose로 전체 스택을 구성할 수 있습니다.
services:
vllm:
image: vllm/vllm-openai:latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
ports:
- "8000:8000"
volumes:
- model-cache:/root/.cache/huggingface
environment:
- HUGGING_FACE_HUB_TOKEN
command: >
--model meta-llama/Llama-3.1-8B-Instruct
--host 0.0.0.0
--port 8000
--max-model-len 4096
--gpu-memory-utilization 0.90
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
start_period: 120s
retries: 3
proxy:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
vllm:
condition: service_healthy
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
model-cache:
prometheus-data:
grafana-data:events {
worker_connections 1024;
}
http {
upstream vllm_backend {
server vllm:8000;
}
server {
listen 80;
location / {
proxy_pass http://vllm_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# SSE 스트리밍 지원
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
}
location /health {
proxy_pass http://vllm_backend/health;
}
}
}docker compose up -dDocker 이미지의 빌드 속도를 높이려면 레이어 캐싱을 최대한 활용해야 합니다. 변경 빈도가 낮은 레이어(베이스 이미지, 시스템 패키지)를 먼저 배치하고, 변경 빈도가 높은 레이어(애플리케이션 코드)를 나중에 배치합니다.
FROM vllm/vllm-openai:latest
# 1. 시스템 패키지 (거의 변경 없음)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl && \
rm -rf /var/lib/apt/lists/*
# 2. Python 의존성 (가끔 변경)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 3. 설정 파일 (가끔 변경)
COPY configs/ /app/configs/
# 4. 애플리케이션 코드 (자주 변경)
COPY src/ /app/src/대용량 AI 서비스 이미지를 효율적으로 관리하려면 적절한 컨테이너 레지스트리를 선택해야 합니다.
# ECR 리포지토리 생성
aws ecr create-repository --repository-name ai-serving
# 로그인
aws ecr get-login-password --region ap-northeast-2 \
| docker login --username AWS --password-stdin \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com
# 태깅 및 푸시
docker tag my-vllm-server:latest \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/ai-serving:latest
docker push \
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/ai-serving:latestAI 서비스 컨테이너에서는 다음과 같은 보안 사항을 고려해야 합니다.
FROM vllm/vllm-openai:latest
# 비루트 사용자 생성
RUN groupadd -r vllm && useradd -r -g vllm -d /app vllm
# 불필요한 패키지 제거
RUN apt-get purge -y --auto-remove && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --chown=vllm:vllm . .
# 비루트 사용자로 전환
USER vllm
EXPOSE 8000Hugging Face 토큰이나 AWS 자격 증명 등의 시크릿을 Docker 이미지에 직접 포함하면 안 됩니다. 환경 변수나 시크릿 관리 서비스(AWS Secrets Manager, Kubernetes Secrets 등)를 통해 런타임에 주입해야 합니다.
NVIDIA CUDA에는 두 가지 호환성 규칙이 있습니다.
컨테이너의 CUDA 버전은 호스트 GPU 드라이버가 지원하는 CUDA 버전 이하여야 합니다.
호환성 확인:
호스트 드라이버: 550.54 --> CUDA 12.4 이하 지원
컨테이너 CUDA: 12.4 --> 호환
컨테이너 CUDA: 12.6 --> 비호환 (드라이버 업그레이드 필요)멀티 GPU 환경에서 텐서 병렬 처리를 사용하는 경우, GPU 간 통신을 위해 공유 메모리(Shared Memory) 크기를 충분히 설정해야 합니다.
# Docker 실행 시 --shm-size 옵션으로 설정
docker run --gpus all --shm-size 4g my-vllm-server:latest
# 또는 /dev/shm을 tmpfs로 마운트
docker run --gpus all \
--mount type=tmpfs,destination=/dev/shm,tmpfs-size=4294967296 \
my-vllm-server:latest공유 메모리가 부족하면 NCCL(NVIDIA Collective Communications Library) 통신이 실패하거나 성능이 크게 저하될 수 있습니다. 일반적으로 GPU 수에 비례하여 1~2GB씩 할당하는 것을 권장합니다.
AI 서비스의 컨테이너화는 대용량 이미지, GPU 드라이버 호환성, 모델 관리 등 고유한 과제가 있습니다. 모델을 이미지 외부에서 관리하고, 멀티 스테이지 빌드와 레이어 캐싱을 활용하며, 보안 모범 사례를 따르는 것이 핵심입니다.
다음 장에서는 컨테이너화된 AI 서비스를 Kubernetes 클러스터에서 운영하기 위한 기초 개념을 다루겠습니다. AI 워크로드에 특화된 클러스터 설계와 GPU 노드 관리를 중심으로 설명합니다.
이 글이 도움이 되셨나요?
Kubernetes의 핵심 개념을 AI 워크로드 관점에서 설명하고, GPU 노드 구성과 AI 서비스에 적합한 클러스터 아키텍처를 설계합니다.
LLM 추론 성능을 극대화하기 위한 양자화 기법, 배칭 전략, KV 캐시 튜닝 방법을 실전 예제와 함께 체계적으로 다룹니다.
Kubernetes에서 GPU 기반 AI 서비스를 배포하는 실전 과정을 다루며, 프로브 설정, 리소스 관리, 무중단 배포 전략을 구현합니다.