https://github.com/krallin/tini
- コンテナ用の
超軽量initプロセス - 一言でいうと「コンテナの中に本物の
PID 1(init)を立ててあげるツール」
前提: なぜコンテナはPID 1問題を抱えるのか
- 通常のLinuxマシンでは
PID 1はinit(systemdなど)で、以下を専門に担当している- シグナルの正しい転送・処理
- 親を失った孤児プロセスを引き取り、終了時に
wait()で回収する
- コンテナではこの
initを省略し、アプリ自身がPID 1になるケースが多い- しかしアプリは「自分がinitになる」ことを想定して作られていないため、上記の役割を果たせない
役割(何を代行するか)
- シグナルの転送:
docker stop(SIGTERM)などを受け取り、子プロセス(実際のアプリ)へ正しく転送する - ゾンビプロセスの回収(reap): 孤児化した子プロセスを引き取り、
wait()して片付ける
よくある使い方
- Dockerに
tiniが組み込み済みなので、--initフラグを付けるだけでも良いdocker run --init myimage- このフラグを付けると、Dockerエンジンが
docker-init(静的リンクされたtini相当)を自動的に注入してPID 1にする - イメージ側(
myimage)は何も変更しなくてよい。Docker側が「docker-init→ アプリ」という親子関係を作ってくれるdocker run --init myimage │ ▼ Dockerが docker-init(tini相当) を注入し、PID 1として起動 │ fork + exec ▼ docker-init(PID 1) ──▶ your-app(子プロセス) │ └─ SIGTERM転送・ゾンビ回収を代行 - ただしこれは
docker runのフラグなので、起動コマンドを自前で指定できないCloud Run等では使えない(→ 後述のENTRYPOINT方式が必要)
- このフラグを付けると、Dockerエンジンが
- Dockerfileで明示的に
ENTRYPOINTに指定する方法もよく使われる(アプリはCMD側に書く)ENTRYPOINT ["/tini", "--"] CMD ["your-app"]
Cloud Runなどdocker runできない環境の場合
- Cloud RunやKubernetesなどは起動コマンドを自前で指定できず、
--initフラグは使えない - その場合は
ENTRYPOINT方式一択。イメージにtiniを焼き込んでおけば、実行環境側の起動方法によらず常にPID 1として動くRUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/* ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["your-app"]
このコマンドの意味
ENTRYPOINTとCMDはexec形式(配列表記)だと連結され、実際には/usr/bin/tini -- your-appというコマンドラインが実行される/usr/bin/tini:tini実行ファイルのフルパス。tiniだけ(フルパスなし)でもPATHが通っていれば動くが、ENTRYPOINTは生死を握る最重要プロセスなのでPATH依存を避けてフルパス指定するのが公式推奨--: 「オプションはここまで」を示す区切り。以降のyour-app側の引数をtini自身のオプションと誤解釈させないための安全策your-app:tiniが実際に起動する対象プロセス(CMD側)
Cloud Runでの内部動作
① Cloud Runがインスタンスを起動
│
▼
② コンテナ起動: /usr/bin/tini -- your-app が実行される
│
▼
③ tini が PID 1 として起動
│ fork + exec
▼
④ your-app が子プロセスとして起動(リクエスト処理を開始)
│
▼
⑤ Cloud Runがインスタンスを停止する時
Cloud Run ──SIGTERM──▶ tini(PID 1) ──転送──▶ your-app
│ graceful shutdown
▼
exit
│
tini が wait() で回収
▼
tini も同じ終了コードでexit
▼
コンテナ終了 ✅
tiniはアプリをexec置換するのではなくforkで子として起動し、自分はPID 1に居座り続ける- これにより
SIGTERMを正しく中継でき、your-appが生成する孫プロセスのゾンビ化も防げる
実際に確認してみる
1. --initの有無でプロセスを比較
docker run -d --name til-no-init alpine sleep 300
docker exec til-no-init ps
# PID 1 = sleep 300 (initなし=アプリ自身がPID 1)
docker run -d --init --name til-with-init alpine sleep 300
docker exec til-with-init ps
# PID 1 = /sbin/docker-init -- sleep 300
# PID 7 = sleep 300 (initあり=docker-initが挟まる)
| PID 1 | 子プロセス | |
|---|---|---|
--initなし | sleep 300 | なし |
--initあり | /sbin/docker-init -- sleep 300 | sleep 300 |