【Docker】Rails開発で知っておきたい!gemの永続化による作業効率アップの話

インフラ

こんにちは。Enjoy IT Life管理人の仁科(@nishina555)です。

前回、『Dockerのデータを永続化!Data Volumeの理解から始める環境構築入門』の記事でデータベースを例にとり、Dockerのデータの永続化について紹介しました。

Dockerのデータを永続化!Data Volume(データボリューム)の理解から始める環境構築入門

2019.08.17

データの永続化というと、データベース情報を指す場合が多いですが、アプリケーション開発を効率的に行うためにはデータベース情報以外にも永続化したほうがいい情報があります。
例えば、Ruby on Railsではインストールしたgemの一覧を永続化することで開発効率がグッとあがります。

しかし、Dockerの公式ドキュメント『Quickstart: Compose and Rails』もそうなのですが、RailsアプリをDocker化するチュートリアルでgemの永続化については省略されているケースも多くみられます。

そこで今回はデータボリュームを活用してgemの永続化をする手順、およびそのメリットについて紹介したいと思います。

今回は具体例としてRailsアプリのgemを利用しますが、『パッケージ管理ツールによってインストールされたライブラリ一覧を永続化する』というTIPSはRuby以外の言語でも活かせる知識だと思います。

ですので、Ruby以外の言語でアプリ開発をする場合もぜひ参考にしていただければと思います。

こんな人におすすめの記事です
  • ライブラリの永続化のメリットを理解したい
  • gemの永続化の手順について知りたい
  • サクサク開発できるDocekr環境を構築したい

データの永続化に対応したDocker環境構築手順

ここからは具体的なサンプルアプリを例にとり、データを永続化する方法について紹介をしたいと思います。

今回は『Ruby on RailsとMySQL』で構成されたDocker環境をサンプルとして利用します。永続化する対象のデータはインストールされたgemです。

なお、『Railsアプリの準備』の手順は『Dockerのデータを永続化!Data Volumeの理解から始める環境構築入門』で紹介した手順と同じですので、詳細についてはそちらの記事を参考にしてください。

Dockerのデータを永続化!Data Volume(データボリューム)の理解から始める環境構築入門

2019.08.17

上記の記事を既に読まれている方は『インストールしたgemを永続化をする』から読み始めてください。

Railsアプリの準備

まずは作業ディレクトリの作成・移動をします。

$ mkdir rails_app && cd rails_app

Docker環境のrubyを利用してローカル環境にGemfileを用意します。

$ docker run --rm -v `pwd`:/rails_app -w /rails_app ruby:2.6 bundle init

作成されたGemfileの『gem “rails”』の部分を修正します。

Gemfile

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

- # gem "rails"
+ gem 'rails', '~> 5.0.2'

次にDockerファイルを用意します。

Dockerfile

FROM ruby:2.6
WORKDIR /rails_app
COPY Gemfile* /rails_app/
RUN bundle install

上記のDockerファイルをビルドしてDockerイメージを作成します。

$ docker build -t rails_app .

これでrailsが実行できるDockerイメージが作成できたので、rails newでRailsアプリの雛形を作成します。

$ docker run --rm -v `pwd`:/rails_app rails_app rails new . -B --database=mysql

Ruby on Rails側のデータベース接続設定を行います。

config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
- password:
+ password: pass
- host: localhost
+ host: db

次にdocker-compose.ymlを作成します。

docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails_app
    depends_on:
      - db
  db:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
volumes:
  mysql_data:

Dockerファイルを以下のように編集し、Ruby on Railsが起動するDockerイメージの準備をしておきましょう。

Dockerfile

FROM ruby:2.6
+ RUN apt-get update -qq && apt-get install -y nodejs
WORKDIR /rails_app
COPY Gemfile /rails_app/Gemfile
RUN bundle install
+ CMD ["rails", "server", "-b", "0.0.0.0"]

Dockerファイルを修正したので、イメージをビルドしなおします。

$ docker-compose build

Railsアプリで利用するデータベースを作成します。

$ docker-compose run web rake db:create

以上でサンプルとなるRailsアプリの雛形の作成が完了しました。

インストールしたgemを永続化をする

今回はgem_dataという名前付きボリュームを作成し、インストールしたgemの永続化をしたいと思います。

まずは永続化の対象となるgemがインストールされるディレクトリについて確認をします。

gemのインストール先はgem environmentで表示される『INSTALLATION DIRECTORY』の項目で確認できます。

$ docker-compose run web gem environment

RubyGems Environment:
  (中略)
  - INSTALLATION DIRECTORY: /usr/local/bundle
  (中略)

gemのインストール先は/usr/local/bundleであることがわかったので、このディレクトリのデータの永続化を行います。

docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails_app
+     - gem_data:/usr/local/bundle
    depends_on:
      - db
  db:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
volumes:
  mysql_data:
+ gem_data:

永続化の確認

ここまででgemの永続化がされたDocker環境を構築することができました。

ここからは構築したDocker環境を実際に利用し、永続化のメリットなどについて紹介をしたいと思います。

まずはDockerコンテナを起動します。

$ docker-compose up

コンテナ起動後、新たにgemを追加してみます。今回は例としてransackを追加してみます。

Gemfile

+ gem 'ransack'

gemを追加後、bundle installを実行します。

$ docker-compose exec web bundle install

(中略)
Fetching ransack 2.3.0
Installing ransack 2.3.0
(中略)
Bundle complete! 16 Gemfile dependencies, 64 gems now installed.
Bundled gems are installed into `/usr/local/bundle`

ここで、一旦コンテナを削除します。

$ docker-compose down

もし、gemを永続化していない場合、上記のように『コンテナ上でgemをインストール → コンテナを削除』という手順をふむと、以下のようにコンテナ起動時にエラーが発生してしまいます。

エラーをみてみると、Bundler::GemNotFoundが発生しており、先ほどインストールしたはずのransackが見つからない状態であることがわかります。

$ docker-compose up

Starting rails_app_db_1 ... done
Starting rails_app_web_1 ... done
Attaching to rails_app_db_1, rails_app_web_1
(中略)
web_1  | /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:287:in `block in verify_gemfile_dependencies_are_found!': Could not find gem 'ransack' in any of the gem sources listed in your Gemfile. (Bundler::GemNotFound)
gemの永続化をしていない場合、コンテナ上でbundle installを実行してもDockerイメージにはgemの保存がされないためエラーが発生します。
エラーを防ぐにはDocekrイメージに新しく追加したgemを保存するためにビルドを実行する必要があります。

つまり、gemの永続化をしないとgemをインストールするたびに毎回『ビルド実行 → コンテナ起動』という操作をしなければいけません。

しかも、イメージをビルドしなおす場合、ゼロからgemをインストールすることになるので、全てのgemがインストールが完了するまでに時間がかかります。

一方、gemを永続化した場合、『コンテナ上でgemをインストール → コンテナを削除』という手順をふんでも、問題なく再度コンテナを起動することができます。

$ docker-comose up

実際にインストールされたgemの一覧を確認してみると、コンテナ削除前にインストールしたransackが含まれていることがわかります。

$ docker-compose exec web bundle list |grep 'ransack'

  * ransack (2.3.0)

gemの永続化をすることで、永続化をしていなかった時は毎回おこなっていたイメージのビルドをする必要がなくなるため、開発効率があがります。
つまり、『毎回ゼロからgemのインストールをしなくてすむ』という点がgemを永続化する理由です。

では、確認のために作成されたデータボリュームを調べてみます。

データボリュームはdocker volume lsで確認することができます。

実行結果をみると、gem_data(今回の場合はアプリケーション名のrails_appが頭についてrails_app_gem_data)のデータボリュームが作成されていることがわかります。

$ docker volume ls

DRIVER              VOLUME NAME
local               rails_app_gem_data
local               rails_app_mysql_data

docker inspectでデータボリュームの詳細を確認してみます。
『Labels』の項目を確認してわかる通り、rails_app(今回のサンプルアプリの名前)のデータボリュームであることがわかります。

$ docker inspect rails_app_gem_data

[
    {
        "CreatedAt": "2019-08-23T12:51:36Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "rails_app",
            "com.docker.compose.version": "1.24.1",
            "com.docker.compose.volume": "gem_data"
        },
        "Mountpoint": "/var/lib/docker/volumes/rails_app_gem_data/_data",
        "Name": "rails_app_gem_data",
        "Options": null,
        "Scope": "local"
    }
]

webコンテナの永続化された情報について確認してみます。

コンテナのマウント情報はdocker inspectの『Mounts』の項目で確認できます。

結果をみてわかる通り、/usr/local/bundleのディレクトリがrails_app_gem_dataという名前で永続化(データボリュームに保存)されていることがわかります。

$ docker inspect $(docker ps -f name=rails_app_db_1 -q) | grep -A 10 'Mounts'

        "Mounts": [
            {
                "Type": "volume",
                "Name": "rails_app_gem_data",
                "Source": "/var/lib/docker/volumes/rails_app_gem_data/_data",
                "Destination": "/usr/local/bundle",
                "Driver": "local",
                "Mode": "rw",
                "RW": true,
                "Propagation": ""
            },

まとめ: Dockerビルドを行う頻度を減らして開発効率をあげよう

以上でインストールされたgemの永続化をおこなったRuby on RailsのDocker環境の構築方法について紹介を終わります。

Dockerイメージのビルドは時間のかかる作業ですので、永続化などを活用してビルド回数を減らせると開発効率をあげる事ができます。

今回のまとめ
  • gem environmentコマンドでgemのインストール先が確認できる
  • gemのインストール先ディレクトリに対して名前付きボリュームを作成することでgemの永続化ができる
  • Dockerイメージのビルドをする機会を減らすことで開発効率をあげることができる

この記事がいいなと思いましたらツイッター(@nishina555)のフォローもよろしくお願いします!