トラブルシューティング:一般的なDockerコンテナエラーを素早く診断する方法
この必須ガイドで、迅速なDockerコンテナトラブルシューティングの技術を習得しましょう。コアとなるDockerコマンドを使用して起動障害を診断する構造化プロセスを解説します。`docker ps -a` を使用したクラッシュの特定、`docker logs` を使用した重要な情報の抽出、`docker inspect` を使用した高度な設定分析の方法を詳しく説明します。この記事では、終了コード127エラー、ポート競合、OOMKilledイベントなど、頻繁に発生する問題に対する実践的な例と的を絞った解決策を提供し、根本原因を迅速に特定してサービスを復旧できるようにします。
トラブルシューティング:一般的なDockerコンテナエラーを素早く診断する方法
Dockerコンテナがすぐに終了してしまう場合、イメージを再構築したり、ランダムなフラグを変更したりしてはいけません。まず、Dockerが把握している情報(コンテナの状態、終了コード、ログ、Dockerが実行しようとした正確なコマンド)を確認することから始めましょう。これらの4つの情報で、通常は問題を素早く絞り込めます。
コンテナとは、周囲に隔離機能を持つプロセスです。メインプロセスが終了すると、コンテナも終了します。これは、クラッシュ、実行ファイルの欠落、バッチジョブの完了、ヘルスチェックの依存関係の失敗、またはメモリを使いすぎたためにカーネルがプロセスを強制終了した場合などが考えられます。以下のコマンドは、これらのケースを区別するのに役立ちます。
まず停止したコンテナを見つける
docker ps は実行中のコンテナのみを表示します。起動に失敗したコンテナは、通常、すべてのコンテナを表示するように指定しないと非表示になります。
docker ps -a
STATUS、COMMAND、NAMES を確認します。
CONTAINER ID IMAGE COMMAND STATUS NAMES
2d3f4b5c6e7a my-app:latest "/usr/bin/start" Exited (127) 2 minutes ago web-service
91aa34c0db22 worker:latest "python worker.py" Exited (0) 10 minutes ago nightly-worker
Exited (0) は、多くの場合、プロセスが正常に完了したことを意味します。これは、1回限りのジョブでは正常です。Webサービスの場合、コマンドがフォアグラウンドで実行され続ける代わりに、実行されて完了したことを意味する可能性があります。
ゼロ以外の終了コードは障害を示しますが、最終的な答えではなく手がかりとして扱ってください。終了コード 127 は、通常、コマンドが見つからないことを意味します。126 は、通常、見つかったが実行できないことを意味します。137 は、多くの場合、プロセスがSIGKILLを受信したことを意味します。コンテナでは、これはメモリプレッシャーに関連していることがよくありますが、常にそうとは限りません。必ずログとinspect出力で確認してください。
何かを変更する前にログを読む
Dockerは、デフォルトのロギングドライバーに対して、コンテナのメインプロセスからの標準出力と標準エラー出力をキャプチャします。次のコマンドを使用します。
docker logs web-service
便利なオプション:
docker logs --tail 100 web-service
docker logs --since 15m web-service
docker logs -t web-service
docker logs -f web-service
ログに config file not found と表示されている場合は、マウントと環境を確認してください。アプリケーションのスタックトレースが表示されている場合は、アプリケーションをデバッグしてください。ログが空の場合は、プロセスが出力を生成する前に失敗したか、イメージのエントリポイントが間違っている可能性があります。
クラッシュループの場合、docker logs -f だけをツールとして使用しないでください。状態を把握せずに、障害がアクティブであるかのように感じさせてしまう可能性があります。ログは docker inspect と組み合わせて使用してください。
コンテナの状態を検査する
docker inspect は、大きなJSONドキュメントを返します。そのすべてが必要になることはほとんどありません。フォーマットされたフィールドから始めましょう。
docker inspect -f 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}' web-service
次に、コマンドとイメージの設定を検査します。
docker inspect -f 'entrypoint={{json .Config.Entrypoint}} cmd={{json .Config.Cmd}} user={{.Config.User}}' web-service
エラーにファイルが関係する場合は、マウントを確認します。
docker inspect -f '{{json .Mounts}}' web-service
コンテナがメモリ不足で強制終了された場合、.State.OOMKilled が重要なフィールドです。これが true の場合、メモリを増やすと解決する可能性がありますが、より良い次の質問は、なぜメモリが増加したのかということです。制限を大きくすると、後で障害が発生するまでリークを隠してしまう可能性があります。
可能であればインタラクティブシェルで再現する
イメージにシェルが含まれている場合は、エントリポイントをオーバーライドしてファイルシステムを検査します。
docker run --rm -it --entrypoint /bin/sh my-app:latest
Bashを持つイメージもあります。
docker run --rm -it --entrypoint /bin/bash my-app:latest
内部で、ファイルとコマンドパスを確認します。
ls -l /usr/bin/start
id
env
最小限のイメージにはシェルが含まれていない場合があります。その場合は、イメージが利用可能なツールを使用するか、一時的なデバッグバリアントを再構築するか、Dockerfileとビルド出力を検査します。トラブルシューティングが一度不便だったからといって、デバッグパッケージを本番イメージに恒久的に追加しないでください。
コマンドが見つからない:終了コード127
終了コード 127 は、通常、Dockerが ENTRYPOINT または CMD で指定された実行ファイルを見つけられなかったか、スタートアップスクリプトが見つからないコマンドを実行しようとしたことを意味します。
一般的な原因:
- 実行ファイルがイメージにコピーされていない。
- パスはホスト上では正しいが、イメージ内では正しくない。
- スクリプトが
/bin/bashを使用しているが、イメージには/bin/shしかない。 - コマンドが
PATHに依存しており、PATHが想定と異なる。
イメージのコマンドを確認します。
docker inspect -f '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' web-service
エントリポイントがスクリプトの場合は、そのシバンと改行コードを確認してください。WindowsのCRLF改行コードを持つスクリプトは、インタプリタのパスに実質的にキャリッジリターンが含まれているため、「見つかりません」という紛らわしいメッセージで失敗する可能性があります。
権限が拒否されました:終了コード126またはファイルエラー
終了コード 126 は、多くの場合、Dockerがコマンドを見つけたが実行できなかったことを意味します。スクリプトの場合、ファイルに実行可能ビットが欠落している可能性があります。
COPY start.sh /usr/local/bin/start.sh
RUN chmod 0755 /usr/local/bin/start.sh
ENTRYPOINT ["/usr/local/bin/start.sh"]
ボリュームマウントされたファイルの場合、ホストの権限が適用されることに注意してください。コンテナがUID 1000で実行され、ホストディレクトリがrootによって所有されており書き込み権限がない場合、「Docker内」にあるという理由だけでコンテナはそこに書き込むことはできません。
ランタイムユーザーを確認します。
docker inspect -f 'user={{.Config.User}}' web-service
空白の場合、多くのイメージはデフォルトでrootとして実行されますが、すべてがそうとは限りません。公式イメージやセキュリティが強化されたイメージは、多くの場合、非rootユーザーを使用します。
ポートがすでに割り当てられている
バインドエラーは、通常、使用中のホストポートを公開しようとしたときに発生します。
docker run -p 8080:80 nginx
Dockerは bind: address already in use のようなメッセージを報告する場合があります。競合を見つけます。
docker ps --format 'table {{.Names}}\t{{.Ports}}'
lsof -iTCP:8080 -sTCP:LISTEN
次に、競合しているプロセスを停止するか、別のホストポートを選択します。
docker run -p 8081:80 nginx
コンテナポートは同じままにできます。ホストポートはコロンの前の部分です。
ファイルの欠落とマウントの不良
ログに設定ファイルが見つからないと表示されている場合は、アプリケーションが期待するものとDockerがマウントしたものを比較します。
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
よくある間違いは、イメージ内にすでにファイルがあったパスにホストディレクトリをマウントすることです。マウントは、その宛先にあるイメージのコンテンツを隠します。イメージに /app/config/default.yml が含まれており、空のホストディレクトリを /app/config にマウントすると、デフォルトファイルはコンテナのビューから消えます。
また、相対パスも確認してください。-v ./config:/app/config は、Dockerfileがあるディレクトリではなく、docker run を実行したディレクトリに依存します。
ヘルスチェックの失敗は必ずしもコンテナのクラッシュではない
コンテナは実行中であっても、異常な状態である可能性があります。
docker ps
Up 2 minutes (unhealthy) と表示される場合があります。ヘルスチェックの出力を検査します。
docker inspect -f '{{json .State.Health}}' web-service
ヘルスチェックが失敗するのは、多くの場合、アプリが別のポートでリッスンしている、127.0.0.1 のみにバインドしている、起動にヘルスチェックが許可するよりも時間がかかる、またはデータベースの準備がまだできていないためです。異常なコンテナと終了したコンテナを混同しないでください。診断パスは異なります。
迅速なトラブルシューティング手順
迅速に答えが必要な場合は、次の順序を使用します。
docker ps -aでコンテナと終了コードを見つける。docker logs --tail 100 <name>でアプリケーションエラーを読む。docker inspect -f ...で状態、コマンド、ユーザー、マウントを確認する。- コマンドまたはファイルシステムが疑わしい場合は、イメージ内で一時的なシェルを実行する。
- ポートとマウントされたディレクトリの権限についてホストの競合を確認する。
- 問題がイメージのコンテンツ、ランタイムフラグ、アプリケーション設定のいずれであるかを確認した後にのみ再構築する。
この手順により、調査の根拠を確かなものにできます。Dockerは通常、十分な証拠を持っています。コツは、状況を変える前にそれを読むことです。
表示されている内容を信頼する前に再起動ポリシーを確認する
再起動ポリシーにより、コンテナが常に失敗しているように見えたり、常に回復しているように見えたりする可能性があります。確認します。
docker inspect -f 'restart={{json .HostConfig.RestartPolicy}}' web-service
ポリシーが always または unless-stopped の場合、Dockerはクラッシュするたびにコンテナを再起動する可能性があります。docker ps では、数秒間実行されてから再び再起動しているように見える場合があります。その場合は、タイムスタンプ付きのログを使用し、再起動回数を検査します。
docker inspect -f 'restarts={{.RestartCount}} started={{.State.StartedAt}} finished={{.State.FinishedAt}}' web-service
再起動回数が多い場合は、通常、メインプロセスがすぐに終了することを意味します。修正方法は、「再起動ポリシーを変更する」ことではありません。ポリシーは、根本的な障害を明らかにしているにすぎません。
ビルド時の問題と実行時の問題を区別する
コンテナ内でファイルが見つからない場合は、いつ表示されるべきだったかを考えてください。Dockerfileでコピーされたファイルはビルド時の問題です。-v またはComposeボリュームでマウントされたファイルは実行時の問題です。
ビルド時の確認:
docker image inspect my-app:latest
docker run --rm --entrypoint /bin/sh my-app:latest -c 'ls -la /app'
実行時の確認:
docker inspect -f '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' web-service
この分割により時間を節約できます。イメージを再構築しても、ホストマウントの不良は修正されません。ボリュームフラグを変更しても、バイナリをコピーしなかったDockerfileは修正されません。
環境変数とシークレットは静かに失敗する可能性がある
多くのアプリケーションは、必須の環境変数が欠落しているために終了しますが、Dockerエラーにはプロセスがコード1で終了したとしか表示されません。設定された環境を注意深く検査します。
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service
このコマンドを実行する場所には注意してください。シークレットが出力される可能性があります。共有ログでは、変数名のみを出力します。
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' web-service | sed 's/=.*//'
--env-file を使用する場合は、CRLF改行コード、引用符で囲まれていないスペース、ファイルの欠落を確認してください。Docker envファイルは完全なシェルスクリプトではありません。KEY=value 行、Dockerバージョンでサポートされている場合はコメント、ファイル内でシェル展開が行われるという想定は避け、シンプルに保ちます。
出荷前の実践的なレビュー
スクリプトまたはコンテナのセットアップが完了したと呼ぶ前に、午前2時にデバッグしなければならない次の人の立場になって一度読んでみてください。そうすることで、気づく点が変わります。スクリプト作成時に意味があったプロンプトが、CIログに表示されると曖昧になる可能性があります。明白に思えたDockerサービス名が、アプリケーションの変数名と一致しない可能性があります。開発には安全で本番には危険なBashのデフォルトがあるかもしれません。
私は、意図的に扱いにくい値を使って短いドライランを行うのが好きです。スペースを含むパスを使用します。空のオプション値を使用します。ダッシュで始まるファイル名を試します。異なる作業ディレクトリからスクリプトを実行します。1つの予想される環境変数なしでコンテナを起動します。これらのテストは派手ではありませんが、通常最初に壊れる前提条件を捉えます。
また、失敗メッセージも確認してください。出力が failed だけの場合、記事のアドバイスは実装に反映されていません。有用な失敗メッセージは、使用された値、失敗したチェック、オペレーターが変更できる内容を示します。これは、すべての環境変数をダンプしたり、シークレットを出力したりすることを意味しません。具体的な情報が役立つ場合に具体的にすることを意味します。設定パス、欠落しているコマンド名、ネットワーク名、サービスホスト名、プロセスがバインドしようとしたポートなどです。
最後の習慣は、例をシステムが実際に実行される方法に近づけることです。本番環境でComposeを使用している場合は、Composeでテストします。スクリプトがsystemdによって起動される場合は、systemdまたは同様に最小限の環境でテストします。コマンドがコピー&ペーストしても安全であるべき場合は、引用符、-- セパレーター、および検証を例自体に含めます。読者は、警告よりも動作するパターンをコピーすることがよくあります。
このレビュープロセスは官僚主義ではありません。これは、小さな自動化を退屈なものに保つ方法です。退屈さとは、シェルプロンプト、設定ローダー、変数展開、コンテナ診断、Dockerネットワーキングに求めるものです。動作の驚きが少なければ少ないほど、次のオペレーターがそれを信頼しやすくなります。
Dockerの障害については、可能であればコンテナを作成した正確なコマンドを保存してください。docker inspect は現在の設定を表示できますが、元の docker run コマンド、Composeサービス、イメージタグ、envファイル名を含むインシデントノートは、後で再現するのがはるかに簡単です。深刻なデバッグ中は latest の使用を避けてください。移動するタグは、ログを読んでいる間にイメージが変更されるため、1つの障害を2つの異なる調査に変える可能性があります。
本番環境に近い問題をローカルで診断している場合は、同じイメージダイジェストまたはタグをプルし、同じボリュームレイアウトを使用し、同じ非シークレット環境形状を渡します。再現は推測に勝ります。