Docker Image 최적화

·2023년 7월 23일
0
post-thumbnail

이번 포스트에서는 어플리케이션을 운용 및 배포 시 사용하게 되는 Docker 이미지에 대한 최적화 방법에 대해서 설명합니다.

Background

Docker는 어플리케이션을 개발하고 배포하는데 있어서 많은 유용성을 제공한다. 이러한 편리함을 얻는 대신, Docker 이미지의 크기와 관리가 문제가 될 수 있다. 이는 배포 및 관리에 영향을 미치므로, Docker 이미지를 최적화하는 것은 중요하다. 이번 포스트에서는 Docker 이미지를 최적화하는 방법에 대해 살펴본다.

How to optimize Docker image?

1. 더 작은 Base 이미지 사용하기

Docker 이미지를 구축할 때 사용하는 Base 이미지는 이미지 크기에 큰 영향을 미치기때문에, 더 작은 Base 이미지를 사용게되면 이미지 크기를 크게 줄일 수 있다. 예를 들어, Python 애플리케이션을 실행하는데 전체 OS를 포함하는 Ubuntu 대신에 Python 런타임만 포함하는 이미지를 사용하게 되면 이미지 크기를 더 줄일 수 있다.

또, 일반적으로 Python 어플리케이션을 위한 Docker 이미지를 만들 때 python:3.8 같은 베이스 이미지를 많이 사용하지만 생각보다 큰 크기를 가지고 있다(약 1GB). 반면에 python:3.8-alpine(약 50MB)과 같은 이미지를 사용하면 엄청나게 이미지 크기를 줄일 수 있다.

하지만, Alpine은 일부 Python 패키지와 호환성 문제가 발생할 수 있으므로 호환성 관련된 문제를 확인하고 사용해야한다. 그래서 중간 크기 정도의 python:3.8-slim(약 150MB)과 같은 이미지를 사용하면 크기도 어느정도 줄일 수 있고 Python 패키지와의 호환성 문제도 최소화 할 수 있다.

2. 멀티 스테이지 빌드 사용하기

Docker의 멀티 스테이지 빌드를 사용하면, 빌드 단계와 최종 배포 단계를 분리시켜 최종 이미지에는 빌드 단계로부터 배포에서만 필요한 부분만 가져와 사용하여 이미지 크기를 줄일 수 있다. 이렇게하면 필요한 파일과 디렉토리만 최종 이미지에 포함하므로, 불필요한 파일로 인한 이미지 크기 증가를 방지할 수 있다. 아래는 python:3.8 이미지를 사용하는 멀티 스테이지 빌드의 예시이다.

# 빌드 스테이지
FROM python:3.8 AS build

# 패키지 설치
COPY requirements.txt .
RUN pip install -r requirements.txt

# 배포 스테이지
FROM python:3.8-slim AS deployment

# 빌드 스테이지에서 필요한 파일 복사
COPY --from=build /usr/local/lib/python3.8/site-packages/ /usr/local/lib/python3.8/site-packages/

# 배포 스테이지 이미지 명령어
CMD ["input your cmd"]

3. 불필요한 Layer 제거

Docker 이미지는 여러 개의 레이어(layer)로 구성되어 있다. 각각의 레이어는 이미지 빌드 과정에서 Dockerfile의 각 명령어에 의해 생성된다. 이 레이어들은 모두 독립적으로 존재하며, 이들의 합이 최종 Docker 이미지의 크기를 결정한다.

불필요한 레이어를 제거하는 것은 Docker 이미지를 최적화하는 데 있어 중요한 전략 중 하나이다. 이를 위한 방법 중 하나는 RUN, COPY, ADD 등의 명령어를 최소한으로 사용하는 것이다. 예를 들어, 여러 개의 RUN 명령어를 한 줄로 합치는 것은 불필요한 레이어를 줄이는 데 도움이 된다.

아래는 불필요한 레이어가 있는 Dockerfile의 예시이다.

# 불필요한 레이어 생성
FROM debian:buster
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

아래는 최적화한 Dockerfile의 예시이다.

# 레이어 최적화
FROM debian:buster
RUN apt-get update && apt-get install -y curl && apt-get clean

4. dockerignore 파일 사용하기

dockerignore 파일을 사용하여 Docker가 불필요한 파일을 이미지에 추가하는 것을 방지할 수 있다. gitignore 파일과 비슷하게 동작하며, Docker 이미지 생성 시 특정 파일 또는 디렉토리를 제외할 수 있는 기능을 제공한다. 이를 통해 불필요한 파일들을 이미지에서 제외시켜 이미지 크기를 줄일 수 있다.

예를 들어 다음과 같은 프로젝트 구조가 있다고 가정해보자.

/myapp
  |-- .dockerignore
  |-- Dockerfile
  |-- app.py
  |-- /data
      |-- large-dataset.csv
  |-- /tests
  |-- /venv
  |-- .git

이 경우, .git, tests, venv, data 디렉토리가 이미지에 포함된다면 불필요한 저장 공간을 사용하는 것이다. 왜냐하면 해당 파일들은 어플리케이션 빌드에 필요한 파일들이지 배포와 관련된 파일은 아니기 때문이다.

따라서 아래와 같이 해당 파일들을 무시하는 .dockerignore 파일을 만들어 불필요한 파일들을 이미지에 포함시키지 않을 수 있다.

# .dockerignore
.git
tests
venv
data

5. 자주 변하지 않는 내용을 위로 올리기

해당 부분은 Docker 이미지의 크기를 최적화 한다기보다는 빌드시의 최적화를 위한 것이다. Dockerfile에서는 자주 변경되는 내용(예: 소스 코드 복사)을 하단에 배치하고, 자주 변경되지 않는 내용(예: 라이브러리 설치)을 상단에 배치하게되면 Layer Cache를 효과적으로 활용할 수 있다. Docker는 이미지를 빌드 할 때 Layer cache 기능을 지원하는데, 이전과 같은 Layer 빌드가 있다면, 이전에 저장되어 있던 cache를 사용하여 빌드를 빠르게 수행할 수 있도록 도와주는 기능이다.

하지만 Layer Cache는 변화가 감지된 Layer 이후 단계의 Layer는 모두 새로 빌드 하기 때문에 Layer(ADD, COPY 등)의 위치를 적절한 위치에 잘 배치해야한다.

아래는 자주 변경되는 내용의 위치에 따른 Dockerfile 예시이다.

FROM python:3.8
WORKDIR /app

# 자주 변경되는 소스 코드를 상단에 배치
COPY ./src ./src

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

위의 Dockerfile은 이미지에서 사용하는 소스 코드를 복사하는 부분인 "COPY ./src ./src"을 상단에 위치했다. 이렇게 되면 이미지를 빌드 할 때 src 파일의 내용 변동이 있을 시(소스 코드가 변경될 시) 해당 line 부터는 cache를 사용하지 못한다. 즉 requirments를 복사하고 pip install 하는 작업을 소스 코드만 변경되어도 실행된다는 것이다. 소스 코드만 변경하였기 때문에 pip install을 다시 할 필요는 없지만 layer의 위치를 잘못 배치하여 필요 없는 빌드 시간이 생기게 되었다.

# 상대적으로 자주 변경되지 않는 내용을 상단에 배치
FROM python:3.8
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

# 자주 변경되는 소스 코드를 하단에 배치
COPY ./src ./src

위의 Dockerfile은 이미지에서 사용하는 소스 코드를 복사하는 부분인 "COPY ./src ./src"을 하단에 위치했다. 방금 전 케이스에서 설명했던 것과 달리 해당 Dockerfile은 소스 코드만 변경이 될 시 맨 하단에 위치한 "COPY ./src ./src"만 새로 빌드를 진행한다. 이유를 다시 한번 설명하자면, Layer Caching은 변경이 감지된 Layer과 이후 단계의 Layer는 모두 새로 빌드 하기 때문에 위의 예제에서는 소스 코드만 변경이 될 시에는 해당 부분만 적절하게 cache를 사용 한 것이다.

profile
Machine Learning Engineer

0개의 댓글