dockerでrailsの実行環境(nginx + rails)を作る

## 参考にしたところ

- [Docker で Rails（Nginx + Unicorn + MySQL) を動かす。](http://qiita.com/7kaji/items/e4ad05188df6d05aff97)

## モチベーション

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で動かしたい場合の構成はどうするべきか](http://qiita.com/k-shogo/items/592e243f9763d0c3b10a)もあります。自分たちはそこまで頻繁にビルドするわけではないことと、ベースシテムはセキュリティホール以外はそんなにアップデートしたくないので、2つにしています。


まずは、ベースリポジトリを作ります。
Dockerファイルは次の通りです。ubuntuの公式リポジトリをベースに作成します。

```Docker
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でソーツツリーをコピーします。

```bash
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に保存します。

```rb: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の設定を完全に忘れていました。