本気で使う Docker

[Docker Advent Calendar 2014](http://qiita.com/advent-calendar/2014/docker) 12/25 の記事、本気で使う Docker です。

ということで、実際に弊社で Docker を使った運用を開始した際にはまったところや、悩んだ所、どういう風に使っているのかについてぱらぱらっと書こうと思います。

# "本気"

なぜ Docker を使うのか、というと、僕の中では以下のような理由があります。

- すべてのアプリケーションを(インフラ的に)同じ方法でデプロイ、管理したい
- 特定のサーバー / インスタンスの状況に依存することなく、アプリケーションの依存とインフラ都合の依存を別管理したい
- Docker なんかかっこいいっぽいし使ってみたい


上記のような都合から、どうやって作っていくかを考えていきます。基本的には1番目と2番目の理由が重要です。

## Docker コンテナのいいところ

とある Rails アプリケーションをデプロイするためには、サーバー上に nginx が入っており、かつその nginx の設定は期待どおりに設定されており、デプロイ用のユーザーがおり……となんやかんや色々あって大変です。

こういった設定がアプリケーションエンジニアからインフラエンジニアに伝わっていないなどの場合もあり、その設定漏れを無くしたい、というような場合もあります。

個人的により重要なのはアプリケーションの依存ライブラリとサーバー状況を分離できることです。ImageMagick のバージョンを上げたい！という時に全部のサーバーで作業するとかちょっとぞっとしますからね。

というような Docker コンテナのいいところを挙げたところで、実際に本気で Docker を使っていきます。

# docker-registry 選定

Docker コンテナを作るだけならどこそこのチュートリアルを見ればなんとなくおわかりいただけると思うので、実際に docker-registry を選定していきます。個人的なおすすめ registry は [quay.io](https://quay.io/) です。もちろん、自分で docker-registry を運用したいという場合も止めはしませんが、大変なので覚悟が必要です。僕は試しに運用してみてすごく苦労しました。遅いとか遅いとか遅いとかで。

quay.io なら bot アカウントの発行ができ、リポジトリへのアクセス権も設定できるので、それらを使って運用することをおすすめします。この辺の機能、 Docker Hub の方では見かけなかったのですが、同じような設定が可能なのでしょうか？詳しい方はコメント欄にでもぽこっと書いていただけると幸いです。

ちなみに quay.io の最も小さなプラン(Personal を除く)は現在 SKIFF で `$25/m` です。これは t2.medium を一ヶ月動かす `$37.44/m` より安価であり、かつ GitHub との連携で Auto build も quay.io 上でやってくれることを考えれば、どちらが安価かは言うまでもないかと思います。

(おすすめとしては quay.io 上に master を置いて自分で docker-registry を t2 なりどこかに建てて mirror しておくといいかなぁと少し考えています)

# コンテナ保存先の設定

docker コンテナの配置先として、 `/var/lib/docker` 以下に保存されるのがデフォルトですが、EC2 かつ EBS-Backend であれば、この保存先を Ephemeral Disk などにしておくと良いです。8GB の EBS などにしておくと、結構簡単に Disk full が起こってしまって、 docker コンテナが立ち上がってこない、 pull できないなどの問題が発生します。docker 起動時に `-g /epdisk/var/lib/docker` などのオプションを渡して、保存先ディスクを切り替えておくのが吉です。その際にファイルシステムもきちんと確認しておきましょう。

Docker daemon 起動時のオプションについては[公式のドキュメント](http://docs.docker.com/reference/commandline/cli/#daemon)を穴が空くほど見ると良いです。

# Dockerfile の作り方

Dockerfile の書き方については世の中に多くの良い記事にあふれているので割愛しますが、概ねざっくり言うならば

- チーム的なベースコンテナ(Ruby が入れてあるとか、 go が入れてある、とか)
    - アプリ A
    - + ImageMagick 6.7
        - アプリ B
    - + ImageMagick 6.8
        - アプリ B の次のバージョン

みたいな形にしておくと、便利につかえておすすめです。

# 自動で起動するコンテナ

例えば、EC2 インスタンス起動時につけられたタグから Docker コンテナを pull して自動で立ち上げる、などのスクリプトを組む際、僕は単純に `rc.local` の最後にコマンドを書いていたのですが、 `rc.local` からの起動だと、 root のホームディレクトリが設定されていない(？この辺よくわかってない)などの事情で `~/.dockercfg` が読み込まれず、 quay.io や docker-registry との認証に失敗して pull が行えないなどの問題が発生します。僕はこれで1日潰したので、お気をつけ下さい。

# 外部と通信するコンテナ

弊社で運用している Docker コンテナは、ホスト側のポートを固定することなく `EXPOSE 80` で 80 番ポートで HTTP 通信を受けるようにすること、という規約を設けてあります。

なので、以下のような形でコンテナが待ち受けている HTTP のポートを取って nginx の config を書き換えるというような運用をしています。

```bash:実コードから該当部分のみ抜き出し
cid=$(docker run -d -P ${CONTAINE_NAME} | cut -c1-12)
port=$(docker port ${cid} 80 | sed -e -e 's/.*://')
config-updater ${port}
```

`docker run` して得たコンテナ ID を用いて `docker port` コマンドから実ポートを取得するような形です。こういった制約を設けることで、すべてのコンテナで同じ方法で管理することができます。コンテナ内に nginx も同梱するような設定の場合は、 haproxy の tcp モードで振り分けつつ、 graceful にコンフィグを入れ替えて、古いコンテナを落とす、などのようなことも可能です。

こういった設定と運用のおかげで、何言語でどういうつくりのアプリケーションであっても graceful にデプロイ可能にできて、相当楽をさせてもらっています。

# Docker コンテナのクリーンアップ

これは特に悩むことなく

```bash
docker rm $(docker ps -a -q)
docker rmi $(docker images -a | grep none | awk '{print $3}')
```

を実行してしまってよいかと思います。注意としてコマンドの順序を入れ替えてしまうと、 stop 済みのコンテナのイメージが先に削除されてしまって参照が消えて docker の挙動がおかしくなるかも、程度です(どういう状況で再現するのかよくわかってないのですが、 rmi が一切できなくなる時があった)。

起動中のコンテナは `rm` で削除されることはありませんし、起動中のコンテナが参照しているイメージも `rmi` で削除されることはないので、思い切ってやってしまってかまいません。

# k8s など

Docker をベースにした PaaS 環境構築ツールなどはいろいろ開発されていて、群雄割拠の時代感がありますが、基本的にはそれらのツールは利用せず、地味〜な bash スクリプトや、古きよき awk による整形などで、現在運用しています。ことは単純で、あまりに複雑なツールは運用するにも開始するにもコストがかかりすぎるから、というだけです。

個人的には 1 コンテナ 1 ホストで十分最初の利点を得られていますし、これ以上複雑になるのはむしろ望むところではない、という感覚でしょうか。そういう意味では AWS の ECS にはとても期待しています。もっと楽になってほしい！

# まとめ

特に新たに得るものもないような内容かと思いますが、いかがでしたでしょうか。ホントは朝出すつもりだったのですが、クリスマスの空気に当てられて甘いものを食べまくったり、新しいポケモンをやったりしていてすっかり出遅れてしまいました。すみません。

ということで、これで今年の Docker Advent Calendar は終了なります。みなさん、おつかれさまでした。良いお年を！