Marathonを使ってWebアプリのBlue/Greenデプロイを簡単にするツール


こんにちは、さくらインターネットのこたまごです。
さくらインターネット Advent Calender 2015もちょうど半分を超えました。

社内向けを含む、弊社の一部サービスは、Mesos+Marathon+Dockerのクラスタ環境上にデプロイされています。

本日は、Marathonによるクラスタ環境上にデプロイされたWebサービスにリバースプロキシをしているNginxの設定を、動的に変更することでBlue/Greenデプロイを簡単に行うための社内ツール 「ProxyManager(仮)」を紹介します。

(実装方法の検証から、大半の実装まで @kamijin_fanta 氏にしてもらいました。[2日目の記事](http://qiita.com/kamijin_fanta/items/21a061a00575f0a357c6) で紹介があった`Q`も利用されています。)

# Marathonとは

最近流行りのこの3つ (+Chronos) の構成を簡単に整理すると以下のような機能を持っています。

## Docker

Linuxのカーネルに実装されているNamespace機能をラッピングしたlibcontainerを使ったアプリケーションコンテナを簡単に実行するソフトウエアです。
今回はデプロイしたいWebアプリケーションをイメージにしておいて、クラスタ環境上で実行する機能を担っています。

## Apache Mesos

任意のコマンドをマスターに実行するように指示すると、多数あるスレーブの中からリソースに空きがあるサーバを選択し実行してくれる機能を持っています。
今回の用途では実行したいDockerコンテナを余裕のあるサーバ上で実行する機能を担っています。

## Marathon

Mesosは一度実行したらそれっきりで、終了してしまったDockerコンテナを再起動する、といったような機能は提供されていません。MarathonはMesosのクライアントとして動作し、任意のコマンドを指定した並列数で、自動再起動も含めてタスクを管理してくれます。

つまり、実行したいDockerイメージと、同時起動数をMarathonに投げると、死活監視も含めて、どっかのMesosサーバ上で実行しておいてくれることになります。


構成を簡単な図にすると以下のような感じになります。

![image](https://qiita-image-store.s3.amazonaws.com/0/73788/ff597d3e-5648-797a-13d8-9d120ea62647.png)


# ProxyManagerを使う前のデプロイ

ProxyManagerを用意する前は以下のような手順を踏んでいました。

0. まず、実行するアプリケーションのためにポートを2つ予約し、実行するサーバも決めておきます。たとえば、「10000番と、10001番をつかって、サーバはnode-01, node-02で実行する」というような感じです。この2つのポートをBlue/Greenデプロイ時に交互に利用します。
0. 新しいバージョンのイメージを今使っていないほうのポート番号で起動するようにMarathonに指示します。
0. デプロイしたアプリケーションが正常に稼働しているか確認。
0. LBとして稼働しているNginxの設定はAnsibleで管理されているので、プロキシ先を変更してplaybookを流す。

概ね3番までは各アプリケーションの開発側で自動化されていますが、これでは、Marathon側の設定を変更して稼働台数を増やす時にもNginxの設定を変更する必要がありますし、ポート番号を管理する手間もかかります。
本番環境ならまだしも、頻繁に変更する個人の試験環境でこれをやるのは極めて労力を必要とします。

# ProxyManagerを使ったデプロイ

Marathonへデプロイする部分は変わりませんが。上記のNginxの設定変更をAPIから行うことが可能になります。

0. ポートもサーバも指定せずにMarathonに指示する
1. デプロイしたアプリケーションが正常に稼働しているか確認。
2. ProxyManagerにバージョン変更をAPI経由で通知

新しいアプリケーションをデプロイする場合には以下のようなJSONをProxyManagerにPOSTします。

```json:POST
{
  "name": "helloworld",
  "url": "/helloworld",
  "appname": "/helloworld",
  "proxyPath": "",
  "nginxOptions": [
  ]
}
```

このJSONでは、以下のようなNginxの設定に対応します。

```nginx
location /helloworld {
    # ProxyManagerName: helloworld  MarathonAppName: /helloworld  
    include proxy_params;
    proxy_pass http://プロキシ先/;
}
```

その後、Marathonをつかってコンテナをデプロイします。この時、必ず `/helloworld/:バージョン名` という命名規則に従うことで、ProxyManagerが同じアプリケーションの各バージョンを認識できるようにしています。
Marathonからコンテナが起動すると、ProxyManagerから以下のようなJSONが取得できるようになり、どのバージョンのコンテナが、どのサーバのどのポートで起動しているかがわかるようになっています。

```json:POST
{
  "name": "helloworld",
  "url": "/helloworld",
  "appname": "/helloworld",
  "proxyPath": "",
  "nginxOptions": [],
  "version": null,
  "current": [],
  "versions": {
    "v0.0.1": [
      "node-01.example.com:31062",
      "node-02.example.com:31065",
      "node-03.example.com:31066",
      "node-04.example.com:31061",
      "node-05.example.com:31054",
    ]
  }
}
```

最後に、リバースプロキシする先のバージョンを変更するJSONをPUTすれば、自動的にNginxに反映されます。

```json:PUT
{"version":"v0.0.1"}
```

以上でアプリケーション一つに対して以下のようなプロキシの設定が自動反映されます。

```nginx
upstream autogenproxy_12 {
    #　Endpoint: /helloworld ProxyManagerName: helloworld
    # MarathonAppName: /helloworld  CurrentVersion: v0.0.1
    server node-01.example.com:31062;
    server node-02.example.com:31065;
    server node-03.example.com:31066;
    server node-04.example.com:31061;
    server node-05.example.com:31054;
}

location /helloworld {
    # ProxyManagerName: helloworld  MarathonAppName: /helloworld CurrentVersion:v0.0.1 
    include proxy_params;
    proxy_pass http://autogenproxy_12/;
}
```


# メリット

## 1.ポート番号を気にしなくて良い。

どのコンテナがどのポートで起動しているかはMarathonから取得するようになったので、ポート番号を管理しなくてよくなりました。

## 2.スケールへの対応も自動

どのサーバで稼働しているかもMarathonから取得するので、スケールしたい時もMarathonにだけ指示をすればNginxのことは気にしなくてOKです。

## 3.完全自動化ができる

これまではポートの指定とNginxの設定反映が人による作業が必要でしたが、コンテナのデプロイと動作検証が自動化できるものに関してはNginxの設定までAPIを叩いて自動化できるようになりました。

# 実装の詳細

Nginxの冗長化はPacemaker+CorosyncとVIPを使った旧来の方法で行われています。ProxyManagerはすべてのNginxと一緒に稼働させることによって同じように冗長化されています。

アプリケーションごとの設定等は、ZooKeeperに保存しており、どこかのProxyManagerからか設定が変更されると、すべてのProxyManagerに通知が届き最新の設定が反映されます。

Marathon側の変更情報もEvent通知を受け取っているのですが、なぜかコンテナの再起動による変更が通知されてこない感じに見えるので、polling動作もしています。

# 感想

ZooKeeperめっちゃ便利！