弊社の基幹システムをVPSからAWSに移行するにあたって、まずはrailsを動かしてみる、という段階で無事に死にました☆
今後のための備忘録です。
やりたいこと
今回やったこと
ファイル構成
プロジェクトルート
├ docker
│ ├ mysql
│ │ ├ volumes
│ │ │ └ ※mysqlの永続化に使う。後述
│ │ ├ charset.cnf
│ │ ├ password.yml
│ │ └ Dockerfile
│ ├ nginx
│ │ ├ development
│ │ │ ├ default.conf
│ │ │ └ Dockerfile
│ │ ├ production
│ │ │ ├ default.conf
│ │ │ └ Dockerfile
│ │ └ nginx.conf
│ └ rails
│ └ Dockerfile
├ src
│ └ ※railsアプリのソースをここに置きます。後述
├ .gitignore
├ buildspec.yml
├ docker-compose.common.yml
├ docker-compose.development.yml
├ docker-compose.production.yml
└ README.md
開発環境と本番環境でdocker-compose
の内容にかなり差があるので、ファイルを分けています。
共通する内容はdocker-compose.common.yml
に記述して、なるべく助長化を防ぎます。
ただし、nginxのDockerfile
に関しては環境ごとにほぼ同じ内容でファイルを分けて作っています。
これも後述しますが、最後までハマり続けたポイントを解決するための苦肉の策です。
docker-compose
version: '2'
services:
server:
environment:
TZ: Asia/Tokyo
ports:
- '80:80'
web:
build:
context: .
dockerfile: ./docker/rails/Dockerfile
ports:
- '3000:3000'
volumes:
- ./src:/app
extends:
file: ./docker/mysql/password.yml
service: password
db:
build:
context: .
dockerfile: ./docker/mysql/Dockerfile
ports:
- '3306:3306'
extends:
file: ./docker/mysql/password.yml
service: password
version: '2'
services:
datastore:
image: busybox
volumes:
- /share
- ./docker/mysql/volumes:/var/lib/mysql
server:
build:
context: .
dockerfile: ./docker/nginx/development/Dockerfile
extends:
file: docker-compose.common.yml
service: server
volumes_from:
- datastore
depends_on:
- datastore
web:
extends:
file: docker-compose.common.yml
service: web
command: bundle exec unicorn -p 3000 -c /app/config/unicorn.rb
volumes_from:
- datastore
depends_on:
- db
db:
extends:
file: docker-compose.common.yml
service: db
volumes_from:
- datastore
depends_on:
- datastore
version: '2'
services:
server:
build:
context: .
dockerfile: ./docker/nginx/production/Dockerfile
extends:
file: docker-compose.common.yml
service: server
web:
extends:
file: docker-compose.common.yml
service: web
depends_on:
- server
environment:
RAILS_ENV: production
DB_HOST: ◯◯.△△.ap-northeast-1.rds.amazonaws.com
DB_USERNAME: ユーザー名
DB_PASSWORD: パスワード
DB_DATABASE: データベース名
serviceで言えば、
-
development
- datastore → busybox
- server → nginx
- web → ruby(rails)
- db → mysql
-
production
- server → nginx
- web → ruby(rails)
といった構成になっています。
developmentのdatastoreでvolumesとしてローカルディレクトリのdocker/mysql/volumes
を/var/lib/mysql
にマウントしています。
dockerはイメージを停止するとデータが消えてしまいますが、このようにすることで、開発環境のデータベースを永続化できます。
docker/mysql/volumes
の中身は.gitignore
に設定して、git管理はしないようにしましょう。
本番環境ではECS fargate上でストレージの共有ができるのでdatastore
は不要です。
また、データベースはRDSを用いるのでdb
も不要です。
Dockerfile
nginx
FROM nginx:1.11
RUN apt-get update && \
apt-get install -y apt-utils \
locales && \
sed -i -e 's/# ja_JP.UTF-8/ja_JP.UTF-8/g' /etc/locale.gen && \
locale-gen ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LC_TIME C
ADD ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
# productionでは次の行のファイルパスが変わる
ADD ./docker/nginx/development/default.conf /etc/nginx/conf.d/default.conf
rails
FROM ruby:2.2.3
RUN apt-get update -qq && \
apt-get install -y apt-utils \
build-essential \
libpq-dev \
nodejs \
mysql-client
RUN mkdir /app
WORKDIR /app
ADD ./src/Gemfile /app/Gemfile
ADD ./src/Gemfile.lock /app/Gemfile.lock
RUN bundle install -j4
ADD ./src /app
EXPOSE 3000
# 次の行は本番環境でしか実行されない
CMD ["unicorn", "-p", "3000", "-c", "/app/config/unicorn.rb", "-E", "production"]
mysql
FROM mysql:5.7
RUN apt-get update && \
apt-get install -y apt-utils \
locales && \
rm -rf /var/lib/apt/lists/* && \
echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
locale-gen ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ADD ./docker/mysql/charset.cnf /etc/mysql/conf.d/charset.cnf
それぞれのコンテナのコマンドやファイルのコピー、ライブラリの更新などはDockerfile
に記述するようにしています。
ただ、ローカルのdockerではコマンドが動いても本番では動かない、もしくはその逆が度々あったので、railsのDockerfile
にはCMD
の記述がされていたりしますが、productionでしか使わないコマンドがDockerfile
に書いてあるのは気持ち悪い気がするので、ここは後々修正したいです。
その他のファイル
nginx
upstream unicorn {
# productionではserver以降が 127.0.0.1:3000 になる
server unix:/share/unicorn.sock;
}
server {
listen 80 default_server;
server_name localhost;
root /app/public;
try_files $uri/index.html $uri @unicorn;
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
location @unicorn {
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://unicorn;
}
}
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 600;
include /etc/nginx/conf.d/*.conf;
}
ここでのポイントはdefault.conf
の2行目の記述と、それぞれのファイルのtimeoutの時間設定です。
この2点が今回ハマって、かなりの時間を奪われたポイントでした。
1. ソケットファイル
upstream unicorn {
# productionではserver以降が 127.0.0.1:3000 になる
server unix:/share/unicorn.sock;
}
developmentではbusyboxを使ったストレージ共有で、ソケットファイルを準備していました。
nginxとrails(unicorn)の両方から、同じソケットファイルを見ることで繋げるようなイメージですが、本番環境ではストレージの共有ができず、直接ローカルIPとポート127.0.0.1:3000
を指定したら動きました。
なるべく助長なファイル構成は避けたかったのですが、nginxの設定ファイルに環境変数などを読み込ませることが手間だったので、このファイルは開発環境と本番環境でほとんど同じ内容なのですが、環境ごとにファイルを分けて対応しています。
2. タイムアウト
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
keepalive_timeout 600;
railsサーバーやRDSへの接続には少なからず時間がかかります。
キャッシュが効き始めれば読み込み速度は大きく向上するのですが、初回アクセス時などは504 Gateway Time-Out
のエラーになることも多くありました。
そのため、タイムアウトまでの時間を伸ばしました。
デフォルトは60秒などで設定されていると思います。
mysql
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
[client]
default-character-set=utf8mb4
version: '2'
services:
password:
environment:
MYSQL_ROOT_PASSWORD: ルートパスワード
TZ: "Asia/Tokyo"
セキュアな情報はできるだけコード上には書きたくなかったのですが、最低限環境変数として管理できるようにしました。
運用段階までにはもう少し改善したいです。
railsソース側の変更
当方は複数のrailsアプリケーションを管理する必要があったので、docker関連のファイルを容易に使い回せるように、あえてrailsアプリケーションのソースはsrcディレクトリに分けて配置しています。
ここではunicorn
というgemの設定と、mysqlとRDSを使い分けるための設定をします。
unicorn
unicorn
はrailsのアプリケーションサーバとnginxのwebサーバを繋げるためのpassengerのようなモジュールの位置付けです。
単体でもサーバとして使えるらしいです。
# 追記
gem "unicorn"
bundle install
インストールが完了したら、早速設定ファイルを編集します。
src/config
ディレクトリにunicorn.rb
ファイルがない場合は作ります。
worker_processes 2
pid "/var/run/unicorn.pid"
# developmentとproductionで場合分け
if ENV['RAILS_ENV'] == 'production'
listen 3000
else
listen "/share/unicorn.sock"
end
# タイムアウトまでの時間を伸ばす
timeout 600
ここでも先程のnginxと同じように、開発環境と本番環境の場合分け、タイムアウトの設定をしています。
特にlisten
の値はdefault.conf
と設定がズレていると起動後に上手く繋がらないので、ご注意ください。
mysql
default: &default
adapter: mysql2
encoding: utf8
pool: 5
timeout: 5000
development:
<<: *default
username: 開発環境のユーザー名
password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>
database: 開発環境のデータベース名
host: db
production:
<<: *default
host: <%= ENV['DB_HOST'] %>
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>
database: <%= ENV['DB_DATABASE'] %>
Dockerfile
やpassword.yml
で設定してきた環境変数がここで読み込まれていきます。
developmentのhostで指定しているdb
というのはdocker-compose.yml
でデータベースに設定しているサービス名です。
別の名前に設定している場合はそちらの名前にしてください。
ビルドと起動
やっと動かす段階です。
dockerを起動しておいてください。
また、3000
番ポート、3306
番ポートを使うので、他で使っていれば、そちらを停止しておきます。
プロジェクトのルートディレクトリでコマンドを打ちます。
docker-compose -f docker-compose.development.yml -p development build
docker-compose -f docker-compose.development.yml -p development run --rm web rake db:create
docker-compose -f docker-compose.development.yml -p development run --rm web rake db:migrate
docker-compose -f docker-compose.development.yml -p development up -d
これでブラウザでlocalhost
にアクセスすると、railsのホーム画面が見られると思います。
ちなみに、停止コマンドは以下です。
docker-compose -f docker-compose.development.yml -p development stop
docker-compose -f docker-compose.development.yml -p development
は共通なので、お使いのshellでエイリアスに登録してもいいかと思います。
当方の設定を参考に載せておきます。
alias dcdev='docker-compose -f docker-compose.development.yml -p development'
# コンテナ全削除
alias drm='docker rm $(docker ps -q -a) -f'
# イメージ全削除
alias dirm='docker rmi $(docker images -q) -f'