vagrant-cloudstackで複数VMの起動&Boxの作成

Vagrantでは1つのVagrantfileから複数の仮想マシンを起動することができます。この機能を利用すると複数台の仮想マシンのクラスタで構成されるような環境も簡単に構築することができて便利です。

# dummy boxで複数VMを作成

複数VMを1つのVagrantfileから作成できるのは便利ですが、dummy boxを利用している場合プロバイダの設定など同じ設定を何度も書いてしまうとVagrantfileが冗長になってしまいます。
例えば、vm01, vm02の2台のVMを起動するVagrantfileはvagrant-cloudstackでは以下のようになります。

vm01, vm02それぞれにプロバイダ・テンプレート・スペックの設定が書かれており非常に長くなっています。vm01, vm02で実際に違いがあるのは名前とポートフォワーディングのポートだけですが把握するのは困難です。

```rb:Vagrantfile
Dotenv.load

Vagrant.configure("2") do |config|

  config.ssh.username         = "#{ENV['VAGRANT_SSH_USERNAME']}"
  config.ssh.private_key_path = "#{ENV['VAGRANT_SSH_PRIVATE_KEY']}"
  config.vm.box = "dummy"
  config.vm.box_url = "https://github.com/klarna/vagrant-cloudstack/raw/master/dummy.box"

  config.vm.define "vm01", primary: true do |vm01|
    vm01.vm.provider :cloudstack do |cloudstack, override|

      cloudstack.host    = "compute.jp-east.idcfcloud.com"
      cloudstack.path    = "/client/api"
      cloudstack.port    = "443"
      cloudstack.scheme  = "https"

      cloudstack.api_key    = "#{ENV['CLOUDSTACK_API_KEY']}"
      cloudstack.secret_key = "#{ENV['CLOUDSTACK_SECRET_KEY']}"

      cloudstack.zone_name             = "tesla"
      cloudstack.template_name         = "CentOS 6.5 64-bit for Vagrant"
      cloudstack.service_offering_name = "light.S1"
      cloudstack.network_type          = "Advanced"
      cloudstack.network_name          = "network1"

      cloudstack.name = "vm01"
      cloudstack.pf_ip_address_id = "#{ENV['CLOUDSTACK_PF_IP_ADDRESS_ID']}"
      cloudstack.pf_public_port   = "2201"
      override.ssh.port           = "2201"
      cloudstack.pf_private_port  = "22"
      cloudstack.keypair          = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
    end
  end

  config.vm.define "vm02" do |vm02|
    vm02.vm.provider :cloudstack do |cloudstack, override|

      cloudstack.host    = "compute.jp-east.idcfcloud.com"
      cloudstack.path    = "/client/api"
      cloudstack.port    = "443"
      cloudstack.scheme  = "https"

      cloudstack.api_key    = "#{ENV['CLOUDSTACK_API_KEY']}"
      cloudstack.secret_key = "#{ENV['CLOUDSTACK_SECRET_KEY']}"

      cloudstack.zone_name             = "tesla"
      cloudstack.template_name         = "CentOS 6.5 64-bit for Vagrant"
      cloudstack.service_offering_name = "light.S1"
      cloudstack.network_type          = "Advanced"
      cloudstack.network_name          = "network1"

      cloudstack.name = "vm02"
      cloudstack.pf_ip_address_id = "#{ENV['CLOUDSTACK_PF_IP_ADDRESS_ID']}"
      cloudstack.pf_public_port   = "2202"
      override.ssh.port           = "2202"
      cloudstack.pf_private_port  = "22"
      cloudstack.keypair          = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
    end
  end
end
```

```rb:.env
CLOUDSTACK_API_KEY          = 'YOUR_API_KEY'
CLOUDSTACK_SECRET_KEY       = 'YOUR_SECRET_KEY'
CLOUDSTACK_PF_IP_ADDRESS_ID = 'YOUR_IP_ADDRESS_ID'
CLOUDSTACK_SSH_KEYPAIR      = 'YOUR_KEYPAIR_NAME'
VAGRANT_SSH_USERNAME        = 'vagrant'
VAGRANT_SSH_PRIVATE_KEY     = 'YOUR_SSH_KEY_PATH'
```

up, ssh, destroyといったコマンドは基本的に1台の場合と同じです。SSHする際にはVM名を指定します。修正したVagrantfileについてもコマンドは同じなので以降コマンドについての説明は割愛します。

```bash
$ vagrant up --provider=cloudstack
$ vagrant ssh vm01
$ vagrant ssh vm02
$ vagrant destroy
```

# 共通部分をまとめてみる

vm01とvm02のプロバイダ・テンプレート・スペックは共通なのでまとめてみます。

vm01、vm02の個別の設定が分離されて大分読みやすくなりました。

```rb:Vagrantfile
Dotenv.load

Vagrant.configure("2") do |config|

  config.ssh.username         = "#{ENV['VAGRANT_SSH_USERNAME']}"
  config.ssh.private_key_path = "#{ENV['VAGRANT_SSH_PRIVATE_KEY']}"

  config.vm.box = "dummy"
  config.vm.box_url = "https://github.com/klarna/vagrant-cloudstack/raw/master/dummy.box"

  config.vm.provider :cloudstack do |cloudstack|

    cloudstack.host    = "compute.jp-east.idcfcloud.com"
    cloudstack.path    = "/client/api"
    cloudstack.port    = "443"
    cloudstack.scheme  = "https"

    cloudstack.api_key    = "#{ENV['CLOUDSTACK_API_KEY']}"
    cloudstack.secret_key = "#{ENV['CLOUDSTACK_SECRET_KEY']}"

    cloudstack.zone_name             = "tesla"
    cloudstack.template_name         = "CentOS 6.5 64-bit for Vagrant"
    cloudstack.service_offering_name = "light.S1"
    cloudstack.network_type          = "Advanced"
    cloudstack.network_name          = "network1"
  end

  config.vm.define "vm01", primary: true do |vm01|
    vm01.vm.provider :cloudstack do |cloudstack, override|
      cloudstack.name = "vm01"
      cloudstack.pf_ip_address_id = "#{ENV['CLOUDSTACK_PF_IP_ADDRESS_ID']}"
      cloudstack.pf_public_port   = "2201"
      override.ssh.port           = "2201"
      cloudstack.pf_private_port  = "22"
      cloudstack.keypair          = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
    end
  end

  config.vm.define "vm02" do |vm02|
    vm02.vm.provider :cloudstack do |cloudstack, override|
      cloudstack.name = "vm02"
      cloudstack.pf_ip_address_id = "#{ENV['CLOUDSTACK_PF_IP_ADDRESS_ID']}"
      cloudstack.pf_public_port   = "2202"
      override.ssh.port           = "2202"
      cloudstack.pf_private_port  = "22"
      cloudstack.keypair          = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
    end
  end
end
```
# Boxの作成

共通部分をまとめることで大分読みやすくなりました。この共通部分をよく見てみると、プロバイダやテンプレート・スペックといった設定は他のVagrantfileでも使えそうです。そこでこの設定をBox化し再利用できるようにしてみます。

ここでは「idcfcloud_centos_6.5」というBoxを作成します。

まず、Box作成用にディレクトリを作成しておきます。どこでも良いのですが今回はVagrantfileと同じディレクトリにボックス名と同じ名前のディレクトリを作ります。

```bash
$ mkdir idcfcloud_centos_6.5
```

作成したディレクトリの中に以下の2つのファイルを作成します。
Vagrantfileでは先ほどまとめた共通部分の設定内容を指定しています。

```rb:Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.provider :cloudstack do |cloudstack|

    cloudstack.host    = "compute.jp-east.idcfcloud.com"
    cloudstack.path    = "/client/api"
    cloudstack.port    = "443"
    cloudstack.scheme  = "https"

    cloudstack.api_key    = "#{ENV['CLOUDSTACK_API_KEY']}"
    cloudstack.secret_key = "#{ENV['CLOUDSTACK_SECRET_KEY']}"

    cloudstack.zone_name             = "tesla"
    cloudstack.template_name         = "CentOS 6.5 64-bit for Vagrant"
    cloudstack.service_offering_name = "light.S1"
    cloudstack.network_type          = "Advanced"
    cloudstack.network_name          = "network1"
  end
end
```

```json:metadata.json
{
    "provider": "cloudstack"
}
```

作成できたら以下のコマンドで固めてBoxにします。

```bash
$ cd idcfcloud_centos_6.5
$ tar cfvz idcfcloud_centos_6.5.box  Vagrantfile metadata.json
```

うまく作成できたらBoxの追加を行います。

```bash
$ vagrant box add --name idcfcloud_centos_6.5 idcfcloud_centos_6.5.box
```

成功すると以下のようなメッセージが表示されます。

```text
==> box: Successfully added box 'idcfcloud_centos_6.5' (v0) for 'cloudstack'!
```

# Boxを使って複数VMを作成する

作成したBoxを利用してVagrantfileを書き直してみます。dummy boxを作成したBoxに書き換え、Boxに記述した共通部分を削除すればOKです。

最初のVagrantfileと比べると大分すっきりしました。Boxの設定はVM毎にできるのでBoxさえ作成しておけば異なる構成のVMからなるクラスタも簡単に記述できます。

```rb:Vagrantfile
Dotenv.load

Vagrant.configure("2") do |config|

  config.ssh.username         = "#{ENV['VAGRANT_SSH_USERNAME']}"
  config.ssh.private_key_path = "#{ENV['VAGRANT_SSH_PRIVATE_KEY']}"
  config.vm.box = "idcfcloud_centos_6.5"

  config.vm.define "vm01", primary: true do |vm01|
    vm01.vm.provider :cloudstack do |cloudstack, override|
      cloudstack.name = "vm01"
      cloudstack.pf_ip_address_id = "#{ENV['CLOUDSTACK_PF_IP_ADDRESS_ID']}"
      cloudstack.pf_public_port   = "2201"
      override.ssh.port           = "2201"
      cloudstack.pf_private_port  = "22"
      cloudstack.keypair          = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
    end
  end

  config.vm.define "vm02" do |vm02|
    vm02.vm.provider :cloudstack do |cloudstack, override|
      cloudstack.name = "vm02"
      cloudstack.pf_ip_address_id = "#{ENV['CLOUDSTACK_PF_IP_ADDRESS_ID']}"
      cloudstack.pf_public_port   = "2202"
      override.ssh.port           = "2202"
      cloudstack.pf_private_port  = "22"
      cloudstack.keypair          = "#{ENV['CLOUDSTACK_SSH_KEYPAIR']}"
    end
  end
end
```

# まとめ

Boxを使用すると複数台の仮想マシンを1つのVagrantfileから作成する場合にも簡潔な記述で済ますことができます。作成したBoxは他のVagrantfileで再利用できるのでよく使う設定についてはBoxを作成しておくと管理が楽になると思います。

# 資料

今回作成したVagrantfile等は以下にあります。
* https://github.com/atsaki/vagrant-examples/tree/master/multivm