Dockerを使いRuby on Railsアプリ、PostgreSQL、Nginxなどのコンテナーをクラウドサービスで動かしてみた

Docker

環境構築

普通に Boot2DockerMacにインストールしました。Boot2Dockerは ここのブログの中ほどの画像のようにVitualBox上でDockerサーバーを動かし、Macの dockerコマンドがDockerサーバーと通信して動作します。

Dockerサーバーの作成・起動などは boot2docker コマンドで行います。

作成・起動は、

% boot2docker init
% boot2docker up

これで、dockerコマンドが使えるようになりますが、通信用のDOCKER_HOST環境変数を設定する必要があります。

% export DOCKER_HOST=tcp://192.168.59.103:2375
% docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
nginx               latest              c43e4a994b02        4 hours ago         231.7 MB
app                 latest              4b465cc33c7c        4 hours ago         602.7 MB
pg                  latest              8f1dd8d424ad        4 hours ago         380.3 MB
....

dockerコマンドの詳細はドキュメント等を参考にして下さい。Dockerでば docker build コマンドで Docefile に従いイメージ(ファイルシステムのアーカイブ?)を作成します。そして、 docker run コマンドでコンテナー(仮想マシン?)を作成しイメージを実行します。

コンテナーの割り振り

Ruby on Railsのアプリケーションは通常、アプリケーションを動かすサーバー(Unicron等)、データベース(PostgreSQL, MySQL等)、画像CSS等の配信を行うWebサーバー(Nginx,Apache等)のサーバーソフトが必要になります。これらを1つのコンテナーの中で動かす事も出来ますが、今回はメンテナンス性等を考え、3つのコンテナーで動かす事にします

ちなみに、今回参考にした Deploy Rails Applications Using Docker では RDBサーバー、アプリ+Nginxの2つのコンテナーで動いていいます。

Ruby on Rails アプリケーションの作成

シンプルな TODOアプリです。コードは GitHub あります。

作成手順

% rails new rails_app_with_docker
% cd rails_app_with_docker
% rails g scaffold todo due:date task:string
% rake db:migrate
% ruby s

その後 unicorn, pgをインストルし、production環境ではRDBPostgreSQLになるように、またサーバーはUnicornに変更しました。

pgコンテナー

pgコンテナーは DockerのドキュメントにあるDockerizing a PostgreSQL serviceを、ほぼそのまま使っています。

行っているのは、ubuntu14.04にPostgreSQLをインストールし docker というデータベースを作成し、runPostgreSQLサーバーが起動するようにしています。また、VOLUME 命令でバックアップ等で使うディレクトリーを他のコンテナーからマウント出来るようにしています。

  • config/docker/pg/Dockerfile
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y -q postgresql-9.3 libpq-dev postgresql-client-9.3 postgresql-contrib-9.3

USER postgres
RUN /etc/init.d/postgresql start &&\
    psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" &&\
    psql --command "CREATE DATABASE docker WITH OWNER docker TEMPLATE template0 ENCODING 'UTF8';"

RUN echo "host all  all    0.0.0.0/0  md5" >> /etc/postgresql/9.3/main/pg_hba.conf
RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf

EXPOSE 5432
VOLUME  ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"]

CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"]

appコンテナー

appコンテナーrbdock Gem が生成するDockerfile を参考に作りました。内容は

  1. ubuntu14.04 + ruby2.1.2のインストール(後ほど説明します)
  2. Gemfileをコピーしてbundleコマンドで必要な Gem をインストール
  3. アプリケーションのコピー
  4. 不要なファイルの削除

やはり、VOLUME 命令でアプリ全体を他のコンテナーからマウント出来るようにしています、この一部は nginxコンテナーで使います。

  • Dockerfile
FROM yuumi3/ruby:2.1.2

RUN mkdir /home/app
WORKDIR /home/app
ADD Gemfile /home/app/Gemfile
RUN bundle install
ADD . /home/app
RUN rake tmp:clear
RUN rake log:clear

VOLUME  ["/home/app"]

ENTRYPOINT bin/start_server.sh

run で実行されるスクリプトでは、Rails用の環境変数の設定、データベースのマイグレーションUnicornサーバーの起動です。

データベースのマイグレーションは、Dockerfileで指定したかったのですが、buildの際にはpgコンテナーと接続出来ないのでここで行っています。

  • bin/start_server.sh
#!/bin/bash -x
export RAILS_ENV=production
export SECRET_KEY_BASE=`rake secret`
rake db:migrate
unicorn_rails -c config/unicorn.rb

ubuntu14.04 + ruby2.1.2のインストールは既に Docker Hubに登録してあるイメージを使っています。 このDockerfileもrbdock Gem が生成するDockerfile ほぼそのままです。ありがとうございます! 内容は

  1. Rubyコンパイルするためのツール・ライブラリーのインストール
  2. Rubyコンパイル・インストール
  3. gemのアップデート、bundler gemのインストール

  4. yuumi3/ruby:2.1.2作成時のDockerfile

FROM ubuntu:14.04

# Install basic dev tools 
RUN apt-get update && apt-get install -y \
    build-essential \
    wget \
    curl \
    git

# Install package for ruby 
RUN apt-get install -y \
    zlib1g-dev \
    libssl-dev \
    libreadline-dev \
    libyaml-dev \
    libxml2-dev \
    libxslt-dev

# Install package for sqlite3
RUN apt-get install -y \
    sqlite3 \
    libsqlite3-dev

# Install package for postgresql
RUN apt-get install -y libpq-dev 

# 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.1.2
RUN ruby-build 2.1.2 /usr/local

# Install bundler
RUN gem update --system
RUN gem install bundler --no-rdoc --no-ri

nginxコンテナー

nginxコンテナーDeploy Rails Applications Using Docker に書かれているものを参考にしています。

ただし、sedを使いnginxの設定ファイルを書き換える力業は Server Fault を参考にしました。通常のnginxの設定ファイルに環境変数の値を使う事は出来ないのですね(luaを組み込んだnginxなら出来るようです)。

  • config/docker/nginx/Dockerfile
FROM ubuntu:14.04

RUN apt-get update
RUN apt-get install -y nginx

RUN echo "daemon off;" >> /etc/nginx/nginx.conf
ADD default  /etc/nginx/sites-available/default

EXPOSE 80
VOLUME  ["/var/log/nginx"]

ENTRYPOINT /bin/sed -i "s/[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+/${APP_PORT_3000_TCP_ADDR}/" \
  /etc/nginx/sites-available/default &&\
  /usr/sbin/nginx

nginxの設定ファイルも、ほぼDeploy Rails Applications Using Docker に書かれているものです。

root に指定されている /home/app/public は appコンテナーでexportしている /home/app をマウント(共有)しています。これで画像やcssなどがnginxで配信できます。

  • config/docker/nginx/default
cat config/docker/nginx/default
server {
    server_name _;

    root /home/app/public;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_redirect off;
        proxy_set_header Host $http_host;
        if (!-f $request_filename) {
            proxy_pass http://192.168.111.111:3000;
            break;
        }
    }
}

Dockerイメージの作成と実行

コンテナーは以下のRakeコマンドで作成 docker build 、実行 docker run しています。オプションの指定がたくさんあるので rakeコマンドにしてあります。

例えば、 nginxコンテナーの作成では -t nginx でイメージの名前を付け、次にDockefileのあるディレクトリーのパスを指定しています。 実行は * -d でバックグラウンド実行 * -p 80:80 はコンテナーのポート80を外部から80でアクセス出来るように設定 * --link app:app はappコンテナーの情報をnginxコンテナー内で app(例 APP_PORT_3000_TCP_ADDR 環境変数はappコンテナーIPアドレス)で参照出来るようにする * --volumes-from app appコンテナーVOLUME で指定されたディレクトリーをこのコンテナーでマウント(共有) * --name nginx 起動されたコンテナーに nginx という名前を付ける

namespace :docker do
  desc "Build Postgresql container"
  task :build_pg do
    sh "docker build  -t pg config/docker/pg"
  end

  desc "Run Postgresql container"
  task :run_pg do
    sh "docker run -d -p 5432:5432 --name pg pg"
    sh "docker ps"
  end

  desc "Build Rails application container"
  task :build_app do
    Rake::Task['assets:precompile'].invoke
    sh "docker build  -t app ."
  end

  desc "Run Rails application container"
  task :run_app do
    sh "docker run -d -p 3000:3000 --link pg:db --name app app"
    sh "docker ps"
  end

  desc "Build Nginx container"
  task :build_nginx do
    sh "docker build  -t nginx config/docker/nginx"
  end

  desc "Run Nginx container"
  task :run_nginx do
    sh "docker run -d -p 80:80 --link app:app --volumes-from app --name nginx nginx"
    sh "docker ps"
  end
end

DigitalOceanへdeploy

激安なクラウドサービス DigitalOcean に出来た Docker Image をデプロイしてみましょう。

1. DigitalOcean に SignUpし、サーバー(droplet)を作成

SIGN UPで登録し $ 0.015 / Hour のサーバーを選んでみました、OSはUbuntu 14.04 x64

メールでサーバー(droplet)のIPやアカウント情報が送られてきます。

2. Dockerのインストールと確認

Ubuntu - Docker Documentation の手順で最新版のDockerのインストールし、確認。

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
$ sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
$ sudo apt-get update
$ sudo apt-get install -y lxc-docker
$ sudo docker run -i -t ubuntu /bin/bash
/# ....
/# exit
$

3. 作ったDocker ImageをDocker Hubから取り込み実行

DockerコマンドでDocker HubからImageを取得し実行

$ sudo docker run -d -p 5432:5432 --name pg yuumi3/postgres:docker
$ sudo docker run -d -p 3000:3000 --link pg:db --name app yuumi3/rails_app:todo
$ sudo docker run -d -p 80:80 --link app:app --volumes-from app --name nginx yuumi3/nginx

ブラウザーでアクセスしTODOアプリの動作確認。

f:id:yuum3:20140806111148p:plain

動いた ^^)/

DigitalOceanではサーバー(droplet)を停止しても、存在すれば課金されるのようなので 終わったらDestroyしてしまいましょう :-)

Docker感想

既にChefは使っていますが、今回Dockerを使って思った事は

  • Chefはサーバー構築手順をコード化するものですが、Dockerはイメージを構築し利用するものなので、Chefのような考え方は切り換えないと行けない。例えば実行時に決まるIPアドレスなどはイメージ/Dockerfileには書けない。
  • Dockerfileはシンプルな記述しか出来ないので、shell script等に頼る事になり、Rubyプログラマーの私には色々な事ができるChefの方が良いな
  • イメージが出来てしまえば確かにサーバーへのデプロイは早いが、大きなイメージファイルをやり取りするので思ったほど早くはない。イメージをネットワーク的にデプロイ先に近いところ置けば良いのだろうか?
  • 一つのサーバー上で複数コンテナーが作れるので、今回のようにRDB,Web,アプリを分けておけばメンテナンス等でのダウンタイムが減らせるかも。
  • 現在はクラウド+ Chefで満足しているので直ぐにDockerに移ろうとは私は思わないですが、Dockerをベースにしたサービスや新たな技術が生まれてきているので将来が楽しみな技術ですね。