Rootless, Seamless, Stateless Linux Desktop
開発環境をすべてDocker Rootlessコンテナ内にまとめ、複数のマシンをシームレスに使い分けられる環境を作りました。その上で遭遇した課題をまとめておきます。
概要
ローカルで起動すればローカルコンテナ上でアプリを実行し、SSH接続先で起動すればリモートコンテナ上でアプリを実行しローカルに画面転送します。
user@local:~$ vscode # <--- vscodeをローカル実行
user@local:~$ ssh remote
user@remote:~$ vscode # <--- vscodeをリモートで実行してローカルに表示
管理しやすいように作ったツールはこちら。
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
に以下のように設定していたとします。
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
となります。
ストレージ
$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
になっています。
では、ホスト側でどうなっているか確認しましょう。
$ 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:1000
、1000:1000
->100999:100999
とマッピングされています。
ここで注意すべきなのは、下記の2点です。
- コンテナ内のユーザrootで作成したファイルは、ホストでは、
1000:1000
となり、ホストユーザで削除できる。 - コンテナ内の一般ユーザで作成したファイルは、ホストでは、
100999:100999
等となり、ホストユーザで削除できない。
$ rm ~/test/user_create
rm: cannot remove '/home/hostuser/test/user_create': Permission denied
これでは困ってしまいそうですが、コンテナ内のrootユーザなら、削除可能です。
ホストユーザにマッピングされているので混同しますが、コンテナ内では、rootユーザなのです。
$ 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で実行するようにすれば、すべてのファイルを一元的に扱うことが可能です。
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と一致させることはできません。
ホストユーザとコンテナ内の一般ユーザがずれることで以下の対処が必要になります。
- ソケットファイルの権限変更
- 認証設定
具体的には下記のとおりです。
chmod 777 /tmp/.X11-unix/X0
xhost +local:
この設定により、ホストPCのユーザであれば誰でもX Serverにアクセスできるようになるので、セキュリティ的には劣化します。
pulseaudio
pulseaudioのソケットファイルは、666
となっているので、共有するだけで問題ないです。
認証のためのcookieファイル~/.config/pulse/cookie
は600
なので、共有するだけでは読み取りができません。
コンテナ起動時にrootユーザでコピーして権限を変更することで解決できます。
dbus
dbusのuser用ソケットファイルは、666
となっているので、共有するだけで問題ないです。
認証はユーザが異なるので、そのままではアクセスできません。ホストのシステム設定変更で対処することができます。
/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を使ってシームレスなアプリの切り替えを実現します。
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