Multi Stage Build in Docker

문지은·2024년 3월 31일
0

Multi Stage Build에 대해서...

도커에서 Multi Stage Build란 말 그대로 도커 이미지를 여러 단계를 거쳐서 build하는 것을 의미한다. 도커에는 이미지를 build할 때 base image가 존재하며 FROM을 이용해서 base image를 지정한다. 그 후에 COPY, RUN 등의 다양한 Dockerfile instructions를 사용해서 이미지를 어떻게 생성할 지 지정한다. (docker instructions 참고)

이 때 base image는 하나의 stage를 나타내며 두 개 이상의 base image를 사용할 경우 Multi Stage Build라고 한다. Multi Stage Build를 하는 이유는 두 가지 장점을 가지기 때문이다.

  • 각 stage 들이 병렬적으로 build 되어 더 빠르고 효율적으로 파이프라인을 구성할 수 있다.
  • image들을 더 작은 size로 빌드할 수 있다.

실습을 해봅시다!

간단한 java 프로그램을 build하고 run하는 docker image를 Dockerfile을 이용해서 만들어보자

  1. print하는 java 파일 작성
public class Main {
    public static void main(String[] args) {
        System.out.println("hello docker multi stage world");
    }
}
  1. 먼저 단일 stage의 Dockerfile을 만들어 보자.
# 베이스 이미지를 가져옵니다. build와 run을 모두 해야하므로 jdk를 사용
FROM openjdk:11

# 작업 디렉토리를 설정
WORKDIR /usr/src/app

# 호스트의 java 파일을 컨테이너의 작업 디렉토리로 복사
COPY ./src/Main.java .

# javac를 이용해서 Main.java 파일을 build
RUN javac ./Main.java

# 빌드된 어플리케이션의 class 파일을 실행
CMD ["java", "Main"]

해당 Dockerfile로 image를 build해보자.

docker build -t single-stage:0.1 .
  1. 이제 multi stage의 Dockerfile로 수정해보자.
# 단일 stage의 Dockerfile과 동일하게 java 프로그램을 build
FROM openjdk:11 AS builder
WORKDIR /usr/src/app
COPY ./src/Main.java .
RUN javac ./Main.java

# 두번째 베이스 이미지를 정의합니다.build는 앞단계에서 진행해서 run만 하면 되므로 jre를 가져온다.
FROM openjdk:11-jre-slim

# 작업 디렉토리를 설정
WORKDIR /usr/src/app

# 빌드 스테이지에서 빌드된 class 파일을 실행 스테이지로 복사
COPY --from=builder /usr/src/app/Main.class .

# 어플리케이션을 실행
CMD ["java", "Main"]

마찬가지로 해당 Dockerfile로 image를 build해보자.

docker build -t multi-stage:0.1 .

정말 효과가 있었을까요?

속도

사실 image를 build할 때 multi-stage:0.1은 single-stage:0.1 이후에 진행했기 때문에 동일한 커맨드의 경우 캐시가 되어 있어서 체감으로 빠르다고 느끼긴 했지만 정확도가 떨어진다.
time docker build --no-cache 명령어를 통해 실제로 얼마나 빠르게 진행되었는지 확인해 보자.

# single-stage build

time docker build --no-cache -t single-stage:0.1 .
Sending build context to Docker daemon  18.43kB
Step 1/5 : FROM openjdk:11
 ---> c8fb8288d1c8
Step 2/5 : WORKDIR /usr/src/app
 ---> Running in 345fcc2eb9f0
Removing intermediate container 345fcc2eb9f0
 ---> afc0fe17c589
Step 3/5 : COPY ./src/Main.java .
 ---> c19de8b75015
Step 4/5 : RUN javac ./Main.java
 ---> Running in b30580aa2777
Removing intermediate container b30580aa2777
 ---> 0edd35bc3625
Step 5/5 : CMD ["java", "Main"]
 ---> Running in db5b3cc19fd8
Removing intermediate container db5b3cc19fd8
 ---> c492fa5772fe
Successfully built c492fa5772fe
Successfully tagged single-stage:0.1
docker build --no-cache -t single-stage:0.1 .  0.04s user 0.03s system 5% cpu 1.162 total

# multi-stage build

time docker build --no-cache -t multi-stage:0.1 .
Sending build context to Docker daemon  18.94kB
Step 1/8 : FROM openjdk:11 AS builder
 ---> c8fb8288d1c8
Step 2/8 : WORKDIR /usr/src/app
 ---> Running in 06ea96721d20
Removing intermediate container 06ea96721d20
 ---> 3b4d7e12de74
Step 3/8 : COPY ./src/Main.java .
 ---> b42490efde73
Step 4/8 : RUN javac ./Main.java
 ---> Running in e2c82a62bfa7
Removing intermediate container e2c82a62bfa7
 ---> 42d1b42a3b47
Step 5/8 : FROM openjdk:11-jre-slim
 ---> a1aef5106238
Step 6/8 : WORKDIR /usr/src/app
 ---> Running in b31cb88a3525
Removing intermediate container b31cb88a3525
 ---> adcf96d18139
Step 7/8 : COPY --from=builder /usr/src/app/Main.class .
 ---> 7f437f3ca604
Step 8/8 : CMD ["java", "Main"]
 ---> Running in bcf95a481770
Removing intermediate container bcf95a481770
 ---> 967fd7f600c7
Successfully built 967fd7f600c7
Successfully tagged multi-stage:0.1
docker build --no-cache -t multi-stage:0.1 .  0.03s user 0.02s system 4% cpu 1.196 total

total 값을 확인했을 때 거의 차이가 안 나긴 하지만 오히려 multi stage가 더 오래걸린 것을 확인할 수 있다. 추측을 해보자면 너무 간단한 프로그램이라 build, run하는 과정이 짧아서 병렬의 효과를 못 본 것 같다. 오히려 multi-stage의 경우 base image가 2개이기 때문에 다운로드에 시간이 오래 걸린 것 같다.

size

그렇다면 build된 image의 size를 확인해보자.

docker image list   
             
REPOSITORY     TAG  IMAGE ID       CREATED          SIZE
multi-stage    0.1  78befda6e55d   10 seconds ago   216MB
single-stage   0.1  50d026550c2a   2 minutes ago    645MB

single-stage는 645MB, multi-stage는 216MB로 간단한 프로그램임에도 약 3배 정도로 꽤나 큰 차이가 나는 걸 확인할 수 있다. multi-stage는 두번째 stage로 가면서 첫번째 stage에서 사용된 데이터들이 포함되지 않았기 때문에 마지막 stage에서 생성된 파일만 포함되게 된다.

정리해보면 size 면에서는 마지막 단계의 파일만 image에 저장되므로 확실히 작아지지만 build 시간의 경우 base image가 너무 많거나 단일 build stage에 소요되는 시간이 짧을 경우 오히려 길어질 수 있다.

참고 자료

profile
백엔드 개발자입니다.

0개의 댓글