更新gem时Docker bundle安装缓存问题

3phpmpom  于 2023-05-22  发布在  Docker
关注(0)|答案(4)|浏览(168)

我在开发和生产中都使用Docker,有一件事让我很困扰,那就是Docker缓存的简单性。我有一个ruby应用程序,它需要bundle install来安装依赖项,所以我从下面的Dockerfile开始:ADD Gemfile Gemfile ADD Gemfile.lock Gemfile.lock RUN bundle install --path /root/bundle所有依赖项都被缓存,在我添加一个新的gem之前,它工作得很好。即使我添加的gem只有0.5MB,从头开始安装所有应用程序gem仍然需要10-15分钟。由于依赖文件夹的大小(约300 MB),然后再花10分钟部署它。
我在使用node_modules和npm时也遇到了同样的问题。我在想,有没有人找到解决这个问题的方法?
目前为止的研究成果:

  • Source to image-跨增量构建缓存任意文件。不幸的是,由于它的工作方式,它需要将整个300 MB推送到注册表,即使gems没有改变。更快的构建->更慢的部署,即使gem没有更新。
  • tip -将Gemfile分割成两个不同的文件,只在其中一个文件中添加gem。非常具体的解决方案,以bundler和我不相信它是要扩大超越添加1-2宝石。
  • Harpoon-如果不是他们强制放弃Dockerfile并切换到他们自己的格式,那将是一个很好的选择。这对团队中的所有新开发人员来说都意味着额外的痛苦,因为这个工具集需要时间从docker中单独学习。
  • 临时包缓存。这只是一个想法,我不确定这是可能的。在安装软件包之前,以某种方式将软件包管理器缓存(而不是依赖文件夹)带到机器上,然后将其删除。根据我的破解,它显著加快了bundler和npm的包安装速度,而不会用不必要的缓存文件使机器膨胀。
n3ipq98p

n3ipq98p1#

我发现了两种可能的解决方案,使用外部数据卷进行gem存储:onetwo
简单地说

  • 指定一个仅用于存储gem的图像
  • app 镜像中,在docker-compose.yml中,您通过volumes_from指定BUNDLE_PATH的挂载点。
  • 当你的应用容器启动时,它会执行bundle check || bundle install,一切就绪。

这是一个可能的解决方案,但对我来说,它似乎有点违背Docker的方式。具体来说,bundle install听起来应该是构建过程的一部分,而不应该是运行时的一部分。其他依赖于bundle install(如asset:precompile)的任务现在也是运行时任务。
这是一个可行的解决方案,但我期待着一些更强大的东西。

6rqinv9w

6rqinv9w2#

我将gem缓存到应用程序tmp目录中的tar文件中。然后,在安装bundle之前,我使用ADD命令将gem复制到一个层中。关于Dockerfile.yml

WORKDIR /home/app

# restore the gem cache. This only runs when
# gemcache.tar.bz2 changes, so usually it takes
# no time
ADD tmp/gemcache.tar.bz2 /var/lib/gems/

COPY Gemfile /home/app/Gemfile
COPY Gemfile.lock /home/app/Gemfile.lock
RUN gem update --system && \
gem update bundler && \
bundle install --jobs 4 --retry 5

确保你正在将gem缓存发送到你的docker机器。我的gemcache是118MB,但由于我是在本地构建的,所以它复制得很快。我的.dockerignore

tmp
!tmp/gemcache.tar.bz2

您需要缓存构建映像中的gem,但最初可能没有映像。像这样创建一个空缓存(我在一个rake任务中有这个):

task :clear_cache do
  sh "tar -jcf tmp/gemcache.tar.bz2 -T /dev/null"
end

镜像构建完成后,将gem复制到gem缓存中。我的图片被标记为app。我从镜像创建了一个docker容器,使用docker cp命令将/var/lib/gems/2.2.0复制到我的gemcache中,然后删除容器。下面是我的rake任务:

task :cache_gems do
  id = `docker create app`.strip
  begin
    sh "docker cp #{id}:/var/lib/gems/2.2.0/ - | bzip2 > tmp/gemcache.tar.bz2"
  ensure
    sh "docker rm -v #{id}"
  end
end

在随后的图像构建中,gemcache被复制到调用bundle install之前的层。这需要一些时间,但它比从头开始的bundle install快。
之后的构建甚至更快,因为docker已经缓存了ADD tmp/gemcache.tar.bz2 /var/lib/gems/层。如果对Gemfile.lock有任何更改,则仅构建这些更改。
没有理由在每个Gemfile.lock更改时重建gem缓存。一旦该高速缓存和Gemfile.lock之间存在足够的差异,bundle install很慢,您就可以重建gem缓存。当我想重建gem缓存时,它是一个简单的rake cache_gems命令。

irtuqstp

irtuqstp3#

“复制本地依赖项”方法(接受的答案)是一个坏主意IMO。将你的环境对接的全部意义在于拥有一个隔离的、可复制的环境。
我们是这样做的。

# .docker/docker-compose.dev.yml
version: '3.7'
services:

  web:
    build: .
    command: 'bash -c "wait-for-it cache:1337 && bin/rails server"'
    depends_on:
      - cache
    volumes:
      - cache:/bundle
    environment:
      BUNDLE_PATH: '/bundle'

  cache:
    build:
      context: ../
      dockerfile: .docker/cache.Dockerfile
    volumes:
      - bundle:/bundle
    environment:
      BUNDLE_PATH: '/bundle'
    ports:
      - "1337:1337"

volumes:
  cache:
# .docker/cache.Dockerfile
FROM ruby:2.6.3
RUN apt-get update -qq && apt-get install -y netcat-openbsd
COPY Gemfile* ./
COPY .docker/cache-entrypoint.sh ./
RUN chmod +x cache-entrypoint.sh
ENTRYPOINT ./cache-entrypoint.sh
# .docker/cache-entrypoint.sh
#!/bin/bash

bundle check || bundle install
nc -l -k -p 1337
# web.dev.Dockerfile
FROM ruby:2.6.3
RUN apt-get update -qq && apt-get install -y nodejs wait-for-it
WORKDIR ${GITHUB_WORKSPACE:-/app}
# Note: bundle install step removed
COPY . ./

这与@EightyEight解释的概念类似,但它不会将bundle install放入主服务的启动中,而是由不同的服务管理更新。无论哪种方式,都不要在生产中使用这种方法。如果在构建步骤中没有安装依赖项的情况下运行服务,至少会导致不必要的停机时间。

myss37ts

myss37ts4#

这个问题很老了,但我最近遇到了这个问题。我正在做一个项目,它有几个依赖项和原生扩展,这使得每当Gemfile中发生更改时,构建一个新的docker镜像需要大约15分钟。
我并不是真的想用外部卷将依赖项安装推迟到运行步骤,所以我做了更多的研究,经过一些尝试和错误,我设法让捆绑包缓存在构建过程中工作。
我所做的是使用以下代码将缓存挂载添加到我的Dockerfile:

...
COPY Gemfile Gemfile.lock $app
RUN --mount=type=cache,target=/bundler \
    BUNDLE_PATH=/bundler bundle install --jobs 3 --verbose && \
    cp -r /bundler/ruby/3.2.0/* /usr/local/bundle
...

(Note路径可能会有所不同,具体取决于您使用的ruby版本)
我想在提交其他答案时,这可能不是一个受支持的docker功能。
希望这能帮助那些努力让bundler与docker cache合作的人!

相关问题