開発環境の構築に systemd-nspawn を使用する
開発用の Arch マシンを新調したついでに systemd-nspawn
を用いて開発環境を構築しようと試行錯誤したので、その備忘録を残しておく。
本当は LXC/LXD を使いたかったのだが、AUR の lxd
がうまくインストールできなかった…
systemd-nspawn
とは
端的に言うと「systemd
と連携するすごい chroot
」。
いわゆるシステムコンテナと呼ばれているやつで、比較的長命なコンテナを VM チックに用いる目的に特化している感じ。
Canonical がやっている LXD と似たような立ち位置だが、systemd
に組み込まれているので特別なインストール手順を踏まずにサクッと使い始められるなどの利点がある。
詳細は Arch Wiki などを参照されたし。
インストール
systemd
に組み込まれているので、最近のディストリビューションであれば既に使用可能になっているはず。
ネットワーク周りは systemd-networkd
の使用を前提にしているみたいなので、NetworkManager や netctl など他のものを使用している場合は適宜調整が必要かも。
コンテナを作成するためのツールは用意されていないので、必要になったら適宜インストールする。
コンテナの作成・起動
devenv
という名前の Ubuntu コンテナを作成してみる。
コンテナをインストールする場所は任意だが、後ほど登場する machinectl
などの管理コマンド群が /var/lib/machines
直下にコンテナのルートファイルシステムが置いてあることを前提としているので、今回はそのように進めていく。
まず、/var/lib/machines
下にコンテナのイメージを保管するためのディレクトリを作成する。
# mkdir /var/lib/machines/devenv
Btrfs のサブボリュームを使用する場合は次の通り。 サブボリュームにしておくと、後でコンテナのイメージを複製するときにスナップショットを使って良しなにしてくれるらしい。
# btrfs subvolume create /var/lib/machines/devenv
作成したディレクトリに debootstrap
を用いて Ubuntu をインストールする。このとき、署名チェックが行われるため ubuntu-keyring
(Debian の場合は debian-archive-keyring
)をあらかじめインストールしておく。
# debootstrap --arch=amd64 disco /var/lib/machines/devenv \ http://ftp.jaist.ac.jp/pub/Linux/ubuntu/
次のように systemd-nspawn
コマンドを実行することでいい感じに隔離された chroot 環境が立ち上がるので、アプリケーションのインストールなど必要なセットアップを実行する。
# systemd-nspawn -D /var/lib/machines/devenv
コンテナの管理
systemd-nspawn
は systemd
の一部なので、コンテナの起動・管理も systemd
を介して行う。
コンテナ管理用の machinectl
というコマンドが付属しており、これを用いてコンテナの起動や停止、状態表示などの操作を行うことができるようになっている。
例えば、先ほど作成した devenv
コンテナを起動するためには次のようにコマンドを実行する。
# machinectl start devenv
上のようにすると、 systemd-nspawn@devenv.service
というサービスが作成される。
これを見るとわかるように、コンテナは systemd のサービスになっているので journalctl
でログを見たり色々できる。
次のようにすることで、ホスト OS の起動時にコンテナを立ち上げることも出来る。
# systemctl enable machines.target # systemctl enable systemd-nspawn@devenv.service # machinectl enable devenv でも可
コンテナの複製
残念ながら、systemd-nspawn
には docker pull
のようにリポジトリから手軽にイメージを引っ張ってくる機能はないので、コンテナの作成にある程度の手間が生じる(逆に言えば、overlay とか Dockerfile とか細々したことを考えず rootfs だけ用意すればコンテナを実行できるという利点でもある)。
リリースサイクルがはっきりとしているのであれば、ベースとなるコンテナのイメージをあらかじめ用意しておき、コンテナ作成時にそれを複製してしまうのが手っ取り早い。
machinectl
には、ちょうどそのような用途で使える clone
というサブコマンドが用意されている。
例えば、すでに存在する ubuntu-bionic
というコンテナを複製して devenv
というコンテナを新たに作成する場合は次のようにする。
# machinectl clone ubuntu-bionic devenv
複製元(上の場合は ubuntu-bionic
)が Btrfs のサブボリュームである場合、イメージの複製はスナップショットを用いてコピーが少なくなるよう上手くやってくれるらしい(このあたりよくわかっていない)。
複製元のイメージがコンテナとして実行されると内容が変更されてしまう可能性があるので、次のように読み込み専用にしておくと良いかもしれない。
# machinectl read-only ubuntu-bionic
ローリングリリースモデルを採用している Arch Linux や Gentoo みたいなディストリビューションだと上のような使い方で得られる恩恵はあまりなさそうだが、単純にコンテナの設定を単純化できるので実験用途には良いかもしれない。 なお、Docker のイメージを tar 形式でエクスポートすることでそのまま使うことも出来るらしいので、DB など使えそうなイメージを適宜取り込むとコンテナ作成を効率化できるかもしれない(それで本当に楽できるかは不明だが)。
ネットワーク設定
TODO
コンテナ内で Docker を動かす
本当に書きたかったのはこのセクションで、上に書いたのはすべておまけ。 デフォルトで起動されるコンテナは非特権モードであり、そのままでは Docker を動かすことができない。 なので、コンテナの設定を変更して特権モードに切り替える必要がある。 セキュリティリスクが高まる上 systemd/Docker が公式に対応しているわけではないので、設定は最新の注意を行いつつ自己責任で行うこと。
まず、systemd-nspawn@<container>.service
を編集し、systemd-nspawn 側で cgroup namespacing が使用されないようにする。
$ sudo systemctl edit systemd-nspawn@<container>.service
[Service] Environment=SYSTEMD_NSPAWN_USE_CGNS=0
次に、/etc/systemd/nspawn/<container>.nspawn
を作成し、コンテナが特権ユーザで実行されるようにする。
$ sudo -e /etc/systemd/nspawn/devenv.nspawn
[Exec] Capability=all SystemCallFilter=add_key keyctl PrivateUsers=no [Files] Bind=/sys/fs/cgroup [Network] VirtualEthernet=yes
設定が完了したらコンテナを再起動し、Docker をインストール・実行して問題がなければよし。
おそらく重要なのは [Exec]
セクションにある PrivateUsers=no
で、これがないとコンテナ側の root とホスト側の root が対応せず btrfs のスナップショット作成などの操作ができずにコケるっぽい。
SystemCallFilter
の部分を適切に設定すれば良いのかもしれないがその辺よくわかっていない…
また、/sys/fs/cgroup
を直接マウントしていることからもわかるように、コンテナ内で実行される Docker は自身がコンテナ内で実行されていることを考慮していないと思われる。なので、ホスト側で Docker が起動していたり同じ設定で複数個コンテナを立ち上げたりすると恐らく破綻する。
Rootless Docker が使えるようになればそちらを使えばよいかもしれないが、まだ実験段階であるせいか上手く動作しなかったので今後に期待したい。