GCE 仮想マシンの入れ子(Nested Virtualization)を許可して Vagrant box ビルド環境を作る

# 背景・目的

Vagrant box をビルドしてアプリケーション開発環境を作ると便利です。
ビルドした Box をチーム全員で利用することで開発環境を統一することが出来ますし、VM なのでプロジェクトや開発に使う言語やフレームワークごとに Box を変えることができ、Box を最新化すれば開発環境のアップデートも出来ます。

今回、Vagrant box を build する [packer](https://www.packer.io/) を使って Ubuntu 18.04LTS ベースの Vagrant box をビルドすることを目的とします。

GCE の Always free で割り当てられるインスタンスが Nested Virtualization に対応していることが分かったので、GCE 上にビルド環境を作ることにします。

(今回はそこまでやりませんが、Jenkins 等の CI ツールでビルド処理を実行すれば、開発環境も CI できます)

# 用意した環境

- ホスト
    - GCE ゾーン `us-west1-b`, Ubuntu 16.04LTS + Nested Virtualization
    - ゾーンは default CPU が Nested Virtualization 対応の Haswell 以降である必要があります
    - Always free を使いたい場合は[ゾーン毎の機能](https://cloud.google.com/compute/docs/regions-zones/regions-zones)を確認してください
- VirtualBox(6.0.8)
    - Extension Pack(6.0.8)
- vagrant (2.2.5)
- packer (1.4.2)

# Netsted Virtualization 対応インスタンスを起動する

[公式ドキュメント](https://cloud.google.com/compute/docs/instances/enable-nested-virtualization-vm-instances)に従って作成すれば簡単に作成できます。

1. gcloud をインストールする
    - https://cloud.google.com/sdk/docs/ を参照して Google Cloud SDK をインストールする
2. カスタムイメージを作成する
    - gcloud コマンドを使って下記(※1)のとおりカスタムイメージを作成する
    - Google Cloud Console の「Compute Engine > イメージ」にカスタムイメージ `nested-ubuntu-xxxx-vyyyymmdd` が作成されたことが確認できる
3. カスタムイメージを使って Compute Engine の VM インスタンスを作成する
    - Google Cloud Console の GCE VMインスタンス作成画面からインスタンス作成ボタンを押す
    - 作成したカスタムイメージ `nested-ubuntu-xxxx-vyyyymmdd` を使う
4. VT-x が有効になっていることを確認する
    - `$ grep -cw vmx /proc/cpuinfo` が 1 を返せばよい

```bash:Ubuntu16.04LTSベースでNestedVirtualization対応イメージを作成する(※1)
$ gcloud compute images create nested-ubuntu-1604-xenial-v20181204 \
--source-image=projects/ubuntu-os-cloud/global/images/ubuntu-1604-xenial-v20181204 \
--licenses "https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx"
```

```bash:Ubuntu18.04LTSベースでNestedVirtualization対応イメージを作成する
gcloud compute images create nested-ubuntu-1804-bionic-v20190628 \
--source-image=projects/ubuntu-os-cloud/global/images/ubuntu-1804-bionic-v20190628 \
--licenses "https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx"
```

# VirtualBox をインストールする

以下、カスタムイメージを使ってインストールした VM インスタンス上で作業します。

最新の VirtualBox がよい場合は https://www.virtualbox.org/wiki/Linux_Downloads から URL を確認してコマンドを変更してください。

## VirtualBox 5.1.38 の場合

```bash:VirtualBoxをインストールする
$ wget https://download.virtualbox.org/virtualbox/5.1.38/virtualbox-5.1_5.1.38-122592~Ubuntu~xenial_amd64.deb
$ sudo apt-get update
$ sudo apt-get install -y ./virtualbox-5.1_5.1.38-122592~Ubuntu~xenial_amd64.deb
```

## VirtualBox 6.0.8 の場合

```
$ wget https://download.virtualbox.org/virtualbox/6.0.8/virtualbox-6.0_6.0.8-130520~Ubuntu~bionic_amd64.deb
$ sudo apt-get update
$ sudo apt-get install -y ./virtualbox-6.0_6.0.8-130520~Ubuntu~bionic_amd64.deb
```

# Vagrant をインストールする

以下、カスタムイメージを使ってインストールした VM インスタンス上で作業します。

最新の Vagrant がよい場合は 
https://www.vagrantup.com/downloads.html から URL を確認してコマンドを変更してください。

## Vagrant 1.9.8 の場合

```bash:vagrantをインストールする
$ wget https://releases.hashicorp.com/vagrant/1.9.8/vagrant_1.9.8_x86_64.deb
$ sudo apt-get update
$ sudo apt-get -y install ./vagrant_1.9.8_x86_64.deb
```

## Vagrant 2.2.5 の場合

$ wget https://releases.hashicorp.com/vagrant/2.2.5/vagrant_2.2.5_x86_64.deb
$ sudo apt-get update
$ sudo apt-get -y install ./vagrant_2.2.5_x86_64.deb

# packer をインストールする

以下、カスタムイメージを使ってインストールした VM インスタンス上で作業します。

## Packer 1.4.2 の場合

```bash:packerをインストールする
$ sudo apt-get update
$ sudo apt-get install -y unzip
$ wget https://releases.hashicorp.com/packer/1.4.2/packer_1.4.2_linux_amd64.zip
$ unzip packer_1.4.2_linux_amd64.zip
$ sudo chown root:root packer
$ sudo mv -i packer /usr/local/bin/
```

# Vagrant Box をビルドする

packer はビルド方法を記した JSON ファイルを基にしてビルド処理を行います。
OS インストールから実施することが出来るが大変なのでベースとなる OVF を使ってカスタムすることにし、下記のような JSON ファイルを作成します。

```bash:ビルドする
$ vagrant box add bento/ubuntu-18.04 # Provider は VirtualBox を選ぶ
$ packer build build.json
```

* `/home/YOUR_ACCOUNT/.vagrant.d/boxes/bento-VAGRANTSLASH-ubuntu-18.04/201812.27.0/virtualbox/box.ovf` は YOUR_ACCOUNT を自身のアカウント名に変更し、日付(201812.27.0)の部分はパスを調べて適宜変更してください

```json:packer用ビルドファイル(build.json)
{
  "variables": {
    "box_basename": "ruby",
    "headless": "true"
  },
  "builders": [
    {
      "type": "virtualbox-ovf",
      "source_path": "/home/YOUR_ACCOUNT/.vagrant.d/boxes/bento-VAGRANTSLASH-ubuntu-18.04/201906.18.0/virtualbox/box.ovf",
      "output_directory": "packer-buildtmp-{{ user `box_basename` }}",
      "vm_name": "{{ user `box_basename` }}",
      "virtualbox_version_file": ".vbox_version",
      "guest_additions_path": "VBoxGuestAdditions_{{.Version}}.iso",
      "headless": "{{ user `headless` }}",
      "ssh_username": "vagrant",
      "ssh_password": "vagrant",
      "shutdown_command": "echo 'vagrant' | sudo -S shutdown -P now"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "execute_command": "echo 'vagrant' | {{.Vars}} sudo -S -E -u root -H sh -eux '{{.Path}}'",
      "scripts": [
        "scripts/ubuntu/custom.sh"
      ],
      "expect_disconnect": true
    }
  ],
  "post-processors": [
    [
      {
        "type": "vagrant",
        "output": "builds/{{ user `box_basename` }}.box",
        "keep_input_artifact": true,
        "compression_level": 9
      }
    ]
  ]
}
```

```scripts/ubuntu/custom.sh
echo HELLO WORLD
# apt-get install で BOX にパッケージをインストールする
```

# その他

## packer の動作ログを表示する

packer 実行時に環境変数 `PACKER_LOG`, `PACKER_LOG_PATH` を設定します。([参考](https://www.packer.io/docs/other/debugging.html))

```packer.logにログを出力する
PACKER_LOG=1 PACKER_LOG_PATH=packer.log packer build dev.build.json
```

## VirtualBox の動作ログを表示する

`~/VirtualBox VMs/${SOME_VM_NAME}/Logs/VBox.log` に VirtualBox の動作ログが表示されます。
VirtualBox で何か問題が発生した場合は、ここを参照するとよいでしょう。