Dockerを使いRuby on Railsアプリ、PostgreSQL、Nginxなどのコンテナーをクラウドサービスで動かしてみた
環境構築
普通に Boot2Docker をMacにインストールしました。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つのコンテナーで動かす事にします
- pgコンテナー : RDBサーバー、PostgreSQL
- nginxコンテナー : Webサーバー、Nginxと画像/CSS等のコンテンツ
- appコンテナー : アプリケーション、Ruby, Ruby on Rails, Unicorn
ちなみに、今回参考にした 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環境ではRDBがPostgreSQLになるように、またサーバーはUnicornに変更しました。
pgコンテナー
pgコンテナーは DockerのドキュメントにあるDockerizing a PostgreSQL serviceを、ほぼそのまま使っています。
行っているのは、ubuntu14.04にPostgreSQLをインストールし docker というデータベースを作成し、run でPostgreSQLサーバーが起動するようにしています。また、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 を参考に作りました。内容は
- ubuntu14.04 + ruby2.1.2のインストール(後ほど説明します)
- Gemfileをコピーしてbundleコマンドで必要な Gem をインストール
- アプリケーションのコピー
- 不要なファイルの削除
やはり、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 ほぼそのままです。ありがとうございます! 内容は
- Rubyをコンパイルするためのツール・ライブラリーのインストール
- Rubyのコンパイル・インストール
gemのアップデート、bundler gemのインストール
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アプリの動作確認。
動いた ^^)/
DigitalOceanではサーバー(droplet)を停止しても、存在すれば課金されるのようなので 終わったらDestroyしてしまいましょう :-)
Docker感想
既にChefは使っていますが、今回Dockerを使って思った事は
- Chefはサーバー構築手順をコード化するものですが、Dockerはイメージを構築し利用するものなので、Chefのような考え方は切り換えないと行けない。例えば実行時に決まるIPアドレスなどはイメージ/Dockerfileには書けない。
- Dockerfileはシンプルな記述しか出来ないので、shell script等に頼る事になり、Rubyプログラマーの私には色々な事ができるChefの方が良いな
- イメージが出来てしまえば確かにサーバーへのデプロイは早いが、大きなイメージファイルをやり取りするので思ったほど早くはない。イメージをネットワーク的にデプロイ先に近いところ置けば良いのだろうか?
- 一つのサーバー上で複数のコンテナーが作れるので、今回のようにRDB,Web,アプリを分けておけばメンテナンス等でのダウンタイムが減らせるかも。
- 現在はクラウド+ Chefで満足しているので直ぐにDockerに移ろうとは私は思わないですが、Dockerをベースにしたサービスや新たな技術が生まれてきているので将来が楽しみな技術ですね。