How to use Docker with FreeBSD and bhyve (linux)

# How to use Docker with FreeBSD and bhyve (linux)

今回はFreeBSDのHostワークステーション上にBhyveを使用してLinuxゲストを作成し、そのゲストにDockerでアクセスする方法を簡単に解説します。

## このドキュメントの前提

- FreeBSD Host
- Linux Bhyve Guest OS (今回はOpenSUSE MicroOS)
- Linuxmutorやらでできるけどnative linuxのDockerがほしいor環境を隔離したい

## 簡単に状況説明+ワード解説

FreeBSD 14.xRのWorkstaionを自宅で使用しており、さらっと環境を分離した開発環境を作りたいみたいなことがあると思います。（Goで作ってるアプリケーションをとか、Apacheはすでに動いていて別Portで動かしたいけど環境設定混ざるの面倒みたいなパターンありますよね？）

本来ならこういうのはjailでやったり、Linuxでしか動かないならjailedしてLinuxmutorで動かしてしまうというのがFreeBSD的には正攻法な解決法ですが、今回はこれをついでに**Bhyveスーパーバイザー**を使用して**OpenSUSE MicroOS**を使用してやっていこうかなという趣旨でやりました。

BhyveはFreeBSDをそれなりに触ったことがある方には説明不要かもしれませんが他のOSのユーザーに説明するとWindowsにおけるHyper-V、LinuxにおけるKVMの立ち位置にあたるものです。

FreeBSDは以前から**jail**(FreeBSDユーザーランドを隔離する)、**Linuxmutor**(FreeBSDカーネル上でLinuxのシステムコールをエミュレートする＋関連pkgでLinux環境を再現)などの環境がありましたが、Bhyveは、仮想化を提供し、非\*nixのWindowsなども動作させることができます。

**OpenSUSE MicroOS**は最近流行りを見せているイミュータブルOSであり、roファイルシステムと、スナップショットとトランザクションによるrootパーティション更新、ローリングリリース方式＋自動更新のメンテナンスフリー、最小限のツールながらも、Podman（Redhat製Docker互換コンテナ管理ツール）などを備えており、コンテナをホストする環境用OSやエッジコンピューティングにフォーカスしたOSです。これに似たOSとしてFedora CoreOSなどがあります。今回はちょうどDocker（実態はPodman）基盤にして、あとは何もしないOSとして動かしたかったので、これを採用します。

## セットアップ

### Bhyve構築

Bhyveの構築については、すでにそれなりの数の参考文献や記事がありますので、それを参照しながら進めていけば問題なくセットアップできると思います。

以下にいくつか参考文献を示しますので、それをもとに行ってください。個人的にはｖｍ-bhyveを使用するのが一番割にあっていると思います。

<https://qiita.com/bsd-hacker/items/1505f8888147f93bb763>  
<https://qiita.com/masatonasou/items/2fc8bccbf2aea7baebdc>

<https://wiki.freebsd.org/bhyve>

Bhyveで構築していてしばらくハマったのはbridgeを作ってネットワークを作成してもDHCPが認識してくれないという話です。これは色々解決法はあると思いますが、私はstatic IPにすることで解決をおこないました。

static IPにすることで、今後のアクセスやらなんやらもやりやすくなります。衝突する可能性はありますが、そのときはこちらでずらす形にしようと思っています。

### VM管理方法

ここではあまりサンプルがなかった`virt-manager` を使用したVM管理やシリアルコンソールアクセス、注意点の話をしたいと思います。

通常では、vm-bhyveやその他のツールを使うのが主流ですが、Xwindowを使用しているし、わかりやすいのでvirt-managerを使用していきます。

virt-managerはRedhatによるPython製のVM管理用GUI,CLIクライアントです。バックエンドとしてlibvirtdを使用し、多くの仮想化ハイパーバイザ（QEMU、KVM、Bhyve、Xen,etc...）をサポートしています。SSHを経由してリモートのVMも管理でき、さながらVMWare Workstation ProでvSphereにアクセスして管理するような形を取ることができます。

virt-managerはｐｋｇでインストールできます。

```sh
$ pkg install virt-manager
```

依存で色々入ってきますが、その中でlibvirtdを有効化するように述べています。さらっと有効化してしまいたいのですが、その前にユーザーがこのままツールを起動すると、socketの権限が足りないと怒られてしまうので`doas`などで昇格させるのが面倒な場合はsocketの権限を変える必要があります。（本来`libvirt`グループに入ればよいはずですが、うまく動きません。)

```
$ vi /usr/local/etc/libvirt/libvirtd.conf
unix_sock_ro_perms="0777"
unix_sock_rw_perms="0770"
```

ではlibvirtdを有効化しましょう。

```
$ service libvirtd enable
$ service libvirtd start
```

その後`virt-manager` を起動し、ファイル->接続の追加を行うと、新しい接続先を追加できます。bhyveを追加しましょう。

これで新しいVMを簡単に作成できます。networkで適切なインタフェースがないと言われたらBhyve構築時に作ったbridgeを指定しましょう。

ここでVMをクリックすればVNCコンソールに入れますが、若干パフォーマンスが悪かったり、そもそもVNCじゃなくてシリアルコンソールがいいよという方も多いと思います。Linuxならここからそのままシリアルコンソールに入れたのですが、どうもBhyveだと入れないようなので入り方を説明します。

VMの構成を確認するとSerialがnｍdmとなっていると思います。これはFreeBSDが提供する仮想のモデムデバイスでここにシリアルポートが接続されていることを示しています。XMLからMasterとSlaveが見え、Slaveに`cu` や`tip` を使って接続します。

```
doas cu -s 9600 -l /dev/nmdme43a54a9-9bbc-449c-8fd7-57a1f586e261B
```

参考：<https://man.freebsd.org/cgi/man.cgi?query=nmdm>

これで仮想ポートに接続しました。

### OpenSUSE MicroOS

OpenSUSE MicroOSはOpenSUSE公式からISOもしくはQCOW2イメージをDLしてきて変換することでBhyveに使用できるようになります。今回はセルフホスト用ISOを使用しました。

<https://get.opensuse.org/ja/microos/>

ISOをセットしたらBootします。tを押すことでコンソールにGRUBメニューが表示され、Install OpenSUSE MicroOSを押してeを押し、Editモードに入ります。

その`linux` から始まる列`console=ttyS0,9600 textmode=1` を追加すると、シリアルコンソールでBootが始まります。コンソールにぞろぞろと文字が出てきたら成功です。

いきなり、/dev/sda消していい？と聞かれます。YESで先に進みましょう。

networkを設定すると、sshで接続して！とプロンプトがでます。このタイミングでhostからsshすることでkeyをコピーしてpubkey接続にすることができます。

だいたいインストールでコケるのはこの辺だけです。あとは勝手にインストールされてBootできるようになります。

ではHDDからブートしたあとの話をします。

まずは、podmanが入っているか確認してみましょう。MicroOSではデフォルトでインストールされているはずです。

```
$ podman version
Client:       Podman Engine
Version:      5.4.2
API Version:  5.4.2
Go Version:   go1.24.2
```

MicroOSはイミュータブルなOSなので、一般的なパッケージインストールや設定変更はこのままではできません。ファイルシステムがroであるためです。そこで、そのような作業をする際に`transactional-update` というコマンドを使用します。OpenSUSEではsudoがrootパスワードを要求し、sbinなどに入っているのはrootでないと見えないことに注意してください。

```
$ sudo su
# transactional-update shell
...
#
```

これで新しいスナップショット用のRWなファイルシステムに入ることができました`zypper` を使用したパッケージ管理などをここから行えます。今回はいい感じにdocker.localのような形で使うため`avahi` を導入したいと思います。

```
# hostname -s docker
# zypper ref
# zypper in -y avahi
# systemctl avahi enable
```

このシェルをexitで抜けるとそれまでに変更したファイルの差分が取得され、新しいスナップショットになります。これをシステム全体に適用し、更新をかけてみましょう。

```
# exit
...
# transactional-update apply <- ファイルシステムを切り替え
# transactional-update reboot <- システムを更新
```

このときMicroOS全体が再起動されるという形ではなく、スナップショットの入れ替えによって更新されるという形を取るので、もしうまく動かないdaemonがあったときはOS全体の再起動を試すと良いかもしれません。

ここまで来たらDockerが使えるはずです！が、私は一点詰まってかなり時間を取られたのでそれを紹介しておきます。

```
saffron@fbsd $ DOCKER_HOST="ssh://dockeruser@docker.local" docker info
Docker can't connect "http://docker"...
```

となってうまく動きません。バージョンやらなんやら色々調べましたが、これはどうもsystemdのpodman.socketがユーザー側にないことに問題があるようなのでMicroOS内で

```
$ systemctl --user enable --now podman.socket
```

とやることで動きました。

### FreeBSD側の設定

まずは、デフォルトだとavahiなどが入っていないので、ｍDNSでの探索ができません。いくつかｍDNSの実装はありますが、ここではavahiを使用します。

```
$ pkg install avahi nss-mdns
$ service avahi-daemon enable
```

そしてこのままだとmDNSをコマンド上では探索できますが、名前解決に使ってくれません。nssswitchを編集して名前解決に使ってもらいましょう。

```
$ vi /etc/nssswitch.conf
hosts: files mdns dns <- mdnsをdnsの前に追加
```

これ`docker.local` のような形で名前解決できるようになりました。

```
$ ssh dockeruser@docker.local
dockeruser@docker ~> 
```

ではDockerをインストールしてテストしてみましょう。

```
$ pkg install docker
$ export DOCKER_HOST="ssh://dockeruser@docker.local"
$ docker info
....
```

これでずらずらとインフォが出てきたら成功です。

FreeBSDのDockerはバージョンが古く、docker contextなどのモダンな機能が使えません。なので、`.profile`にVMが立ち上がっているときは環境変数を設定するように書きましょう。

```
$ vi .profile
if [ "$(LANG=C virsh -c bhyve:///system domstate docker)" = "running" ]; then
export DOCKER_HOST="ssh://dockeruser@docker.local"
fi
```

これでスムーズにVM上のPodmanを操作して、Dockerを使用する準備が整いました。テストしてみましょう。

```
$ docker run hello-world
!... Hello Podman World ...!

         .--"--.           
       / -     - \         
      / (O)   (O) \        
   ~~~| -=(,Y,)=- |         
    .---. /`  \   |~~      
 ~/  o  o \~~~~.----. ~~   
  | =(X)= |~  / (O (O) \   
   ~~~~~~~  ~| =(Y_)=-  |   
  ~~~~    ~~~|   U      |~~ 
```

このようにHello Worldが表示されれば成功です。

## 実際の使用例

`apache` を建ててみましょう。

imageをpullしてきましょう。

```
$ docker image pull httpd:latest
```

今回はたまたまドキュメントフォルダにSingle Unix Specificationの2024年版のHTMLが転がっていたのでそれを使います。

```
$ cd ./susv5-html
$ cat >Dockerfile <<EOF
FROM httpd:latest
COPY . /usr/local/apache2/htdocs
$ docker build -t susv5-apache-test .
$ docker run -it -p 8080:80 susv5-apache-test
...
```

これで起動できたので、firefoxで確認してみましょう。

```
$ firefox http://docker.local:8080
```

![2025-04-29_15-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/572200/687be2b5-afa5-45d5-9544-984996227c97.png)

うまく動きましたね！これでLinuxしかないサーバアプリケーションなどもFreeBSDで簡単にテストしたり、開発環境としてポイ捨てするような運用ができるようになりました。

## まとめ

これで様々なOSやアプリケーションを動かしたりすることができるようになりました。FreeBSDでの仮想化はFreeBSD同士ならスムーズですが、このように他のOSを動かしたり、Dockerを使用するのにはひと手間かかります。  
しかし、これでかなり実用的にFreeBSDを普段使いできるようになったのではないでしょうか。

正直、Linuxと比較してFreeBSDはコンテナ化やデスクトップ使用という点で遅れを撮っている面もおおいですが、systemdの煩雑さや業務のサーバもLinuxで開発もLinuxでみたいなのに飽きてきた人や、手軽にUNIX世界を試したいという方にとっては良い選択だと思います。ユーザーランドがディストリ間で合わなくてゴニョゴニョみたいなのもほぼありません。

今回の解説でホビーユースや開発ユーザーのFreeBSD人口がちょっと増えるとよいなと思っております。