들어가며
이번 포스팅에서는 효과적으로 Docker image를 빌드하여 사이즈를 줄이는 방법에 대해서 알아보도록 한다. 필자는 주로 Spring Boot App을 빌드/배포한다. 오늘 다룰 조사내용을 알게 되기 이전에는 이미지의 크기가 마이크로서비스 하나당 약 700M 정도 잡아먹었다. 하지만 지금은 약 300M 정도까지 줄여서 사용하고 있다.
( x 마이크로 서비스 갯수 x 버전별 이미지 이런 식으로 계산해 보면 레이어가 재활용된다고 해도 생각보다 큰 용량을 차지하게 된다.)
Docker Image에 대하여
Docker Image는 원하는 프로세스를 구동하기 위한 모든 파일들을 한번에 묶은 것이다. Docker Image는 순수한 파일들의 집합으로써 메모리 정보나 독자적인 형식으로 저장된 시스템 정보 같은 것들이 없다.
Docker Image 최적화 이유
Docker Image 사이즈가 작을수록 배포 시간을 단축할 수 있고, 디스크 공간을 절약할 수 있다. 또한 큰 이미지를 repository에 저장하고 배포 환경에서 다운로드하는 network latency는 CI/CD 파이프라인의 퍼포먼스를 저하시키는 요인이 된다.
Docker Image 용량 최적화 방법
1. 저용량 base image사용
특정 Linux 배포판을 사용하지 않아도 된다면 alpine linux와 같은 저용량 배포 이미지를 사용한다.
scratch 라는 이미지는 alpine보다 가볍지만 https를 사용할 경우 이에 필요한 ssl 관련 내용이 없기 때문에 이를 복사해서 넣어줘야 하는 불편함이 생긴다.
사례 1 | Go 애플리케이션 이미지 생성시 저용량 base image 사용
# scratch image 사용 전
#Builder
FROM golang:1.16.3-apline3.13
RUN apk update
RUN apk add git
RUN apk add ca-certificates;
WORKDIR /usr/src/app
COPY . .
ENV GO111MODULE=on
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a ldflags="-s -w" -o bin/main cmd/server/main.go
# Executable Image
FROM alpine
COPY --from=builder /usr/src/app/bin/main ./main
EXPOSE 8080
ENTRYPOINT ["./main"]
################################################################################
# scratch image 사용 후
#Builder
FROM golang:1.16.3-apline3.13
RUN apk update
RUN apk add git
RUN apk add ca-certificates;
WORKDIR /usr/src/app
COPY . .
ENV GO111MODULE=on
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a ldflags="-s -w" -o bin/main cmd/server/main.go
# Executable Image
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /usr/src/app/bin/main ./main
EXPOSE 8080
ENTRYPOINT ["./main"]
사례 2 | Python 애플리케이션 이미지 생성 시 alpine linux image 사용
FROM python:3.8-alpine AS base
WORKDIR /app
COPY requirements.txt .
RUN apk add --no-cache gcc musl-dev python3-dev libffi-dev openssl -dev \\
&& pip install --no-cache-dir -r requirements.txt \\
&& apk del gcc musl-dev python3-dev libffi-dev openssl-dev
FROM base AS final
COPY . .
CMD ["python", "app.py"]
2. 불필요한 파일 삭제
이미지 빌드시 불필요한 파일(로그 파일 또는 기타 프로그램 실행에 필요 없는 파일)을 삭제하여 용량을 줄인다.
3. 명령어 최적화를 통한 컴파일 경량화
컴파일시 스택 추적과 같은 디버깅을 위한 정보 기록 기능이 내장되어 있을 수 있으므로, 이러한 것들을 skip 할 수 있는 명령어 옵션을 찾아서 적용한다.
사례 1 | Go 애플리케이션 이미지 빌드 명령어 최적화
# 최적화 전
FROM golang:1.16.3-apline3.13
RUN apk update
RUN apk add git
RUN apk add ca-certificates
WORKDIR /usr/src/app
COPY . .
ENV GO111MODULE=on
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin/main cmd/server/main.go
ENTRYPOINT ["./main"]
################################################################################
# 최적화 후
FROM golang:1.16.3-apline3.13
RUN apk update
RUN apk add git
RUN apk add ca-certificates
WORKDIR /usr/src/app
COPY . .
ENV GO111MODULE=on
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a ldflags="-s -w" -o bin/main cmd/server/main.go
# ldflags="-s -w" 이용하여
ENTRYPOINT ["./main"]
4. 바이너리 압축
UPX를 이용하여 이미지를 실행가능한 바이너리 형태로 압축할 수 있다. UPX는 포터블하고 확장 가능한 고성능의 실행파일 압축 프로그램이다.
UPX는 바로 실행이 가능하고, 압축 효율이 매우 좋으며 압축해제 속도가 아주 빠르다고한다.
(아직 업무에 적용해 보지는 못했어요.. 사용해 보면 따로 포스팅을 올리도록 할게요!)
사례 1 | Go Application 압축
# 바이너리 압축 전
#Builder
FROM golang:1.16.3-apline3.13
RUN apk update
RUN apk add git
RUN apk add ca-certificates;
WORKDIR /usr/src/app
COPY . .
ENV GO111MODULE=on
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a ldflags="-s -w" -o bin/main cmd/server/main.go
# Executable Image
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /usr/src/app/bin/main ./main
EXPOSE 8080
ENTRYPOINT ["./main"]
################################################################################
# 바이너리 압축 후
### Bulder
FROM golang:1.16.3-alpine3.13 as builder
RUN apk update
RUN apk add git
RUN apk add ca-certificates
RUN apk add upx
WORKDIR /usr/src/app
COPY . .
ENV GO111MODULE=on
RUN go mod tidy
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags="-s -w" -o bin/main cmd/server/main.go
RUN upx --best --lzma bin/main
### Executable Image
FROM scratch
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /usr/src/app/bin/main ./main
EXPOSE 8080
ENTRYPOINT ["./main"]
5. [Best] 경량 이미지 + Gradle bootBuildImage 사용 (Spring Boot 한정)
필자가 여러 가지 방법들을 조사하던 중 부장님께 전해 들은 방법으로.. 다른 여타 방식들보다 월등히 이미지의 용량을 줄이는데 도움이 되었다. Pivotal과 Heroku에서 만든 Cloud Native Buildpacks을 이용하여 이미지를 빌드하는 이 방법은 Spring Boot 2.3 이후부터 제공된다.
./gradlew.bat bootBuildImage
마치며
이번 포스팅에서는 Docker image의 용량을 줄일 수 있는 다양한 방법들에 대해 살펴보았다.
사실 방법들이 많을 것이라고는 예상했지만, 제대로 여러 케이스를 살펴보고 적용해 보는 것이 쉽게 되지 않았다. 프로젝트의 규모나 회사에서 빌드해야 하는 이미지들이 많지 않다면, 이러한 조사를 통해 이미지 크기를 줄인다는 것이 그렇게 중요하게 느껴지지 않을 수도 있다.
하지만 앞으로 계속 이러한 형태로 업무를 해야 한다면.. 조금은 신경 써야 할 부분이지 않을까?
혹시 필자처럼 alpine이 아닌 base image를 사용하거나 docker에서 제공하는 기본 build 명령어를 사용하여 업무를 하고 있다면, 필자의 글을 활용해 최적화를 도전해 보면 어떨까 싶다.
(더 좋은 방법이나 활용하고 계신 방법이 있다면 소개 부탁드려요!)
'클라우드 컴퓨팅 & NoSQL > Docker' 카테고리의 다른 글
[도커(Docker)] 도커 컴포즈(Docker compose) 사용법 (0) | 2021.04.17 |
---|---|
도커 컨테이너(Docker container) 빌드하기 (0) | 2021.04.11 |
도커(Docker) docker run <image>의 실행순서 (0) | 2021.04.09 |
도커(Docker)를 사용하는 이유 (0) | 2021.04.08 |
[Docker] Container 를 사용하는 이유 (0) | 2021.03.24 |