2017年5月12日金曜日

Rails5.1でdockerの開発環境を整える

環境

  • ruby 2.4.1
  • Rails 5.1.0(+Puma)
  • MySQL 5.7
  • Wercker CI
  • RSpec + FactoryGirl + Faker
  • Rubocop
  • Brakeman

Dockerfile


FROM ruby:2.4.1

ENV APP_ROOT /usr/src/app
ENV ENTRYKIT_VERSION 0.4.0

WORKDIR $APP_ROOT
EXPOSE 3000

## SSLのレポジトリをインストールできるようにします
RUN apt-get update && apt-get install -y apt-transport-https


## yarnのレポジトリをインストールします
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
## Nodejsのレポジトリをインストールします
RUN curl -sL https://deb.nodesource.com/setup_6.x | bash -

# 依存ライブラリをインストールします
RUN apt-get update && apt-get install -y \
      nodejs \
      mysql-client \
      postgresql-client \
      sqlite3 \
      yarn \
      --no-install-recommends && rm -rf /var/lib/apt/lists/*

# EntryKitのインストール
RUN apt-get install openssl \
  && rm -rf /var/cache/apk/* \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

# Rubyのライブラリをインストール
COPY Gemfile $APP_ROOT
COPY Gemfile.lock $APP_ROOT

RUN bundle install --jobs=4

# レポジトリの内容をコピーしておきます
COPY . $APP_ROOT

# サーバー起動前の処理を定義します
ENTRYPOINT [ \
  # 起動前準備スクリプトです。実行権限をつけます。
  "prehook", "chmod +x boot.sh", "--", \
  # 起動前準備スクリプトを実行します。
  "prehook", "bash ./boot.sh", "--"]

解説

アプリのルートディレクトリは/usr/src/appにしていますがこれは特に理由はありません。好みでOKです。

yarnのレポジトリがSSLになっているのですが、使っているイメージruby-2.4.1では下記のエラーが出てインストールできませんでした。
E: The method driver /usr/lib/apt/methods/https could not be found.
apt-transport-httpsを入れて解決してあります。
## SSLのレポジトリをインストールできるようにします
RUN apt-get update && apt-get install -y apt-transport-https

更にEntryKitを使って、サーバー起動前の処理(boot.sh)に繋いでいます。

boot.sh


# Rubyのバージョンを表示します。
ruby -v
# Rubyのライブラリをインストールします。
bundle install -j4
# フロントのライブラリをインストールします。
yarn install --no-optional
# 特定のサーバー・ポートが起動するまで待つスクリプトです。実行権限をつけます。
chmod +x wait-for-it.sh
# MySQL起動を待ちます
./wait-for-it.sh mysql:3306
# DBを更新します。
rake db:create
rake db:migrate
# railsサーバーが起動済だったら殺します
kill -s 9 `pgrep -f 'rails s' `
# pidファイルを消します
rm -f app/tmp/pids/server.pid

boot.shでは以下を行っています。
  • 依存ライブラリの解決
  • MySQLの起動待ち
  • DBの更新
  • rails sの二重起動防止(killspidファイルも消し、)

MySQLの起動待ちにはwait-for-itを使っています。(rootに配置)

docker-compose.yml



version: '3'
services:
  # Railsサーバー
  app:
    build: . # Dockerfileを元にビルドします
    environment:
      RAILS_ENV: development
      DATABASE_URL: mysql2://root:pass@mysql:3306
      PORT: 3000
    ports:
      - 3000:3000
    volumes:
      - .:/usr/src/app
      - bundle:/usr/local/bundle
    depends_on:
      - mysql
    command: ["rails", "s", "-b", "0.0.0.0"]

  # MySQL
  mysql:
    image: mysql:5.7.10
    environment:
      MYSQL_ROOT_PASSWORD: 'pass'
    ports:
      - 7207:3306
    volumes:
      - mysql-data:/var/lib/mysql
# グローバルにvolumesを書くとホストボリュームになり、ファイルを永続化できます。
volumes:
  mysql-data:
    driver: local
  bundle:
    driver: local

docker-composeのバージョン3からホストvolume(グローバルにvolumesを書く)を使ってファイルを永続化しています。
これによりbundle installにやり直しが発生しません。MySQLのデータも永続化している為、開発環境のデータが毎回クリアされることもありません。
また、rails5.1からPORT:3000と指定しないとデフォルトの9292ポートでpumaが起動してしまいます。(バグかは追っていません)

config/application.rb


require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module App
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.1

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.
    config.generators do |g|
      g.stylesheets false
      g.javascripts false
      g.helper false
      g.test_framework :rspec, view_specs: false, helper_specs: false, fixture: true
      g.template_engine = :slim
    end
  end
end


rails g(enerate) のコマンドの設定を書いています。
  • スタイルシート・JS・ヘルパーの生成を無効に(必要なファイルだけ後で生成します)
  • テストフレームワークはrspecに
  • テンプレートエンジンはslimに
※RSpecでテストを実行する前に、忘れずにrails generate rspec:installを実行してください。

Gemfile


source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.0'
# Use mysql2 as the database for Active Record
gem 'mysql2'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
gem 'therubyracer', platforms: :ruby

# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
# テンプレートエンジンにslimを使います
gem 'slim-rails'

# Use Capistrano for deployment
gem 'capistrano-rails', group: :development

group :development, :test do
  # セキュリティチェックのBrakemanを導入
  gem 'brakeman', :require => false
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # Fixtureの代替となるFactoryGirlを導入
  gem 'factory_girl_rails'
  # テストデータ生成のFakerを導入
  gem 'faker'
  # Adds Support for RSpec unit testing
  gem 'rspec-rails', '~> 3.5'
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '~> 2.13.0'
  gem 'selenium-webdriver'
  # CSチェック
  gem 'rubocop', require: false
end

group :development do
  # HTML/ERBからslimに変換します
  gem 'html2slim'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console', '>= 3.3.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]


Gemfileは日本語注釈がついたものを追加したのと、redis, bcryptあたりは後々使いそうだったのでコメントを外しています。

開発運用

docker-compose up -d(初回のみ)
docker-compose logs(うまく動かない時のログを見る)
docker-compose restart(gem等がうまくrailsサーバーに伝わらない時実行)
docker-compose down(コンテナを落とす)
docker-compose rm(イメージを消す)
docker-compose exec app bash (擬似的にシェルログインできます。アプリサーバーでコマンド実行 ※これで普通にrailsコマンドが実行できるようになります)

wercker.yml


# 下記を参考に作成しています
# http://qiita.com/masashi-sutou/items/d0fcec3a14c1c89ae702

# This references the default Ruby container from
# the Docker Hub.
# https://registry.hub.docker.com/_/ruby/
# If you want to use a specific version you would use a tag:
# ruby:2.2.2
box: ruby:2.4.1
# You can also use services such as databases. Read more on our dev center:
# http://devcenter.wercker.com/docs/services/index.html
# services:
    # - postgres
    # http://devcenter.wercker.com/docs/services/postgresql.html

    # - mongo
    # http://devcenter.wercker.com/docs/services/mongodb.html

services:
  - id: mysql
    env:
      MYSQL_ROOT_PASSWORD: root_password
      MYSQL_USER: test_user
      MYSQL_PASSWORD: test_password
      MYSQL_DATABASE: test_database

# This is the build pipeline. Pipelines are the core of wercker
# Read more about pipelines on our dev center
# http://devcenter.wercker.com/docs/pipelines/index.html
build:
    # Steps make up the actions in your pipeline
    # Read more about steps on our dev center:
    # http://devcenter.wercker.com/docs/steps/index.html
    steps:
        - install-packages:
            name: Install mysql client
            packages: mysql-client
        - rails-database-yml:
            service: mysql-docker
        - bundle-install:
            jobs: 4
        - script:
            name: Run RSpec
            code: bundle exec rspec
        - script:
            name: Run Brakeman
            code: bundle exec brakeman -qz

mysql clientをインストールする前にbundle installすると、bundle install時にリンクに失敗しているのか、libmysqlclientが見つからないエラーが出ます。
bundle installの結果は永続化されている為、2回目以降のビルドが高速です。

開発が進んだ後で、アプリのビルドイメージを作ってレポジトリに上げたものを使うようにwerckerの設定を修正する予定です。

参考記事・サイト