LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第17回LXCの構築・活用 [4] ― 一般ユーザによるLXCコンテナの利用

前回は一般ユーザでコンテナを起動する際に利用するカーネルのユーザ名前空間について紹介しました。

今回はそのユーザ名前空間を使って実現している、一般ユーザによるLXCコンテナの作成や起動の方法について説明します。一般ユーザによるコンテナの利用については第14回にPlamo Linuxでの例を簡単に紹介しています。今回はUbuntu 14.04 LTSを使って改めて整理して説明すると同時に、他のディストリビューションの場合の説明も行います。

一般ユーザでコンテナを利用できるディストリビューション

第16回でユーザ名前空間で複数のIDのマッピングを行うためにshadowに導入された「サブID」を紹介しました。

一般ユーザでLXCコンテナを利用する場合は、ユーザに対して「サブID」を設定し、サブIDを使ってユーザ名前空間用のマッピングを設定できる環境が必要です。

Ubuntu 14.04 LTS以降は当然この対応が済んだshadowを使っていますし、第14回で紹介したようにPlamo Linuxの現在のバージョンである5.3でもサブIDの設定が可能になっています。

筆者は、以上の2つのディストリビューションをコンテナホストとした場合で、一般ユーザでコンテナの利用ができることを確認しています。他に、確認はしていませんが、shadowのバージョンを見る限りではArchとDebianのjessie以上で動きそうです。

一般ユーザでコンテナを利用する場合のOSでの準備

一般ユーザでLXCコンテナを利用する場合は、先に説明したようにサブIDの設定が必要です。

Ubuntu 14.04 LTS以上ではユーザを追加するたびにサブIDが自動的に設定されますのでこの作業を行う必要はありません。

$ cat /etc/subuid /etc/subgid
ubuntu:100000:65536
ubuntu:100000:65536

この例はubuntuというユーザをインストール時に追加したときに設定され たサブIDです。100000から65536個のIDがubuntuで使えるように設定されています。

これとは別の範囲をサブIDとして設定したい場合や、サブIDが使える他のディストリビューションで設定する場合は第16回で説明したようにusermodコマンドを使います。

$ sudo usermod -v 200000-265535 -w 200000-265535 user

-vはUIDのサブIDを、-wはGIDのサブIDの範囲を設定します。

UbuntuのLXCパッケージ使用時の各種パス

一般ユーザでLXCを使ってコンテナを利用する際の設定ファイルなどの場所について第8回で示したのと同様の表を使って紹介しましょう。

ファイル/ディレクトリ root権限の時 一般ユーザの時
システム設定ファイル /etc/lxc/lxc.conf ~/.config/lxc/lxc.conf
コンテナのデフォルト設定ファイル /etc/lxc/default.conf ~/.config/lxc/default.conf
テンプレートファイルの置き場所 /usr/share/lxc/config (root権限の場合と同じ)
コンテナ作成時のキャッシュの置き場所 /var/cache/lxc ~/.cache/lxc
コンテナの保存場所 /var/lib/lxc ~/.local/share/lxc
ログファイルの出力場所 /var/log/lxc ~/.local/share/lxc/(コンテナ名)
一般ユーザが使うネットワークインターフェースの設定ファイル (なし) /etc/lxc/lxc-usernet

root権限の場合のパスは各ディストリビューションのコンパイル時のオプションに依存します。一方で一般ユーザの場合は、システム設定ファイルでデフォルト値を変更しない限りは、どのディストリビューションでも図のようになるはずです。

テンプレートファイルに関しては、root権限用、一般ユーザ用という区別はなく同じテンプレートを使用しますので、一般ユーザ用のテンプレート置き場は存在しません。

一般ユーザでコンテナを利用する場合のLXCの設定

ネットワークインターフェースの設定

第16回で説明したように、一般ユーザでコンテナを扱うためにユーザ名前空間を作成すると、名前空間内でネットワークインターフェースを作成するといった特権が必要な操作は名前空間内のrootユーザで行えます。

現時点では一般ユーザ権限で起動したコンテナと外部の通信をする際は第6回で説明したvethインターフェースを使う必要があります。

コンテナがこのvethインターフェースを使って外部と通信するためには、vethインターフェースを作成した際にできるペアのインターフェースの片側をホストの名前空間に割り当てますので、その際ににはホストOS上での特権が必要となります。

一般ユーザでこの特権が必要な操作をするために、LXCではlxc-user-nicというプログラムを使用します。

$ ls -l /usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
-rwsr-xr-x 1 root root 34888 Sep 30 17:13 /usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic

このlxc-user-nicはUbuntuでは/usr/lib/x86_64-linux-gnu/lxc/にインストールされています(amd64版⁠⁠。以上のようにsetuidすることにより一般ユーザでも特権が必要なホストOS上でvethインターフェースを扱う操作ができるようになっています

このプログラムは、あらかじめホストOSの管理者が設定しておく/etc/lxc/lxc-usernetというファイルを参照して、ユーザが作成できるインターフェースの数と、作成したインターフェースをホスト上のどのブリッジに接続するかを決めます。

lxc-usernetファイルは以下のような書式で1行に1ユーザの設定を記述します。

(ユーザ名) (インターフェースのタイプ) (接続するブリッジ) (作成できるインターフェースの数)

たとえば、ubuntuユーザがvethインターフェースを10個まで作成でき、作成したインターフェースはlxcbr0に接続する場合は以下のようになります。

ubuntu veth lxcbr0 10

現時点ではインターフェースのタイプはveth以外は指定できません。

各ユーザのLXCの設定ファイル

それでは一般ユーザでコンテナを起動するためのLXCの準備をしていきましょう。

第14回でも簡単に方法に触れていますが、ここでもう一度じっくり手順を追ってみます。

まずは設定ファイルを置くディレクトリがない場合は作成しましょう。

$ mkdir -p ~/.config/lxc

Ubuntuではシステム設定ファイルであるlxc.confはデフォルトでは使われていません。コンテナイメージが置かれる場所を変えたいなど、必要があれば~/.config/lxc/lxc.confファイルを作成して設定を書いておきましょう。設定についてはman lxc.system.confをご覧ください。

次はコンテナのデフォルト設定ファイルであるdefault.confです。これはLXCパッケージをインストールしたときに作成されますので、それを元に一般ユーザでコンテナを起動する際に必要な設定を追加すれば良いでしょう。

$ cp /etc/lxc/default.conf ~/.config/lxc/default.conf
$ vi ~/.config/lxc/default.conf

ここで追加する設定は/etc/subuid/etc/subgidで行ったようなユーザ名前空間を使うときのマッピング情報です。この設定はlxc.id_mapです。

この設定は4つのフィールドから成っています。最初から順に以下のようになります。

  • uidのマッピングの場合は"u"、gidのマッピングの場合は"g"の一文字
  • あとの3つは第16回で紹介した/proc/[PID]/uid_map/proc/[PID]/gid_mapと同じ書式

たとえばUIDをマッピングする場合で、コンテナ内のrootをコンテナ外で100000のUIDにマッピングし、コンテナ内で65536個のUIDを使う場合は

lxc.id_map = u 0 100000 65536

となります。

GIDにも同様のマッピングをすると、~/.config/lxc/default.confは以下のようになります。

# /etc/lxc/default.conf に書かれていた設定
lxc.network.type = veth
lxc.network.link = lxcbr0
lxc.network.flags = up
lxc.network.hwaddr = 00:16:3e:xx:xx:xx
# このあとが一般ユーザ向けに追加する行
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536

コンテナの作成

一般ユーザのコンテナイメージと設定ファイルは、システム設定ファイルで場所を設定していない場合は~/.local/share/lxcにコンテナ名のディレクトリを作成してそのディレクトリ以下に置かれます。

このディレクトリ(~/.local/share/lxc)がない場合はコンテナ作成時に自動的に作成されますので、設定ファイルの準備ができたら早速コンテナを作成してみましょう。

一般ユーザ権限の場合は、第7回第8回で紹介したダウンロードテンプレートを使います。

lxc-ubuntulxc-centosなどの各ディストリビューション用のテンプレートは使えません。これは、テンプレート内でユーザ名前空間内のrootが許可されていない処理を実行する場合が多いためです。

一般ユーザでコンテナを作成する場合もrootで実行する場合と同じようにlxc-createを使います。使い方もrootで実行する場合と同じです。

$ lxc-create -n ct01 -t download -- -d ubuntu -r trusty -a amd64
Using image from local cache
Unpacking the rootfs

---
You just created an Ubuntu container (release=trusty, arch=amd64, variant=default)

To enable sshd, run: apt-get install openssh-server

For security reason, container images ship without user accounts
and without a root password.

Use lxc-attach or chroot directly into the rootfs to set a root password
or create user accounts.

以上はUbuntu Trusty(amd64)のコンテナイメージをダウンロードして作成た例です。

作成されたコンテナディレクトリを確認しておきましょう。

$ ls -F ~/.local/share/lxc
ct01/
$ ls -F ~/.local/share/lxc/ct01
config  rootfs/

以上のようにroot権限で/var/lib/lxc以下に作成されたコンテナと同様に作成されています。

設定ファイルは以下のようになっています。

$ cat ~/.local/share/lxc/ct01/config 
  :(略)
# Distribution configuration
lxc.include = /usr/share/lxc/config/ubuntu.common.conf
lxc.include = /usr/share/lxc/config/ubuntu.userns.conf
lxc.arch = x86_64

# Container specific configuration
lxc.id_map = u 0 100000 65536
lxc.id_map = g 0 100000 65536
lxc.rootfs = /home/ubuntu/.local/share/lxc/ct01/rootfs
lxc.utsname = ct01

# Network configuration
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxcbr0
lxc.network.hwaddr = 00:16:3e:9f:c4:5a

root権限で作成したコンテナと一般ユーザで作成したコンテナの違いはubuntu.userns.confをincludeしている部分です。これは一般ユーザでコンテナを作成する際に使用する共通の設定をまとめたファイルで、ディストリビューションごとに準備されています。

$ ls /usr/share/lxc/config/*.userns.conf
/usr/share/lxc/config/centos.userns.conf
/usr/share/lxc/config/debian.userns.conf
/usr/share/lxc/config/fedora.userns.conf
/usr/share/lxc/config/gentoo.userns.conf
/usr/share/lxc/config/oracle.userns.conf
/usr/share/lxc/config/plamo.userns.conf
/usr/share/lxc/config/ubuntu-cloud.userns.conf
/usr/share/lxc/config/ubuntu.userns.conf

たとえばubuntuの場合以下のような内容です。デバイスファイルを作成するなどのユーザ名前空間内では実行できない処理の代わりに、ホストOSのファイルをバインドマウントする設定などが書かれています。

$ cat /usr/share/lxc/config/ubuntu.userns.conf 
# CAP_SYS_ADMIN in init-user-ns is required for cgroup.devices
lxc.cgroup.devices.deny =
lxc.cgroup.devices.allow =

# We can't move bind-mounts, so don't use /dev/lxc/
lxc.devttydir =

# Extra bind-mounts for userns
lxc.mount.entry = /dev/console dev/console none bind,create=file 0 0
lxc.mount.entry = /dev/full dev/full none bind,create=file 0 0
lxc.mount.entry = /dev/null dev/null none bind,create=file 0 0
lxc.mount.entry = /dev/random dev/random none bind,create=file 0 0
lxc.mount.entry = /dev/tty dev/tty none bind,create=file 0 0
lxc.mount.entry = /dev/urandom dev/urandom none bind,create=file 0 0
lxc.mount.entry = /dev/zero dev/zero none bind,create=file 0 0

# Extra fstab entries as mountall can't mount those by itself
lxc.mount.entry = /sys/firmware/efi/efivars sys/firmware/efi/efivars none bind,optional 0 0
lxc.mount.entry = /proc/sys/fs/binfmt_misc proc/sys/fs/binfmt_misc none bind,optional 0 0

# Default seccomp policy is not needed for unprivileged containers, and
# non-root users cannot use seccmp without NNP anyway.
lxc.seccomp =

一般ユーザでのコンテナの起動

一般ユーザでコンテナを起動するには、ユーザ権限で書き込みできるcgroupが存在している必要があります。ディストリビューションによって、このようなcgroupが自動的に作成される場合も、自分で作成する必要がある場合もあります。

Ubuntu 14.04 LTS の場合

Ubuntu 14.04 LTSでは、第10回で説明したように、LXCコンテナのcgroupの管理はCGManagerというcgroup管理ソフトで行っています。このCGManagerが一般ユーザ権限のcgroupを準備してくれますので、lxc-createでコンテナが作成できれば、すぐにlxc-startでコンテナを起動できます。

あとでcgroupの制限が有効になっているのかを確認するために、ct01の設定ファイルにcgroupの設定を足しておきます。以下のような2行を追加しました。

$ grep cgroup .local/share/lxc/ct01/config 
lxc.cgroup.cpu.shares = 1000
lxc.cgroup.memory.limit_in_bytes = 512m

それでは起動しましょう。

$ lxc-ls -f
NAME  STATE    IPV4  IPV6  AUTOSTART  
------------------------------------
ct01  STOPPED  -     -     NO         
$ lxc-start -n ct01 -d
$ lxc-ls -f
NAME  STATE    IPV4        IPV6  AUTOSTART  
------------------------------------------
ct01  RUNNING  10.0.3.218  -     NO         

無事実行できましたね。このようにUbuntuだと簡単に一般ユーザ権限でコンテナが実行できます。

この起動したコンテナできちんとcgroupを使ったリソース制限が効いているか確認するために、このコンテナ用のcgroupがどのように作られているか見てみましょう。

CGManagerは新しいマウント名前空間を作成してその中でcgroupfsをマウントしますので、そちらの様子を見てみましょう。他の名前空間に入ってコマンドを実行するにはnsenterコマンドを使うと便利です。しかしUbuntu 14.04 LTSのutil-linuxにはnsenterコマンドは含まれませんので、ここでは他の環境から持ってきて実行しています。

$ sudo ./nsenter -t `pgrep cgmanager` --mount -- \
> ls -F /run/cgmanager/fs/cpu/user/1000.user/3.session
cgroup.clone_children  cpu.cfs_period_us  cpu.stat       tasks
cgroup.event_control   cpu.cfs_quota_us   ct01/
cgroup.procs           cpu.shares     notify_on_release

ご覧のようにcgmanagerが作成したマウント名前空間内の/run/cgmanager/fs/cpu/user/1000.user/3.session以下にct01というディレクトリがあります。1000.user3.sessionはログインユーザやログインセッションによって変化します。

Ubuntu 14.04 LTSでは、以下の例のようにログイン時にPAM経由でsystemd-logindによりユーザのログインセッション用のcgroupが/sys/fs/cgroup/systemd/user以下に作成されます。CGManagerはそのグループをそのまま使っているようです。

$ ls -ld /sys/fs/cgroup/systemd/user/1000.user/3.session/ (ホストOSの名前空間で実行)
drwxr-xr-x 3 ubuntu ubuntu 0 Jan  6 17:01 /sys/fs/cgroup/systemd/user/1000.user/3.session/

では、先に設定したcgroupの設定がct01用のcgroupで反映されているか確認してみましょう。

$ sudo ./nsenter -t `pgrep cgmanager` --mount -- \
> cat /run/cgmanager/fs/cpu/user/1000.user/3.session/ct01/cpu.shares
1000
$ sudo ./nsenter -t `pgrep cgmanager` --mount -- \
> cat /run/cgmanager/fs/memory/user/1000.user/3.session/ct01/memory.limit_in_bytes
536870912

確かに先に設定した通りの値になっています。

以上のように一般ユーザで作成したコンテナがきちんと起動し、cgroupによるリソース制限も有効なのが確認できました。

作成されたコンテナは先に述べたようにアクセスできるユーザやパスワードの設定がされていませんので、必要であればlxc-attachコマンドを使って設定したり、必要なパッケージをインストールしたりしましょう。

一般ユーザで操作可能なcgroup

Plamo Linuxや、その他ログイン時にユーザ権限で操作可能なcgroupが作られないディストリビューションでは 第14回で紹介したように、ユーザが書き込めるcgroupを作成するためにinitスクリプトを作成して対応します。

$ cat /etc/rc.d/init.d/cgroups-mount-user
#!/bin/sh
# mount cgroup filesystems per subsystem for user name space

USERNS="taro hanako"

start() {
  echo 1 > /sys/fs/cgroup/memory/memory.use_hierarchy
  for c in /sys/fs/cgroup/* ; do
    echo 1 > $c/cgroup.clone_children
    for u in $USERNS ; do
      mkdir -p $c/$u
      chown -R $u $c/$u
      if [ ${c##*/} == cpuset ] ; then
        echo 0 > $c/$u/cpuset.cpus
        echo 0 > $c/$u/cpuset.mems
      fi
    done
  done
}

stop() {
  :
}

case "$1" in
start)
  start
  ;;
stop)
  stop
  ;;
restart)
  stop
  start
  ;;
*)
  echo $"Usage: $0 {start|stop|restart}"
  exit 1
  ;;
esac

このスクリプトは

  • memoryサブシステムで階層構造が使えるように設定する
  • cpusetサブシステムで親のcgroupの設定を子に引き継ぐように設定する
  • ユーザ名のcgroupを作成し、chownする
  • cpusetサブシステムが使えるようにcpuset.cpusとcpuset.memsに初期値を入れる

という操作を指定したユーザごとに実行しています。

ユーザ用のcgroupが準備できたら、lxc-startを実行するシェルで以下のように実行し、シェルのPIDをtasksに登録します。

$ for d in /sys/fs/cgroup/*/${USER}
> do
> echo $$ > $d/tasks
> done

これで準備ができたのでコンテナを開始します。

$ lxc-ls -f
NAME      STATE    IPV4  IPV6  GROUPS  AUTOSTART  
------------------------------------------------
ct01      STOPPED  -     -     -       NO         
$ lxc-start -n ubuntu01
$ lxc-ls -f
NAME      STATE    IPV4          IPV6  GROUPS  AUTOSTART  
--------------------------------------------------------
ct01      RUNNING  10.0.100.188  -     -       NO         

まとめ

今回は一般ユーザでコンテナを作成し、実行する設定や方法を説明しました。

Ubuntuだと各ユーザのサブIDを設定し、ネットワーク用の設定ファイルを作成し、ユーザ用のデフォルト設定ファイルを作成するだけで簡単に一般ユーザでコンテナが起動できました。

一般ユーザによるコンテナの話題は今回で終わりです。次回からは、LXCがコンテナイメージを置くためにサポートしているストレージバックエンドの話題を取り上げ、便利にコンテナを使う方法を紹介していきたいと思います。

おすすめ記事

記事・ニュース一覧