🖥️

Rootless, Seamless, Stateless Linux Desktop

2022/01/06に公開

開発環境をすべてDocker Rootlessコンテナ内にまとめ、複数のマシンをシームレスに使い分けられる環境を作りました。その上で遭遇した課題をまとめておきます。

概要

ローカルで起動すればローカルコンテナ上でアプリを実行し、SSH接続先で起動すればリモートコンテナ上でアプリを実行しローカルに画面転送します。

bash
user@local:~$ vscode  # <--- vscodeをローカル実行
user@local:~$ ssh remote
user@remote:~$ vscode # <--- vscodeをリモートで実行してローカルに表示

管理しやすいように作ったツールはこちら。

https://github.com/ysuito/xsprash.git

vscode以外にもよく使うアプリのDockerfileが入れてあります。

ユースケース

  • ブラウザはラップトップで起動して、開発環境はデスクトップで起動して負荷分散する
  • AWS EC2 g4ad.2xlarge等のGPUインスタンスを使うことで非力なPCで快適な開発環境を作る
  • プロジェクト毎にvscode環境を作成して完全に分離し認証情報などが漏れるリスクを防ぐ
  • レンダリングはリモートで行ってローカルには画面転送だけする自作リモートブラウザ
  • ホストOSをとにかく汚したくない方。事故ったときはユーザを削除するだけで対処完了

背景

Linuxにはいつまでもきれいでいてもらいたいのに、自堕落さ故に汚し続ける自分が嫌でした。

  • NOPASSWDでsudoを使う
  • 不必要なパッケージをインストールして、インストールしたことを忘れる
  • $HOME配下に無数の隠しファイルができて、管理不能になる
  • ビルドの早いデスクトップがあるのに、結局ラップトップを寝ながら使う
  • すべてCLIで賄うのは辛い
  • でも、自由でいたい

そこでDocker Rootlessとxpraを使って私的理想環境を作りました。
その過程において特に、Rootlessコンテナを扱う上での課題を以下で解説していきたいと思います。

課題

Rootlessコンテナ内での一般ユーザ

ホストのuid,gidはコンテナ内で/etc/subuid,/etc/subgidで定義されたid空間にマッピングされます。
/etc/subuid,/etc/subgidに以下のように設定していたとします。

/etc/subuid
hostuser:100000:65536
ホスト コンテナ内
1000:1000 0:0
100999:100999 1000:1000

このようにホストのdocker実行ユーザのuid,gidはコンテナ内では、root:root(0:0)にマッピングされます。

コンテナ内での一般ユーザ(1000:1000)は、ホスト上では、

100000(/etc/subuidの開始id) + 1000(コンテナ内でのuid) - 1(コンテナ内でのrootはホストのユーザidにマッピングされているので除外) = 100999

となります。

ストレージ

bash
$docker run --rm -it -v /home/hostuser/test:/home/user ubuntu bash -c "useradd user && chown -R 1000:1000 /home/user && touch /home/user/root_create && su - user -c 'touch user_create && ls -lan' "
total 0
drwxr-xr-x 2 1000 1000 44 Jan  6 04:19 .
drwxr-xr-x 1    0    0 18 Jan  6 04:19 ..
-rw-r--r-- 1    0    0  0 Jan  6 04:19 root_create
-rw-rw-r-- 1 1000 1000  0 Jan  6 04:19 user_create

コンテナ内でrootが作るファイルは0:0で一般ユーザが作るファイルは、1000:1000になっています。
では、ホスト側でどうなっているか確認しましょう。

bash
$ ls -ln ~/test
total 0
-rw-r--r-- 1   1000   1000 0 Jan  6 13:19 root_create
-rw-rw-r-- 1 100999 100999 0 Jan  6 13:19 user_create

このように0:0->1000:10001000:1000->100999:100999とマッピングされています。

ここで注意すべきなのは、下記の2点です。

  • コンテナ内のユーザrootで作成したファイルは、ホストでは、1000:1000となり、ホストユーザで削除できる。
  • コンテナ内の一般ユーザで作成したファイルは、ホストでは、100999:100999等となり、ホストユーザで削除できない。
bash
$ rm ~/test/user_create
rm: cannot remove '/home/hostuser/test/user_create': Permission denied

これでは困ってしまいそうですが、コンテナ内のrootユーザなら、削除可能です。
ホストユーザにマッピングされているので混同しますが、コンテナ内では、rootユーザなのです。

bash
$ docker run --rm -it -v /home/hostuser/test:/home/user ubuntu bash -c "rm /home/user/user_create"
$ ls -ln ~/test
total 0
-rw-r--r-- 1 1000 1000 0 Jan  6 13:19 root_create

シームレスな環境を作るにあたっては、複数ホストでファイル同期する際に、この所有権のズレは大きな課題となります。
コンテナ内でのrootユーザを利用することによって、解決可能です。syncthing公式のdocker imageは、コンテナ内での実行UIDが指定可能なので、以下のようにrootで実行するようにすれば、すべてのファイルを一元的に扱うことが可能です。

bash
docker run -p 127.0.0.1:8384:8384 -p 22000:22000/tcp -p 22000:22000/udp \
    -v /wherever/st-sync:/var/syncthing \
    --hostname=my-syncthing \
    -e PUID=0 -e PGUID=0 \
    syncthing/syncthing:latest

ただし、コピー先のホストでは、コピーされたファイルの所有権は、全てコピー先ホストユーザになります。
各rootlessコンテナの起動時に、HOMEディレクトリをchown -R user:userすれば、ワークアラウンドとなります。

X Server

rootlessでは、rootfullモードで画面共有するよりもひと工夫必要になります。
上記でも説明したように、rootlessコンテナ内の一般ユーザのIDは、ホストユーザのIDと一致させることはできません。
ホストユーザとコンテナ内の一般ユーザがずれることで以下の対処が必要になります。

  • ソケットファイルの権限変更
  • 認証設定

具体的には下記のとおりです。

bash
chmod 777 /tmp/.X11-unix/X0
xhost +local:

この設定により、ホストPCのユーザであれば誰でもX Serverにアクセスできるようになるので、セキュリティ的には劣化します。

pulseaudio

pulseaudioのソケットファイルは、666となっているので、共有するだけで問題ないです。
認証のためのcookieファイル~/.config/pulse/cookie600なので、共有するだけでは読み取りができません。
コンテナ起動時にrootユーザでコピーして権限を変更することで解決できます。

dbus

dbusのuser用ソケットファイルは、666となっているので、共有するだけで問題ないです。
認証はユーザが異なるので、そのままではアクセスできません。ホストのシステム設定変更で対処することができます。

/usr/share/dbus-1/session.d/session-local.confを作成

/usr/share/dbus-1/session.d/session-local.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <auth>ANONYMOUS</auth>
  <allow_anonymous/>
</busconfig>

ホストマシンを再起動してください。

ローカルとリモートのシームレスな切り替え

xpraとssh configを使ってシームレスなアプリの切り替えを実現します。

~/.ssh/config
Host dev-server
HostName       IP_ADDRESS
Port           22
User           USERNAME
IdentityFile   ~/.ssh/KEY
RequestTTY     force
RemoteCommand  xpra start :101 --bind-tcp=127.0.0.1:10001 --start="xhost +local:" & SHELL
PermitLocalCommand yes
LocalCommand   xpra attach tcp://127.0.0.1:10001/101 &
LocalForward   10001 127.0.0.1:10001

IP_ADDRESS,USERNAME,KEY,SHELL の部分は各環境に置き換えてください。

上記のように設定で、いつも通りsshでリモートに接続するだけで、画面転送のためのサーバとクライアントを準備して、さらにシェルを起動します。
リモートでアプリを起動するために必要なのは、ssh接続して、コマンドを一つ打つだけです。

残念な点

  • ec2インスタンスでandroidエミュレーターを動かそうとしたが、/dev/kvmが利用できないので不可
  • rootlessにしてセキュアな開発環境を作りたかったが、いくつかのリソースの認証を甘くしているので、今後認証を厳しくする。

Discussion