📖

Rootless dockerデーモンから学ぶ、プロセスID/ユーザーID、そしてユーザーネームスペースの関係性、彼らは運命共同体だった・・

に公開

初めに

あえてDocker Desktopを使わず、Rootless Dockerで運用しているぜ
なんて、ちょっと尖ったエンジニアに出会ったことはありませんか?

私はというと、社内の先輩から「Rootless入れてみて」と言われて導入したクチですが、使ってみると、これは間違いなく学ぶ価値のある仕組みだと実感しました。

今回は、そんなRootless Dockerを使ってハマったエラーから、「プロセス」や「ユーザーネームスペース」について深掘りした学習ログをまとめます。

rootless dockerのメリット

まず、Rootless Dockerのメリットを簡単にまとめると:

  • Desktopに比べ、容量が段違いに少ないこと
    • Desktopは3~4GB
    • rootlessは30KB
  • root権限が不要でセキュア
    • 実行ユーザー権限で動作するため、OS全体に影響を与えにくい

この投稿の発端、それは権限エラーとの出会いでした。

まず、Rails8とbootstarpの学習をしたく、docker composeのためのファイルを作成していました。

ERRO[0041] error waiting for container: context canceled
Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "./entrypoint.sh": permission denied: unknown

おやおや、entrypoint.sh の実行権限不足とのこと。
やれやれ😥 chmod x+ entrypoint.shっと・・・
ちょっと待った~とchatgpt君が私の歩を止めました。

なんでそれやるの???

うーーーん、権限エラーと言われたから?

なら、実行ユーザーだけに配布すればよくないか?
今回は、ファイル作成者がdockerを動かすのだから、作成者・グループ・他ユーザーに付与は必要なくないか?

うーーーん、シラン・・・。

ということで、本題へ。

ファイルの作成者 ≠ ファイルの実行者(by Docker)

なぜなら、dockerコマンドを動かす際の、実行ユーザーはファイル作成者ではないから。
ここがかなりミソ。もうカニみそです。
お子様がみんな嫌いで、大人になればなるほど、味を理解するアレです。

なぜdockerコマンドを動かす際の、実行ユーザーはファイル作成者ではないのか

前提として、下記の情報のインプットが必要です。

  • プロセス
  • ユーザーネームスペース

プロセスとは

簡単に言うと、

プロセス = 実行中のプログラムの“実体”

コマンドを叩くと、裏で何かしらのバイナリが起動され、それがメモリ上で「プロセス」として動きます。このとき、カーネルがプロセスID(PID)を割り当てて、プロセスを管理しています。

ユーザーネームスペースとは

簡単に言うと、

そのプロセスだけのためのユーザーIDマッピングの空間

Rootless Dockerは、Linuxの「ユーザーネームスペース(User Namespace)」機能を使って、ホストOSと異なるUIDを仮想的に割り当てた空間でプロセスを実行しています。

このネームスペースとは、ホストOSがもつユーザーID(uid)が、プロセスを実行する際に、プロセス間で採番される疑似的uidです。
要するに、ホスト内のuidとプロセス内の疑似uidのマッピングを管理する空間です。

たとえば、ホストOS内で、docker rootlessを使ってまーす。という方がいたとしましょう。
docker xxxxをした際には、下記の空間が生成されます。

[ ホストOS空間 ]
  └─ UID 0 (root)
  └─ UID 1000 (あなた)
  └─ ...
     └─ [ Rootless Dockerデーモンの空間 ]
           └─ USER NS: UID 0(仮想root)
           └─ マッピング: 0〜65535 → 100000〜165535
           └─ 他のUID(たとえば1000)はマッピングされてない

ホストOS内では1000のあなたはrootless dockerデーモン内では、uidは1000ではありません。
別のuidとなります。ここが大事です。rootless dockerがセキュアと称される特大重要ポイントです。

冒頭に話した、作成者が実行しているのに権限エラーとなるのは、dockerプロセス内で栽培されたあなたのuidはホストOSから見れば、誰やねん状態なんです。だから権限エラーとなる。

そもそものプロセス立ち上がりまでの流れ

# プロセス下準備作成(=型を作るとでもいうのかな?)
clone(..., CLONE_NEWUSER | CLONE_NEWNS, ...); or unshare --user --fork --pid --mount-proc bash
↓
# 作成したプロセスにユーザーネームスペースを作成。(これがいわゆるuidmapかな?)
newuidmap <プロセスID> <ネームスペース内のUID> <ホストOS上のUID> <数>
↓
# 作成したプロセスの実行
setuid(0)
exec bash

プロセスを動かすための下地を作り、そこにマッピングとなるユーザーネームスペースを作り、最後にプロセス実行。という流れ。

ユーザーネームスペースを作成の際のコマンドについて、
<プロセスID>は作成したpid。
<ネームスペース内のUID>は対象プロセスの疑似的に採番するuidの開始番号。
<ホストOS上のUID>はホスト上のuidの開始番号。
<数>は疑似的に採番するuidの数。
例として挙げるならば、

newuidmap 12345 0 100000 65536
<プロセスID> -> 12345
<ネームスペース内のUID> -> 0
<ホストOS上のUID> -> 100000
<数> -> 65536

つまり、
・ネームスペースの中では「UID 0〜65535」は使える
・でもそれはホスト側では「UID 100000〜165535」にマッピングされる

参考文献

Rootless Docker公式ドキュメント
Linux Namespacesについての解説

Discussion