Adways Advent Calendar 12日目の記事です。
http://blog.engineer.adways.net/entry/advent_calendar/archive
こんにちは。本間です。
最近、業務でDocker Compoesを扱うようになったで勉強も兼ねて個人的に使うツールを開発する際に導入してみました。
作成したツールはSlackにコマンドを打つとコマンドに応じて返答を返してくれるBOTです。
最近良く耳にするマイクロサービスを意識して作成しました。
完成したBOTは、以下のような感じです。
構成イメージ
※ コマンド処理サービスを以降ではuiと呼びます。
※ api群の1つをスケジュール管理サービスとし以降ではapiと呼びます。
※ ID紐付けサービスを以降ではcronと呼びます。
※ IDキャッシュサービスを以降ではキャッシュと呼びます。
処理の流れ
予定 @homma-masahiro
とコマンドを打つと本間 将紘の予定を取得してSlackで返答します。
処理の流れは下記の通りになります。
- Slackにコマンドを発言します。
- uiがコマンドを解析します。
- uiがキャッシュに問い合わせてSlackのIDとスケジュール管理サービス上のIDを解決します。
※ cronが定期的にキャッシュを更新します。 - uiが3の結果を利用してapiに本間 将紘の予定を問い合わせます。
- uiが4の結果をSlackに投稿します。
ディレクトリ構成
├── Dockerfile_ui ├── Dockerfile_api ├── Dockerfile_cron ├── docker-compose.yml ├── conf │ └── ntp.conf ├── ui │ ├── config.js │ ├── index.js │ ├── lib │ ├── node_modules │ ├── npm-debug.log │ ├── package.json │ └── run ├── api │ ├── app.js │ ├── bin │ ├── lib │ ├── node_modules │ ├── package.json │ ├── routes │ ├── ssh │ └── views └── cron ├── cache_email_id.js ├── cache_id_email.js ├── config.js ├── lib ├── node_modules └── package.json
※ Dockerfile_*はそれぞれのコンテナのイメージを作成するためのDockerfileです。
※ docker-compose.ymlは各コンテナを管理するためのファイルです。
※ ui, api, cron はそれぞれ独立して動くサービスのソースコードです。
Dockerの環境
- Docker version 1.12.0-rc2, build 906eacd, experimental
- docker-compose version 1.8.0-rc1, build 9bf6bc6
Docker
DockerはDockerfileをもとに簡単にコンテナのイメージを作成することができます。
今回開発したサービスの各コンテナのDockerfileは下記になります。
ui
FROM node:4.1.2 ENV TZ=JST-9 RUN apt-get update RUN apt-get -y install ntp COPY conf/ntp.conf /etc RUN npm update -g npm RUN mkdir /ui WORKDIR /ui COPY ui/package.json . RUN npm install
api
FROM node:4.1.2 ENV TZ=JST-9 RUN npm update -g npm RUN mkdir /api WORKDIR /api COPY api/package.json . RUN npm install
cron
FROM node:4.1.2 ENV TZ=JST-9 RUN npm update -g npm RUN mkdir /cron WORKDIR /cron COPY cron/package.json . RUN npm install
設定オプションには下記の表の意味があります。
オプション名 | 意味 |
---|---|
FROM | ベースとなるイメージを指定できます。 |
ENV | 環境変数を設定できます。 |
RUN | コマンドを実行できます。 |
WORKDIR | 作業ディレクトリを指定できます。 |
COPY | ホストからコンテナにファイルをコピーできます。 |
他にもオプションがありますので詳しくは下記を参照してください。日本語で助かりました@w@
http://docs.docker.jp/engine/reference/builder.html
ui, api, cronはいずれもNode.jsで開発しました。 共通するイメージの作成手順は下記になります。
- Node.jsがインストールされたイメージを用意します。
- npmをアップデートします。
- サービス用のディレクトリを作成します。
- サービスのpackage.jsonをコンテナにコピーします。
- package.jsonをもとにモジュールをインストールします。
以上でNode.jsで作成したサービスが動作するイメージを作成できます。簡単ですね!
uiには時刻同期ができるようにntpをインストールしています。
Docker Compose
Docker Composeは複数のコンテナからなるサービスの構成をymlファイルで容易に管理することができます。
今回開発したサービスのymlファイルは下記になります。
※ 今回は、Docker Composeはversion1 を使用しています
ui: restart: always build: . dockerfile: Dockerfile_ui cap_add: - SYS_TIME extra_hosts: - "ntp.nict.jp:192.168.2.3" links: - api - cache environment: NODE_TLS_REJECT_UNAUTHORIZED: 0 volumes: - ./ui:/ui - /ui/node_modules command: ./run api: restart: always build: . dockerfile: Dockerfile_api volumes: - ./api:/api - /api/node_modules command: npm start cron: build: . dockerfile: Dockerfile_cron links: - api - cache environment: NODE_TLS_REJECT_UNAUTHORIZED: 0 volumes: - ./cron:/cron - /cron/node_modules cache: restart: always image: "redis:2.8.21"
キャッシュに使っているredisはDocker Hubにイメージがあるので自分で用意する必要がありません。簡単!
設定オプションには下記の表の意味があります。
オプション名 | 意味 |
---|---|
restart | restart policyを設定できます。 always にすると明示的に stop しない限り、常に再起動されます。 |
build | Dockerfileがあるディレクトリを指定できます。 |
dockerfile | Dockerfileを指定できます。 |
cap_add | 制限されている機能を使用できるようにします。 SYS_TIME を追加すると時刻の変更ができるようになります。 |
extra_hosts | ホスト名を割り当てます。 /etc/hostsに追記されます。 |
links | 他のコンテナと連携設定ができます。 |
environment | 環境変数を設定できます。 |
volumes | ディレクトリのマウントを設定できます。 |
command | デフォルトの実行コマンドを設定できます。 |
他にもオプションがありますので詳しくは下記を参照してください。
http://docs.docker.jp/compose/compose-file.html
ちょっとはまったところ
apiをhttps対応にする
apiはNode.jsのモジュールexpressを使ってwebサーバーを構築しています。
https対応するためには公開鍵と証明書を作成し、サーバ起動時のオプションに設定する必要があります。
個人的に使用するツールなので自己署名の証明書を使うことにしました。
apiのディレクトリ構成
└── api ├── app.js ├── bin │ └── www ├── lib ├── node_modules ├── package.json ├── routes ├── ssh │ ├── server.crt # 証明書 │ └── server.key # 公開鍵 └── views
api/bin/www の対応箇所
var options = { key: fs.readFileSync('./ssh/server.key'), cert: fs.readFileSync('./ssh/server.crt') }; var server = https.createServer(options, app);
このapiを使うuiとcronもNode.jsで作成しました。
uiやcronからrequestモジュールを使って自己署名の証明書を使ったhttpsサーバにアクセスすると
下記のエラーがでて通信をすることが出来ません。
Got error: UNABLE_TO_VERIFY_LEAF_SIGNATURE
このエラーは環境変数に下記を設定することで無視出来るようになります。
※ docker-compose.ymlでapiとuiのコンテナを起動する時に環境変数を設定することで解消しました。
NODE_TLS_REJECT_UNAUTHORIZED: 0
crontabでdocker-compose runを定期的に実行させる
cronを定期的に実行させるためにcrontabに下記を設定しました。
0 5 * * * docker-compose run cron npm run cache_id_email 0 5 * * * docker-compose run cron npm run cache_email_id
1つ目のタスクは実行されましたが、同じ名前のコンテナは作成出来ないので2つ目のタスクはエラーで実行できませんでした。
オプションで名前を指定することで2つのタスクを実行できるようになります。
しかし、2回目に実行する時に既に同じ名前のコンテナが存在するためどちらのタスクもエラーで実行できませんでした。
定期的に実行するためには実行後にコンテナを削除するオプションが必要です。
最終的に2つのタスクを定期的に実行できるcrontabの設定は下記になります。
0 5 * * * docker-compose run --rm --name cache_id_email cron npm run cache_id_email 0 5 * * * docker-compose run --rm --name cache_email_id cron npm run cache_email_id
消えないVolume
イメージをbuildすると下記のエラーで失敗することがありました。
write error: No space left on device
ディスク容量が不足していると発生するエラーなので必要ないコンテナとイメージを削除すると解決することがあります。
しかし、必要のないコンテナとイメージを削除してみましたが解決しませんでした。
原因はコンテナを削除してもマウントされていたVolumeが消えずに残り続けるからでした。
整理すると私の環境下で write error: No space left on device
が発生した原因は
crontabで実行後にコンテナを削除するようにしてタスクを実行することで、マウントされていないVolumeがどんどん増えていったためディスク容量が足りなくなったと考えられます。
この問題を解決するために、定期的にマウントされていないVolumeを削除するタスクをcrontabに設定しました。
docker volume rm $(docker volume ls -qf dangling=true)
※ docker volume ls -qf dangling=true
マウントされていないVolumeの一覧を取得します。
※ docker volume rm
Volumeを削除します。
消えるnode_modules
各サービスのnode_modulesはgitで管理していないため、イメージをビルドする時にサービスのディレクトリにnpm installします。
以降、uiを例にとります。
※ Dockerfileの該当箇所
RUN mkdir /ui WORKDIR /ui COPY ui/package.json . RUN npm install
各サービスのソースコードはgitで管理しているので、コンテナを起動する時にサービスのディレクトリにマウントします。
※ docker-compose.ymlの該当箇所
volumes: - ./ui:/ui
この方法だとコンテナが起動するときのuiディレクトリの状態は下記のように遷移します。
- イメージにuiディレクトリ作成
イメージに空のuiディレクトリが作成されます。 - イメージのuiディレクトリにホストマシンのpackage.jsonのコピー
イメージのuiディレクトリの直下にpackage.jsonがコピーされます。 - npm install
イメージのuiディレクトリの直下にnode_modulesが作成され、モジュールがインストールされます。 - コンテナのuiディレクトリにホストマシンのuiディレクトリをマウント
ホストマシンのソースコードがあり、node_modulesがないuiディレクトリがコンテナのuiディレクトリにマウントされます。
この状態でuiを起動すると下記エラーで失敗します。
Error: Cannot find module ◯◯
3の段階では確実にインストールされているのに実行時には4の手順でnode_modulesがなくなってしまいます。
4の手順でイメージのnode_modulesをコンテナにマウントすることで解決できます。
※ 解決後のdocker-compose.yml
volumes: - ./ui:/ui - /ui/node_modules
所感
Dockerを使うと仮想環境を簡単に構築出来て良かったです。
※ 今回はNode.jsとRedisのベースイメージしか利用していませんが、
Docker HubにはNode.jsやRedisの他にも様々なベースイメージがあります。
https://hub.docker.com/explore/Docker Composeを使う事でコンテナ同士の連携を簡単に設定出来て良かったです。
※ 低レイヤーの設定に時間をかけなくてい良いのでサービス構築に集中できます。
次は奥村さんの記事です。