xhyveで立てたVMをdocker-machineで管理する

開発環境をDockerを利用したものにシフトしていこうとしている昨今，MacだとDockerを動かすのにVirtualBoxかーってなってました．

[xhyve](https://github.com/mist64/xhyve)はYosemiteから入ったHypervisor.frameworkというやつを利用しているらしい．．．よくわからない所もあるんですが，
> xhyve, a lightweight OS X virtualization solution

を信じてやってみることにしました．

# 準備
## xhyve
```sh
# xhyveのインストール
$ brew install xhyve

$ xhyve -v
xhyve: 0.2.0

xhyve is a port of FreeBSD's bhyve hypervisor to OS X that
works entirely in userspace and has no other dependencies.

Homepage: https://github.com/mist64/xhyve
License: BSD
```

## ubuntu
Dockerを動かすためのホストとなるVM用です．
### 参考
- https://hnakamur.github.io/blog/2015/06/11/tried_xhyve/
- http://www.thegeekstuff.com/2009/07/how-to-view-modify-and-recreate-initrd-img/
- https://help.ubuntu.com/lts/installation-guide/i386/apb.html
- http://takuya-1st.hatenablog.jp/entry/2015/04/04/044017

## VM作り
ほぼ一番上のページの通りやればよかったのですが，何度もイメージを作り直すのにubuntuのインストールが面倒になってpreseedに手を出してしまいました．
preseedで心が折れそうになりましたが，やっておいて本当によかったといちおう出来た今はそう思います．．．

```sh
# 作業用ディレクトリ
mkdir ubuntu
cd ubuntu

# 元記事はubuntu14.04だったが，同じでも面白くないのでバージョン上げてみた
curl -O http://ftp.riken.jp/Linux/ubuntu-releases/15.10/ubuntu-15.10-server-amd64.iso

dd if=/dev/zero bs=2k count=1 of=tmp.iso
dd if=ubuntu-15.10-server-amd64.iso bs=2k skip=1 >> tmp.iso
hdiutil attach tmp.iso
cp /Volumes/Ubuntu-Server\ 15/install/vmlinuz .
cp /Volumes/Ubuntu-Server\ 15/install/initrd.gz .

# HDD用のイメージファイルを生成
dd if=/dev/zero of=hdd.img bs=1g count=8
```

```sh:install.sh
#! /bin/sh

KERNEL="./vmlinuz"
INITRD="./initrd.gz"
CMDLINE="earlyprintk=serial console=ttyS0 acpi=off"

MEM="-m 1G"
NET="-s 2:0,virtio-net"
IMG_CD="-s 3,ahci-cd,./ubuntu-15.10-server-amd64.iso"
IMG_HDD="-s 4,virtio-blk,./hdd.img"
PCI_DEV="-s 0:0,hostbridge -s 31,lpc"
LPC_DEV="-l com1,stdio"

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

```sh
sudo ./install.sh
```

### ちょっと脱線して`initrd.gz`作り
最初数回は手動でubuntuをセットアップしていたものの，さすがに毎回矢印キーとエンターを押しまくる生活に辛くなってきました．そこで，preseedをやってみることに．．．

いろいろ紆余曲折はあったのですができあがったpreseed.cfgはこちら

```cfg:preseed.cfg
ontents of the preconfiguration file (for wheezy)
### Localization
d-i debian-installer/language string en
d-i debian-installer/country string US
d-i debian-installer/locale string en_US.UTF-8
d-i localechooser/supported-locales en_US.UTF-8
d-i console-setup/ask_detect boolean true
d-i console-setup/layoutcode string us
d-i console-setup/charmap select UTF-8

# キーボードレイアウトの特性の設定（日本語キーボード）
d-i console-keymaps-at/keymap select us 
d-i keyboard-configuration/layoutcode string us
d-i keyboard-configuration/modelcode pc105

### ネットワーク周り
d-i netcfg/choose_interface select auto
d-i netcfg/disable_autoconfig boolean false
#d-i netcfg/dhcp_failed note
#d-i netcfg/dhcp_options select Configure network manually
# static
#d-i netcfg/get_nameservers string 192.168.64.1
#d-i netcfg/get_ipaddress string 192.168.64.20
#d-i netcfg/get_netmask string 255.255.255.0
#d-i netcfg/get_gateway string 192.168.64.1
#d-i netcfg/confirm_static boolean true

d-i netcfg/get_hostname string ubuntu
d-i netcfg/get_domain string 
d-i netcfg/wireless_wep string 

## non-free なハードウェアで質問させない。
d-i hw-detect/load_firmware boolean true

### ミラーサーバーの選択
# If you select ftp, the mirror/country string does not need to be set.
#d-i mirror/protocol string ftp
d-i mirror/country string manual
#d-i mirror/http/hostname string ftp.kddilabs.jp
#d-i mirror/http/directory string /pub/Linux/distributions/Debian/debian
d-i mirror/http/hostname string archive.ubuntu.com
d-i mirror/http/directory string /ubuntu
### プロキシは使わない
d-i mirror/http/proxy string

### Account setup
# Skip creation of a root account (normal user account will be able to
# use sudo).
d-i passwd/root-login boolean false

# Root password, either in clear text
#d-i passwd/root-password password r00tme
#d-i passwd/root-password-again password r00tme

# To create a normal user account.
d-i passwd/user-fullname string dev
d-i passwd/username string dev
d-i passwd/user-password password pw
d-i passwd/user-password-again password pw
d-i user-setup/allow-password-weak boolean true

# ユーザのホームディレクトリを暗号化しない
d-i user-setup/encrypt-home boolean false

### Clock and time zone setup
# HW の時計をUTCベースにするか➝させない
d-i clock-setup/utc boolean false
# タイムゾーン
d-i time/zone string Asia/Tokyo

# インスト中に時計合わせるか 
d-i clock-setup/ntp boolean true
# ntpサーバー
d-i clock-setup/ntp-server string ntp.nict.jp

### Partitioning
d-i partman-auto/init_automatically_partition select biggest_free
d-i partman-auto/method string regular
#
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-md/device_remove_md boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
#d-i partman-auto-lvm/guided_size string max
d-i partman-auto/choose_recipe select atomic
#
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true

### Package selection
tasksel tasksel/first multiselect ubuntu-server,ssh-server
d-i pkgsel/include string openssh-server
d-i pkgsel/update-policy select none
popularity-contest popularity-contest/participate boolean false

## grub
d-i grub-installer/grub2_instead_of_grub_legacy boolean true
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true

# IPを調べたいが、どこにも表示できない
d-i preseed/late_command string cd target; nc -l -p 1234; tar c boot | nc -l -p 1234

## 最後
d-i finish-install/reboot_in_progress note
```

もうなんか意味わかりませんね．．．
preseed.cfgは参考サイトの例をもとにいくつかうまく動かなかったとろを修正しています．

preseed.cfgをinitrdのルートディレクトリにつっこんであげれば，セットアップ時に自動でよみこんでくれます．

ちょっとだけ工夫してみたのがこの部分．

```cfg
d-i preseed/late_command string cd target; nc -l -p 1234; tar c boot | nc -l -p 1234
```

これは，vm上のubuntuのインストールの最後で，シェルを実行してごにょごにょしなければいけないところを何とかしようと試みたものです．

インストールの最後の部分で画面に何かを表示させる，というのがどうにもできなかったため，ポートスキャン用に1回，ファイル受け取り用にもう一回ncを実行するということをやってます．

ここはインストールの最後の最後の部分なので，カジュアルに試行錯誤できたのはpreseedのおかげです．

ちなみに，initrdの展開と生成は

```sh
# 作業用ディレクトリ
mkdir initrd && cd initrd

# 展開
# sudo付きでやらないと，/dev/nullとか作れなくてコケた
sudo cpio -id < ../initrd.gz

# 生成
find . | cpio -ov -H newc | gzip > ../initrd.gz
```

## 続・VM作り
```sh
# 上記で生成したinitrd.gzを使えばセットアップ中は休憩できる
sudo ./install.sh

# セットアップ終了間際，もうすぐ終了というあたりで，ncによる待ちが発生
nmap -p1234 192.168.64.0/24
#Starting Nmap 6.47 ( http://nmap.org ) at 2015-11-02 02:23 JST
#Strange error from connect (65):No route to host
#Nmap scan report for 192.168.64.1
#Host is up (0.00042s latency).
#PORT     STATE  SERVICE
#1234/tcp closed hotline

#Nmap scan report for 192.168.64.33
#Host is up (0.00049s latency).
#PORT     STATE SERVICE
#1234/tcp open  hotline

#Nmap done: 256 IP addresses (2 hosts up) #scanned in 2.47 seconds

# 上記の結果より，待ち人がいるのは192.168.64.33であると判明．(192.168.64.1は自分自身)

# ファイル受け取り
nc 192.168.64.33 1234 | tar x
#作業用ディレクトリにbootディレクトリが転送され，インストールは終了
```

これでVMを起動する準備が整いました．

## VM起動
```sh:run.sh
#! /bin/sh

KERNEL="./boot/vmlinuz-4.2.0-16-generic"
INITRD="./boot/initrd.img-4.2.0-16-generic"
CMDLINE="earlyprintk=serial console=ttyS0 acpi=off root=/dev/vda1 ro"

MEM="-m 1G"
#SMP="-c 2"
NET="-s 2:0,virtio-net"
IMG_HDD="-s 4,virtio-blk,./hdd.img"
PCI_DEV="-s 0:0,hostbridge -s 31,lpc"
LPC_DEV="-l com1,stdio"

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

```sh
sudo run.sh
```

うまくいけば，ubuntuのログイン画面が表示され，dev/pwでログインできます．

# docker-machineの管理下に置く
ここまででubuntuが使えるようになったので，あとはdockerを入れてしまえば，Macからでも使えるはず．

```sh
# docker-machineはSSHが通ってる必要がある
ssh-keygen -t ed25519
ssh-copy-id -i ~/.ssh/id_ed25519 dev@192.168.64.33
# -> まだ鍵認証ではないので，pwを入れる

ssh dev@192.168.64.33
# -> 鍵認証できてるはず
exit

# genericドライバで作成
docker-machine create --driver generic --generic-ip-address 192.168.64.33 --generic-ssh-key ~/.ssh/id_ed25519 --generic-ssh-port 22 ubuntu
# Importing SSH key...
```

ここでハマる．なぜかリモート(192.168.64.33)のDocker-daemonと通信できないと言われる．

```sh
# genericドライバで作成
## デバッグログ出す
docker-machine -D create --driver generic --generic-ip-address 192.168.64.33 --generic-ssh-key ~/.ssh/id_ed25519 --generic-ssh-port 22 ubuntu

# ...
#Daemon not responding yet: dial tcp 192.168.64.33:2376: connection refused
```

nmapで見てみると，`tcp:2376`は`CLOSED`．ハテ?
小一時間悩んだ結果，そもそもdocker-daemonがtcpでの接続を受けつけていないことが判明

```:/lib/systemd/system/docker.service
# こうなってたのを
ExecStart=/usr/bin/docker daemon -H fd://

# こうした
ExecStart=/usr/bin/docker daemon -H fd:// -H tcp://0.0.0.0:2376
```

これで，tcp経由での接続も受けつけてくれるはず!!

というわけで

```sh
docker-machine create --driver generic --generic-ip-address 192.168.64.33 --generic-ssh-key ~/.ssh/id_ed25519 --generic-ssh-port 22 ubuntu
#Importing SSH key...
#To see how to connect Docker to this machine, run: docker-machine env ubuntu
```

できた!!

```sh
# 環境変数をセットしてからの
docker-machine env ubuntu
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.64.33:2376"
export DOCKER_CERT_PATH="/Users/uraura/.docker/machine/machines/ubuntu"
export DOCKER_MACHINE_NAME="ubuntu"

# OSX上でdocker ps
docker ps
#An error occurred trying to connect: Get https://192.168.64.33:2376/v1.20/containers/json: tls: oversized record received with length 20527
# -> Σ(´Д｀ )

# もう疲れたのでTLS関連を無効に．どうせVMとの通信だし．．．
unset DOCKER_CERT_PATH
unset DOCKER_TLS_VERIFY

# 再度 docker ps
docker ps
# CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

docker run hello-world
#Hello from Docker.
#This message shows that your installation appears to be working correctly.
#
#To generate this message, Docker took the following steps:
# 1. The Docker client contacted the Docker daemon.
# 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
# 3. The Docker daemon created a new container from that image which runs the
#    executable that produces the output you are currently reading.
# 4. The Docker daemon streamed that output to the Docker client, which sent it
#    to your terminal.
#
#To try something more ambitious, you can run an Ubuntu container with:
# $ docker run -it ubuntu bash
#
#Share images, automate workflows, and more with a free Docker Hub account:
# https://hub.docker.com
#
#For more examples and ideas, visit:
# https://docs.docker.com/userguide/
```

# つかれた
あんまり整理できてないけどとりあえずまとめておく．．．