⑥１日１０分で理解するコンテナ技術入門 - Docker Composeの概要 -

[前回](https://qiita.com/goroneko/items/48b7036d10a0d6548114)までで、コンテナ（docker）の基本的な使い方を説明しました。ここからは、複数コンテナの管理に使える、`docker compose`の使い方について説明していきたいと思います。
コード-> https://github.com/sogawa-yk/simple-knowledge-sharing-platform/tree/6-article
# Docker Composeとは
Docker Composeは、複数のコンテナを使ったアプリケーションを定義して実行するためのツールです。設定ファイル（docker-compose.yaml）を使って、複数のコンテナとアプリケーションの構成を定義して、その構成に基づいて一括でコンテナをビルド・実行・停止などが行えます。

## どんなアプリケーションに使える？
例えば、[以前](https://qiita.com/goroneko/items/8024bee8e8f58da49101)の記事で作成した、ウェブサーバ用のコンテナと、DBのコンテナを使ったアプリケーションにも使えます。以前の記事では、起動する順番に注意しながら各コンテナに対して`docker image build`コマンドと、`docker container run`コマンドを使いましたが、docker composeを用いると、一括でこれらの操作を行うことができます。

# 基本的な書き方
yamlファイルなので、インデントを使って構造化して記述します。例えば以下のような具合です。
```yaml:docker-compose.yamlのサンプル
version: '3'
services:
  web:
    image: my-web-app:latest
    build: .
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=postgres://user:password@db:5432/mydatabase
    depends_on:
      - db

  db:
    image: postgres:13
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydatabase

volumes:
  db-data:
```
`docker-compose.yaml`に記述する内容を、上位レイヤーから順に説明していきます。

## 最上位レイヤー
最上位レイヤーには、`version`, `services`, `networks`, `volumes`などを記述します。
- version
    composeファイルのバージョンを記述
- services
    各サービスの定義を記述
- networks
    カスタムネットワークの定義を記述
- volumes
    カスタムボリュームの定義を記述
ここからは、この最上位レイヤーの各レイヤーごとに、その中身に記述する内容を説明します。
## servicesレイヤー
ここには各サービスを定義します。各サービスには任意の名前を付けることができます。`docker-compose.yamlのサンプル`の例では、以下の部分です。
```yaml
services:
  web:         # サービス名
    # サービスの詳細設定はここに続きます
  db:          # 別のサービス名
    # サービスの詳細設定はここに続きます
```
### サービスの詳細設定のレイヤー
各サービスの設定には、コンテナイメージ、ビルド、ポート、環境変数、依存関係などの詳細を定義します。
```yaml
services:
  web:
    image: my-web-app:latest           # 使用するDockerイメージ
    build: .                           # Dockerfileがあるディレクトリ
    ports:
      - "5000:5000"                    # ポートマッピング
    environment:
      - DATABASE_URL=postgres://user:password@db:5432/mydatabase # 環境変数
    depends_on:
      - db                             # 起動順序の依存関係
    networks:
      - app-network                    # 使用するネットワーク

  db:
    image: postgres:13                 # 使用するDockerイメージ
    volumes:
      - db-data:/var/lib/postgresql/data # ボリュームマッピング
    environment:
      - POSTGRES_USER=user             # 環境変数
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydatabase
    networks:
      - app-network                    # 使用するネットワーク
```
`ports`で行えるポートのマッピングは、`docker container run`コマンド時と同様、`[ホストのポート]:[コンテナのポート]`の順に指定します。

また、`depends_on`で指定するのは`docker-compose.yaml`内で指定したサービス名です。ここに指定したサービスが起動するまで、`depends_on`が設定されたサービスは起動しないようになります。このようにすることで、コンテナの起動順序を制御することができます。

## ネットワークレイヤー
カスタムネットワークの定義を記述します。サービスの詳細設定レイヤーで、そのサービスが配置されるネットワークを指定できますが、そのネットワークはこのネットワークレイヤーで定義しておく必要があります。
```yaml
networks:
  app-network:                         # ネットワーク名を指定
    driver: bridge                     # ネットワークドライバを指定
    driver_opts:                       # ドライバ固有のオプションも指定可能
      com.docker.network.bridge.name: my-custom-bridge
```
ネットワークドライバには以下のようなものが設定できます。
| ネットワークドライバ | 説明 |
| --- | --- |
| bridge | デフォルトのネットワークドライバ。ネットワーク作成時にドライバを指定しなければこのネットワークが使われる。通常、スタンドアロンコンテナが通信するために使用。 |
| host | スタンドアロンコンテナ用。コンテナとDockerホスト間のネットワーク隔離を解除し、ホスト側のネットワーク機能を直接使用。 |
| overlay | 複数のDockerデーモンを接続し、swarmサービスが相互に通信可能にする。OSレベルのルーティング設定が不要。 |
| ipvlan | IPv4とIPv6のアドレス割り当てをまとめてコントロール。レイヤー2 VLANタギングやIPvlan L3ルーティングも完全に操作可能。 |
| macvlan | コンテナにMACアドレスを割り当て、物理デバイスとして見えるようにする。物理ネットワークへの直接接続が想定される場合に最適。 |
| none | コンテナの全てのネットワーク機能を無効化。通常、カスタムネットワークドライバとの競合を避けるために使用。swarmサービスでは利用不可。 |
| ネットワークプラグイン | サードパーティ製のネットワークプラグインをインストールして利用可能。Docker Hubやサードパーティベンダーから提供。ベンダーのドキュメントを参照。 |

## ボリュームレイヤー
ボリュームレイヤーも書き方はネットワークレイヤーと同様です。
```yaml
volumes:
  db-data:                      # ボリューム名を指定
    driver: local               # ボリュームドライバを指定
    driver_opts:                # ドライバ固有のオプションを指定
      type: nfs
      o: addr=192.168.1.100,rw
      device: ":/path/to/dir"
```
# 使用例
それでは、[前回](https://qiita.com/goroneko/items/8024bee8e8f58da49101)作成したウェブアプリケーションの`docker-compose.yaml`を書いてみます。
まず、作成したウェブアプリケーションを少し編集します。DBへの接続をハードコーディングしていましたが、その部分を環境変数から取得する形に変更します。
```python:app.pyの一部
def init_db():
    conn = mysql.connector.connect(
        host=os.environ.get('DATABASE_HOST', 'db'),
        port='3306',
        user=os.environ.get('DATABASE_USER', 'root'),
        password=os.environ.get('DATABASE_PASSWORD', 'password'),
        database=os.environ.get('DATABASE_NAME', 'flask_app')
    )
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE IF NOT EXISTS articles (id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255), content TEXT)')
    conn.commit()
    cursor.close()
    conn.close()

init_db()

def get_db_connection():
    conn = mysql.connector.connect(
        host=os.environ.get('DATABASE_HOST', 'db'),
        port='3306',
        user=os.environ.get('DATABASE_USER', 'root'),
        password=os.environ.get('DATABASE_PASSWORD', 'password'),
        database=os.environ.get('DATABASE_NAME', 'flask_app')
    )
    return conn
```

前回のウェブアプリケーション起動時に使ったdockerコマンドは、
```bash
$ docker network create flask-net
$ docker build -t simple-knowledge-sharing-platform .
$ docker run --name mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=flask_app --network flask-net -d mysql:8.0
$ docker run -d -p 8080:5000 --network flask-net -v $(pwd)/uploads:/app/uploads simple-knowledge-sharing-platform
```
でした。これらの操作を`docker-compose`に落としていきます。
まず、以下のようなひな型を作成します。
```yaml
version: '3.8'

services:
  db:

  web:

networks:

volumes:

```
## services
### MySQL
MySQLのコンテナ起動コマンドは`$ docker run --name mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=flask_app --network flask-net -d mysql:8.0`でした。これを`docker-compose.yaml`に落とすと、
```yaml
  db:
    image: mysql:8.0
    container_name: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=flask_app
    networks:
      - flask-net
    volumes:
      - db-data:/var/lib/mysql
    restart: unless-stopped
```
となります。
### webアプリ
webアプリ側の起動コマンドは、`$ docker run -d -p 8080:5000 --network flask-net -v $(pwd)/uploads:/app/uploads simple-knowledge-sharing-platform`でした。これを`docker-compose.yaml`に落とすと、
```yaml
  web:
    build: .
    container_name: web
    ports:
      - "8080:5000"
    networks:
      - flask-net
    volumes:
      - ./uploads:/app/uploads
    depends_on:
      - db
    environment:
      - DATABASE_HOST=db
      - DATABASE_USER=root
      - DATABASE_PASSWORD=password
      - DATABASE_NAME=flask_app
    restart: unless-stopped
```
となります。`environment`の`DATABASE_HOST`には、`docker-compose.yaml`内で定義しているサービス名を指定します。
## networks
ネットワーク作成時のコマンドは、`$ docker network create flask-net`でした。これを`docker-compose.yaml`に落とすと、
```yaml
networks:
  flask-net:
    driver: bridge
```
となります。
## volumes
アップロードされた画像ファイルを保存するために、ボリュームはウェブ側のみ用意しています。以下のように記述します。
```yaml
volumes:
  web-data:
    driver: local
```
## 全体
ここまで加えた変更をみると、以下のような`docker-compose.yaml`になります。
```yaml
version: '3.8'

services:
  db:
    image: mysql:8.0
    container_name: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=flask_app
    networks:
      - flask-net
    restart: unless-stopped

  web:
    build: .
    container_name: web
    ports:
      - "8080:5000"
    networks:
      - flask-net
    volumes:
      - web-data:/app/uploads
    depends_on:
      - db
    environment:
      - DATABASE_HOST=db
      - DATABASE_USER=root
      - DATABASE_PASSWORD=password
      - DATABASE_NAME=flask_app
    restart: unless-stopped

networks:
  flask-net:
    driver: bridge

volumes:
  web-data:
    driver: local
```
## docker-compose.yamlからサービスを起動
`docker-compose.yaml`があるディレクトリに移動し、以下のコマンドを実行します。
```bash
$ docker compose up -d --build #docker composeを使っている場合
$ podman-compose up -d --build #podmanを使っている場合
```
## サービスを停止
```bash
$ docker compose down # docker composeを使っている場合
$ podman-compose down # podman-composeを使っている場合
```

# まとめ
基本的な`docker compose (podman-compose)`の使い方を何となく理解できたかと思います。[次回](https://qiita.com/goroneko/items/4c34db356e0224e7b0df)は、ボリュームを使ったデータ永続化について、もう少し深掘りしていきます。