Posted at

RailsのToDoアプリチュートリアル(on Docker)

More than 1 year has passed since last update.

Ruby on Rails始めました。


概要


  • Ruby on Railsを始めました

  • どうやらsprocketsというのが初学者には曲者らしい。面倒なので、使わないことにした


  • docker-composeでJSビルド用nodeコンテナ、ファイル配信用nginxを置いて、RailsはAPIの提供だけをすることにした

  • 結構大変だった

リポジトリはこちら

KeitaMoromizato/rails_on_docker


構成


  • nginxがすべてのリクエストを受ける。静的ファイルはwwwボリュームに格納されたファイルを配信、APIへのリクエストはRailsコンテナへ

  • nodeコンテナがJavaScriptをビルドしてwwwボリュームにエクスポート

  • DBはMySQLを仕様。データはmysql_dataボリュームで永続化

docker.png


ハマリポイント


Gemのインストール先

Rubyのgem(nodeでいうnpm)はデフォルトのインストール先が、アプリケーションのディレクトリ内ではなくシステムのディレクトリになっている。これがDockerで動かすときに問題になる。

Dockerコンテナは、起動するたびに等価なものが起動するようにできている。つまり、コンテナ上のシステムディレクトリ(という表現でいいのか)にGemをインストールしたとしても、コンテナを立ち上げ直すとそれは無かったことになる。

ひとつの案としては、Dockerfile内でbundle installを実行、Gemもまとめてイメージに突っ込む方法がある。世のサンプルを見ても、こうしているケースが多いのだけれど、これだと開発中にGemを新しくインストールしようと思うとdocker-compose buildし直さないといけない。


Dockerfile

RUN \

bundle install

解決策としては、Gemをインストールするディレクトリを変更し、Dockerボリュームとして永続化するか、ホストPCのディレクトリにマウントして保存するかという手法が考えられる。

どうやらRails界隈では、アプリケーションディレクトリのvendor/bundleにGemのインストール先を変更するのが主流?らしい。という訳で、今回はマウントされたアプリケーションディレクトリのvendor/bundleに保存することにした。

Gemのインストール先を変更するには、bundle configコマンドを実行する。Dockerfile上にこれを記述することで、コンテナ上でbundle installするときのディレクトリが変わる。


Dockerfile

RUN \

bundle config --local path vendor/bundle

アプリケーションディレクトリは、docker-composeでマウントしている。


docker-compose.yml

  app:

build: .
volumes:
- .:/usr/src/rails_on_docker

ちなみに、bundle installをコンテナ上で実行するコマンドはこちら。appの部分は、docker-composeに記載されているコンテナ名。

$ sudo docker-compose run --rm app bundle install


CSRFの設定

RailsのAPI化は以下の手順で行った。


  1. レスポンスをJSON化するためにconfig/routes.rbdefaults: { format: 'json' }を追加

  2. CSRFトークンを無視する設定

  3. ページネーションのためにkaminariを導入

RailsはCSRF対策のため、CSRFトークンをチェックして不正なPOSTリクエストを弾いている。これは普通にRailsを書いていれば、Formにtokenが自動的に埋め込まれているため、特に気にすることはない。

ただし、API化する場合はリクエストヘッダに自分でtokenを埋めなければならない。もしsprocketsを使っているのなら、JSからCSRFトークンを取れるらしいが、今回はRailsとJSを完全に分離しているので、それはできない。

ということで調べた所、下記のようにCSRFトークンのチェックをスキップする処理を入れると良いらしい。


app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

protect_from_forgery with: :exception
skip_before_filter :verify_authenticity_token
end


手順


1. Railsアプリを作る

RailsアプリをDockerで開発するための手順を参考に、rails newまでを実施。


2. Dockerfileの準備

Railsアプリを動かすDockerイメージを作成。まだこなれていない。。。

FROM ruby:2.3

ENV APP_ROOT /usr/src/rails_on_docker

WORKDIR $APP_ROOT

RUN apt-get update && \
apt-get install -y nodejs mysql-client postgresql-client sqlite3 --no-install-recommends && \
rm -rf /var/lib/apt/lists/*

COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

RUN \
echo 'gem: --no-document' >> ~/.gemrc && \
cp ~/.gemrc /etc/gemrc && \
chmod uog+r /etc/gemrc && \
bundle config --global build.nokogiri --use-system-libraries && \
bundle config --global jobs 4 && \
bundle config --local path vendor/bundle && \
bundle install && \
rm -rf ~/.gem

COPY . $APP_ROOT

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


3. docker-composeのセットアップ


docker-compose.yml

version: '2'

services:
app:
build: .
command: rake unicorn:start
environment:
RAILS_ENV: development
DATABASE_URL: mysql2://root:pass@mysql:3306
MYSQL_ROOT_PASSWORD: 'pass'
ports:
- '3000:3000'
volumes:
- .:/usr/src/rails_on_docker
links:
- mysql
mysql:
image: mysql:5.7.10
environment:
MYSQL_ROOT_PASSWORD: 'pass'
ports:
- '3306:3306'
volumes:
- mysql-data:/var/lib/mysql
node:
image: node:6.9
command: npm run watch
ports:
- '1234:1234'
working_dir: /client
volumes:
- www:/client/out
- ./client:/client
nginx:
image: nginx
ports:
- '80:8080'
volumes:
- ./nginx:/etc/nginx/conf.d:ro
- www:/www/app:ro
links:
- 'app'
volumes:
mysql-data:
driver: local
www:
driver: local


4. railsアプリの初期設定

今回は、ModelひとつのみのToDoアプリを構築する。TaskモデルをRailsのscaffoldを使って作成する。

$ sudo docker-compose run --rm app rails generate scaffold Task content:text

ついでにgemのインストールと、DBのmigrateも実行。

$ sudo docker-compose run --rm app bundle install

$ sudo docker-compose run --rm app rake db:migrate


5. nginxコンテナの設定

nginx/default.confにあるファイルを参照。ルートとリソースへのアクセスは、静的ファイルをそのまま配信し、/apiへのアクセスはRailsアプリへフォワードする(/api/taskがRailsアプリ上からは/taskに見える)。


nginx/default.conf

server {

listen 8080;
server_name localhost;
root /www/app;

location /api/ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://app:3000/;
}

location = / {
index index.html;
}

location ~ /r/(.+\.(js|css|jpg|png))$ {
expires -1;
alias /www/app/$1;
}
}



6. nodeコンテナの設定

長いのでclientディレクトリを参照。

https://github.com/KeitaMoromizato/rails_on_docker/tree/master/client

Gemと同様に、npmのインストールはコンテナ上で。

$ sudo docker-compose run --rm node npm install


7. RailsアプリのAPI化


レスポンスのJSON化


config/routes.rb

Rails.application.routes.draw do

resources :tasks, defaults: { format: 'json' }


CSRF対策対策


app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

protect_from_forgery with: :exception
skip_before_filter :verify_authenticity_token
end


7. Railsアプリのページネーション

kaminariを使用するので、Gemfileに追記。


Gemfile

gem 'kaminari'


bundle installを実行してgemをインストール。

$ sudo docker-compose run --rm app bundle install

コントローラーにページネーションの設定を追記。


app/controllers/task_controller.rb

  def index

@tasks = Task.page(params[:page])
render json: @tasks.to_json(:except => [:url])
end


まとめ

Gemのインストールのところで特に詰まって、普通にチュートリアルをするより時間がかかった。