我在开发和生产中都使用了docker,而真正让我烦恼的一件事就是docker cache simple。我有ruby应用程序,需要bundle install
来安装依赖项,所以我从以下Dockerfile开始:
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install --path /root/bundle
所有依赖项都被缓存,并且在我添加新gem之前它很有效。即使我添加的gem只有0.5 MB,从头开始安装所有应用程序宝石仍需要10-15分钟。然后由于依赖项文件夹的大小(约300MB)再部署10分钟。
我遇到了与node_modules和npm完全相同的问题。我在想,有没有人找到解决这个问题的方法?
到目前为止我的研究结果:
Source to image - 在增量版本中缓存任意文件。不幸的是,由于它的工作方式,它需要将整个300MB推送到注册表,即使宝石没有改变。更快的构建 - >即使未更新宝石,也会更慢部署。
Gemfile.tip - 将Gemfile拆分为两个不同的文件,只将gems添加到其中一个文件中。对捆绑器的非常具体的解决方案,我不相信它将超出添加1-2宝石的规模。
Harpoon - 如果不是他们强行抛弃Dockerfile并切换到他们自己的格式,那将是一个很好的选择。这意味着团队中所有新开发人员都会感到额外的痛苦,因为这个工具集需要时间与docker分开学习。
暂时打包缓存。这只是一个我不确定是否可能的想法。在安装软件包之前以某种方式将软件包管理器缓存(而不是依赖项文件夹)带到计算机,然后将其删除。基于我的hack,它可以显着加快bundler和npm的软件包安装,而不会使机器膨胀不必要的缓存文件。
答案 0 :(得分:3)
我发现两种可能的解决方案使用外部数据量进行宝石存储:one和two。
简而言之,
docker-compose.yml
中,您通过BUNDLE_PATH
指定volumes_from
的装入点。bundle check || bundle install
并且事情很好。这是一种可能的解决方案,但对我而言,它感觉就像是对着码头工人方式略有不同。具体来说,bundle install
对我来说听起来应该是构建过程的一部分,不应该成为运行时的一部分。其他依赖bundle install
之类的asset:precompile
现在也是运行时任务。
这是一个可行的解决方案,但我期待更强大的东西。
答案 1 :(得分:2)
我将gems缓存到应用程序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
您需要从构建的图像缓存宝石,但最初您可能没有图像。像这样创建一个空缓存(我在rake任务中有这个):
task :clear_cache do
sh "tar -jcf tmp/gemcache.tar.bz2 -T /dev/null"
end
构建映像后,将gem复制到gem缓存中。我的图片已标记为app
。我从图像创建一个docker容器,使用/var/lib/gems/2.2.0
命令将docker cp
复制到我的gemcache中,然后删除容器。这是我的佣金任务:
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
在随后的图像构建中,在调用bundle install
之前将gemcache复制到图层。这需要一些时间,但从头开始比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
命令。
答案 2 :(得分:0)
“复制本地依赖项”方法(可接受的答案)是IMO的一个坏主意。对环境进行docker化的全部目的是要拥有一个隔离的,可复制的环境。
# .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
放入主服务的启动中,而是由另一个服务来管理更新。无论哪种方式,都不要在生产中使用这种方法。在构建步骤中未安装依赖项的情况下运行服务至少会导致不必要的停机时间。