해당 스터디는 90DaysOfDevOps
https://github.com/MichaelCade/90DaysOfDevOps
를 기반으로 진행한 내용입니다.
Day 19 - Building Efficient and Secure Docker Images with Multi-Stage Builds
기존 방식으로 빌드된 도커 이미지는 불필요한 요소들을 너무 많이 포함하고 있다.
이상적인 이미지는 실제 서비스에 필요한 어플리케이션과 런타임만 포함되는 것이다.
하지만, 최적화되지 않은 일반적인 이미지는, 어플리케이션, 런타임 외에도 컴파일러, 소스 코드, 테스트 코드, 각종 의존성 라이브러리, 빌드 로그 등 실행에 필요 없는 요소들이 포함된다.
이러한 불필요한 요소들은 다음과 같은 문제들을 야기시킨다.
이러한 문제점을 해결하기 위해 제시된 방법이 멀티스테이지 빌드이다.
멀티스테이지 빌드 (Multi-Stage Build)는 하나의 Dockerfile 안에서 이미지 빌드 과정을 여러 단계 (Stage)로 나누어, 최종 이미지를 최적화하는 기법이다.
핵심은 코드를 컴파일하는 빌드 환경과 어플리케이션을 실행하는 런타임 환경을 분리하는 것으로, 컨테이너 이미지를 만들면서, 최종 컨테이너에는 필요 없는 환경 (빌드, 테스트 등) 을 제거한다.
멀티스테이지 빌드를 사용하면 해당 문제들을 해결할 수 있다.
이미지 크기 감소
보안 강화
편리한 관리
동일한 문법 사용
# Base Image
FROM alpine:latest AS base
# First Image
FROM ubuntu:latest AS first
RUN echo "Hello" > /hello
# Second Image
FROM debian:latest AS second
RUN echo "conference" > /conference
# Final Image
FROM base
COPY --from=first /hello /hello
COPY --from=second /conference /conference
CMD cat /hello && cat /conference
1. 베이스 이미지 (base) 스테이지
# Base Image
FROM alpine:latest AS base
2. 첫 번째 이미지 (first) 스테이지
# First Image
FROM ubuntu:latest AS first
RUN echo "Hello" > /hello
3. 두 번째 이미지 (second) 스테이지
# Second Image
FROM debian:latest AS second
RUN echo "conference" > /conference
4. 최종 이미지 (Final Image) 스테이지
# Final Image
FROM base
COPY --from=first /hello /hello
COPY --from=second /conference /conference
CMD cat /hello && cat /conference
해당 스테이지가 멀티스테이지 빌드의 핵심 포인트
FROM base :
빌드를 ubuntu나 debian이 아닌 처음에 정의했던 가벼운 alpine 이미지, 즉 base에서 새로 시작
COPY --from=first /hello /hello :
COPY --from 구문을 사용하여 first 스테이지인 Ubuntu 환경에서 만들었던 /hello 파일만 가져옴. (Ubuntu OS 전체가 아닌 파일 하나만 복사)
COPY --from=second /conference /conference :
COPY --from 구문을 사용하여 second 스테이지인 Debian 환경에서 만들었던 /conference 파일만 가져옴. (Debian OS 전체가 아닌 파일 하나만 복사)
CMD cat /hello && cat /conference :
cat 명령어로 두 파일의 내용을 모두 출력하도록 설정하여, 다른 환겨에서 만들어진 결과물들이 최종 이미지에 성공적으로 합쳐졌음을 보여주는 과정
결론적으로, 해당 Dockerfile은 각기 다른 운영체제 환경에서 필요한 파일을 생성한 뒤, 최종적으로는 아주 가벼운 베이스 이미지 (Alpine)에 해당 결과물들만 선택적으로 합쳐 최적화된 최종 이미지를 만드는 과정을 보여주는 Dockerfile이다.
FROM golang:1.19.5-alpine3.17
EXPOSE 9001
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /app/api .
CMD ["/app/api"]
# 스테이지 1: 빌드 환경
FROM golang:1.19.5-alpine3.17 AS builder
WORKDIR /build
EXPOSE 9001
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /app .
# 스테이지 2: 런타임 환경
FROM alpine:3.17
COPY --from=builder /app /bin/app
CMD ["/bin/app"]
스테이지 1: 빌드 환경
스테이지 2: 최종 이미지 (런타임 환경)
해당 방식을 사용하게 되면, builder 스테이지에 있던 Go 컴파일러, 소스 코드, 의존성 모듈은 모두 버려지게 되고, 최종 이미지는 alpine 베이스와 실행파일만 구성되게 된다.
따라서, 이미지 크기는 일반 Dockerfile보다 훨씬 가벼워지고, 실행에 필요한 최소 요소만 포함되므로 보안성이 강화되게 된다.