最近在 macOS 用 Go 語言開發專案,不過遇到一些 Cross-Compiling 無法運作的問題,礙於時間壓力只得想辦法避開 Cross-Compiling 的問題,最直覺的方式就是直接在最終需要部署的作業系統中編譯,所以決定暫時用相同作業系統版本的 Docker container 以進行編譯,再將 Docker image 內最終編譯完成的執行檔匯出即可。
Docker 官方也提供文件指引如何匯出檔案,但是過程可能會遇到讀寫的問題,所以本文重新以比較簡單的方式介紹如何匯出 Docker image 內的檔案。
本文環境
- macOS
- Docker
專案結構
本文的專案結構如下,為了著重介紹匯出的部分,所以專案結構非常簡單,只有 go.mod
, go.sum
, main.go
, Dockerfile
4 個檔案,是一個非常簡單的 Go 專案:
.
├── Dockerfile
├── go.mod
├── go.sum
└── main.go
其中 main.go
的程式碼如下,是一個非常簡單的 fiber HTTP server:
package main
import (
"log"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/", func (c *fiber.Ctx) error {
return c.SendString("Hello, World!")
})
log.Fatal(app.Listen(":3000"))
}
安裝 fiber package 的指令如下:
$ go get github.com/gofiber/fiber/v2
Dockerfile 解說
Dockerfile
的檔案內容如下:
ARG GO_VERSION=1.20
FROM golang:${GO_VERSION} AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
FROM base as build-stage
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
mkdir /app && \
go build -o /app/main main.go
FROM scratch AS binaries
COPY --from=build-stage /app/main /
上述的 Dockerfile
使用 multi-stage builds 技巧,在 base
階段將專案資料夾內的 go.sum
與 go.mod
綁定(bind)到 base
container 內,並執行 go mod download -x
下載所需要的 Go packages 。
p.s. --mount=type=cache,target=/go/pkg/mod/
是將 /go/pkg/mod/
做為快取的意思,讓其他階段的 Docker builds 可以使用 /go/pkg/mod
這個快取,這是由於 Golang Docker image 預設的 GOPATH
是 /go
,所以執行 go mod download -x
時, Go packages 會下載到 /go/pkg/mod/
內,只要將這個路徑 cache 起來,其他階段的 Docker builds 就不用再額外下載一次 Go packages 。
接著,在 build-stage
階段,將當前資料夾綁定到 build-stage
container 內,讓 Docker 容器可以讀取到 main.go
,並額外建立一個資料夾 /app
作為編譯指令 go build -o /app/main main.go
輸出執行檔的地方。
如果不建立 /app
資料夾作為輸出的地方的話,而執行 go build -o main main.go
指令時,就有可能遇到檔案權限的問題(錯誤訊息 read-only file system 如下所示),所以建立 1 個額外的資料夾作為輸出的地方,可以解決這個問題。
> [build-stage 1/1] RUN --mount=type=cache,target=/go/pkg/mod/ --mount=type=bind,target=. go build -o main main.go:
#0 5.944 command-line-arguments: go build command-line-arguments: copying /tmp/go-build4172345745/b001/exe/a.out: open main: read-only file system
而 binaries
階段使用 scratch
image 將編譯好的檔案從 build-stage
的 /app/main
複製到 binaries
的根目錄 /
下。
匯出 Docker image 內的檔案
最後只要執行以下指令,將 binaries
階段的檔案輸出到當前資料夾下,也就是 --target=binaries --output=.
的部分:
$ docker build --target=binaries --output=. .
p.s. scratch
image 是 Docker 設計的 1 個空的 image, 使用 scratch
可以確保我們執行上述指令時,不會輸出其他多餘的檔案,
執行成功的話,就可以看到當前資料夾多了 1 個檔名為 main
的執行檔:
$ tree .
.
├── Dockerfile
├── go.mod
├── go.sum
├── main <--- hola!
└── main.go
這就是用 Docker 編譯並匯出檔案的方法。
重新編譯
如果需要重新編譯,請先執行以下指令,刪除 Docker builder 所有 cache 以確保每次的編譯都是乾淨的:
$ docker builder prune -af
以上, Happy Coding!
References
scratch - Docker Official Image
Export binaries | Docker Documentation