LoginSignup
63
63

More than 3 years have passed since last update.

Rails newからproductionモードで動くようになるまで

Last updated at Posted at 2019-09-18

Rails完全に理解した!ドン!!
スクリーンショット 2019-09-12 20.05.30.png

または、scaffoldでCRUDが一通りできた!!ドン!!!!
スクリーンショット 2019-09-14 23.09.33.png

で満足していませんか?

Webアプリを作ったら最終的にはproductionで動かすと思うので、(たとえ練習であっても)developmentで動かして終わるのではなくproductionでも動かしてみるのが良いです。
RAILS_ENV=developmentで動いてもproductionにしたら動かないなんてことはざらにあります。
そこで、この記事ではRailsをproductionで動かすところまでやってみたいと思います。

この記事でやること

RailsをRAILS_ENV=productionで動かします。
空のRailsでやっても面白くないので、Webアプリを作るのであればやると思われるアセット管理やデータベース操作なども実施したいと思います。
具体的には下記を実施します。

  • rails new
  • ソース管理(GitHub)
  • 実行環境(Docker)
  • データベース操作(MySQL)
  • アセット管理(webpacker)

構成

versions

  • Ruby 2.6.3
  • Rails 6.0.0
  • Docker desktop 2.1.0.2
  • docker-compose 3.5
  • MySQL 5.7.27
  • nginx 1.17.3

インフラ

動作環境はdevelop, production共にローカル環境のDocker上で動かします。
環境ごとの構成は次の通り。

development

webとdbという名前の2つのコンテナを立てます。
webではRailsを実行します。アプリケーションサーバーとwebサーバーの役割を持っており、クライアントがRailsアプリへアクセスするときはこのサーバーへ直接リクエストを送信します。
dbではMySQLを実行します。
スクリーンショット 2019-09-17 12.16.17.png

production

productionでは負荷などの観点からアプリケーションサーバーとWebサーバーを分けることが一般的です。それを実現するためにWebサーバー(nginx)を別コンテナで立てています。
クライアントはnginxにアクセスし、jsなどの静的ファイルはそのまま返却し、動的処理はアプリケーションサーバーへリクエストを流します。
スクリーンショット 2019-09-17 12.16.24.png

開発

ここからは実際に開発していきます。

リポジトリ作成

ソース管理は必須だと思うのでまずはリポジトリを作りましょう。
GitHubを使います。
スクリーンショット 2019-09-12 14.14.08.png

リポジトリをローカルにクローンします。

$ git clone git@github.com:ham0215/rails_in_production.git
Cloning into 'rails_in_production'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
$ cd rails_in_production/
$ ls
LICENSE

Docker(develop)

最終目標はproductionで動かすことですが、まずはdevelopmentで動くようにします。
実行環境のDockerfile、docker-composeを作成します。

Dockerfile

動かすだけなら必須ではない(むしろproductionには入れない方がいい)ですが、開発しやすいようにvimやlocalesなどを入れています。

Dockerfile
FROM ruby:2.6.3

RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
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
RUN apt-get update && apt-get install -y \
    build-essential \
    nodejs \
    yarn \
    vim \
    locales \
    locales-all \
    mysql-client \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*

ENV LANG ja_JP.UTF-8

RUN mkdir -p /app
WORKDIR /app

COPY Gemfile Gemfile.lock ./

RUN gem update bundler
RUN bundle install
COPY . .
RUN yarn install --check-files

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

docker-compose.yml

Railsを動かす'web'とMySQLを動かす'db'を定義します。

docker-compose.yml
version: '3.7'
services:
  web:
    build: .
    command: bundle exec rails s -b 0.0.0.0
    volumes:
      - .:/app:cached
    ports:
      - 3000:3000
    depends_on:
      - db
    tty: true
    stdin_open: true
  db:
    image: mysql:5.7.27
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
  db-volume:

rails new

それでは新規Railsを作成しましょう。
rails newコマンドもDocker内で実行したいのでまずはDockerを起動します。
Dockerfileも記述しているCOPY Gemfile Gemfile.lock ./を成功させるためにファイルを作成します。

Gemfile
source 'https://rubygems.org'

gem 'rails', '6.0.0'

Gemfile.lockは空ファイルを作成

$ touch Gemfile.lock

準備ができたのでDockerを起動してrails newを実行、、、の前にコマンドのヘルプを見ておきましょう。
rails newの時点でいらない機能は抜いておかないと後から消すのは面倒です。
オプションはバージョンごとに結構異なるのできちんと確認しましょう。

$ docker-compose run web bundle exec rails new --help
Usage:
  rails new APP_PATH [options]

Options:
      [--skip-namespace], [--no-skip-namespace]              # Skip namespace (affects only isolated applications)
  -r, [--ruby=PATH]                                          # Path to the Ruby binary of your choice
                                                             # Default: /usr/local/bin/ruby
  -m, [--template=TEMPLATE]                                  # Path to some application template (can be a filesystem path or URL)
  -d, [--database=DATABASE]                                  # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/frontbase/ibm_db/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
                                                             # Default: sqlite3
      [--skip-gemfile], [--no-skip-gemfile]                  # Don't create a Gemfile
  -G, [--skip-git], [--no-skip-git]                          # Skip .gitignore file
      [--skip-keeps], [--no-skip-keeps]                      # Skip source control .keep files
  -M, [--skip-action-mailer], [--no-skip-action-mailer]      # Skip Action Mailer files
      [--skip-action-mailbox], [--no-skip-action-mailbox]    # Skip Action Mailbox gem
      [--skip-action-text], [--no-skip-action-text]          # Skip Action Text gem
  -O, [--skip-active-record], [--no-skip-active-record]      # Skip Active Record files
      [--skip-active-storage], [--no-skip-active-storage]    # Skip Active Storage files
  -P, [--skip-puma], [--no-skip-puma]                        # Skip Puma related files
  -C, [--skip-action-cable], [--no-skip-action-cable]        # Skip Action Cable files
  -S, [--skip-sprockets], [--no-skip-sprockets]              # Skip Sprockets files
      [--skip-spring], [--no-skip-spring]                    # Don't install Spring application preloader
      [--skip-listen], [--no-skip-listen]                    # Don't generate configuration that depends on the listen gem
  -J, [--skip-javascript], [--no-skip-javascript]            # Skip JavaScript files
      [--skip-turbolinks], [--no-skip-turbolinks]            # Skip turbolinks gem
  -T, [--skip-test], [--no-skip-test]                        # Skip test files
      [--skip-system-test], [--no-skip-system-test]          # Skip system test files
      [--skip-bootsnap], [--no-skip-bootsnap]                # Skip bootsnap gem
      [--dev], [--no-dev]                                    # Setup the application with Gemfile pointing to your Rails checkout
      [--edge], [--no-edge]                                  # Setup the application with Gemfile pointing to Rails repository
      [--rc=RC]                                              # Path to file containing extra configuration options for rails command
      [--no-rc], [--no-no-rc]                                # Skip loading of extra configuration options from .railsrc file
      [--api], [--no-api]                                    # Preconfigure smaller stack for API only apps
  -B, [--skip-bundle], [--no-skip-bundle]                    # Don't run bundle install
  --webpacker, [--webpack=WEBPACK]                           # Preconfigure Webpack with a particular framework (options: react, vue, angular, elm, stimulus)
      [--skip-webpack-install], [--no-skip-webpack-install]  # Don't run Webpack install

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

Rails options:
  -h, [--help], [--no-help]        # Show this help message and quit
  -v, [--version], [--no-version]  # Show Rails version number and quit

Description:
    The 'rails new' command creates a new Rails application with a default
    directory structure and configuration at the path you specify.

    You can specify extra command-line arguments to be used every time
    'rails new' runs in the .railsrc configuration file in your home directory.

    Note that the arguments specified in the .railsrc file don't affect the
    defaults values shown above in this help message.

Example:
    rails new ~/Code/Ruby/weblog

    This generates a skeletal Rails installation in ~/Code/Ruby/weblog.

databaseはmysqlを指定。必要ないものは--skip-xxxでスキップします。
今回のコマンドは下記の通り。

$ docker-compose run web bundle exec rails new . --database=mysql --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-active-storage --skip-spring --skip-turbolinks --skip-bootsnap --skip-test --force

rails newが成功したらファイルが一通り生成されていると思うのでコミットしておきましょう。
README.mdや.gitignoreもRails用に更新されているので必要に応じて更新しておきましょう。
ちなみに私はvimを使っているので、.gitignoreに*.swptagsは必ず追加します。

一通り揃ったところで一度dockerをビルドしてサーバーを起動してみます。-dでバックグランドで起動しています。
成功したらdocker container lsでwebとdbのコンテナが表示されるはずです。

$ docker-compose up --build -d
・・・
$ docker container ls
CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS                                NAMES
3dbc395255eb        rails_in_production_web          "bundle exec rails s…"   6 seconds ago       Up 5 seconds        0.0.0.0:3000->3000/tcp               rails_in_production_web_1
443d1337b008        mysql:5.7.27                     "docker-entrypoint.s…"   28 hours ago        Up 16 minutes       3306/tcp, 33060/tcp                  rails_in_production_db_1

起動できたらhttp://localhost:3000 にアクセスしてみましょう。
スクリーンショット 2019-09-13 19.05.19.png

エラーが発生しました。MySQLに接続できていないようですね。
ということでMySQLの設定に進みます。

MySQL

まずは接続情報を修正しましょう。
hostにdocker-composeでつけたMySQLコンテナの名前dbがhost名として使えるので修正します。
usernameやpasswordはroot/(なし)にしているのでそのままでOKです。

--- a/config/database.yml
+++ b/config/database.yml
@@ -15,7 +15,7 @@ default: &default
   pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
   username: root
   password:
-  host: localhost
+  host: db

続いてDBを作成します。

$ docker-compose exec web bundle exec rake db:create
Created database 'app_development'
Created database 'app_test'

ここまでできたら再度 http://localhost:3000/ にアクセスしてみましょう。
無事、Yay! You’re on Rails!が表示されたら成功です。

スクリーンショット 2019-09-14 22.59.26.png

何もテーブルがないと面白くないのでユーザーを管理するテーブルを作ります。
サンプルなのでscaffoldを使ってサクッと一式作ります。

$ docker-compose exec web bundle exec rails g scaffold user name:string
      invoke  active_record
      create    db/migrate/20190914140349_create_users.rb
      create    app/models/user.rb
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
      create      app/views/users/edit.html.erb
      create      app/views/users/show.html.erb
      create      app/views/users/new.html.erb
      create      app/views/users/_form.html.erb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke    jbuilder
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder
      create      app/views/users/_user.json.jbuilder
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/users.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss
$ docker-compose exec web bundle exec rake db:migrate
== 20190914140349 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0384s
== 20190914140349 CreateUsers: migrated (0.0389s) =============================

usersテーブルが作成できました。一旦コミットしておきましょう。
これで http://localhost:3000/users にアクセスするとusersのCRUDが一通りできるようになりました。

Webpaker

Rails6からはWebpackerが標準で入るようになったのでjsとcssの管理をそちらに寄せます。
config/webpacker.ymlを見るとソースパスがapp/javascriptとなっていますが、scaffoldではscssがapp/assetsに作られているのでapp/javascript配下に移動します。

$ mv app/assets/stylesheets app/javascript/

cssもwebpackerの対象となるようにapp/javascript/packs/application.jsでcssをimportして、cssを読み込んでいる箇所をWebpackerで生成したcssを読み込むように修正します。
layouts/applicationでusers.scssなど個別のcssまで読み込んでいて微妙ですが、現状ではusersしかないのでとりあえずこれでいきます。
こういう必須じゃないけど微妙だなーと感じるところはissueとして残しておいて、後々対応していきましょう(この記事では対応しません。。。)

diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 239b600..459fc07 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -13,3 +13,6 @@ require("channels")
 //
 // const images = require.context('../images', true)
 // const imagePath = (name) => images(name, true)
+import '../stylesheets/application.css'
+import '../stylesheets/scaffolds.scss'
+import '../stylesheets/users.scss'
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index b0772fa..c3f192a 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -5,7 +5,7 @@
     <%= csrf_meta_tags %>
     <%= csp_meta_tag %>

-    <%= stylesheet_link_tag 'application', media: 'all' %>
+    <%= stylesheet_pack_tag 'application', media: 'all' %>
     <%= javascript_pack_tag 'application' %>
   </head>

再度/usersを開いてデザインが効いていたらcssの設定は成功です。コミットしておきましょう。

productionで起動

developmentで動くことが確認できたので次はproductionで動かします。

まずはnginxのDockerfile, nginx.confとdocker-composeを作成しました。

nginx.conf
server {
    listen       80;
    server_name  web;
    # 静的ファイルを格納するディレクトリ。ホストの./publicと同期します。
    root /usr/share/nginx/public;

    access_log /var/log/default.access.log;
    error_log /var/log/default.error.log;

    location / {
        # 静的ファイルが見つかればそれを返却。なければappへ接続
        try_files $uri @app;
    }

    location @app {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Client-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://app:3000;
    }
}
Dockerfile_nginx
FROM nginx:1.17.3

RUN mkdir -p /usr/share/nginx/public

# 設定ファイルを上書き
COPY nginx.conf /etc/nginx/conf.d/default.conf

CMD ["nginx", "-g", "daemon off;"]
docker-compose.prd.yml
version: '3.7'
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile_nginx
    volumes:
      - ./public:/usr/share/nginx/public:cached
    ports:
      - 80:80
    depends_on:
      - app
  app:
    build: .
    command: bundle exec rails s -b 0.0.0.0
    volumes:
      - .:/app:cached
    depends_on:
      - db
    tty: true
    stdin_open: true
    environment:
      RAILS_ENV: production
  db:
    image: mysql:5.7.27
    volumes:
      - db-volume:/var/lib/mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
volumes:
  db-volume:

次にデータベースの設定をします。
productionのデータベース接続情報を修正します。今回はdevelopmentと同じくroot/(なし)にしています。

conf/database.yml
 production:
   <<: *default
   database: app_production
-  username: app
-  password: <%= ENV['APP_DATABASE_PASSWORD'] %>

productionのデータベース作成&マイグレーション

rails_in_production:$ docker-compose -f docker-compose.prd.yml exec app bundle exec rake db:create
Created database 'app_production'
rails_in_production:$ docker-compose -f docker-compose.prd.yml exec app bundle exec rake db:migrate
== 20190914140349 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0087s
== 20190914140349 CreateUsers: migrated (0.0089s) =============================

それでは起動してみましょう。3つのコンテナが起動したら成功です。

$ docker-compose -f docker-compose.prd.yml up --build -d
・・・
$ docker container ls
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                 NAMES
3f0771d9c7e2        rails_in_production_web   "nginx -g 'daemon of…"   28 seconds ago      Up 26 seconds       0.0.0.0:80->80/tcp    rails_in_production_web_1
844d4e71e549        rails_in_production_app   "bundle exec rails s…"   31 seconds ago      Up 29 seconds                             rails_in_production_app_1
443d1337b008        mysql:5.7.27              "docker-entrypoint.s…"   5 days ago          Up 3 hours          3306/tcp, 33060/tcp   rails_in_production_db_1

今回は80ポートなので http://localhost/users でアクセスします。
スクリーンショット 2019-09-17 16.49.34.png

うーん、エラーになりました。ログを確認しましょう。

# nginx: access.log
$ docker-compose -f docker-compose.prd.yml exec web tail -f /var/log/default.access.log
172.23.0.1 - - [17/Sep/2019:07:55:27 +0000] "GET /users HTTP/1.1" 500 1635 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"

# nginx: error.log
$ docker-compose -f docker-compose.prd.yml exec web tail -f /var/log/default.error.log

# rails: production.log
$ docker-compose -f docker-compose.prd.yml exec app tail -f log/production.log
I, [2019-09-17T07:55:27.766706 #1]  INFO -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5] Started GET "/users" for 172.23.0.4 at 2019-09-17 07:55:27 +0000
I, [2019-09-17T07:55:27.768054 #1]  INFO -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5] Processing by UsersController#index as HTML
I, [2019-09-17T07:55:27.769031 #1]  INFO -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5]   Rendering users/index.html.erb within layouts/application
D, [2019-09-17T07:55:27.772991 #1] DEBUG -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5]    (0.3ms)  SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
D, [2019-09-17T07:55:27.774112 #1] DEBUG -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5]   User Load (0.4ms)  SELECT `users`.* FROM `users`
I, [2019-09-17T07:55:27.774743 #1]  INFO -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5]   Rendered users/index.html.erb within layouts/application (Duration: 5.3ms | Allocations: 1132)
I, [2019-09-17T07:55:27.775551 #1]  INFO -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5] Completed 500 Internal Server Error in 7ms (ActiveRecord: 0.7ms | Allocations: 1660)
F, [2019-09-17T07:55:27.777753 #1] FATAL -- : [9d8bb416-82f3-4e28-9d38-8339c90ba0c5]
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5] ActionView::Template::Error (Webpacker can't find application in /app/public/packs/manifest.json. Possible causes:
1. You want to set webpacker.yml value of compile to true for your environment
   unless you are using the `webpack -w` or the webpack-dev-server.
2. webpack has not yet re-run to reflect updates.
3. You have misconfigured Webpacker's config/webpacker.yml file.
4. Your webpack configuration is not creating a manifest.
Your manifest contains:
{
}
):
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]      5:     <%= csrf_meta_tags %>
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]      6:     <%= csp_meta_tag %>
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]      7:
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]      8:     <%= stylesheet_pack_tag 'application', media: 'all' %>
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]      9:     <%= javascript_pack_tag 'application' %>
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]     10:   </head>
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]     11:
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5]
[9d8bb416-82f3-4e28-9d38-8339c90ba0c5] app/views/layouts/application.html.erb:8

Railsのログに色々出ていますが、manifest.jsonが見つからないのが問題みたいです。
webpacker.ymlを見るとproductionではcompile: falseになっています。ここがtrueだとアクセス時にアセットをコンパイルしてくれるのですが、都度コンパイルしているとパフォーマンスが悪くなってしまうのでproductionではfalseになっています。

conf/webpacker.yml
# Production depends on precompilation of packs prior to booting for performance.
compile: false

このエラーを回避するために事前にアセットをコンパイルしておく必要があります。原因がわかったのでアセットをコンパイルしましょう。
コマンドをコンテナに入って直接実行してもよいのですが次回以降再現できなくなってしまう可能性があるのでDockerfileにコマンドを追加してbuildからやり直します。

--- a/Dockerfile
+++ b/Dockerfile
@@ -25,5 +25,6 @@ RUN gem update bundler
 RUN bundle install
 COPY . .
 RUN yarn install --check-files
+RUN bundle exec rails assets:precompile

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

再ビルド

$ docker-compose -f docker-compose.prd.yml up --build -d

それでは改めてアクセスしてみます。
無事、productionで動かすことができました。登録・更新・削除もできているようです。
スクリーンショット 2019-09-17 17.31.08.png

おまけ

productionでwebサーバーを使わない場合

今回はnginxを使いましたが、使わないことも可能です。
ただ、productionの設定に下記が記載されているため、アセットを配信するためには環境変数にRAILS_SERVE_STATIC_FILES=trueを設定しておく必要があります。

config/environments/production.rb
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

リポジトリ

この記事で作成したリポジトリです。
https://github.com/ham0215/rails_in_production

63
63
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
63