search
LoginSignup
357

More than 3 years have passed since last update.

posted at

updated at

Docker + Rails + Puma + Nginx + MySQL

Dockerに興味があったものの、コマンドを叩く以外はほとんど手を出す機会がなかったので、
夏季休暇の間にしっかりと基本を身につけるべく、検証用にRails環境を構築してみました。
思ってた以上に悪戦苦闘したので、記憶からすっ飛ばないようにちゃんと記事として残しておこうと思います。

前提

  • 作業はすべて ConoHaVPS のUbuntu16.04上でやってます。
  • OS上にすでに Docker と docker-compose がインストール済みという前提で話を進めます。
  • この環境では フロント に Nginx を配置して、バックエンドの Puma にプロキシしています。

バージョン

  • ホストOS(Ubuntu16.04 Xenial Xerus)
  • Docker(17.06.1-ce)
  • docker-compose(1.11.2)
  • Nginx(1.15.8)
  • Ruby(2.5.1)
  • Ruby on Rails(5.2.0)
  • MySQL(5.7)

ディレクトリ構成

/var/webapp
├── containers
│   └── nginx
│       ├── Dockerfile
│       └── nginx.conf
├── docker-compose.yml
├── Dockerfile
├── environments
│   └── db.env
├── Gemfile
└── Gemfile.lock

1. 各ディレクトリの作成

アプリケーションルート

どこでもいいんですが、自分は /var/webapp として作成しました。

$ sudo mkdir -p /var/webapp
$ sudo chown -R $USER:$USER /var/webapp

Nginxコンテナディレクトリ

$ mkdir -p /var/webapp/containers/nginx

環境変数用ディレクトリ

$ mkdir /var/webapp/environments

2. コンテナ生成のための各ファイルを作成

※ 以降はアプリケーションルート内で行う

Rails用Dockerfile

$ vim Dockerfile
FROM ruby:2.5.1

# リポジトリを更新し依存モジュールをインストール
RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       nodejs

# ルート直下にwebappという名前で作業ディレクトリを作成(コンテナ内のアプリケーションディレクトリ)
RUN mkdir /webapp
WORKDIR /webapp

# ホストのGemfileとGemfile.lockをコンテナにコピー
ADD Gemfile /webapp/Gemfile
ADD Gemfile.lock /webapp/Gemfile.lock

# bundle installの実行
RUN bundle install

# ホストのアプリケーションディレクトリ内をすべてコンテナにコピー
ADD . /webapp

# puma.sockを配置するディレクトリを作成
RUN mkdir -p tmp/sockets

Gemfile

$ vim Gemfile
Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.0'

Gemfile.lock

$ touch Gemfile.lock

中身は空でOK

Nginx用Dockerfile

$ vim containers/nginx/Dockerfile
FROM nginx:1.15.8

# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
ADD nginx.conf /etc/nginx/conf.d/webapp.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

Nginx設定ファイル

$ vim containers/nginx/nginx.conf
nginx.conf
# プロキシ先の指定
# Nginxが受け取ったリクエストをバックエンドのpumaに送信
upstream webapp {
  # ソケット通信したいのでpuma.sockを指定
  server unix:///webapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name example.com [or 192.168.xx.xx [or localhost]];

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

  # ドキュメントルートの指定
  root /webapp/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @webapp;
  keepalive_timeout 5;

  # リバースプロキシ関連の設定
  location @webapp {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://webapp;
  }
}

DB接続用の情報ファイル

$ vim environments/db.env
db.env
MYSQL_ROOT_PASSWORD=db_root_password
MYSQL_USER=user_name
MYSQL_PASSWORD=user_password

パスワードとユーザー名は適宜変更してください。

docker-compose.yml

Dockerに触れて一番頭を悩ませたのがデータの永続化でしたが、
自分はほとんどトップレベルの名前付き Volume で対応するようにしました。
ちなみに、バージョン3からは volumes_from が削除されたため使えません。(詳しくはここ

$ vim docker-compose.yml
docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
    env_file:
      - ./environments/db.env
    command: bundle exec puma -C config/puma.rb
    volumes:
      - .:/webapp
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
      - log-data:/webapp/log
    depends_on:
      - db
  db:
    image: mysql:5.7
    env_file:
      - ./environments/db.env
    volumes:
      - db-data:/var/lib/mysql
  web:
    build:
      context: containers/nginx
    volumes:
      - public-data:/webapp/public
      - tmp-data:/webapp/tmp
    ports:
      - 80:80
    depends_on:
      - app
volumes:
  public-data:
  tmp-data:
  log-data:
  db-data:

3. Railsプリケーションの生成と編集

Railsの生成

ここまでの準備ができたら rails new でアプリケーションを生成します。
具体的には下記のコマンドを打つとappコンテナ内で rails new が実行されRailsが生成されます。

$ docker-compose run --rm app rails new . --force --database=mysql --skip-bundle

また、docker-compose.yml の app に定義している volumes にて

volumes:
  .:/webapp

ホストのカレントディレクトリ(この場合アプリケーションルート)とコンテナ内の /webapp ディレクトリを繋ぐことで、コンテナ側で生成されたRailsをホスト側で参照することができます。

権限の変更

生成されたRailsアプリの所有権が root:root となっているので(Dockerの操作は基本すべてroot権限で実行されるため)、現在のログインユーザーに変更しておきます。

$ sudo chown -R $USER:$USER .

puma.rbの編集

コメント行がたくさんあって編集がけっこう面倒なので、ファイルをゼロバイトにしたあとで、あらかじめ用意しておいたものをコピペすると楽です。

$ cp /dev/null config/puma.rb
$ vim config/puma.rb
puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true

database.ymlの編集

こちらもファイルをゼロバイトにしてからコピペ。

$ cp /dev/null config/database.yml
$ vim config/database.yml
database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('MYSQL_USER') { 'root' } %>
  password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %>
  host: db

development:
  <<: *default
  database: webapp_development

test:
  <<: *default
  database: webapp_test

設定の中で MYSQL_USER と MYSQL_PASSWORD は DB接続用の情報ファイル で定義した環境変数名を設定してくだい。

4. イメージのビルドとコンテナの起動

いよいよコンテナを作ってそれらを起動してみます。

イメージのビルド
docker-compose.yml の各サービス(appとかdbとかweb)の build に指定されている Dockerfile を元にイメージを作成します。
build ではなく image が定義されている場合は DockerHub からイメージを pull してきます。

$ docker-compose build

ビルドが完了したら一旦確認してみます。

$ docker images
REPOSITORY          TAG                IMAGE ID            CREATED             SIZE
webapp_web          latest             6c3ae1c68ff0        18 hours ago        107MB
webapp_app          latest             fe241efeb525        18 hours ago        820MB
<none>              <none>             36b3d89bfec7        18 hours ago        781MB
ruby                2.4.1              15b96d1e91df        2 days ago          679MB
nginx               1.13.3             b8efb18f159b        4 weeks ago         107MB
mysql               5.7                c73c7527c03a        4 weeks ago         412MB

コンテナの起動
ビルドが完了しイメージができたら下記のコマンドで一気にコンテナ(サービス)を立ち上げます。

$ docker-compose up -d

オプションに -d を指定するとデーモンとしてバックグラウンドで起動します。

コンテナが起動しているか確認します。

$ docker-compose ps
   Name                  Command               State         Ports
-------------------------------------------------------------------------
webapp_app_1   bundle exec puma -C config ...   Up
webapp_db_1    docker-entrypoint.sh mysqld      Up      3306/tcp
webapp_web_1   /bin/sh -c /usr/sbin/nginx ...   Up      0.0.0.0:80->80/tcp

5. DB設定

権限の付与

DBの操作は一般ユーザーで行いたいので、すでに作成されている一般ユーザーに対して実行権限を付与しておきます。

GRANT文を記述したSQLファイルを作成します。
user_name は DB接続用の情報ファイル に設定した MYSQL_USER の値に置き換えてください。

$ vim db/grant_user.sql
grant_user.sql
GRANT ALL PRIVILEGES ON *.* TO 'user_name'@'%';
FLUSH PRIVILEGES;

ホストからdbコンテナに向けてクエリを流し込みます。

$ docker-compose exec db mysql -u root -p -e"$(cat db/grant_user.sql)"

念のため権限が付与できたか確認します。

$ docker-compose exec db mysql -u user_name -p -e"show grants;"
+------------------------------------------------+
| Grants for user_name@%                         |
+------------------------------------------------+
| GRANT ALL PRIVILEGES ON *.* TO 'user_name'@'%' |
+------------------------------------------------+

ちなみに、 docker-compose.yml の db で下記のような環境変数が設定されていると、
ビルド時に指定されたユーザーが作成されます。

environment:
  MYSQL_USER: user_name
  MYSQL_PASSWORD: user_password

また、MYSQL_USER と MYSQL_PASSWORD は必ずセットで記述しないとダメです。
詳しくはEnvironment Variablesを参照
自分はこれらの設定をハードコーディングしたくなかったので、別ファイル(db.env)に切り出して読み込むようにしました。
あと .gitignore で除外できるので!

最初、extends キーワードでこれらのシークレット情報を記述した yml を読み込もうとしたのですが、
version3では extends が削除されていて使用できませんでした。

DBの作成

railsコマンドでDBを作成します。

$ docker-compose exec app rails db:create

ここまでで一旦環境構築は完了です。
ブラウザにアクセスしてRailsのWelcomeページが表示できるか確認しておきましょう。

http://localhost
スクリーンショット 2017-08-25 14.40.21.png

6. scaffoldでアプリのベースを構築

最後に名前とメールアドレスを持った簡単な CRUD を作成してみます。

scaffoldの実行

$ docker-compose exec app rails g scaffold User name:string email:string

マイグレートの実行

$ docker-compose exec app rails db:migrate

確認

ブラウザを開いて http://localhost/users にアクセスしてみてください。
下記の通りに表示されればOKです。
スクリーンショット 2017-08-25 14.47.32.png

CRUD操作が問題なく実行できるか試してみてください。

crud.png

7. おまけ

docker-composeコマンド

コンテナの停止

$ docker-compose stop

コンテナの停止と削除

$ docker-compose down

コンテナの起動

$ docker-compose start

MySQLへの接続

$ docker-compose exec service_name mysql -u user_name -p [-D DB名]

dockerコマンド

停止中のコンテナをまとめて削除

$ docker rm `docker ps -a -q`

参照されていないイメージをまとめて削除

docker rmi `docker images | sed -ne '2,$p' -e 's/  */ /g' | awk '{print $1":"$2}'`

使用されていないボリュームの削除

$ docker volume prune

8. まとめ

そもそもこの構築手順が Docker のお作法的に良いのか悪いのかはわかりませんが、
ひとまず1コンテナ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
What you can do with signing up
357