Rails アプリケーションをコンテナで開発しよう ! 第 2 回 - Rails 用のコンテナイメージを作ってみる
Author : トリ
Ruby on Rails といえば泣く子も黙る超有名 Web アプリケーションフレームワークで、数多くの利用事例があります。本連載ではそんな Rails アプリケーションをイチから作り、開発環境を整え、最終的に AWS のコンテナサービスを使って動かしていきます。が、連載のゴールはいきなりキレイなものを作ることではありません。世の中そんなに都合良くコトは運ばないのです。いかにも起きそうなエラーや浮かび上がってきた課題を必要に応じて解決しつつ、ちょっとずついい感じのコンテナ環境を作り上げていくことが本連載の目的です。すでにある程度の知識レベルにある方にとってはすんなりと物事が進んでいかなかったり、ガッツリアンチパターンを踏んでいる様子にヤキモキするかもしれませんが、優しい気持ちで見守りつつ、例えば同僚なんかと自分だったらどうするかをディスカッションしてみると良いかもしれません。
前回のおさらいと今回のゴール
前回 (第 1 回) はローカル環境を可能な限りキレイに保ちたいというちょっとだけ潔癖症気味なトリが、Ruby や Rails とその依存物をラップトップにインストールせずに rails new コマンドを実行できるようにし、ローカル環境でフレッシュな Rails アプリを動かせるようになるところまでを確認しました。
連載第 2 回となる今回も前回記事の続きに沿って Rails チュートリアルを進めますが、まずはローカル環境で Rails アプリの動作確認を簡単にするためのコンテナイメージを作成していきます。最終的に新しいページを Rails アプリに追加するところまでが今回のゴールです。
このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »
毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。
毎日毎日コンテナをセットアップするのって非生産的ですよね
前回は Ruby 2.5 コンテナを実行してコンテナの中でインタラクティブにコマンドを実行することでコンテナ内部で rails コマンドを実行できる環境を作りました。これからトリは Rails アプリの開発作業に着手していくわけですが、業務開始後に毎日あのコマンド群を実行してコンテナをセットアップするのは生産的とは言えません。日々の業務を快適に進めるために、まずは依存物が全てインストールされた状態の新しいコンテナイメージを作ります。
ひとまず前回実行したコマンド群をそのまま Dockerfile にまとめてみると以下のような感じでしょうか。
FROM ruby:2.5.0
# Node のインストール
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get install -y nodejs
# Yarn のインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt update && apt install -y yarn
# Rails のインストール
RUN gem install rails
前回の作業ディレクトリである {省略}/develop-rails-6-app-with-containers/my-super-awesome-blog に、上記を Dockerfile という名前でテキストファイルとして保存します。ちゃんと保存されているか確認してみましょう。
# 前回の作業ディレクトリにいることを確認
mac$ pwd
{省略}/develop-rails-6-app-with-containers/my-super-awesome-blog
mac$ cat Dockerfile
FROM ruby:2.5.0
# Node のインストール
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get install -y nodejs
# Yarn のインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt update && apt install -y yarn
# Rails のインストール
RUN gem install rails
作成した Dockerfile を利用してコンテナイメージをビルドしてみます !
mac$ docker build -t my-rails-image .
docker build という新しいコマンドが出てきました。コマンドのオプションでどんなことを指定しているのか、簡単に見てみましょう。
コマンドを実行すると以下のような出力を確認できるはずです。
Sending build context to Docker daemon 80.83MB
Step 1/7 : FROM ruby:2.5.0 # 注: 前回記事でダウンロードしてきた ruby:2.5.0 コンテナイメージがローカルにない場合はここであらためてダウンロードが実行されます
---> 896afe8ad24a
Step 2/7 : RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
---> Running in 0e9417456368
## omitted
Successfully tagged my-rails-image:latest
my-rails-image:latest という名前のコンテナイメージを作成できました ! このコンテナイメージに本当に rails コマンドがインストール済みなのか、コンテナを実行して試してみましょう。(第 1 回同様、以下の ffcc7536c7de という値はコンテナ実行時にランダムに割り当てられるコンテナ ID です。同じ手順を実行していても値が異なると思いますが、正常な挙動ですので気にせず進めて大丈夫です。)
# コンテナを実行します。前回記事で実行したのと同じようなコマンドですね。
mac$ docker run -it --rm my-rails-image bash
root@ffcc7536c7de:/#
root@ffcc7536c7de:/# which rails
/usr/local/bundle/bin/rails ## 👈 ちゃんと rails コマンドが存在していますね!
root@ffcc7536c7de:/#
root@ffcc7536c7de:/# exit
exit
mac$
素晴らしい。これで今後は apt install や gem install を毎回打たなくても済みそうな気がします。
前回作った Rails アプリを動かしてみる
依存物もろもろが入ったコンテナイメージができあがりましたので、実際にこのコンテナイメージを使って前回作ったアプリを動かしてみましょう。今回は実際にブラウザから動いている様子を確認してみたいと思います。
mac$ docker run -it \
-v $(pwd):/work \
-w /work \
-p 4649:3000 \
my-rails-image bash
第1回でも実行した “docker run” コマンドですが、見たことのないオプション “-p” が出てきましたね。確認しておきましょう。
コンテナが実行されたら念のため作業ディレクトリにアプリケーションのファイルがあることを確認し、前回同様 rails server コマンドを実行してみましょう。
root@546f6480d345:/work#
root@546f6480d345:/work# ls
Dockerfile app db postcss.config.js vendor
Gemfile babel.config.js lib public yarn.lock
Gemfile.lock bin log storage
README.md config node_modules test
Rakefile config.ru package.json tmp
root@546f6480d345:/work#
root@546f6480d345:/work# rails server
Could not find rake-13.0.1 in any of the sources
Run `bundle install` to install missing gems.
なんということでしょう。謎のエラーが出てしまいました。
* rake-13.0.1 が見つからない
* bundle install を実行して存在していない Gem をインストールしてください
なるほど、必要な Gem が入っていないということですね。
前回は bundle install なんてコマンド実行しなかったので最初に作った Dockerfile にもそんなコマンドは記述していません。なんでこんなことが起きるんだろうと思って調べていると、チュートリアルの rails new コマンドのところにちゃんと書いてありました。
This will create a Rails application called Blog in a blog directory and install the gem dependencies that are already mentioned in Gemfile using bundle install.
なるほど ! rails new コマンドを実行すると Rails アプリの作成に加えてアプリが依存している Gem のインストールまで bundle install コマンドで合わせて実行してくれていたんですね ! しょうがないのでここでは手で bundle install コマンドを実行しちゃうことにしましょう。
root@546f6480d345:/work# bundle install
# 記事公開後のどの時点で作業を進めているかによって bundler のバージョンについて警告が出るかもしれません。が、ここでは見なかったことにして進めましょう。
Fetching gem metadata from https://rubygems.org/............
Fetching rake 13.0.1
Installing rake 13.0.1
Fetching concurrent-ruby 1.1.6
Installing concurrent-ruby 1.1.6
Using i18n 1.8.5
## omitted
## (完了までそれなりに待ちます)
Bundle complete! 17 Gemfile dependencies, 74 gems now installed.
Bundled gems are installed into `/usr/local/bundle`
root@546f6480d345:/work#
それではあらためて rails server してみます。
root@546f6480d345:/work# rails server
=> Booting Puma
=> Rails 6.0.3.2 application starting in development
=> Run `rails server --help` for more startup options ## ここでちょっと待ち時間があります
Puma starting in single mode...
* Version 4.3.5 (ruby 2.5.0-p0), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
Use Ctrl-C to stop
前回記事同様に Rails サーバーが起動した気がします ! おもむろにブラウザを開いて http://127.0.0.1:4649 にアクセスしてみます。
いかがでしたでしょうか ?
そうですね。何も表示されませんでしたね。
Rails サーバーコマンドのヘルプを読んでみたところ、どうやら rails server で起動するサーバーはデフォルトではローカルホスト (つまり今回のケースではコンテナの中) からのアクセスしか受け付けないようです。
一度 Ctrl-C で Rails サーバーを停止し、コンテナの外からもアクセスできるようにして再起動してみましょう。rails server コマンドに --binding=0.0.0.0 オプションを付けて実行します。
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2020-09-01 08:46:36 +0000 ===
- Goodbye!
Exiting
root@546f6480d345:/work#
root@546f6480d345:/work# rails server --binding=0.0.0.0
=> Booting Puma
=> Rails 6.0.3.2 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.5 (ruby 2.5.0-p0), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000 ## なんだかさっきと表示が違いますね
Use Ctrl-C to stop
今度こそいけそうな気がします!ブラウザで http://127.0.0.1:4649 にアクセスしてみます !
うおー !!! ちゃんと動作しているのが確認できましたね !!!
なんだか非常にすっきりしたので、Rails サーバーを停止してコンテナからも抜けちゃいましょう。
Started GET "/" for 172.17.0.1 at 2020-09-01 08:53:19 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
(1.1ms) SELECT sqlite_version(*)
Processing by Rails::WelcomeController#index as HTML
Rendering /usr/local/bundle/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb
Rendered /usr/local/bundle/gems/railties-6.0.3.2/lib/rails/templates/rails/welcome/index.html.erb (Duration: 7.3ms | Allocations: 458)
Completed 200 OK in 21ms (Views: 14.9ms | ActiveRecord: 0.0ms | Allocations: 2571)
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2020-09-01 09:03:53 +0000 ===
- Goodbye!
Exiting
root@546f6480d345:/work# exit
exit
mac$
毎回 bundle install するの...?
鋭いですね。確かにいま手元にあるコンテナイメージには Rails アプリそのものの依存ライブラリが含まれていません。 rails server コマンドを実行した時にエラーが出てましたね。とすると、このコンテナを実行した後は毎回 bundle install して待たなきゃいけないということでしょうか...?毎日毎日依存ライブラリのインストールに午前中を奪われていては仕事もなにもあったものではありません。毎回 bundle install しなくても済むようにして、生産性を爆上げしちゃいましょう。
さて、毎回 bundle install しなくても済むようにする方法はいろいろ思いつきそうですが、今回はとりあえず動けばいいでしょ、な精神でやってしまうことにします。
今まさに手元で動かしているコンテナは bundle install で入った依存ライブラリをすでに中に持っていますよね?それならこのコンテナをそのままコンテナイメージとして固めてしまえばいいのでは ? と思うのが人の心というものです。固めてしまいましょうそうしましょう。
まずはさきほど停止したコンテナを探します。
mac$ docker ps --all
過去に他のコンテナを実行したことがある場合は複数が並んでいる出力が得られるかもしれません。また、"CONTAINER ID” や “NAMES” など、以下の出力とは異なるかもしれませんが、“IMAGE” が “my-rails-image” となっている行を見つけましょう。見つけたら “CONTAINER ID” (ここでは 546f6480d345) をクリップボードにコピーしておきましょう。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
546f6480d345 my-rails-image "bash" 5 minutes ago Exited (0) About a minute ago musing_pascal
コピーしたら次のコマンドをおもむろに実行してください。
mac$ MY_CONTAINER_ID=546f6480d345 ## 先ほどコピーしたご自身の CONTAINER ID を利用してください
mac$ docker commit ${MY_CONTAINER_ID} my-bundled-rails-image
sha256:20a516549889327e5a7b28a407008391991ec4048e41da0a1c48a3a00f54aba4 ## CONTAINER ID 同様、値は異なりますがそれが正常な挙動です
mac$
新しいコマンド “docker commit” が出てきました。何をやっているのか確認してみましょう。
それでは、実際にコンテナイメージが作成されているかを確認しましょう。コンテナイメージが意図したとおりに作成できていたら、そのコンテナイメージを使ってあらためてコンテナを実行し、 bundle install コマンドを実行しなくても Rails サーバーが起動するかを確認していきます。これまでは bash を起動してから rails server コマンドを実行していましたが、今回はいきなり rails server コマンドを実行してみます!
mac$ docker images
## 過去に Docker を利用したことがある場合は出力内容が異なる可能性がありますが、
## "my-bundled-rails-image" が存在するかどうかを確認してください :)
REPOSITORY TAG IMAGE ID CREATED SIZE
my-bundled-rails-image latest 20a516549889 2 minutes ago 1.22GB
my-rails-image latest abd377c3e786 About an hour ago 1.04GB
ruby 2.5.0 213a086149f6 2 years ago 869MB
mac$
## 新しく作ったコンテナイメージからコンテナを実行し、Rails サーバーも合わせて起動する
mac$ docker run -it --rm \
-v $(pwd):/work \
-w /work \
-p 4649:3000 \
my-bundled-rails-image rails server --binding=0.0.0.0
=> Booting Puma
=> Rails 6.0.3.2 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.5 (ruby 2.5.0-p0), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop ## 起動している気がします!!!
さきほどと同様、 http://127.0.0.1:4649 にブラウザでアクセスして Rails サーバーが期待通り動いているか確認してみましょう。ターミナル側にはアクセスログの出力が、ブラウザには Ruby on Rails の Welcome ページが表示されたでしょうか ?
これで毎朝 bundle install しなくても docker run だけで Rails サーバーを起動できる気がしますね ! スッキリした気持ちで Rails サーバーを停止してみましょう。停止後、さきほどは exit コマンドを実行して明示的に bash を終了させていましたが、今回は Rails サーバーを停止すると自動的にコンテナからも抜けるはずです。
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2020-09-01 09:50:23 +0000 ===
- Goodbye!
Exiting
mac$
前回の記事で実施していたチュートリアルの続きを進めます
実は前回の記事では チュートリアル 4.1 - Starting up the Web Server まで進めたところで終わっていました。ここに到るまでにだいぶ遠回りしましたが、本記事では最後に チュートリアル 4.2 - Say “Hello”, Rails を進めていきましょう。
チュートリアルによれば、次は rails generate contoller というコマンドを使ってこの Rails アプリのトップページ (さっきブラウザで見たやつ) を新しいトップページに置き換えるようです。
以下のコマンドをターミナルで実行し rails generate controller コマンドを実行してみましょう。
mac$ docker run -it --rm \
-v $(pwd):/work \
-w /work \
my-bundled-rails-image rails` generate controller ``Welcome`` index`
`## そこそこ待ちます``
Running via Spring preloader in process 139
create app/controllers/welcome_controller.rb
route get 'welcome/index'
invoke erb
create app/views/welcome
create app/views/welcome/index.html.erb
invoke test_unit
create test/controllers/welcome_controller_test.rb
invoke helper
create app/helpers/welcome_helper.rb
invoke test_unit
invoke assets
invoke scss
create app/assets/stylesheets/welcome.scss
mac$ `
今回は何かを停止しなくてもコマンド終了とコンテナも終了して macOS ターミナル側にすんなり戻ってきたようです。
そして何やらいろいろとファイルが生成されたようです。macOS Finder (あるいはお使いのファイルマネージャー) で作業ディレクトリを開いて、本当にファイルが生成されてたのかちょっと確認してみましょう。
## 現在の作業ディレクトリを macOS Finder で開く
mac$ open .
さきほどの rails generate controller コマンドの出力に、いくつもの create ... のような行があったことにお気付きの方もいるかもしれませんが、そこに書いてあったファイル名のファイルが実際に存在しているはずです。確認できましたか ?
では次にチュートリアルで指定されているとおり、 app/views/welcome/index.html.erb ファイルをテキストエディタで開き、中身を <h1>Hello, Rails!</h1> に書き換えます。
それではあらためて Rails サーバーを起動し、実際にこのページがブラウザで表示されるかどうか確認してみましょう。
mac$ docker run -it --rm \
-v $(pwd):/work \
-w /work \
-p 4649:3000 \
my-bundled-rails-image rails server --binding=0.0.0.0
rails generate controller コマンドで追加した Welcome ページは http://127.0.0.1:4649/welcome/index にアクセスすることで表示できます。
うおー !!! ちゃんと期待通りに動いてますね !!! 🎉🎉🎉
ローカルで動くようになったら AWS 上でも動かしてみたくなるのが人の心というものなので、次回はこの Rails アプリケーションを実際に AWS 環境に一度デプロイし、動作確認が済んだら (お金がもったいないので) 削除するところをやってみます。お楽しみに!
筆者プロフィール
トリ
Sr. Product Developer Advocate, Containers Product, Amazon Web Services
Twitter が大好きです。
AWS を無料でお試しいただけます