docker ps の出力を1列ずつ解剖
コンテナが落ちた、外から繋がらない。docker psの7列を正しく読めば原因はその場で絞れる。STATUSの終了コードとPORTSの矢印の向きを軸に、docker statsまで含めて現場の読み方を身につける。
- docker psが既定で見せるのは実行中だけで、停止済みには-aが要る。STATUSのUp 3 hoursは起動後の経過時間、(healthy)はHEALTHCHECKを定義したコンテナだけに現れる。
- Exited (0)は正常終了、128超はシグナル終了の合図だ。128+シグナル番号の規則で137はSIGKILL、143はSIGTERM。137ならOOM Killerか停止猶予切れを疑う。
- PORTSは矢印の左がホスト、右がコンテナで、ホスト側に届いた通信がコンテナ側へ転送される向きを示す。矢印が無い表記はEXPOSEの宣言だけ、空欄はそれすら無い状態で、どちらもホストや外部からは到達できない。
このコマンドは何を見せているか
docker ps は、ホスト上のコンテナを1行1件で並べるコマンドだ。正式には docker container ls の別名で、名前は Unix の ps に由来するが、並べる単位はプロセスではなくコンテナである。
前提は2つ。既定で出るのは実行中のコンテナだけで、落ちたものは -a を付けない限り現れない。そしてこの出力は、イメージ側の設計と実行時の指定の答え合わせだ。IMAGE と COMMAND はイメージを作った側の決定を、PORTS は docker run や Compose が下した決定を映す。イメージ側の設計はDockerfileを1行ずつ解剖に譲り、本稿は出た文字列の読解に徹する。
出力は空白区切りに見えるが実際は固定幅のパディングで、STATUS の Up 3 hours (healthy) のように1つの列の中に空白が入る。awk で切り出すと必ず壊れる。機械的に扱うなら --format で列を指名する。
横にスクロール
1列ずつ解剖
題材は、APIサーバーとPostgreSQL、Redisの3コンテナで動くWebアプリの平常時の出力だ。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3f2a9c1d8e77 myapp/api:1.8.2 "node dist/server.js" 3 hours ago Up 3 hours (healthy) 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp shop-api
b41c7e05aa19 postgres:16-bookworm "docker-entrypoint.s…" 2 days ago Up 12 minutes 127.0.0.1:5432->5432/tcp shop-db
9d5be3f10c42 redis:7-alpine "docker-entrypoint.s…" 2 days ago Up 2 days 6379/tcp shop-cache
CONTAINER ID — 64桁のうち先頭12桁
コンテナIDは16進64桁の識別子だが、既定では先頭12桁だけが出る。全体を見るなら --no-trunc、逆に引数としては一意に定まる長さまで縮めてよく、他と衝突しなければ 3f2a だけで docker stop も docker logs も通る。IDは停止しても削除するまで残る。
IMAGE — どのイメージから生まれたか
そのコンテナの元になったイメージ。ただし出るのは作成時に指定した参照文字列であり、いま同じ文字列で pull しても同じ中身が来る保証はない。タグは付け替え可能なラベルだからだ。latest では正体をダイジェストで確かめるほかない。
COMMAND — PID 1 になったコマンド
コンテナ内で PID 1 として起動したコマンドで、ENTRYPOINT と CMD を連結した文字列が入る。既定では20文字で切り詰められ、全体を見るには --no-trunc が要る。
読みどころは先頭の1語だ。サンプルの shop-api は "node dist/server.js" で、node が直接 PID 1 になっている。これが /bin/sh -c で始まっていたらイメージ側がシェル形式で書かれた合図で、PID 1 が sh になり docker stop の SIGTERM がアプリまで届かない疑いが立つ。公式イメージに多い docker-entrypoint.sh 経由でも、末尾が exec なら PID 1 は本体に渡る。
CREATED — 起動時刻ではなく作成時刻
コンテナが作られた時点からの相対時間であり、起動時刻ではない。普通は一致するが、止めて start し直したコンテナや再起動ポリシーで蘇ったコンテナでは、CREATED が古いまま Up だけが短くなる。
サンプルの shop-db がそれで、CREATED が 2 days ago なのに STATUS は Up 12 minutes、2日前のコンテナが12分前に起動し直されたと読める。このズレは最近何かがあった証拠だ。
STATUS — 最も情報密度が高い列
STATUS はこの出力の心臓部で、状態と経過時間、終了コードやヘルスチェック結果までを1つの文字列に詰め込む。
Up で始まればコンテナは動いており、後ろの時間は起動からの経過だ。括弧が付くのは HEALTHCHECK を定義したイメージだけで、中身は starting(猶予期間中)、healthy、unhealthy のいずれか。括弧が出ないことは不健康を意味しない。単に検査を定義しておらず、Docker が中身の正否を知らないだけだ。
Exited で始まれば停止済みで、括弧内は PID 1 が返した終了コードである。0 は正常終了、1 はアプリ自身の異常終了。そして128以上には特別な意味がある。128 + シグナル番号、すなわちシグナルで殺された符号だ。137 なら 137 − 128 = 9 で SIGKILL、143 なら 15 で SIGTERM と逆算できる。番号の一覧はポート・シグナル・errno早見表にまとめてある。
Restarting は再起動ポリシーが働いてループ中で、括弧内は直前の終了コードだ。
| STATUSの表示 | 意味 | まず疑うこと |
|---|---|---|
| Up 3 hours | 起動から3時間経過 | CREATEDと差があれば再起動を疑う |
| Up 3 hours (healthy) | HEALTHCHECKが成功している | 括弧が無いのは検査未定義であって不健康ではない |
| Up 3 hours (unhealthy) | HEALTHCHECKが規定回数連続で失敗 | 検査コマンド自体の誤りも多い |
| Exited (0) | 正常終了 | 常駐すべきプロセスなら設計ミス |
| Exited (137) | SIGKILL(128+9) | OOM Killerか停止猶予切れ |
| Exited (143) | SIGTERM(128+15) | docker stopによる通常停止 |
| Restarting (1) | 再起動ポリシーでループ中 | 括弧内が直前の終了コード |
PORTS — 矢印はホストからコンテナへ
ネットワーク到達性の答えが書いてある列で、0.0.0.0:8080->80/tcp の読み方さえ固まれば迷わない。矢印の左がホスト側、右がコンテナ側。ホストの8080番に届いた通信がコンテナの80番へ転送される、と読む。番号の大小に関係なく、常に左がホストだ。
到達範囲を決めるのは左側のアドレスである。0.0.0.0 はホストの全アドレスで待ち受ける意味で、経路上の遮断が無ければ外部からも届く。一方 shop-db の 127.0.0.1:5432->5432/tcp はループバックだけに束ねられ、ホストの中からしか繋がらない。同じ5432番でも、左側の1語で公開範囲がまるで違う。[::]:8080->80/tcp は同じ公開のIPv6側で、古い版の :::8080->80/tcp も意味は同じだ。
矢印が無く 6379/tcp のように片側だけの行は性格が異なる。サンプルの shop-cache がそれで、同じユーザー定義ネットワークの shop-api からは6379番に繋がるが、ホストや外部からは繋がらない。
矢印の無い表記はイメージ側の宣言にすぎない。ホストに穴を開けるのは docker run の -p か Compose の ports だけだ。
NAMES — 人間とDNSのための名前
--name を省くと、形容詞と科学者名を繋いだ nostalgic_hopper のような名前が自動生成される。名前は飾りではない。同じユーザー定義ネットワークのコンテナは、Dockerの組み込みDNSで互いの名前を解決できる。shop-api から shop-db という名前だけでPostgreSQLへ繋がるのはこの仕組みによる。
docker stats — 動いている間の数字を見る
docker ps が状態を見せるのに対し、docker stats は動作中のコンテナの資源消費を見せる。既定では画面に居座って更新し続けるので、1回だけ取るなら --no-stream を付ける。
$ docker stats --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
3f2a9c1d8e77 shop-api 2.41% 186.3MiB / 512MiB 36.39% 1.24GB / 3.1GB 12.3MB / 0B 23
b41c7e05aa19 shop-db 0.85% 412.7MiB / 2GiB 20.15% 3.1GB / 1.24GB 88.6MB / 1.4GB 12
9d5be3f10c42 shop-cache 0.12% 8.91MiB / 2GiB 0.44% 40.2MB / 61.7MB 0B / 0B 5
CPU % はホストのCPU全体に対する割合で、100%が上限ではない。4コアで4コア分を使い切れば400%と出る。MEM USAGE / LIMIT は現在の使用量と上限で、--memory を指定していなければ LIMIT にホストの搭載量がそのまま出る。この値がやたら大きい行は、制限していないという意味だ。
NET I/O と BLOCK I/O は、それぞれ受信と送信、読みと書きの累計値である。現在の速度ではないので、速度が欲しければ2回の観測の差分を自分で取る。PIDS はコンテナ内のプロセスとスレッドの数で、--pids-limit と対になる。stats もまた、既定では動作中のコンテナしか見せない。
つまずきやすい点
Exited (137) を見たらメモリを疑う。128+9 の 9 は SIGKILL であり、プロセス側で捕捉できないため、アプリのログには痕跡が残らないまま消える。候補は3つ。コンテナのメモリ制限に当たって cgroup の OOM Killer に殺された、ホスト全体のメモリ枯渇でカーネルの OOM Killer に選ばれた、docker stop の猶予(既定10秒)を過ぎて Docker 自身が SIGKILL を撃った、のいずれかだ。1つ目かは inspect で確定できる。
docker inspect --format '{{.State.OOMKilled}}' shop-worker
true なら制限超過によるOOMで、対策は上限の引き上げかアプリ側の削減だ。false ならホストの dmesg と停止所要時間を順に当たる。
Exited (0) は正常終了である。ただし正常の意味は用途で反転する。バッチなら期待どおりだが、常駐すべきサーバーが Exited (0) で並んでいるなら異常だ。フォアグラウンドで動くべきプロセスがデーモン化して即座に抜けた、といった設計側の問題を示す。
PORTS が空なら外からは繋がらない。コンテナは Up なのに外部から開けない、という相談の大半はこれだ。矢印付きのマッピングが無い以上、ホストから見た入口はどこにも無い。なお停止中のコンテナは publish 済みでも PORTS が空になる。マッピングは動作中しか存在しないからだ。
docker ps は生存者しか見せない。落ちたコンテナを探すのに -a を忘れると、そんなコンテナは無いと誤診する。障害調査の第一手は docker ps -a であるべきで、数が多いなら --filter で絞る。
docker ps -a --filter status=exited --filter name=shop
docker stats の MEM USAGE はアプリの使用量ではない。この値はcgroupの統計から取られ、cgroup v2 では memory.current から非アクティブなファイルキャッシュを引いた数字が出る。裏を返せばアクティブなページキャッシュは含まれたままだ。大きなファイルを読み書きするコンテナは、アプリが余計に確保していなくても上限近くまで膨らむ。メモリリークと即断する前に、ヒープかキャッシュかを切り分けたい。
まとめ
docker ps の7列には、コンテナの素性・状態・到達性が詰まっている。読む順序を決めておけば迷わない。まず STATUS で生死と終了コードを取り、128を超えていればシグナル番号へ逆算する。次に CREATED と Up のズレで再起動の有無を確かめ、PORTS の矢印で外から届くかを判定する。当たりが付いたら docker stats で資源側を裏取りし、必要なら inspect で確定させる。ほかのコマンドの読み方はコマンド出力の読み方から辿ってほしい。
コマンド出力の読み方の記事ガイド
docker ps の出力を1列ずつ解剖を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
コマンド出力
比較で見る軸
難易度: advanced / カテゴリ: コマンド出力の読み方 / タグ数: 4
導入後に効く点
Exited (0)は正常終了、128超はシグナル終了の合図だ。128+シグナル番号の規則で137はSIGKILL、143はSIGTERM。137ならOOM Killerか停止猶予切れを疑う。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- コマンド出力の読み方
- タグ数
- 4
判断チェックリスト
- 自社の用途が「コマンド出力 / Docker」に近いか確認する。
- 強みである「docker psが既定で見せるのは実行中だけで、停止済みには-aが要る。STATUSのUp 3 hoursは起動後の経過時間、(healthy)はHEALTHCHECKを定義したコンテナだけに現れる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。