Search

Go Docker 경량화

주제
DevOps
날짜
2023/07/03

Go Docker 경량화

기존 Dockerfile의 경우 우분투 이미지를 사용하고 있으며 go build후 실행 파일을 우분투에 올려 실행합니다.
하지만 이렇게 할 경우 우분투의 기능을 사용하지 않음에도 무거운 우분투 이미지를 이용해 빌드하기에 비효율적이라고 생각되었습니다.

Image Options

Golang의 docker 이미지는 대표적으로 아래와 같이 5가지 정도입니다.
Debian 11 (Bullseye)
Alpine 3.16
Distroless
BusyBox
Scratch

이미지 용량 비교

기본적으로 scratch는 timezone과 CA certificates가 포함되어 있지 않기에 정확한 비교가 될 수 없습니다.
BusyBox, Scratch-advancedDistroless는 용량이 3.06MB와 4.17MB 사이에 위치하여 매우 비슷합니다. 특히 컨테이너에 연결하여 다른 도구를 설치하지 않을 경우(예: 디버깅 목적) 안전한 이미지들 입니다.
Debian은 가장 큰 이미지이며 개발 목적과 테스트, 컨테이너 내부에 다른 도구를 배치하려는 경우에 좋을 수 있습니다.
Alpine은 가장 작은 세 개의 이미지와 Debian 사이에 좋은 절충안을 제공합니다.

scratch

여러 이미지들 중에서 가장 경량화가 잘 되어 있는 scratch를 선택했습니다.
scratch - 텅 비어있는 이미지로, 베이스 이미지 또는 super minimal image를 만들기에 유용하다고 합니다.
Docker Hub에서 scratch 이미지의 설명입니다.
An explicitly empty image, especially for building images "FROM scratch".
This image is most useful in the context of building base images (such as debian and busybox) or super minimal images (that contain only a single binary and whatever it requires, such as hello-world).

Golang Build 환경 변수

GO11MODULE=on
빌드 중에 $GOPATH 대신 모듈(go.mod)에 있는 패키지를 사용합니다.
CGO_ENABLED=0
cgo를 사용하지 않습니다. Scratch 이미지에는 C 바이너리조차 없기 때문에, 반드시 cgo를 비활성화 후 빌드해야합니다.
GOOS=linux GOARCH=amd64
OS와 아키텍쳐 설정입니다.
a
모든(all) 의존 패키지를 cgo를 사용하지 않도록 재빌드합니다.

Dockerfile

기존 Dockerfile

FROM ubuntu:latest ADD ./deploy/ / RUN apt-get update \ && apt-get install -y apt-transport-https curl ARG COMMIT_SHA ENV COMMIT_SHA ${COMMIT_SHA} RUN chmod +x /product EXPOSE 8080 ENTRYPOINT ["/product"]
Docker
복사

경량화된 Dockerfile

go.mod 파일과 main.go 파일의 위치에 따라 경로 수정이 필요합니다.
############################ # STEP 1 build executable binary ############################ FROM golang:1.20-alpine AS build # Install git + SSL ca certificates. ## Git is required for fetching the dependencies. ## Ca-certificates is required to call HTTPS endpoints. RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates # Set build environment ARG wavve_env ARG GITLAB_USER ARG GITLAB_PASSWORD # Set Golang Env ENV GO111MODULE=on \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64 \ wavve_env=${wavve_env} \ GITLAB_USER=${GITLAB_USER} \ GITLAB_PASSWORD=${GITLAB_PASSWORD} RUN echo "machine gitlab.wavve.com login ${GITLAB_USER} password ${GITLAB_PASSWORD}" > ~/.netrc && chmod 600 ~/.netrc WORKDIR /go-app COPY . . # Fetch dependencies. ## Using go get. ## RUN go get -d -v ## Using go mod. ## go.mod must be in WORKDIR ## WORKDIR /go-app/{go.mod path} RUN go mod download RUN go mod verify # Build the binary. ## main.go must be in WORKDIR ## WORKDIR /go-app/{main.go path} RUN go build -a -ldflags="-w -s" -o demo . RUN rm -rf ~/.netrc ############################ # STEP 2 build a small image ############################ FROM scratch # Import from builder. COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ WORKDIR /go-app # Copy our static executable COPY --from=build /go-app . # Open Port EXPOSE 8080 # Run the binary. ENTRYPOINT ["./demo"]
Docker
복사

경량화 결과

member - 기존 Dockerfile로 빌드한 결과입니다.
member-light - 경량화한 Dockerfile로 빌드한 결과입니다.
기존 대비 약 83% 경량화 되었습니다.
166.04 MB → 28.17 MB

Docker에서 Private Repo에 있는 Go Modules 설치하기

Private Repo를 가져오기 위해 설정하는 부분입니다. 아래 부분만 수정해주시면 됩니다!
gitlab-ci-token$CI_JOB_TOKEN 는 Gitlab CI에서 삽입해주는 정보입니다.
docker build --build-arg GITLAB_USER=gitlab-ci-token --build-arg GITLAB_PASSWORD=$CI_JOB_TOKEN -t $COMMIT_IMAGE_TAG .
Bash
복사

Makefile

Go 실행과 Docker 실행의 편의성을 위해 작성한 Makefile입니다.
# import config. # You can change the default config with `make cnf="config_special.config" build` cnf ?= docker.config.env include $(cnf) export $(shell sed 's/=.*//' $(cnf)) # HELP # This will output the help for each task # thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html .PHONY: help help: ## This help. @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) .DEFAULT_GOAL := help # DOCKER TASKS # Build the container build-dev: ## Build the container docker build --build-arg wavve_env=dev --build-arg GITLAB_USER=${GITLAB_USER} --build-arg GITLAB_PASSWORD=${GITLAB_PASSWORD} -t $(APP_NAME) . build-prod: ## Build the container docker build --build-arg wavve_env=prod --build-arg GITLAB_USER=${GITLAB_USER} --build-arg GITLAB_PASSWORD=${GITLAB_PASSWORD} -t $(APP_NAME) . build-nc: ## Build the container without caching docker build --no-cache -t $(APP_NAME) . run: ## Run container on port configured in `docker.config.env` docker run -i -t --rm --env-file=./docker.config.env -p=$(PORT):$(PORT) --name="$(APP_NAME)" $(APP_NAME) up-dev: build-dev run ## Run container on port configured in `docker.config.env` (Alias to run) up-prod: build-prod run ## Run container on port configured in `docker.config.env` (Alias to run) stop: ## Stop and remove a running container docker stop $(APP_NAME); docker rm $(APP_NAME) # LOCAL RUN local-dev: wavve_env=dev go run . ### Run go application in local with dev configure local-prod: wavve_env=prod g go run . ### Run go application in local with prod configure # RUN TEST clean-test-cache: go clean -testcache test-run: wavve_env=dev go test -v ./app/service/ test-e2e-run: wavve_env=dev go test -v ./__test__ test: clean-test-cache test-run test-e2e: clean-test-cache test-e2e-run # HELPERS # generate script to login to aws docker repo CMD_REPOLOGIN := "eval $$\( aws ecr" ifdef AWS_CLI_PROFILE CMD_REPOLOGIN += " --profile $(AWS_CLI_PROFILE)" endif ifdef AWS_CLI_REGION CMD_REPOLOGIN += " --region $(AWS_CLI_REGION)" endif CMD_REPOLOGIN += " get-login --no-include-email \)" # login to AWS-ECR repo-login: ## Auto login to AWS-ECR unsing aws-cli @eval $(CMD_REPOLOGIN) version: ## Output the current version @echo $(VERSION)
Makefile
복사

docker.config.env

Makefile에서 사용될 환경 변수들입니다.
CI를 통해 Gitlab 인증정보를 넣을 수 없는 경우 GITLAB_USERGITLAB_PASSWORD를 입력해주세요.
# You have to define the values in {} APP_NAME=member-voc # optional aws-cli options # AWS_CLI_PROFILE={aws-cli-profile} # AWS_CLI_REGION={aws-cli-region} # Gitlab authorization options # GITLAB_USER={GITLAB_USER} # GITLAB_PASSWORD={GITLAB_PASSWORD} # Port to run the container PORT=8080
Makefile
복사
make 명령어를 통해 간단하게 빌드 및 실행을 할 수 있습니다.
make build-dev
make up-dev
… 등등
자세한건 위 Makefile 스크립트 확인해주세요.