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

  • 5
    いいね
  • 0
    コメント

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のインストールのところで特に詰まって、普通にチュートリアルをするより時間がかかった。