Giới thiệu

Build docker image là một thành phần rất cần thiết mà chúng ta cần làm khi muốn containerize ứng dụng Golang. Build một docker image lớn nghĩa là chúng ta cần nhiều data hơn để di chuyển giữa image repository, CI/CD platform, và deployment server. Vì vậy việc tạo một docker image nhỏ hơn là việc làm cần thiết để tiết kiệm thời gian. Không quá khó khăn để chúng ta có thể giảm kích thước của các docker image. Đặc biệt với các ứng dụng Go, bời vì chúng đã đi kèm với file binary có nghĩa là chúng không cần bất kỳ environmental server như Nginx, Node, ...

Trong bài viết này, chúng ta sẽ học cách làm sao để có thể giảm kích thước của docker image cho ứng dụng Go bằng Docker

Golang app

Trong ví dụ này chúng ta có sample code Golang như sau:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"

    "github.com/julienschmidt/httprouter"
)

func main() {
    r := httprouter.New()

    r.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
        m := map[string]interface{}{
            "time": time.Now().UnixMilli(),
        }
        w.Header().Add("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(m)
    })

    fmt.Println("Run on port :8000")
    http.ListenAndServe(":8000", r)
}

Chúng ta có thể import package "httprouter" bằng cách sử dụng câu lệnh go mod download trong Dockerfile

Viết Dockerfile

Giả sử đây là Dockerfile ban đầu của chúng ta

FROM golang:1.17.3-alpine3.14

WORKDIR /app

ENV GO111MODULE=on CGO_ENABLED=0

COPY go.mod go.sum /app/

RUN go mod download

COPY . .

RUN go build -o /app/main /app/main.go

CMD [ "/app/main" ]

Típ: Chúng ta nên sử dụng alpine image để giảm kích thước của image khi build và tiết kiệm data. Và tận dụng cache bằng cách đặt các dòng có ít sự thay đổi lên trên đầu để giảm thiểu thời gian build image.

Bây giờ chúng ta sẽ build image và gán tag cho image dựa trên Dockerfile

$ docker build -t example:initial .
$ docker images example
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
example      initial   0d1bfb281019   37 seconds ago   337MB

Kích thước của image ban đầu là 337MB, bây giờ chúng ta sẽ cải thiện nó

Dockerfile với Multistage

# base image
FROM golang:1.17.3-alpine3.14 as base

WORKDIR /builder

ENV GO111MODULE=on CGO_ENABLED=0

COPY go.mod go.sum /builder/

RUN go mod download

COPY . .

RUN go build -o /builder/main /builder/main.go

# runner image
FROM gcr.io/distroless/static:latest

WORKDIR /app

COPY --from=base /builder/main main

EXPOSE 8000

CMD ["/app/main"]

Multistage build giúp chúng ta để loại bỏ các thành phần không cần thiết trong base image và sử dụng image mới để chạy ứng dụng.

FROM golang:1.17.3-alpine3.14 as base

Có thể thấy, chúng ta có thể đặt tên (alias) cho các stage, Do đó nếu chúng ta muốn copy thứ gì từ stage đó, chúng ta có thể cần ghi tên của stage đó ở dòng COPY.

...

# runner image

FROM gcr.io/distroless/static:latest

WORKDIR /app

COPY --from=base /builder/main main

EXPOSE 8000

CMD ["/app/main"]

Bây giờ chúng ta sẽ build lại image, và quan sát kết quả

$ docker build -t example:multistage .
$ docker images example
REPOSITORY   TAG          IMAGE ID       CREATED          SIZE
example      multistage   6c54eb031f69   2 seconds ago    8.63MB
example      initial      0d1bfb281019   20 minutes ago   337MB

Bây giờ kích thước của image giảm xuống còn 8.63MB, rất đáng kể phải không nào

UPX Dockerfile

# base image

FROM golang:1.17.3-alpine3.14 as base

WORKDIR /builder

RUN apk add upx

ENV GO111MODULE=on CGO_ENABLED=0

COPY go.mod go.sum /builder/

RUN go mod download

COPY . .

RUN go build -o /builder/main /builder/main.go

RUN upx -9 /builder/main

# runner image

FROM gcr.io/distroless/static:latest

WORKDIR /app

COPY --from=base /builder/main main

EXPOSE 8000

CMD ["/app/main"]

UPX là một công cụ giúp chúng ta thu nhỏ kích thước của file binary, không chỉ dành riêng cho các ứng dụng Go. Chúng có thể cài đặt UPX ở dòng 4 và chạy lệnh UPX ở dòng 13 để tận dụng builder cache. upx -9 có nghĩa là chúng ta muốn nén tốt hơn. Ngoài ra có thể sử dụng lệnh upx -h để xem các flags liên quan

Build lại image với tag là example:with-upx

$ docker build -t example:with-upx .
$ docker images example
REPOSITORY   TAG          IMAGE ID       CREATED          SIZE
example      with-upx     0831b4ee8d1a   2 seconds ago    5.91MB
example      multistage   6c54eb031f69   12 minutes ago   8.63MB
example      initial      0d1bfb281019   33 minutes ago   337MB

Lần này kết quả là 5.91MB

Tận dụng Go Flags

...

RUN go build \
  -ldflags "-s -w" \
  -o /builder/main /builder/main.go

...

Trong Dockerfile cuối cùng này, chúng ta sẽ sửa lại câu lệnh go build. Thêm flag -ldflags "-s -w" để disable symbol table và DWARF generation để hỗ trợ việc tạo debugging data. Chúng ta có thể xem các tùy chọn khác thông qua lệnh go tool link -h

Build lại image với tag là example:latest

$ docker build -t example:latest .
$ docker images example
REPOSITORY   TAG          IMAGE ID       CREATED          SIZE
example      latest       fd81bd6268bd   1 second ago     4.16MB
example      with-upx     0831b4ee8d1a   9 minutes ago    5.91MB
example      multistage   6c54eb031f69   22 minutes ago   8.63MB
example      initial      0d1bfb281019   42 minutes ago   337MB

Trong trường hợp này, docker image đã giảm đáng kể kích thước, chỉ còn 4.16MB