Ruby 更新gems时Docker bundle安装缓存问题
我在开发和生产中都使用docker,而真正困扰我的是docker缓存的简单性。我有一个ruby应用程序,它需要Ruby 更新gems时Docker bundle安装缓存问题,ruby,docker,npm,bundler,dockerfile,Ruby,Docker,Npm,Bundler,Dockerfile,我在开发和生产中都使用docker,而真正困扰我的是docker缓存的简单性。我有一个ruby应用程序,它需要bundle install来安装依赖项,因此我从以下Dockerfile开始: 添加Gemfile Gemfile 添加Gemfile.lock Gemfile.lock 运行bundle安装--path/root/bundle 所有依赖项都被缓存,在我添加一个新的gem之前,它工作得很好。即使我添加的gem只有0.5MB,从零开始安装所有应用程序gem仍然需要10-15分钟。然后
bundle install
来安装依赖项,因此我从以下Dockerfile开始:
添加Gemfile Gemfile
添加Gemfile.lock Gemfile.lock
运行bundle安装--path/root/bundle
所有依赖项都被缓存,在我添加一个新的gem之前,它工作得很好。即使我添加的gem只有0.5MB,从零开始安装所有应用程序gem仍然需要10-15分钟。然后,由于dependencies文件夹的大小(大约300MB),需要另外10分钟来部署它
我在node_模块和npm中遇到了完全相同的问题。我在想,有没有人找到解决这个问题的办法
到目前为止,我的研究成果如下:
- -跨增量生成缓存任意文件。不幸的是,由于它的工作方式,即使gem没有更改,也需要将整个300MB推送到注册表。更快的构建->更慢的部署,即使GEM没有更新
- -将Gemfile拆分为两个不同的文件,并仅向其中一个文件添加gem。bundler的解决方案非常具体,我不相信它的规模会超过添加1-2颗宝石
- -如果不是因为他们强制放弃Dockerfile并切换到他们自己的格式,这将是一个很好的选择。这意味着团队中的所有新开发人员都会感到额外的痛苦,因为该工具集需要时间与docker分开学习
- 临时打包缓存。这只是一个我不确定是否可能的想法。在安装包之前,以某种方式将包管理器缓存(而不是依赖项文件夹)带到计算机,然后将其删除。基于我的hack,它大大加快了bundler和npm的软件包安装速度,而不会用不必要的缓存文件使机器膨胀
- 您指定的图像仅用于存储宝石
- 在应用程序图像中,在
中,通过docker compose.yml
指定volumes\u from
BUNDLE\u路径的装载点
- 当你的应用程序容器启动时,它会执行
,一切都很好捆绑检查| |捆绑安装
bundle安装
在我看来应该是构建过程的一部分,而不应该是运行时的一部分。其他依赖于捆绑包安装的东西,比如资产:预编译
现在也是一项运行时任务
这是一个可行的解决方案,但我期待着更强大的解决方案。我将gems缓存到应用程序tmp目录中的tar文件中。然后在安装包之前,我使用ADD
命令将gems复制到一个层中。从myDockerfile.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,但因为我在本地构建,所以它拷贝速度很快。我的.dockrignore
:
tmp
!tmp/gemcache.tar.bz2
您需要缓存构建映像中的宝石,但最初可能没有映像。像这样创建一个空缓存(我在rake任务中有这个):
生成映像后,将gem复制到gem缓存。我的图像被标记为app
。我从图像创建一个docker容器,使用docker cp
命令将/var/lib/gems/2.2.0
复制到我的gemcache中,然后删除该容器。这是我的任务:
task :clear_cache do
sh "tar -jcf tmp/gemcache.tar.bz2 -T /dev/null"
end
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
。这需要一些时间,但比从头开始安装捆绑包要快
之后的构建速度更快,因为docker缓存了addtmp/gemcache.tar.bz2/var/lib/gems/
层。如果对Gemfile.lock有任何更改,则只生成这些更改
没有理由在每个Gemfile.lock
change上重建gem缓存。一旦缓存与Gemfile.lock
之间存在足够的差异,以至于捆绑安装
速度较慢,您就可以重建gem缓存。当我确实想要重建gem缓存时,它是一个简单的rake cache\u gems
命令。在我看来,“复制本地依赖项”方法(接受的答案)是一个坏主意。将您的环境固定化的全部目的是拥有一个隔离的、可复制的环境
给你
#.docker/cache-entrypoint.sh
#!/bin/bash
捆绑检查| |捆绑安装
nc-l-k-P1337
这与@EightyEight所解释的概念类似,但它不会将捆绑安装
放入主服务的启动中,而是由不同的服务管理更新。无论如何,不要在生产中使用这种方法。在构建步骤中未安装依赖项的情况下运行服务,至少会导致超过必要的停机时间。应该可以将gem目录从构建映像复制到tar文件中。然后,您可以在add Gemfile之前添加一个层,该Gemfile将该gem目录恢复回映像中。此时,只需要重新构建更改。我现在正在试验这种技术。如果我能让它发挥作用,我会发布一个答案。@ScottJacobsen到目前为止运气如何?@jQwierdy-发布了我的解决方案。有趣的解决方案。虽然我担心的是在某个未定义的时间点运行缓存gems的手动步骤。可以在dockerfile“bundle check | | |(bundle install&rake cache_gems)”中执行类似操作,但这是否具有预期的行为?只有在bundle发生更改时才会这样做,它会安装,每次安装时都会缓存gems:-)bundle-check可以稍微加快速度,但是bundle
# .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
# 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 . ./