vagrant + ansible + CentOS7.0 + VirtualBox 環境で仮想マシンを良い感じに初期化する

# 概要

vagrant + ansible + CentOS7.0 + VirtualBox 環境で
テスト用仮想マシンを立ち上げる際、
初期状態としてプリインされているソフトウェアを最新にしておきたいものです。

ですが、単純に`yum update`だけ行うとカーネルアップデートが生じることがあります。
するとその影響でVirtualBox Guest Additions(以下 vboxguest)が正しく動かなくなっていまう場合があります。

そこで、ソフトウェアアップデートとvboxguestのリビルドをvagrantのプロビジョンで行うようにしたところ、うまく行ったので紹介します。


# 実行環境

- Mac OS Xを使用します。
- VirtualBox: 公式サイトからインストーラをダウンロードします。
- vagrant: 公式サイトからインストーラをダウンロードします。
- vagrant-reload
  プロビジョンにおいてマシンの再起動をかけるために、vagrantのプラグインを導入します。下記コマンドでインストールします。

~~~
$ vagrant plugin install vagrant-reload
~~~

- ansible: homebrewからインストールします。


# 構成

## provision

provision.ymlというansibleのレシピを作成します。
vagrantのプロビジョンでは、それの呼び出しと再起動を2セット行うようにします。

~~~ruby
Vagrant.configure(2) do |config|
  config.ssh.insert_key = false

  config.vm.define "piyo" do |c|
    c.vm.box = "chef/centos-7.0"
    c.vm.network :forwarded_port, guest: 22, host: 2422
    c.vm.provision "ansible" do |ansible|
      ansible.playbook = "provision.yml"
    end
    c.vm.provision "reload"
    c.vm.provision "ansible" do |ansible|
      ansible.playbook = "provision.yml"
    end
    c.vm.provision "reload"
  end

end
~~~

ベースボックスとして`chef/centos-7.0`を使用しています。

## ロール

provision.ymlの内部は下記のようなロール構成にします。

~~~yaml
    - { role: epel,            tags: ['epel'] }
    - { role: yum-update,      tags: ['yum-update'] }
    - { role: vboxguest,       tags: ['vboxguest'] }
~~~

ロールに分けずに中身を書き下しても良いでしょう。

epelロールではepelをインストールします。これは後の工程でdkmsをインストールするためです。

yum-updateロールでは全てのソフトウェアのアップデートを行います。ここでカーネルアップデートが生じる事を想定しています。

vboxguestロールではvboxguest(VirtualBox Guest Additions)のリビルドを行います。

## dkms

vboxguestのようなカーネルモジュールはカーネルのバージョンと対応付けてビルドされます。また、vboxguestのビルドスクリプトはその時起動しているカーネルのバージョン用にビルドを行います。
しかし、カーネルの更新があった時は次回起動時にその新しいカーネルで起動します。そうするとビルドされたカーネルモジュールは再起動前の古いバージョン用となっているために動作に問題が起きることがあります。

実際vboxguestがうまく動作せずvagrant upの際にshared folderの設定でエラーが生じてしまいました。

これを解決してくれるのがdkmsです。dkmsはカーネルモジュールを複数のバージョン用に事前にビルドして登録しておき、OSが起動する時にカーネルバージョンに合わせて適切なモジュールをロードするしくみを提供してくれます。

vboxguestもdkmsに対応しており、ビルドスクリプトがdkmsを検出するとdkms用にビルドしてくれます。

そこで、vboxguestのビルド前にdkmsをインストールし、現在起動しているカーネルとアップデート先のカーネル両方にdkms用のvboxguestをビルドさせることで、再起動時に問題が起こらなくなります。

dkmsをインストールするまでのvboxguestロールのソースは下記のようになります。

~~~yaml
# 最新のカーネル用を取得する
- yum: name=kernel-devel state=latest
# 現在のカーネル用を取得する
- yum: name="kernel-devel-{{ ansible_kernel }}"

- yum: name=gcc
- yum: name=dkms
~~~

1つ目のkernel-develは最新のものを取得しています。2つ目のkernel-develは現在起動しているカーネルのものを取得しています。そのためにansible_kernelという変数を使用しています。

gccはvboxguestのビルドに必要です。

最後にdkmsをインストールしています。なお、dkmsはcentos標準リポジトリには無いためこれより前の段階でepelを登録する必要があります。

## vboxguest CD

vboxguestには仮想環境ホストのVirtualBoxのバージョンに対応してバージョンがあります。
そのためVirtualBoxのバージョンに合わせたvboxguestをインストールします。

vboxguestのインストールは、インストールCDを使用するとvboxguestのバージョンが指定できます。例えば4.3.20は下記URLからダウンロードできます。

http://download.virtualbox.org/virtualbox/4.3.20/VBoxGuestAdditions_4.3.20.iso

このようなisoをダウンロードしてマウントし、中に入っているスクリプトを実行するとvboxguestのリビルドが行われます。

この部分のvboxguestのロールのソースを示します。

~~~
- get_url: url={{ vboxguest_release_url }}
           dest="/usr/local/src/{{ vboxguest_release }}.iso"
- file: path={{ vboxguest_mount }} state=directory
- shell: mount -o loop,ro "/usr/local/src/{{ vboxguest_release }}.iso" {{ vboxguest_mount }}
- shell: "{{ vboxguest_mount }}/VBoxLinuxAdditions.run"
  register: ret
  changed_when: "'All good' in ret.stdout"
  failed_when: "not ret.changed"
- shell: umount {{ vboxguest_mount }}
- file: path={{ vboxguest_mount }} state=absent
~~~

## dkms installed-weak

dkmsに登録されているモジュールの状態にはinstalledとinstalled-weakがあります。
ビルドした時の起動カーネルとモジュールの対象カーネルが一致していた場合にはinstalled, 一致していなかった場合にはinstalled-weakになるようです。

下記はカーネルが`3.10.0-123.el7.x86_64`で起動しているときにビルドした状態です。
最新カーネル用のモジュールがinstalled-weakとなっています。

~~~
[vagrant@localhost ~]$ uname -r
3.10.0-123.el7.x86_64
[vagrant@localhost ~]$ sudo dkms status
vboxguest, 4.3.20, 3.10.0-123.el7.x86_64, x86_64: installed
vboxguest, 4.3.20, 3.10.0-123.20.1.el7.x86_64, x86_64: installed-weak from 3.10.0-123.el7.x86_64
~~~

その後再起動しカーネルが`3.10.0-123.20.1.el7.x86_64`で起動した状態を示します。
カーネルは更新されていますがinstalled-weakの状態は変わっていません。

~~~
[vagrant@localhost ~]$ uname -r
3.10.0-123.20.1.el7.x86_64
[vagrant@localhost ~]$ sudo dkms status
vboxguest, 4.3.20, 3.10.0-123.el7.x86_64, x86_64: installed
vboxguest, 4.3.20, 3.10.0-123.20.1.el7.x86_64, x86_64: installed-weak from 3.10.0-123.el7.x86_64
~~~

使用することになる最新用のモジュールがinstalled-weakであるのは気持ちが悪いため、
最新カーネルで起動している状態で再度モジュールをリビルドします。

このために初めのprovisionで2セット目のレシピ実行と再起動を行っています。
installed-weakでも良い場合は2セット目は不要です。

# 完全なソース

[githubに実行可能な完全なソースをアップしました。](https://github.com/omochi/vagrant-ansible-provision-example)
下記の手順で実際に試すことができます。

~~~
$ git clone https://github.com/omochi/vagrant-ansible-provision-example.git
$ cd vagrant-ansible-provision-example
$ vagrant up
~~~

# 参考資料

- [vboxguestについての公式マニュアル](https://www.virtualbox.org/manual/ch04.html#idp54932560)
- [vagrantのvboxguestインストールのガイド](https://docs.vagrantup.com/v2/virtualbox/boxes.html)