OpenStackでKVMの仮想マシンHAを実現するMasakariを触ってみた

[日本OpenStackユーザ会 第25回勉強会](http://openstack-jp.connpass.com/event/27532/) でテーマとなっていた"OpenStack上の仮想マシンHA"。
今回はそれを実現するツールの1つである[Masakari](https://github.com/ntt-sic/masakari)を試してみました。

Masakariを試すにあたり、開発元であるNTT-SICさんがVirtualBox上ですぐに環境構築ができる[masakari-deploy](https://github.com/ntt-sic/masakari-deploy)を公開していたので、今回はこれを使ってみます。

## Masakariとは
"OpenStack上の仮想マシンHA"を実現するツールの1つで、NTTの研究所で開発されています。
具体的には以下の4つのコンポーネントで形成されています。

* Masakari Controller
  * 障害通知を受け取り、OpenStack APIを叩く。
* Masakari Instance Monitor
  * コンピュートノード上で稼働中のVMの生存監視を行う。Libvirtのイベントを監視しており、たとえばqemuのプロセスが異常終了するとControllerに通知する。
* Masakari Process Monitor
  * あらかじめリストに追加したプロセスの生存監視を行う。プロセスが終了しているのを検知すると起動を試みる。起動に失敗した場合はContorollerへ通知、当該コンピュートノードはスケジューリング対象から外される(nova-computeサービスがdisableになる)。 
* Masakari Host Monitor
  * コンピュートノード自体の生存監視をPacemaker/Corosyncを利用して行う。クラスタ内でOfflineとなったノードを検知するとControllerへ通知が行き、nova-evacuate APIが発行されてインスタンスのマイグレーションが行われる。

これらの関係性は[MasakariのReadme](https://github.com/ntt-sic/masakari/blob/master/README.md)にわかりやすい図があります。

![masakari_architecture](https://raw.githubusercontent.com/ntt-sic/masakari/master/contents/architecture.png)

## 前提
masakari-deployはVagrant + VirtualBoxで環境構築するよう作られていますが、今回は私の検証環境の都合上、Ubuntu14.04 + Vagrant1.8.1 + KVMで環境構築を進めていきます。
そのため何点かVagrantfileに変更を加えています。VirtualBoxを使用する方は特に変更は必要ないと思います。

なお、VagrantからKVMを利用する方法に関しては、
[KVM用仮想マシンをVagrantで手軽に作る](http://knowledge.sakura.ad.jp/tech/2535/)を参照してください。
libvirt用 ubuntu/trusty64 イメージが既に作成されている前提で話を進めます。

## 自動でプロビジョニングされる構成
* OpenStack 管理ノード＆Masakari Controller
  * `controller`
  * IP: `192.168.50.10` → `192.168.122.10` (KVMのデフォルトブリッジ用IPアドレスへ変更した)
  * CPU: 2Core
  * RAM: 8192MB
* OpenStackコンピュートノード1号機＆Masakari Monitor類
  * `compute1`
  * IP: `192.168.50.11` → `192.168.122.11`
  * CPU: 1Core
  * RAM: 1024MB
* OpenStackコンピュートノード2号機＆Masakari Monitor類
  * `compute2`
  * IP: `192.168.50.12` → `192.168.122.12`
  * CPU: 1Core
  * RAM: 1024MB

CPUコア数やメモリ容量は適宜変更してもいいと思います（管理ノードはRAM4GBに落とすなど）。

## 構築してみる
[masakari-deployのReadme](https://github.com/ntt-sic/masakari-deploy/blob/master/README.md)通りに構築していきます（Vagrant+libvirt用に多少変更を加えつつ）。
流れとしては、
1. 検証用VMの作成
2. OpenStackのインストール(DevStack使用)
3. Masakariの初期設定
となります。

なお2016/04/22現在、OpenStack Mitakaがリリースされたので、DevStackもMitakaが入るようになっています！
コンピュートノード上で作成されるインスタンスのデータはすべてNFSでマウントされた領域に保存されるようになっています(/mnt/instances/)

### 1. Masakari-deployをClone

```
$ git clone https://github.com/ntt-sic/masakari-deploy.git
$ cd masakari-deploy/
~/masakari-deploy$ ls
cookbooks  LICENSE  README.md  roles  Vagrantfile
```

### 2. KVMで使用するためにVagrantfileの編集
主に使用するProvider部分を変更しました。例えば管理ノードであれば以下のように変更しました(ProviderにVirtualBoxを使用する場合は変更不要)。

オリジナル

```
...(略)...
config.vm.define "controller" do |controller|
    controller.vm.hostname = "controller"
    controller.vm.network "private_network", ip: "192.168.50.10"
    controller.vm.provider "virtualbox" do |vb|
      vb.customize ["modifyvm", :id, "--memory", "8192", "--cpus", "4", "--ioapic", "on"]
    end
    controller.vm.provision "chef_solo" do |chef|
...(略)...
```

KVM用に改良

```
...(略)...
  config.vm.define "controller" do |controller|
    controller.vm.hostname = "controller"
    controller.vm.network "private_network", ip: "192.168.122.10" ★
    controller.vm.provider "libvirt" do |libvirt| ★
      libvirt.memory = 4096 ★
      libvirt.cpus = 2 ★
    end
    controller.vm.provision "chef_solo" do |chef|
...(略)...
```

コンピュートノードに関しては、nested virtualizationを有効にする一文も追加しています。

```
...(略)...
  config.vm.define "compute1" do |compute1|
    compute1.vm.hostname = "compute1"
    compute1.vm.network "private_network", ip: "192.168.122.11"
    compute1.vm.provider "libvirt" do |libvirt|
      libvirt.memory = 1024
      libvirt.cpus = 1
      libvirt.nested = true ★
    end
    compute1.vm.provision "chef_solo" do |chef|
...(略)...
```

### 3. プロビジョニングの開始

```
~/masakari-deploy$ vagrant up
```

VM作成後、自動的にChefでのプロビジョニングが開始されます。
具体的には

* DevStackのダウンロードとlocal.confの設定
* Pacemaker/Corosyncのインストールとクラスタ形成(コンピュートノードのみ)
* Masakariのインストール
* NFSサーバの設定(管理ノードがサーバとなり、コンピュートノードがマウントしにいく)

が行われるようです。

私が試したとき(2016/04/13)は、管理ノードでPythonのライブラリをインストール中に `pytz` が無いという理由でpip install がこけていました(4/22に再挑戦したら特にエラーはなかった)。おもむろに `vagrant provision` を実行したらなんとか通過したので、なにかタイミングが悪かったのかもしれません。

### 4. DevStackを使ってOpenStackのインストール
OpenStackの構築はDevStackを用いて行われます。3VMに対してそれぞれ`stack.sh`を実行していきます。

・devstackディレクトリへ移動

```
~/masakari-deploy$ vagrant ssh controller
vagrant@controller:~$ sudo -u stack -i
stack@controller:~$ cd devstack/
stack@controller:~/devstack$
```

手順2でネットワーク構成を変更しているため、OpenStackのプロビジョニング情報が書いてある`local.conf`を変更します。
※この作業は管理ノードのみです。コンピュートノードでは必要ありません。

```local.conf
...(略)...
## neutron options
Q_USE_SECGROUP=True
FLOATING_RANGE=192.168.122.0/24 ★192.168.50.0/24から変更
FIXED_RANGE=10.0.0.0/24
Q_FLOATING_ALLOCATION_POOL=start=192.168.122.128,end=192.168.122.253 ★192.168.50.0/24から変更
PUBLIC_NETWORK_GATEWAY=192.168.122.1 ★(KVMホスト側のvirbr0インターフェースのIPアドレス)
...(略)...
```

変更したら構築開始です。

```
stack@controller:~/devstack$ ./stack.sh
```

環境にもよりますが30分くらいかかります。

以下のような出力があればDevStack構築成功です。
（例：管理ノード）

```
This is your host IP address: 192.168.122.10
This is your host IPv6 address: ::1
Keystone is serving at http://192.168.122.10:5000/
The default users are: admin and demo
The password: password
```

コンピュートノードでも`This is your host IP address: 192.168.122.11`のように表示されていると思います。

コンピュートノードが管理ノードから認識されているか確認します。

```
stack@controller:~/devstack$ source openrc admin admin
stack@controller:~/devstack$ nova hypervisor-list
+----+---------------------+-------+---------+
| ID | Hypervisor hostname | State | Status  |
+----+---------------------+-------+---------+
| 1  | compute1            | up    | enabled |
| 2  | compute2            | up    | enabled |
+----+---------------------+-------+---------+
```

### 5. Masakariの初期設定
・初期DBの流し込み(管理ノード作業)

```
stack@controller:~/devstack$ cd ~/masakari/
stack@controller:~/masakari$ ./masakari_database_setting.sh
```
追加されたDBテーブルが出力されます。

・masakari-controllerの起動

```
stack@controller:~/masakari$ sudo service masakari-controller start
Starting masakari:  *
stack@controller:~/masakari$ sudo service masakari-controller status
 * masakari is running
```

・Masakari Monitor類の起動(コンピュートノード作業)
念のため、Corosyncでクラスタが組まれているか確認します。

```
stack@compute1:~/devstack$ sudo crm_mon -A1
Last updated: Fri Apr 15 00:54:47 2016
Last change: Wed Apr 13 14:57:00 2016 via crmd on compute1
Stack: corosync
Current DC: compute1 (1) - partition with quorum
Version: 1.1.10-42f2063
2 Nodes configured
0 Resources configured


Online: [ compute1 compute2 ]


Node Attributes:
* Node compute1:
* Node compute2:
```

compute1, compute2共にOnlineとなっていれば大丈夫です。

compute1, compute2の両方でサービスを起動します。

```
stack@compute1:~/devstack$ sudo service masakari-hostmonitor start
stack@compute1:~/devstack$ sudo service masakari-instancemonitor start
stack@compute1:~/devstack$ sudo service masakari-processmonitor start

stack@compute1:~$ for i in {instance,process,host}; do sudo service  masakari-${i}monitor status;done
 * masakari-instancemonitor is running
masakari-processmonitor start/running, process 11401
masakari-hostmonitor start/running, process 11489
```

```
stack@compute2:~/devstack$ sudo service masakari-hostmonitor start
stack@compute2:~/devstack$ sudo service masakari-instancemonitor start
stack@compute2:~/devstack$ sudo service masakari-processmonitor start

stack@compute2:~/devstack$ for i in {instance,process,host}; do sudo service  masakari-${i}monitor status;done
 * masakari-instancemonitor is running
masakari-processmonitor start/running, process 11391
masakari-hostmonitor start/running, process 11425
```

### 6. Masakariの仮想マシンHAを試してみる

管理ノードで作業。

・退避先ホストとしてcompute2を登録

```
stack@controller:~/masakari$ ./reserved_host_add.sh compute2
reserve_host_manage execution start
+---------------------+---------+-----------------+----------+
| create_at           | deleted | cluster_port    | hostname |
+---------------------+---------+-----------------+----------+
| 2016-04-15 23:22:43 |       0 | 226.94.1.1:5405 | compute2 |
+---------------------+---------+-----------------+----------+
reserve_host_manage execution end
```

・compute2にインスタンスが立たないように設定

```
stack@controller:~/masakari$ cd ~/devstack/
stack@controller:~/devstack$ source openrc admin admin
stack@controller:~/devstack$ nova service-disable compute2 nova-compute
+----------+--------------+----------+
| Host     | Binary       | Status   |
+----------+--------------+----------+
| compute2 | nova-compute | disabled |
+----------+--------------+----------+
```

・お試し用にスペックの小さなフレーバーを作成

```
stack@controller:~/devstack$ nova flavor-create m1.nano auto 64 1 1
+--------------------------------------+---------+-----------+------+-----------+------+-------+-------------+-----------+
| ID                                   | Name    | Memory_MB | Disk | Ephemeral | Swap | VCPUs | RXTX_Factor | Is_Public |
+--------------------------------------+---------+-----------+------+-----------+------+-------+-------------+-----------+
| 2a38abf7-42e7-4aa1-b539-f61cf3e72056 | m1.nano | 64        | 1    | 0         |      | 1     | 1.0         | True      |
+--------------------------------------+---------+-----------+------+-----------+------+-------+-------------+-----------+

```

・インスタンス作成

```
stack@controller:~/devstack$ source openrc demo demo
stack@controller:~/devstack$ nova boot --image cirros-0.3.4-x86_64-uec --flavor m1.nano vm1
+--------------------------------------+----------------------------------------------------------------+
| Property                             | Value                                                          |
+--------------------------------------+----------------------------------------------------------------+
| OS-DCF:diskConfig                    | MANUAL                                                         |
| OS-EXT-AZ:availability_zone          |                                                                |
| OS-EXT-STS:power_state               | 0                                                              |
| OS-EXT-STS:task_state                | scheduling                                                     |
| OS-EXT-STS:vm_state                  | building                                                       |
| OS-SRV-USG:launched_at               | -                                                              |
| OS-SRV-USG:terminated_at             | -                                                              |
| accessIPv4                           |                                                                |
| accessIPv6                           |                                                                |
| adminPass                            | Tjn5g4vCjrcW                                                   |
| config_drive                         |                                                                |
| created                              | 2016-04-22T17:02:14Z                                           |
| description                          | -                                                              |
| flavor                               | m1.nano (2a38abf7-42e7-4aa1-b539-f61cf3e72056)                 |
| hostId                               |                                                                |
| id                                   | 721ebba1-8543-4ba8-9518-4934a0ebdf9e                           |
| image                                | cirros-0.3.4-x86_64-uec (b82f4797-0ba7-4608-b942-33e9f27e08ee) |
| key_name                             | -                                                              |
| locked                               | False                                                          |
| metadata                             | {}                                                             |
| name                                 | vm1                                                            |
| os-extended-volumes:volumes_attached | []                                                             |
| progress                             | 0                                                              |
| security_groups                      | default                                                        |
| status                               | BUILD                                                          |
| tenant_id                            | 9bd82adbe5a14592bea7ca1df7b6ee73                               |
| updated                              | 2016-04-22T17:02:15Z                                           |
| user_id                              | 6b7ea4a443284d679bb84cbcb6a8c566                               |
+--------------------------------------+----------------------------------------------------------------+


stack@controller:~/devstack$ nova list
+--------------------------------------+------+--------+------------+-------------+--------------------------------------------------------+
| ID                                   | Name | Status | Task State | Power State | Networks                                               |
+--------------------------------------+------+--------+------------+-------------+--------------------------------------------------------+
| 721ebba1-8543-4ba8-9518-4934a0ebdf9e | vm1  | ACTIVE | -          | Running     | private=fd81:7cfe:cd33:0:f816:3eff:fe93:7dd1, 10.0.0.5 |
+--------------------------------------+------+--------+------------+-------------+--------------------------------------------------------+

```

・インスタンスがcompute1に立っているか確認

```
stack@controller:~/devstack$ source openrc admin admin
stack@controller:~/devstack$ nova hypervisor-servers compute1
+--------------------------------------+-------------------+---------------+---------------------+
| ID                                   | Name              | Hypervisor ID | Hypervisor Hostname |
+--------------------------------------+-------------------+---------------+---------------------+
| 721ebba1-8543-4ba8-9518-4934a0ebdf9e | instance-00000005 | 1             | compute1            |
+--------------------------------------+-------------------+---------------+---------------------+

stack@controller:~/devstack$ nova hypervisor-servers compute2
+----+------+---------------+---------------------+
| ID | Name | Hypervisor ID | Hypervisor Hostname |
+----+------+---------------+---------------------+
+----+------+---------------+---------------------+
```

・疑似障害を発生させる(compute1の強制停止)

ホストOSで以下のコマンドを実行。

```
ubuntu@host01:~/masakari-deploy$ vagrant halt compute1 --force
```

するとcompute2側で障害を検知し、Masakari Controllerへ通知が飛びます。
Masakari Controllerは障害が起きたcompute1のnova-computeサービスをdisableにし、OpenStack側でcompute1のstatusがdownと認識されるまでMasakari Controllerは180秒間(デフォルト値)待機します。
やがてcompute2へのHAが開始されます。

HAが完了すると、インスタンスが稼働するコンピュートノードが変化していることが分かります。

```
stack@controller:~/devstack$ source openrc admin admin
stack@controller:~/devstack$ nova hypervisor-servers compute1
+----+------+---------------+---------------------+
| ID | Name | Hypervisor ID | Hypervisor Hostname |
+----+------+---------------+---------------------+
+----+------+---------------+---------------------+

stack@controller:~/devstack$ nova hypervisor-servers compute2
+--------------------------------------+-------------------+---------------+---------------------+
| ID                                   | Name              | Hypervisor ID | Hypervisor Hostname |
+--------------------------------------+-------------------+---------------+---------------------+
| 721ebba1-8543-4ba8-9518-4934a0ebdf9e | instance-00000005 | 2             | compute2            |
+--------------------------------------+-------------------+---------------+---------------------+
```

・落としたコンピュートノードを復活させる(compute1の再始動)
Readmeによると、再度Vagrantから `$ vagrant up compute1 --provision` を実行し、DevStackの再構築＆Masakariサービスの起動を行うことで復旧できるそうです。


## まとめ

今回はmasakari-deployを使ってMasakariの検証環境を作成し、Host Monitorを利用してOpenStackの仮想マシンHAを試してみました。
他にもInstance MonitorやProcess Monitorもありますので、別の機会に触ってみたいと思います。

## 余談
nova-evacuate APIの引数として `onSharedStorage`があり、Masakariでは`True`に設定されています。そのためエフェメラルが作成される領域がNFSのような共有ストレージ上であれば今回のようなHAが可能です。
Cinderボリュームから起動している場合は`onSharedStorage`を`False`にする必要があります。そのためにはMasakari Controllerのソースコードを変更します。

 /opt/masakari/controller/masakari_util.py

```masakari_util.py
693     def do_instance_evacuate(self, uuid, targethost):
694         """Call evacuate API for server instance.
695
696         :uuid : Instance id
697         :targethost: The name or ID of the host where the server is evacuated.
698         """
699         try:
700             self.rc_util.syslogout('Call Evacuate API with %s to %s' %
701                                    (uuid, targethost), syslog.LOG_INFO)
702             self.nova_client.servers.evacuate(uuid, host=targethost,
703                                               on_shared_storage=False) ★ここを変更
704         except exceptions.ClientException as e:
705             error_code = "[RecoveryControllerUtilApi_0007]"
706             msg = ('Fails to call Instance Evacuate API onto %s: %s'
707                    % (targethost, e))
708             self.rc_util.syslogout(error_code + msg, syslog.LOG_ERR)
709             raise
```