改めてDockerを理解する⑨[Composeによる複数コンテナ連携]

## はじめに
こんにちは！ITスクールRareTECHにてCS（Customer Support）を担当している池村です。今回の記事はDocker Composeの続きの記事になります。
連続した記事なので先に前回の記事を見ていただけますと幸いです。

https://qiita.com/ikemura-ren/items/7296576db5b5ce5c60e5

## 複数コンテナの連携
早速本題です。前回までは単一のコンテナをComposeで動かすことを行いました。
ただ、やはりアプリを作るときはプログラムが動くコンテナ、データベースのコンテナなどを連携させたいと思います。⑥のネットワークの記事で二つのコンテナ間通信を実現していたわけですが、それをDocker Composeで実現したいと思います。

ではまず今回作成する環境のdocker-compose.ymlファイルの構成を見ていきましょう。

```YAML:書き方
services:
  flask:
    build:
      context: .
    ports:
      - "5000:5000"
    volumes:
      - ./src:/app
    environment:
      FLASK_DEBUG: 1
    depends_on:
      - db

  db:
    image: mysql:latest
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test_db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - db_data:/var/lib/mysql
      - ./sql:/docker-entrypoint-initdb.d/

volumes:
  db_data:
```

前回の記事では紹介しなかったいくつかの項目があります。
#### 1. `environment`
これは環境変数を設定する部分です。環境変数とは、プログラムとかPCとかが動く際に使う設定情報のようなものとお考えください。
```YAML:FLASK_ENVについて
FLASK_DEBUG: 1
```
これはFlaskの設定としてデバッグモードで起動してね！と設定しておく値です。コンテナの中でこの値が読み込まれて、Flaskアプリが動く際に使われています。自動リロードがされるようになって、コードの変更などを察知してくれるものです。

:::note warn
開発環境前提のお話です。
:::


```YAML:MySQLの環境変数
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test_db
MYSQL_USER: user
MYSQL_PASSWORD: password
```
同様に、MySQLでも環境変数としてこちらの値が使われます。
これはデータベースの設定ですね。

:::note warn
MySQLの詳細は省きます。
:::


#### 2. `depends_on`
では次に`depends_on`ですが、こちらはコンテナの作成順を決めている記述です。なので、MySQLのコンテナが作成された後にFlaskのコンテナが動くということです。
```YAML:書き方
depends_on:
    - db
```
`db`というのは`services`の名前ですね。

## ハンズオン環境を作って動かしてみよう
このセクションでは、先ほどのdocker-compose.ymlファイルと、コンテナを動かすためのファイル類を作っていきましょう。そして実行までしていきます。

```Bash:ディレクトリ構成を作成
mkdir -p flask_project/src flask_project/sql && touch flask_project/src/app.py flask_project/src/requirements.txt flask_project/sql/init.sql flask_project/docker-compose.yml flask_project/Dockerfile
```

```Bash:作成したディレクトリ構成
flask_project/
├── src/
│   ├── app.py
│   ├── requirements.txt
├── sql/
│   ├── init.sql
├── docker-compose.yml
├── Dockerfile
```
上記のようなディレクトリができていればOKです。
次にDockerfileです。

```Dockerfile:中身
# ベースイメージ
FROM python:3.9-slim

# 作業用のディレクトリを設定しています
WORKDIR /app

# 必要なファイル類をコピーしてライブラリをインストールしています
COPY src/ /app/
RUN pip install -r requirements.txt

# コンテナがリッスンするポート番号を指定してます
EXPOSE 5000

# Flaskアプリを起動します
CMD ["python", "app.py"]
```

ではapp.pyに移ります。前の記事よりも少しだけ難しく作成します。
```Python:app.pyの中身
from flask import Flask, request, jsonify
import mysql.connector
from mysql.connector import Error

app = Flask(__name__)

# データベースの接続情報
DB_HOST = "db"  # ここはdocker-compose.yml の `services` で指定した名前を
DB_USER = "user"
DB_PASSWORD = "password"
DB_NAME = "test_db"

# データベースとの接続をするための関数
def get_db_connection():
    try:
        connection = mysql.connector.connect(
            host=DB_HOST,
            user=DB_USER,
            password=DB_PASSWORD,
            database=DB_NAME
        )
        return connection
    except Error as e:
        print(f"エラー: {e}")
        return None

# ここからがAPI
@app.route('/')
def home():
    return "Welcome to the Flask-MySQL App!"

@app.route('/add', methods=['POST'])
def add_data(): # データベースに名前を登録する関数
    data = request.get_json()
    if not data or 'name' not in data:
        return jsonify({"error": "Invalid input"}), 400
    
    connection = get_db_connection()
    if connection is None:
        return jsonify({"error": "Database connection failed"}), 500

    try:
        cursor = connection.cursor()
        cursor.execute("INSERT INTO users (name) VALUES (%s)", (data['name'],))
        connection.commit()
        return jsonify({"message": f"User {data['name']} added successfully"}), 200
    except Error as e:
        return jsonify({"error": f"Database error: {e}"}), 500
    finally:
        connection.close()

@app.route('/users', methods=['GET'])
def get_users(): # データベースから登録されている名前を全て取得する関数
    connection = get_db_connection()
    if connection is None:
        return jsonify({"error": "Database connection failed"}), 500

    try:
        cursor = connection.cursor(dictionary=True)
        cursor.execute("SELECT * FROM users")
        rows = cursor.fetchall()
        return jsonify(rows), 200
    except Error as e:
        return jsonify({"error": f"Database error: {e}"}), 500
    finally:
        connection.close()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
```
requirements.txtも作成します。
```Bash:書き方
Flask
mysql-connector-python==8.0.32
```

最後にinit.sqlの中身を書いていきましょう。

:::note info
init.sqlはデータベースの設定を書き込んでおくことで、コンテナ起動時などにデータベースとその中のテーブルを作っておけるファイルです。

※もちろんDocker関連の設定ファイルに書き込むことが前提です。
:::

```SQL:書き方
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;

CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);
```
MySQLの公式イメージでは、`/docker-entrypoint-initdb.d/`に置いたSQLファイル（init.sqlとか）を起動時に自動で実行する仕組みがあります。


最後にdocker-compose.ymlですが。

:::note info
docker-compose.ymlファイルは記事の最初に書いたものを使って下さい。
:::

### では実行しましょう！
```Bash:実行コマンド
docker compose up
```

:::note warn
docker-compose.ymlファイルのあるディレクトリで実行して下さい。
:::

以下の画像のように動いているのを確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/cc1aab22-b456-3781-3ce1-d64930b2c004.png)


では実際にGETリクエストである`/`にリクエストを送ってみましょう。

http://localhost:5000/

添付画像のようになっていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/01a7027b-f21c-8d7d-dba5-b8d0089107cf.png)

次にユーザー登録をしていきます。
これはcurlコマンドを使ってPOSTリクエストを送っていきます。
```Bash:
curl -X POST -H "Content-Type: application/json" -d '{"name": "TestName"}' http://localhost:5000/add
```

```Bash:成功メッセージ
{
  "message": "User TestName added successfully"
}
```
こうなっていれば問題ありません。

では最後に登録されたユーザーの一覧を出してみましょう。

http://localhost:5000/users

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/c59d315b-deac-f224-8e1e-624aff46637e.png)

無事登録ができていました！


## あれ？これはどこでコンテナ同士が繋がってるの？
と思う人は勘がいいです。
Docker Composeではネットワークは内部的に作成されて勝手に連携されています。
勝手にやってくれるのはとてもいいことなんですが、中で何が行われているかわからないのは怖いですね。
```Bash:まずはネットワーク一覧で探してみる
docker network ls
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/d3b584d0-bf46-fd83-9b6a-3af3294d5bf4.png)
この実行結果にある`flask_project_default`というのが、今回自動的に作られたネットワークです。ではこのネットワークの詳細を見てみましょう。

```Bash:詳細を調べる
docker network inspect flask_project_default 
```
私の環境では以下の画像のような結果が表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/62cc4820-0d6b-471b-32cd-5d93d45e25f1.png)

Dockerネットワークの記事でこの中身については記述しているので、がっつり説明する気はありませんが、ラベル情報が追記されていますね。これも勝手に書き込まれているようです。

## おわりに
さて、ここまでの読了お疲れ様でした。初学者が学ぶべきDocker基礎の記事はここまででおしまいです。新しい技術を最初から深ぼろうとはせず、さらっと全体をなぞるのが大事だと思っています。
ただ、Dockerは難しい技術です。Linuxの集大成のような技術ですし、**各言語ごとの環境構築方法もわかっていないといけません**。私のDocker記事は通してPython（Flask）を使ってきましたが、他の言語の場合はその環境構築方法、フレームワークの使い方を覚えてから応用してみて下さい。

まとめ記事に戻るにはこちら👇

https://qiita.com/ikemura-ren/items/b961642d55e4e6515203

![715527be-86a3-4835-be5e-5b6b45d618f8_720.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/1df61840-d1c4-fe56-d407-253b54084182.png)


## 余談
フルリモートフルフレックスの会社にいるのに、家では仕事や学習をできないタイプなので、コワーキングかカフェにいることが多い人間です。PRONTカフェが好きです☕️