About Portfolio Posts Tags

Incusの非特権コンテナでDockerを動かす

はじめに

IncusでDockerを動かそうとすると、多くの解説記事は「特権コンテナ(security.privileged=true)にすれば動く」で終わってしまいます。

しかし特権コンテナはコンテナ脱獄時のリスクが非特権コンテナと比べ高いためむやみに特権コンテナとすることはセキュリティ上よろしくないです。

この記事では、非特権コンテナのままrootless Dockerを動かす方法を、実際に踏んだエラーとその原因、対処法まで含めて記録します。

手順だけを知りたい方は「セットアップ手順」まで飛んでください。

なお、本記事ではDebian 13のコンテナを用いました。

なぜ非特権コンテナでDockerが素直に動かないのか

Incus(LXC)の非特権コンテナは、コンテナ内のroot権限をホスト側の非特権UIDにマッピングする仕組み(user namespace)で動いています。そのため万が一コンテナ内でroot権限を奪われても、ホストのrootには直結しません。

問題はDockerが動作にroot権限や各種特権を必要とする点です。

非特権コンテナはすでに権限が制限された箱であり、その中でDockerがさらに権限を要求すると制約同士がぶつかりコンテナを立ち上げられない事が多いです。

これを回避する現実的な選択肢が2つあります。

  1. Incus側でnestingを許可し、Docker daemon自体をrootlessモードで動かす(この記事の方法)
  2. Podmanなど、最初からrootless前提で設計されたコンテナランタイムに乗り換える

Dockerのコマンド体系をそのまま使いたい場合は、1番目のrootless Dockerが現実的な落とし所です。

セットアップ手順

1. Incusホスト側の設定

対象コンテナ(以下mycontainerとします)に対して、nestingとsyscallインターセプトを有効化します。

incus config set mycontainer security.nesting=true
incus config set mycontainer security.syscalls.intercept.mknod=true
incus config set mycontainer security.syscalls.intercept.setxattr=true

設定が反映されているか確認します。

incus config get mycontainer security.nesting

trueが返ってくることを確認してから、コンテナを再起動します。

incus restart mycontainer

2. コンテナ内で必要パッケージをインストール

incus exec mycontainer bash
apt update
apt install -y uidmap dbus-user-session fuse-overlayfs slirp4netns kmod iptables

3. subuid/subgidの確認

rootless Dockerはuser namespaceを利用するため、対象ユーザーにsubuid/subgidの割り当てが必要です。

grep <ユーザー名> /etc/subuid /etc/subgid

割り当てがなければ追加します。

usermod --add-subuids 100000-165535 --add-subgids 100000-165535 <ユーザー名>

4. rootless Dockerのインストール

ここで最初のつまずきポイントが出ます。公式インストールスクリプトを素直に実行すると、以下のようなエラーが出ることがあります。

sh: 190: lsmod: not found
grep: /lib/modules/6.12.107+deb13-amd64/modules.builtin: No such file or directory

原因: インストーラーはiptables関連のカーネルモジュール(nf_tablesなど)が使えるかを、コンテナ内の/lib/modules/を見て判定しようとします。しかしIncusコンテナはホストとカーネルを共有しているため、コンテナ内には/lib/modules/配下の実体が存在しません。ホスト側でモジュールがロード済みでも、コンテナの名前空間からは確認する手段がなく、誤検知として警告が出ます。

対処法として、まずホスト側でモジュールがロード済みか確認します。

lsmod | grep nf_tables

ロードされていなければホスト側でロードします。

sudo modprobe nf_tables

ロード済みであることを確認できたら、チェック自体をスキップしてインストールを進めます。

curl -fsSL https://get.docker.com/rootless | SKIP_IPTABLES=1 sh

SKIP_IPTABLES=1は事前診断をスキップするだけで、iptables機能そのものを無効化するものではありません。ホスト側でモジュールがロード済みであれば、コンテナ内のiptables/nftablesコマンドは通常通り機能します。

5. PATHを通す

インストール後、dockerコマンドはデフォルトでPATHが通されてないので通します。

echo 'export PATH=/home/furu/bin:$PATH' >> ~/.bashrc

注意: ~/.bashrcへの追記は対話ログインシェルでのみ有効です。cronやCIツールなど非対話シェルからdockerを叩く場合はこれだけでは効きません。対処法は後述。

6. lingerの有効化

systemd –userでDocker daemonを管理する場合、ユーザーがログアウトすると通常はセッションごとdaemonが終了します。これを防ぐのがlinger設定です。

loginctl enable-linger <ユーザー名>

確認:

loginctl show-user <ユーザー名> | grep Linger

Linger=yesと出れば成功です。

7. 動作確認

docker run hello-world

ここまで完了すれば、非特権コンテナ上でrootless Dockerが動作している状態です。

つまずいたポイントと解決策のまとめ

症状原因対処
lsmod: not found / modules.builtin: No such fileコンテナはホストとカーネルを共有しており、/lib/modules/の実体がコンテナ内にないホスト側でモジュールロードを確認し、SKIP_IPTABLES=1でインストーラーの誤検知を回避
docker: command not found(初回ログイン時)PATH設定が現在のシェルに反映されていない~/.bashrcに追記後、シェルを再読み込みするかログインし直す
Failed to connect to user scope bus via local transportincus exec ... su - <user> -c ...ではPAM経由のsystemd-logind連携(pam_systemd)が正しく発火せず、XDG_RUNTIME_DIRが未定義になるXDG_RUNTIME_DIR=/run/user/<uid>を明示するか、ps auxでプロセス生存を直接確認する
cron/CIからdockerが見つからない~/.bashrcは対話ログインシェル専用で非対話シェルには効かない/usr/local/binにシンボリックリンクを張るか、スクリプト内でPATHとDOCKER_HOSTを明示する

最後のcron対応について、最も確実な方法はシンボリックリンクです。

sudo ln -s /home/<ユーザー名>/bin/docker /usr/local/bin/docker

/usr/local/binは大半のディストリビューションでデフォルトPATHに含まれるため、シェルの種類を問わず解決します。

rootless Dockerはコンテキストがrootlessに切り替わっていないとDOCKER_HOSTの指定が別途必要になる場合もあるため、自動化スクリプトの先頭では以下も明示しておくと事故が減ります。

export DOCKER_HOST=unix:///run/user/1000/docker.sock
export PATH=/home/<ユーザー名>/bin:$PATH

rootless Dockerの制約について

rootless Dockerは万能ではありません。導入前に把握しておくべき制約を挙げます。

  • ネットワークがデフォルトでslirp4netnsを使うため、通常のDockerよりスループットが落ちる
  • 1024番未満の特権ポートを直接バインドできない(ホスト側でnet.ipv4.ip_unprivileged_port_startを緩めるか、rootlesskitのポートフォワーディング機構を経由する必要がある)
  • cgroup v2が前提
  • 一部の高度な機能(特定のカーネルモジュールを要求するコンテナなど)が使えない

開発・検証用途であれば十分実用的ですが、本番トラフィックを捌く用途で使う場合は、事前にポートバインドとネットワーク帯域の検証を行うことをおすすめします。

まとめ

非特権コンテナのままDockerを動かすことは可能ですが、公式インストーラーの誤検知や、Incus execとsystemd –userの相性問題など、いくつかの構造的なつまずきポイントがあります。

今回まとめた原因と対処法が同じ壁にぶつかった方の助けになれば嬉しいです!