<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>soulteary (苏洋)</title>
    <link>https://ruby-china.org/soulteary</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Lobsters 应用容器封装踩坑记录</title>
      <description>&lt;p&gt;作为 Ruby 新手，最近在折腾 Lobsters 的容器化封装。&lt;/p&gt;

&lt;p&gt;分享一篇踩坑记录，希望能帮到和“bundler/ruby 2.7 搏斗”的同学。 : )&lt;/p&gt;
&lt;h2 id="Ruby 应用容器封装踩坑记录（Lobsters）"&gt;Ruby 应用容器封装踩坑记录（Lobsters）&lt;/h2&gt;
&lt;p&gt;最近在基于 Lobsters 进行社区部分功能的开发，在开发过程中，需要将应用进行容器化配置和部署，经历了比较典型的 Ruby 老版本软件升级，过程中遇到了不少问题。&lt;/p&gt;

&lt;p&gt;在此记录下，希望能帮到有相同需求的同学。&lt;/p&gt;
&lt;h2 id="写在前面"&gt;写在前面&lt;/h2&gt;
&lt;p&gt;首先回答为什么要考虑对 Ruby 应用进行容器化封装。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一来，目前线上运行的应用必须以容器方式进行交付运行，我们使用容器的方式注册应用，对外提供服务；&lt;/li&gt;
&lt;li&gt;二来，个人倾向并坚持使用容器方案，可以方便后续快速水平扩展；以及最重要的一点，“代码和命令皆有记录”，方便离线的问题排查。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一般的 Web 应用封装都会经历下下几个阶段，整合源代码，安装应用依赖和环境，进行程序/产物的编译，调整权限和目录结构，进行测试，完成后对镜像打标签进行版本管理。&lt;/p&gt;

&lt;p&gt;这次的踩坑记录亦是如此。&lt;/p&gt;
&lt;h2 id="故事的开始"&gt;故事的开始&lt;/h2&gt;
&lt;p&gt;应用镜像的封装最早要从年前的一次模版风格定制开始，当时我们参考 &lt;a href="https://github.com/utensils/docker-lobsters" rel="nofollow" target="_blank" title=""&gt;https://github.com/utensils/docker-lobsters&lt;/a&gt; 封装了一套镜像，因为当时并未对官方程序进行依赖修改，所以用着这套镜像的程序在线上安然跑了两个多月，直至最近复工，当时的镜像文件是这样编写的：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Lobsters
#
# VERSION latest
ARG BASE_IMAGE=ruby:2.3-alpine
FROM ${BASE_IMAGE}

# Create lobsters user and group.
RUN set -xe; \
    addgroup -S lobsters; \
    adduser -S -h /lobsters -s /bin/sh -G lobsters lobsters;

# Install needed runtime dependencies.
RUN set -xe; \
    chown -R lobsters:lobsters /lobsters; \
    apk add --no-cache --update --virtual .runtime-deps \
        mariadb-connector-c \
        bash \
        nodejs \
        npm \
        sqlite-libs \
        tzdata;

# Change shell to bash
SHELL ["/bin/bash", "-c"]

# Install needed development dependencies. If this is a developer_build we don't remove
# the build-deps after doing a bundle install.
# Copy Gemfile to container.
COPY --chown=lobsters:lobsters ./lobsters/Gemfile ./lobsters/Gemfile.lock /lobsters/
ARG DEVELOPER_BUILD=false
RUN set -xe; \
    apk add --no-cache --virtual .build-deps \
        build-base \
        curl \
        gcc \
        git \
        gnupg \
        linux-headers \
        mariadb-connector-c-dev \
        mariadb-dev \
        sqlite-dev; \
    export PATH=/lobsters/.gem/ruby/2.3.0/bin:$PATH; \
    export SUPATH=$PATH; \
    export GEM_HOME="/lobsters/.gem"; \
    export GEM_PATH="/lobsters/.gem"; \
    export BUNDLE_PATH="/lobsters/.bundle"; \
    cd /lobsters; \
    su lobsters -c "gem install bundler --user-install"; \
    su lobsters -c "gem update"; \
    su lobsters -c "gem install rake -v 12.3.2"; \
    su lobsters -c "bundle install --no-cache"; \
    su lobsters -c "bundle add puma --version '~&amp;gt; 3.12.1'"; \
    if [ "${DEVELOPER_BUILD,,}" != "true" ]; \
    then \
        apk del .build-deps; \
    fi; \
    mv /lobsters/Gemfile /lobsters/Gemfile.bak; \
    mv /lobsters/Gemfile.lock /lobsters/Gemfile.lock.bak;

# Copy lobsters into the container.
COPY ./lobsters ./docker-assets /lobsters/

# Set proper permissions and move assets and configs.
RUN set -xe; \
    mv /lobsters/Gemfile.bak /lobsters/Gemfile; \
    mv /lobsters/Gemfile.lock.bak /lobsters/Gemfile.lock; \
    chown -R lobsters:lobsters /lobsters; \
    mv /lobsters/docker-entrypoint.sh /usr/local/bin/; \
    chmod 755 /usr/local/bin/docker-entrypoint.sh;

# Drop down to unprivileged users
USER lobsters

# Set our working directory.
WORKDIR /lobsters/

# Build arguments.
ARG VCS_REF
ARG BUILD_DATE
ARG VERSION

# Labels / Metadata.
LABEL \
    org.opencontainers.image.authors="James Brink &amp;lt;brink.james@gmail.com&amp;gt;" \
    org.opencontainers.image.created="${BUILD_DATE}" \
    org.opencontainers.image.description="Lobsters Rails Project" \
    org.opencontainers.image.revision="${VCS_REF}" \
    org.opencontainers.image.source="https://github.com/utensils/docker-lobsters" \
    org.opencontainers.image.title="lobsters" \
    org.opencontainers.image.vendor="Utensils" \
    org.opencontainers.image.version="${VERSION}"

# Set environment variables.
ENV MARIADB_HOST="mariadb" \
    MARIADB_PORT="3306" \
    MARIADB_PASSWORD="password" \
    MARIADB_USER="root" \
    LOBSTER_DATABASE="lobsters" \
    LOBSTER_HOSTNAME="localhost" \
    LOBSTER_SITE_NAME="Example News" \
    RAILS_ENV="development" \
    SECRET_KEY="" \
    GEM_HOME="/lobsters/.gem" \
    GEM_PATH="/lobsters/.gem" \
    BUNDLE_PATH="/lobsters/.bundle" \
    RAILS_MAX_THREADS="5" \
    SMTP_HOST="127.0.0.1" \
    SMTP_PORT="25" \
    SMTP_STARTTLS_AUTO="true" \
    SMTP_USERNAME="lobsters" \
    SMTP_PASSWORD="lobsters" \
    RAILS_LOG_TO_STDOUT="1" \
    PATH="/lobsters/.gem/ruby/2.3.0/bin:$PATH"

# Expose HTTP port.
EXPOSE 3000

# Execute our entry script.
CMD ["/usr/local/bin/docker-entrypoint.sh"]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而因为要对 lobsters 进行用户系统对接等修改，Gemfile / Gemfile.lock 不可避免的需要更新，开发工程师也顺手将 Ruby 版本调整到了 2.4.0，然而没想到只因为这么一个小小的变动，就开始了连环踩坑。&lt;/p&gt;

&lt;p&gt;Gemfile 的变更记录其实不多：&lt;/p&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/Gemfile b/Gemfile
index 37f698d..ed43b5c 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/Gemfile
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/Gemfile 
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@@ -11,6 +13,7 @@&lt;/span&gt; gem "mysql2"
 gem 'scenic'
 gem 'scenic-mysql_adapter'
 gem "activerecord-typedstore"
&lt;span class="gi"&gt;+gem 'jbuilder'
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; # js
 gem "dynamic_form"
&lt;span class="p"&gt;@@ -19,9 +22,9 @@&lt;/span&gt; gem "json"
 gem "uglifier", "&amp;gt;= 1.3.0"
&lt;span class="err"&gt;
&lt;/span&gt; # deployment
&lt;span class="gd"&gt;-gem "actionpack-page_caching"
&lt;/span&gt;&lt;span class="gi"&gt;+gem "actionpack-page_caching", "~&amp;gt; 1.1.1"
&lt;/span&gt; gem "exception_notification"
&lt;span class="gd"&gt;-gem "unicorn"
&lt;/span&gt;&lt;span class="gi"&gt;+gem "puma", "~&amp;gt; 4.3.3"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; # security
 gem "bcrypt", "~&amp;gt; 3.1.2"
&lt;span class="p"&gt;@@ -42,6 +45,14 @@&lt;/span&gt; gem 'transaction_retry' # mitigate https://github.com/lobsters/lobsters-ansible/
&lt;span class="err"&gt;
&lt;/span&gt; gem "scout_apm", "2.6.2"
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+gem 'settingslogic'
+
+# for oauth2
+gem 'oauth2'
+gem 'whenever', require: false
+
+gem "paranoia", "~&amp;gt; 2.2"
+
&lt;/span&gt; group :test, :development do
   gem 'bullet'
   gem 'capybara'
&lt;span class="p"&gt;@@ -57,3 +68,17 @@&lt;/span&gt; group :test, :development do
   gem "byebug"
   gem "rb-readline"
 end
&lt;span class="gi"&gt;+
+group :development do
+  gem 'web-console', '&amp;gt;= 3.3.0'
+  gem 'spring'
+  gem 'spring-watcher-listen', '~&amp;gt; 2.0.0'
+  gem "ed25519" , "~&amp;gt; 1.2", require: false
+  gem "bcrypt_pbkdf", "~&amp;gt; 1.0", require: false
+
+  gem "capistrano",            require: false
+  gem 'capistrano-rvm',        require: false
+  gem 'capistrano-rails',      require: false
+  gem 'capistrano-bundler',    require: false
+  gem 'capistrano3-puma',      require: false
+end
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里需要额外提一个点，Gemfile.lock 中除了依赖更新外，bundle 版本有变化：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; BUNDLED WITH
-   2.0.2
+   1.17.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;基本需要关注的内容都介绍完毕了，我们先使用上面提到的 Dockerfile 进行镜像构建。&lt;/p&gt;
&lt;h2 id="第一回合：尝试升级 Ruby 2.4.0"&gt;第一回合：尝试升级 Ruby 2.4.0&lt;/h2&gt;
&lt;p&gt;第一回合在更新镜像 Ruby 依赖时，报了版本不兼容的错误。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Successfully installed bundler-2.1.4
1 gem installed
+ su lobsters -c 'gem update'
ERROR:  Error installing bigdecimal:
    There are no versions of bigdecimal (= 2.0.0) compatible with your Ruby &amp;amp; RubyGems
    bigdecimal requires Ruby version &amp;gt;= 2.4.0. The current ruby version is 2.3.8.459.
ERROR:  Error installing io-console:
    There are no versions of io-console (= 0.5.6) compatible with your Ruby &amp;amp; RubyGems
    io-console requires Ruby version &amp;gt;= 2.4.0. The current ruby version is 2.3.8.459.
Updating installed gems
Updating bigdecimal
Updating bundler
Successfully installed bundler-2.1.4
Updating io-console
Updating json
Building native extensions. This could take a while...
Successfully installed json-2.3.0
Updating psych
Building native extensions. This could take a while...
ERROR:  Error installing rdoc:
    There are no versions of rdoc (= 6.2.1) compatible with your Ruby &amp;amp; RubyGems
    rdoc requires Ruby version &amp;gt;= 2.4.0. The current ruby version is 2.3.8.459.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;考虑到实际运行环境已经升级到 ruby 2.4，故这里需要对容器配置文件进行修改，将 &lt;code&gt;BASE_IMAGE=ruby:2.3-alpine&lt;/code&gt; 修改为 &lt;code&gt;BASE_IMAGE=ruby:2.4-alpine&lt;/code&gt;，镜像配置文件中包含 &lt;code&gt;2.3.0&lt;/code&gt; 的 Path 也需要更新为 &lt;code&gt;2.4.0&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;修改完毕后，我们继续下一场战斗。&lt;/p&gt;
&lt;h3 id="额外的小坑：官方镜像路径"&gt;额外的小坑：官方镜像路径&lt;/h3&gt;
&lt;p&gt;我们使用 &lt;code&gt;ruby -v&lt;/code&gt; 命令可以清楚看到我们实际使用的版本是 &lt;strong&gt;2.4.9p362&lt;/strong&gt;。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -it ruby:2.4-alpine ruby -v

ruby 2.4.9p362 (2019-10-02 revision 67824) [x86_64-linux-musl]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是在检查本地的安装目录时，可以看到安装目录是 &lt;strong&gt;2.4.0&lt;/strong&gt;。也就是说，官方镜像会忽略版本号最后一位修正版本号。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -it ruby:2.4-alpine ls /usr/local/lib/ruby/site_ruby/

2.4.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以在编写配置的时候，如果涉及定义具体路径，注意不要把修正版本写进去。&lt;/p&gt;
&lt;h2 id="第二回合：手动指定 Puma 版本"&gt;第二回合：手动指定 Puma 版本&lt;/h2&gt;
&lt;p&gt;将镜像升级到 &lt;code&gt;ruby:2.4-alpine&lt;/code&gt; 后，经过漫长的编译等待，终于看到了熟悉的“Bundle complete! 53 Gemfile dependencies, 134 gems now installed.”提示，说明软件依赖顺利安装完毕。&lt;/p&gt;

&lt;p&gt;本以为这个事情就这么愉快结束了，万万没想到紧接着出现了一个经典错误，环境和实际依赖不一致：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Post-install message from capistrano3-puma:

    All plugins need to be explicitly installed with install_plugin.
    Please see README.md
  + su lobsters -c 'bundle add puma --version '\''~&amp;gt; 3.12.1'\'''

[!] There was an error parsing `injected gems`: You cannot specify the same gem twice with different version requirements.
You specified: puma (~&amp;gt; 4.3.3) and puma (~&amp;gt; 3.12.1). If you want to update the gem version, run `bundle update puma`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive.. Bundler cannot continue.

 #  from injected gems:1
 #  -------------------------------------------
 &amp;gt;  gem "puma", "~&amp;gt; 3.12.1"
 #  -------------------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还记得之前的容器配置文件中，有一句 &lt;code&gt;su lobsters -c "bundle add puma --version '~&amp;gt; 3.12.1'"&lt;/code&gt;命令吗？&lt;/p&gt;

&lt;p&gt;这句命令和当前应用依赖配置中声明的 &lt;code&gt;gem "puma", "~&amp;gt; 4.3.3"&lt;/code&gt; 冲突了。&lt;/p&gt;

&lt;p&gt;将容器配置中的命令修改为 &lt;code&gt;~&amp;gt; 4.3.3&lt;/code&gt; ，开始下一次尝试。&lt;/p&gt;
&lt;h2 id="第三回合：手动指定 Rake 版本"&gt;第三回合：手动指定 Rake 版本&lt;/h2&gt;
&lt;p&gt;在修改容器环境后，我们很“顺利”的将镜像打包完毕。虽然还在报类似上面的错误，但是看起来仅仅是因为软件依赖文件的声明的问题，应该不影响运行。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Post-install message from capistrano3-puma:

    All plugins need to be explicitly installed with install_plugin.
    Please see README.md
  + su lobsters -c 'bundle add puma --version '\''~&amp;gt; 4.3.3'\'''
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;倔强的尝试启动应用，会发现出现了一个新的问题 Rake 任务执行出错。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rake aborted!
Gem::LoadError: You have already activated rake 12.3.2, but your Gemfile requires rake 13.0.1. Prepending `bundle exec` to your command may solve this.
/lobsters/config/boot.rb:3:in `&amp;lt;top (required)&amp;gt;'
/lobsters/config/application.rb:1:in `require_relative'
/lobsters/config/application.rb:1:in `&amp;lt;top (required)&amp;gt;'
/lobsters/Rakefile:4:in `&amp;lt;top (required)&amp;gt;'
/lobsters/.gem/gems/rake-12.3.2/exe/rake:27:in `&amp;lt;top (required)&amp;gt;'
(See full trace by running task with --trace)
2020-03-21 23:26:00 - DB Version: 
2020-03-21 23:26:00 - Creating database.
rake aborted!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据线索，我们在 Dockerfile 中添加一条命令，强制执行任务的 rake 软件版本。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUN gem install rake --version 13.0.1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续新的尝试。&lt;/p&gt;
&lt;h2 id="第四回合：完成 Ruby 2.4 软件运行环境"&gt;第四回合：完成 Ruby 2.4 软件运行环境&lt;/h2&gt;
&lt;p&gt;在幸运倔强下，这次软件正常运行起来了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Puma starting in single mode...
* Version 4.3.3 (ruby 2.4.9-p362), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;curl&lt;/code&gt; 命令验证一下程序，看到程序已经正常跑起来了。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl https://127.0.0.1 -H "host:hub.lab.com" -I

HTTP/2 200 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本来到这里，应该一切就完结了，但是考虑到应用未来的可维护性，我们需要继续尝试对应用进行升级处理。&lt;/p&gt;

&lt;p&gt;毕竟自 2.4.x 在 &lt;a href="http://www.ruby-lang.org/en/news/2016/12/25/ruby-2-4-0-released/" rel="nofollow" target="_blank" title=""&gt;2016 年末推出后&lt;/a&gt;，官方后续陆续的也出了不少&lt;a href="http://www.ruby-lang.org/en/security/" rel="nofollow" target="_blank" title=""&gt;安全修复&lt;/a&gt;，而且多数受到影响的都是老版本的 Ruby / RubyGems，我可不想在 2020 年还在维护一个五年的软件环境，以及一堆不知道哪年推出的软件包依赖，以及他们潜在的莫名其妙的问题，和一堆已知的安全风险。&lt;/p&gt;

&lt;p&gt;将 Dockerfile 中的 &lt;code&gt;ruby:2.4-alpine&lt;/code&gt; 调整至 &lt;code&gt;ruby:2.7-alpine&lt;/code&gt;，记得注意第一回合里记录的“路径细节”，再次尝试构建镜像。&lt;/p&gt;
&lt;h2 id="第五回合：尝试升级 Ruby 2.7 运行环境"&gt;第五回合：尝试升级 Ruby 2.7 运行环境&lt;/h2&gt;
&lt;p&gt;不出意外，又遇到了新的问题。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+ su lobsters -c 'bundle install --no-cache'
/usr/local/lib/ruby/2.7.0/rubygems.rb:275:in `find_spec_for_exe': Could not find 'bundler' (1.17.3) required by your /lobsters/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:1.17.3`
    from /usr/local/lib/ruby/2.7.0/rubygems.rb:294:in `activate_bin_path'
    from /lobsters/.gem/ruby/2.7.0/bin/bundle:23:in `&amp;lt;main&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据错误提示在镜像文件中的 &lt;code&gt;bundle install --no-cache&lt;/code&gt; 前添加两条命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+ su lobsters -c "bundle update --bundler"; \
+ su lobsters -c "gem install bundler:1.17.3"; \
su lobsters -c "bundle install --no-cache"; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次构建会发现除了报告了两条警告外一切正常。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
Installing whenever 1.0.0
Warning: the lockfile is being updated to Bundler 2, after which you will be unable to return to Bundler 1.
Bundle updated!
...
+ su lobsters -c 'bundle install --no-cache'
[DEPRECATED] The `--no-cache` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set no-cache 'true'`, and stop using this flag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;和第四回合一样，验证应用可以正常启动，说明修改是正确的。&lt;/p&gt;

&lt;p&gt;但是还是存在一些问题，我们继续进行优化，解决这些不应该存在的“警告”，避免程序在运行时出现其他问题。&lt;/p&gt;
&lt;h2 id="第六回合：升级 Bundler 到合适版本"&gt;第六回合：升级 Bundler 到合适版本&lt;/h2&gt;
&lt;p&gt;迄今为止我们主要完成了下面两件事：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;在 2.4.x 版本的 ruby 镜像中启动 lobsters&lt;/li&gt;
&lt;li&gt;在 2.7.x 版本的 ruby 镜像中启动 lobsters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;目前剩下的问题还有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;尝试升级比 ruby 2.4.x 推出时间更早的 bundler 1.7（&lt;a href="https://bundler.io/v1.7/whats_new.html" rel="nofollow" target="_blank" title=""&gt;2015 年&lt;/a&gt;），以避免后续遇到更多各种奇怪的问题&lt;/li&gt;
&lt;li&gt;尝试解决各种老版本依赖、组件的潜在兼容性问题，比如 rake、puma...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上一回合中，构建镜像出现警告的根本原因在于文章开头我们指定了&lt;strong&gt;BUNDLED WITH  1.17.3&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这里推荐一个解决方案，参考 Node 和 NPM，选择跟随语言运行环境推出时间段的相关工具版本，不要 hardcode 写死版本。&lt;/p&gt;

&lt;p&gt;其实最初的镜像文件中，其实默认就会使用 &lt;code&gt;gem&lt;/code&gt; 安装最新兼容的 &lt;code&gt;bundler&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
+ su lobsters -c 'gem install bundler --user-install'
Successfully installed bundler-2.1.4
1 gem installed
+ su lobsters -c 'gem update'
Updating installed gems
Updating bundler
Successfully installed bundler-2.1.4
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以在 Gemfile.lock 中，可以直接删除 &lt;code&gt;BUNDLED WITH&lt;/code&gt; 相关版本配置，另外可以将上一回合添加的安装旧版本的 &lt;code&gt;bundler&lt;/code&gt; 命令从 &lt;code&gt;Dockerfile&lt;/code&gt; 也删除掉。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su lobsters -c "bundle update --bundler"; \
su lobsters -c "gem install bundler:1.17.3"; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试构建顺利成功，启动应用也没有问题。&lt;/p&gt;
&lt;h2 id="第七回合：升级 Rake 版本到合适版本"&gt;第七回合：升级 Rake 版本到合适版本&lt;/h2&gt;
&lt;p&gt;接着来解决 &lt;code&gt;rake&lt;/code&gt; 的版本问题，和 &lt;code&gt;bundler&lt;/code&gt; 的处理思路一样，如非必要，不需要进行额外指定是最好的。&lt;/p&gt;

&lt;p&gt;除了第三回合我们有指定 rake 版本外，其实最初的镜像也有声明 rake 的版本。所以我们先尝试将两条声明都删除，进行镜像构建测试：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
Fetching rake 13.0.1
Installing rake 13.0.1
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来默认的 rake 版本就是 13.0.1，似乎是“减负成功”了。但是启动应用的时候，我们发现又有新的问题，“bundler 找不到可执行的命令”。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rake aborted!
Bundler::GemNotFound: Could not find rake-13.0.1 in any of the sources
...
bundler: failed to load command: rake (/usr/local/bin/rake)
Bundler::GemNotFound: Could not find rake-13.0.1 in any of the sources
...
bundler: command not found: rails
Install missing gem executables with `bundle install`
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在容器镜像文件中我们有定义 &lt;code&gt;bundle install --no-cache&lt;/code&gt;，所以这里错误提示后的建议的内容是不准确的，推测这里的问题是缺失 &lt;code&gt;rake&lt;/code&gt; 依赖包，在镜像文件中添加命令，对其进行安装。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
su lobsters -c "gem install bundler --user-install"; \
su lobsters -c "gem install rake";
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是报错依旧，再次看错误日志，看到一个隐藏逻辑：“rake 调用者是 bundler”，所以是不是应该先安装 rake，再安装 bundler 呢？&lt;/p&gt;

&lt;p&gt;将上面两条命令顺序颠倒，或者使用下面的方式合并为一条。（目前 gem 还是顺序安装，没有“并发安装模式”，所以下面的命令是可行的。）&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su lobsters -c "gem install rake bundler --user-install";
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;果不其然，之前找不到 rake 的问题解决了，但是出现了一个新的问题。&lt;/p&gt;
&lt;h2 id="第八回合：探究迷一样的 Bundler 经典报错"&gt;第八回合：探究迷一样的 Bundler 经典报错&lt;/h2&gt;
&lt;p&gt;新出现的问题是个经典问题，程序报错形式如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/local/lib/ruby/2.7.0/rubygems.rb:275:in `find_spec_for_exe': can't find gem rake (&amp;gt;= 0.a) with executable rake (Gem::GemNotFoundException)
    from /usr/local/lib/ruby/2.7.0/rubygems.rb:294:in `activate_bin_path'
    from /lobsters/.gem/ruby/2.7.0/bin/rake:23:in `&amp;lt;main&amp;gt;'
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个问题在 bundler 官方博客中有记录：&lt;a href="https://bundler.io/blog/2019/05/14/solutions-for-cant-find-gem-bundler-with-executable-bundle.html" rel="nofollow" target="_blank" title=""&gt;Solutions for 'Cant find gem bundler (&amp;gt;= 0.a) with executable bundle'&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;在官方博客文章中，有提到“The bug is fixed in RubyGems 2.7.10 or 3.0.0 and above”，理论来说我们使用的是 2.7.x 版本的最新镜像，应该是不会出现这个问题的，难道...&lt;/p&gt;

&lt;p&gt;故技重施，查看当前使用的容器镜像中的 ruby 版本：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -it ruby:2.7-alpine ruby -v

ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux-musl]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;果不其然，官方镜像是“老版本”...那么我们只好尝试在容器配置文件中添加一句命令，来解决这个 bug 了。&lt;/p&gt;

&lt;p&gt;将我们之前在容器配置文件中的命令进行升级：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- su lobsters -c 'gem update'
+ su lobsters -c 'gem update --system'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重新构建镜像，再次启动应用，会发现还是报相同的错误。&lt;/p&gt;

&lt;p&gt;再次围观官方说明，会发现这个 BUG 本质是 RubyGems 和 Bundler 团队的软件约定未安装预期执行，根据官方在“Why does this bug exist?”中的说明，推测还是得在 Gemfile.lock 中指定的 Bundler 软件版本。但是实际测试，不论是在 Gemfile.lock 中声明最初的 2.0.2，还是当前最新的 2.1.4，都无济于事。&lt;/p&gt;

&lt;p&gt;既然版本没有达到官方文件中提到的 Ruby 2.7.10，根据报错行为继续推测，会不会还是环境变量中未指定路径，或者 Bundler 参数的问题呢？&lt;/p&gt;

&lt;p&gt;在 &lt;a href="https://bundler.io/v2.0/man/bundle-install.1.html" rel="nofollow" target="_blank" title=""&gt;Bundler v2.0 官方文档&lt;/a&gt; 中找不到 &lt;code&gt;--user-install&lt;/code&gt; 参数说明，但是在 &lt;a href="https://bundler.io/doc/troubleshooting.html" rel="nofollow" target="_blank" title=""&gt;Troubleshooting common issues&lt;/a&gt;中有提到这个参数仅会将软件安装至用户目录。&lt;/p&gt;

&lt;p&gt;虽然我们在容器镜像构建时将 root 切换到 lobsters 用户，运行应用也使用的是该用户，但是说不定这个 2.7.0 版本就是根本不会读取运行用户路径下的软件呢？毕竟它身后还有至少 10 个修正版本。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su lobsters -c "gem install rake bundler --user-install"; \
su lobsters -c "gem update --system"; \

+ gem install rake; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在构建过程中添加一句使用 root 用户安装 rake 至全局的命令，再次构建镜像。这里不指定版本的原因上面已经说过。&lt;/p&gt;

&lt;p&gt;再次尝试启动镜像，一切顺利。&lt;/p&gt;

&lt;p&gt;但是优化升级，还没有结束，我们继续战斗。&lt;/p&gt;
&lt;h3 id="额外的小坑：Ruby 2.7.0 版本下 Rails 启动警告"&gt;额外的小坑：Ruby 2.7.0 版本下 Rails 启动警告&lt;/h3&gt;
&lt;p&gt;先说结论，这个问题官方&lt;a href="https://github.com/rails/rails/issues/38202" rel="nofollow" target="_blank" title=""&gt;正在解决&lt;/a&gt;。具体情况表现为，在应用启动时会报告类似下面的警告：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/lobsters/.bundle/ruby/2.7.0/gems/activerecord-5.2.4.1/lib/active_record/migration.rb:871: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想让警告消失，可以采用：&lt;a href="https://stackoverflow.com/questions/59491848/how-to-fix-railss-warning-messages-with-ruby-2-7-0" rel="nofollow" target="_blank" title=""&gt;How to fix Rails's warning messages with Ruby 2.7.0&lt;/a&gt; 提到的方法。&lt;/p&gt;

&lt;p&gt;不过个人不推荐使用非治本的方式解决问题，如果没有从本质解决问题，那么应该让问题继续暴露出来，提醒维护者后面处理掉它，而不是进行选择性遗忘。&lt;/p&gt;
&lt;h3 id="额外的小坑：lockfile 和 Bundler “打架”"&gt;额外的小坑：lockfile 和 Bundler“打架”&lt;/h3&gt;
&lt;p&gt;如果你尝试将 Bundle 指定版本降至 1.x 版本，会收到下面的错误。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You must use Bundler 2 or greater with this lockfile.
https://stackoverflow.com/questions/53231667/bundler-you-must-use-bundler-2-or-greater-with-this-lockfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于这个错误，&lt;a href="https://bundler.io/blog/2019/01/04/an-update-on-the-bundler-2-release.html" rel="nofollow" target="_blank" title=""&gt;官方也有解释&lt;/a&gt;，并提到："This bug was fixed in RubyGems 3.0.0 "。&lt;/p&gt;

&lt;p&gt;果然，升级到新版本才能解决这些边边角角的奇怪问题。&lt;/p&gt;
&lt;h2 id="第九回合：解决 Bundle 安装警告"&gt;第九回合：解决 Bundle 安装警告&lt;/h2&gt;
&lt;p&gt;第五回合结束时候，我们提到了 Bundle 的安装警告。&lt;/p&gt;

&lt;p&gt;虽然我们在容器中首次进行安装，不需要清理缓存，但是考虑到官方镜像潜在的 tricks，还是选择设置安装时不从缓存中读取内容稳妥些。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- su lobsters -c "bundle install --no-cache"; \

+ su lobsters -c "bundle config set no-cache 'true'"; \
+ su lobsters -c "bundle install"; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将配置文件参考上面的修改进行更新，再次构建镜像，这个构建过程中的安装警告果然消失了。&lt;/p&gt;
&lt;h2 id="第十回合：去掉对 Puma 的版本指定"&gt;第十回合：去掉对 Puma 的版本指定&lt;/h2&gt;
&lt;p&gt;第二回合在 Ruby 2.4.0 中，我们需要指定 Puma 版本，而在 Ruby 2.7.0 中，我们可以将这句显式声明的内容删除掉，比如像下面这样修改 Dockerfile。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su lobsters -c "bundle install"; \
- su lobsters -c "bundle add puma --version '~&amp;gt; 4.3.3'"; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么可以删除这条命令呢，因为在 2.7.0 的镜像容器中执行 &lt;code&gt;bundle list&lt;/code&gt; 会发现当前环境已经能够根据我们的文件声明正确安装依赖了：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle list | grep puma

* capistrano3-puma (4.0.0)
* puma (4.3.3)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次构建镜像，测试应用启动，一切正常。至此，在第六回合中我们提到的问题就都解决了。&lt;/p&gt;
&lt;h2 id="第十一回合：禁止安装非必要依赖"&gt;第十一回合：禁止安装非必要依赖&lt;/h2&gt;
&lt;p&gt;为了可维护性，去掉不必要的冗余“代码”是很必要的。在 Gemfile 里，开发工程师定义了 development 和 test 两个分组的依赖，因为容器运行在正式环境，可以避免安装这些依赖。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- su lobsters -c "bundle install"; \
+ su lobsters -c "bundle install --without=development,test"; \
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对比构建结果，可以看到构建体积从 382MB 降低到了 374MB。&lt;/p&gt;

&lt;p&gt;或许你会疑问，为什么不考虑在最初就禁用这些依赖呢？因为后续我们考虑开发环境也在容器中进行，所以需要保障带有开发依赖的配置也能够被正确初始化。&lt;/p&gt;

&lt;p&gt;至此，让 Lobsters 正常运行在 Ruby 2.7 版本的容器中就完成了。&lt;/p&gt;
&lt;h2 id="其他"&gt;其他&lt;/h2&gt;
&lt;p&gt;如果你使用云平台的数据库产品，记得对 lobsters 使用的连接账号进行合理的授权，赋予 &lt;code&gt;ALTER&lt;/code&gt; 等权限，避免应用启动时报错。&lt;/p&gt;

&lt;p&gt;如果你也使用阿里云，则需要先登陆管理后台，再登陆数据库后台对指定用户进行授权，默认的云控制台做的太简单了，不能完成需求。&lt;/p&gt;
&lt;h2 id="最后"&gt;最后&lt;/h2&gt;
&lt;p&gt;Ruby 的构建过程是真的慢，希望有朝一日，它能够学习 Node / NPM / YARN 将一些固定环境下的编译文件进行预编译，在用户进行初始化安装的时候，能够直接提供产物，为开发者行方便，开发者也会为你提供更多有价值的回馈。&lt;/p&gt;

&lt;p&gt;在写完这篇文章后，我对本地和服务器上进行了构建过程镜像清理，清理了大概 50 G 左右的过程产物。&lt;/p&gt;

&lt;p&gt;--EOF&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议，欢迎转载、或重新修改使用，但需要注明来源。 &lt;a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="nofollow" target="_blank" title=""&gt;署名 4.0 国际 (CC BY 4.0)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文作者：苏洋&lt;/p&gt;

&lt;p&gt;创建时间：2020 年 03 月 22 日
统计字数：15156 字
阅读时间：31 分钟阅读
本文链接：&lt;a href="https://soulteary.com/2020/03/22/dockerize-ruby-application-lobsters.html" rel="nofollow" target="_blank"&gt;https://soulteary.com/2020/03/22/dockerize-ruby-application-lobsters.html&lt;/a&gt;&lt;/p&gt;</description>
      <author>soulteary</author>
      <pubDate>Sun, 22 Mar 2020 12:54:52 +0800</pubDate>
      <link>https://ruby-china.org/topics/39641</link>
      <guid>https://ruby-china.org/topics/39641</guid>
    </item>
  </channel>
</rss>
