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
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が受け取ったリクエストをバックエンドの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
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
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
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
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 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ページが表示できるか確認しておきましょう。
##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です。
CRUD操作が問題なく実行できるか試してみてください。
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プロセスに則って実現できたのは良かった。
なにか間違っている説明や不明点などがあったらご指摘をお願いします!