OS XのネイティブHypervisorを使うxhyveと、ネイティブDockerを立ち上げるdocker-machine-driver-xhyveを作った話

OS XのHypervisorを使ってネイティブでローカル環境にDockerを構築する、docker-machine-driver-xhyveを作ったので、そのお話。
長いです。覚悟するか目次を見て読み飛ばしてください。まとめる技術がないようです。

またここでいうネイティブは、`Virtualbox`などを使用しないでOS Xが自前でVMを立ち上げる、という意味で、内部には相変わらずboot2dockerがいます。少し釣りですみません。
はぁ、いい加減本当のネイティブでDocker動かしたいですね。


# xhyve
Appleがついに公式にサポートした仮想化技術`Hypervisor.framework`を使って、FreeBSDの`bhyve`をOS Xにポートした`xhyve`というものが以前GitHubにされました。
[mist64/xhyve <i class="fa fa-external-link"></i>](https://github.com/mist64/xhyve)
割と日本のメディアでも取り上げられ、皆さんの周りでも話題になったんではないでしょうか？

Ubuntuのvmlinuzとinitrd.gzを用意して起動時に指定してあげればちゃんとUbuntu起動しますし、ArchlinuxだってGentooだって起動します。
初めはkexecしかインターフェースがサポートされていなかったんですが、後にFreeBSDなどの`userboot.so`も立ち上げるように対応が入ったので、FreeBSDやそのポートであるNextBSDは起動できるはずです。

## xhyveの試し方
xhyveの割と使い方は広まってきていますが、後のdocker-machine-driver-xhyveが何をしているかを知っていただくためにも、簡単に説明していきます。

### ビルドについて
xhyveをソースからビルドします。例によってXcodeなりCommand Line Toolsは必須です。
Homebrewから適当に持ってきてもいいのですが、このようなかなり先端（異端）の技術・プロジェクトの場合ものすごい勢いでコード変わってますので、Homebrew Formulaのものが古くなっている場合があります。
後述のdockerやdocker-machineもまた同じで、面倒臭がらず自分でビルドすることで新しい機能や修正されたネガティブ（意味のない）なバグを事前に回避できます。

…まぁ、それで新しいバグ踏むときもあります。ただそれはソースコードを読むきっかけになったり、修正してPull Requestできるようなポジティブなyak shavingです。
OSSの成果物を使わせていただいている以上、何か還元できるように、ぼくは常用するものは常にHEADビルドすることを心がけています。

### xhyveの入手・ビルド
ということで、まずはソースコードを手元の環境に持ってきましょう。
[mist64/xhyve <i class="fa fa-external-link"></i>](https://github.com/mist64/xhyve)

```bash
git clone git@github.com:mist64/xhyve.git
cd xhyve
```

`xhyve`に限らず、大体C言語で書かれたものはsrcにはコア部分のソースがあり、includeにはそれらのヘッダファイルがあるみたいです。
tmuxのように同じディレクトリにどちらも混在してる場合もありますが、それは作者の意向ですのでMakefileがちゃんと書かれていれば特に問題にはならないようです。

さて、おもむろに

```
make
```

と打ちます。
clangを使ってビルドが始まり、経過が表示されて最後にstripに少し時間がかかった後に、build以下にコンパイルされたコード群が展開されます。xhyveはOS X Frameworks以外に依存がないので、これでビルドは終わりです。
注意する点が、Xcode付属のclangではなく純粋なLLVMのソースからビルドしたclangだと途中でfailする場合があります。

### xhyveの起動
xhyveのレポジトリにテスト用として[Tiny Cored Linux](http://www.tinycorelinux.net/)に少しパッチを当てたvmlinuzとinitrd.gzが同梱されていますので、それを使ってテストできます。
起動引数がそれなりに長いので、https://github.com/mist64/xhyve/blob/master/xhyverun.sh にスクリプトが用意されてます。
コメントを入れると以下のような感じです。Linux用に削ってます。

```bash
#!/bin/sh

KERNEL="test/vmlinuz" # vmlinuxの場所
INITRD="test/initrd.gz" # initrd.gzの場所
CMDLINE="earlyprintk=serial console=ttyS0" # ブート時のコマンド

# 割り当てるメモリーのサイズ。KMG表記には対応していますが、現状3G以上割り当てられないバグ?があります
MEM="-m 1G"

# 割り当てるCPUの数。指定しないと1つだけ割り当てされます
#SMP="-c 2"

# vmnet.frameworkとvirtio-netを使ったインターネット共有
# コメントアウトするとホストからvmnetを使ってゲストに共有されますが、xhyveの起動にsudoが必要になります
#NET="-s 2:0,virtio-net"

# 必要ならcdとhddを設定できます
# TinyCoredLinuxの場合は必要ないですが、HDDないと保存する場所がないのでLiveBootのような感じになります
#IMG_CD="-s 3,ahci-cd,/somepath/somefile.iso"
#IMG_HDD="-s 4,virtio-blk,/somepath/somefile.img"

# PCI、LPC設定
PCI_DEV="-s 0:0,hostbridge -s 31,lpc"
LPC_DEV="-l com1,stdio"

# ACPI使用の有無
ACPI="-A"

# virtio-netを使いつつコメンドアウトすれば、次の起動でも同じIPが使用されます
# OS Xがvmnetの機能からUUIDとIPの関係を保存しているためです。
#UUID="-U deadbeef-dead-dead-dead-deaddeafbeef"

build/xhyve $ACPI $MEM $SMP $PCI_DEV $LPC_DEV $NET $IMG_CD $IMG_HDD $UUID -f kexec,$KERNEL,$INITRD,"$CMDLINE"
```

有効になっているフラグは4つとkexec起動用しか定義されていませんが、これで最低限動作します。

```bash
./xhyverun.sh
```

とすると、ばーっとログが起動ログが出てきて、一瞬スクリーンがフラッシュした後、ログインされます。
場合によってはカーソルでないので、画面の描画が止まったようでしたらスペースでも押してみてください。
後はbusyboxの範疇でほぼなんでも動かせます。起動時にvmnetを有効にしてあげていると、

```bash
ping google.com
```

も通るはずです。
一点、VMの終了時の注意ですが、`exit`などしてターミナル抜けようとすると抜け先がないので操作不能になります。必ず`halt`なり`poweroff`で電源落として抜けてください。

ということで、ついにOS XでHypervisorが実現可能になりました。

# docker-machine-driver-xhyve
当然boot2dockerのisoを使って起動すれば、Dockerを使えるようになります。やばいですね。遂にです。
xhyve界隈で初めて?boot2dockerを起動可能にした
[ailispaw/boot2docker-xhyve](https://github.com/ailispaw/boot2docker-xhyve)
などに実際に実装があります。
なおこのお方、日本の方?でtwitterで何度かお話しさせていだたきました。その節はありがとうございました。

さて、Dockerが公式のプロジェクトとして開発している3種の神器のひとつ、Docker環境を簡単に構築するDocker Machineというものがあります。後の管理も簡単で、Docker Wayに乗れるツールです。（なお、Docker Wayなので多少のバグも点在します…）

そのツールが最近外部プラグイン機構を採用し、コアに取り込まれなくても各サービスに対応できるようになりました。
じゃあそれでxhyveを起動できたらかっこよさそう。ということで作りました。

[zchee/docker-machine-driver-xhyve](https://github.com/zchee/docker-machine-driver-xhyve)

実はその機構が出るちょっと前にPR出してたんですが、機構の予定があるのでcloseされました。
その後実装が落ち着くまで様子を見ていたのですが、遂にDocker社のnathanleclaireが実現してくれました。

プラグイン機構のサンプルとしてぼくが出してたPRをもとにしたコードを公開してくれていましたが、その状態ではxhyveの制約により動かなかったのでcgoとか使って結構がんばって動くようにしてPRしたら、maste branch譲るから君の方で開発してくれ！と言われ現在に至ります。

## docker-machine-driver-xhyveのインストール
前置き長かったですが、インストールしましょう。とりあえず今はGoの環境が必要です。
最近`libguestfs`の依存をなくしてboot2dockerの方でext4フォーマットできるようにPRがきてマージしましたので、その頃インストールしていただいた方は再度インストールしてください。
READMEにもありますが、以下です。

```bash
# Install docker-machine
go get github.com/docker/machine
cd $GOPATH/src/github.com/docker/machine
# Build docker-machine and some docker-machine official(embedded) driver binary
make build
# Install all binary into /usr/local/bin/
make install

# Install xhyve-bindings
go get -d github.com/zchee/xhyve-bindings
cd $GOPATH/src/github.com/zchee/xhyve-bindings
make
make install

# Install docker-machine-driver-xhyve
go get -d github.com/zchee/docker-machine-driver-xhyve
cd $GOPATH/src/github.com/zchee/docker-machine-driver-xhyve
make
make install
```

## docker-machine-driver-xhyveを使ったdockerの起動

順調に行けば、これでxhyveを使ってboot2dockerを立ち上げることが可能になります。
有効なフラグは以下。

| Flag name | Type | Description |
|-----------|------|-------------|
| xhyve-boot2docker-url | string | boot2docker isoの場所の指定 |
| xhyve-cpu-count | int | 割り当てCPUの数 |
| xhyve-memory-size | int | 割り当てMemoryの数 |
| xhyve-disk-size | int | 作成するディスクサイズ |
| xhyve-boot-cmd | string | xhyveの起動時のコマンド |
| xhyve-experimental-nfs-share | bool | NFSフォルダシェアの有無 |

だいだいのフラグがよしなになるようになってますので、早速起動してみましょう。

```bash
docker-machine --debug create --driver xhyve --xhyve-disk-size 2000 --xhyve-experimental-nfs-share xhyve
```
今回はさっと試すためにディスクサイズは2G、NFSフォルダシェアをONでやってみます。
なお、なにが起きてるか見ると楽しいので、--debugフラグつけてます。

たぶんこんな感じで起動するはず。

[![launch.png](https://qiita-image-store.s3.amazonaws.com/0/36099/3a7f9a66-bd52-4762-7bd6-72db2ee5e9eb.png)](https://asciinema.org/a/29930)

動画は最後まで行っちゃってますが、無事起動したら環境変数をevalしましょう。

```bash
eval "$(docker-machine env xhyve)"
```

これにて環境が整ったので、試しにdockerコマンドを打ってみましょう。

```bash
docker version
docker info
docker pull busybox
docker run busybox /bin/echo 'Hello world'
```
無事ハローワールドな感じでしょうか？
割と簡単に行けたかと思います。それがプラグイン機構ないしDocker Machineの狙いです。

## docker-machine-driver-xhyveの現状
とりあえずはxhyveを使ってDockerを扱えるようになりました。がんばった甲斐があった。
virtualboxのvboxfsよりもI/Oパフォーマンスは出てるとの報告もありますので（これはぼくの成果ではない）、割とローカルの簡単なテストにはお使いいただけるかと思います！

ただ、幾つか問題はまだ残っています。

### ディスクサイズが超でかい
現状xhyveは、qemuやvboxfsなどで可能なダイナミックアロケートをサポートしていません。最大ディスクサイズまではいま使っている分で節約してくれるあれです。
xhyveは最初に作ったディスクサイズのままずっと書き込み続けます。ので、Mac側のHDDをずいぶん占有してくれます。
ぼくは50G捧げてます。

### NFSフォルダシェア
まず、現在のフォルダシェアはNFSです。分かってます、いにしえのプロトコルです。
xhyveは現状フォルダシェアをサポートしておらず、OS XのHypervisor.frameworkもまたそんな機能はありません。
本家xhyveではvirtio-9pを使うのが一番良いかもね、との話になってますが、結構ハードワークだねということで実装されてません。

ぼくもあまりNFSに乗り気でなく面倒だなーと当初サポートしていなかったのですが、突如現れたjohanneswuerbach氏により実装されました。
OSS最高。

### xhyve? goxhyve?
現状、xhyveのソースそのものをgo build時にembedして内部からxhyveを使うようになっています。
そのバイナリ名はとりあえずgoxhyveで動かしてるので、プロセスでxhyveは動いてません。

これはDocker社のtiborvassが実現してくれました。
最初のPRの時に、これどうやってxhyve呼ぼうかなーと相談したらさくっとやってくれました。OSS最高。
それをForkして開発してます。
ただdocker-machineのプロセス終わったら終了されてくれると困るので単純なgoroutineは使えません。
ので、現状バイナリを分けてos.execをgroutineで起動してます。
そしてgoxhyveは`-d`フラグつけると自身をgoroutineでexecします。ややこしさ。
これは後述。

### VMの状態取得
VirtualboxやVMWareはちゃんとcliツールありますので、それを使ってVMの状態取得や操作はできます。Docker Machineも直接バイナリを呼んで操作してます。
ただ、xhyveはそんな調子の良いものはありません。
またOS X Hypervisorの構造上、そのプロセスからしかVMの状態取得はできないとのことがheaderソースにあります。
簡単に言うと自分の状態しか取得できないです。そんなの…

ここが最大の難関です。
現状場当たり的にSSHで`exit 0`をsendして状態確認しています。最悪です…
ただこれは解決策を考えていて、これも後述します。

## docker-machine-driver-xhyveの今後
### xhyve
現状xhyveは開発ストップしてます。と言っていいと思います。
mist64氏は別のを進めているようです。
これは結構痛くて、xhyve側でなにかやるならフォークして独自開発か、go build embed前にpatch当てて…という感じです。
つらい。

カーネルに近いことをxhyveはやってるので、go側でできることには限界があるのは見えてます。どうしようかなと悩んでます。
そしてOS X独自の技術でパフォーマンスを改善できそうなところは多くありますし、ごく一部GCDを使うように変更したPRも存在します。

### 状態取得
状態取得も結構つらいです。ただこれはembedしてるので、go側で取得するのは少し希望があります。
そこでいろいろ調べまして行き着いたのが、OS Xのカーネルで多く使われているGrand Central Dispatch、またOS Xのsystemd,init役であるlaunchdの内部で使われるXPC Serviceです。

#### Grand Central Dispatch
GCD、またはdispatchと呼ばれるものです。
iOSやってる方は結構ご存知かと思いますが、簡単に非同期処理をスレッドセーフで書けるApple謹製のすごい技術。
これには`dispatch_async`などで処理することと別に、I/Oを操作する`dispatch_io`やデータをやり取りする`dispatch_data`、各種の状態変化を監視する`dispatch_source`というものがあります。
FSEvents、notifydなどの裏にはGCDがいます。

その状態変化のところに、`dispatch_source_proc`があるんです。あわよくばこれでいけないかと思ってます。
また、OS Xのシステムコールにない`preadv`と`pwritev`も、dispatch周りで何とかなるかもしれません。

Swiftと同じくしてオープンソースになり、Apple公式の元でLinuxをサポートすることを目標にしています。
そしてカーネルを支えてる技術ですので、希望はありそうです。

#### XPC Service
OS Xのカーネルレベルで情報を送受信できる、RPCだと理解しています。
デベロッパーライブラリにも、

> The XPC Services API, part of libSystem, provides a lightweight mechanism for basic interprocess communication integrated with Grand Central Dispatch (GCD) and launchd. 

とあります。この裏にもGCDがいます。
難しいことはせず、ぼくは状態を知りたいだけなので、これでもいけそうです。

これも実は、FreeBSDからOS Xを内包しようとしているNextBSDプロジェクトにて、既にポートされています。
現状BSDだけなのかわかりませんが、mpack(msgpack)と絡めてどの言語でもOS X VMを操作できるようにという構想があるように見えます。
ちなみちNextBSDは既にlaunchd,notifyd,asld,GCDをポートし終わっています。NextBSDを起動すると同時にapple launchdも起動してます。
これ結構やばいと思ってます。

#### これらを
使えば、なんとなくいけそうな気はしますし、状態取得以外にもstartやstopもカーネルレベルでできそうです。
ただ、これをgo側からやるのかxhyve側なのかは分かってません。GoもCもまだ満足に書けるレベルではありません…
そして、構想が大げさなのは分かってますw
ただ、どうせHypervisor触るなら他のとこもFramework使いたい。
死ぬ気でやればなんでもできそうなので、勉強のためにもやってみるつもりです。

# さいごに
docker-machine-driver-xhyveは、xhyveを作り上げたmist64氏、Docker社の数人、vmnet周りでお世話になったailispawさんにかなりの部分を助けてもらいました。
ぼくが書いた部分なんてごく一部です。
ありがとうございました。
今は汚いソースだし、いろいろ突っ込みどころあるんですが、がんばって勉強して改善します。

久しぶりにこれすごいと思ったDocker Container技術。
初めてGo言語をやるきっかけになったDocker Machine、そしてC言語もやってみようと思わせてくれたxhyve。
そしてOS X。
これからも追っていきたいです。

また、身の上を書きますと、現在失業中です…
仕事くださいw

次はGoその3でお会いしましょう！