Heroku Container Registryで作るDocker時代のRails 5 入門

  • 35
    いいね
  • 0
    コメント

この記事はRuby Advent Calendar 2016の5日目です。

昨日はiguchi1124さんの「Railsアプリケーションでより良いリソース設計を実装するためのテクニック」 でした。
明日は、tomokane@githubさんの記事になります。

はじめに

最近、個人的なプロダクトを久しぶりにRailsで書いています。

自分の場合、Rubyはテキスト処理とかスクレイピングとかそういったスクリプトで使う用途が多いので、
Railsをまともに触るのは実に2以来。そのせいで5を触るにあたって大分忘れてたことや時代に取り残されたところがありました。
あと、Herokuも本格的に使ったことは無かったのですが、今回Dockerに完全対応したということで使ってみたので、
Heroku Container Registryを使ったDockerによるRails環境について備忘録がてら、今年はまとめてみようと思います。

今回はWindowsで開発してますがDockerをベースとすることで、
Rubyの開発をWindowsで実施しても苦しまなかったのが良いことですね。

ターゲットアプリ

今回はDBへのCRUDをするだけのシンプルなTODOアプリを作ります。ええ、Scaffoldするだけです>-<
ActiveJobとHerokuの組み合わせが結構便利だったので面白そうだったんですが、長くなりそうだったので割愛。
そっちは、また今度書きます。

テーブルスキーマはこんな感じ。
alt

"command"に記載されているスクリプトを実行する形です。"status"には"未実行"|"実行中"|"完了"の三つを付けます。

環境

  • Windows 10
  • Rails 5
  • Heroku(Heroku Container Registry)
  • Docker-Toolbox
  • PostgreSQL

DockerでRails開発環境を作る

まずは、DockerでローカルにRails環境を作ります。
Dockerは入ってること前提にしますので、まだ入れてない人はDocker-Toolboxとかで入れてください。

Heroku Container Registryは別に特殊なコンテナをベースにする必要はないので、通常通りのDockerコンテナを使うことが出来きます。
今回はrubyイメージをベースにkoduki/railsを作って使っていますので、問題なければそのまま使ってもらえればと。

最初はGemfileを作ります。あとから上書こうと思うので、いったんこんな感じで。

source 'https://rubygems.org'
gem 'rails', '~> 5.0.0', '>= 5.0.0.1'

続いてプロジェクトの作成をします。

$ docker run -it -v `pwd`:/usr/src/app -w /usr/src/app koduki/rails rails new .
.
.
Overwrite /usr/src/app/Gemfile? (enter "h" for help) [Ynaqdh] Y

途中、Gemfileの上書きを確認されますが、これはYesにしておきます。
ボリュームをカレントディレクトリに指定し、そこをコンテナ上のワーキングディレクトリに指定しているので、
これでカレントディレクトリにRailsのプロジェクトが作られることになります。

それでは起動してみます。

$ docker run -it -p 3000:3000 -v `pwd`:/usr/src/app -w /usr/src/app koduki/rails bundle exec rails server -b 0.0.0.0
=> Booting Puma
=> Rails 5.0.0.1 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.6.2 (ruby 2.3.0-p0), codename: Sleepy Sunday Serenity
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

ポイントは-b 0.0.0.0で、これが無いとDocker-Toolbox等で動かしている時は、アクセス先IPがlocalhostじゃないとつながりません。
http://192.168.99.100:3000/などlocalhost以外のURLでアクセスしたい場合は、着けるのを忘れないようにしてください。

最後に、今回はPostgreSQLを使うのでGemfileに下記のようにpg gemを追加しておきます。

gem 'sqlite3'
gem 'pg'

これで、開発環境の準備は整いました。

実行用Webイメージの作成

開発だけなら上記の設定だけでできますが、引き続き実行用のDockerイメージを作成します。
アプリケーション名はexample-todoとしました。

下記のようなDockerfileをアプリケーションルート配下に作成します。

FROM koduki/rails

ENV APP_ROOT /usr/src/app
COPY . $APP_ROOT

EXPOSE  3000
CMD ["rails", "server", "-b", "0.0.0.0"]

続いてビルド。

$ docker build -t koduki/example-todo .

では、今度は実行用のイメージを動かしてみましょう。

$ docker run -it -p 3000:3000 koduki/example-todo
=> Booting Puma
=> Rails 5.0.0.1 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.6.2 (ruby 2.3.0-p0), codename: Sleepy Sunday Serenity
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000

ちゃんと、うごきましたね!

Herokuにデプロイする

では、ローカル環境で動いたところで、さっそくHerokuにデプロイしましょう。

Heroku Container Registryの使い方は公式のContainer Registry and Runtimeに詳しく乗っています。
ただ、私にはちょっと分かり難いところもあったので、もう少し補足を加えながらの説明をしていきます。

サインアップ

とにもかくにもサインアップ。
まだの人は、ここ https://api.heroku.com/signup からユーザー登録をしてください。

Heroku Toolbeltのインストール

こちらも導入してない人はHerokuのCommand Lineをインストールします。
昔みたいにGemじゃないので、Windowsで開発する場合には嬉しいですね。

https://devcenter.heroku.com/articles/heroku-command-line#download-and-install

Heroku Container Registryプラグインのインストール

お待たせしました。
ここからがHeroku Container Registryのための設定になります。

$ heroku plugins:install heroku-container-registry

こちらのコマンドを実行して、Heroku Container Registryプラグインのインストールをします。

Heroku アプリケーションの作成

プロジェクトのルートでheroku createコマンドを実行します。
デフォルトではアプリ名はランダムに作られますが、後から変更することもできます。

$ heroku container:login # 初回のみ
$ heroku create
Creating app... done, hidden-escarpment-87242
https://hidden-escarpment-87242.herokuapp.com/ | https://git.heroku.com/hidden-escarpment-87242.git

デプロイ

いよいよHerokuへのDockerコンテナのデプロイです。
Dockerfileがある場所でheroku container:push webを実行すると、
先ほどheroku createで作ったアプリケーションにデプロイされます。

$ heroku container:push web
Sending build context to Docker daemon 163.3 kB
Step 1 : FROM koduki/rails
 ---> c28f05cfaafb
Step 2 : EXPOSE 3000
 ---> Using cache
 .
 .
The push refers to a repository [registry.heroku.com/hidden-escarpment-87242/web]
 .
 .

これでデプロイが完了しました。

https://${your_app_id}.herokuapp.com/

で、アクセスすることが出来ます。

Heroku上のアプリをproductionモードにする

この時点ではHeroku上のアプリはdevelopmentモードになっています。
それでは色々と困るので、productionモードにするための方法を説明します。

Herokuはご存知の通りThe Twelve Factorsの元祖であり、III. 設定の原則に則のって設定は環境変数に記載されています。
おそらく、Dockerではなく通常のCedarであればデフォルトで設定されてると思われますが、Dockerの場合は自分で設定する必要があります。

まず、現在のHeroku上に指定されている環境変数を確認します。

$ heroku config
=== hidden-escarpment-87242 Config Vars

空であることが分かりますね。
続いて、Railsを本番環境で動かすための設定を下記に追加します。

$ heroku config:add RAILS_ENV=production
$ heroku config:add RACK_ENV=production
$ heroku config:add RAILS_SERVE_STATIC_FILES=enabled
$ heroku config:add RAILS_LOG_TO_STDOUT=enabled
$ heroku config:add LANG=en_US.UTF-8

再びconfigコマンド。

$ heroku config
=== hidden-escarpment-87242 Config Vars
LANG:                     en_US.UTF-8
RACK_ENV:                 production
RAILS_ENV:                production
RAILS_LOG_TO_STDOUT:      enabled
RAILS_SERVE_STATIC_FILES: enabled

追加されましたね!

最後にセッションキー等のシードに使われるSECRET_KEY_BASEを追加します。
これは通常コマンドで生成したものを指定するので下記のようにしました。

$ heroku config:add SECRET_KEY_BASE=$(docker run -v `pwd`:/usr/src/app -w /usr/src/app -it koduki/example-todo bundle exec rake secret)

なお、この値は秘密キーなので他の人に間違って共有しないように注意しましょう。

データベースの準備とCRUD画面の作成

とりあえず、動くようになったところでデータベースの作成とCRUD画面の登録をしましょう。

PostgreSQLの導入と設定

従来は開発環境ではデータベースはsqliteを使うことも多いと思いますが、今回は本番同様にPostgreSQLを使います。
postgresもDockerがあるので導入が簡単ですからね。sqliteとの差分を意識しながら作るよりこっちの方がずっと簡単だと思います。

というわけで、下記のようなdocker-compose.ymlを書きます。

version: '2'
services:
  db:
    image: postgres
  web:
    image: koduki/example-todo
    ports: 
      - 3000:3000
    volumes:
      - "$PWD:/usr/src/app"
    depends_on:
      - db

これでホスト名postgresでexample-todoからpostgresサーバにアクセスできるようになりました。

続いて、config/database.ymlを修正します。
HerokuではDATABASE_URLという環境変数に接続先情報を格納し、PGの修正をすることなく接続先のDBになっていているので、下記のように開発環境もプロダクション環境も同様に設定します。

development:
  url: <%= ENV['DATABASE_URL'] %>

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  url: <%= ENV['DATABASE_URL'] %>

production:
  url: <%= ENV['DATABASE_URL'] %>

これで、接続先は環境変数になりました。ではもう一度docker-compose.ymlを開いて設定ファイルを開いて環境変数に関しての追記をします。

version: '2'
services:
  db:
    image: postgres
  web:
    image: koduki/example-todo
    ports: 
      - 3000:3000
    volumes:
      - "$PWD:/usr/src/app"
    environment:
      - DATABASE_URL=postgres://postgres:mysecretpassword@db:5432/postgres
    depends_on:
      - db

あとは、下記のコマンドでdocker-composeを立ち上げればOKです

$ docker-compose up

ScaffoldでモデルとCRUDページを作る

DBの設定が終わったところで、実際のモデルとCRUDページを作成していきます。
RailsならまずはScaffoldですね。DBがpostgresなので同時に起動させる必要があるため、本来は下記のようにdocker-compose runで実行します。

$ docker-compose run web bundle exec rails generate scaffold todo title:string description:string

しかし、Windowsでは下記のような古から続くエラーが発生します。MacやLinuxを見習ってほしい...

ERROR: Interactive mode is not yet supported on Windows.
Please pass the -d flag when using `docker-compose run`.

あまり良い解決策はないので、シンプルにdockerコマンドを直接使うか、docker-composeで立ち上げたコンテナにdocker exec -itするかって形になります。
今回はpostgresとの環境変数の連携とかが色々あるので、あきらめてdocker execする方式にしてきます。

$ docker-compose up -d
Starting devdockerrails_database_1
Starting devdockerrails_web_1
$ docker-compose ps
          Name                         Command               State           Ports
-------------------------------------------------------------------------------------------
devdockerrails_database_1   /docker-entrypoint.sh postgres   Up      5432/tcp
devdockerrails_web_1        rails server -b 0.0.0.0          Up      0.0.0.0:3000->3000/tcp

$ docker exec -it devdockerrails_web_1 /bin/bash
root@2adf5610dc52:/usr/src/app#

root@2adf5610dc52:/usr/src/app# bundle exec rails generate scaffold todo title:string description:string
Running via Spring preloader in process 55
.
.
      invoke  scss                                                                                                                  
      create    app/assets/stylesheets/scaffolds.scss   

続いてテーブル作成。

root@2adf5610dc52:/usr/src/app# bundle exec rake db:migrate
== 20161205025803 CreateJobs: migrating =======================================
-- create_table(:jobs)
   -> 0.0035s
== 20161205025803 CreateJobs: migrated (0.0041s) ==============================

これでCRUDページが出来ました。
それではさっそくアクセスしてみましょう。

open http://192.168.99.100:3000/todos

無事動きました!

Heroku上でデータベースを動かす

では、ローカルでDBが動いたところで、さっそくHerokuに上げてみましょう。

$ docker build -t koduki/example-todo .
$ heroku container:push web
open https://${your_app_id}.herokuapp.com/todos

おそらく、エラーになったかと思います。これはHeroku上でデータベースの設定をしていないからです。
というわけで、Herokuでの設定をしていきます。

HerokuにPostgreSQLの追加

HerokuではAdd-onsとして簡単にデータベースとかKVSを追加することが出来ます。

002.png

  1. Herokuの管理画面から「Resouces」
  2. 「Add-ons」の検索バーで「heroku-postgresql」と入力
  3. Provision

これだけです。ね、簡単でしょ?
この時点で環境変数にも登録されています。heroku configを見てみましょう。

$ heroku config
=== limitless-tor-12820 Config Vars
DATABASE_URL:             postgres://xxx...
.
.

上記のように環境変数に登録されていることが分かりますね?

テーブルの作成

HerokuとPostgresの関連付けは出来たのでテーブルを行います。
下記のように実行することで、Heroku上のDBにテーブルを作成することが出来ます。

$ heroku run rake db:migrate
$ heroku restart

今度はちゃんとエラー無くアクセスできましたね?

open http://192.168.99.100:3000/todos

まとめ

Heroku Container Registryを使うことで素のDockerを利用しつつ、各種Add-onsの恩恵を受けれる環境を作ることが出来ました。

また、Rails開発するならやはりMacやLinux!ってのが今までの流れだったと思いますが、Dockerを今回のように使えばローカル環境にはRubyのインストールすら不要なので、Windowsでもはまりどころは随分減るのかな、と思います。

今回はページの都合で書かなかったのですが、RedisとSidekiqとActiveJobの組み合わせが結構良かったのでこれは別途記事を書こうと思います。asset周りも少し罠がありましたし。

それでは、来年もHappy Hacking!

参考