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

  • 151
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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
割と日本のメディアでも取り上げられ、皆さんの周りでも話題になったんではないでしょうか?

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

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に少しパッチを当てたvmlinuzとinitrd.gzが同梱されていますので、それを使ってテストできます。
起動引数がそれなりに長いので、https://github.com/mist64/xhyve/blob/master/xhyverun.sh にスクリプトが用意されてます。
コメントを入れると以下のような感じです。Linux用に削ってます。

#!/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起動用しか定義されていませんが、これで最低限動作します。

./xhyverun.sh

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

ping google.com

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

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

docker-machine-driver-xhyve

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

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

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

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にもありますが、以下です。

# 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フォルダシェアの有無

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

docker-machine --debug create --driver xhyve --xhyve-disk-size 2000 --xhyve-experimental-nfs-share xhyve

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

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

launch.png

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

eval "$(docker-machine env xhyve)"

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

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のシステムコールにないpreadvpwritevも、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でお会いしましょう!