Dockerの基礎概念と仕組み
1. Docker と Docker Compose の違い
Docker (Engine / CLI)
- 役割: コンテナを1つずつ作成・実行・管理する技術です。
- コマンド:
docker run,docker build,docker imagesなど。 - 限界: 複数のコンテナ(Webサーバー + DB + WordPress)を連携させる時、毎回長いコマンドでネットワーク接続やボリューム設定を書くのは大変でミスが起きやすいです。
Docker Compose
- 役割: 「複数のコンテナの構成」をコード (
docker-compose.yml) で定義・管理するツールです。 - メリット:
- 一括管理:
docker compose up一発で全コンテナが正しい順序・設定で起動します。 - 再現性: YAMLファイルがあれば、誰でも同じ構成を再現できます。
- ネットワーク自動化: プロジェクト専用のネットワークを自動で作ってくれます。
- 一括管理:
2. Dockerネットワークの仕組み
Docker Infrastructureでは docker-compose.yml でネットワークを定義しています。なぜこれが必要なのでしょうか?
デフォルト (Bridge Network)
コンテナは起動すると、デフォルトで bridge0 という仮想ネットワークに繋がります。 しかし、デフォルトブリッジでは**「コンテナ名での名前解決(DNS)」ができません**。IPアドレスで通信する必要がありますが、コンテナのIPは再起動のたびに変わります。
ユーザー定義ネットワーク (User-defined Bridge)
docker-compose.yml でネットワークを定義すると、Dockerは新しい仮想ブリッジを作成します。 最大の特徴: 自動DNS解決 (Service Discovery)
- 同じネットワーク内のコンテナ同士は、サービス名(例:
mariadb) で通信できます。 - WordPressの設定で「DBホスト:
mariadb」と書けるのは、この機能のおかげです。
3. PID 1 問題とDockerfileベストプラクティス
「PID 1 (Process ID 1)」 は、Linuxシステムで最初に起動するプロセス(init/systemd)です。 役割: システムの初期化と、孤児プロセス(親がいなくなったゾンビプロセス)の回収(reap)。
コンテナ内の PID 1
コンテナ内では、ENTRYPOINT や CMD で指定したコマンドが PID 1 になります。
問題点: シェル形式 vs exec形式
Dockerfileでの書き方によって、挙動が変わります。
シェル形式:
CMD /script.sh- 実際には
/bin/sh -c /script.shが PID 1 になります。 - 問題: シェルが信号(SIGTERM/Ctrl+C)を受け取っても、子プロセス(アプリ)に転送しないことがあります。結果、コンテナが正常終了できず、強制終了(Kill)されることになります(データの破損リスク)。
- 実際には
exec形式 (推奨):
CMD ["/script.sh"]- スクリプト自体が PID 1 になります。
- スクリプト内で
exec "$@"を使うことで、最終的に実行するアプリ(nginxなど)に PID 1 を譲ることができます。これにより、シグナルハンドリングが正常に機能します。
ベストプラクティス
- 必ず exec形式 (ブラケット
[]で囲む) を使う。 - エントリーポイントスクリプトの最後は
exec "$@"で終わる(またはexec nginx -g ...のようにexecをつける)。
4. 詳細 Q&A: PID 1 とシグナル、デーモン化
Q1. なぜ tail -f /dev/null で常駐させるのは推奨されないのか?
コンテナを終了させずに動かし続けるためのハックとして ENTRYPOINT ["tail", "-f", "/dev/null"] が使われることがありますが、アンチパターンです。
- 理由:
tailコマンドは、docker stopが送ってくる停止信号(SIGTERM)を受け取っても、それを無視してしまいます(正しくハンドリングしません)。 - 結果: Dockerは停止を待ちますが、反応がないため10秒後に 強制終了 (SIGKILL) します。
- 影響: データベースのデータが保存されずに破損したり、書きかけのファイルが壊れたりするリスクがあります。正しくは「アプリ自体をフォアグラウンドで動かす」べきです。
Q2. docker stop と Ctrl+C の違い
docker stop: 外部からコンテナに優しく「終わってください」という信号 (SIGTERM) を送ります。アプリはこれを受け取って、終了処理(DB切断やログ保存)をしてから終了します。- Ctrl+C: ターミナルで実行中のプロセスに SIGINT (Interrupt) を送ります。基本的には SIGTERM と似た「中断」の意味ですが、対話的なプログラムでは挙動が異なる場合があります。
- 重要: PID 1問題があると、これらの信号がアプリに届かず、「Ctrl+Cを押しても止まらない!」 という事態になります。
Q3. tini コマンドは邪道か?
いいえ、むしろ業界標準のベストプラクティスの一つです。tini は「超軽量な init プロセス」で、PID 1 として起動し、受け取った信号を適切に子プロセスに渡したり、ゾンビ回収を行ったりします(Docker自体にも --init フラグとして組み込まれています)。
- Docker Infrastructureでは: 「仕組みを理解しているか?」を問うために、あえて
tiniを使わず、シェルスクリプトのexecで解決することを求められる傾向があります。使っても減点にはなりにくいですが、「なぜ必要か」の説明は必須です。
5. デーモン(Daemon)とDockerの関係
デーモンとは?
「バックグラウンド(裏)で動き続ける常駐プログラム」 のことです(Windowsでいう「サービス」)。 LinuxのWebサーバー(nginx)やDB(mariadb)は、通常コマンドを打つとすぐに制御をターミナルに返し、裏でこっそり動き始めます(デーモン化)。
Dockerでの矛盾
Dockerコンテナは 「PID 1 のプロセスが動いている間だけ生き続ける」 という仕様です。
- もし
nginxを普通のデーモンモードで起動すると…nginxコマンド実行 → 裏で起動して、コマンド自体はすぐ終了する。- PID 1(コマンド)が終了した!
- Docker「あ、メインプロセス終わったね。じゃあコンテナ停止するわ」
- コンテナが即座に停止してしまう。
解決策: フォアグラウンド実行
そのため、Docker内ではサービスを 「デーモン化させない(フォアグラウンドで動かす)」 設定が必要です。
- Nginx:
daemon off;を設定ファイルに書く。 - MariaDB/WordPress: 専用の起動スクリプトで、裏に回らないようにする。
6. Docker Infrastructureの実装分析
今回のプロジェクトでは、以下のように対策されています。
- execの使用: 各コンテナの
ENTRYPOINTスクリプトの最後はexec "$@"またはexec nginx ...となっています。これにより、シェルではなくアプリケーション自体が PID 1 になり、信号を直接受け取れます。 - フォアグラウンド設定: Nginxの設定で
daemon off;が指定されており、勝手に終了しないようになっています。 - シグナル停止:
docker stopを実行すると、NginxやMariaDBが正しく終了処理を行ってからコンテナが閉じます(10秒待たされません)。