背景と理由:午前2時の緊急呼び出し
夜中の2時、ナイトスタンドの上でスマホが震える音は、私が最も恐れる音の一つになりました。Prometheusが、セカンダリのアプリサーバーでCPU使用率が98%に達しているというアラートを飛ばしていました。その数時間前、5,000件に及ぶSSHブルートフォース攻撃の波をプロンプトでブロックしたばかりだったので、私はすでに神経を尖らせていました。
今回の脅威は内部からでした。攻撃者が古いPHPアプリの遠隔コード実行(RCE)の脆弱性を突いたのです。Dockerデーモンが root で動作していたため、この攻撃によってコンテナエスケープが発生し、攻撃者は私のホストシステム上でフル権限を持つシェルを手に入れてしまいました。
この一件で、私は手痛い教訓を学びました。多くの管理者はDockerをインストールし、自分のユーザー名を docker グループに追加して、それで仕事は完了だと考えています。しかし、ここに落とし穴があります。通常、Dockerデーモン(dockerd)は全能のrootユーザーとして実行されます。もしハッカーがコンテナを突破すれば、アプリだけでなくサーバー全体が支配されてしまいます。Rootless Dockerはこの根本的な欠陥を解決します。
Rootless Dockerとは正確には何か?
Rootlessモードでは、Dockerデーモンとコンテナの両方を、標準的な非特権ユーザーとして実行できます。これにより、デーモンの周囲に事実上の壁が構築されます。たとえコンテナが侵害されてエスケープが発生したとしても、攻撃者は低レベルユーザーの権限内に閉じ込められたままになります。彼らはシステムファイルに触れることも、他のサービスへ攻撃を広げることもできません。この仕組みは、内部UIDの範囲をホスト上の安全な非特権範囲にマッピングする user_namespaces によって実現されています。
インストール:下地を作る
これをセットアップするには、クリーンな状態から始める必要があります。標準のDockerサービスがすでにシステムソケットを占有している場合、Rootlessモードを簡単に実行することはできません。あの夜の緊急復旧作業中、私は名前空間の分離に必要な特定の構成ツールがホストに用意されているかを確認しなければなりませんでした。
前提条件
UbuntuおよびDebianユーザーには、newuidmap と newgidmap が必要です。これらがないと、Rootlessセットアップは名前空間内に複数のユーザーIDをマッピングできず、プロセスは即座に失敗します。
# マッピングツールとdbus-user-sessionをインストール
sudo apt-get update
sudo apt-get install -y uidmap dbus-user-session
ユーザーにはUIDの割り当てが必要です。/etc/subuid と /etc/subgid を確認し、アカウントに定義された範囲があることを確認してください。私の deploy ユーザーには、複雑なマルチコンテナスタックに十分な余裕を持たせるため、65,536個のID範囲を割り当てました。
# 現在のsubuidの範囲を確認
grep $(whoami) /etc/subuid
# 空の場合は、手動で追加('user'を自分のユーザー名に置き換えてください)
# sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)
インストールスクリプト
Dockerは、主要な処理を自動で行うシェルスクリプトを提供しています。これはコンテナを管理する特定のユーザーとして実行してください。ここでは絶対に sudo を使わないでください。
# 一般ユーザーとしてRootlessインストールスクリプトを実行
curl -fsSL https://get.docker.com/rootless | sh
スクリプトはバイナリを ~/bin にダウンロードし、ホームディレクトリ下の ~/.config/systemd/user/ にsystemdサービスファイルを作成します。
設定:永続化させる
スクリプトが出力する環境変数を無視しないでください。今すぐターミナルを閉じると、Rootlessソケットの場所が分からないため、docker ps は「コマンドが見つからない」または接続エラーを返します。
環境変数
これらの行を .bashrc または .zshrc ファイルに追加してください。以前、リモートサーバーでこれをスキップしてしまい、実際には正常に動いているデーモンを「見当たらない」と10分間も格闘する羽目になりました。
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
変更を即座に適用します:
source ~/.bashrc
永続化の有効化(Linger)
通常、ユーザーレベルのサービスはログアウトした瞬間に終了します。これは本番環境では致命的です。切断した後もコンテナをオンラインに保つ必要があります。loginctl を使用して、ユーザーセッションをバックグラウンドで「居残り(linger)」させます。
# アクティブなSSHセッションがなくてもユーザーサービスが実行されるようにする
sudo loginctl enable-linger $(whoami)
サービスの管理
Dockerの管理に --user フラグが必要になります。これは少し意識の切り替えが必要です。sudo systemctl の代わりに、自分自身のサービスドメインの主権を持つことになります。
# Rootless Dockerデーモンを起動
systemctl --user start docker
# 起動時に自動開始するように設定
systemctl --user enable docker
検証とモニタリング
検証こそが、rootで実行されていないことを確信する唯一の方法です。偽りの安心感ほど恐ろしいものはありません。
Rootlessデーモンのテスト
docker info を実行し、「Security Options」セクションを注意深く確認してください。
docker info | grep -i rootless
rootless: true と表示されるはずです。もう一つの簡単なテストは、ポート80へのバインドを試みることです。Rootless Dockerは、特別な設定なしでは1024番未満のポートを使用できません。これはバグではなく機能です。非特権ユーザーが標準のWebトラフィックを乗っ取るのを防ぐためです。
# Rootlessが正しく設定されていれば、これは必ず失敗します
docker run -p 80:80 nginx
モニタリングとログ
/var/log/docker.log のことは忘れてください。ログはユーザーレベルの journald で管理されるようになります。何か問題が発生したときは、まずここを確認します:
# Rootlessデーモンのリアルタイムログを表示
journalctl --user -u docker.service -f
トレードオフ
私のサーバーは格段に安全になりましたが、いくつかの注意点もあります。Rootlessモードはネットワークに slirp4netns を使用します。うまく機能しますが、標準のブリッジと比較してネットワークスループットが10〜15%低下することを覚悟してください。さらに、UIDマッピングがネットワークファイルシステムとうまく適合しないことがあるため、NFS共有のマウントが複雑になります。
それだけの価値はあるでしょうか?間違いなくあります。インターネットに公開するアプリをRootlessモードに移行して以来、私はよく眠れるようになりました。もし攻撃者が侵入しても、彼らは低権限のサンドボックス内に留まり、私のホストは無傷のままです。セキュリティとは「ハックされないこと」ではなく、侵害が発生した際に被害を厳格に封じ込めることなのです。

