Docker 이미지는 디스크 공간을 차지하고 배포 속도를 늦추는 문제를 유발할 수 있습니다. 하지만 최상위 DevOps 팀들은 이미 이미지 크기를 최대 99%까지 줄이는 기술을 활용하고 있습니다. 이번 글에서는 이러한 숨겨진 최적화 기법을 공개합니다.
🚀 과도한 Docker 이미지가 초래하는 숨은 비용
Docker 이미지가 너무 크면 단순한 불편을 넘어 다음과 같은 비용을 발생시킵니다.
- ✅ 시간 낭비: 빌드 및 배포 속도가 느려짐
- ✅ 비용 증가: 저장 공간과 네트워크 대역폭 사용 증가
- ✅ 성능 저하: 애플리케이션 응답 속도 감소
📉 1.2GB → 8MB로 최적화: 실제 사례
이러한 기술의 힘을 보여주기 위해 실제 사례를 살펴보겠습니다. 초기 Docker 이미지 크기가 1.2GB인 기존 Python 기반 머신 러닝 애플리케이션을 가져와서 8MB로 최적화했습니다. 그 방법은 다음과 같습니다.
- 멀티 스테이지 빌드(Multi-Stage Builds)
- 레이어 최적화(Layer Optimizations)
- 최소 베이스 이미지(Scratch, Alpine 등) 활용
- Distroless 이미지 및 고급 최적화 기법
- 보안 모범 사례 적용
기본 Dockerfile: 비효율적인 설정
일반적인 Dockerfile은 다음과 같을 것입니다.
1 | FROM python:3.9 |
이 Dockerfile은 정상적으로 작동하지만, 다음과 같은 이유로 거대한 이미지 크기를 초래합니다.
- 전체 Python 이미지를 사용
- 불필요한 빌드 도구 및 종속성 포함
- 비효율적인 레이어 캐싱
- 불필요한 파일이 포함될 가능성
이제 최적화 기술을 활용하여 이 문제를 해결해 보겠습니다.
1️⃣ 멀티 스테이지 빌드(Multi-Stage Builds)
멀티 스테이지 빌드는 빌드 단계에서 필요한 패키지와 런타임 단계에서 필요한 패키지를 분리하여 최종 Docker 이미지의 크기를 크게 줄일 수 있습니다.
✅ 최소한의 베이스 이미지 사용
- 전체 Python 버전 대신 Slim 또는 Alpine 버전을 선택하여 사용 사례에 맞게 최적화합니다.
기존 단일 스테이지 Dockerfile
1 | # 공식 Python 런타임을 부모 이미지로 사용 |
이 이미지의 최종 크기는 약 1.2GB입니다.
이는 빌드 도구와 개발 라이브러리를 포함하고 있기 때문입니다.
멀티 스테이지 Dockerfile 적용
🎯 단계 1: 빌드 스테이지
- 작업 디렉토리 설정
- 필수 빌드 도구 설치
- Python 의존성 설치
- 애플리케이션 코드 복사
- PyInstaller를 사용하여 독립 실행 파일 생성
1 | # 빌드 스테이지 |
🎯 단계 2: 프로덕션 스테이지
scratch
이미지를 사용하여 완전히 비어있는 환경에서 시작- 빌드 스테이지에서 필요한 파일만 복사
- 컴파일된 실행 파일을 엔트리포인트로 설정
1 | # 프로덕션 스테이지 |
✅ 빌드 결과: 85MB 크기의 Docker 이미지 생성 (90% 이상 감소)
2️⃣ 레이어 최적화(Layer Optimization): 불필요한 용량 줄이기
Dockerfile에서 RUN
, COPY
, ADD
명령어는 각각 새로운 레이어를 생성합니다.
즉, 불필요한 레이어를 최소화하면 이미지 크기를 더욱 줄일 수 있습니다.
🚨 비효율적인 예제
1 | RUN apt-get update |
🔴 각 RUN 명령어가 별도 레이어를 생성하여 이미지 크기가 커집니다.
✅ 최적화된 예제
1 | RUN apt-get update && apt-get install -y python3-pip python3-dev && \ |
✅ 하나의 RUN 명령어에서 여러 작업을 수행하면 불필요한 레이어 생성을 방지할 수 있습니다.
3️⃣ 최소 베이스 이미지 사용 (Scratch, Alpine 등)
가장 강력하면서도 도전적인 방법은 Scratch 이미지를 사용하는 것입니다.
✅ Scratch 이미지란?
- 운영체제가 포함되지 않은 완전한 빈 이미지
- 의존성 없음 (필요한 파일을 직접 추가해야 함)
- 가장 작은 이미지 크기 유지
💡 Scratch 이미지가 유용한 경우:
-
사용자 정의 베이스 이미지 생성: 독자적인 리눅스 배포판을 만든 경우, Ubuntu 같은 베이스 이미지 없이 Scratch 이미지 위에서 실행 가능
-
독립 실행형 애플리케이션 배포: Python 기반 머신러닝 모델 서버를 PyInstaller로 컴파일 후, Scratch 이미지에 추가
📌 예제 Dockerfile (Scratch 이미지 사용)
1 | # syntax=docker/dockerfile:1 |
4️⃣ Distroless 이미지 사용
Distroless 이미지는 Scratch 이미지와 전체 운영체제 이미지의 중간 형태입니다.
Google에서 제공하며, 필수 구성 요소만 포함하여 불필요한 부분을 제거한 최적의 솔루션입니다.
📌 Distroless 이미지의 특징:
- ✅ 전체 운영체제 이미지보다 더 작음
- ✅ 보안성이 강화됨 (불필요한 패키지 제거)
- ✅ SSL 인증서 및 타임존 데이터 포함
1 | FROM gcr.io/distroless/python3-debian10 |
5️⃣ Docker BuildKit 활성화
Docker BuildKit을 활성화하면 더 빠르고 안전한 빌드가 가능합니다.
1 | DOCKER_BUILDKIT=1 docker build -t myapp . |
6️⃣ 불필요한 파일 제거
- ❌ 애플리케이션 데이터를 Docker 이미지 내부에 보관하지 마세요.
- ✅ 외부 스토리지 (예: AWS S3, MySQL) 또는 Docker 볼륨을 활용하세요.
📌 .dockerignore
파일 활용:
1 | # 대용량 데이터셋 제외 |
7️⃣ 이미지 분석 도구 활용
Dive 및 Docker Slim 같은 도구를 활용하여 Docker 이미지 내부를 분석하고 불필요한 파일을 제거하세요.
📌 Dive 사용법:
1 | dive my-docker-image |
📌 Docker Slim 사용법:
1 | docker-slim build my-docker-image |
8️⃣ 보안 강화를 위한 모범 사례
- ✅ 신뢰할 수 있는 공식 베이스 이미지 사용
- ✅ 루트 사용자를 피하고 일반 사용자로 컨테이너 실행
1 | RUN adduser --disabled-password --gecos "" appuser |
- ✅ 네트워크 노출 최소화
1 | docker run -p 127.0.0.1:8080:8080 myimage |
- ✅ 보안 취약점 스캔 (Trivy 활용)
1 | docker scan my-image:tag |
최적화 결과
항목 | 최적화 전 | 최적화 후 |
---|---|---|
이미지 크기 | 1.2GB | 8MB (99.33% 감소) |
배포 시간 | 10초 | 1.5초 (85% 감소) |
클라우드 비용 | 100% | 40% 절감 |
결론
- 멀티 스테이지 빌드와 Distroless 이미지를 활용하여 Docker 이미지를 최적화하세요.
- 최소한의 패키지만 포함한 Slim 또는 Scratch 이미지를 사용하세요.
- 레이어를 최소화하고 불필요한 파일을 제거하여 성능과 보안을 강화하세요.
이제 여러분도 Docker 이미지를 99% 더 작게 만들 수 있습니다! 🚀