Docker를 이용하여 이미지 파일을 만들어보니 약 5.1GB로 생각보다 이미지의 크기가 컸다. 큰 용량으로 인해 빌드 시간이 오래 걸려 배포가 늦어진다는 문제점이 존재했다. 이를 해결하고자 Docker 이미지 최적화를 찾아봤는데 Multi Stage Build, 작은 Base 이미지 등을 활용하여 줄이는 방법이 있었다. 이에 대해 알아보고 프로젝트에 적용해보려고 한다.
위 포스팅을 통해 Docker 문법에 대해서 학습할 수 있다.
1. Docker 이미지 저장 방식
Docker 이미지를 pull 받으면 여러 개의 조각으로 분리되어 다운받아 진다.
$ docker pull nginx:latest
Using default tag: latest
latest: Pulling from library/nginx
c499e6d256d6: Already exists
74cda408e262: Pull complete
ffadbd415ab7: Pull complete
Digest: sha256:282530fcb7cd19f3848c7b611043f82ae4be3781cb00105a1d593d7e6286b596
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
이렇게 분리되어 받아진 데이터를 레이어(Layer)라고 한다.
Docker 이미지가 빌드될 때, Dockerfile에 정의된 명령문을 실행하면서 만들어지며 각각 독립적으로 저장되고 읽기 전용이라 수정할 수 없다.
레이어가 어떻게 저장되는지 알고 싶다면 docker image inspect 명령어를 통해 확인할 수 있다.
위 해시값은 Host 머신 내에 어딘가에 실제 레이어 내용이 저장된 것을 찾을 수 있을 것이다.
위 그림과 같이 Docker Container는 실행이 되면 모든 읽기 전용 레이어들을 쌓고 마지막에 쓰기 전용 레이어를 추가한다.
Container에서 발생하는 결과물들이 쓰기 전용 레이어에 기록되는 것으로 많은 Docker Container를 실행하더라도 읽기 전용 레이어는 변하지 않고 Container마다의 쓰기 전용 레이어에 데이터가 쌓이기 때문에 서로 겹치지 않게 된다.
이러한 레이어들은 캐싱되어 재사용되기 때문에 빌드 시간을 단축할 수 있다는 장점이 있다.
또한, Dockerfile에 정의된 모든 명령어가 레이어가 되는 것은 아니고 RUN, ADD, COPY 만 레이어가 생성되며 메타 데이터를 다루는 CMD, LABEL, ENV, EXPOSE 등은 임시 레이어로 생성되지만 저장되지 않아 Docker 이미지 크기에 영향을 주지 않는다.
2. Docker 이미지 최적화 방안
2.1. 가벼운 Base 이미지 사용
기본 Base 이미지에는 불필요한 패키지들이 포함되어 있다.
name:version: 가장 기본이 되는 이미지로 운영에 필요한 대부분의 패키지들이 포함되어 있다.
name:version-slim: Container를 실행하기 위한 최소한의 패키지만 설치된 이미지이다.
name:version-alpine: Container에서 사용하기 위해 만들어진 Alpine 리눅스를 기반으로 배포된 이미지로 가장 용량이 적다.
Python을 사용할 때는 slim을 사용해야 하는데 그 이유는 Python은 내부 구현체로 C를 사용하고 많은 패키지들이 C를 활용하여 구현되어 있다.
즉, glibc라는 라이브러리를 활용해야 하는데 alpine의 경우 다른 패키지들과 다르게 C 라이브러리가 musl을 사용하여 추가적인 작업으로 인해 시간과 크기가 증가되는 문제점이 존재한다.
2.2. 불필요한 레이어 제거
과거 Docker에서는 이미지 레이어의 개수가 성능에 영향을 줬지만 현재는 아니라고 한다. 하지만, 가독성과 유지 보수를 고려한다면 도움이 될 것이라 생각된다.