参考にしたところ
モチベーション
dockerを使ってproductionでrailsアプリケーションを動かします。データベースはdockerのイメージの中に同梱するのではなく、別にあるDBサーバに接続します。
dockerのイメージには、supervisordがnginxとrailsのプロセスを起動します。
RailsのWebアプリケーションは開発は継続しているので、なるべく迅速にgitリポジトリからソーツツリーをクローンしてdeployできるようにしたいです。
dockerはMacで1.8を使っています。
ベースのリポジトリを作る
一気にdockerイメージをスクラッチからビルドしてもいいのですが、その場合、rubyのコンパイルなどが走って時間がかかります。そのため、ベースとなるdockerイメージと、railsのWebアプリケーションと幾つかの設定ファイルだけをインストールするための二つのイメージを作成します。rubyがインストールされたベースとなるイメージとrailsのイメージ、最後にWebアプリケーションの3つのイメージで管理しているサイトrailsをdockerで動かしたい場合の構成はどうするべきかもあります。自分たちはそこまで頻繁にビルドするわけではないことと、ベースシテムはセキュリティホール以外はそんなにアップデートしたくないので、2つにしています。
まずは、ベースリポジトリを作ります。
Dockerファイルは次の通りです。ubuntuの公式リポジトリをベースに作成します。
FROM ubuntu:14.04
# Install basic packages
RUN apt-get update
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:chris-lea/node.js
RUN apt-key adv --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A
RUN echo deb http://repo.percona.com/apt trusty main >> /etc/apt/sources.list
RUN echo deb-src http://repo.percona.com/apt trusty main >> /etc/apt/sources.list
RUN apt-get update
RUN apt-get install -y wget curl git
RUN apt-get install -y libc6-dev libc6 libc-dev gcc g++ cpp g++ build-essential
RUN apt-get install -y zlib1g-dev libssl-dev libreadline-dev libyaml-dev libxml2-dev libxslt-dev
# supervisor, nginx...
RUN apt-get install -y nginx supervisor
RUN apt-get install -y libssl-dev libperconaserverclient18.1-dev
RUN apt-get install -y nodejs
# Install ruby-build
RUN git clone https://github.com/sstephenson/ruby-build.git .ruby-build
RUN .ruby-build/install.sh
RUN rm -fr .ruby-build
# Install ruby-2.2.2
RUN ruby-build 2.2.2 /usr/local
# Install bundler
RUN gem update --system
RUN gem install bundler --no-rdoc --no-ri
RUN gem install mysql2 --version "=0.3.19" --no-rdoc --no-ri
RUN gem install rails --version "~>4.2.0" --no-rdoc --no-ri
RUN gem install unicorn --version "~>4.9.0" --no-rdoc --no-ri
ENTRYPOINT ["bash", "-l", "-c"]
インストールしたのは
- rubyのビルドに必要な環境
- ruby 2.2.2
- nginx
- supervisord
- nodejs
- gem
- rails
- mysql2
- unicorn(railsアプリはunicornで動かします)
libperconaserverclient18.1-devはmysqlのクライアントライブラリです。この環境ではMySQL5.6で動かします。ubuntu 14.04では5.5ベースなので、http://repo.percona.com/ のMySQLのライブラリを使っています。
このDockerファイルで
docker build --no-cache -t webapp-base:0.1 .
でビルドしています。--no-cacheが付いているのは、キャッシュが効いてaptでのインストールが失敗することがあるからです。
Webアプリを含めたイメージを作る
ベースとなるイメージができたので次にWebアプリを含めたイメージを作ります。
gitリポジトリの展開
下準備として、Dockerファイルのあるディレクトリにgitリポジトリをコピーします。今回は、手元にcloneしてあるところからarchiveでソーツツリーをコピーします。
git archive --format=tar HEAD . | tar -C $DOCKER_DIR/webapp/ -x
これで、ソーツツリーがwebappの下に展開されました。
nginxの設定ファイル
nginxの設定ファイルは次のようになります。
http {
# unicornのproxy設定
# unixドメインでnginxとunicornを通信する
upstream unicorn {
server unix:/tmp/unicorn.sock;
}
server {
listen 80;
# Railsのassetファイルの参照先設定
location /assets {
root /webapp/public;
}
# webアプリのunicornでのproxy
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
proxy_pass http://unicorn;
}
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}
pid /var/run/nginx.pid;
worker_processes 2;
events {
worker_connections 1024;
# multi_accept on;
}
Railsのアプリケーションは/webappに配置します。
unicornの設定
unicornの設定ファイルは$GIT_REPOSITORY/conf/unicorn.rbに保存します。
# -*- coding: utf-8 -*-
# WEB_CONCURRENCY : nubmer of worker process.
# LOG_DIR : directory name to store log files
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true
listen "/tmp/unicorn.sock"
pid File.expand_path("unicorn.pid", "/var/run")
before_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
Process.kill 'QUIT', Process.pid
end
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
end
after_fork do |server, worker|
Signal.trap 'TERM' do
puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
end
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
end
stderr_path File.expand_path('unicorn-err.log', ENV['LOG_DIR'] || "log")
stdout_path File.expand_path('unicorn.log', ENV['LOG_DIR'] || "log")
基本は参考にしたサイトのものをそのままですが、ログファイルの保存先はLOG_DIRは環境変数で設定可能にしています。
supervisord
supervisordの設定は次のようになります。
[supervisord]
nodaemon=true
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
[program:unicorn]
command=unicorn_rails -c config/unicorn.rb -E production
directory=/webapp
autostart=true
autorestart=true
わりかし普通。
dockerfile
dockerfileは次のようになります。
FROM webapp-base:0.1
RUN mkdir /webapp
WORKDIR /webapp
ADD webapp /webapp
RUN bundle install --without test development
RUN npm install
# react.jsの処理
RUN npm run bundle
# production環境precompile
RUN RAILS_ENV=production rake assets:precompile
WORKDIR /
copy conf/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
copy conf/nginx.conf /etc/nginx/nginx.conf
ENV SECRET_KEY_BASE=秘密
EXPOSE 22 80
CMD ["/usr/bin/supervisord"]
必要なものをDockerイメージにコピー後にgemとnpmのインストールを行っています。そのあと、production環境で動かすので、jsやcssはプレコンパイルしてnginxで配信できるようにしています。
データベースのホスト名はdatabase.yamlでhost: dbserverとしています。
これをベースにdockerイメージをビルドします。
起動
docker imageはour-webappとしてビルドしました。次のように起動します。
docker run -p 80:80 -it --add-host='dbserver:DBサーバの動いているIP' our-webapp
-p 80:80 でホストも80ポートでListenさせています。データベースの接続情報はdatabase.yamlにdbserverというホスト名を設定しているので、--add-host='dbserver:DBサーバの動いているIPで名前解決できるようんしています。
最後に
これで全部出来上がった、と思ったらHTTPSの設定を完全に忘れていました。