【问题标题】:Cache "go get" in docker build在 docker build 中缓存“go get”
【发布时间】:2017-01-09 18:21:03
【问题描述】:

我想将我的 golang 单元测试封装在一个 docker-compose 脚本中,因为它依赖于几个外部服务。我的应用有很多依赖,所以go get需要一段时间。

如何以一种允许构建 docker 容器的方式缓存包,而无需在每次我想测试时下载所有依赖项?

我的 Dockerfile:

FROM golang:1.7

CMD ["go", "test", "-v"]

RUN mkdir -p /go/src/app
WORKDIR /go/src/app

COPY . /go/src/app
RUN go-wrapper download
RUN go-wrapper install

每次我想运行单元测试时,我都会在以下脚本上运行 docker-compose up --build backend-test

version: '2'
services:
  ...
  backend-test:
    build:
      context: .
      dockerfile: Dockerfile
    image: backend-test
    depends_on:
      ...

但是现在每次我想运行测试时都会调用go-wrapper download,并且需要很长时间才能完成。

解决方案?提前致谢!

【问题讨论】:

  • 如果您要“缓存”依赖项,请在供应商目录中执行此操作,这样您就可以确切知道缓存的版本。如果您不想使用vendor/,只需复制到您的 GOPATH 中即可。
  • 您可能对dobi 感兴趣,将其作为运行此类任务的一种方式。 Compose 更多地是为运行服务而设计的,而 dobi 则专注于这些类型的构建任务。

标签: unit-testing go docker docker-compose


【解决方案1】:

我个人使用govendor。根据 golang 供应商约定,它将您的依赖项保存在项目内的供应商目录中。这仍然需要在构建时复制到您的 docker 映像中。

但是有很好的理由不提供供应商。例如,当您构建 pkg 时,您不应该供应商。当您有不同的 pkg 使用不同版本的依赖项时,事情会变得混乱。这可以通过仅供应可执行文件来解决。

因此,如果您有充分的理由不向供应商供应商,您可以分开几个步骤。 将它们按正确的顺序排列会加快速度。

您可以使用一些 go get 命令创建一个 shell 脚本 (get.sh) 以获取依赖关系。 (你可以把它们放在你的 Dockerfile 中,但是它们有一个行数限制)

go get github.com/golang/protobuf/proto
go get github.com/pborman/uuid
go get golang.org/x/net/context
go get golang.org/x/net/http2
go get golang.org/x/net/http2/hpack

然后在您的 Dockerfile 中,您首先复制并执行 shell 脚本。 每次更新 get.sh 时,它都会完全重建。它仍然运行got get ./... 以确保所有依赖项都存在。但是,如果将所有内容都添加到 get.sh 脚本中,您将获得不错的速度提升。

FROM golang:1.6

RUN mkdir -p /go/src/app

COPY get.sh /go/src/app

WORKDIR /go/src/app

RUN bash get.sh

COPY . /go/src/app

RUN go get ./...

CMD go test -v

一般的想法是,您在 Dockerfile 中保持经常更改的内容较低,而顶部的内容则相当稳定。即使您必须添加另一个或两个命令。 Docker 会逐行执行,直到找到需要重建的内容,然后也会执行之后的每一行。

【讨论】:

    【解决方案2】:

    我一直在寻找您问题的答案,但具有讽刺意味的是,我发现了一个我有答案的问题(如何快速运行 docker 测试)。如果你真的想要快速测试,你最好在运行它们时完全避免重建容器。但是等等,如何将新的源代码放到容器中?卷我的朋友,卷。以下是我的设置方式:

    docker-compose.dev.yml:

    backend-test:
      volumes:
        - .:/path/to/myapp
    

    当然,/path/to/myapp 是图像中的路径。您必须为开发人员明确传递此图像:

    docker-compose up -f docker-compose.dev.yml
    

    但是现在,当您运行测试时,您将不再使用 docker-compose,而是将使用 docker exec:

    docker exec -it backend-test go test
    

    如果你做对了,你在后端测试容器中的 src 目录将永远是最新的,因为它实际上是一个挂载的卷。附加到正在运行的容器并运行测试应该比每次都启动一个新容器要快得多。

    编辑:评论者正确地指出,这只会在您的依赖项没有改变时避免重建图像(不需要go get)。好处是它不仅避免了重建,而且还避免了重新启动。当我像这样进行测试并添加依赖项时,我通常只是直接从我的测试控制台中go get。让go get 在您的容器中工作可能有点棘手,但一种方法是通过mounting SSH_AUTH_SOCK 将您的 ssh 代理转发到您的容器。遗憾的是,您无法在构建期间挂载卷,因此如果您希望构建目标能够在运行测试之前提取新的依赖项,则可能需要在映像中包含某种部署密钥。但是,我的回答的重点是将构建和测试分开,以避免在您准备好生成最终工件之前进行完整构建。

    也就是说,我想我可能明白我没有按照您提出的方式回答问题。在 ruby​​ 中,答案很简单,只需复制 Gemfile 和 Gemfile.lock,然后运行 ​​bundle install --deploy,然后再复制您更改的代码。就我个人而言,我不介意添加依赖项时的重建成本,因为我的 99% 的更改仍然不会涉及重建。也就是说,您可能会考虑使用受 golang 启发的新 Bundler 依赖管理器:dep。安装 dep 后,我很确定您可以将 Gopkg.tomlGopkg.lock 复制到您的工作目录中,运行 dep ensure,然后复制您的代码。这只会在更新 Gopkg 时拉取依赖项 - 否则 docker 将能够重用缓存层并安装您以前的依赖项。抱歉编辑太长了!

    【讨论】:

    • 这行得通,但忽略了我原来的问题的一部分(所以不确定它是否适合这里):您的解决方案根本不涉及“去获取”。如果依赖关系发生变化,您仍然必须重建容器。要么,要么你也将 golang 二进制输出文件夹放入一个卷中(用于持久性)——但这与我试图做的事情背道而驰,将整个测试封装在容器中。
    猜你喜欢
    • 2020-08-12
    • 2018-05-16
    • 1970-01-01
    • 1970-01-01
    • 2013-10-14
    • 1970-01-01
    • 1970-01-01
    • 2018-11-04
    相关资源
    最近更新 更多