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

この記事は[Ruby Advent Calendar 2016](http://qiita.com/advent-calendar/2016/ruby)の5日目です。

昨日はiguchi1124さんの[「Railsアプリケーションでより良いリソース設計を実装するためのテクニック」](http://qiita.com/iguchi1124/items/76d8880349511966a636) でした。
明日は、tomokane@githubさんの記事になります。

はじめに
==============

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

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

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


ターゲットアプリ
---------------

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

テーブルスキーマはこんな感じ。
![alt](http://yuml.me/diagram/plain/class/[Todo|title:string;description:string])

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

環境
---------------

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

DockerでRails開発環境を作る
=======================

まずは、DockerでローカルにRails環境を作ります。
Dockerは入ってること前提にしますので、まだ入れてない人は[Docker-Toolbox](https://www.docker.com/products/docker-toolbox)とかで入れてください。

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

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

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

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

```bash
$ 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のプロジェクトが作られることになります。

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

```bash
$ 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を追加しておきます。

```Gemfile
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"]
```

続いてビルド。

```bash
$ docker build -t koduki/example-todo .
```

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

```bash
$ 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://devcenter.heroku.com/articles/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`コマンドを実行します。
デフォルトではアプリ名はランダムに作られますが、後から変更することもできます。

```bash
$ 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`で作ったアプリケーションにデプロイされます。

```bash
$ 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](https://12factor.net/ja/)の元祖であり、[III. 設定](https://12factor.net/ja/config)の原則に則のって設定は環境変数に記載されています。
おそらく、Dockerではなく通常のCedarであればデフォルトで設定されてると思われますが、Dockerの場合は自分で設定する必要があります。

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

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

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

```bash
$ 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コマンド。

```bash
$ 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`を追加します。
これは通常コマンドで生成したものを指定するので下記のようにしました。

```bash
$ 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を書きます。

```yaml
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になっていているので、下記のように開発環境もプロダクション環境も同様に設定します。

```yaml
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を開いて設定ファイルを開いて環境変数に関しての追記をします。


```yaml
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です

```bash
$ docker-compose up
```

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

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

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

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

```bash
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`する方式にしてきます。

```bash
$ 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   
```

続いてテーブル作成。

```bash
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](https://qiita-image-store.s3.amazonaws.com/0/54904/60cf61bc-73ff-9d25-fd60-6ef2385a5adc.png)


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

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

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

```

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

テーブルの作成
----------------------------

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

```bash
$ 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!

参考
==========

- [Heroku Container Registry and Runtime](https://devcenter.heroku.com/articles/container-registry-and-runtime)
- [Quickstart: Compose and Rails](https://docs.docker.com/compose/rails/)