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


背景・目的

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

今回、Vagrant box を build する packer を使って 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 を使いたい場合はゾーン毎の機能を確認してください
  • VirtualBox(6.0.8)
    • Extension Pack(6.0.8)
  • vagrant (2.2.5)
  • packer (1.4.2)

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

公式ドキュメントに従って作成すれば簡単に作成できます。

  1. gcloud をインストールする
  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 を返せばよい
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"
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 の場合

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 の場合

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 の場合

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 ファイルを作成します。

ビルドする
$ 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)の部分はパスを調べて適宜変更してください
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 を設定します。(参考)

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 で何か問題が発生した場合は、ここを参照するとよいでしょう。