简体   繁体   English

在Docker构建中缓存“获取”

[英]Cache “go get” in docker build

I want to encapsulate my golang unit testing in a docker-compose script because it depends on several external services. 我想将golang单元测试封装在docker-compose脚本中,因为它取决于多个外部服务。 My application has quite a lot of dependencies, so it takes a while to go get . 我的应用程序具有很多依赖关系,因此需要一段时间才能go get

How can I cache packages in a way that allows the docker container to build without having to download all dependencies every time I want to test? 如何以一种允许docker容器构建的方式缓存程序包,而不必每次我要测试时都下载所有依赖项?

My Dockerfile: 我的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

Every time I want to run my unit tests I run docker-compose up --build backend-test on the following script: 每次我想运行单元测试时,都会在以下脚本上运行docker-compose up --build backend-test

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

But now go-wrapper download is called each time I want to run the tests and it takes a looong time to complete. 但是现在每次我要运行测试时都会调用go-wrapper download ,这需要很短的时间才能完成。

Solutions? 解决方案? Thanks in advance! 提前致谢!

Personally I use govendor . 我个人使用govendor It keeps your dependencies in a vendor dir inside your project according to golang vendor conventions. 根据golang供应商约定,它将依赖项保留在项目内的供应商目录中。 This will still need to be copied to your docker image on build. 在构建时,仍然需要将其复制到您的Docker映像。

But there are very good reasons not to vendor. 但是有很好的理由不与供应商合作。 For example when you are building a pkg you should not vendor. 例如,当您构建pkg时,您不应供应商。 When you have different pkg's using different versions of dependencies things get messy. 当您使用不同版本的依赖项使用不同的pkg时,情况会变得混乱。 This can be remedied by only vendoring executables. 只有供应商提供的可执行文件可以解决此问题。

So if you have a good reason not to vendor you can seperate a few steps. 因此,如果您有充分的理由不愿与供应商合作,则可以分开几个步骤。 Putting them in the right order will speed things up. 按照正确的顺序放置它们可以加快处理速度。

You can create a shell script ( get.sh ) with some go get commands for dependencies. 您可以使用一些用于获取依赖项的go get命令来创建Shell脚本( get.sh )。 (you can put these in your Dockerfile, but they have a line limit) (您可以将它们放在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

Then in your Dockerfile you first copy and execute the shell script. 然后,在您的Dockerfile中,首先复制并执行shell脚本。 Each time you update the get.sh it will rebuild entirely. 每次更新get.sh时,它将完全重建。 It still runs got get ./... to make sure all dependencies are there. 它仍然运行got get ./...来确保所有依赖项都在那里。 But if everything is added in the get.sh script, you will get a decent speed boost. 但是,如果将所有内容添加到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

The general idea is that you keep frequently changing content lower in your Dockerfile and stuff that is pretty constant at the top. 通常的想法是,您要经常在Dockerfile中降低内容的更改频率,而在顶部保持不变。 Even if you have to add another command or two. 即使您必须添加另一个或两个命令。 Docker will go line by line until it finds something that needs a rebuild and will then do every line after that too. Docker会逐行处理,直到找到需要重建的内容,然后再进行每一行。

I was looking for an answer to your question, but ironically found a question I have an answer to (how to run docker tests quickly). 我一直在寻找您问题的答案,但具有讽刺意味的是,我找到了一个答案(如何快速运行Docker测试)。 If you really want fast tests, you should ideally avoid rebuilding the container at all when you run them. 如果您确实需要快速测试,则理想情况下,应在运行它们时完全避免重建容器。 But wait, how to get the new source code onto the container? 但是,等等,如何将新的源代码放入容器? Volumes my friend, volumes. 卷我的朋友,卷。 Here's how I've set this up: 这是我的设置方法:

docker-compose.dev.yml: docker-compose.dev.yml:

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

Where /path/to/myapp is the path in the image, of course. 当然,/ path / to / myapp是图像中的路径。 You'll have to explicitly pass in this image for dev: 您必须为开发人员显式传递此图像:

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

But now, when you run your tests, you're not going to use docker-compose anymore, you're going to use docker exec: 但是现在,当您运行测试时,您将不再使用docker-compose,而将使用docker exec:

docker exec -it backend-test go test

If you do this right, your src dir in the backend-test container will always be up to date because it's in fact a mounted volume. 如果正确执行此操作,则后端测试容器中的src目录将始终是最新的,因为它实际上是已装入的卷。 Attaching to a running container and running tests should prove far faster than spinning up a new one each time. 事实证明,连接到运行中的容器并运行测试要比每次旋转一个新容器快得多。

EDIT: Commenter correctly pointed out that this only avoids rebuilding the image when you're dependencies haven't changed (no need go get ). 编辑:Commenter正确指出,这仅避免在您依赖项没有更改时避免重建图像(无需go get )。 The nice thing is that it not only avoids rebuilding, but it also avoids restarting. 令人高兴的是,它不仅避免了重建,而且还避免了重新启动。 When I'm testing like this, and I add a dependency, I typically just go get directly from my test console. 当我像这样进行测试并添加依赖项时,通常只go get直接从测试控制台go get即可。 It can be a bit tricky to get go get to work within your container, but one way is to forward your ssh agent through to your container by mounting SSH_AUTH_SOCK . 它可以是一个有点棘手得到go get到工作中的容器内,但一个方法是通过您的容器转发你的SSH代理安装SSH_AUTH_SOCK Sadly, you can't mount volumes during build, so you may need to include some kind of deploy key in your image if you want your build target to be able to pull fresh dependencies before running tests. 遗憾的是,您无法在构建期间挂载卷,因此,如果您希望构建目标能够在运行测试之前提取新的依赖项,则可能需要在映像中包括某种部署密钥。 However, The main point of my answer was to separate out the build and test, to avoid the full build until you're ready to generate the final artifact. 但是,我的回答的重点是分离构建和测试,以避免完整构建,直到您准备生成最终工件为止。

That said, I think I might understand that I'm not answering the question in the way that you asked it. 就是说,我想我可能会理解,我不是按照您提出的方式回答问题。 In ruby, the answer would be as simple as copying the Gemfile and Gemfile.lock, and running bundle install --deploy , before copying over the code you've changed. 在ruby中,答案很简单,就是复制Gemfile和Gemfile.lock,然后运行bundle install --deploy ,然后再复制已更改的代码。 Personally I don't mind the cost of rebuilding when I add dependencies, since 99% of my changes still won't involve a rebuild. 就我个人而言,我不介意添加依赖项时的重建成本,因为99%的更改仍然不会涉及重建。 That said, you might look into using golang's new Bundler inspired dependency manager: dep . 也就是说,您可能会考虑使用golang的新Bundler启发式依赖管理器: dep With dep installed, I'm pretty sure you can just copy your Gopkg.toml and Gopkg.lock into your workdir, run dep ensure , and then copy your code. 安装dep后,我敢肯定您可以将Gopkg.tomlGopkg.lock复制到您的工作目录中,运行dep ensure Gopkg.lock ,然后复制您的代码。 This will only pull dependencies when the Gopkg has been updated - otherwise docker will be able to reuse the cached layer with your previous dependencies installed. 这只会在Gopkg更新后拉取依赖关系-否则docker将能够在安装了先前依赖关系的情况下重用缓存的层。 Sorry for the long edit! 抱歉,长时间编辑!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM