2016/10/29

systemd-nspawn を使ってみる

Debian 8.5 (Jessie)環境でコンテナを作り、
テスト版であるDebian 9 (stretch)を入れてみる[1]。

Dockerは使わず、systemd-nspawnで行う。
これならJessieのパッケージ管理システムに異物を入れずに済む。

以下、作業ログ。

  1. debootstrapをインストール(aptitudeかapt-getで)。
  2. $ mkdir ~/container
  3. $ cd container
  4. ルート権限で
    $ debootstrap stretch ./stretch http://ftp.jp.debian.org/debian
    を実行。内部でchrootを使っているらしく、ルート権限がないと動作しない。

    なお、fakerootとfakechrootを使えば、ルート権限なしでも実行できるようだ[2]。
    しかし、試してみたところ、
    $ fakechroot
    $ fakeroot
    $ export PATH=/usr/sbin:/sbin:$PATH
    $ debootstrap --variant=fakechroot stretch ./stretch http://ftp.jp.debian.org/debian
    
    では残念ながら動作しなかった。詳しくは下のほうで。
  5. 作った環境のルートのパスワードを設定する。これもルート権限が必要。
    $ systemd-nspawn --directory=stretch passwd
    
    パスワード入力前に以下のメッセージが出るが、とりあえず無視。
    /etc/localtime is not a symlink, not updating container timezone.
  6. コンテナを起動する。ネットワークが必要なければ、
    $ systemd-nspawn --directory=stretch --boot
    
    を実行すればよい。

    実行すると以下のようなログが流れる。仮想マシンで起動しているかのようだ!
    Spawning container stretch on /home/xxx/container/stretch.
    Press ^] three times within 1s to kill container.
    /etc/localtime is not a symlink, not updating container timezone.
    systemd 231 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ -LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD +IDN)
    Detected virtualization systemd-nspawn.
    Detected architecture x86-64.
    
    Welcome to Debian GNU/Linux stretch/sid!
    
    Set hostname to <debian64>.
    [  OK  ] Listening on Syslog Socket.
    [  OK  ] Reached target Swap.
    [  OK  ] Listening on Journal Socket.
    [  OK  ] Listening on Journal Socket (/dev/log).
    [  OK  ] Started Dispatch Password Requests to Console Directory Watch.
    [  OK  ] Created slice System Slice.
    Mounting POSIX Message Queue File System...
    Starting Remount Root and Kernel File Systems...
    Starting Journal Service...
    [  OK  ] Created slice system-getty.slice.
    Mounting FUSE Control File System...
    [  OK  ] Reached target Encrypted Volumes.
    [  OK  ] Listening on /dev/initctl Compatibility Named Pipe.
    [  OK  ] Reached target Sockets.
    [  OK  ] Reached target Remote File Systems.
    [  OK  ] Started Forward Password Requests to Wall Directory Watch.
    [  OK  ] Reached target Paths.
    Mounting Huge Pages File System...
    [  OK  ] Reached target Slices.
    [  OK  ] Started Remount Root and Kernel File Systems.
    [  OK  ] Reached target Local File Systems (Pre).
    [  OK  ] Reached target Local File Systems.
    Starting Raise network interfaces...
    Starting Load/Save Random Seed...
    [  OK  ] Mounted POSIX Message Queue File System.
    [  OK  ] Mounted FUSE Control File System.
    [  OK  ] Mounted Huge Pages File System.
    [  OK  ] Started Journal Service.
    Starting Flush Journal to Persistent Storage...
    [  OK  ] Started Load/Save Random Seed.
    [  OK  ] Started Flush Journal to Persistent Storage.
    Starting Create Volatile Files and Directories...
    [  OK  ] Started Create Volatile Files and Directories.
    Starting Update UTMP about System Boot/Shutdown...
    [  OK  ] Reached target System Time Synchronized.
    [  OK  ] Started Update UTMP about System Boot/Shutdown.
    [  OK  ] Reached target System Initialization.
    [  OK  ] Reached target Basic System.
    [  OK  ] Started Regular background program processing daemon.
    Starting System Logging Service...
    [  OK  ] Started Daily apt activities.
    [  OK  ] Started Daily Cleanup of Temporary Directories.
    [  OK  ] Reached target Timers.
    [  OK  ] Started Raise network interfaces.
    [  OK  ] Reached target Network.
    Starting Permit User Sessions...
    [  OK  ] Started Permit User Sessions.
    [  OK  ] Started Console Getty.
    [  OK  ] Reached target Login Prompts.
    [  OK  ] Started System Logging Service.
    [  OK  ] Reached target Multi-User System.
    [  OK  ] Reached target Graphical Interface.
    Starting Update UTMP about System Runlevel Changes...
    [  OK  ] Started Update UTMP about System Runlevel Changes.
    
    Debian GNU/Linux stretch/sid debian64 console
    
    debian64 login: root  
    Password: ←ここで先ほど設定したパスワードを入力
    Linux debian64 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u1 (2016-09-03) x86_64
    
    The programs included with the Debian GNU/Linux system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.
    
    Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
    permitted by applicable law.
    
  7. 終了するときは、ログの最初に書かれているように、
    Press ^] three times
    とすればよい。つまり Ctrl+] を3回連続で押す。
さて、以降の内容は、通常ユーザー権限によるコンテナ起動に関するメモである。
結論から言うと、通常ユーザーでコンテナは起動できず、ルート権限が必要である。

こんなことを試したのは、単に起動するたびにルート権限がいるのは面倒だというだけのこと。
まず、
$ fakeroot
$ systemd-nspawn --directory=stretch --boot
を実行してみたが、
Press ^] three times within 1s to kill container.
clone() failed: Operation not permitted
が表示されて起動できない。

というわけで、4.からfakechrootとfakerootを使った環境で作業をしてみる。
$ fakechroot
$ fakeroot
$ export PATH=/usr/sbin:/sbin:$PATH
$ debootstrap --variant=fakechroot stretch ./fake-stretch http://ftp.jp.debian.org/debian

最後に次のメッセージを出力して止まってしまった。
W: Failure trying to run: chroot /home/xxx/container/fake-stretch dpkg --force-depends --install /var/cache/apt/archives/base-passwd_3.5.40_amd64.deb
W: See /home/xxx/container/fake-stretch/debootstrap/debootstrap.log for details (possibly the package matches is at fault)
debootstrap.logには、
Setting up base-passwd (3.5.40) ...
Can't load '/usr/lib/x86_64-linux-gnu/perl-base/auto/POSIX/POSIX.so' for module POSIX: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.23' not found (required by /home/xxx/container/fake-stretch/usr/lib/x86_64-linux-gnu/perl-base/auto/POSIX/POSIX.so) at /usr/lib/x86_64-linux-gnu/perl-base/XSLoader.pm line 96.
^@ at /usr/lib/x86_64-linux-gnu/perl-base/POSIX.pm line 36.
Compilation failed in require at /usr/share/perl5/Debconf/Template.pm line 7.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Template.pm line 7.
Compilation failed in require at /usr/share/perl5/Debconf/Question.pm line 8.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Question.pm line 8.
Compilation failed in require at /usr/share/perl5/Debconf/Config.pm line 7.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Config.pm line 7.
Compilation failed in require at /usr/share/perl5/Debconf/Log.pm line 10.
Compilation failed in require at /usr/share/perl5/Debconf/Db.pm line 7.
BEGIN failed--compilation aborted at /usr/share/perl5/Debconf/Db.pm line 7.
Compilation failed in require at /usr/share/debconf/frontend line 6.
BEGIN failed--compilation aborted at /usr/share/debconf/frontend line 6.
dpkg: error processing package base-passwd (--install):
subprocess installed post-installation script returned error exit status 255
Errors were encountered while processing:
base-passwd
が出力されていた。

無視して、そのまま(fakeroot環境のまま)
systemd-nspawn --directory=stretch --boot
を実行してみたが、
Press ^] three times within 1s to kill container.
clone() failed: Operation not permitted
と表示され、やはり実行できない。

コマンドの実行方法を替えてみる[3]。
$ fakechroot fakeroot debootstrap stretch ./fake-stretch http://ftp.jp.debian.org/debian
しかし、同じエラーで停止する。

そこで、debootstrapの代わりにcdebootstrapを使ってみる。
$ fakechroot fakeroot cdebootstrap stretch ./fake-stretch http://ftp.jp.debian.org/debian 

stretchがないといわれる。testingに替えても、結局、
E: Unknown suite stretch
といわれる。

[4]によると、/usr/share/cdebootstrap/suitesを修正すればよいらしい。
Suite: stretch
Config: generic
Keyring: debian-archive-keyring.gpg
を追加した。これでstretchのパッケージの取得は動作するようになったが、結局、
...
P: Validating wget
P: Retrieving whiptail
P: Validating whiptail
E: Failed to unshare: Operation not permitted
が表示されて、停止。

仕方がないので、ルート権限で
$ cp -r stretch stretch2
$ chown -R xxx stretch2
として、ルート権限で作ったstretchのディレクトリを一般ユーザーxxxに変更して、
$ fakeroot systemd-nspawn --directory=stretch2 --boot
を試してみたが、だめ。
$ fakechroot fakeroot systemd-nspawn --directory=stretch2 --boot
もだめ。
clone() failed: Operation not permitted
というエラーが出る。

[5]によると、cloneには、CAP_SYS_ADMINが必要らしい。
さらに[6]によると、
# Linux 3.8 以降では、ユーザー名前空間の作成にどのケーパビリティも必要としない
ということなので、カーネルのバージョンを見てみると、
$ uname -r
3.16.0-4-amd64
であった。あれ? 3.8以上のように見えるが…。

とりあえず、systemd-nspawnにCAP_SYS_ADMINを付与してみる。
$ setcap "CAP_SYS_ADMIN=eip" /usr/bin/systemd-nspawn

設定されたか確認してみると、
$ getcap /usr/bin/systemd-nspawn
/usr/bin/systemd-nspawn = cap_sys_admin+eip
となっており、設定された。

この状態で一般ユーザーで実行してみる。

$ fakeroot systemd-nspawn --directory=stretch2 --boot
ERROR: ld.so: object 'libfakeroot-sysv.so' from LD_PRELOAD cannot be preloaded (cannot     open shared object file): ignored.
Need to be root.
となり、残念ながら起動できない。

fakeroot-ng [7]というパッケージがあるので、それをfakerootの代わりに使ってみた。
しかし、CAP_SYS_ADMINの設定の有無に関わらず、

$ fakeroot-ng systemd-nspawn --directory=stretch2 --boot
Spawning container stretch on /home/xxx/container/stretch.
Press ^] three times within 1s to kill container.
clone() failed: Operation not permitted
となり、やはり起動できない。

CAP_SYS_ADMINはあきらめ、[8]を参考に、

$ echo 1 > /proc/sys/kernel/unprivileged_userns_clone
を設定してみたが、
$ systemd-nspawn --directory=stretch2 --boot
$ fakeroot systemd-nspawn --directory=stretch2 --boot
$ fakeroot-ng systemd-nspawn --directory=stretch2 --boot
の3つとも動作しない。

やはりルート権限が必要なようだ。


--- 参考 ---
[1] https://lindenberg.io/blog/post/debian-containers-with-systemd-nspawn/
[2] http://rail.hatenablog.jp/entry/2012/10/25/234638
[3] https://github.com/dex4er/fakechroot/wiki
[4] http://d.hatena.ne.jp/embedded/20150926/p1
[5] http://systemd-devel.freedesktop.narkive.com/GchaIOxs/systemd-nspawn-create-container-under-unprivileged-user
[6] https://linuxjm.osdn.jp/html/LDP_man-pages/man7/capabilities.7.html
[7] https://fakeroot-ng.lingnu.com/index.php/Home_Page
[8] http://systemd-devel.freedesktop.narkive.com/GchaIOxs/systemd-nspawn-create-container-under-unprivileged-user