我们现有方法的缺点
让我们回顾一下我们一直用于安装 gem 的方法。
Gem 管理
我们现在拥有一个基于 Docker 的完整开发环境。 然而,有一个方面值得我们多加思考:Gem 管理。
到目前为止,为了安装或更新 Gem,我们只是简单地重新构建 Rails 应用的镜像。这之所以有效,是因为 bundle install 是我们 Dockerfile 中的一个步骤。不过,稍后我们会看到,与我们在非 Docker 环境中管理 Gem 时的习惯相比,这种方法存在一些小缺点。
我们将探索一种替代的 Gem 管理方法,该方法通过做出不同的权衡来避免上述缺点(一开始会更复杂)。是否继续使用我们目前采用的简单方法,还是改用这种新方法,完全取决于你。我们将学习这一技术,了解其中的权衡,然后根据你的需求和偏好自行选择。
我们简单方法的缺点
当前管理 Gem 的方法是:每次需要更新 Gem 时,就重新构建镜像。然而,你可能已经注意到,只要修改了 Gemfile,哪怕只是添加一个 Gem,所有 Gem 都必须从头开始重新安装。因此,更新 Gem 所需的时间比我们通常习惯的要长。
为什么会这样?
Bundler 和 Docker 镜像都试图实现相同的目标——确保环境的一致性,但它们实现的方式不同。Docker 的镜像构建过程破坏了 Bundler 的一些关键假设,导致其无法像我们习惯的那样正常工作。
Bundler
Bundler 最初是为长期运行的系统设计的,其中已安装的 Gem 会持续存在。主要使用场景是更新当前安装的 Gem 集合。然而,重新构建镜像相当于从零开始搭建一台新机器。在这种情况下,Bundler 并未被用于长期运行的系统。本质上,这就是问题的根源。
回顾 Dockerfile
让我们再次审视我们的 Dockerfile,并逐步分析重新构建镜像时发生了什么。以下是最后几行代码:
1COPY Gemfile* /usr/src/app/ 2WORKDIR /usr/src/app 3RUN bundle install 4COPY . /usr/src/app/ 5CMD ["bin/rails", "s", "-b", "0.0.0.0"]
当我们修改 Gemfile 时,会触发该片段第一行的缓存失效。这意味着后续步骤的缓存中间层都会被丢弃,包括之前在 bundle install 步骤中安装的 Gem。当镜像构建再次到达这一步时,就好像这台机器从未运行过 bundle install 一样。难怪 Gem 必须从头开始重新安装。
🍀 实践
让我们在下面的终端中尝试一下。点击 Run 按钮后,Gem 的下载将花费较长时间;如果你修改了 Gemfile,所有 Gem 都将被重新安装。
注意: 应用程序大约在 2 分钟后启动。如果终端断开连接,请点击 Run 按钮重新连接。
No configuration found for ID: 55b243bda0befac03d016faebebf7879a2166b13a0de6142d30da1716de2df72
这个问题有多大?
这取决于你的具体情况。你的项目中可能有更多或更少的 Gem,对等待 Gem 安装时间的容忍度也有所不同。我们基础方法的优势在于——它很简单。无需额外配置或记忆任何内容,只需重新构建镜像即可完成。这对许多人来说可能是完全可以接受的选择。然而,如果你对等待 Gem 构建的时间感到困扰,那么考虑另一种方案是值得的。