使用Docker容器中的go1.10构建缓存加速Go编译

10
我有一个Go项目,其中包含一个几乎从不更改的大型vendor/目录。
我正在尝试使用新的go 1.10构建缓存功能,在Docker引擎本地加速我的构建过程。
避免重新编译我的vendor/目录将足以进行优化。因此,我正在尝试进行与Python的常见Dockerfile模式相当的Go等效操作:
FROM python
COPY requirements.txt .              # <-- copy your dependency list
RUN pip install -r requirements.txt  # <-- install dependencies
COPY ./src ...                       # <-- your actual code (everything above is cached)

同样地,我尝试了:

FROM golang:1.10-alpine
COPY ./vendor ./src/myproject/vendor
RUN go build -v myproject/vendor/... # <-- pre-build & cache "vendor/"
COPY . ./src/myproject

然而,这会出现“找不到包”错误(很可能是因为通常情况下您也不能直接在vendor/中构建内容)。有人能解决这个问题吗?

正如你所说,你不能直接构建供应商软件包。为什么不直接构建 myproject 呢? - JimB
因为我有300多个供应商包(380万行代码),每次都必须重新构建(结果不会被缓存),而“myproject”只有530行代码。 :) - ahmet alp balkan
但是构建mypackage将构建并缓存所有供应商软件包,这似乎正是您想要做的。 - JimB
@JimB 但是为了让Docker的缓存生效,构建所有vendored包和构建项目本身需要分为两个步骤,从而在Docker镜像中创建2个层。 - Benjamin Gorman
4个回答

2

这是我使用的方法:

FROM golang:1.10-alpine
WORKDIR /usr/local/go/src/github.com/myorg/myproject/
COPY vendor vendor
RUN find vendor -maxdepth 2 -mindepth 2 -type d -exec sh -c 'go install -i github.com/myorg/myproject/{}/... || true' \;
COPY main.go .
RUN go build main.go

它确保先安装 vendored libraries。只要你不更改库,就没问题。


这对一些仓库有效,并加快了速度!但是,如果您的供应商软件包也有一个vendor/目录,则它们实际上是在您提到的最后一个RUN go build中构建的(添加-v以查看)。例如,软件包:github.com/myuser/myproj/vendor/k8s.io/client-go/kubernetes/typed/settings/v1alpha1。所以这是一个hacky/half-solution。但还是谢谢。 - ahmet alp balkan

2

只需使用go install -i ./vendor/...

考虑以下Dockerfile:

FROM    golang:1.10-alpine

ARG     APP
ENV     PTH $GOPATH/src/$APP
WORKDIR $PTH

# Pre-compile vendors.
COPY    vendor/ $PTH/vendor/
RUN     go install -i ./vendor/...

ADD     . $PTH/

# Display time taken and the list of the packages being compiled.
RUN     time go build -v

你可以通过类似如下的方式进行测试:

你可以尝试执行以下操作:

docker build -t test --build-arg APP=$(go list .) .

在我正在工作的项目中,没有预编译的情况下,每次需要处理90多个包,需要大约12秒的时间,而有了预编译之后,只需要处理3个本地包,仅需1.2秒左右的时间。
如果您仍然遇到“找不到包”的问题,这意味着缺少供应商。重新运行“dep ensure”应该可以解决问题。
另一个与Go无关的提示是将您的“.dockerignore”以“*”开头。即忽略所有内容,然后列出所需的内容。

很遗憾,这个不起作用,因为我的 vendor/ 目录被"pruned"(不包含在我的程序中未使用的子包)。因此执行 go install -i ./vendor/... 会编译一个像 github.com/alex/repo/foo 的包,它需要 github.com/alex/repo/bar,但是由于被剪枝了,它不存在于我的 vendor/ 中。然而,我的程序没有 bar 也可以编译成功。 - ahmet alp balkan
@AhmetAlpBalkan-Google,链接器/编译器难道不已经修剪了未使用的代码吗?您是否检查过减少供应商所做的这种修剪与不修剪但利用构建缓存之间的合理权衡? - RayfenWindspear

1

从Go 1.11开始,您将使用go模块来实现此目的;

FROM golang
WORKDIR /src/myproject
COPY go.mod go.sum ./             # <-- copy your dependency list
RUN go mod download               # <-- install dependencies
COPY . .                          # <-- your actual code (everything above is cached)

只要 go.sum 没有改变,由 go mod download 创建的镜像层将从缓存中重复使用。

0

仅使用go mod download并不能解决我的问题。

最终对我有用的是利用buildkit挂载Go的构建缓存。

这篇文章引导我了解到了这个特性。

我的Dockerfile大致如下:

FROM golang AS build

WORKDIR /go/src/app
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . ./
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \ 
    go build -o /my-happy-app 

对于本地开发而言,这对我来说产生了很大的不同,将构建时间从1.5分钟缩短到了3秒。

我正在使用colima(在我的Mac上运行Docker)。只是提一下,因为我没有使用Docker for Mac,但它应该能够正常工作。

colima version 0.4.2
git commit: f112f336d05926d62eb6134ee3d00f206560493b

runtime: docker
arch: x86_64
client: v20.10.9
server: v20.10.11

在golang 1.17中,因此这不是一个特定于1.10的答案。

我从docker compose的cli dockerfile here中获取了这个设置。

你的结果可能会有所不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接