Dockerコンテナのトラブルシューティング:よくある起動問題と解決策
終了する、ポートバインドに失敗する、ファイルが見つからない、権限エラーが発生する、メモリ不足で強制終了されるDockerコンテナを診断します。
Dockerコンテナのトラブルシューティング:よくある起動問題と解決策
Dockerコンテナが起動しない場合、最も早い修正は推測を避けることから始まります。コンテナは、ファイルシステム、環境、ネットワーク設定、制限で囲まれた単なるプロセスです。そのプロセスが終了すると、Dockerはその理由を記録します。あなたの仕事は、正しい順序で証拠を収集することです。
私は通常、3つの質問から始めます:Dockerはコンテナを作成したか、メインプロセスは起動したか、プロセスの外部から何かがプロセスを強制終了またはブロックしたか?これらの質問は、間違ったイメージ名と壊れたコマンド、ポート競合とアプリケーションクラッシュ、権限問題とメモリ制限を区別します。
真実を教えてくれる基本的なコマンドから始めましょう:
docker ps -a
STATUS、PORTS、NAMESを確認します。CreatedはDockerがコンテナを作成したが、実際には起動していないことを意味します。Exited (1)は多くの場合、アプリケーションが通常のエラーを返したことを示します。Exited (127)は一般的にコマンドが見つからないことを示します。Exited (137)は多くの場合、プロセスが外部から強制終了されたことを意味し、頻繁にメモリプレッシャーが原因です。これらのコードは手がかりであり、最終的な答えではありませんが、間違ったレイヤーをデバッグすることを防ぎます。
次にログを読みます:
docker logs --tail 100 <container>
docker logs -f <container>
コンテナがすぐに終了する場合、docker logsは同じdocker runコマンドを再実行するよりも通常は有用です。アプリケーションフレームワークは、終了する前に、欠落している環境変数、移行の失敗、無効な設定ファイル、またはバインドエラーを正確に出力することがよくあります。
低レベルの状態を確認するには、コンテナを検査します:
docker inspect <container> --format '{{json .State}}'
docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} error={{.State.Error}}'
2番目のコマンドは覚えておく価値があります。DockerがOOMキルを検出したかどうか、記録された終了コード、ランタイム自体にエラーがあったかどうかを教えてくれます。
コンテナがすぐに終了する場合
コンテナは、メインプロセスが生きている間だけ生き続けます。コマンドが完了すると、Dockerはコンテナを停止します。これは、バックグラウンドでデーモンを起動してから戻るスクリプトを実行するときに人々を驚かせます。
例えば、このパターンはしばしば終了します:
CMD service nginx start
serviceコマンドはnginxを起動してから終了することができます。Dockerはメインプロセスが終了したと見なし、コンテナを停止します。コンテナフレンドリーなパターンは、サーバーをフォアグラウンドで実行することです:
CMD ["nginx", "-g", "daemon off;"]
同じ考え方がNode、Python、Java、ワーカープロセスにも当てはまります。CMDまたはENTRYPOINTのコマンドは、実際の作業をバックグラウンドにして終了するランチャーではなく、長時間実行されるプロセスであるべきです。
ログにcommand not found、no such file or directory、またはexec format errorが表示される場合は、イメージをインタラクティブにテストします:
docker run --rm -it --entrypoint sh <image>
一部のイメージ、特にAlpineやdistrolessスタイルのイメージにはbashが含まれていません。bashが存在することがわかっていない限り、最初にshを使用してください。内部に入ったら、ファイルパス、権限、インタプリタを確認します:
ls -l /app
which python || true
head -1 /app/start.sh
スクリプトが存在していても、そのシェバングが欠落しているインタプリタ(例えば、/bin/shしかないイメージで#!/bin/bash)を指している場合、no such file or directoryで失敗することがあります。もう一つの一般的な原因はWindowsの改行コードです。シェルスクリプトがWindowsで編集された場合、見えない\rによってLinuxが/bin/sh\rを探すことがあります。
Dockerがポートは既に割り当てられていると言う場合
ポート競合はホスト側で発生します。-p 8080:80では、8080がホストポート、80がコンテナポートです。ホストポート8080で既に何かがリッスンしている場合、Dockerはバインドできません。
bind: address already in useやport is already allocatedのようなエラーが表示されることがあります。リスナーを見つけます:
sudo lsof -i :8080
# または
sudo ss -ltnp 'sport = :8080'
macOSでは、lsofが通常最も簡単です。Linuxサーバーでは、ssがデフォルトで利用可能なことがよくあります。Windows PowerShellでは、次を使用します:
Get-NetTCPConnection -LocalPort 8080
次に、別のホストポートを選択するか、それを所有するサービスを停止します:
docker run -d -p 8081:80 nginx
コンテナ内のアプリケーションが実際にその新しいポートでリッスンしていない限り、コンテナポートを変更しないでください。nginxがコンテナ内でポート80でリッスンしている場合、-p 8081:80が正しいです。-p 8081:8081は、コンテナ内の何もポート8081でリッスンしていない場合、ブラウザから失敗します。
アプリは起動するが設定が見つからない場合
多くの起動失敗は、欠落している環境変数が原因です。イメージは問題なく、コマンドも問題ありませんが、アプリはDATABASE_URL、REDIS_URL、APIキー、または設定ファイルを期待しています。
Dockerが渡したものを確認します:
docker inspect <container> --format '{{range .Config.Env}}{{println .}}{{end}}'
Composeプロジェクトの場合、docker-compose.ymlだけを読むのではなく、解決された設定を検査します:
docker compose config
これにより、インデントの間違い、.envファイルの驚き、空の文字列に展開された変数がキャッチされます。実際の例:DATABASE_URL=${DATABASE_URL}は無害に見えますが、シェルまたは.envファイルがそれを定義していない場合、アプリケーションは空の値を受け取り、起動中に失敗する可能性があります。
ログやターミナル履歴のシークレットに注意してください。迅速なローカルデバッグには、-e NAME=valueを渡すことで問題ありません。共有システムでは、プラットフォームのシークレットメカニズムまたは制御された権限を持つ環境ファイルを使用してください。
バインドマウントまたはボリュームが権限エラーを引き起こす場合
コンテナは、設定ファイルを読み取れない、PIDファイルを書き込めない、キャッシュディレクトリを作成できない、データベースディレクトリを初期化できないために起動時に失敗することがあります。ログには通常、permission denied、read-only file system、またはoperation not permittedと表示されます。
最初にマウントを検査します:
docker inspect <container> --format '{{json .Mounts}}'
次に、コンテナがどのユーザーとして実行されているかを確認します:
docker inspect <container> --format 'user={{.Config.User}}'
userが空の場合、イメージはデフォルトでrootとして実行される可能性がありますが、多くの本番イメージは非rootユーザーを設定します。ローカルのUIDが所有するホストディレクトリは、コンテナ内のUID 1000、1001、またはサービス固有のユーザーによって書き込み可能でない場合があります。
実用的なデバッグシーケンスは次のとおりです:
ls -ld ./data
docker run --rm -it -v "$PWD/data:/data" --entrypoint sh <image>
id
ls -ld /data
touch /data/test
すべての権限問題をchmod 777で解決することは避けてください。それは当面の問題を隠しながら、より悪い問題を引き起こす可能性があります。所有権を一致させるか、アプリケーションデータに名前付きボリュームを使用することを優先します:
docker volume create app_data
docker run -d -v app_data:/var/lib/app <image>
名前付きボリュームは、バインドマウントが仮想化境界を越え、ネイティブのLinuxファイルシステムとは異なる動作をする可能性があるDocker Desktopで特に便利です。
コンテナがメモリ不足で強制終了された場合
終了コード137は、プロセスがSIGKILLを受け取ったことを強く示唆しています。Dockerの作業では、これは多くの場合、カーネルまたはDocker Desktopがメモリ不足のためにプロセスを強制終了したことを意味します。inspectで確認します:
docker inspect <container> --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}}'
OOMKilledがtrueの場合、2つの仕事があります:プロセスが起動するのに十分なメモリを与えること、そしてなぜそれだけのメモリが必要だったのかを理解することです。制限を引き上げることは、データベースやJVMサービスのための正しい本番修正かもしれません。小さなWebサービスの場合、それは悪いデフォルトを明らかにするかもしれません。
Javaアプリは典型的な例です。古いJVMの動作はコンテナの制限に常にうまく適合するとは限らず、現代のJVMでも予測可能な動作のためには適切な-Xmxまたはパーセンテージベースの設定が必要です。Nodeサービスは、メモリ制約のある環境で--max-old-space-sizeが必要な場合があります。データベースには明示的なキャッシュ設定が必要な場合があります。
一回限りのテストの場合:
docker run --memory=1g <image>
Docker Desktopを使用している場合は、Docker VMに割り当てられたメモリも確認してください。VM自体が不足している場合、コンテナの制限は役に立ちません。
イメージがプルされない、またはビルドがイメージを生成しなかった場合
時々、使用可能なイメージがないため、コンテナの問題はありません。docker runがコンテナを作成する前に失敗した場合、イメージを個別に確認します:
docker image ls | grep my-app
docker pull my-registry/my-app:tag
プライベートレジストリの場合は、認証を確認します:
docker login <registry>
ローカルイメージの場合は、実行するタグがビルドしたタグであることを確認します:
docker build -t my-app:dev .
docker run --rm my-app:dev
よくあるローカルの間違いは、my-app:devをビルドしてmy-app:latestを実行することです。これは古いイメージまたは何も指していない可能性があります。
ネットワークが非難されるが、サービスがリッスンしていない場合
ブラウザがコンテナに到達できない場合、人々はしばしばDockerネットワーキングに飛びつきます。まず、アプリケーションがコンテナ内でリッスンしていることを証明します。
docker exec -it <container> sh
ss -ltnp || netstat -ltnp
アプリがコンテナ内で127.0.0.1にバインドされている場合、Dockerのポート公開は役に立ちません。アプリは0.0.0.0またはコンテナのインターフェースアドレスでリッスンする必要があります。これは開発サーバーで一般的です。例えば、多くのフレームワークはデフォルトでlocalhostにバインドし、--host 0.0.0.0のようなフラグが必要です。
次に、公開されたポートを確認します:
docker port <container>
docker ps --format 'table {{.Names}} {{.Ports}}'
0.0.0.0:8080->3000/tcpのようなものが表示されることを望みます。公開されたポートがない場合、サービスは同じネットワーク上の別のコンテナからは機能するかもしれませんが、ホストのブラウザからは機能しません。
信頼性のある起動チェックリスト
行き詰まったときは、この順序を使用します:
docker ps -aでコンテナが存在するか、どのように終了したかを確認します。docker logs --tail 100 <container>でアプリケーション自身の不満を読みます。docker inspect <container>で終了コード、OOMステータス、コマンド、ユーザー、マウント、ポートを確認します。docker run --rm -it --entrypoint sh <image>でイメージを手動でテストします。- 一度に一つの変数を削除します:最初にマウントなし、次にカスタムネットワークなし、次に必要な環境変数のみで実行します。
最後のステップが重要です。ポート、ボリューム、envファイル、カスタムDNS、メモリ制限、カスタムエントリポイントを含む長いdocker runコマンドは、疑わしい点が多すぎます。イメージが起動するまで削り、設定を追加して壊れるまで戻します。追加したばかりの設定が、通常、実際の問題が存在する場所です。