坐骑

本节介绍如何在 Docker 构建中使用缓存挂载和绑定挂载。

缓存安装允许您指定要在构建期间使用的持久包缓存。持久缓存有助于加快构建步骤,尤其是涉及使用包管理器安装包的步骤。拥有包的持久缓存意味着即使您重建图层,也只能下载新的或更改的包。

缓存挂载是使用该--mount标志以及RUN Dockerfile 中的指令创建的。要使用缓存挂载,该标志的格式为 --mount=type=cache,target=<path>,其中<path>是您希望挂载到容器中的缓存目录的位置。

添加缓存挂载

用于缓存安装的目标路径取决于您使用的包管理器。本指南中的应用程序示例使用 Go 模块。这意味着缓存挂载的目标目录是写入 Go 模块缓存的目录。根据 Go 模块参考,模块缓存的默认位置是$GOPATH/pkg/mod,默认值 $GOPATH/go

更新下载包和编译程序的构建步骤,将/go/pkg/mod目录挂载为缓存挂载:

  # syntax=docker/dockerfile:1
  FROM golang:1.21-alpine AS base
  WORKDIR /src
  COPY go.mod go.sum .
- RUN go mod download
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+     go mod download -x
  COPY . .

  FROM base AS build-client
- RUN go build -o /bin/client ./cmd/client
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+     go build -o /bin/client ./cmd/client

  FROM base AS build-server
- RUN go build -o /bin/server ./cmd/server
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+     go build -o /bin/server ./cmd/server

  FROM scratch AS client
  COPY --from=build-client /bin/client /bin/
  ENTRYPOINT [ "/bin/client" ]

  FROM scratch AS server
  COPY --from=build-server /bin/server /bin/
  ENTRYPOINT [ "/bin/server" ]

-x添加到命令中的标志会go mod download打印发生的下载执行情况。添加此标志可以让您了解下一步如何使用缓存安装。

重建图像

在重建映像之前,请清除构建缓存。这可以确保您从头开始,从而更容易准确地了解构建正在做什么。

$ docker builder prune -af

现在是重建图像的时候了。调用构建命令,这次与标志一起调用--progress=plain,同时还将输出重定向到日志文件。

$ docker build --target=client --progress=plain . 2> log1.txt

构建完成后,检查log1.txt文件。日志显示了 Go 模块是如何作为构建的一部分下载的。

$ awk '/proxy.golang.org/' log1.txt
#11 0.168 # get https://proxy.golang.org/github.com/charmbracelet/lipgloss/@v/v0.6.0.mod
#11 0.168 # get https://proxy.golang.org/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod
#11 0.168 # get https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod
#11 0.168 # get https://proxy.golang.org/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod
#11 0.169 # get https://proxy.golang.org/github.com/charmbracelet/bubbles/@v/v0.14.0.mod
#11 0.218 # get https://proxy.golang.org/github.com/charmbracelet/bubbles/@v/v0.14.0.mod: 200 OK (0.049s)
#11 0.218 # get https://proxy.golang.org/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod: 200 OK (0.049s)
#11 0.218 # get https://proxy.golang.org/github.com/containerd/console/@v/v1.0.3.mod
#11 0.218 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.0.mod
#11 0.219 # get https://proxy.golang.org/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod: 200 OK (0.050s)
#11 0.219 # get https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod: 200 OK (0.051s)
#11 0.219 # get https://proxy.golang.org/github.com/charmbracelet/lipgloss/@v/v0.6.0.mod: 200 OK (0.051s)
...

现在,为了查看正在使用缓存挂载,请更改程序导入的 Go 模块之一的版本。通过更改模块版本,您将强制 Go 在下次构建时下载新版本的依赖项。如果您没有使用缓存安装,您的系统将重新下载所有模块。但由于您添加了缓存挂载,Go 可以重用大部分模块,并且只下载目录中尚不存在的包版本 /go/pkg/mod

chi更新应用程序的服务器组件使用的包的版本:

$ docker run -v $PWD:$PWD -w $PWD golang:1.21-alpine \
    go get github.com/go-chi/chi/v5@v5.0.8

现在,运行另一个构建,并再次将构建日志重定向到日志文件:

$ docker build --target=client --progress=plain . 2> log2.txt

现在,如果您检查该log2.txt文件,您会发现仅chi下载了更改的包:

$ awk '/proxy.golang.org/' log2.txt
#10 0.143 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.mod
#10 0.190 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.mod: 200 OK (0.047s)
#10 0.190 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.info
#10 0.199 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.info: 200 OK (0.008s)
#10 0.201 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.zip
#10 0.209 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.zip: 200 OK (0.008s)

添加绑定挂载

您还可以实施一些小的优化来改进 Dockerfile。目前,它使用指令在下载模块之前COPY拉入go.mod 和文件。go.sum您可以使用绑定挂载,而不是将这些文件复制到容器的文件系统。绑定挂载使容器可以直接从主机使用文件。此更改COPY完全消除了对附加指令(和层)的需要。

  # syntax=docker/dockerfile:1
  FROM golang:1.21-alpine AS base
  WORKDIR /src
- COPY go.mod go.sum .
  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
  COPY . .

  FROM base AS build-client
  RUN --mount=type=cache,target=/go/pkg/mod/ \
      go build -o /bin/client ./cmd/client

  FROM base AS build-server
  RUN --mount=type=cache,target=/go/pkg/mod/ \
      go build -o /bin/server ./cmd/server

  FROM scratch AS client
  COPY --from=build-client /bin/client /bin/
  ENTRYPOINT [ "/bin/client" ]

  FROM scratch AS server
  COPY --from=build-server /bin/server /bin/
  ENTRYPOINT [ "/bin/server" ]

同样,您也可以使用相同的技术来消除对第二条指令的需要 COPY。在build-client和 阶段指定绑定挂载build-server以挂载当前工作目录。

  # syntax=docker/dockerfile:1
  FROM golang:1.21-alpine 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
- COPY . .

  FROM base AS build-client
  RUN --mount=type=cache,target=/go/pkg/mod/ \
+     --mount=type=bind,target=. \
      go build -o /bin/client ./cmd/client

  FROM base AS build-server
  RUN --mount=type=cache,target=/go/pkg/mod/ \
+     --mount=type=bind,target=. \
      go build -o /bin/server ./cmd/server

  FROM scratch AS client
  COPY --from=build-client /bin/client /bin/
  ENTRYPOINT [ "/bin/client" ]

  FROM scratch AS server
  COPY --from=build-server /bin/server /bin/
  ENTRYPOINT [ "/bin/server" ]

概括

本节展示了如何使用缓存和绑定安装来提高构建速度。

相关信息:

下一步

本指南的下一部分将介绍如何使用构建参数使构建可配置。