🖥️

【3-4】Docker Compose入門!YAMLで複数コンテナを華麗にオーケストレーション

に公開

Docker Compose入門!YAMLで複数コンテナを華麗にオーケストレーション

はじめに

前回の記事では、Dockerfileを使って自分だけのカスタムイメージを作成する方法をマスターしました。しかし、実際のアプリケーションはWebサーバー、データベース、キャッシュサーバーなど、複数のコンテナが連携して動作します。これらを一つずつdocker runで起動し、ネットワーク設定を手作業で行うのは、あまりにも大変でミスも起こりがちです。
https://zenn.dev/koikoi_infra/articles/98d1e3eaaa4450

今回は、この「マルチコンテナ管理」の課題をエレガントに解決する公式ツール、Docker Composeを学びます。もう長いdocker runコマンドを覚える必要はありません。たった一つの設定ファイルで、アプリケーション全体の環境をコードとして管理するInfrastructure as Code (IaC)の世界に足を踏み入れましょう。

この記事では、Docker Composeがなぜ必要なのかという概念から、docker-compose.ymlファイルの書き方、そして基本的なコマンド操作まで、マルチコンテナ環境をゼロから構築・管理する手順を解説します。


なぜDocker Composeが必要なのか?

これまで、私たちはdocker runコマンドでコンテナを一つずつ起動してきました。しかし、アプリケーションが複雑になると、以下のような問題に直面します。

  • コマンドが長く複雑になる: -p-v--networkなど、コンテナごとに大量のオプションを正確に記憶・入力するのは困難です。
  • 起動順序の管理が大変: Webアプリはデータベースが起動してからでないと動き始められない、といったコンテナ間の依存関係を手動で管理するのは面倒です。
  • 環境の再現性が低い: 開発者間でdocker runのオプションが少しでも違えば、同じ環境が作れず「自分のPCでは動いたのに…」問題が再発します。

Docker Composeは、これらの問題を解決します。複数のコンテナからなるアプリケーション全体の構成を、docker-compose.ymlというたった一つの設定ファイルに記述することで、誰がどこで実行しても 「全く同じ環境」を「コマンド一発」 で構築・管理できるのです。


最初のdocker-compose.ymlを作成する

それでは、Nginx (Webサーバー) とMySQL (データベース) が連携する、シンプルなWebアプリケーション環境を定義するdocker-compose.ymlを作成してみましょう。

そもそもサーバーを分ける理由について

物理ホストにデータベース用のドライブを用意するだけ(つまり1台のサーバーで両方の役割を担うこと)で済ませず、サーバー自体を分ける具体的な理由を以下で解説します。

1. 役割の違いと負荷の分散(パフォーマンス)

Webサーバーとデータベースサーバーでは、求められる処理が全く異なります。

Webサーバー: ユーザーからのリクエストを受け取り、Webページを表示したり、アプリケーションを動かしたりする処理(CPUやメモリを瞬間的に多く使う)が中心です。

データベースサーバー: データの読み書きや検索といった、ディスクI/O(読み書き速度)やメモリを継続的に多く使う処理が中心です。

もし1台のサーバーで両方を動かすと、Webサイトへのアクセスが急増した際に、Webサーバーの処理がCPUやメモリを使い果たし、データベースの処理が非常に遅くなる、といった**「リソースの奪い合い」**が発生します。サーバーを分けることで、それぞれが自身の処理にリソースを集中できるため、システム全体として安定した高いパフォーマンスを維持できます。 🏎️

2. セキュリティの向上

サーバーを分けることで、セキュリティリスクを大幅に低減できます。

役割の限定: Webサーバーはインターネットから直接アクセスされる必要がありますが、データベースサーバーはその必要がありません。

防御壁: データベースサーバーを内部ネットワークに配置し、Webサーバーからのアクセスのみを許可するように設定できます。これにより、万が一Webサーバーがサイバー攻撃を受けて乗っ取られたとしても、重要なデータが保存されているデータベースサーバーへの直接的な被害を防ぐことができます。これは、金庫を直接部屋に置くのではなく、さらに別の頑丈な金庫室に保管するようなものです。 🛡️

3. 柔軟な拡張性(スケーラビリティ)

Webサイトの成長に合わせて、システムの能力を増強しやすくなります。

Webサーバーの拡張: アクセス数が増えてWebページの表示が遅くなった場合は、Webサーバーの台数を増やす(スケールアウト)だけで対応できます。

データベースサーバーの拡張: 扱うデータ量が増えてデータベースの処理が遅くなった場合は、データベースサーバーの性能を上げる(スケールアップ)ことで対応できます。

このように、ボトルネックになっている部分だけを効率的に強化できるため、無駄なコストを抑えながらシステムを拡張していくことが可能です。 💪

4. 管理とメンテナンスのしやすさ

サーバーの役割が分かれていると、日々の管理やメンテナンスが容易になります。

問題の切り分け: 「Webサイトの表示が遅い」といった問題が発生した際に、Webサーバーに問題があるのか、データベースサーバーに問題があるのかを特定しやすくなります。

独立したメンテナンス: データベースサーバーのメンテナンス(OSのアップデートや再起動など)を行っている間も、Webサーバーは静的なエラーページを表示し続けるなど、サービスを完全に停止させずに対応できる場合があります。

まとめ

小規模なWebサイトや開発環境では、コストを抑えるために1台のサーバーでWebサーバーとデータベースサーバーを兼ねることもあります。

しかし、本格的なサービスを運用する場合、パフォーマンスの安定化大切なデータを守るセキュリティ将来の成長に備える拡張性、そして安定運用のための管理性といった多くのメリットがあるため、Webサーバーとデータベースサーバーは別々に構築するのが一般的です。

準備 (ディレクトリと設定ファイルの作成)

ubuntu-server-01上で作業用のディレクトリを作成し、docker-compose.ymlとWebコンテンツを配置します。

# 作業ディレクトリを作成して移動
mkdir ~/docker-compose-practice
cd ~/docker-compose-practice

# Webサーバーが表示するHTMLファイルを作成
mkdir html
echo '<h1>Docker Compose Test</h1><p>Web + DB Environment</p>' > html/index.html

# docker-compose.ymlを作成
cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  web:
    image: nginx:alpine
    container_name: my-nginx-compose
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - db
    networks:
      - app-network

  db:
    image: mysql:8.0
    container_name: my-mysql-compose
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - app-network

volumes:
  mysql-data:

networks:
  app-network:
    driver: bridge
EOF
  • services: webとdbという2つのコンテナを定義しています。
  • volumes: dbコンテナのデータを永続化するためのボリュームmysql-dataを定義しています。
  • networks: 2つのコンテナが通信するためのapp-networkを定義しています。
  • depends_on: - db: 「webサービスはdbサービスが起動してから起動する」という依存関係と起動順序を定義しています。
【補足】名前付きボリュームとバインドマウント、2種類のマウント方法の違い

docker-compose.ymlのdbサービスとwebサービスでは、同じvolumesという項目で設定していますが、実はこれら2つは性質が全く異なるマウント方法です。

1. 名前付きボリューム (dbサービスで利用)

# dbサービスの設定
volumes:
  - mysql-data:/var/lib/mysql

# ymlファイルの末尾でボリュームを定義
volumes:
  mysql-data:
  • これは何か?: Dockerが管理する専用のデータ保管領域です。
  • どこにある?: ホストOS上の、Dockerが管理する特別なディレクトリ内に作成されます(通常、私たちはその場所を直接意識しません)。
  • 主な用途: データベースのファイルのように、アプリケーション(コンテナ)が内部で生成・管理するデータの永続化に使います。私たちがホストOSから直接ファイルを編集することはあまりありません。
  • 例えるなら…: 「外付けの専用ハードディスク」。コンテナに接続してデータの読み書きを任せます。

2. バインドマウント (webサービスで利用)

# webサービスの設定
volumes:
  - ./html:/usr/share/nginx/html
  • これは何か?: ホストOS上の特定のディレクトリやファイルを、直接コンテナ内にマウントする方法です。
  • どこにある?: ./htmlは、「docker-compose.ymlファイルがある場所と同じ階層にあるhtmlディレクトリ」を指します。
  • 主な用途: ソースコードや設定ファイル、今回のようなHTMLファイルなど、私たちがホストOS側で用意・編集したいファイルをコンテナに提供するために使います。
  • 大きな特徴: ホストOS側で./html/index.htmlを編集すると、その変更が即座にコンテナ内に反映されます。イメージを再ビルドする必要はありません。
  • 例えるなら…: **「ホストOSとの共有フォルダ」**です。

まとめ:なぜ使い分けるのか?

今回のdocker-compose.ymlでは、それぞれの用途に最適なマウント方法が選択されています。

  • dbサービス (MySQL): データベースファイルはMySQL自身が管理します。私たちはただ安全にデータが消えずに残ってほしいだけなので、Dockerに管理を任せる**「名前付きボリューム」**が最適です。

  • webサービス (Nginx): Webページの内容(HTML)は私たちが開発・編集するものです。ホストOSでファイルを変更したらすぐにブラウザで確認したいため、ホストOSのディレクトリを直接共有できる**「バインドマウント」**が最適なのです。


Compose基本操作・ライフサイクル管理

docker-compose.ymlファイルがあるディレクトリで、以下のコマンドを実行します。

アプリケーション全体の起動 (up)

設計図に書かれた全てのサービス(コンテナ、ネットワーク、ボリューム)をまとめて作成し、起動します。

docker compose up -d

-dオプションで、コンテナをバックグラウンドで起動します。

【実践トラブル】no configuration file providedエラーとの戦い

ここで私はno configuration file provided: not foundというエラーに遭遇しました。ls -lで確認するとファイルは確かに存在するのに、なぜ...?
原因は、恥ずかしながら単純なタイポでした。ファイル名をdocker-compose.ymlとすべきところを、docker-compose,yml(ピリオドがカンマ)と入力していました。mvコマンドでファイル名を正しいものにリネームすることで、無事にdocker compose upが成功しました。Infrastructure as Codeでは、このような小さなミスが大きな問題につながることを痛感した瞬間でした。


状態確認と制御 (ps, logs, stop, start)

# Composeで管理されているサービスの状態を表示
docker compose ps

# webサービスのログを確認
docker compose logs web

# webサービスだけを停止
docker compose stop web

ブラウザでhttp://192.168.3.101:8080にアクセスし、作成したHTMLが表示されることも確認しましょう。


アプリケーション全体の停止・削除 (down)

環境一式を綺麗に後片付けします。

# サービス(コンテナ)とネットワークを停止・削除
docker compose down

# ボリュームもまとめて完全に削除する場合
docker compose down -v

upで立ち上げた環境を、downコマンド一発で安全に破棄できる。これこそがDocker Composeの強力さです。


まとめ

これで、複数のコンテナからなるアプリケーション環境を「コード」として管理できるようになりました。もう「自分のPCでしか動かない」とはおさらばです。


次回予告

今回は基本的なDocker Composeの機能を学びました。次回は、buildコンテキストを使ってDockerfileと連携させたり、環境変数ファイルを活用したりと、より実践的なdocker-compose.ymlの書き方について探求していきたいと思います!

Discussion