Chienomi

アプリケーション環境としてのsystemd-nspawn 更新版

Live With Linux::technique

本記事はLinuxでサンドボックス環境を活用するの更新記事である。

当該記事では非効率的な内容であったり、不明瞭な内容であったりする部分を埋める目的である。

systemd-nspawnはLinuxのnamespcaeを用いたコンテナ環境を起動するものである。 Systemdのコンポーネントのひとつではあるが、ゲスト側はSystemdが必須ではない。

そのメリットは制約が少なく(Linux一式のファイルがあれば起動可能)、組むのが非常に簡単という点だろう。 ソフトウェアをインストールしたいがホスト環境に影響を及ぼしたくない、あるいはプライバシー的観点からソフトウェアからホスト環境のファイルを覗けるようにしたくないといった目的に適する。

Dockerのようなアプリケーションコンテナではなく、システム一式を構成するものである。 システム一式を必要としないサンドボックスを求めている場合、unshare(1)やfirejail(1)を検討すると良いだろう。

環境一式を用意する

Archlinuxの場合はインストール時に用いるpacstrapによってArchlinux環境を用意できる。ディレクトリは予め存在している必要がある。

# mkdir foo_container
# pacstrap -K -c foo_container base base-devel

Manjaroも以前はpacstrapコマンドでManjaro環境を構築するようになっていたが、現在はbasestrapに置き換わっている。 単にコマンド名が違うだけでなく、-Kオプションがなかったりする。

# mkdir foo_container
# basestrap -c foo_container base base-devel

DebianやUbuntuの場合はdebootstrapが使える。 これはホストが他のディストリビューションでもだ。

# debootstrap --include=dbus-broker,systemd-container --components=main,universe buster foo_container http://deb.debian.org/debian/

しかし私が試した限りでは、なぜかdbusがないと言われてうまく動作しなかった。

起動まで持っていく

コンテナの起動からログインまでの話であれば難しいことはあまりない。

環境が用意できたら、systemd-nspawnで指定することでshを起動することができる。

systemd-nspawn -D path/to/container

この状態でログイン環境として必要最低限のセットアップを行い、rootのパスワードを設定する。それが終わったら一旦exitで抜ける。

次に、コンテナを起動する。

systemd-nspawn -D path/to/container -b

これでsystemdの起動シーケンスを経てコンテナが起動される。 initによる起動を介さないでコンテナを使うと色々支障があるので、基本的には-bオプションつきで使うことになる。

コンテナはCtrl+]を3回押すと終了する。

X11

概要

コンテナでグラフィカルアプリケーションを使う方法は色々あるが、ざっくり分けると次のようになる。

  1. コンテナの完全なX11サーバー環境にグラフィカルログインする
  2. ホストのX11サーバーを流用する
  3. コンテナのXクライアントの通信をホストのXサーバーに流す

1はxnestなどを使ってログインする方法。 トラブルは少ないが、オーバーヘッドは大きい。

2は、ホストのX11サーバーにアクセスさせる。 vlanを使っていない場合、ループバックネットワークデバイスは共有されているため、そもそもの話としてアクセス自体は可能な状態になっている。 乱暴な方法だが、オーバーヘッドはない。

3は2に近いが、転送ルートを使うことで認証の問題をなくす。 通信分のオーバーヘッドは発生するが、vlanを使わずループバックデバイスを使う限りであれば、極めて高速なのであまり問題はない。

私はsystemdコンテナでxnestしたことはないので、残りの2つについて解説する。

X11の共有

前提として、Xには認証プロトコルがあり、違うホスト/ユーザーが相乗りすることはできない。 この認証ファイルが~/.XAuthorityだが、このファイルはホスト固有なのでこのファイルを共有しても通用しない。

ArchwikiではXAuthorityを書き換えることでアクセス可能にしているが、これはあまり意味がない。これをするのであれば、そもそもアクセス許可するのと変わらないからだ。

なのでアクセスを可能にする。 systemd-nspawnによる起動はrootでなければできないため、ローカルのrootに対してアクセスを許可すれば良く、

xhost +local:root

となる。

当たり前だが、共有されているホスト、つまり「そのホストのrootがあなたでない場合」はやってはいけない。

この上でコンテナ上で$DISPLAYを設定すれば、Xクライアントアプリケーションが利用できる。

export DISPLAY=:0
xeyes

X11の転送

X11はTCPによる通信を行えるため、この通信を転送することでXクライアントの利用が可能になる。 最も手っ取り早いのは、sshを用いたX11Forwardingである。

まず、コンテナ側のsshdを設定する。 ここではvlanを使わない前提で話を進めるので、sshdのポートを変更し、X11forwardingを許可する。

Port 2222

Match Address 127.*.*.*
  X11Forwarding yes

そしてsshdを起動する。 後述するが、この場合も基本的にユーザーでログインしておく必要はある。

そしてホストからX11Forwardingを有効にしてログインする。

ssh -X -p 2222 foouser@127.0.0.1

基本的にこれでOKだが、この方法だとうまくいかないケースが割とある。 どちらかといえば、Xサーバーを流用するほうが簡単確実。

なお、systemd-nspawn--bind=/tmp/.X11-unixしてしまうと、コンテナ側でソケットを消されて転送できなくなるので注意。

Wayland

「Waylandの場合はどうなんだ」と聞かれると、私はWaylandが好きではないのでちょっと困る。ただ、基本的にはWaylandの場合、こうしたケースではリモートデスクトップログインをする前提になっていると理解している。 このあたりはWaylandにとっては切り捨てた領域であるはずだ。

Xアプリケーション

前述の方法でXサーバーにはアクセスできるのだが、実際にそれで使おうとすると結構問題がある。最大の要因はD-Busである。

dbus-brokerがある前提で話をすると、D-Busの起動はSystemdが面倒を見る。 D-Busがない場合はdbus-launchを使ってD-Busを起動しようとしたりするが、これはうまく行かない。

他にも色々あるのだが、大部分の問題は「Systemdからログインする」ことで解決する。

まず、コンテナにjrhユーザーを作るとする。

useradd -m -U -s /bin/zsh jrh
passwd jrh

これでjrhユーザーでログインできる状態になったので、一旦exitしてログインに戻り、jrhユーザーとしてログインする。

これで問題はだいたい解決した。 が、まだいくつか問題が残っている。

この環境だと/run/user/XXXX/at-spiが生えない。 これについては

export NO_AT_BRIDGE=1

とすればOKだ。

が、まだ問題があり、例えばGnome Terminalは

# Error creating terminal: Could not activate remote peer: startup job failed.

となってしまう。

この問題については――残念ながら、私はまだ解決できていない。

日本語入力

基本的に通常と同じ手順で利用可能だ。 Manjaroだとこんな感じ。

まず、インストール部分

sudo pacman -S fcitx5-im fcitx5-kkc fcitx5-configtool

準備

export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIER=@im=fcitx
fcitx5 -d -r

ちょっとややこしいけれど、systemd-nspawnした端末自体はホスト側のアプリケーションなので、ホストのfcitxで入力ができる。 だが、そのシェルから起動したXクライアントはコンテナ側のアプリケーションなので、コンテナ側のfcitxが必要になる。

ホスト側のfcitxにブリッジする方法を以前確立したような気がするのだけど、記録が残っておらず、データ全滅で起動スクリプトも残っていないため残念ながら分からなくなった。

音声

PulseAudioはUnixソケットを使っているため、PulseAudioを使っている場合はホスト側のPulseAudioでコントロールできる。

前提として、ホスト側のPulseAudioのソケットをコンテナに持ち込まないといけない。

systemd-nspawn -b -D path/to/container --bind-ro=/run/user/1000/pulse:/run/user/host/pulse --bind-ro=/dev/snd

ホスト側のuidが1000でない場合は読み替えること。

その上でコンテナ側のPulseAudioに持ち込んだソケットを使わせる。

export PULSE_SERVER=unix:/run/user/host/pulse/native

これでOK。

ウェブブラウザ

目的である可能性の高いウェブブラウザだけど、Manjaro視点でいうと「ブラウザが使えればんなでも良い」であればVivaldiが依存関係が浅くて楽。 これは、Vivaldiがバイナリ配布のみで、システムのライブラリをほとんど使わないため。

同様の理由でBrave Browserも楽に入る。

Palemoonも楽に入るほう。 Firefox系のブラウザが良ければ選択肢に入る。

Firefox, Chromium, Vivaldi, Brave Browserのいずれも問題なくコンテナ上で動作する。

端末エミュレータ

コンテナ上で複雑な作業をしたい場合、ログイン後に端末エミュレータを起動するのが楽。 特に、ウィンドウやタブを開いていけるタイプであれば環境を増やしていけて、複数のアプリを制御しやすい。

基本的にVTEは動作するようになっていて、VTE自体が新しいウィンドウを開くことができるため、最低限の話をするならばVTEで良い。

VTEベースの端末であれば、Gnome Terminalはうまく動かないため、XFce4 Terminalがおすすめ。

その他xterm, urxvt, zutty, kitty, rio, Alacritty, QTerminalあたりも動作する。

その他コンテナ側に必要となるもの

Archlinux/Manjaroの視点で言えば、たいていのものは依存関係で入るのであまりない。

前述のとおり日本語入力周りは必要になる。

また、表示に関してもフォントはファイルシステム上のものを使うため、コンテナ側に必要。 ~/.local/share/fontsあたりをバインドしてしまうという方法もある。

ビデオドライバなどは、描画では基本的に必要にならないのだが、ハードウェアアクセラレーションやGPGPUなどでライブラリが必要となる場合は入れてあげる必要がある。