構建您的 Go 映象
概述
在本節中,您將構建一個容器映象。該映象包含執行應用程式所需的一切——編譯後的應用程式二進位制檔案、執行時、庫以及應用程式所需的所有其他資源。
所需軟體
要完成本教程,您需要以下內容:
- 本地執行的 Docker。請按照說明下載並安裝 Docker。
- 用於編輯檔案的 IDE 或文字編輯器。Visual Studio Code 是一個免費且受歡迎的選擇,但您可以使用任何您熟悉的工具。
- 一個 Git 客戶端。本指南使用基於命令列的
git客戶端,但您可以自由使用任何適合您的工具。 - 一個命令列終端應用程式。本模組中顯示的示例來自 Linux shell,但它們應該在 PowerShell、Windows 命令提示符或 OS X 終端中工作,只需進行少量修改(如果有的話)。
瞭解示例應用程式
這個示例應用程式是一個微服務的漫畫。它故意設計得簡單,以便將重點放在學習 Go 應用程式容器化的基礎知識上。
該應用程式提供兩個 HTTP 端點:
- 它對
/的請求響應一個包含心形符號 (<3) 的字串。 - 它對
/health的請求響應{"Status" : "OK"}JSON。
它對任何其他請求響應 HTTP 錯誤 404。
該應用程式監聽由環境變數 PORT 的值定義的 TCP 埠。預設值為 8080。
該應用程式是無狀態的。
該應用程式的完整原始碼可在 GitHub 上獲取:github.com/docker/docker-gs-ping。我們鼓勵您分叉它並盡情地進行實驗。
要繼續,請將應用程式儲存庫克隆到您的本地機器:
$ git clone https://github.com/docker/docker-gs-ping
如果您熟悉 Go,應用程式的 main.go 檔案非常簡單。
package main
import (
"net/http"
"os"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.GET("/", func(c echo.Context) error {
return c.HTML(http.StatusOK, "Hello, Docker! <3")
})
e.GET("/health", func(c echo.Context) error {
return c.JSON(http.StatusOK, struct{ Status string }{Status: "OK"})
})
httpPort := os.Getenv("PORT")
if httpPort == "" {
httpPort = "8080"
}
e.Logger.Fatal(e.Start(":" + httpPort))
}
// Simple implementation of an integer minimum
// Adapted from: https://gobyexample.com/testing-and-benchmarking
func IntMin(a, b int) int {
if a < b {
return a
}
return b
}為應用程式建立 Dockerfile
要使用 Docker 構建容器映象,需要一個包含構建指令的 Dockerfile。
使用(可選的)解析器指令行開始您的 Dockerfile,該指令行指示 BuildKit 根據指定語法的語法規則解釋您的檔案。
然後您告訴 Docker 您希望為應用程式使用哪個基礎映象。
# syntax=docker/dockerfile:1
FROM golang:1.19Docker 映象可以從其他映象繼承。因此,您可以不從頭開始建立自己的基礎映象,而是使用官方的 Go 映象,該映象已經擁有編譯和執行 Go 應用程式所需的所有工具和庫。
注意如果您對建立自己的基礎映象感到好奇,可以檢視本指南的以下部分:建立基礎映象。但請注意,這對於您手頭的任務並非必需。
現在您已經為即將到來的容器映象定義了基礎映象,您可以開始在其之上進行構建。
為了讓您在執行其餘命令時更輕鬆,請在您正在構建的映象中建立一個目錄。這還指示 Docker 將此目錄用作所有後續命令的預設目標。這樣,您就不必在 Dockerfile 中輸入完整的檔案路徑,相對路徑將基於此目錄。
WORKDIR /app通常,一旦您下載了一個用 Go 編寫的專案,您做的第一件事就是安裝編譯它所需的模組。請注意,基礎映象已經有工具鏈,但您的原始碼尚未在其中。
因此,在您可以在映象中執行 go mod download 之前,您需要將 go.mod 和 go.sum 檔案複製到其中。使用 COPY 命令來完成此操作。
在其最簡單的形式中,COPY 命令接受兩個引數。第一個引數告訴 Docker 您要複製到映象中的檔案。最後一個引數告訴 Docker 您希望將該檔案複製到何處。
將 go.mod 和 go.sum 檔案複製到您的專案目錄 /app 中,由於您使用了 WORKDIR,該目錄是映象內的當前目錄 (./)。與一些現代 shell 似乎對使用尾隨斜槓 (/) 無動於衷,並且可以在大多數情況下弄清楚使用者意圖不同,Docker 的 COPY 命令在解釋尾隨斜槓時非常敏感。
COPY go.mod go.sum ./注意如果您想熟悉
COPY命令對尾隨斜槓的處理,請參閱Dockerfile 參考。這個尾隨斜槓可能會以您無法想象的更多方式導致問題。
現在您已經將模組檔案複製到您正在構建的 Docker 映象中,您也可以使用 RUN 命令在那裡執行 go mod download 命令。這與您在本地機器上執行 go 完全相同,但這次這些 Go 模組將安裝到映象中的一個目錄中。
RUN go mod download至此,您已經安裝了 Go 工具鏈版本 1.19.x 和所有 Go 依賴項到映象中。
接下來您需要做的是將您的原始碼複製到映象中。您將像之前處理模組檔案一樣使用 COPY 命令。
COPY *.go ./此 COPY 命令使用萬用字元將主機上(Dockerfile 所在的目錄)當前目錄中所有帶有 .go 副檔名的檔案複製到映象中的當前目錄。
現在,要編譯您的應用程式,請使用熟悉的 RUN 命令:
RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping這應該很熟悉。該命令的結果將是一個名為 docker-gs-ping 的靜態應用程式二進位制檔案,位於您正在構建的映象的檔案系統根目錄中。您可以將二進位制檔案放置在該映象中您想要的任何其他位置,根目錄在這方面沒有特殊含義。使用它只是為了保持檔案路徑短以提高可讀性。
現在,剩下要做的就是告訴 Docker 在您的映象用於啟動容器時要執行什麼命令。
您可以使用 CMD 命令來完成此操作:
CMD ["/docker-gs-ping"]以下是完整的 Dockerfile:
# syntax=docker/dockerfile:1
FROM golang:1.19
# Set destination for COPY
WORKDIR /app
# Download Go modules
COPY go.mod go.sum ./
RUN go mod download
# Copy the source code. Note the slash at the end, as explained in
# https://dockerdocs.tw/reference/dockerfile/#copy
COPY *.go ./
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping
# Optional:
# To bind to a TCP port, runtime parameters must be supplied to the docker command.
# But we can document in the Dockerfile what ports
# the application is going to listen on by default.
# https://dockerdocs.tw/reference/dockerfile/#expose
EXPOSE 8080
# Run
CMD ["/docker-gs-ping"]Dockerfile 還可以包含註釋。它們總是以 # 符號開頭,並且必須位於一行的開頭。註釋是為了方便您記錄您的 Dockerfile。
還有 Dockerfile 指令的概念,例如您新增的 syntax 指令。指令必須始終位於 Dockerfile 的最頂部,因此在添加註釋時,請確保註釋位於您可能使用的任何指令之後:
# syntax=docker/dockerfile:1
# A sample microservice in Go packaged into a container image.
FROM golang:1.19
# ...構建映象
現在您已經建立了 Dockerfile,可以從中構建映象。docker build 命令從 Dockerfile 和上下文建立 Docker 映象。構建上下文是位於指定路徑或 URL 中的一組檔案。Docker 構建過程可以訪問上下文中包含的任何檔案。
構建命令可選地接受 --tag 標誌。此標誌用於用字串值標記映象,該字串值易於人類閱讀和識別。如果您不傳遞 --tag,Docker 將使用 latest 作為預設值。
構建您的第一個 Docker 映象。
$ docker build --tag docker-gs-ping .
構建過程將在執行構建步驟時列印一些診斷訊息。以下只是這些訊息可能是什麼樣子的示例。
[+] Building 2.2s (15/15) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 701B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> resolve image config for docker.io/docker/dockerfile:1 1.1s
=> CACHED docker-image://docker.io/docker/dockerfile:1@sha256:39b85bbfa7536a5feceb7372a0817649ecb2724562a38360f4d6a7782a409b14 0.0s
=> [internal] load build definition from Dockerfile 0.0s
=> [internal] load .dockerignore 0.0s
=> [internal] load metadata for docker.io/library/golang:1.19 0.7s
=> [1/6] FROM docker.io/library/golang:1.19@sha256:5d947843dde82ba1df5ac1b2ebb70b203d106f0423bf5183df3dc96f6bc5a705 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 6.08kB 0.0s
=> CACHED [2/6] WORKDIR /app 0.0s
=> CACHED [3/6] COPY go.mod go.sum ./ 0.0s
=> CACHED [4/6] RUN go mod download 0.0s
=> CACHED [5/6] COPY *.go ./ 0.0s
=> CACHED [6/6] RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:ede8ff889a0d9bc33f7a8da0673763c887a258eb53837dd52445cdca7b7df7e3 0.0s
=> => naming to docker.io/library/docker-gs-ping 0.0s
您的確切輸出可能會有所不同,但如果沒有錯誤,您應該在輸出的第一行看到 FINISHED 字樣。這意味著 Docker 已成功構建名為 docker-gs-ping 的映象。
檢視本地映象
要檢視本地機器上的映象列表,您有兩種選擇。一種是使用 CLI,另一種是使用Docker Desktop。由於您目前正在終端中工作,因此讓我們看看如何使用 CLI 列出映象。
要列出映象,請執行 docker image ls 命令(或簡寫 docker images):
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping latest 7f153fbcc0a8 2 minutes ago 1.11GB
...
您的確切輸出可能會有所不同,但您應該看到帶有 latest 標籤的 docker-gs-ping 映象。因為您在構建映象時沒有指定自定義標籤,所以 Docker 假定標籤將是 latest,這是一個特殊值。
標記映象
映象名稱由斜槓分隔的名稱元件組成。名稱元件可能包含小寫字母、數字和分隔符。分隔符定義為句點、一個或兩個下劃線,或者一個或多個破折號。名稱元件不能以分隔符開頭或結尾。
一個映象由一個清單和層列表組成。簡單來說,一個標籤指向這些工件的組合。您可以為一個映象設定多個標籤,事實上,大多數映象都有多個標籤。為您構建的映象建立一個輔助標籤,然後檢視其層。
使用 docker image tag(或 docker tag 簡寫)命令為您的映象建立一個新標籤。此命令接受兩個引數;第一個引數是源映象,第二個是要建立的新標籤。以下命令為您構建的 docker-gs-ping:latest 建立一個新標籤 docker-gs-ping:v1.0:
$ docker image tag docker-gs-ping:latest docker-gs-ping:v1.0
Docker tag 命令為映象建立了一個新標籤。它不建立新映象。該標籤指向同一個映象,只是引用該映象的另一種方式。
現在再次執行 docker image ls 命令以檢視更新後的本地映象列表:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping latest 7f153fbcc0a8 6 minutes ago 1.11GB
docker-gs-ping v1.0 7f153fbcc0a8 6 minutes ago 1.11GB
...
您可以看到有兩個以 docker-gs-ping 開頭的映象。您知道它們是同一個映象,因為如果您檢視 IMAGE ID 列,您可以看到這兩個映象的值是相同的。此值是 Docker 內部用於標識映象的唯一識別符號。
刪除您剛剛建立的標籤。為此,您將使用 docker image rm 命令,或其簡寫 docker rmi(代表“移除映象”)
$ docker image rm docker-gs-ping:v1.0
Untagged: docker-gs-ping:v1.0
請注意,Docker 的響應告訴您該映象尚未刪除,只是取消了標籤。
透過執行以下命令進行驗證:
$ docker image ls
您將看到標籤 v1.0 不再在 Docker 例項保留的映象列表中。
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping latest 7f153fbcc0a8 7 minutes ago 1.11GB
...標籤 v1.0 已刪除,但您的機器上仍有 docker-gs-ping:latest 標籤可用,因此該映象仍然存在。
多階段構建
您可能已經注意到您的 docker-gs-ping 映象大小超過 1 GB,這對於一個微小的編譯 Go 應用程式來說太多了。您可能還會想,在構建映象之後,完整的 Go 工具套件(包括編譯器)去了哪裡。
答案是,完整的工具鏈仍然存在於容器映象中。這不僅因為檔案過大而帶來不便,而且在容器部署時也可能存在安全風險。
這兩個問題可以透過使用多階段構建來解決。
簡而言之,多階段構建可以將一個構建階段的工件帶到另一個構建階段,並且每個構建階段都可以從不同的基礎映象例項化。
因此,在以下示例中,您將使用一個完整的官方 Go 映象來構建您的應用程式。然後,您將把應用程式二進位制檔案複製到另一個基礎非常精簡且不包含 Go 工具鏈或其他可選元件的映象中。
示例應用程式儲存庫中的 Dockerfile.multistage 包含以下內容:
# syntax=docker/dockerfile:1
# Build the application from source
FROM golang:1.19 AS build-stage
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /docker-gs-ping
# Run the tests in the container
FROM build-stage AS run-test-stage
RUN go test -v ./...
# Deploy the application binary into a lean image
FROM gcr.io/distroless/base-debian11 AS build-release-stage
WORKDIR /
COPY --from=build-stage /docker-gs-ping /docker-gs-ping
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/docker-gs-ping"]由於您現在有兩個 Dockerfile,您必須告訴 Docker 您希望使用哪個 Dockerfile 來構建映象。用 multistage 標記新映象。這個標籤(像其他任何標籤一樣,除了 latest)對 Docker 沒有特殊含義,它只是您選擇的一個名稱。
$ docker build -t docker-gs-ping:multistage -f Dockerfile.multistage .
比較 docker-gs-ping:multistage 和 docker-gs-ping:latest 的大小,您會發現它們之間存在幾個數量級的差異。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-gs-ping multistage e3fdde09f172 About a minute ago 28.1MB
docker-gs-ping latest 336a3f164d0f About an hour ago 1.11GB
這是因為您在構建的第二階段使用的 “無發行版” 基礎映象非常精簡,專為靜態二進位制檔案的精益部署而設計。
多階段構建還有更多內容,包括多架構構建的可能性,因此請隨意檢視多階段構建。然而,這對於您在這裡的進展並非必不可少。
後續步驟
在本模組中,您瞭解了您的示例應用程式併為其構建了容器映象。
在下一個模組中,您將瞭解如何將您的映象作為容器執行。