Vagrantで複数環境を立ち上げる際、IPアドレスの割当やポートフォワードを全て自動化する

以前の記事で [Vagrant＋Docker（Provider）を使って複数の環境を立ち上げた](http://qiita.com/jyokyoku/items/db30fee4c66dedaefdd6) が、管理するVagrantfileが増えて行くにつれ、各環境のIPアドレスや設定したポートが分からなくなり、新しい環境を立ち上げる際にポートやIPアドレスがブッキングすることが出てきた。

この問題を解消するべく、どうにかVagrantの機能を使って、IPアドレスの管理とポートフォワードを自動化する方法を試したのでメモ。

# 環境

- Mac OSX Yosemite 10.10.2
- Vagrant 1.7.2
- VirtualBox 4.3.26

# やりたいこと

- Vagrantfile毎にIPアドレスを自動で割り振りたい
- ポートフォワードも自動で設定したい
- `/etc/hosts` にホスト名は自動で設定したい
- NFSを使って共有ディレクトリをマウントしたい

# Vagrantfileの設定

## IPアドレス割り当ての自動化

VMのネットワーク設定をプライベートネットワークとし、`type: "dhcp"` とすることでDHCPによるIPアドレスの割り当てができる。

デフォルトのネットワークアドレスが微妙だったので、ソースコードを読んでみたところ **DHCPのネットワークアドレスや、IPアドレスの割当範囲も設定できる** ようだったので、こちらも設定。

```rb
node.vm.network :private_network, id: "default-network", type: "dhcp", ip: "192.168.34.0"
```

`192.168.34.xxx` のネットワークで自動割り当てがされるように。

もし割当範囲を変えたい場合、`dhcp_lower: "192.168.34.10", dchp_upper: "192.168.34.50"` といった具合に設定を追加すればよい。
 
## ポートフォワードの自動化

コレはとても簡単。
ポートフォワードの設定に `auto_correct: true` を設定するだけ。

```rb
config.vm.network :forwarded_port, guest: 22, host: 2222, id: "ssh", auto_correct: true
```

これでSSH用のポートがぶつかったら、Vagrantがよしなにしてくれる

## `/etc/hosts` ファイルの自動更新

これは以前も使っていた [vagrant-hostupdater](https://github.com/cogitatio/vagrant-hostsupdater) プラグインで対応出来ると思ったら、このプラグインはDHCPでの割り当てには対応していないということが判明。

急遽代替プラグインを探していたら、[vagrant-hostmanger](https://github.com/smdahlen/vagrant-hostmanager) というプラグインを発見。

このプラグインはIPリゾルバにクロージャを設定することができ、VMから割り当てられたIPアドレスを無理矢理引っ張ってくることが出来るという優れもの。

以下のような記述を加えることで、無事 `/etc/hosts` も自動更新ができるように。

```rb
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.hostmanager.ip_resolver = proc do |vm, resolving_vm|
  ip_address = ''
  if hostname = (vm.ssh_info && vm.ssh_info[:host])
    vm.communicate.execute("/sbin/ifconfig eth1 | grep 'inet addr' | tail -n 1 | egrep -o '[0-9\.]+' | head -n 1 2>&1") do |type, contents|
      ip_address = contents.split("\n").first
    end
  end
  ip_address
end
```

上記のIPアドレスを取得している箇所 `vm.communicate.execute("/sbin/ifconfig eth1 | grep 'inet addr' | tail -n 1 | egrep -o '[0-9\.]+' | head -n 1 2>&1")` は、ゲストマシンの種類や環境によって変わると思うので、参考までに。

`vm.communicate` というメソッドが便利で、システムコールで `vagrant ssh -c ...` で実行しようとしてた部分を割とシンプルに記載できた。

## NFSでのディレクトリ共有

これは現状のVagrantだと複数環境でNFSマウントを利用しようとすると、最後に起動した環境しか正しくディレクトリがマウントされないという状態に陥るため、[Vagrant側から/etc/exportsを変更することをやめ、手動で/etc/exportsを設定するように](http://qiita.com/jyokyoku/items/8c6921f741c8d9420e0f) している。

というわけで現時点では手動管理とし、`/etc/exports` にDHCPのネットワークを追記。

```sh:/etc/exports
/srv -network 192.168.34.0 -mask 255.255.255.0 -alldirs -mapall=501:20
```

VagrantfileにもSynced Folderを設定

```rb
config.vm.synced_folder "/srv", "/var/www/html/", type: "nfs", nfs_export: false, mount_options: ["nolock", "vers=3", "udp"]
```

問題なく `/srv` 以下のディレクトリがNFSでマウントできることを確認。

# できあがったVagrantfile

こんな感じになった。

```rb:Vagrantfile
Vagrant.configure(2) do |config|
  config.hostmanager.enabled = true
  config.hostmanager.manage_host = true

  config.vm.define "development.local" do |node|
    node.vm.box = "dduportal/boot2docker"

    node.vm.network :private_network, id: "default-network", type: "dhcp", ip: "192.168.34.1", dhcp_lower: "192.168.34.3", dhcp_upper: "192.168.34.254"
    node.vm.network :forwarded_port, guest: 22, host: 2222, id: 'ssh', auto_correct: true
    
    node.hostmanager.ip_resolver = proc do |vm, resolving_vm|
      ip_address = ''
      if hostname = (vm.ssh_info && vm.ssh_info[:host])
        vm.communicate.execute("/sbin/ifconfig eth1 | grep 'inet addr' | tail -n 1 | egrep -o '[0-9\.]+' | head -n 1 2>&1") do |type, contents|
          ip_address = contents.split("\n").first
        end
      end
      ip_address
    end

    node.hostmanager.aliases = ["development.local"]
    node.ssh.insert_key = false
    
    node.vm.synced_folder "/srv", "/var/www/html/", type: "nfs", nfs_export: false, mount_options: ["nolock", "vers=3", "udp"]
  end
end
```

# 問題点

Macなどホストマシンを再起動した場合に1つの問題が発生。

全てのVMのIPアドレスが、DHCPにより **起動順に再度割り当てられてしまう** ため、IPアドレスに対するホスト名の記述が被ってしまう。

```
## vagrant-hostmanager-start id: {id}
192.168.34.3    test1.local
## vagrant-hostmanager-end

## vagrant-hostmanager-start id: {id}
192.168.34.3    test2.local
## vagrant-hostmanager-end
```

これは vagrant-hostmanager が仮想マシンの `up` と `destroy` の時にのみ `/ect/hosts` ファイルを書き換える動作を行うため、再起動後に各マシンを `vagrant up` しないと、最新の情報に書き換えてくれないからだ。

しかしこの状態だと、例えば開発環境（プロキシVM用のVagrantfile）が10個あったとすると、再起動後に全ての環境をいちいち `vagrant up` しないと、どこかでホストのIPアドレスがぶつかる可能性があり、とても面倒。

結局のところ、これを解決できるプラグインを発見することはできなかったこと、vagrant-hostmanager は既に開発がストップしており、作者がPullRequestにも反応していない状況から自分でやるしかないと判断し、プラグインを改造してみた。

## 改造版vagrant-hostmanager

下記のリポジトリに公開した。
[改造版vagrant-hostmanager](https://github.com/jyokyoku/vagrant-hostmanager)

**注意**
ダウンロードしただけでは利用できないので、自前でGemとしてBuildして、ローカルからインストールする必要があるので注意。

### 改造内容

改造版では、以下のオプションをサポートした。

```sh
vagrant hostmanager --cleanhost
```

`--cleanhost` オプションを付けて実行することで、vagrant-hostmanager が登録したホスト設定を一括してクリアするように。

こうすることでマシンの再起動後に上記コマンドを1回実行すれば、その後は必要なマシンだけを `vagrant up` できる。`/etc/hosts` に設定されたIPアドレスが被っているかを気にする必要は無くなる。

また、本家では `vagrant reload` の際には `/etc/hosts` の書き換えをしてくれなかったので、`vagrant reload` の際にも書き換えをするようにフックを追加してみた。

一応、機を見てPullRequestしてみようと思うけれど、作者が放置気味なので、改造しつつ自前でビルドして使っていくのが一番良いのかもしれない。