machinectlコマンドを使ったコンテナ(systemd-nspawn)操作

[systemd-nspawn](https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html)はsystemdの一部として提供されているコンテナ機能。名前空間やcgroupsといった仕組みで環境を隔離できるが、Dockerなどよりはシンプルな作りになっている。
systemd-nspawnは、そのままの名前の```systemd-nspawn```コマンド以外に、同梱の```machinectl```コマンドでも操作することができる。このコマンドを使うことで、他のコンテナツールと似たような操作感になる。そこで、この記事では、主に```machinectl```コマンドによる操作方法について説明していきたい。

# 1 コンテナイメージの用意
## 1.1 debootstrap
Dockerのようなメジャーなツールとは違って、systemd-nspawnにはイメージを配布している専用のサイトが無いため少し厄介になる。
ここではDebian上で```debootstrap```コマンドを使ってみる。
```
# debootstrap --include=systemd,dbus stable /tmp/debian-container http://deb.debian.org/debian/
```
コンテナ操作にはsystemdとdbusが必要になるが、デフォルトでは入らないので指定しておく必要がある。
## 1.2 tarにまとめる
```debootstrap```で作ったディレクトリをtarでまとめておく。
```
# tar cvzf /tmp/debian-container.tar.gz /tmp/debian-container
```
# 2 イメージのインポート
```machinectl```コマンドでコンテナイメージをインポートする。
```
# machinectl import-tar /tmp/debian-container.tar.gz
```
インポートに成功すると、以下のコマンドでインポートしたイメージが表示されるようになる。
```bash
$ machinectl list-images
NAME             TYPE      RO USAGE CREATED                     MODIFIED
debian-container directory no       Sun 2025-01-19 13:42:55 JST -

1 images listed.
```
# 3 コンテナ操作
## 3.1 コンテナ操作コマンド
インポート成功後は、machinectlコマンドで容易に操作できる。
※コンテナ操作には基本的にroot権限が必要になる。
| コマンド | 操作 |
|:-|:-|
| machinectl start [NAME]  |  コンテナを起動する。 |
| machinectl poweroff [NAME] |  コンテナを停止する。 |
| machinectl reboot [NAME]| コンテナを再起動する。|
| machinectl shell [NAME]  | コンテナ内のシェルに入る。  |
| machinectl shell [NAME] [コマンド] | コンテナ内でコマンドを実行する。  |
| machinectl login [NAME]  | コンテナにログインする(shellと違ってパスワードの設定が必要)  |
| machinectl status [NAME]  | コンテナの状態を表示する。  |
| machinectl copy-to [NAME] [SRC] [DST]  | ホスト側のファイルをコンテナ内にコピーする。  |
| machinectl copy-from [NAME] [SRC] [DST]  | コンテナ内のファイルをホスト側にコピーする。  |
| machinectl list | 起動中のコンテナを表示する。 |
| machinectl enable [NAME] | ホスト起動時に自動起動する設定に変更する。 |
| machinectl disable [NAME] | ホスト起動時に自動起動しない設定に変更する。 |

コンテナ内でsystemdが動くので、コンテナ内で```systemctl start```を実行してサービスを開始したり、```systemctl enable```でコンテナ起動時に自動でサービスを開始するように設定したりすることができる。
## 3.2 ネットワーク
デフォルトの設定では、コンテナのネットワークはホスト側とは隔離され、vethが起動時に作成されてホスト側と接続される(コンテナを終了するとvethは削除される)。
Dockerなどとは違って、ネットワークの細かい設定は自動で行われないので、ipアドレスやNAT、ポート公開などについては別途自分で設定する必要がある。

## 3.3 コンテナイメージの操作コマンド
| コマンド | 操作 |
|:-|:-|
| machinectl rename [NAME] [NAME] | イメージの名前を変更する。  |
| machinectl clone [NAME] [NAME]  | イメージのクローンを作る。  |
| machinectl remove [NAME]  | イメージを削除する。  |

# 4 コンテナのエクスポート・インポート
コンテナの環境を保存してエクスポートし、後でインポートすることもできる。
| コマンド | 操作 |
|:-|:-|
| machinectl export-tar [NAME] [*image*.tar] | tarファイルとしてエクスポートする。<br>ファイル名の拡張子を```.tar.gz```、```.tar.bz2```、```.tar.xz```にすると自動判定して圧縮される。  |
| machinectl import-tar [*image*.tar] | tarファイルをイメージとしてインポートする。|

# 5 ネットワークの設定(一例)
例として、立ち上げたコンテナがネットワーク接続できるようにするシェルスクリプトを書いてみたので載せておく。細かいエラーハンドリングなどはしていない。
ファイアウォールの設定については環境に依存するので、使う場合は環境に合わせて適宜修正すること。
```sh
#!/bin/sh
set -e

container_name="debian-container"
host_ip_address="10.0.10.1"
container_ip_address="10.0.10.2"
ip_address_range="24"

ip link set dev "ve-${container_name}" down # ホスト側のvethの名前は「ve-(コンテナ名)」になる
ip address add 10.0.10.1/24 dev "ve-${container_name}"
ip link set dev "ve-${container_name}" up

# manには、スクリプトで使う場合はmachinectl shellよりもsystemd-runのほうが良いという記述があるので、ここではsystemd-runを使っている
systemd-run --quiet --wait --machine="${container_name}" bash -c \
"ip link set dev host0 down; ip address add ${container_ip_address}/${ip_address_range} dev host0; \
ip link set dev host0 up; ip route add default via ${host_ip_address} dev host0;"
# コンテナ側のvethはhost0という名前になる

ping -c 3 -q ${container_ip_address} > /dev/null

sysctl -w net.ipv4.ip_forward=1 > /dev/null

# ファイアウォールの設定
nft add table inet nat
nft add chain inet nat postrouting \{ type nat hook postrouting priority srcnat\; \}
nft add rule inet nat postrouting ip saddr "${container_ip_address}/32" oifname enp3s0 masquerade

systemd-run --quiet --wait --machine="${container_name}" bash -c "curl -sS https://example.com || wget -q -O- https://example.com"
```