hidemium's blog

日々学んだことをアウトプットする。

Figで複数のDockerコンテナをまとめて起動して、Redmineを構築する

以前sameersbn/redmineというRedmineのイメージを試してみましたが、アプリケーションとMySQLでコンテナを分離した場合、複数のコンテナ起動や、DBの作成、マイグレーションの実行が個別に必要となり手間がかかっていました。

そこで今回は、マルチコンテナ構成ツールであるFigを用いて、アプリケーションとMySQLでコンテナを分離した場合でも、Redmineを一括起動するDockerfileを書いてみました。

構成

  • Ubuntu 14.04
  • Docker 1.0.0
  • Fig 0.5.2

コンテナとDockerfileの割り振り

Redmineの起動のためには、DBサーバ、アプリケーションサーバ、Webサーバが必要となります。これらを1つのコンテナの中で動かす事も出来ますが、安定性等を考え、以下のように2つのコンテナで動かす事にします。

また、Dockerfileのメンテナンス性を考え、APコンテナはRubyまでを導入したイメージと、そのイメージをベースにRedmineを導入したイメージに分離させています。Redmineの設定を変更したい場合、Rubyのインストールを省くことができます。

  • DBイメージ  : 共通設定、MySQL
  • Rubyイメージ : 共通設定、Ruby
  • APイメージ  : Redmine、Nginx、Unicorn (Rubyイメージをベースに差分ビルド)

Dockerfile

Dockerfileを以下のように作成します。
ソースコードこちら@githubで公開しています。

Dockerfileの構成

Dockerfileの構成は以下の通りです。
このほかにもコンテナに追加する定義ファイルもいくつかあります。

fig-redmine 
├──fig.yml           ... Figのコンテナ起動定義ファイル
├──mysql
│ └──Dockerfile      ... MySQL用のDockerfile
├──ruby     
│ └──Dockerfile      ... Ruby用のDockerfile 
└──redmine     
   └──Dockerfile      ... Redmine用のDockerfile 

MySQL

$ vi mysql/Dockerfile
FROM ubuntu:14.04

MAINTAINER hidemium

# install basic package
ADD sources.list /etc/apt/sources.list
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && \
    apt-get -y install \
      mysql-server \
      openssh-server \
      supervisor && \
      apt-get clean

# root user
RUN echo 'root:root' | chpasswd

# install sshd
RUN mkdir -p /root/.ssh /var/run/sshd
ADD id_rsa.pub /root/.ssh/authorized_keys
RUN chmod 700 /root/.ssh
RUN chmod 600 /root/.ssh/authorized_keys
RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
RUN sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config
RUN sed -ri 's/PermitRootLogin without-password/PermitRootLogin yes/g' /etc/ssh/sshd_config

# config supervisor
RUN mkdir -p /var/log/supervisor /etc/supervisor/conf.d
ADD mysqld.conf /etc/supervisor/conf.d/mysqld.conf
ADD sshd.conf /etc/supervisor/conf.d/sshd.conf

# install MySQL
RUN rm -rf /var/lib/mysql/mysql
RUN rm -rf /var/lib/apt/lists/*
ADD my.cnf /etc/mysql/my.cnf
ADD mysql-listen.cnf /etc/mysql/conf.d/mysql-listen.cnf
RUN chown -R mysql:mysql /var/lib/mysql
RUN chmod 700 /var/lib/mysql

# initialize MySQL 
RUN mysql_install_db --user mysql > /dev/null
RUN /usr/bin/mysqld_safe & \
    sleep 3 && \
    mysqladmin -u root password "root" && \
    mysql -u root -proot -e "GRANT ALL ON *.* TO 'root'@'172.17.%.%' IDENTIFIED BY '' WITH GRANT OPTION;" && \
    mysql -u root -proot -e "CREATE USER 'redmine'@'%.%.%.%' IDENTIFIED BY 'redmine';" && \
    mysql -u root -proot -e "CREATE DATABASE IF NOT EXISTS redmine_production DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;" && \
    mysql -u root -proot -e "GRANT ALL PRIVILEGES ON redmine_production.* TO 'redmine'@'%.%.%.%';" && \
    mysql -u root -proot -e "FLUSH PRIVILEGES;"

# expose ports
EXPOSE 22 3306 

# define default command.
CMD ["supervisord", "-n"]
  • MySQLのDockerfile内でRedmine用のユーザ作成、データベース作成を定義しています。

Ruby

$ vi ruby/Dockerfile
FROM ubuntu:14.04

MAINTAINER hidemium

# install basic package
ADD sources.list /etc/apt/sources.list
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && \
    apt-get -y install \
    openssh-server \
    supervisor \
    build-essential \
    curl \
    unzip \
    git-core \
    subversion \
    mercurial \
    libcurl4-openssl-dev \
    libreadline-dev \
    libssl-dev \
    libxml2-dev \
    libxslt1-dev \
    libyaml-dev \
    zlib1g-dev && \
    apt-get clean && \
    curl -O http://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz && \
    tar -zxvf ruby-2.1.2.tar.gz && \
    cd ruby-2.1.2 && \
    ./configure --disable-install-doc && \
    make && \
    make install && \
    cd .. && \
    rm -r ruby-2.1.2 ruby-2.1.2.tar.gz && \
    echo 'gem: --no-document' > /usr/local/etc/gemrc

# root user
RUN echo 'root:root' | chpasswd

# install sshd
RUN mkdir -p /root/.ssh /var/run/sshd
ADD id_rsa.pub /root/.ssh/authorized_keys
RUN chmod 700 /root/.ssh
RUN chmod 600 /root/.ssh/authorized_keys
RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
RUN sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config
RUN sed -ri 's/PermitRootLogin without-password/PermitRootLogin yes/g' /etc/ssh/sshd_config

# install supervisor
RUN mkdir -p /var/log/supervisor /etc/supervisor/conf.d
ADD sshd.conf /etc/supervisor/conf.d/sshd.conf

# install bundler
RUN \
  echo 'gem: --no-rdoc --no-ri' >> /.gemrc && \
  gem install bundler
  • rbenvを使ってRubyをインストールする方法もありますが、複数バージョンのRubyを使い分ける必要はないため、ソースからのビルドしています。DockerはImmutableなので、異なるバージョンのRubyを使用したい場合は、コンテナを作り直せばいいという考えです。
  • Redmineに必要なパッケージもこちらでインストールしておきます。

Redmine

$ vi redmine/Dockerfile
FROM ubuntu-ruby:14.04

MAINTAINER hidemium

ENV REDMINE_VERSION 2.5-stable
ENV REDMINE_HOME /var/redmine
ENV SETUP_DIR /app/setup
ENV RAILS_ENV production

# install basic package
RUN apt-get update && \
    apt-get -y install \
      nginx \
      imagemagick \
      libmagickcore-dev \
      libmagickwand-dev \
      libmysqlclient-dev

# install Redmine
RUN git clone -b ${REDMINE_VERSION} https://github.com/redmine/redmine.git ${REDMINE_HOME}
WORKDIR /var/redmine 
RUN echo 'gem "mysql2", "~> 0.3.11"' >> Gemfile
RUN echo 'gem "unicorn"' >> Gemfile
RUN bash -l -c 'bundle install --without development test'

# install plugins
RUN mkdir -p ${SETUP_DIR}
ADD setup/themes_install ${SETUP_DIR}/themes_install
ADD setup/plugins_install ${SETUP_DIR}/plugins_install
RUN chmod 755 ${SETUP_DIR}/*
RUN ${SETUP_DIR}/themes_install
RUN ${SETUP_DIR}/plugins_install

# add config file
ADD config/database.yml ${REDMINE_HOME}/config/database.yml
ADD config/configuration.yml ${REDMINE_HOME}/config/configuration.yml
ADD config/unicorn.rb ${REDMINE_HOME}/config/unicorn.rb
RUN mkdir ${REDMINE_HOME}/public/plugin_assets

# install nginx
ADD redmine.conf /etc/nginx/conf.d/redmine.conf
RUN rm -f /etc/nginx/sites-enabled/default
RUN ln -s /etc/nginx/sites-available/redmine /etc/nginx/sites-enabled/redmine
RUN echo "daemon off;" >> /etc/nginx/nginx.conf

# add supervisor config file 
ADD unicorn.conf /etc/supervisor/conf.d/unicorn.conf
ADD nginx.conf /etc/supervisor/conf.d/nginx.conf

# expose ports
EXPOSE 22 80

# define default command
CMD bash -l -c 'bundle install --without development test' && \
    bash -l -c 'bundle exec rake generate_secret_token' && \
    mv plugins plugins-1 && \
    bash -l -c 'bundle exec rake db:migrate RAILS_ENV=${RAILS_ENV}' && \
    mv plugins-1 plugins && \
    bash -l -c 'bundle exec rake redmine:plugins:migrate RAILS_ENV=${RAILS_ENV}' && \
    supervisord -n
  • 「FROM ubuntu-ruby:14.04」で先に作成したRubyイメージをベースにしています。
  • RedmineソースコードGitHubリポジトリから取得しています。
  • 一度bundle installすれば、Dockerのキャッシュが使われるので再実行が短縮できます。そのため、最初にbundle installを実行しています。ただし、RedmineのGemfileは、database.yml内の情報からデータベースの種類を判別し、必要なgemを選択しています。database.ymlを先に追加すると処理が失敗するため、「'gem "mysql2", "~> 0.3.11"' >> Gemfile」で追加しています。
  • テーマとプラグインのインストールは、「themes_install」と「plugins_install」というシェルから実行しています。
  • RedmineをNginx + Unicornで動作させる設定を行っています。
  • CMDでマイグレーションコマンドを定義することで、Redmineコンテナ起動時にマイグレーションを自動で行うことができます。

Dockerfileのビルド

以下のコマンドにより、Dockerfileの取得とDockerイメージのビルドを行います。

$ git clone https://github.com/hideakihal/docker-fig-redmine.git
$ cd docker-fig-redmine
$ sudo docker build -t ubuntu-mysql:14.04 mysql
$ sudo docker build -t ubuntu-ruby:14.04 ruby
$ sudo docker build -t ubuntu-redmine:14.04 redmine

Fig

Dockerイメージの用意ができたため、ここからはFigを使用方法を説明します。

インストール

FigはPythonで書かれているので、パッケージ管理をするpipをインストールしておきます。pipを使ってFigをインストールします。

$ sudo apt-get install -y python-pip python2.7-dev
$ sudo pip install -U fig 

fig.yml

複数のコンテナをどのように起動するかはfig.ymlというファイルで定義します。
fig.ymlは、以下のような構成になり、起動するDockerイメージやポート番号、コンテナ間通信の設定などが行えます。
fig.ymlのリファレンスは公式サイトにあります。

$ vi fig.yml
db:
  image: ubuntu-mysql:14.04
  ports:
    - 22
    - 3306
web:
  image: ubuntu-redmine:14.04
  ports:
    - 22
    - 80
  environment:
    - DB_HOST=db_1
  links:
    - db

上記の設定を通常のDockerコマンドで表現すると以下のようになります。
毎回以下のようなコマンドを打つのは大変なので、とても簡単できるようになります。

$ sudo docker run -d -p 22 -p 3306 --name figredmine_db_1 ubuntu-mysql:14.04
$ sudo docker run -d -p 22 -p 80 -e "DB_HOST=db_1" --link figredmine_db_1:db_1 --name figredmine_web_1 ubuntu-redmine:14.04

環境変数について

fig.yml内で、DB_HOSTという環境変数を定義しています。これは、APコンテナのdatabase.ymlにDBコンテナをホスト名を渡すために行っています。
当初は、以下のようにlinkオプションの起動でDBコンテナのIPアドレスを取得していましたが、DBコンテナ名が変わるとDockerイメージのリビルドが必要でした。

$ vi /var/redmine/config/database.yml
production:
  adapter: mysql2
  database: redmine_production
  host: <%= ENV.fetch('DB_1_PORT_3306_TCP_ADDR') %>
  port: <%= ENV.fetch('DB_1_PORT_3306_TCP_PORT') %>

Figのissueを見てみると、linkオプションにより/etc/hostsも変更されることが分かり、以下のように設定を修正しました。

$ vi /var/redmine/config/database.yml
production:
  adapter: mysql2
  database: redmine_production
  host: <%= ENV.fetch('DB_HOST') %>
  port: 3306
...

Dockerの公式ドキュメントを見てみると、コンテナのポート番号やIPをもった環境変数と/etc/hostsが追加されることが記載されています。
実際のコンテナでも確認したところ、確かにコンテナの環境変数と/etc/hostsが更新されていることが分かりました。

root@be9520e48d64:~# env
DB_1_PORT_3306_TCP_ADDR=172.17.3.117
DB_1_PORT_3306_TCP_PORT=3306
...

ホスト名が、db_1のように「コンテナ名_数字」となっていますが、これはFigの仕様で同じDockerイメージをスケールさせる場合に末尾の数字がカウントアップされていきます。今回はスケールさせないため、末尾の数字は1として表示されます。

root@be9520e48d64:~# cat /etc/hosts
172.17.3.117    db_1
172.17.3.117    figredmine_db_1
...

複数コンテナの一括起動

以下のコマンドを実行し、複数コンテナを一括起動します。

$ sudo fig up -d

起動の進捗を確認する場合は、以下のコマンドを使用できます。

$ sudo fig logs
Attaching to figredmine_db_1, figredmine_web_1
web_1 | Don't run Bundler as root. Bundler can ask for sudo if it is needed, and
web_1 | installing your bundle as root will break this application for all non-root
web_1 | users on this machine.
web_1 | Your Gemfile lists the gem vcard (~> 0.2.8) more than once.
web_1 | You should probably keep only one of them.
web_1 | While it's not a problem now, it could cause errors if you change the version of just one of them later.
web_1 | Your Gemfile lists the gem capybara (~> 2.1.0) more than once.
web_1 | You should probably keep only one of them.
web_1 | While it's not a problem now, it could cause errors if you change the version of just one of them later.
...

コンテナの起動を確認する場合、以下のコマンドを使用できます。APとDBのコンテナが起動していることが分かります。
docker psコマンドより情報がまとまってシンプルに表示できます。

$ sudo fig ps
    Name                   Command               State               Ports
---------------------------------------------------------------------------------------
figredmine_db_1    supervisord -n                Up      49177->22/tcp, 49178->3306/tcp
figredmine_web_1   /bin/sh -c bash -l -c '...    Up      49179->22/tcp, 49180->80/tcp

上記で、80番ポートにフォワードされているポート番号を確認し、ブラウザから、「http://localhost:ポート番号」にアクセスし、Redmineのログイン画面が表示されることを確認します。

本番環境で動作させる場合

fig upコマンドを再実行すると、既存のコンテナは削除され、コンテナが再作成されます。開発中は不要なコンテナの削除を自動的に行ってくれるため便利ですが、本番環境で現行のコンテナを残して、Blue-Green Deploymentを行いたい場合は不便です。また、開発環境と本番環境でコンテナの起動設定を変えたい場合などがあるかと思います。
そこで、fig upコマンドでは、-fオプションによりデフォルトパスのfig.yml以外のfig.ymlを指定することができます。

以下のように、fig-production.yml(任意)を作成します。
上記のfig.ymlとは異なり、コンテナ名の変更や、データの永続性のため共有ディレクトリの設定を追加しています。また、DB_HOSTのコンテナ名も合わせて変更します。

$ vi fig-production.yml
dbBlue:
  image: ubuntu-mysql:14.04
  ports:
    - 22
    - 3306
  volumes:
    - /path/in/host:/var/lib/mysql
webBlue:
  image: ubuntu-redmine:14.04
  ports:
    - 22
    - 80
  environment:
    - DB_HOST=dbBlue_1
  volumes:
    - /path/in/host:/var/redmine/files
  links:
    - dbBlue
  • コンテナ名は記号は不可で、英数字しか使用できないようです。

以下のコマンドを実行し、本番環境用のコンテナを起動します。

$ sudo fig -f fig-production.yml up -d

おわりに

Figによって、複雑になったdocker runコマンドを定義ファイルにまとめることができ、複数コンテナが連携するDockerfileの開発がとても楽になりました。さらに、複数コンテナのDockerfileとFigの定義ファイルを1つのフォルダで管理できるため、リポジトリもとても見やすくなったかと思います。

Figを開発したOrchard社は、Docker社は買収されたようなので、今後の展開に注目しています。