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
ボリュームで永続化
ハマリポイント
Gemのインストール先
Rubyのgem(nodeでいうnpm)はデフォルトのインストール先が、アプリケーションのディレクトリ内ではなくシステムのディレクトリになっている。これがDockerで動かすときに問題になる。
Dockerコンテナは、起動するたびに等価なものが起動するようにできている。つまり、コンテナ上のシステムディレクトリ(という表現でいいのか)にGemをインストールしたとしても、コンテナを立ち上げ直すとそれは無かったことになる。
ひとつの案としては、Dockerfile
内でbundle install
を実行、Gemもまとめてイメージに突っ込む方法がある。世のサンプルを見ても、こうしているケースが多いのだけれど、これだと開発中にGemを新しくインストールしようと思うとdocker-compose build
し直さないといけない。
RUN \
bundle install
解決策としては、Gemをインストールするディレクトリを変更し、Dockerボリュームとして永続化するか、ホストPCのディレクトリにマウントして保存するかという手法が考えられる。
どうやらRails界隈では、アプリケーションディレクトリのvendor/bundle
にGemのインストール先を変更するのが主流?らしい。という訳で、今回はマウントされたアプリケーションディレクトリのvendor/bundle
に保存することにした。
Gemのインストール先を変更するには、bundle config
コマンドを実行する。Dockerfile上にこれを記述することで、コンテナ上でbundle install
するときのディレクトリが変わる。
RUN \
bundle config --local path vendor/bundle
アプリケーションディレクトリは、docker-compose
でマウントしている。
app:
build: .
volumes:
- .:/usr/src/rails_on_docker
ちなみに、bundle install
をコンテナ上で実行するコマンドはこちら。app
の部分は、docker-compose
に記載されているコンテナ名。
$ sudo docker-compose run --rm app bundle install
CSRFの設定
RailsのAPI化は以下の手順で行った。
- レスポンスをJSON化するために
config/routes.rb
にdefaults: { format: 'json' }
を追加 - CSRFトークンを無視する設定
- ページネーションのために
kaminari
を導入
RailsはCSRF対策のため、CSRFトークンをチェックして不正なPOSTリクエストを弾いている。これは普通にRailsを書いていれば、Formにtokenが自動的に埋め込まれているため、特に気にすることはない。
ただし、API化する場合はリクエストヘッダに自分でtokenを埋めなければならない。もしsprocketsを使っているのなら、JSからCSRFトークンを取れるらしいが、今回はRailsとJSを完全に分離しているので、それはできない。
ということで調べた所、下記のようにCSRFトークンのチェックをスキップする処理を入れると良いらしい。
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のセットアップ
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
に見える)。
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
ディレクトリを参照。
Gemと同様に、npmのインストールはコンテナ上で。
$ sudo docker-compose run --rm node npm install
7. RailsアプリのAPI化
レスポンスのJSON化
Rails.application.routes.draw do
resources :tasks, defaults: { format: 'json' }
CSRF対策対策
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
skip_before_filter :verify_authenticity_token
end
7. Railsアプリのページネーション
kaminari
を使用するので、Gemfile
に追記。
gem 'kaminari'
bundle install
を実行してgemをインストール。
$ sudo docker-compose run --rm app bundle install
コントローラーにページネーションの設定を追記。
def index
@tasks = Task.page(params[:page])
render json: @tasks.to_json(:except => [:url])
end
まとめ
Gemのインストールのところで特に詰まって、普通にチュートリアルをするより時間がかかった。