애플리케이션을 Docker 이미지로 만들고, 배포해본 경험 다들 있으신가요? 무거운 이미지를 베이스 이미지로 사용해 한 번 다운받는 데 무척 오랜 시간이 걸리거나, 민감한 데이터를 포함하게 되어 보안상 문제가 생겼던 경험도 다들 있으실 겁니다. 이번 포스팅에서는 더 가볍고, 안전한 이미지를 만들기 위해서는 어떻게 해야 하는지 한 번 알아보겠습니다.
이제까지는…
아래는 평범한 Dockerfile입니다. 우분투 위에서 Go를 설치하고 파일을 복사한 후 빌드하고 실행합니다. 가장 단순한 방법이지만, Ubuntu는 용량이 크기 때문에 매번 내려받는 시간이 매우 오래 걸립니다. 또 아래에서 설명하겠지만 여러 보안 취약점도 보이는군요.
FROM ubuntu
ARG DEBAIN_FRONTEND=noninteractive
# 여러 명령을 &&을 통해 연결합니다.
RUN apt-get update && apt-get install -y golang-go
COPY . .
RUN CGO_ENABLED=0 go build app.go
CMD ["./app"]
이미지 크기 줄이기
도커 이미지가 가벼워질수록 애플리케이션 빌드, 배포 속도는 빨라집니다. 그렇게 되면 더 자주, 많이 배포할 수 있기 때문에 개발자의 생산성이 확연히 증가하게 됩니다.
- 멀티 스테이징 기법을 사용해서 이미지 크기를 줄입니다. 멀티 스테이징이란 스테이지를 여러 개 만들고 각각 따로 빌드한 후, 가장 가벼운 이미지에 결과를 통합하는 방식을 말합니다.
- RUN 명령을 최대한 적게 쓰는 방법이 있습니다. RUN 명령은 개별 이미지를 만들기 때문에 이를 최소한으로 만들기 위해 RUN 명령 한 번에 최대한 많은 스 크립트를 실행합니다.
잘 와닿지는 않지요? 예시와 함께 보겠습니다.
# 첫 스테이지는 go 파일들을 빌드해서 하나의 실행 가능한 파일로 만듭니다.
# 최종적으로 우리는 실행 가능한 파일만 필요하고 나머지는 불필요합니다.
FROM ubuntu AS builder
ARG DEBAIN_FRONTEND=noninteractive
# 여러 명령을 &&을 통해 연결합니다.
RUN apt-get update && apt-get install -y golang-go
COPY . .
RUN CGO_ENABLED=0 go build app.go
# 그렇다면 딱 실행할 수 있는 파일만 가져옵니다.
# alpine은 매우 가벼운 linux 이미지입니다.
FROM alpine
COPY --from=builder /app .
CMD ["./app"]
Go를 빌드하기 위해서는 다양한 의존성과 설치 파일이 필요합니다. 반면 Go를 실행할 때는 실행 파일 하나만 있으면 됩니다. 그렇다면 실행 환경에는 가장 가벼운 alpine 위에 Go 실행 파일만 있으면 애플리케이션을 빠르게 실행할 수 있게 됩니다.
위 예시는 Ubuntu에서 Go를 빌드하고, alpine으로는 실행 파일만 복사해 실행하는 과정을 설명합니다.
.dockerignore로 불필요한 소스 코드 없애기
README.md나 테스트 코드는 실제 애플리케이션을 빌드할 때는 불필요합니다. 또한, API 인증 토큰과 같은 민감한 정보가 포함되어 있는 .env
파일, .pem
프라이빗 키 파일 및 Git 커밋 이력이 포함되어 있는 .git
디렉터리는 도커 이미지에 포함되어서는 안됩니다. 이런 파일들을 도커 이미지에 포함시키지 않기 위해 우리는 .dockerignore
파일을 만듭니다. 아래처럼 제외할 파일을 추가하면 자동으로 빌드할 때 적용됩니다.
.env
.git
.gitignore
*.pem
*.md
test/