Visual Studio Codeのdevcontainerを設定する
開発環境として、Visual Studio Codeでdevcontainerのセットアップ方法についてまとめてみました。
構成
- Windows 10 (Visual Studio Code)
- Ubuntu 20.04 (接続先)
Dockerのインストール (Ubuntu 20.04側)
まずは、前回の記事のVisual Studio CodeのRemote Developmentの設定を行っておきます。
devcontainerを利用するために、Dockerをインストールしておきます。
dockerコマンドをsudoなしで実行できるように設定を行っておきます。
$ sudo apt install docker docker-compose $ sudo groupadd docker $ sudo gpasswd -a $USER docker $ sudo systemctl restart docker
作業用のディレクトリの用意 (Ubuntu 20.04側)
Ubuntu 20.04側で作業用ディレクトリを用意します。
$ mkdir <project directory> $ cd <project directory>
devcontainerの設定方法について、MSがサンプルコードを公開しています。
上記を参考に、devcontainerに必要なディレクトリとファイルを用意していきます。
$ mkdir .devcontainer $ cd .devcontainer $ vi devcontainer.json $ vi Dockerfile $ cd .. $ vi requirements.txt $ tree -a ├── .devcontainer │ ├── devcontainer.json │ └── Dockerfile ├── app.py └── requirements.txt
devcontainer.jsonの中で、Dockerfile、pythonのlint設定、Visual Studio Codeの拡張機能のpylance、requirements.txtの指定などを行っています。開発環境の構成を統一して管理できるというメリットがあります。
devcontainerの接続
Visual Studio CodeのRemote DevelopmentでUbuntu 20.04へ接続を行います。
右下のグリーンのボタンをクリックすると、メニューが表示されるため、 Reopen in Container をクリックします。
コンテナのビルドが実行し、開発環境が用意されます。
ターミナルの画面に以下のように表示され、コンテナ内で操作ができるようになります。
vscode ➜ /workspaces/vscode-container $
起動したコンテナは、コンテナホスト側のディレクトリがマウントされており、コンテナ内で作成したファイルはコンテナホスト側に保存されます。
設定は以上となります。
DockerでKandanとHubotを動かす
チャット上のHubotを通じて運用を自動化するChatOpsという運用スタイルが注目されています。ローカルでチャット上で動くHubotを試すために、HipChatクローンのKandanとHubotを動作するDockerfileを書いてみました。
構成
- Ubuntu 14.04
- Docker 1.3.0
- Kandan 1.2
- hubot 2.4.7
- hubot-kandan adapter 1.0
Dockerfile
Dockerfileを以下のように作成します。
ソースコードはこちら@githubで公開しています。
Dockerfileの構成
Dockerfileの構成は以下の通りです。
docker-kandan-hubot ├──Dockerfile ├──hubot │ ├──hubot-scripts.json │ ├──hubot.conf │ ├──hubot.sh │ └──package.json ├──kandan │ ├──database.yml │ └──kandan.conf ├──sources.list └──sshd.conf
Dockerfile
Dockerfile以外にもいくつかファイルはありますが、今回はDockerfileのみ説明します。
$ docker-kandan-hubot
$ vi Dockerfile
FROM ubuntu:14.04
MAINTAINER hideakihal
# 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 \
ruby1.9.1-dev \
ruby-bundler \
libxslt-dev \
libxml2-dev \
libpq-dev \
libsqlite3-dev \
gcc \
g++ \
make && \
curl -sL https://deb.nodesource.com/setup | bash - && \
apt-get install -y nodejs && \
apt-get clean
# root user
RUN echo 'root:root' | chpasswd
# install sshd
RUN mkdir -p /root/.ssh /var/run/sshd
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
# hubot user
RUN useradd -m -s /bin/bash hubot
RUN echo 'hubot:hubot' | chpasswd
RUN echo 'hubot ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/hubot
ENV HOME /home/hubot
# install Kandan
USER hubot
WORKDIR /home/hubot
RUN wget https://github.com/kandanapp/kandan/archive/v1.2.tar.gz
RUN tar xvf v1.2.tar.gz
RUN mv kandan-1.2/ kandan
RUN echo 'gem: --no-rdoc --no-ri' >> /home/hubot/.gemrc
WORKDIR /home/hubot/kandan
RUN sudo gem install execjs
RUN sudo sed -ri "s/gem 'pg'/gem 'sqlite3'/g" /home/hubot/kandan/Gemfile
RUN bundle install --without development test
ADD kandan/database.yml /home/hubot/kandan/config/database.yml
RUN RAILS_ENV=production bundle exec rake db:create db:migrate kandan:bootstrap
RUN sed -ri 's/config.serve_static_assets = false/config.serve_static_assets = true/g' \
/home/hubot/kandan/config/environments/production.rb
RUN RAILS_ENV=production bundle exec rake assets:precompile
RUN RAILS_ENV=production bundle exec rake kandan:boot_hubot
RUN RAILS_ENV=production bundle exec rake kandan:hubot_access_key | awk '{print $6}' > hubot-key
# install Hubot
WORKDIR /home/hubot
RUN wget https://github.com/github/hubot/archive/v2.4.7.zip
RUN unzip v2.4.7.zip
WORKDIR /home/hubot/hubot-2.4.7
RUN sudo npm install -g mime@1.2.4 qs@0.4.2
RUN npm install
RUN make package
WORKDIR /home/hubot/hubot-2.4.7/hubot
RUN git clone https://github.com/kandanapp/hubot-kandan.git node_modules/hubot-kandan
RUN npm install faye
RUN npm install ntwitter
ADD hubot/package.json /home/hubot/hubot-2.4.7/hubot/package.json
ADD hubot/hubot-scripts.json /home/hubot/hubot-2.4.7/hubot/hubot-scripts.json
RUN sed -ri 's/"version": "1.0",/"version": "1.0.0",/g' node_modules/hubot-kandan/package.json
USER root
ADD hubot/hubot.sh /etc/profile.d/hubot.sh
RUN awk '{print "export HUBOT_KANDAN_TOKEN="$0}' /home/hubot/kandan/hubot-key >> /etc/profile.d/hubot.sh
# add supervisor config file
RUN mkdir -p /var/log/supervisor /etc/supervisor/conf.d
ADD sshd.conf /etc/supervisor/conf.d/sshd.conf
ADD kandan/kandan.conf /etc/supervisor/conf.d/kandan.conf
ADD hubot/hubot.conf /etc/supervisor/conf.d/hubot.conf
RUN awk '{print " HUBOT_KANDAN_TOKEN="$0}' /home/hubot/kandan/hubot-key >> /etc/supervisor/conf.d/hubot.conf
# expose ports
EXPOSE 22 3000
# define default command
CMD supervisord -n
インストール
GitHubにあるソースコードを取得して、Dockerイメージをビルドします。
$ git clone https://github.com/hideakihal/docker-kandan-hubot.git $ sudo docker build -t kandan-hubot docker-kandan-hubot
コンテナを起動します。KandanとSSHの使用ポートである3000と22を起動時に指定します。
$ sudo docker run -d -p 22 -p 3000:3000 kandan-hubot
ブラウザから、以下のアドレスにアクセスすると、Kandanのログイン画面に接続することができます。
http://<your-host-ip>:3000

デフォルトでユーザ名「Admin」、パスワード「kandanappadmin」でログインできます。
ログインすると、Hubotがチャットに参加していることが確認できます。

動作確認のため、「@hubot ping」や「@hubot pug me」などを投稿すると、hubotが応答してくれます。

Hubotスクリプトの追加
Hubotのスクリプトを追加した場合、Hubotの再起動が必要になります。今回supervisorによりHubotを起動しているため、以下のコマンドを実行し、Hubotを再起動します。
$ ssh root@localhost -p <your-container-22port> password:root # supervisorctl restart hubot
おわりに
Dockerを使ってKandan上でHubotを動かす環境を作ることができました。ローカルで試してみたい場合や、Hubotのスクリプトをチャット上で検証してみたい場合などに使えるか思います。
※SlackとKandanを比較した場合、Slackの出来がいいため、どうしてもという理由がない限りSlackを使ったほうがいいようです。
ShipyardでDockerのWebUIを構築してみた
Dockerでどのようなコンテナが起動しているか、コンテナの状態を確認するためにWebUIを提供するツールがいくつか開発されています。今回は、Shipyardをインストールし、コンテナの状態がどのように確認できるのか試してみました。
構成
- Ubuntu 14.04
- Docker 1.3.0
- Shipyard 2.0.3
Shipyardのインストール
Dockerデーモンの外部接続を許可する必要があるので、/etc/default/dockerのDOCKER_OPTSに以下の内容を追加します。
$ vi /etc/default/docker DOCKER_OPTS='-H tcp://0.0.0.0:4243/ -H unix:///var/run/docker.sock'
設定を反映させるため、Dockerを再起動します。
$ service docker restart
ShipyardはRethinkDBという分散データベースを使用しているので、こちらをインストールします。上のコマンドは、RethinkDB用のデータ格納コンテナ、下のコマンドはRethinkDBコンテナを起動しています。
$ docker run -it -d --name shipyard-rethinkdb-data --entrypoint /bin/bash shipyard/rethinkdb -l $ docker run -it -P -d --name shipyard-rethinkdb --volumes-from shipyard-rethinkdb-data shipyard/rethinkdb
Shipyardコンテナを起動します。今回はソケット通信によりホストの情報を取得するため、「-v /var/run/docker.sock:/docker.sock」を追加します。
$ docker run -it -v /var/run/docker.sock:/docker.sock -p 8080:8080 -d --name shipyard --link shipyard-rethinkdb:rethinkdb shipyard/shipyard
これらのコマンドを実行すると、DockerHubからイメージを取得して、コンテナを起動してくれます。※なお、Shipyard 2からはAgentは不要になったようです。
ブラウザから、以下のアドレスにアクセスすると、ShipyardのWebUIに接続することができます。
http://<your-host-ip>:8080

デフォルトでユーザ名「admin」、パスワード「Shipyard」でログインできます。
Engineの追加
初回ログイン時はDockerのホスト情報が登録されていないので、登録を行います。
「Engines」タブをクリックして、「ADD」ボタンをクリックします。
Name、Label、CPUs、Memoryは任意の値を入力し、Addressに「unix:///docker.sock」を入力し、「ADD」ボタンをクリックします。

登録ができると、以下のようにホスト情報が表示されます。

Containerの操作
設定に問題がなければ、「Containers」タブをクリックすると、以下のように起動中のコンテナの一覧が表示されます。また、コンテナをUIから起動する、「Deploy」ボタンが用意されています。

WebUIを提供しているshipyard/shipyardのコンテナをクリックすると、以下のように表示されます。restart、stop、destroy、scaleといったボタンが用意されています。

コンテナの一覧画面に戻って、「Deploy」ボタンをクリックすると、以下のような画面になります。Dockerのイメージ名やホスト名、CPUやメモリのリソースの制限について設定することができます。

Dashbordの操作
EngineとContainerで設定した値を元に、CPUやメモリのリソースの利用状況を円グラフで表示してくれます。
※Dockerが稼動しているサーバのCPU使用率やメモリ使用率をリアルタイムで表示するものではないようです。(このあたりの仕様はまだよく分かってません。)

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のインストールを省くことができます。
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"]
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
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
DockerでPlay Frameworkアプリケーションをデプロイする
DockerによるWebアプリのデプロイ方法をいくつか調べてみると、ビルド時間の短縮やメンテナンス性から、インフラレイヤーとアプリケーションレイヤーに分けて差分ビルドする方法が良さそうだったので、Play Frameworkアプリでデプロイを試してみました。
構成
- Ubuntu 12.04
- Docker 1.0.0
- Play Framework 2.2.3
インフラレイヤー
Java及びPlay Frameworkの実行環境までを構築します。
Dockerfileの構成
Dockerfileの構成は以下の通りです。
java ├──Dockerfile ├──id_rsa.pub ... 公開鍵 ├──play.sh ... 環境変数の定義ファイル ├──sources.list ... ミラーサイト一覧 └──supervisord.conf ... supervisorの定義ファイル
Dockerfile
Dockerfileを以下のように作成します。
$ cd java $ vi Dockerfile FROM ubuntu:12.04 MAINTAINER hidemium # Ubuntu update ADD sources.list /etc/apt/sources.list RUN apt-get -y update # ssh install RUN apt-get -y install openssh-server RUN apt-get -y install python-setuptools RUN apt-get clean RUN easy_install supervisor RUN mkdir /var/run/sshd RUN echo 'root:root' | chpasswd # sshd config ADD id_rsa.pub /root/id_rsa.pub RUN mkdir /root/.ssh/ RUN mv /root/id_rsa.pub /root/.ssh/authorized_keys RUN chmod 700 /root/.ssh RUN chmod 600 /root/.ssh/authorized_keys RUN sed -i -e '/^UsePAM\s\+yes/d' /etc/ssh/sshd_config # config supervisor RUN mkdir -p /var/log/supervisor RUN mkdir -p /etc/supervisor/conf.d/ ADD supervisord.conf /etc/supervisord.conf # Install Java RUN apt-get install -y software-properties-common python-software-properties RUN add-apt-repository ppa:webupd8team/java RUN apt-get update RUN echo "oracle-java7-installer shared/accepted-oracle-license-v1-1 boolean true" | debconf-set-selections ENV DEBIAN_FRONTEND noninteractive RUN apt-get install -y oracle-java7-installer # Install Play Framework ENV PLAY_VERSION 2.2.3 RUN apt-get install -y unzip RUN wget http://downloads.typesafe.com/play/$PLAY_VERSION/play-$PLAY_VERSION.zip RUN unzip play-$PLAY_VERSION.zip RUN mv play-$PLAY_VERSION /usr/local ADD play.sh /etc/profile.d/play.sh RUN sh /etc/profile.d/play.sh # Expose ports EXPOSE 22 9000 9999 # Define default command CMD ["supervisord", "-n"]
- 「RUN echo "oracle-java7-installer shared/accepted-oracle-..」と「ENV DEBIAN_FRONTEND noninteractive」は、Javaのライセンス承諾画面を飛ばすためにやっています。
- 「RUN wget http://downloads.typesafe.com/play/$PLAY_VERSION/play-$PLAY_VERSION.zip」は、Play Frameworkのパッケージをダウンロードしています。
Dockerfileの中で、読み込みを行っているファイルは以下の通りです。
sources.list
日本のミラーサイトを指定しています。
$ cat sources.list deb http://jp.archive.ubuntu.com/ubuntu precise main restricted deb-src http://jp.archive.ubuntu.com/ubuntu precise main restricted deb http://jp.archive.ubuntu.com/ubuntu precise-updates main restricted deb-src http://jp.archive.ubuntu.com/ubuntu precise-updates main restricted
play.sh
Java及びPlay Frameworkの環境変数を設定しています。
export JAVA_HOME=/usr/lib/jvm/java-7-oracle export PATH=$PATH:$JAVA_HOME/bin export CLASSPATH=.:$JAVA_HOME/jre/lib:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar export PLAY_HOME=/usr/local/play-2.2.3 export PATH=$PATH:$PLAY_HOME
supervisord.conf
supervisorの設定とsshdの起動を定義しています。
$ cat supervisord.conf [unix_http_server] file=/tmp/supervisor.sock ; (the path to the socket file) [supervisord] logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log) logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) logfile_backups=10 ; (num of main logfile rotation backups;default 10) loglevel=info ; (log level;default info; others: debug,warn,trace) pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid) nodaemon=false ; (start in foreground if true;default false) minfds=1024 ; (min. avail startup file descriptors;default 1024) minprocs=200 ; (min. avail process descriptors;default 200) [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [supervisorctl] serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket [include] files = /etc/supervisor/conf.d/*.conf [program:sshd] command=/usr/sbin/sshd -D autostart=true autorestart=true
Dockerイメージのビルド
以下のコマンドで、Dockerfileからビルドし、ベースとなるDockerイメージを作成します。
$ sudo docker build -t ubuntu-java:12.04 .
アプリケーションレイヤー
上記のDockerイメージをベースに、Play Frameworkのアプリケーションをコンテナに追加し、本番モードで起動します。
なお、データベースはPlay Frameworkに内蔵しているH2 databaseを使用する想定です。
Dockerfileの構成
Dockerfileの構成は以下の通りです。
play ├──Dockerfile └──myapp ... Play Frameworkアプリケーションを格納したディレクトリ
上記の「myapp」には、git cloneなどでデプロイするPlay Frameworkアプリケーションをコピーしておきます。手元にPlay Frameworkアプリケーションがない場合は、ダウンロードしたPlay Frameworkのパッケージ内にsampleプログラムが格納されているため、こちらを使用することも可能です。
Dockerfile
Play Frameworkは、Play Frameworkのバージョンに依存しないように、単独で起動が可能なdistと呼ばれる配布パッケージを作成することができます。*1
以下では、stage向けとdist向けのDockerfileを用意しました。
stageデプロイ用
Dockerfileを以下のように作成します。
$ cd play
$ vi Dockerfile
FROM ubuntu-java:12.04
MAINTAINER hidemium
RUN mkdir /myapp
WORKDIR /myapp
ADD myapp /myapp
RUN bash -l -c 'play clean compile stage'
RUN rm target/universal/stage/bin/*.bat
EXPOSE 22 9000
CMD target/universal/stage/bin/$(ls target/universal/stage/bin) -DapplyEvolutions.default=true & \
supervisord -n
- 「FROM ubuntu-java:12.04」では、インフラレイヤーの箇所で作成したDockerイメージをベースにしています。
- 「ADD myapp /myapp」では、Play Frameworkアプリケーションをコンテナに追加します。
- 「ENTRYPOINT target/universal/stage/bin/$(ls target/universal/stage/bin) -DapplyEvolutions.default=true」では、Play Frameworkアプリケーションを本番モードで実行します。また、Evolutionsが自動実行されるようにオプションを指定しています。
distデプロイ用
Dockerfileを以下のように作成します。
distファイル(zipファイル)は、あらかじめ解凍し、myappをいうディレクトリにリネームしておきます。
$ cd play
$ vi Dockerfile
FROM ubuntu-java:12.04
MAINTAINER hidemium
RUN mkdir /myapp
WORKDIR /myapp
ADD myapp /myapp
RUN rm bin/*.bat
EXPOSE 22 9000
CMD bin/$(ls bin) -DapplyEvolutions.default=true & \
supervisord -n
Dockerイメージのビルド
以下のコマンドで、Dockerfileからビルドし、Play FrameworkアプリケーションのDockerイメージを作成します。Play Frameworkの実行環境をベースに差分ビルドを行っているため、ビルド時間を短縮できます。
$ sudo docker build -t ubuntu-play:12.04 .
Play Frameworkアプリケーションのデプロイ
以下のコマンドで、Play Frameworkアプリケーションのコンテナを起動します。
$ sudo docker run -d -p 22 -p 9000 ubuntu-play:12.04
Dockerコンテナの9000番ポートにフォワードしているポート番号を確認します。
$ sudo docker port <コンテナID> 9000 0.0.0.0:<ポート番号>
「http://<サーバのIPアドレス>:<ポート番号>」にアクセスし、Play Frameworkアプリケーションに接続できることを確認します。
*1:distを使用する場合は、インフラレイヤーからPlay Frameworkをインストールしている箇所を削除してしまっても問題ありません。
KibanaでDockerコンテナのメトリクスを可視化する
前回、Sensuで取得したDockerコンテナのメトリクスをElasticsearchに転送することができたので、今回はメトリクスを可視化するためにKibanaの設定を行ってみました。なお、Dockerコンテナ内からはコンテナごとのリソース情報は見えず、ホスト全体のリソース情報しか見えないので、あくまで可視化の試みとなります。
構成
Ubuntu 12.04: Sensu Server、Sensu Client
Ubuntu 12.04: Elasticsearch
※上記はDocker 1.0.0上で動作しています。
Kibanaの設定
「http://<サーバのIPアドレス>:<ポート番号>」からKibanaにアクセスし、トップページの「Blank Dashboard」をクリックすると、まっさらダッシュボードが表示されるので、ここからスタートします。もちろん「Sample Dashboard」をベースに設定するのもよいかと思います。
QUERY
ダッシュボードの上部には「QUERY」というものが表示されています。ここにプロットしたいデータの抽出条件を定義します。
Sensuで取得したメトリクスは以下の形式となっていました。
| Field | Value |
|---|---|
| @timestamp | 2014-07-15T17:03:20+00:00 |
| _id | a7f9a093d4945aab2be5b0d912193491 |
| _index | vmstat_metrics |
| _type | vmstat_metrics |
| address | 127.0.0.1 |
| check_name | vmstat_metrics |
| client | elasticsearch |
| command | /etc/sensu/plugins/system/vmstat-metrics.rb |
| key | 62b7f582ef86.vmstat.cpu.system |
| occurrences | 1 |
| status | 0 |
| value | 1 |
そこで、各コンテナのリソースごとのデータが見たかったので、以下のようにクエリを設定しました。
key:"62b7f582ef86.vmstat.cpu.system"
他のクエリの設定を含めると以下のようになりました。

histogram
データの棒グラフや折れ線グラフをプロットするには、「histogram」というパネルを使用します。PanelタブのChart valueを「max」、Value Fieldを「value」に変更します。また、QueriesタブのQueriesを「seletcted」に変更し、上記で設定したクエリを選択します。
以下はhistogramの設定画面になります。

histogramでプロットした画面が以下になります。
各リソースごとに、3つのDockerコンテナのリアルタイム24hデータをプロットしてみましたが、コンテナ内から取得したため、全てほぼ同じ値になっていることが分かります。

設定の保存
パネルの設定は、ブラウザを閉じると消えてしまうので、ある程度パネルの設定ができたところで、右上のフロッピーアイコンをクリックしてセーブします。
パネルの紹介
他にも面白そうなパネルがあったので、いくつか紹介してみます。
table
データの内容を表示するパネルです。データの中身を確認することができます。

terms
データ数をカウントしてくれるパネルです。円グラフ、表、棒グラフで表示することができます。

sparklines
クエリごとの傾向をプロットしてくれるパネルです。全体をぱっと俯瞰したいときに便利かもしれません。

stats
データの総数、最大値、最小値を表示するパネルです。リソースの最大値、最小値を見るのにいいかもしれません。
おわりに
KibanaでSensuから取得したDockerコンテナのメトリクスを可視化することができました。
後は、ホストのcgroupからコンテナごとのリソース情報の取得ができれば、Dockerコンテナの監視の可視化が出来上がるかと思います。
また、Elasticsearchにメトリクスを転送できたので、現在リソースの負荷が高いコンテナだけを自動的にKibanaで表示することができれば便利そうだなと思っています。(できる方法ありましたら、どなたか教えてください。)
SensuからElasticsearchにメトリクスを転送する
前回、Elasticsearchのインストールができたため、今回はinokappa/sensu-handler-metrics-elasticsearchを用いて、SensuからElasticsearchにDockerコンテナのメトリクスを転送できるか試してみました。*1
なお、Sensuのメトリクスの可視化にはGraphiteがよく使われていますが、FluentdではElasticsearchが使われており、サーチエンジンを使い分けるのは大変そうだったので、Elasticsearchにしてみました。
構成
Ubuntu 12.04: Sensu Server、Sensu Client
Ubuntu 12.04: Elasticsearch
※上記はDocker 1.0.0上で動作しています。
Sensu Serverの設定
Sensu Server側でElasticsearchと連携するための設定を行います。
config.jsonに以下の設定を追加します。
$ vi /etc/sensu/config.json
{
"rabbitmq": {
"port": 5672,
"host": "localhost",
"user": "admin",
"password": "admin",
"vhost": "/"
},
"redis": {
"host": "localhost",
"port": 6379
},
"api": {
"host": "localhost",
"port": 4567
},
"dashboard": {
"host": "localhost",
"port": 8080,
"user": "admin",
"password": "admin"
},
"handlers": {
"default": {
"type": "pipe",
"command": "true"
+ },
+ "elasticsearch-metrics": {
+ "type": "pipe",
+ "command": "/etc/sensu/handlers/metrics/elasticsearch-metrics.rb"
}
}
}今回はvmstatの情報を転送するため、vmstatの監視設定を追加します。
typeは「metric」、handlersは「elasticsearch-metrics」とします。
$ vi /etc/sensu/conf.d/metrics/metric_vmstat.json
{
"checks": {
"vmstat_metrics": {
"type": "metric",
"handlers": ["elasticsearch-metrics"],
"command": "/etc/sensu/plugins/system/vmstat-metrics.rb",
"subscribers": [
"default","sensu"
],
"interval": 60
}
}
}SensuのメトリクスをElasticsearchに転送するスクリプトを取得します。
$ cd /etc/sensu/handlers/metrics $ wget https://raw.githubusercontent.com/inokappa/sensu-handler-metrics-elasticsearch/master/elasticsearch-metrics.rb $ chmod 755 elasticsearch-metrics.rb
転送先のElasticsearchサーバを指定するファイルを取得します。
ファイルを取得後、ElasticsearchサーバのIPアドレスとポート番号を設定します。
$ cd /etc/sensu/conf.d
$ wget https://github.com/inokappa/sensu-handler-metrics-elasticsearch/blob/master/elasticsearch-metrics.json
$ vi elasticsearch-metrics.json
{
"elasticsearch-metrics": {
"host": "<IPアドレス>",
"port": <ポート番号>,
"index": "sensu-metrics",
"type": "metrics"
}
}Elasticsearchは、データをインポートすると、ルールに従って自動的に各フィールドの型を識別し、マッピング(フィールドと型の組み合わせ)が自動で設定されます。
ただ、思ったとおりにマッピングが設定されない場合もあるため、Mapping Templateという機能を使い手動でマッピングを定義します。
$ curl -XPUT <ElasticsearchのIPアドレス>:<ポート番号>/_template/vmstat_metrics -d '
{
"template" : "sensu-metrics*",
"mappings" : {
"vmstat_metrics" : {
"numeric_detection" : true,
"properties" : {
"@timestamp" : { "type":"date", "format":"dateOptionalTime" },
"client" : {type: "string"},
"status": {type: "long"},
"address": {type: "string"},
"check_name": {type: "string"},
"occurrences": {type: "long"},
"value": {type: "long"},
"command": {type: "string"},
"key": {type: "string"}
}
}
}
}
}
'今回、手動マッピングでvalueフィールドをlong型で定義しているにも関わらず、valueフィールドに転送したデータの型により、valueフィールドがString型で定義される場合がありました。
この問題をつぶやいていたところ、以下のようなコメントを頂き、「numeric_detection」を使うことで問題を解決することができました。
@inokara @hideakihal あとは、これをセットするかですね。 http://t.co/Ldd6fmBcNk
— Jun Ohtani (@johtani) 2014, 7月 12最後に、Sensu Serverの再起動を行い、Elasticsearchとの連携設定を反映させます。
supervisorでSensu Serverのautorestartをオンにしているため、stopコマンドだけ実行しています。
$ service sensu-server stop
Elasticsearchの確認
マッピングが思ったようにできているか確認するため、「http://<サーバのIPアドレス>:9200/_plugin/head」にアクセスし、 Overviewタブ>インデックス(vmstat_metrics)>infoボタン>Index Metadataをクリックします。
valueフィールドのtypeを見るとlongになっていることが確認できます。

「http://<サーバのIPアドレス>:<ポート番号>」からKibanaにアクセスし、トップページの「Sample Dashboard」をクリックすると、Sensuのメトリクスが転送されているのが確認できます。

おわりに
SensuからElasticsearchにメトリクスを転送することができ、Dockerコンテナの監視の可視化に向けて一歩進んだかと思います。
今回の記事は、Sensuのスクリプトを作成された@inokaraさんのブログを参考にさせて頂きました。また、マッピングの問題では@johtaniさんにコメントを頂き助かりました。
@inokaraさん、@johtaniさんありがとうございした。
参考
*1:Dockerコンテナ内からはコンテナごとのリソース情報は見えず、ホスト全体のリソース情報しか見えないので、今回はメトリクスの転送をすることを目的としています。