본문 바로가기

About/Docker

[Docker] Dockerfile - Multi-stage build(멀티스테이지 빌드)

 

애플리케이션 개발 시에 개발 환경에서 사용한 라이브러리나 개발 지원 툴이 제품 환경에서 반드시 사용되는 것은 아닙니다. 제품 환경에는 애플리케이션을 실행하기 위해 최소한으로 필요한 실행 모듈만 배치하는 것이 컴퓨팅 리소스를 효율적으로 활용할 수 있다는 점에서 보안 관점으로 볼 때 바람직합니다.

 

멀티스테이지 빌드란?

컨테이너 이미지를 만들면서 빌드 등에는 필요하지만, 최종 컨테이너 이미지에는 필요 없는 환경을 제거할 수 있도록 단계를 나누어 기반 이미지를 만드는 방법

 

Multi-stage build

멀티스테이지 빌드를 사용하게 되면 위의 그림처럼 컨테이너 실행 시에는 빌드에 사용한 파일 및 디렉토리과 같은 의존 파일들이 모두 삭제된 상태로 컨테이너가 실행되게 됩니다. 결론적으로 좀 더 가벼운 크기의 컨테이너를 사용할 수 있게 됩니다.

 

 

Dockerfile 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. Build Image
FROM golang:1.13 AS builder
 
# Install dependencies
WORKDIR /go/src/github.com/asashiho/dockertext-greet
RUN go get --v github.com/urfave/cli
 
# Build modules
COPY main.go .
RUN GOOS=linux go build --o greet .
 
# ------------------------------
# 2. Production Image
FROM busybox
WORKDIR /opt/greet/bin
 
# Deploy modules
COPY --from=builder /go/src/github.com/asashiho/dockertext-greet/ .
ENTRYPOINT ["./greet"]
cs

위의 Dockerfile은 두 개의 부분으로 구성되어 있습니다.

 

1. 개발 환경용 Docker 이미지

Go의 버전 1.13을 베이스 이미지로 하고 'builder'라는 이름을 붙입니다. 그리고 개발에 필요한 것들을 모두 설치하여 로컬환경의 소스코드를 컨테이너로 복사합니다. 이 후 이 소스코드를 go build를 통하여 'greet'이라는 실행 가능한 바이너리 파일을 생성합니다.

 

2. 제품 환경용 Docker 이미지

제품 환경용 Docker 이미지의 베이스 이미지는 'busybox'를 사용합니다.

 

1
COPY --from=builder /go/src/github.com/asashiho/dockertext-greet/ .
cs

 

이후 개발용 환경의 Docker 이미지로 빌드한 'greet'이라는 이름의 바이너리 파일(실행파일)을 제품 환경의 Docker 이미지로 복사합니다. 이 때 --from 옵션을 사용하여 'builder'라는 이름의 이미지로 부터 복사한다는 것을 선언합니다. 그리고 복사한 실행 파일을 실행하는 명령어를 적습니다.

이렇게 두 개의 Docker 이미지를 생성할 수 있는 Dockerfile을 작성하였습니다.

Dockerfile 빌드

작성한 Docekrfile을 바탕으로 Docker 이미지를 빌드합니다. (Dockerfile이 저장된 디렉토리에서 실행하세요.)

1
# docker build -t greet .
cs

 

빌드 결과

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Sending build context to Docker daemon  3.584kB
Step 1/9 : FROM golang:1.13 AS builder
1.13: Pulling from library/golang
...생략...
 ---> d6f3656320fe
Step 2/9 : WORKDIR /go/src/github.com/asashiho/dockertext-greet
 ---> Running in 2fff632654fd
Removing intermediate container 2fff632654fd
 ---> 1402883766eb
Step 3/9 : RUN go get --v github.com/urfave/cli
 ---> Running in 074623bde944
github.com/urfave/cli (download)
github.com/cpuguy83/go-md2man (download)
Removing intermediate container 074623bde944
 ---> bd25b6a92b46
Step 4/9 : COPY main.go .
 ---> 564233d46ae8
Step 5/9 : RUN GOOS=linux go build --o greet .
 ---> Running in a124ea2555fd
Removing intermediate container a124ea2555fd
 ---> 8fb64c0839d4
Step 6/9 : FROM busybox
...생략...
Step 7/9 : WORKDIR /opt/greet/bin
 ---> Running in a0e4798c47c3
Removing intermediate container a0e4798c47c3
 ---> 277d6a3d35ac
Step 8/9 : COPY --from=builder /go/src/github.com/asashiho/dockertext-greet/ .
 ---> 91bf7d5707a3
Step 9/9 : ENTRYPOINT ["./greet"]
 ---> Running in 026f6dce8aee
Removing intermediate container 026f6dce8aee
 ---> 047a37ee0f00
Successfully built 047a37ee0f00
Successfully tagged greet:latest
cs

 

이미지 확인

1
2
3
4
5
6
7
# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
greet               latest              6a9a7567bffc        51 seconds ago      6.1MB
<none>              <none>              97ca0eb13472        56 seconds ago      837MB
busybox             latest              388056c9a683        3 weeks ago         1.23MB
golang              1.13                d6f3656320fe        8 months ago        803MB
 
cs

 

개발 환경용 이미지인 golang:1.13은 803MB이지만, 제품 환경용인 'greet'은 겨우 6.1MB라는 것을 알 수 있습니다.

제품 환경용 베이스 이미지인 'busybox'의 1.13MB에 어플리케이션 실행에 필요한 모듈만 추가한 정도라는 것을 알 수 있습니다. 제품 환경에서는 부하에 따라 작동하는 컨테이너의 수가 바뀌므로, 가능한 한 용량이 적은 이미지를 사용하면 시스템 전체의 컴퓨팅 리소스를 효율적으로 활용할 수 있습니다.

 

컨테이너 실행

제품 환경용 Docker 이미지인 'greet'을 사용하여 컨테이너를 실행합니다.

1
2
# docker container run -it --rm greet asa
Hello asa
cs

간단한 출력을 하는 커맨드라인 툴이지만 제품환경으로 만든 저용량 이미지로만 작동한다는 것을 확인할 수 있습니다.


Docker를 사용한 애플리케이션 개발에서는 Dockerfile을 사용하여 애플리케이션 구성 정보를 정의합니다. Dockerfile을 사용하여 Multi-stage 빌드를 하는 법을 알아보았습니다.

참고