Vulcand を使って Docker コンテナをブルーグリーンデプロイする


## 概要
vulcand という HTTP プロキシを使って Docker コンテナをブルーグリーンデプロイするという話。
Consul と Nginx を組み合わせて動的にロードバランスするという話とやりたいことは同じだと思う。

## vulcand とは
[vulcand](https://github.com/mailgun/vulcand) は、[Mailgun](http://www.mailgun.com/) というメール系 SaaS の開発チームが作った HTTP プロキシ。

* etcd をバックエンドに利用した HTTP プロキシ
* 再起動なしで設定を反映できる
* HTTP API と CLI
* プラガブルなミドルウェア
* ゼロダウンタイムデプロイのサポート
* リアルタイムメトリクスレポート
* TLS と certificate の管理

といった特徴を持つ。設定ファイルを etcd に保存して再起動なしで設定反映できるというのが非常に便利なポイントだと思う。 

### vulcand の仕組み

![](https://coreos.com/assets/images/media/vulcan-diagram.png)

vulcand に 以下の 2 つの主要な概念がある。

* locations
* upstreams

#### Location 
Location はホスト名とリクエストパスの組み合わせからなり、名前を持つ。リクエストされた URL に対して正規表現マッチが行われる。

例

* `search` という名前の location で `example.com` を host として `/search` を正規表現マッチャとして使う 
* `user` という名前の location で `example.com` を host として `/^users.*$/` を正規表現マッチャとして使う


そして Location は後述する upstream を持ち、正規表現マッチしたリクエストをupstreams へプロキシする。

#### Upstream
Upstream は後述する endpoint の集合であり、名前を持つ。

例

* `search-backend` という名前の upstream で endpoint は `http://10.10.1.2:8080` と `http://10.10.1.3:8080` 

#### Endpoint
Endpoint は最終的にリクエストを処理するところ。 `<schema>://<host>:<port>` 形式で定義する

例

* `http://localhost:5000`

## Vulcand を使って Docker コンテナのブルーグリーンデプロイ
Vagrant で起動した CoreOS マシン上の vulcand に OS X から接続してブルーグリーンデプロイを試す。

イメージ図:

![](https://coreos.com/assets/images/media/vulcan-2-upstreams.png)

例として、`example` というアプリケーションがあるとして、v1 から v2 への切り替えをするデプロイをする。

### OS X 側の hosts を設定しておく

```bash
# on CoreOS
core@core-01 ~ $ cat /etc/environment
COREOS_PUBLIC_IPV4=172.17.8.101
COREOS_PRIVATE_IPV4=172.17.8.101
```

```
# on OS X
$ sudo vi /etc/hosts
```

```
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1       localhost
255.255.255.255 broadcasthost
::1             localhost

172.17.8.101    example.com # 追加
```

### サービスコンテナ v1 の起動
example アプリケーションコンテナの v1 を2 台起動する

```bash
# on CoreOS
core@core-01 ~ $ docker run -d --name example-v1.1 -p 8086:80 coreos/example:1.0.0
0940df1b68a823e68d165196a7fde12922b6c57ff6b4ad95441a2fa9fd64a95b

core@core-01 ~ $ docker run -d --name example-v1.2 -p 8087:80 coreos/example:1.0.0
e35ed7de9766ae67e14bc05f95e5c1a207d74b03e3dd4f350b99192c4ff51845
```

### Upstream `example-v1` の設定
続いて、`example-v1.1`, `exaple-v1.2` を endpoint とする `example` という upstream をセットする。

```bash
# on CoreOS
core@core-01 ~ $ etcdctl set /vulcand/upstreams/example-v1/endpoints/1 http://172.17.8.101:8086
http://172.17.8.101:8086

core@core-01 ~ $ etcdctl set /vulcand/upstreams/example-v1/endpoints/2 http://172.17.8.101:8087
http://172.17.8.101:8087
```

### Location の設定
`home` という名前の location を作成する。正規表現マッチャは `/.*` つまり全てのリクエストをマッチさせる。

```bash
# on CoreoS
core@core-01 ~ $ etcdctl set "/vulcand/hosts/example.com/locations/home/path" '/.*'
/.*
```

そして `home` location を `example-v1` upstream にプロキシさせるようセットする

```bash
# on CoreOS
core@core-01 ~ $ etcdctl set /vulcand/hosts/example.com/locations/home/upstream example-v1
example-v1
```

### vulcand コンテナの起動

```bash
# on CoreOS
core@core-01 ~ $ docker run \
  --rm \
  --name vulcan \
  -p 80:80 \
  -p 8182:8182 \
  mailgun/vulcand \
  /opt/vulcan/vulcand \
  -apiInterface="0.0.0.0" \
  -interface="0.0.0.0" \
  -etcd="http://172.17.8.101:4001" \
  -port=80 \
  -apiPort=8182
```

### アクセスしてみる
`http://example.com` にアクセスしてみる。example アプリケーションの v1 が見れた :)

![スクリーンショット 2014-11-05 16.32.09.png](https://qiita-image-store.s3.amazonaws.com/0/20833/748442aa-bdcc-7475-3f14-e0d4dd603416.png "スクリーンショット 2014-11-05 16.32.09.png")

### サービスコンテナ v2 の起動

```bash
# on CoreOS
core@core-01 ~ $ docker run -d --name example-v2.1 -p 8088:80 coreos/example:2.0.0
8aa0f8cd402fa8be29b96f37bc97a2899bb32510cc3a8899d1338d0f8c8e1040

core@core-01 ~ $ docker run -d --name example-v2.2 -p 8089:80 coreos/example:2.0.0
8db14040b1207ec42a5c08236bd4c7214323db36058d47e77606256231b6f5e8
```

### upstream `example-v2` の設定

```bash
# on CoreOS
core@core-01 ~ $ etcdctl set /vulcand/upstreams/example-v2/endpoints/1 http://172.17.8.101:8088
http://172.17.8.101:8088

core@core-01 ~ $ etcdctl set /vulcand/upstreams/example-v2/endpoints/2 http://172.17.8.101:8089
http://172.17.8.101:8089
```

### v1 から v2 に切り替え

```bash
# on CoreOS
core@core-01 ~ $ etcdctl set /vulcand/hosts/example.com/locations/home/upstream example-v2
```

### 再度アクセスしてみる
example アプリケーションの v2 になってる :)
ゼロダウンタイムブルーグリーンデプロイ。

![スクリーンショット 2014-11-05 16.48.26.png](https://qiita-image-store.s3.amazonaws.com/0/20833/32a7a52a-a197-1a4b-9e25-f5834a930674.png "スクリーンショット 2014-11-05 16.48.26.png")

ここでまた

```
# on CoreOS
core@core-01 ~ $ etcdctl set /vulcand/hosts/example.com/locations/home/upstream example-v1
```

とすれば v1 のコンテナにプロキシされてロールバックができる。

### ローリングデプロイ
![](https://coreos.com/assets/images/media/vulcan-1-upstream.png)

上記の例だと、`home` location へ紐づける `upstream` を `example-v1` から `example-v2` に切り替えてデプロイを行ったが、`upstream` を固定して、endpoint を追加・削除することでローリングデプロイも可能。

## TODO: registrator を使って upstream の自動登録をする
registrator または docker-plugin を使って、コンテナの `start` hook 時に etcd に起動 IP と Port を登録する

## メモと感想
### HA 構成できる
etcd から設定を読むので、HA 構成が実現できそう。例えば AWS で 3 台からなる CoreOS クラスタがあるとき、全台で vulcand を起動しておき、vulcand の前段に ELB を置く事で、

* Docker ホストレベル（＝EC2 インスタンス）のロードバランスを ELB に
* Docker コンテナレベルのロードバランスを vulcan に

やらせることで割とシンプルな構成にできそう。

```bash
# requests:
#  - www.example.com
#  - admin.example.com
#  - search.example.com


                     vulcan.1 on coreos.1 
                   /                        --> upstream `search`
                  /                       /
request --> ELB ---  vulcan.2 on coreos.2  ---> upstream `admin`
                 \                        \ 
                   \                        --> upstream `www`
                     vulcan.3 on coreos.3

```

### Nginx と比べて

* コンテナとして動かすのに向いている
* プロキシとしての機能は nginx と比べると乏しい？

#### コンテナとして動かすことについて
nginx がコンテナとして動かそうと思うと後から設定をダイナミックには変更しづらい。なので、Docker ホスト側に Nginx をインストールすることになる。なるだけ全てをコンテナでやりたいと思うとこれは微妙。

頑張ってコンテナでやろうとするなら

* コンテナを Nginx として動かすが設定ファイルを -v でホストと共有して docker exec で nginx を reload 
* コンテナを Nginx として動かすが、設定変更時は起動スクリプトに環境変数や引数を渡して動的に設定ファイル作ってコンテナ再起動

といった感じでできなくもないと思うが、とても複雑（他にもやり方はあると思う）

#### TODO: プロキシとしての機能について
ドキュメントまだ全部読めてない。[deis は「色々足りてないからまだ Nginx 使うよ」](https://github.com/deis/deis/issues/1829#issuecomment-55607828) って言ってる。

## REF

* [mailgun/vulcand - GitHub](https://github.com/mailgun/vulcand)
* [mailgun/vulcand - DockerHub](https://registry.hub.docker.com/u/mailgun/vulcand/)
* [vulcan Documentation](https://www.vulcanproxy.com/)
* [Zero Downtime Frontend Deploys with Vulcand on CoreOS](https://coreos.com/blog/zero-downtime-frontend-deploys-vulcand/)
* [Golang Reverse Proxy - r7km/s](http://r7kamura.github.io/2014/07/20/golang-reverse-proxy.html)