一般ユーザーからCGroup V2でLXCを使う

(LXC 4.0.2に合わせて記事を更新した)

[Fedra 31](https://www.redhat.com/sysadmin/fedora-31-control-group-v2)でCGroup V2 (Unified CGroup Hierarchy, カーネルオプションに `systemd.unified_cgroup_hierarchy`が付けられて起動)がデフォルトになり、[Ubuntu](https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/1850667)ならびに[Debian](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=943981)でもCGroup V2をデフォルトにしようとしている。CGroup V2で起動されたLinuxホスト上でLXCを使うと

* [RedHatで動かない](https://bugzilla.redhat.com/show_bug.cgi?id=1765821)
* [Debianで動かない](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=944389)

などの報告が寄せられているが、[その対策](https://wiki.debian.org/LXC/CGroupV2)を紹介する。後半では一般（非ルート）ユーザーからCGroup 2でLXCを使う手順を紹介する。LXC 3.0.4 以降、3.2.1以降なら以下の手順で概ね正常にLXCを使える。

# CGroup V2でのLXCトラブルシューティング
## Failed to setup limits for the "devices" controller

```text
ERROR    cgfsng - cgroups/cgfsng.c:cg_legacy_set_data:2415 - Failed to setup limits for the "devices" controller. The controller seems to be unused by "cgfsng" cgroup driver or not enabled on the cgroup hierarchy
ERROR    start - start.c:lxc_spawn:1910 - Failed to setup legacy device cgroup controller limits
```

のようなメッセージがログファイルに残ってLXCコンテナがスタートしない場合、設定ファイルでCGroup V1のデバイスコントローラーを使う設定がされていて、そのようなコントローラーが使えずにエラーが起きている。

```config:/var/lib/lxc/コンテナ名/configの末尾
lxc.cgroup.devices.allow =
lxc.cgroup.devices.deny =
```

を追加するとCGroup V1のデバイスコントローラーを用いなくなるため、コンテナが起動するようになる。最新版では[lxc.cgroup2.devicesという設定項目が使える](https://github.com/lxc/lxc/issues/3183#issuecomment-560138088)。上記の設定はLXC 4.0.2から不要となった。

## Failed to mount cgroup at /sys/fs/cgroup/systemd: Operation not permitted

```text
Failed to mount cgroup at /sys/fs/cgroup/systemd: Operation not permitted
[!!!!!!] Failed to mount API filesystems.
Exiting PID 1...
```

というエラーが出た場合、コンテナ内のsystemdがCGroupファイルシステムのマウントに失敗しているため

```config:/var/lib/lxc/コンテナ名/configの末尾
lxc.init.cmd = /sbin/init systemd.unified_cgroup_hierarchy=1
```

**または**

```config:/var/lib/lxc/コンテナ名/configの末尾
lxc.mount.auto = cgroup:rw:force
```

を追加するとよい。後者の`cgroup:rw:force` はLXCのバージョンが3.0.4や3.2.1あたりだとこの設定をしても駄目で、3.0.5以降や3.2.2以降だとこれで動くようになる。Ubuntu/Debian の場合新しいバージョンのLXCが https://launchpad.net/~ubuntu-lxc/+archive/ubuntu/daily から入手できる。

## libvirt lxc
libvirt 5.6 あたりだと `virt-install --memory 2048  --connect lxc:/ --os-variant fedora31 --filesystem /var/lib/lxc/fedora31/rootfs,/ --network none   --transient --import --name fedora31` とすると `内部エラー: 'devices' cgroup コントローラーのマウントを確認できません` というエラーが出て異常終了するので、libvirt を 5.10 以降に更新するとlibvirt lxcを使えるようになる。

## ホストLinuxから見てコンテナのCGroupが変な位置に置かれる

[CGroupのルートに勝手に階層を作るな](https://github.com/systemd/systemd/blob/master/docs/CGROUP_DELEGATION.md#some-donts) という話があるが、コンテナのCGroupが`/sys/fs/cgroup`直下に作られることがある。それが嫌なら

```config:/var/lib/lxc/コンテナ名/configの末尾
lxc.cgroup.relative = 1
```

とする。そのように設定した上で `systemctl start lxc@コンテナ名` でLXC特権コンテナを起動するとsystemdが推奨するやり方に従うようになる。

## LXD のCGroup 2対応
リリース3.18までは上記の`lxc.mount.auto = cgroup:rw:force` [に対応する設定](#failed-to-mount-cgroup-at-sysfscgroupsystemd-operation-not-permitted)がされておらずコンテナがまともに起動しなかった。[それに対するイシューを上げてクローズされたがクローズされた後もまだ動作せず、それ以降はアホ臭くなったので見ていない](https://github.com/lxc/lxd/issues/6587#issuecomment-565151463)。
LXD 4.0になったら普通に使えることを確認した。

## CGroup 2 リソース制限
設定項目 `lxc.cgroup2.memory.high`, `lxc.cgroup2.pids.max` などで標準的なリソース制限を課すことができる。また`lxc.cgroup.devices.*` と同じ機能が `lxc.cgroup2.devices.*` で使える。またPSI (Pressure Stall Information)の情報を `lxc-cgroup -n コンテナ名 cpu.pressure` などで得ることができる。

# LXCによる非特権コンテナ
基本的には https://linuxcontainers.org/lxc/getting-started/ に書いてある通りですが、CGroup V2 を使うことによる変更点が少しあります。ユーザー名は`testuser`とします。CGroup V2を非特権コンテナで使いたい場合githubの2019年12月以降のLXCでないと**多量のトラブルが生じる(本記事最下部参照)**と思います。

## ユーザーID置換の設定
### newuidmap のインストール
`which newuidmap` で `/usr/bin/newuidmap` が表示されなければインストールする。ubuntu/debian では`uidmap`パッケージを入れる。このステップはrootでも必要。

### /etc/subuid, /etc/subgid の設定
```config:/etc/subuid,/etc/subgid
testuser:置き換えられるIDの最初の数字:IDの個数
```
という行を`/etc/subuid`, `/etc/subgid` に設定する。IDの個数は最低65536は必要。置き換えられるIDの最初の数字はすでに使われているIDと重ならないように65536以上にする。例えば「testuser:100000:65536」とする。このステップはrootでも必要。

### `kernel.unprivileged_userns_clone`
`sysctl kernel.unprivileged_userns_clone` を実行して結果が1では無い場合非ルートユーザーによるUID置換が出来ない。一時的にできるようにするためには `sudo sysctl kernel.unprivileged_userns_clone=1` とする。永続的に設定する方法はディストリビューション依存。

### `lxc-usernsexec` による確認
`lxc-usernsexec` を実行してエラーが出ないことを確認する。

## LXCの設定
### `lxc-usernet`
```config:/etc/lxc/lxc-usernet
testuser veth lxcbr0 10
```
という行を追加する。これはrootでは不要。また `/etc/default/lxc-net` のどこかで `USE_LXC_BRIDGE="true"` がセットされていることを確認する。

### `default.conf`
```config:~/.config/lxc/default.conf
lxc.include = /etc/lxc/default.conf
lxc.idmap = u 0 置き換えられるIDの最初の数字  IDの個数
lxc.idmap = g 0 置き換えられるIDの最初の数字  IDの個数

# 以下はrootから非特権コンテナを開始する場合は不要
lxc.apparmor.profile = unconfined
# rootから（非）特権コンテナを開始するときは下記のようにAppArmorを使える
# lxc.apparmor.profile = generated
```

上記は`/etc/subuid`, `/etc/subgid` と合わせる。rootの場合は上記と同じものを `/root/lxcidmap.conf` に置いておく。

## コンテナイメージの取得
### `lxc-create`
`lxc-create -n コンテナ名 -t download` で非特権コンテナで動くように調整されたコンテナイメージを取得する。btrfs を使っているなら `-B btrfs` を付けるのもよい。rootで非特権コンテナを取得する場合 `-f /root/lxcidmap.conf` を追加すること。そうしないと特権コンテナのイメージを取得してしまうし、設定ファイルも特権コンテナと非特権コンテナで異なるものが初期設定される。

### ディレクトリのパーミッション
`chmod a+x ~/.local ~/.local/share ~/.local/share/lxc` を実行して誰でもLXCのディレクトリにアクセスできるようにする。それが嫌なら`setfacl` を適切に使う。rootから非特権コンテナを起動する場合は `chmod a+x /var /var/lib /var/lib/lxc` とする。

### コンテナrootパスワードの設定
`lxc-execute -n コンテナ名 -- passwd` でコンテナ内のrootパスワードを設定する。

## ホスト上のデバイスファイルへのアクセス
ホスト上のデバイスファイル（例えば `/dev/nvidiactl` など）に非特権コンテナからアクセスするための設定は [この記事](https://qiita.com/kakinaguru_zo/items/5ed24876656d047de465#%E3%83%9B%E3%82%B9%E3%83%88%E5%81%B4%E3%81%A7%E3%81%AE%E4%BD%9C%E6%A5%AD) を参照。

## unified CGroup階層を採用するホストLinuxでのLXCの起動
Fedora 31 以降や、起動オプションに `systemd.unified_cgroup_hierarchy` を付けて起動されたホストLinuxでは普通に`lxc-start` や `lxc-execute` を起動するとそれらのコマンドのプロセスが所属するCGroupを`lxc-start` などが操作できないため、そもそも起動しないとかコンテナがcgroup コントローラーを使用できないなどのトラブルが生じる。この問題への対処は `libpam-cgfs` が伝統的に担ってきたが、unified cgroup hierarchy では原理的に`libpam-cgfs`が動作しない ( https://github.com/lxc/lxc/issues/3198 の議論を参照)。この問題への対応は `systemd-run --user --scope -p "Delegate=yes" コマンド` などを使うことで、例えば

```shell
systemd-run --user --scope -p "Delegate=yes" lxc-start -F -n コンテナ名
```

などとする。rootが（非）特権コンテナを `lxc.cgroup.relative = 1` を付けて起動する場合も同様。上記のコマンドは`lxc-start` が変更してよいCGroupを割り当ててその中で`lxc-start`を動作させるように`systemd`に指示する。

# LXCでCGroup V2 がサポートされているという主張は何だったのか…
* [LXC 3.0.0から](https://discuss.linuxcontainers.org/t/lxc-3-0-0-has-been-released/1449) CGroup V2 対応が謳われ
* [開発者もブログ記事を書いて自慢（I’m excited to announce）](https://cbrauner.wordpress.com/2018/02/12/lxc-lands-unified-cgroup-hierarchy-support/) していたのに
* [LXC 3.0.4でもホストLinuxがCGroup V2 (systemd.unified_cgroup_hierarchy) だとrootがコンテナを起動できない](https://github.com/lxc/lxc/issues/3183)とか
* [同じくLXC 3.0.4で一般ユーザー(非root)が別の要因でコンテナを起動できない](https://github.com/lxc/lxc/issues/3221)とか
* LXC 3.2.1でも[非特権コンテナ](https://github.com/lxc/lxc/issues/3206)**または**[lxc.cgroup.relative=1を付けてsystemctlから起動する](https://github.com/lxc/lxc/issues/3211) とコンテナがCGroup V2のリソース制限を何も利用できないとか
* [lxc-checkpointが全く動作しない](https://github.com/lxc/lxc/issues/3240)とか
* LXC 3.0.4でも3.2.1でも[libpam-cgfsが何も機能していない](https://github.com/lxc/lxc/issues/3198)とか
* [lxc-checkconfigがホストLinuxに何も問題ないのに赤字で警告する](https://github.com/lxc/lxc/issues/3208)とか

いろいろあり過ぎで、いくらなんでもこれで「CGroup V2対応」を謳うのは滅茶苦茶だという印象を持った。かなりムカついたので[開発者に英語で文句を直接ギットハブ上で言った](https://github.com/lxc/lxd/issues/6587#issuecomment-565151463)