Vagrant+VirtualBoxでApache CloudStackを動かす

最近Apache CloudStackに試したい機能が色々と追加されているので、さくっと試せるようにVagrant+VirtualBoxでApache CloudStackを動かしてみます。

# DevCloudとは

DevCloudはVagrantとVirtualBoxで簡単にApache CloudStackの環境を構築できるツールです。

[DevCloud - Apache Cloudstack - Apache Software Foundation](https://cwiki.apache.org/confluence/display/CLOUDSTACK/DevCloud)

DevCloud自体は数年前からあり以下のように紹介記事もあります。基本的な部分は大きく変わっていないですが、数年前の記事で変わっている部分もあるので今回は新しくやり直してみます。

[Cloud OS「Apache CloudStack」をお手軽に使ってみる方法](http://www.slideshare.net/samemoon/20131116-osc-fukuoka)

## DevCloudが作成する仮想マシン

DevCloudは管理サーバー(management)とホスト(xenserver)の2台の仮想マシンをVirtualBox上に作成します。

管理サーバーにはCloudStackの管理サーバーの他にもNFSやMySQLがインストールされます。
ホストにはXenServer 6.2がインストールされます。

この2台の仮想マシンにCloudStackの動作に必要なソフトウェアはほとんどインストールされているので、CloudStackの設定だけで様々な構成を試すことができます。

# DevCloudを使ったCloudStack構築手順

## 作成する環境について

### 検証機

* Mac OS X 10.11.6 (El Capitan)
    * プロセッサ 1.4GHz Intel Core i5
    * メモリ 8GB 1600MHz DDR3

### バージョン

* VirtualBox (5.0.26)
* Vagrant (1.8.5)
* vagrant-berkshelf (5.0.0)
* vagrant-omnibus (1.4.1)
* ChefDK (0.17.17)

### CloudStack

今回はApache CloudStack 4.9　を使用します。
他のバージョンでも大きく手順は変わらないと思います。

[CloudStack Packages | The CloudStack Company](http://www.shapeblue.com/packages/)

#### ネットワーク構成

今回はAdvancedゾーンを作成します。

Marvinの設定ファイルを変更すればBasicゾーンも同じやり方で作成できると思います。

#### プライマリストレージ

デフォルトのMarvinの設定ファイルではプライマリストレージにホスト(xenserver)のローカルストレージを使用するようになっていますが、ローカルストレージとNFSでは使い方が異なるところがいくつかあるのでどちらも使えるように設定ファイルを書き換えて見ます。

## 必要なソフトウェアのインストールと設定

まず、以下のソフトウェアをインストールします。

* [VirtualBox](https://www.virtualbox.org/)
* [Vagrant](https://www.vagrantup.com/)
* [ChefDK](https://downloads.chef.io/chef-dk/)

DevCloudは仮想マシンをChefでプロビジョニングします。Chefでのプロビジョニングに必要となるプラグインをインストールします。

```bash
$ vagrant plugin install vagrant-berkshelf vagrant-omnibus
```

### Marvinのインストール

ゾーンを構築する際に使用するMarvinをインストールします。次のように[Jenkinsの成果物](https://builds.apache.org/job/cloudstack-marvin/)を使ってインストールするのが楽だと思います。MarvinがなくてもCloudStackのインストールはできるので、上手く行かなければとりあえずスキップしても良いです。

```bash
$ pip install http://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-2.0.4.zip
$ curl -O https://builds.apache.org/job/cloudstack-marvin/lastBuild/artifact/tools/marvin/dist/Marvin*.tar.gz/*zip*/dist.zip
$ unzip -o dist.zip
$ pip install Marvin*.tar.gz
```

依存関係にあるcryptographyのインストールで失敗する場合は、下記リンクを参考にcryptographyをインストールしてから再度`pip install Marvin*.tar.gz`するとうまくいくかもしれないです。

[Installation — Cryptography 1.6.dev1 documentation](https://cryptography.io/en/latest/installation/)

### VirtaulBoxのネットワーク設定

Macの場合は下記のようにVirtualBoxのネットワーク設定を変更します。
（Windowsの場合はアダプタの名前が違うようです。[README](https://github.com/apache/cloudstack/blob/master/tools/devcloud4/README.md)にしたがって設定してください。）

1. VirtualBoxのメニューから「環境設定」を開きます。
2. 「ネットワーク」タブの「ホストオンリーネットワーク」を開きます。
3. vboxnet0, vboxnet1, vboxnet2をそれぞれ下記のように設定します。（DHCPサーバーは「DHCPサーバー」タブの「サーバーを有効化」のチェックを外してください）

* vboxnet0
    * IPv4アドレス: 192.168.22.1
    * IPv4ネットマスク: 255.255.0
    * DHCPサーバー: 無効
* vboxnet1
    * IPv4アドレス: 192.168.23.1
    * IPv4ネットマスク: 255.255.0
    * DHCPサーバー: 無効
* vboxnet2
    * IPv4アドレス: 192.168.24.1
    * IPv4ネットマスク: 255.255.0
    * DHCPサーバー: 無効

### Vagrant 1.8.5の不具合対応

Vagrantのバージョン1.8.5にはauthorized_keysのパーミッションが正しく設定されない不具合があるので`plugins/guests/linux/cap/public_key.rb`を探して（私の環境では`/opt/vagrant/embedded/gems/gems/vagrant-1.8.5/plugins/guests/linux/cap/public_key.rb`）次のように修正します。
1.8.5以外のバージョンでは修正の必要はありません。

[fix for #7610 (incorrect permissions on ~/.ssh/authorized_keys causes authentication failure after insecure keypair replacement) by Poohblah · Pull Request #7611 · mitchellh/vagrant · GitHub](https://github.com/mitchellh/vagrant/pull/7611/files)

```diff:plugins/guests/linux/cap/public_key.rb
             if test -f ~/.ssh/authorized_keys; then
               grep -v -x -f '#{remote_path}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp
               mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys
+              chmod 0600 ~/.ssh/authorized_keys
             fi
```

### CloudStackのソースコードを取得

DevCloudのソースコードはCloudStackののレポジトリに含まれているので`git clone`等で取得します。

```bash
$ git clone https://github.com/apache/cloudstack.git
```

Advancedゾーン用のファイルがあるディレクトリに移動します。
`cloudstack/tools/devcloud4`にはBasicゾーン用の設定もあるので、Basicゾーンを構築する場合はそちらを使うと良いでしょう。

```bash
$ cd cloudstack/tools/devcloud4/binary-installation-advanced
```

## Vagrantfileとchef_configuration.jsonの修正

### Chefのバージョンをlatestに変更

元のVagrantfileでは仮想マシンにインストールするChefのバージョンに`11.16.4`が指定されていますが、そのままだとエラーになるので`:latest`に書き換えます。

```diff:Vagrantfile
-    management.omnibus.chef_version = "11.16.4" 
+    management.omnibus.chef_version = :latest
```

### SyncedFolderを無効化

私の環境ではデフォルトのSyncedFolderを有効にしておくとエラーになったので無効にしておきます。

```diff:Vagrantfile
@@ -40,6 +40,7 @@ end
 
 Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
 
+  config.vm.synced_folder ".", "/vagrant", disabled: true
   config.vm.define 'xenserver' do |xenserver|
     xenserver.vm.box = 'duffy/xenserver'
```

### Chefレシピの修正

そのままでは仮想マシンを起動時にMarvinのインストールに失敗するので`../common/binary-installation/recipes/management_server.rb`からインストールの処理をコメントアウトします。

[インストールしておいたローカルのMarvin](#必要なソフトウェアのインストールと設定)でゾーンの作成を行うので、この処理は実行されなくても問題ありません。

```diff:../common/binary-installation/recipes/management_server.rb
@@ -17,7 +17,7 @@
 #  under the License.
 #
 
-include_recipe 'cloudstack::marvin'
+# include_recipe 'cloudstack::marvin'
 include_recipe 'cloudstack::management_server'
 
 cloudstack_setup_database node['cloudstack']['db']['host'] do
```

### 仮想マシンのメモリサイズの変更

デフォルトのメモリサイズはxenserverが6GB、managementが2GBとなっていますが、使用するマシンによっては大きすぎて動かせないので調整します。

私の環境では以下のようにしました。

```diff:Vagrantfile
@@ -75,7 +75,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
       v.customize ['modifyvm', :id, '--hostonlyadapter2', virtualbox_interface_0]
       v.customize ['modifyvm', :id, '--hostonlyadapter3', virtualbox_interface_1]
       v.customize ['modifyvm', :id, '--hostonlyadapter4', virtualbox_interface_2]
-      v.customize ['modifyvm', :id, '--memory', 6144]
+      v.customize ['modifyvm', :id, '--memory', 2048]
     end
 
     ## Tweak kernel
@@ -92,7 +92,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
     management.vm.network 'forwarded_port', guest: 8080, host: 8080
 
     management.vm.provider 'virtualbox' do |v|
-      v.customize ['modifyvm', :id, '--memory', 2048]
+      v.customize ['modifyvm', :id, '--memory', 512]
       v.customize ['modifyvm', :id, '--hostonlyadapter2', virtualbox_interface_0]
       v.customize ['modifyvm', :id, '--hostonlyadapter3', virtualbox_interface_1]
     end
```

### CloudStackのバージョン変更

`chef_configuration.json`のレポジトリとシステムVMテンプレートのURLを構築したいバージョンに合わせて変更します。

```diff:chef_configuration.json
@@ -24,12 +24,11 @@
         "path": "/exports/primary"
       },
       "hypervisor_tpl": {
-        "xenserver": "http://packages.shapeblue.com/systemvmtemplate/4.5/systemvm64template-4.5-xen.vhd.bz2"
+        "xenserver": "https://packages.shapeblue.com/systemvmtemplate/4.6/systemvm64template-master-4.6.0-xen.vhd.bz2"
       },
       "configuration": "/vagrant/marvin.cfg.erb",
-      "yum_repo": "http://packages.shapeblue.com/cloudstack/testing/centos/4.5/",
-      "apt_repo": "http://packages.shapeblue.com/cloudstack/testing/debian/4.5/",
-      "version": "4.5.0"
+      "yum_repo": "http://packages.shapeblue.com/cloudstack/main/centos/4.9/",
+      "version": "4.9.0"
   },
   "iptables": {
     "lans": ["eth1", "eth2"]
```

### CloudStack設定の初期値の変更

CloudStackインストール後にMarvinを使って設定を行うため8096番ポートを許可したり、CPUやメモリが少ない環境でも仮想マシンを起動できるようデフォルトのオファリングのスペックを変更したりします。

設定変更は`prefill.sql`というファイルを使用して直接データベースを更新します。
[SyncedFolderを無効にした](#syncedfolder%E3%82%92%E7%84%A1%E5%8A%B9%E5%8C%96)のでパスを修正しておきます。

```diff:Vagrantfile 
@@ -110,6 +111,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
 
     CHEF_CONFIGURATION = JSON.parse(Pathname(__FILE__).dirname.join('chef_configuration.json').read)
 
+    management.vm.provision :file, source: "./prefill.sql", destination: "/tmp/prefill.sql"
     management.vm.provision :chef_solo do |chef|
       chef.log_level = :debug
       chef.run_list = CHEF_CONFIGURATION.delete('run_list')
```

```diff:chef_configuration.json
@@ -15,7 +15,7 @@
         "rootpassword": "password",
         "management_server_key": "password",
         "database_key": "password",
-        "prefill": "/vagrant/prefill.sql"
+        "prefill": "/tmp/prefill.sql"
       },
       "secondary": {
         "path": "/exports/secondary"
```

元の`prefill.sql`は`cloudstack/tools/devcloud4/prefill.sql`にあるのでコピーして必要な箇所を変更します。主な変更点は以下の2つです。

* NFS用のオファリングを追加
* オーバープロビジョニング係数と閾値を引き上げ

```bash
$ cp ../prefill.sql .
```

```diff:prefill.sql
@@ -15,9 +15,12 @@
 -- specific language governing permissions and limitations
 -- under the License.
 
-REPLACE INTO `cloud`.`disk_offering` (id, name, uuid, display_text, created, use_local_storage, type, disk_size) VALUES (17, 'Devcloud4 offering', UUID(), 'Devcloud4 offering', NOW(), 1, 'Service', 0);
+REPLACE INTO `cloud`.`disk_offering` (id, name, uuid, display_text, created, use_local_storage, type, disk_size) VALUES (17, 'Devcloud4 offering - Local Storage', UUID(), 'Devcloud4 offering - Local Storage', NOW(), 1, 'Service', 0);
 REPLACE INTO `cloud`.`service_offering` (id, cpu, speed, ram_size) VALUES (17, 1, 200, 256);
-REPLACE INTO `cloud`.`disk_offering` (name, uuid, display_text, created, use_local_storage, type, disk_size) VALUES ('Devcloud4 disk offering', UUID(), 'Devcloud4 disk offering', NOW(), 1, 'Disk', 1073741824);
+REPLACE INTO `cloud`.`disk_offering` (name, uuid, display_text, created, use_local_storage, type, disk_size) VALUES ('Devcloud4 disk offering - Local Storage', UUID(), 'Devcloud4 disk offering - Local Storage', NOW(), 1, 'Disk', 1073741824);
+REPLACE INTO `cloud`.`disk_offering` (id, name, uuid, display_text, created, use_local_storage, type, disk_size) VALUES (19, 'Devcloud4 offering', UUID(), 'Devcloud4 offering', NOW(), 0, 'Service', 0);
+REPLACE INTO `cloud`.`service_offering` (id, cpu, speed, ram_size) VALUES (19, 1, 200, 256);
+REPLACE INTO `cloud`.`disk_offering` (name, uuid, display_text, created, use_local_storage, type, disk_size) VALUES ('Devcloud4 disk offering', UUID(), 'Devcloud4 disk offering', NOW(), 0, 'Disk', 1073741824);
 REPLACE INTO `cloud`.`configuration` (category, instance, component, name, value) VALUES ('Advanced', 'DEFAULT', 'management-server', 'integration.api.port', '8096');
 REPLACE INTO `cloud`.`configuration` (instance, name,value) VALUE('DEFAULT','router.ram.size', '256');
 REPLACE INTO `cloud`.`configuration` (instance, name,value) VALUE('DEFAULT','router.cpu.mhz','256');
@@ -31,4 +34,10 @@
 REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'expunge.interval', '60');
 REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'management.network.cidr', '0.0.0.0/0');
 REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'secstorage.allowed.internal.sites', '0.0.0.0/0');
-UPDATE `cloud`.`vm_template` SET unique_name="Macchinina",name="Macchinina",url="http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-xen.vhd.bz2",checksum="30985504bc31bf0cd3b9d2c6ca7944d3",display_text="Macchinina" where id=5;
\ No newline at end of file
+REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'cluster.cpu.allocated.capacity.disablethreshold', '1');
+REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'cluster.memory.allocated.capacity.disablethreshold', '1');
+REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'pool.storage.allocated.capacity.disablethreshold', '1');
+REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'pool.storage.capacity.disablethreshold', '1');
+REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'storage.overprovisioning.factor', '3');
+REPLACE INTO `cloud`.`configuration` (instance, name, value) VALUE('DEFAULT', 'mem.overprovisioning.factor', '3');
+UPDATE `cloud`.`vm_template` SET unique_name="Macchinina",name="Macchinina",url="http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-xen.vhd.bz2",checksum="30985504bc31bf0cd3b9d2c6ca7944d3",display_text="Macchinina" where id=5;
```

### 仮想マシンの起動

`vagrant up`を実行すると`xenserver`と`management`の2台の仮想マシンが立ち上がります。

```bash
$ vagrant up
```

完了後にブラウザで[http://192.168.22.5:8080/client/](http://192.168.22.5:8080/client/)または[http://localhost:8080/client/](http://localhost:8080/client/)にアクセスするとCluodStackのUIが開きます。
ユーザーネーム: admin、パスワード: passwordでログインできます。
（CloudStackのUIが見えるようになるまで完了してから数分かかるかもしれません。）

### ゾーン作成

Marvinを使ってゾーンを作成します。

`marvin.cfg.erb`をコピーして管理サーバーのIPアドレスを書き換えます。

```bash
$ cp marvin.cfg.erb marvin.cfg
```

```diff:marvin.cfg
@@ -109,15 +109,15 @@
     },
     "mgtSvr": [
         {
-            "mgtSvrIp": "<%= @management_server_ip %>",
-            "port": <%= @management_server_port %>
+            "mgtSvrIp": "192.168.22.5",
+            "port": 8096
         }
     ],
     "dbSvr": {
-        "dbSvr": "<%= @database_server_ip %>",
-        "port": <%= @database_server_port %>,
-        "user": "<%= @database_user %>",
-        "passwd": "<%= @database_password %>",
-        "db": "<%= @database %>"
+        "dbSvr": "192.168.22.5",
+        "port": 3306,
+        "user": "cloud",
+        "passwd": "cloud",
+        "db": "cloud"
     }
```

次のコマンドを実行するとゾーンが作成されます。

```bash
$ python ../../../tools/marvin/marvin/deployDataCenter.py -i marvin.cfg
```

UIから「Infrastructure > System VMs」を確認して、2台のVM(SSVM, CPVM)が正常に起動していれば成功です。

![システムVMの起動を確認.png](https://qiita-image-store.s3.amazonaws.com/0/15114/45dee1db-a91a-6efa-8241-a012045060fc.png)

### 仮想マシンの作成

動作確認のため仮想マシンを作成してみます。
基本的には通常通りに作成すれば良いのですが、普通のテンプレート、オファリングを使うと大きすぎてキャパシティの制限に引っかかったりするので`prefill.sql`で追加した以下のテンプレート、オファリングを使うと良いです。

* テンプレート: Macchinina
* Compute Offering
    * Devcloud4 offering
    * Devcloud4 offering - Local Storage
* Disk Offering
    * Devcloud4 disk offering
    * Devcloud4 disk offering - Local Storage

`Macchinina`テンプレートから作成したVMには、ユーザー: root、パスワード: passwordでログインできます。

## トラブルシューティング

システムVMが正常に起動しなかったり、仮想マシンの作成に失敗してしまう場合はmanagentにログインしてログを確認します。キャパシティの制限に引っかかっているケースが多いのではないかと思います。

```bash
$ vagrant ssh management
$ sudo less /var/log/cloudstack/management/management-server.log
```