成り行き

私はVirtualBox上に仮想マシンを立ててちょっとした試しをする、というのを週に何回かはやっている。
Vagrantを使えば仮想マシンを立てるのに時間が掛からないというのはわかっているのだけど、Cobbler使ってほぼ最新のパッケージで仮想マシンを立てられるというのも捨て難く、これまでVagrantは採用していなかった。

つまりインターネット上で配布されているVagrant Boxでは満足できない、というわけなのだが、遂に私は両方のメリットを享受すべく、Packerを使ってCobblerにあるパッケージから定期的に自分でVagrant Boxを作成することにしてみた。

また、手習いとしてただ作るだけでは面白くないので、ストレージ容量を簡単に増加できるようなBoxを作ってみる。

とりあえずCentOS 7のVagrant Boxを作ることにする。
他のものは余裕があればここに追記するか、また新しく記事を立て記載するかもしれない。

環境

作業環境はWindowsであり、Vagrantは1.7.2、Packerは0.7.5とこの文章を書いている時点で最新のものをインストールして使用している。
Chocolateyを使ってインストールしたのでパスは通っている。

VirtualBoxはまだ4.3.18を使用している。4.3.20はこちらでは問題が出たし、最新の4.3.22もあまり評判がよろしくないようなのでまだ試していない。
こちらはパスは通っていない。

2015-03-07追記: VirtualBox 4.3.24は特に問題が見つからなかったため、現在4.3.24を使用している。

Cobblerは2.6.7であり、VirtualBox上の仮想マシンの1つで起動している。
もっとも今回Cobblerはkickstartファイルを提供するWebサーバとしてと、yumリポジトリのミラーとしてしか使わないので、kickstartファイルを全部自分で書いて、yumリポジトリはインターネット上のどこかにすれば同じことになる。
PackerでBoxを作る時に、近くのCobblerにあるyumリポジトリのミラーからパッケージをダウンロードする方が、インターネット上からパッケージをダウンロードしてくるより時間が掛からなくて済むというだけだ。

Packer

Packerは仮想マシンイメージを作成してくれるツールである。
VirtualBox用のVagrant Boxを作成する場合、次のような動きをする。

  • 仮想マシンをVirtualBox上に立て、OSのISOイメージを使って起動し、kickstartファイルのような自動応答ファイルを使うなどしてOSをインストールする。
  • OSのインストールが終わった後、再起動されOSが立ち上がってきた時点で、プロビジョニング用の任意のシェルスクリプト等を実行してからOSをシャットダウンする。
  • 作った仮想マシンをエクスポートし、デフォルト設定を定義したVagrantfileなどとひとまとめに圧縮してVagrant Boxにする。

事前に必要なファイル等

Packerを実行する前に、次のようなものが必要になる。

  • Packerの実行内容を定義したJSONファイル。
  • OSのISOインストーラ。今回はkickstartファイルでインストール元にリポジトリを指定するため、ISOはインストールに使用できる中で一番小さなもの、つまりネットインストール用のものが良い。Packerにhttpでダウンロードさせることもできるが、何度も使う以上ダウンロードしておく方が良い。私は http://ftp.iij.ad.jp/pub/linux/centos/7.0.1406/isos/x86_64/CentOS-7.0-1406-x86_64-NetInstall.iso からダウンロードした。
  • ISOインストーラのチェックサム。md5で良い。 http://ftp.iij.ad.jp/pub/linux/centos/7.0.1406/isos/x86_64/md5sum.txt から取得できる。
  • OSインストールに使用する自動応答ファイル。CentOSなのでkickstartファイルを用意する。
  • (任意で)プロビジョニング時に使用するシェルスクリプト等。Packerには例えばAnsibleのplaybook等をプロビジョニングのために食わせることもできるが、今回の内容ではシェルスクリプトを食わせることにする。
  • (任意で)デフォルト設定を定義したVagrantfile。今回は使用しない。

Packerの実行内容を定義したJSONファイル

今回、centos7.jsonという名前のファイルを以下の内容で作成している。

centos7.json
{
  "builders": [
    {
      "type": "virtualbox-iso",
      "vm_name": "c7-packer",
      "boot_wait": "10s",
      "disk_size": 16384,
      "guest_os_type": "RedHat_64",
      "iso_checksum": "96de4f38a2f07da51831153549c8bd0c",
      "iso_checksum_type": "md5",
      "iso_url": "CentOS-7.0-1406-x86_64-NetInstall.iso",
      "ssh_wait_timeout": "15m",
      "ssh_username": "vagrant",
      "ssh_password": "vagrant",
      "shutdown_command": "sudo poweroff",
      "vboxmanage": [
        ["setextradata", "global", "GUI/SuppressMessages", "confirmInputCapture,remindAboutAutoCapture,remindAboutMouseIntegration,remindAboutWrongColorDepth"],
        ["modifyvm", "{{.Name}}", "--nictype1", "virtio"],
        ["modifyvm", "{{.Name}}", "--cpus", "1", "--memory", "1024"]
      ],
      "boot_command": [
        "<up><tab> text ks=http://192.168.11.80/cblr/svc/op/ks/profile/c7-packer<enter><wait>"
      ]
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "execute_command": "sudo sh '{{.Path}}'",
      "override": {
        "virtualbox-iso": {
          "scripts": [
            "virtualbox.sh"
          ]
        }
      }
    }
  ],
  "post-processors": [
    {
      "type": "vagrant",
      "output": "c7-packer.box"
    }
  ]
}

特徴的な部分を説明する。

      "disk_size": 16384,

立ち上げられる仮想マシンのストレージサイズを指定している。
16384なので16GBとなる。
ここで設定した値はPackerで作成したBoxをVagrantで立ち上げた際のデフォルト値にもなる。

今回の目的はVagrantで立ち上げた際に、ここで指定した値より大きなサイズを指定するだけでストレージのサイズが変更され、OS上からも使用できるようにすることである。

ちなみにストレージのサイズはここに指定したより小さくすることはできないので、もしもっと小さいものが必要ならここでもっと小さい値を指定する。

      "iso_checksum": "96de4f38a2f07da51831153549c8bd0c",
      "iso_checksum_type": "md5",
      "iso_url": "CentOS-7.0-1406-x86_64-NetInstall.iso",

iso_urlにはダウンロードしたISOファイルのPacker実行フォルダからの相対、あるいは絶対パスを指定する。
ISOファイルをPacker実行フォルダに置けば、このようにファイル名のみで指定できる。

iso_checksumとiso_checksum_typeはISOファイルのチェックサムとその種類を指定する。

      "ssh_username": "vagrant",
      "ssh_password": "vagrant",

これはプロビジョニングのタイミングでPackerがログインするために使用するユーザ名とパスワードである。
つまり、OSインストール時にこのユーザは作成されていなければならない。

      "shutdown_command": "sudo poweroff",

プロビジョニング終了後にシャットダウンを行う際のコマンドを指定する。
上記のユーザで実行されるが、Packerで作成したBoxをVagrantで起動した際にこのユーザはパスワードなしでsudoできるようになっている必要がある。
ここではすでにパスワードなしでsudoできることを前提にしてシャットダウンコマンドをsudo poweroffにしている。

      "vboxmanage": [
        ["setextradata", "global", "GUI/SuppressMessages", "confirmInputCapture,remindAboutAutoCapture,remindAboutMouseIntegration,remindAboutWrongColorDepth"],
        ["modifyvm", "{{.Name}}", "--nictype1", "virtio"],
        ["modifyvm", "{{.Name}}", "--cpus", "1", "--memory", "1024"]
      ],

vboxmanageの中は仮想マシン作成後、起動前に実行するVBoxManageコマンド(VirtualBoxを操作するコマンド)を定義する。

【setextradataの行】

Packerで仮想マシンを起動すると「現在のビデオモードではゲストOSでマウス統合機能がサポートされません。(以下略)」というメッセージが仮想マシンのコンソールにオーバーレイして表示される。

Packerのソース builder/virtualbox/common/driver_4_2.go を見ると、Packer実行時にはVBoxManage setextradata global GUI/SuppressMessages confirmInputCapture,remindAboutAutoCapture,remindAboutMouseIntegrationOff,remindAboutMouseIntegrationOn,remindAboutWrongColorDepthが実行されているようだ。
これはオーバーレイメッセージを抑止するための命令であり、このうち上記のメッセージはVirtualBox 4.2ではremindAboutMouseIntegrationOffのものに当たるが、VirtualBox 4.3ではremindAboutMouseIntegrationOffとremindAboutMouseIntegrationOnが廃止され、代わりに合わせてremindAboutMouseIntegrationになっているようだ。

そのためこちらで強制的にVBoxManage setextradata global GUI/SuppressMessages confirmInputCapture,remindAboutAutoCapture,remindAboutMouseIntegration,remindAboutWrongColorDepthが実行され、メッセージが正しく抑止されるようにしているのがsetextradataの行である。

VirtualBoxのソースは4.3.22と4.2.28で比較確認した。
4.3.22は src/VBox/Frontends/VirtualBox/src/globals/UIPopupCenter.cpp
4.2.28は src/VBox/Frontends/VirtualBox/src/globals/UIMessageCenter.cpp
辺りである。

なお、これはVirtualBox全体設定に影響する。
つまりこれを実行しておかないと、Packerを一たび実行すれば以降すべてのVMで「現在のビデオモードではゲストOSでマウス統合機能がサポートされません。(以下略)」か、サポートされる方のメッセージのどちらかかが出力され続けることになる。

【modifyvm –nictype1の行】

立ち上げられる仮想マシンのNICはNAT割り当てになっている。
NAT割り当ての場合、アダプタータイプをvirtioにしておくと通信速度が大幅に向上する。というかそうしないと遅すぎてお話にならない。
これを行うのがmodifyvm –nictype1の行である。

なお、NAT割り当て以外、少なくともブリッジアダプター割り当てではvirtioにすると逆に大幅に遅くなった。
また、virtioにするとOS上のNIC名がenp0s3ではなくeth0になる。

【modifyvm –cpus/–memoryの行】

立ち上げられる仮想マシンのCPU数とRAM量を指定している。
ここで設定した値はPackerで作成したBoxをVagrantで立ち上げた際のデフォルト値にもなる。

      "boot_command": [
        "<up><tab> text ks=http://192.168.11.80/cblr/svc/op/ks/profile/c7-packer<enter><wait>"
      ]

インストーラ立ち上げ後に入力する文字列を指定する。
<up>は↑キーの入力である。インストーラ画面を直接見ればわかるが、↑を入力すればISOのチェックが行われない。iso_checksumでチェックしているので再度のチェックは不要だろう。
ks=の後にはkickstartファイルを指定する。ここではCobblerから提供されるようにしている。

  "provisioners": [
    {
      "type": "shell",
      "execute_command": "sudo sh '{{.Path}}'",
      "override": {
        "virtualbox-iso": {
          "scripts": [
            "virtualbox.sh"
          ]
        }
      }
    }
  ],

provisionersにはOSインストールが終了して再起動が行われた後に行う処理を定義する。

ここではsudo sh virtualbox.shが実行されることになる。
virtualbox.shはこちらで用意するものである。Packer実行フォルダからの相対、あるいは絶対パスを指定するが、Packer実行フォルダに置いておくとファイル名のみで指定できる。

kickstartファイル

次のようなkickstartファイルを使用している。
(Cobblerが使う部分は省略した)

auth --useshadow --passalgo=sha512
bootloader --location=mbr --boot-drive=sda
clearpart --all --drives=sda
text
firewall --enabled
firstboot --disable
ignoredisk --only-use=sda
keyboard --vckeymap=jp106 --xlayouts='jp'
lang en_US.UTF-8

url --url=http://192.168.11.80/cblr/links/c7-x86_64
repo --name=c7-updates --baseurl=http://192.168.11.80/cobbler/repo_mirror/c7-updates
repo --name=c7-extras --baseurl=http://192.168.11.80/cobbler/repo_mirror/c7-extras
repo --name=epel7 --baseurl=http://192.168.11.80/cobbler/repo_mirror/epel7
repo --name=source-1 --baseurl=http://192.168.11.80/cobbler/ks_mirror/c7
network --bootproto=dhcp --device=eth0 --onboot=on

reboot

rootpw --lock
user --groups=wheel --name=vagrant --password=vagrant

selinux --enforcing
skipx
timezone Asia/Tokyo --isUtc

install
zerombr
part / --fstype=xfs --size=1 --grow

%pre
%end

%packages
@base
@core
kernel-devel
gcc
cloud-utils-growpart
#cloud-init
%end

%post
cat > /etc/sudoers.d/vagrant <<EOD
vagrant ALL=(ALL) NOPASSWD: ALL
Defaults:vagrant !requiretty
EOD

chmod 440 /etc/sudoers.d/vagrant

mkdir /home/vagrant/.ssh
curl "https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub" -o /home/vagrant/.ssh/authorized_keys
chown -R vagrant /home/vagrant/.ssh
chmod -R go-rwsx /home/vagrant/.ssh

#cat > /etc/cloud/cloud.cfg <<EOD
#cloud_init_modules:
# - growpart
# - resizefs
#datasource_list: [None]
#datasource:
# None:
#  metadata:
#EOD
%end

特徴的な部分を説明する。

lang en_US.UTF-8
part / --fstype=xfs --size=1 --grow

今回、ストレージサイズを大きくするという目的のためにcloud-utils-growpartパッケージにあるgrowpartコマンドを使用する。

しかしgrowpartコマンドにはバグがあり、それを回避するためには以下の2つを満たしていなければならない。

  1. 言語設定が英語である
  2. ルートパーティションはディスクの最初のパーティションである

growpartは実際のところシェルスクリプトであり、1つ目は中で実行しているsfdiskコマンドの出力が英語の場合のフォーマットであることを当て込んでいるために発生する。
試していないがgrowpartシェルスクリプトの最初の方にLANG=Cとなど1行追加しておけばこの問題は解決すると思う。

2つ目はgrowpartの中で実行しているpartxコマンドのバグである。partxはutil-linuxパッケージに含まれ、この問題はutil-linux 2.24で解決しているようだ1。しかしCentOS 7では2.23なのでバックポートを待つことになる。

そのためパーティションはルートパーティション1つだけにしてある。

ルートパーティションの前に1つパーティションを作らないとならないような設定、例えば「EFIを有効化」したり、ディスクラベルをGPTにしたりすることはできない。

なおLVMにしなかったのは元々cloud-initを使ってgrowpartする予定だったからである。
結局cloud-initを使う案はプランBになったのだけど(出力がうざかったから)。
上記kickstartファイルの中で#でコメントアウトされた部分がcloud-initを使った場合に追加していた部分である。

後述するがファイルシステムがxfsかext系かでこの後の処理が変わる。
cloud-initのresizefsモジュールはどのファイルシステムか自動判別して良いようにしてくれるけれど。

url --url=http://192.168.11.80/cblr/links/c7-x86_64
repo --name=c7-updates --baseurl=http://192.168.11.80/cobbler/repo_mirror/c7-updates
repo --name=c7-extras --baseurl=http://192.168.11.80/cobbler/repo_mirror/c7-extras
repo --name=epel7 --baseurl=http://192.168.11.80/cobbler/repo_mirror/epel7
repo --name=source-1 --baseurl=http://192.168.11.80/cobbler/ks_mirror/c7

ここはCobblerが自動生成している部分である(自動生成されたものなので実際には不要なものも含んでいる)。
url行はインストール元を指定する。url行はネットインストールのISOを使用する場合にcdrom行の代わりに使用する。
repo行はOSインストール時に使用する追加リポジトリを指定する(/etc/yum.repos.dに配置するリポジトリを決定するものではない)。

最新のパッケージをインストールするためにはupdatesリポジトリが必要である。
cloud-utils-growpartパッケージはextrasリポジトリにあるので、extrasリポジトリも必要となる。

先に書いたように、Cobblerの力を借りず手でkickstartファイルを作るなら、これらは適当なインターネット上のリポジトリURLを指定する。

インストール元: http://ftp.iij.ad.jp/pub/linux/centos/7/os/x86_64
updatesリポジトリ: http://ftp.iij.ad.jp/pub/linux/centos/7/updates/x86_64
extrasリポジトリ: http://ftp.iij.ad.jp/pub/linux/centos/7/extras/x86_64

など。

rootpw --lock
user --groups=wheel --name=vagrant --password=vagrant

rootpw –lockは、rootユーザはロックし、ログインできないようにする。
user行でvagrantユーザをパスワード:vagrantで作成している。

%packages
@base
@core
kernel-devel
gcc
cloud-utils-growpart
%end

%packages-%end間はインストールパッケージを指定する箇所である。
ここは基本的にはお好みで追加すれば良い。この内容だとインフラストラクチャサーバー相当に追加してkernel-devel,gcc,cloud-utils-growpartがインストールされる。
kernel-develとgccはVirtualBox Guest Additionsのインストールに必要になるので必須である。

%post
cat > /etc/sudoers.d/vagrant <<EOD
vagrant ALL=(ALL) NOPASSWD: ALL
Defaults:vagrant !requiretty
EOD

chmod 440 /etc/sudoers.d/vagrant

mkdir /home/vagrant/.ssh
curl "https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/vagrant.pub" -o /home/vagrant/.ssh/authorized_keys
chown -R vagrant /home/vagrant/.ssh
chmod -R go-rwsx /home/vagrant/.ssh
%end

%post-%end間はOSインストール後、再起動までに実行する処理を記述する箇所である。
sudoersでvagrantユーザをパスワード、ttyなしでsudoできるようにするのと、Vagrantで使用される仮の公開鍵を配置している。

この部分やプロビジョニングの部分は https://github.com/chef/bentohttps://github.com/shiguredo/packer-templates を参考にしている。

プロビジョニング

プロビジョニングはOSのインストールが終わり再起動されOSが立ち上がってきた後に実行する処理を定義する。

プロビジョニングの箇所にはこのタイミングでしか実行できない処理を書くことにしている。つまり、次を実行している。

  • GuestAdditionsのインストール(GuestAdditionsのISOをPackerが仮想マシン内に上げてくれるのがこのタイミングだから)
  • Boxのファイルサイズ削減のための、ディスクの余白の0埋め(エクスポート直前にやるのが最も効果があるから)

virtualbox.shというシェルスクリプトファイルに処理を記述している。

virtualbox.sh
#!/bin/sh

cd /tmp
mount /home/vagrant/VBoxGuestAdditions.iso /mnt
sh /mnt/VBoxLinuxAdditions.run
umount /mnt
rm -f /home/vagrant/VBoxGuestAdditions.iso

dd if=/dev/zero of=/EMPTY bs=1M
rm -f /EMPTY
sync

Boxの生成とVagrantへの取り込み

あとはcentos7.json, CentOS-7.0-1406-x86_64-NetInstall.iso, virtualbox.shが配置されたフォルダにcdしてpacker build centos7.jsonを実行すればBoxが生成される。こちらの環境では10分程度掛かる。

生成されたBoxはvagrant box add --force c7-packer c7-packer.boxでVagrantに取り込む。

これらを定期的に、PCを触っていない時間帯にでも実行すれば、いつでもほぼ最新のパッケージがインストールされたBoxを使用可能となる。

Windowsで定期的に実行するにはタスクスケジューラに登録する。
コマンドプロンプトのウィンドウが開かないようバックグラウンドで実行させるように「ユーザーがログオンしているかどうかにかかわらず実行する」にチェックを入れたら「最上位の特権で実行する」も何故かチェックが必要だった。「パスワードを保存しない」にチェックを入れても良い。
またこの場合、packerを実行するコマンドプロンプトウィンドウだけでなくVirtualBoxのコンソールウィンドウも開かなかった、というかVirtualBoxアプリ内に仮想マシンが登録されている表示はされていないのにこっそり実行されていた。

Vagrant

Packerではディスクのサイズを変更するための仕掛けを作った。
VagrantではVagrantfileを使ってその仕掛けを動かそう。

Vagrantfile

適当なフォルダでvagrant init c7-packerを実行するとVagrantfileが生成されるが、そのVagrantfileを以下のように書き換える。(コメントは基本取り除いてある)

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

name_prefix = "c7-"
servers = {:node1 => {:private_ip => "192.168.56.11", :disk_size => 32}, :node2 => {}}
disk = "c7-packer-disk1"

require 'vagrant/util/platform'

vboxmanage_path = "VBoxManage"
if Vagrant::Util::Platform.windows? || Vagrant::Util::Platform.cygwin?
  if ENV.key?("VBOX_INSTALL_PATH") || ENV.key?("VBOX_MSI_INSTALL_PATH")
    path = ENV["VBOX_INSTALL_PATH"] || ENV["VBOX_MSI_INSTALL_PATH"]
    path.split(";").each do |single|
      single += "\" if !single.end_with?("\")
      vboxmanage = "#{single}VBoxManage.exe"
      if File.file?(vboxmanage)
        vboxmanage_path = Vagrant::Util::Platform.cygwin_windows_path(vboxmanage)
        break
      end
    end
  end
end

vbprop = `"#{vboxmanage_path}" list systemproperties`
raise unless $?.exitstatus == 0

default_dir = nil
default_dir_reg = /Default machine folder: +(.+)$/
vbprop.each_line do |line|
  if line.chomp =~ default_dir_reg
    default_dir = $1
    break
  end
end
raise unless default_dir

Vagrant.configure(2) do |config|
  config.vm.box = "c7-packer"

  servers.each do |k,v|
    name = "#{name_prefix}#{k}"
    config.vm.define k do |server|
      server.vm.network :private_network, ip: v[:private_ip] if v[:private_ip]
      server.vm.provision :shell, :inline => "(growpart /dev/sda 1 && xfs_growfs /dev/sda1) | tee /dev/null"
      server.vm.provider "virtualbox" do |vb|
        vb.name = name
        vb.gui = true

        old_disk = "#{default_dir}\#{name}\#{disk}.vmdk"
        new_disk = "#{default_dir}\#{name}\#{disk}.vdi"
        if v[:disk_size] && !File.exist?(new_disk)
          vb.customize ["clonehd", old_disk, new_disk, "--format", "VDI"]
          vb.customize ["modifyhd", new_disk, "--resize", v[:disk_size] * 1024]
          vb.customize ["storageattach", :id, "--storagectl", "IDE Controller", "--port", "0", "--device", "0", "--medium", new_disk]
          vb.customize ["closemedium", "disk", old_disk, "--delete"]
        end
      end
    end
  end
end

ディスクサイズの変更に関わる部分を説明する。

name_prefix = "c7-"
servers = {:node1 => {:private_ip => "192.168.56.11", :disk_size => 32}, :node2 => {}}

PackerがBoxを生成する際にVirtulaBoxからエクスポートを行うが、エクスポートを行うとディスクイメージのフォーマットは必ずvmdkになる。
しかしVirtualBoxはvmdkフォーマットのディスクサイズ変更ができないため、vdiなどのディスクサイズ変更可能なフォーマットに変換する必要がある。

ディスクイメージの変換を行うにはディスクイメージファイルの絶対パスを知る必要がある。
その絶対パスは<VirtualBoxの「デフォルトの仮想マシンフォルダ」><仮想マシン名><ディスク名>.vmdkである。

仮想マシン名は何も指定しないとVagrantが自動で決定するが、その名称を(特に初回起動前に)得る方法はないと思われる。

なので後の箇所でvb.name = nameとして仮想マシンの名前(=フォルダ名)をこちらから決めてやる必要がある。

ここではvb.nameに与える名前の元となる任意の文字列を決定している。この例だと「c7-node1」「c7-node2」という2つの仮想マシンが作成される。
またここで:disk_size => 32と指定しているnode1の方だけがディスクサイズが32GBに変更される(ような処理を後に書いている)。

disk = "c7-packer-disk1"

ここにはディスクのファイル名を書く。
vmdkディスクのファイル名はpackerでBoxを作った時点で決まっている。Packerで指定したvm_nameの後に”-disk1″をつけた文字列である。それをそのままここに書く。

require 'vagrant/util/platform'

vboxmanage_path = "VBoxManage"
if Vagrant::Util::Platform.windows? || Vagrant::Util::Platform.cygwin?
  if ENV.key?("VBOX_INSTALL_PATH") || ENV.key?("VBOX_MSI_INSTALL_PATH")
    path = ENV["VBOX_INSTALL_PATH"] || ENV["VBOX_MSI_INSTALL_PATH"]
    path.split(";").each do |single|
      single += "\" if !single.end_with?("\")
      vboxmanage = "#{single}VBoxManage.exe"
      if File.file?(vboxmanage)
        vboxmanage_path = Vagrant::Util::Platform.cygwin_windows_path(vboxmanage)
        break
      end
    end
  end
end

vbprop = `"#{vboxmanage_path}" list systemproperties`
raise unless $?.exitstatus == 0

default_dir = nil
default_dir_reg = /^Default machine folder: +(.+)$/
vbprop.each_line do |line|
  if line.chomp =~ default_dir_reg
    default_dir = $1
    break
  end
end
raise unless default_dir

ディスクイメージファイルの絶対パスを知るためにはVirtualBoxの「デフォルトの仮想マシンフォルダ」も得る必要がある。
「デフォルトの仮想マシンフォルダ」はVBoxManage list systempropertiesを実行し、出力の中のDefault machine folder行に書かれているのでそこから取得できる。

前半はVBoxManageの絶対パスを取得している。Vagrantの plugins/providers/virtualbox/driver/base.rb の一部を多少修正して使っている。

      server.vm.provision :shell, :inline => "(growpart /dev/sda 1 && xfs_growfs /dev/sda1) | tee /dev/null"

VagrantでOS起動後に実行される、プロビジョニングの処理を記述する箇所である。
ディスクサイズが大きくなった後にOS上でgrowpart /dev/sda 1xfs_growfs /dev/sda1を実行すれば、サイズ増加分がOSからも使えるようになるので、プロビジョニングの箇所でこれらを実行させる。

サイズが拡張していない時にgrowpartを実行するとリターンコードが1になるが、リターンコードが0以外だとそこで処理が止まるので、括弧して| tee /dev/nullを付けリターンコードが0になるようにしている(それ以外の影響はないはずだ)。

なお、Packer実行時のkickstartファイルに指定した通りファイルシステムがxfsなのでxfs_growfsを使っているが、ext系なら代わりにresize2fsを使用する(引数同じ)。

cloud-initを使用する場合はこの行は不要である。
cloud-initはマシン起動毎に実行されるが、Vagrantのプロビジョニングは初回起動時、vagrant up --provisionで起動した時、vagrant provisionを実行した時にだけ実行される、という違いがある。

        old_disk = "#{default_dir}\#{name}\#{disk}.vmdk"
        new_disk = "#{default_dir}\#{name}\#{disk}.vdi"
        if v[:disk_size] && !File.exist?(new_disk)
          vb.customize ["clonehd", old_disk, new_disk, "--format", "VDI"]
          vb.customize ["modifyhd", new_disk, "--resize", v[:disk_size] * 1024]
          vb.customize ["storageattach", :id, "--storagectl", "IDE Controller", "--port", "0", "--device", "0", "--medium", new_disk]
          vb.customize ["closemedium", "disk", old_disk, "--delete"]
        end

vb.customizeの後には仮想マシン作成後、起動前に実行するVBoxManageコマンドを記述する。
ここでは「VMDKファイルからVDIファイルを生成」(clonehd)→「生成したVDIファイルのストレージサイズの変更」(modifyhd)→「ストレージコントローラにぶら下げるディスクをVMDKファイルからVDIファイルに変更」(storageattach)→「VMDKファイルをVirtualBoxの登録外にし、削除」(closemedium)を行い、サイズが変更されたディスクを仮想マシンに使わせるようにしている。

ちなみに!File.exist?(new_disk)の箇所はFile.exist?(old_disk)にしてはいけない。初回起動時、ここの判定は仮想マシン作成前に行われるため、まだold_disk(VMDKファイル)も存在しないからである。

仮想マシンの起動

ここまでくればvagrant upで自動的にディスクサイズが変更され、OS上からも増加分が利用可能となった仮想マシンができることになる。



  1. https://git.kernel.org/cgit/utils/util-linux/util-linux.git/log/?h=stable/v2.24&ofs=100 にある「partx: fix –update ranges and out of order tables」 

TOP