Ubuntu serverをvm-bhyveにUEFIインストール

# 動機
FreeBSDで作業していて、Linuxバイナリを実行したいけどLinuxulatorではうまく動かないことがある。ちょっとソースをいじれば解決することも多いのだけど、ビルドが大変だったりして面倒くさい。ならbhyveで解決できるんじゃないかな。と思ったら、まずLinuxをインストールするところではまったのでメモ。というか、Qiitaを使ってみるテスト。

# 前提
- FreeBSD 11.1-RELEASE-p9
- vm-bhyve v1.1-p8 (build 101073)
- Ubuntu 16.04.4 Server[^1]

[^1]: Linuxディストロの中でUbuntuだけはちょっと触ったことがあるという理由で選択。本当は18.04でやりたかったのだけど、カーネルが.isoを認識できずにインストーラが途中で止まってしまう。(2018-05-22追記：2018-04-28にuefi-edk2-bhyveが更新されLinux kernel 4.15が利用できるようになったらしいので多分解決しているはず）（2018-0619追記：解決していることを確認）

# 手順
## vm-bhyveの設定
まずは[vm-bhyve](https://github.com/churchers/vm-bhyve)環境の立ち上げ。基本的に[Quickstart](https://github.com/churchers/vm-bhyve/wiki/Quickstart)を参照すればなんとかなる。今回データストアはZFSを使っているがUFSでも大差はないはず。

bhyveでLinuxを起動する方法には
1. grub2-bhyve
2. uefi-edk2-bhyve
の2種類があるのだけど、今回は勉強もかねてUEFIでやることに[^2]。

[^2]: これがはまりポイントだったわけだけど。

```console
> sudo pkg install vm-bhyve uefi-edk2-bhyve
> sudo zfs create tank/vm
> sudo sysrc vm_enable="YES"
> sudo sysrc vm_dir="zfs:tank/vm"
> sudo vm init
> sudo vm switch create public
> sudo vm switch add public em0
```

これでVM作成の準備は整った。

## VMの設計
設計というほどのことではないのだが、まずVMのテンプレートを書く必要がある[^3]。/usr/local/share/examples/vm-bhyve/に色々例が載っているのでそれを参考にすればよい。

[^3]: テンプレートを指定せずに`vm create`して、その後`vm configure`する方がエレガントだが、そのためには少なくともdefault.confが必要。

Ubuntu用のubuntu.confとの違いで重要なのは、

* UEFIを使うので`loader="grub"`の代わりに`uefi="yes"`
* クロック設定を`utctime="yes"`

今回使ったファイルは以下の通り。これをデータストアの.templates/以下に置く。

```sh:/tank/vm/.templates/ubuntu-uefi.conf
uefi="yes"
cpu=4
memory=8G
utctime="yes"
network0_type="virtio-net"
network0_switch="public"
disk0_type="virtio-blk"
disk0_name="disk0"
disk0_dev="sparse-zvol"
```

## Ubuntuのインストール
まずはインストーラのISOイメージを取得。VMの作成時にディスクサイズを指定する必要があるが、現状2Gあればインストール自体はできるようだ。

```console
> sudo vm iso ftp://ftp.riken.go.jp/Linux/ubuntu-releases/16.04.4/ubuntu-16.04.4-server-amd64.iso
> sudo vm create -t ubuntu-uefi -s 4G ubuntu
```

一般的にはvm-bhyveでUEFIを使うのは、VNC経由でGUIを利用したい場合らしい。しかし今回はあくまでCUIベースで作業をするため、インストーラにシリアルコンソールの使用を指示しなければいけない[^4]。

[^4]: 未確認だが起動にgrubを使う場合は自動的にCUIベースでいけるのかな

```console
> sudo vm install -f ubuntu ubuntu-16.04.4-server-amd64.iso
```

これを実行すると、まずISOイメージに入っているgrubが立ち上がるので、タイミングを逃さずeを入力してカーネルのコマンドラインを編集する。vmlinuzの行にある`quiet`を消して`text console=ttyS0,115200`に置き換えてから、Ctrl+Xで起動[^5]。インストール自体は適当にやればいいと思うが、rootでログインできないから一般ユーザーを必ず作ることと、後々つかうためのOpenSSH serverをインストールしておくこと。

[^5]: この件は[FreeBSD bhyve+vm-bhyveでCentOS7をuefiで入れてみる](https://blog.bixr.com/2017/08/1203/)および[@m_bird](https://twitter.com/m_bird/status/867015720596496388)による

## UEFI起動プロセスの補正
インストールは完了したがこのままでは一度VMを停止させたあと再起動させることができない。

これは現行バージョンのbhyveではUEFIのNVRAM (Non-Volatile RAM)が揮発性のため[^6]、VMを停止させるとEFI System PartitionにあるUEFI用grubを読み込めなくなってしまうため。UEFI用grubをデフォルトの位置にコピーしてやれば解決する。つまりインストール直後に再起動した状態で（VMを停止させずに）一般ユーザーでログインして、以下の作業をする。

[^6]: すでに不揮発性にするパッチが存在しておりFreeBSD 12に取り込まれる模様

```console
$ sudo mkdir /boot/efi/EFI/BOOT
$ sudo cp /boot/efi/EFI/ubuntu/grubx64.efi /boot/efi/EFI/BOOT/BOOTX64.EFI
```

もしVMを一旦停止させてしまった場合には、コンソールを有効にして起動し、

```console
> sudo vm -f start ubuntu
```

Boot Failed.のまましばらくまっているとUEFI shellが起動するので、

```console
> FS0:\EFI\ubuntu\grubx64.efi
```

と入力してUEFI用grubを起動し、かつeを押してvmlinuzの行に`text console=ttyS0,115200`を書き足してから起動することができる。あとは一般ユーザーでログインして、上記の作業を行えばよい。

# 補足
参考としてUEFIの起動プロセスの説明。UEFIから起動する場合はGPTディスクである必要があり、そのEFI System Partition (ESP)中にあるブートローダに制御が移る。ESPは昔ながらのFATファイルシステムであり、デフォルトでは\EFI\BOOT\BOOTX64.EFIがブートローダとして読み込まれる[^7]。LinuxディストロはWindowsやFreeBSDとは違ってこれを上書きせず別の場所にブートローダを置き、UEFIのNVRAMに読み込むべきブートローダの位置を書き込んでいる。しかしbhyveではNVRAMの内容が保存されないため、デフォルト位置のブートローダを読み込もうとする。Ubuntuの場合、ESPは/boot/efiにマウントされているので、grubx64.efiをデフォルト位置にコピーしてやれば解決するというわけ。

[^7]: 64bit仕様の場合。32bit仕様だと\EFI\BOOT\BOOTX86.EFIとなる。