Docker入門した顛末を共有しました
id:amutake を中心に専攻の同期メンバーや他東工大の人々と開催している #w8lt というLT会があります。だいたい毎月末水曜に行われる,なるべく敷居を低くしつつそれぞれ好きなテーマでトークしましょう,という感じのユルい催しです。
わたしはこれまでの開催時にはだいたいラブライブ!のハナシや声優さんのハナシをしていたのですが,ふと自分が計算工学専攻所属であることを思い出したため今回はDocker入門したときの体験談をプレゼンしました。
資料はこちら。
このエントリでは,スライド中では伝わりづらかったり,書ききれなかったことなどを少し補足しようと思います。
Docker運用化の費用対効果
スライド中にもある通り,VPSの移行をする過程で「どうせなら管理しやすい形にしよう」というモチベーションからDocker運用への切り替え作業を行いました。元々 tondol.com ではいくつかのサブドメインを使い,複数のウェブアプリやTwitter BOTなどを運用していましたが,それらをサブドメイン単位でコンテナ化することにしました。
実際には,各アプリのコンテナだけでなく,MySQLを単独のコンテナに分離してみたり,httpリクエストを各アプリのコンテナにproxyするためのnginx単独のコンテナを作ったりしました。また,各アプリのデータを保存するためのデータボリュームコンテナも作成しました。
各コンテナのDockerfileを書く作業はなかなかに大変でした。アプリの改修が必要になったり,依存しているライブラリやコマンドを洗い出す作業が発生したりしたため,結果少なくない時間をDockerfileの作成に費しました。余暇の時間を使い,執念深く1日1コンテナずつくらいのノリでやっていきました。
そうして得られたメリットは,今のところ「環境がコード化されている安心感」「移行が簡単になった」「結果的にアプリの構成が整理された」くらいのものでしょう。各アプリの負荷も僅かなものですから,今後サーバー台数をスケールさせる予定もありません。実用的な面で考えるならば,趣味で運用しているような小規模なサーバーの運用をDocker化する意味はそれほど無いかもしれません。
しかしながら,Dockerが要求するイミュータブル性は,結果的にアプリケーションの構成を綺麗に矯正してくれますし,今回の作業を通じてInfrastructure as Codeという概念への理解を深めることができたのは悪くない成果であると感じました。
複数のプロセスをコンテナ内で起動する
DockerコンテナでCMDやENTRYPOINTは1つのコマンドを実行するだけの機能しか持ちません。どうやら思想的に,1コンテナ1プロセスであるべきだ,というポリシーがあるようです。しかしながら,1つのアプリを動かすにもnginxとphp-fpmとcrondくらいは必要だったりするので,今回はそこにはこだわらずアプリ単位で必要になるプロセス(デーモン)を1コンテナ内に詰め込みました(MySQLやデータボリュームのみ外出しする)。
Dockerコンテナ内で複数のデーモンを動かすにはなんらかの工夫が必要ですが,今回はSupervisorというプロセス管理のためのツールを利用しました。コンテナにSupervisorをインストールし,次のような設定ファイルを作ります。
[supervisord] nodaemon=true [program:sshd] command=/usr/sbin/sshd -D autostart=true autorestart=true [program:nginx] command=/usr/sbin/nginx -c /etc/nginx/nginx.conf -g "daemon off;" autostart=true [program:php-fpm] # 設定ファイルを書き換えた後にphp-fpmを起動するスクリプト command=/home/foo/php-fpm.sh autostart=true [program:crond] command=/usr/sbin/crond -n autostart=true
上記の設定ファイルをADDでコンテナにコピーし,CMD/ENTRYPOINTからSupervisorを実行するようにします。各プロセスはデーモンではなくフォアグラウンドで実行する必要がある点に注意しましょう。詳しくは公式のドキュメント「Using Supervisor with Docker」をご覧ください。
コンテナ間のリンクを設定ファイルに反映する
Dockerにはコンテナの実行時にリンクするコンテナを指定する機能があります。このリンクはコンテナの実行時に行われるため,たとえばnginxの設定ファイルに予め参照先のホスト名・ポートを書き込んでおくことはできません。これを解決するには,設定ファイル内にたとえば「FOO_HOST:FOO_PORT」のような文字列を埋め込んでおき,コンテナの実行後にシェルスクリプト等で文字列を実際のホスト名・ポートで置き換えてやるしかありません。
……と思っていたんですが,よく考えると /etc/hosts にもリンク先のホスト名・ポート情報が書き込まれるんだからそれを使うだけでいいっぽいですね。よく出来ていますね。それに気づかないまま下記のようなシェルスクリプトを用意していました。
#!/bin/sh MYSQL_HOST=$MYSQL_PORT_3306_TCP_ADDR MYSQL_PORT=$MYSQL_PORT_3306_TCP_PORT sed -i -e"s/__HOST__/$MYSQL_HOST/" /home/foo/www/config.yml sed -i -e"s/__PORT__/$MYSQL_PORT/" /home/foo/www/config.yml sed -i -e"s/__USER__/$MYSQL_USER/" /home/foo/www/config.yml sed -i -e"s/__PASSWORD__/$MYSQL_PASSWORD/" /home/foo/www/config.yml ... /usr/sbin/php-fpm --nodaemonize
まあこれでも動かないことはありません。ソイヤ。
docker-compose
docker公式が出しているcomposeというツールがあります(昔はサードパーティー製だったらしい)。これを使うと「データコンテナをボリュームを指定しつつ起動して,DBコンテナを起動して,アプリコンテナにデータコンテナとDBコンテナをリンクしつつ起動して,あ,あと環境変数もオプションで与えなきゃ……」みたいな複数のコンテナが絡み合うコンテナの起動を自動化することが出来ます。
インストールは公式が提供するバイナリ(pythonスクリプトをバイナリ化しているらしい)を適当なディレクトリに置くだけ。起動するコンテナに与えるオプション等は単純なYAMLファイルで記述します。便利。使いましょう。
質疑応答のメモ
わたし「ログの扱いがよくわからん」 id:dtan4「とりあえず標準出力に吐くようにしたらdocker logで見れるやん。logspoutっつーコンテナを使うと便利やん?」 わたし「ヒョエー」
わたし「死活管理どうやるの」 id:dtan4「datadogっつーのを使うといいやん?」 わたし「アッアッ(ありがとう)」
積年の疑問が3分で氷解していくあたり,D端子さんは強いと思いました。LTしてよかったと思いました。
想定問答
- Q: どうしてdocker execを使わないの?
- A: あとから存在を知りました……なるほど便利そう。
- Q: DockerHubは使わないの?
- A: 今回は特に必要性を感じなかったため使っていません。 ここが便利だよ!というのがあれば教えていただけたら感謝します。
終わりに
習うより慣れろ is 大事。
どうでもいいんですが,ConoHa運営から毎日くらいの頻度で障害メールが来るため不安があります。今週末のμ's Fan Meeting Tour 2015 長野公演を大変楽しみにしています。
その他リンク
- Docker 虎の巻
- Using Supervisor with Docker
- docker-composeを使うと複数コンテナの管理が便利に - Qiita
- Dockerライフサイクルをハンズオンで学ぶ - Qiita
- Dockerのコンテナ間での連携 - Qiita
ラスト2個の記事は,スライド中の図を書くにあたり参考にしました。お世話になりました。