dockerでWebサイトを集約してみた一事例

本記事は[Docker Advent Calendar 2014](http://qiita.com/advent-calendar/2014/docker)の10日目の記事です。

### 最初に
Ubuntu14.04+nginx(リバースプロクシ)をホストとし、dockerコンテナとして構築された複数のWebサイトを並べてみた。運用のことも考えてみた。

### ポートはバインディングしない
ホストとゲストは、仮想的に構築されたネットワークで、デフォルトだと、172.17.0.0/16のセグメントで繋がっている。リバースプロクシすること考えると、ホストのサービス側に届いたリクエストを、172.17.0.0/16内のゲストに中継できれば良いので、ゲストのポートをバインディングで外に晒す必要はない。

```bash
# docker run -d --name ほげこんてな mnagaku/ほげいめーじ
```

でコンテナを起動。イメージは一応、80をexposeしてdocker buildしてるけど、もしかしたら要らないかも知れない。イメージをイジるときに、手元の環境で、リバースプロクシなしで動かすこともあるので、exposeしといた方が良いとは思うけど。

### /etc/hostsの利用
お手軽運用のため、コンテナはimmutableとしては扱わず、中にDBを抱えたり、動的に変化するものとして扱う。適時、docker commitでスナップショットを取りながらとかな運用を考える。
この時、ホストのメンテで、再起動や、dockerのupdateを行うと、コンテナはstop状態になってしまう。運用を続けるため、docker startすれば良いのだが、startする度にIPアドレスが変わってしまう。リバースプロクシの中継先をIPアドレスで設定してしまうと、startする度にリバースプロクシの設定変更が必要ということになり、これはヤだ。そこで、

```upstart:/etc/init/docker-containers.conf

description "for docker containers auto start"
author mnagaku

start on filesystem and started docker
stop on runlevel [!2345]

respawn

script
  cat /etc/hosts | grep -v 172.17. > /tmp/hostsbase
  docker start $(docker ps -a -q)
  docker inspect $(docker ps -q) | grep IPAddress | tr -d "[:alpha:][:blank:]:,\"" > /tmp/list1
  docker inspect $(docker ps -q) | grep '"Name": "/' | sed -e "s/    \"Name\": \"\///g" | tr -d ",\"" > /tmp/list2
  paste -d " " /tmp/list1 /tmp/list2 >> /tmp/hostsbase
  mv /tmp/hostsbase /etc/hosts
end script

```

upstartのスクリプト書いて、ホスト起動時に、stopしてるコンテナを全部startして、それぞれのコンテナのIPアドレスを調べて、/etc/hostsにコンテナ名で名前解決できるように追記するようにしてみた。リバースプロクシの中継先をコンテナ名で書けるようになるので、コンテナ名を変えない限り（コンテナを捨てて起動し直す場合も、同じ名前になるようにオプションで指定すればよい）、リバースプロクシの設定変更が不要になる。
ただ、init.d側で起動するnginxが、起動時に名前解決を使ってるように見えてて、ほんとはnginxの起動を遅らせたいのだけど、そこはまだやってなくて、dockerのアドレス払い出しがホストの再起動毎に初期化される特徴に依存して、最悪、再起動を2回やれば正しく動くようになっている。

```conf:/etc/hostsの一部
172.17.0.x ほげこんてな
```

起動時にstartしたコンテナの名前解決情報が/etc/hostsに追記されることを確認しておくとよい。

### nginxのリバースプロクシでお約束的なこと
ホスト側

```nginx:/etc/nginx/sites-available/サイトのURL

        location / {
                proxy_pass http://ほげこんてな/;
                proxy_redirect default;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Host $host;
                proxy_set_header X-Forwarded-Server $host;
                proxy_set_header X-Real-IP $remote_addr;
```

ゲスト側

```nginx:/etc/nginx.conf

http {
        set_real_ip_from 172.17.42.1;
        real_ip_header X-Real-IP;
```

コンテナ名で名前解決できるようにしたので、proxy_passにはコンテナ名を書くと、アクセスが転送される。あと、リバースプロクシの内側にあるnginxに、接続してきたクライアントの情報が転送されるように各種設定をしておく。/etc/nginx/sites-enabledに「/etc/nginx/sites-available/サイトのURL」へのシンボリックリンクを作るのも忘れないように。

こんな感じで、docker以外は特に新しい仕掛けを使わず、ホスト上のnginxをリバースプロクシとし、複数のWebサイトを個々にコンテナ化したものを動かすことができる。

* ポートバインディングを使わない
* dockerの内部ネットワークを使う
* /etc/hostsを使う

がキモなので、nginxの部分をapacheに置き換えても、問題はないと思う。

### 定期バックアップ
mutable containerで運用するとなると、コンテナをcommitすることでバックアップが取れる。下記のスクリプトを日次で回している。

```bash:daily.sh
#!/bin/bash

# 動いているコンテナ名の一覧を取る
CONTAINERS=(`docker inspect $(docker ps -q) | grep '"Name": "/' | sed -e "s/    \"Name\": \"\///g" | tr -d ",\""`)

# 動いているコンテナのイメージ名一覧を取る
IMAGES=(`docker inspect $(docker ps -q) | grep 'Image": "mnagaku/' | sed -e "s/        \"Image\": \"//g" | tr -d ",\""`)

# バックアップのバージョンとして曜日もしくは月を決める
VERSION=(`date +%a`)
if [ "`date +%d`" = "01" ] ; then
  VERSION=(`date +%b`)
fi

# 各コンテナのイメージ名:バージョンを指定してcommit
for (( i = 0; i < ${#CONTAINERS[@]}; ++i ))
do
    docker commit ${CONTAINERS[$i]} ${IMAGES[$i]}:$VERSION
done

# 上書きされて名無しになったイメージがあったら削除
docker rmi $(docker images | awk '/^<none>/ { print $3 }')

# ディスクの使用量をtwitterでレポート
python tw.py "$(date +%F) このホストの disk usage : $(df -Th | grep vda1 | sed -e "s/ \{1,10\}/ /g" | cut -d' ' -f6)"

```

因みに、twitter投稿は、下記のスクリプトで行っている。

```python:tw.py
#coding:utf-8

# Install the dependencies on Ubuntu14.04
# apt-get install python2.7 python-simplejson python-httplib2 python-oauth2 python-twitter

# Usage
# python tw.py "message"

import sys, twitter

consumerKey = 'ほげほげ'
consumerSecret = 'ほげほげ'
accessToken = 'ほげほげ'
accessSecret = 'ほげほげ'

argvs = sys.argv
argc = len(argvs)

if (argc < 2):
    quit()

api = twitter.Api(consumerKey,consumerSecret,accessToken,accessSecret)
api.PostUpdates(status=argvs[1])

```

### Webサイトのメンテ
1年でローテートされる月次バックアップと、1週間でローテートされる日次バックアップが取れるので、コンテナが壊れても構わない安心感が手に入る。コンテナ上のWebサイトがCMSでダッシュボードから各種メンテを行ったり、コンテナ内のメンテナンスを

```bash
# docker exec -it ほげこんてな /bin/bash
```

で作業したり。失敗しても、前日のイメージからコンテナをrunすればよい。

個人的には、この環境を作ってから、wordpressが怖くなくなった。いつもバクチだと思ってたので...

### おまけ：サービス監視
Webサイトが落ちてますよって連絡受けたり、落ちてるのに誰にも気付かれなかったり、恥ずかしかったり悲しかったりするので、サービス監視も回しておく。

```bash:hourly.sh
#!/bin/bash

declare -A sites_

# key:監視対象URL、value:検出したい文字列
sites_["http://なんとか.jp"]="このサイトはなんとか.jpです"
sites_["http://かんとか.jp/"]="このサイトはかんとか.jpです"

result=""

for url in ${!sites_[@]}
do
    COUNT=`wget -O - $url | grep -c "${sites_[$url]}"` # 文字列の検出行数をカウント

    if [ $COUNT -le 0 ] ; then
        result="$url is sick, $result" # 文字列が検出できなかったらsick
    else
        result="$url is fine, $result"
    fi
done

python tw.py "$(date) : $result"

```