This article is published under a SIGNATURE 4.0 International (CC BY 4.0) license. Signature 4.0 International (CC BY 4.0)
Author: Su Yang
Creation time: on March 22, 2020 statistical word count: 15156 words reading time: 31 minutes to read this article links: soulteary.com/2020/03/22/…
Ruby Application container encapsulates pit records (Lobsters)
Recently, I developed some functions of the community based on Lobsters. During the development process, I needed to carry out container configuration and deployment of the application, and experienced a typical software upgrade of the old Ruby version, during which I encountered many problems.
In this record, I hope to help students with the same needs.
Writing in the front
First answer why you should consider containerizing Ruby applications.
- For one thing, currently online applications must be delivered and run in a container. We register applications and provide services in a container.
- Secondly, I prefer and insist on using container scheme, which is convenient for rapid horizontal expansion. And most importantly, “code and commands are logged” for offline troubleshooting.
A typical Web application package goes through the following stages: integrating source code, installing application dependencies and environments, compiling programs/artifacts, adjusting permissions and directory structures, testing, and labeling images for version management.
The same is true of this one.
The beginning of the story
The encapsulation of application images started from a template style customization a year ago, when we referred to github.com/utensils/do… Encapsulate a set of mirror, because at that time did not rely on the official program to modify, so using this mirror program ran online safely for more than two months, until recently resumed work, the image file is written like this:
# Lobsters
#
# VERSION latestARG BASE_IMAGE = ruby: 2.3 - alpine the 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; \
exportPATH = / lobsters /. The 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 '~> 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 <[email protected]>" \
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. The gem/ruby / 2.3.0 / bin:$PATH"
# Expose HTTP port.
EXPOSE 3000
# Execute our entry script.
CMD ["/usr/local/bin/docker-entrypoint.sh"]
Copy the code
However, Gemfile/gemfile. lock will inevitably need to be updated due to lobsters user system interworking and other changes. The developers also adjusted Ruby version to 2.4.0.
Gemfile doesn’t have much of a change log:
diff --git a/Gemfile b/Gemfile
index 37f698d..ed43b5c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,6 +13,7 @@ gem "mysql2"
gem 'scenic'
gem 'scenic-mysql_adapter'
gem "activerecord-typedstore"
+gem 'jbuilder'
# js
gem "dynamic_form"@@-19,9 +22,9 @@gem"json"
gem "uglifier"."> = 1.3.0"
# deployment
-gem "actionpack-page_caching"
+gem "actionpack-page_caching"."~ > 1.1.1"
gem "exception_notification"
-gem "unicorn"
+gem "puma"."~ > 4.3.3." "
# security
gem "bcrypt"."~ > 3.1.2." "@@-42,6 +45,14 @@gem'transaction_retry' # mitigate https://github.com/lobsters/lobsters-ansible/
gem "scout_apm"."2.6.2"
+gem 'settingslogic'
+
+# for oauth2
+gem 'oauth2'
+gem 'whenever', require: false
+
+gem "paranoia"."~ > 2.2"
+
group :test, :development do
gem 'bullet'
gem 'capybara'@@-57,3 +68,17 @@group:test, :development do
gem "byebug"
gem "rb-readline"
end
+
+group :development do
+ gem 'web-console'.'> = 3.3.0'
+ gem 'spring'
+ gem 'spring-watcher-listen'.'~ > 2.0.0'
+ gem "ed25519" , "~ > 1.2", require: false
+ gem "bcrypt_pbkdf"."~ > 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
Copy the code
An additional point to note here is that gemfile. lock has changed bundle versions in addition to dependency updates:
BUNDLED WITH -2.0.2 + 1.17.3Copy the code
With all the basic concerns covered, let’s first use the Dockerfile mentioned above to build the image.
Round 1: Try upgrading Ruby 2.4.0
The first round reported a version incompatibility error while updating mirrored Ruby dependencies.
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 & RubyGems BigDecimal requires Ruby Version >= 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 & RubyGems io-console requires Ruby Version >= The current Ruby version is 2.3.8.459. Updating installed gems Updating bigdecimal Updating bundler Successfully Installing 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 & RubyGems Rdoc requires Ruby version >= 2.4.0. The Current Ruby version is 2.3.8.459.Copy the code
Considering that the actual runtime environment has been upgraded to Ruby 2.4, we need to modify the container configuration file here, and change BASE_IMAGE= Ruby :2.3-alpine to BASE_IMAGE= Ruby :2.4-alpine, The Path containing 2.3.0 in the image configuration file also needs to be updated to 2.4.0.
After that, we move on to the next battle.
Extra pit: official mirror path
Using the ruby -v command, we can clearly see that the actual version we are using is 2.4.9p362.
Docker run --rm it Ruby :2.4-alpine ruby -v ruby 2.4.9 P362 (2019-10-02 Revision 67824) [x86_64-linux-musl]Copy the code
However, when you check the local installation directory, you can see that the installation directory is 2.4.0. That is, the official image ignores the last bit of the corrected version number.
Docker run --rm it ruby:2.4-alpine ls /usr/local/ lib/ruby/site_ruby / 2.4.0Copy the code
So when writing the configuration, be careful not to include the modified version if it involves defining specific paths.
Round 2: Manually specify the Puma version
After upgrading the image to Ruby: 2.4-Alpine, after a long compile wait, you finally see the familiar “Bundle Complete! 53 Gemfile dependencies, 134 gems now installed.
I thought this would end happily, but then a classic error occurred, where the environment and actual dependency were inconsistent:
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 '\' '~ > 3.12.1'\'' '[!] There was an error parsing `injected gems`: You cannot specify the same gem twice with different version requirements. You specified: Puma (~> 4.3.3) and puma (~> 3.12.1). If you want to update the gem version, run `bundle update puma`. You may also need to change the version requirement specifiedin the Gemfile if it's too restrictive.. Bundler cannot continue. # from injected gems:1 # ------------------------------------------- > gem "puma", "~ > 3.12.1" # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code
Do you remember the su lobsters -c “bundle add puma –version ‘~> 3.12.1′” command in the container configuration file?
This command conflicts with gem “puma”, “~> 4.3.3” declared in the current application dependency configuration.
Change the command in the container configuration to ~> 4.3.3 for the next attempt.
Round 3: Manually specify the Rake version
After modifying the container environment, we packaged the image “smoothly”. The same error is reported, but it seems to be only because the software depends on the file declaration, should not affect the operation.
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 '\' '~ > 4.3.3'\'' '
Copy the code
Stubborn attempts to start the application reveal a new problem: a Rake task execution error.
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 `<top (required)>'
/lobsters/config/application.rb:1:in `require_relative'
/lobsters/config/application.rb:1:in `<top (required)>'
/lobsters/Rakefile:4:in `<top (required)>'/ lobsters /. The gem/gems/rake - 12.3.2 / exe/rake: 27:in `<top (required)>' (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!Copy the code
Following the clue, we add a command to the Dockerfile to enforce the rake software version of the task.
RUN gem install rake --version 13.0.1;
Copy the code
Keep trying new things.
Round 4: Complete the Ruby 2.4 software runtime environment
Fortunately, the software worked this time.
Puma starting in single mode...
* Version 4.3.3 (ruby 2.4.9-p362), codename: Mysterious Traveller
* MinThreads: 5, Max Threads: 5 * Environment: production * Listening on TCP :// 0.0.0.00:3000 Use Ctrl-c to stopCopy the code
Using the curl command, verify that the program is running correctly.
The curl https://127.0.0.1 - H"host:hub.lab.com" -I
HTTP/2 200
Copy the code
This would have been the end of it, but given the future maintainability of the application, we need to keep trying to upgrade the application.
After all, since the release of 2.4.x at the end of 2016, there have been a number of security fixes, mostly affecting older Versions of Ruby/RubyGems. I don’t want to be in 2020 maintaining a five-year-old software environment with a bunch of software dependencies that came out in an unknown year. And their potential inexplicable problems, and a bunch of known security risks.
Change ruby:2.4-alpine in Dockerfile to Ruby :2.7-alpine, remember to note the “path details” recorded in round 1, and try to build the image again.
Round 5: Try to upgrade the Ruby 2.7 runtime
Not surprisingly, there was a new problem.
+ 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 'The from/lobsters /. The gem/ruby / 2.7.0 / bin/bundle: 23:in `<main>'
Copy the code
Install –no-cache = install –no-cache = install –no-cache
+ su lobsters -c "bundle update --bundler"; \
+ su lobsters -c "Gem install bundler: 1.17.3."; \
su lobsters -c "bundle install --no-cache"; \
Copy the code
Build again and you’ll find that everything is fine except for the two warnings reported.
. Installing clean 1.0.0 Warning: The lockfile is being updated to Bundler 2, afterwhich you will be unable to returnto 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
Copy the code
As in round 4, verify that the application can start normally, indicating that the changes are correct.
However, there are still some problems, and we continue to optimize to resolve these “warnings” that should not exist and avoid other problems when the program runs.
Round 6: Upgrade Bundler to the appropriate version
So far we’ve done two things:
- Start Lobsters in the 2.4.x Ruby image
- Start Lobsters in a Ruby image for version 2.7.x
The remaining questions are:
- Try to upgrade To Bundler 1.7 (2015), which came out earlier than Ruby 2.4.x, to avoid all sorts of weird issues later on
- Try to address potential compatibility issues with various older dependencies, components such as Rake, PUMa…
The fundamental reason for the build images warning last time was that we made BUNDLED WITH 1.17.3 at the beginning of this article.
The recommended solution here is to refer to Node and NPM and choose a version of the tool that follows the time of the runtime, rather than a dead version of Hardcode.
In fact, the original image file, the default installation of the latest compatible Bundler gem.
. + 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...Copy the code
So in gemfile. lock, you can just delete BUNDLED WITH related version configurations, and you can also remove the install old Bundler command from Dockerfile that was added in the last round.
su lobsters -c "bundle update --bundler"; \
su lobsters -c "Gem install bundler: 1.17.3."; \
Copy the code
The test build was successful and the application was launched without problems.
Round 7: Upgrade Rake to the appropriate version
The next step is to solve the rake version problem, which, like Bundler, is best done without additional specification if necessary.
In addition to the third round where we had a rake version specified, the original image also had a rake version declared. So we first try to delete both declarations to test the mirror build:
. Installing rake 13.0.1...Copy the code
It looks like the default version of Rake is 13.0.1, which seems like a “load reduction success.” But when we started the app, we found a new problem: “Bundler couldn’t find commands to execute.”
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`
...
Copy the code
In the container image file, we have defined the bundle install –no-cache, so the error message is inaccurate. Presumably the problem here is missing rake dependencies. Add commands to the image file to install it.
. su lobsters -c"gem install bundler --user-install"; \
su lobsters -c "gem install rake"; .Copy the code
If I look at the error log again, I see the hidden logic: “The caller to Rake is Bundler.” So should I install Rake first and then Bundler?
Reverse the order of the preceding two commands or combine them into one. (Currently gem is installed sequentially, there is no “concurrent install mode”, so the following command is possible.)
su lobsters -c "gem install rake bundler --user-install";
Copy the code
Sure enough, the previous problem of not finding Rake was resolved, but a new problem arose.
Round 8: Explore the mystery of Bundler’s classic error
The new problem is a classic problem. The program reported an error as follows:
/usr/local/ lib/ruby / 2.7.0 / rubygems. Rb: 275:in `find_spec_for_exe': can't find gem rake (>= 0.a) with executable rake (Gem::GemNotFoundException)
from /usr/local/ lib/ruby / 2.7.0 / rubygems. Rb: 294:in `activate_bin_path'the from/lobsters /. The gem/ruby / 2.7.0 / bin/rake: 23: in ` < main >'.Copy the code
This problem is documented on the Official Bundler blog: Solutions for ‘Cant find gem bundler (>= 0.a) with Executable bundle’.
The bug is fixed in RubyGems 2.7.10 or 3.0.0 and above.
To repeat the trick, look at the ruby version in the current container image:
Docker run --rm it ruby:2.7-alpine ruby -v ruby 2.7.0p0 (2019-12-25 Revision 647EE6f091) [x86_64-linux-musl]Copy the code
Sure enough, the official image is “old”… Try adding a command to the container configuration file to fix this bug.
Update our previous commands in the container configuration file:
- su lobsters -c 'gem update'
+ su lobsters -c 'gem update --system'
Copy the code
Re-build the image and start the application again. The same error is reported.
According to the official “Why does this BUG exist?”, the software contract of RubyGems and Bundler team is not installed. , presumably the Version of Bundler software specified in gemfile. lock. However, the actual testing, whether declaring the original 2.0.2 in gemfile. lock or the current latest 2.1.4, does not help.
If Ruby 2.7.10 is not available in Ruby 2.7.10, the error may be caused by the unspecified path in the environment variable or by the Bundler parameter.
The –user-install parameter description is not found in the Official Bundler V2.0 documentation, but Troubleshooting common Issues notes that this parameter will only install the software in the user directory.
Although we switched root to lobsters when the container image was built and used to run the application, what if version 2.7.0 doesn’t read the software in the user’s path at all? After all, it has at least 10 revisions behind it.
su lobsters -c "gem install rake bundler --user-install"; \
su lobsters -c "gem update --system"; \
+ gem install rake; \
Copy the code
Add a command to install rake globally as user root to build the image again. The reason for not specifying a version here has already been mentioned.
Try again to start the image, all is well.
But optimization and upgrading, not over yet, we continue to fight.
Additional pothole: Rails startup warnings under Ruby 2.7.0
Conclusion first, this issue is being addressed officially. A warning similar to the following is reported when the application is started:
/ lobsters/bundle/ruby / 2.7.0 / gems/activerecord - 5.2.4.1 / lib/active_record/migration. The rb: 871: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the callCopy the code
If you want the warning to disappear, you can use How to fix Rails’ Warning Messages with Ruby 2.7.0.
However, I do not recommend using non-root causes to solve the problem. If the problem is not solved at its essence, it should be allowed to continue to expose the problem and remind the maintainer to deal with it later rather than selectively forgetting.
Bonus pit: Lockfile and Bundler ‘fight’
If you try to drop the specified Bundle version to 1.x, you will receive the following error.
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
Copy the code
“This bug was fixed in RubyGems 3.0.0 “.
Sure enough, upgrading to the new version solved these strange problems around the edges.
Round 9: Resolve Bundle installation warnings
At the end of round 5, we mentioned the Bundle installation warning.
Although we installed it for the first time in a container and didn’t need to clean the cache, it was safe to set the installation to not read from the cache, considering the potential tricks of the official image.
- su lobsters -c "bundle install --no-cache"; \
+ su lobsters -c "bundle config set no-cache 'true'"; \
+ su lobsters -c "bundle install"; \
Copy the code
Update the configuration file with reference to the changes above, build the image again, and sure enough, the install warning during the build has disappeared.
Round 10: Remove version designation for Puma
Round 2 In Ruby 2.4.0, we need to specify the Puma version, while in Ruby 2.7.0, we can remove this explicit declaration, such as modifying the Dockerfile as follows.
su lobsters -c "bundle install"; \
- su lobsters -c Bundle add puma --version '~> 4.3.3'"; \
Copy the code
The reason for removing this command is because executing the bundle list in the 2.7.0 image container will find that the current environment is already able to correctly install dependencies according to our file:
The bundle list | grep puma * capistrano3 - puma puma (4.0.0) * (4.3.3)Copy the code
The image is built again, the test application is started, and everything is fine. At this point, all the problems we mentioned in round 6 are solved.
Round 11: Disallow installing unnecessary dependencies
For maintainability, it is necessary to remove unnecessary redundant “code.” In Gemfile, the developer defines the dependency groups development and test, because the container runs in a formal environment and avoids installing these dependencies.
- su lobsters -c "bundle install"; \
+ su lobsters -c "bundle install --without=development,test"; \
Copy the code
Comparing the build results, you can see that the build size was reduced from 382MB to 374MB.
Why not, you might wonder, disable these dependencies in the first place? Since we are considering the development environment in the container, we need to ensure that the configuration with development dependencies is properly initialized as well.
At this point, getting Lobsters to run properly in a Ruby 2.7 container is complete.
other
If you are using the database products of the cloud platform, you should give proper authorization to the lobsters’ connection accounts, such as ALTER permissions, to avoid application startup errors.
If you are using Aliyun, you will need to log in to the admin background and then log in to the database background to authorize the specified user. The default cloud console is too simple to do this.
The last
Ruby’s build process is really slow, hopefully one day it will be able to learn Node/NPM/YARN to precompile some fixed environment compiled files, when the user does the initial installation, it will be able to directly provide the product, for the convenience of developers, developers will provide more valuable feedback for you.
After writing this article, I cleaned up the build process image, both locally and on the server, and cleaned up about 50 gigabytes of process artifacts.
–EOF
I now have a small toss group, which gathered some like to toss small partners.
In the case of no advertisement, we will talk about software, HomeLab and some programming problems together, and also share some technical salon information in the group from time to time.
Like to toss small partners welcome to scan code to add friends. (Please specify source and purpose, otherwise it will not be approved)
All this stuff about getting into groups