改めてDockerを理解する⑥[Dockerのネットワークについて]

## はじめに
こんにちは！ITスクールRareTECHにてCS（Customer Support）を担当している池村です。今回の記事はDockerのネットワークについてです。ここは初学者にとってとてもつまづきやすいポイントだと思っています。

:::note info
というかネットワークの知識が必須なんですよね。ネットワークの基礎を疎かにしていると痛い目を見ます。（ブーメランですが💧）
:::

![281209c1-26d6-49a2-92f8-71086936b543_720.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/6e6e7136-4f61-d8de-ea16-bab27f77ef31.png)


## Dockerネットワークとは
コンテナの中は、基本的に独立した環境なので、外との通信や、コンテナ間の通信を実現させるためにはネットワークを繋げてあげる必要があります。

Dockerのネットワークは作ることができます。
まずは作り方から見ていきましょう。
```Bash:ネットワークを作成
docker network create my_network
```
上記のコマンドを叩くと、ネットワークが作成できます。
```Bash:ネットワーク一覧を確認
docker network ls
```
上記コマンドを叩くことで、一覧が出ると思います。
デフォルトでいくつかあるネットワークについては後ほど説明するとして、先ほど作成した`my_network`はあるでしょうか？

次にネットワークの詳細を見てみましょう。
```Bash:ネットワークの詳細
docker network inspect my_network
```

以下が筆者のローカル環境上の表示結果です。
```Bash:実行結果
[
    {
        "Name": "my_network",
        "Id": "eb552f0eafb95b88844be750416ba7c19cff66a903b6fc30ce81b175776b7442",
        "Created": "2025-01-04T14:44:19.314223857+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.117.0/24",
                    "Gateway": "192.168.117.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

```
全部を理解する必要は本当にないのですが、主要なところは押さえておきたいですね。

#### `Name`
ネットワークの名前ですね。
#### `Id`
ネットワークを識別するためのユニークなIDです。
#### `Created`
作成した日時ですね。
#### `Scope`
localになっているので、ローカルのホストのみ有効なネットワークです。要は自分のPC上で動くためだけのものです。（細かく言うと自身のPC上のDockerEngineのみ）これがglobalになっていると複数ホスト間で動きます。複数ホストは分散システムや別のサーバーで動くコンテナとも共有できるという意味です。
#### `Driver`
ネットワークの種類です。詳しくは後述します。
#### `IPAM`
ここはかなり重要で、IPアドレスの管理について書かれています。特に重要なのが以下になります。
Subnet: 192.168.117.0/24
ネットワークの範囲（サブネット）です。このネットワーク内で使用できるIPアドレスが 192.168.117.1 から 192.168.117.254 に設定されています。
Gateway: 192.168.117.1
このネットワークのゲートウェイ（ルーターの役割を果たすアドレス）です。コンテナが外部のネットワークにアクセスする際にこのアドレスを使用します。
#### `Containers`
現在このネットワークが使われているコンテナを表示します。空なので、今は何も接続されていないただのネットワークです。

:::note info
飛ばした項目は初学者が覚えてもあまり効果がないので、飛ばしてOKです。
:::


## ネットワークの種類について
このネットワークにもいくつか種類があるのでご紹介します。
ただ、基本はBridgeです。
### Bridge
これはコンテナ間の通信を可能にするネットワークです。
これを作成することで、例えば何かしらの言語でWebアプリを動かすコンテナと、MySQLなどのデータベースを動かすコンテナ間を通信させることができます。前項で作成したのもBridgeネットワークになります。ポートフォワーディングを使うことで外部との接続も可能です。

### Host
Hostは名前の通り、ホストであるサーバー or PCのネットワークを使って通信を行うことができます。ホストのネットワークを直接使うわけですから、速度に関してはとても早いです。もちろんポートもホストのポートを使うことができます。セキュリティの観点からもあまりオススメする繋ぎ方ではないと思っています。よっぽどのことがない限りBridgeを使いたいですね。
使いたい場合は以下のコマンドを叩いてください。
```Bash:Hostの使い方
docker run --network host my_container
```

### None
こちらはネットワークから完全に分離する形です。何も繋がっていないので外部とも別コンテナとも通信できません。私自身あまり使ったことないですね。
```Bash:Noneの使い方
docker run --network none my_container
```

### Overlay（上級者向け）
複数のホストにまたがったネットワークを作成します。
分散システム向けで、複数ホストをまたげる分速度もBridgeより遅いです。IPアドレスはDockerが管理しています。分散システム向けって何？と言われると、DockerSwarmやKubernetesのことを指すことが多いです。が、これらはかなり上級者向けて初学者はもっと後半で学習することになります。

### Macvlan（上級者向け）
こちらは、コンテナが独自のIPアドレスとMACアドレスを持つことができるものです。これはルータとかスイッチからも独立したデバイスとして認識されるので、例えば家のプリンターとかとも直接通信できるわけです。ただ、IPアドレスを割り当ててあげる必要はあります。


## ポートフォワーディングについて
ではポートフォワーディングについてみていきましょう。
これはホストのポートとコンテナのポートを繋げてあげることですね。そうすることでコンテナの中に外部の通信を送ることができます。

#### ポートとは？
ポートはPCやサーバーで通信を受け取る際の入り口のことですね。
コンピュータはどの通信をどのポートで受け取るか決まっています。例えばHTTPの通信なら80番、SSHの通信なら22番とかです。これはそのままプロトコル（通信規格）と関係しています。
すでに決まっているポートの他に、自由に使えるポートがあって、Dockerでコンテナに繋げるのはそちらの自由なポートになります。

:::note info
ポート番号について詳しくなりたいなら、頑張ってググってください🖐️
ネットワークの基本の基本です。
:::

やはり簡単な確認方法はWebサーバーですかね。以下のコマンドを叩いて実行してみましょう。
```Bash:ポートフォワーディング
docker run -d -p 8080:80 nginx
```
NginxはWebサーバーをたてるためのアプリケーションです。これを使うことで、80番ポートでの通信が可能になります。
無事にコンテナが動き始めたら、以下のURLをクリックしてみましょう。

http://localhost:8080/

以下のような表示がされていれば、問題なく動いています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/f0a9fc6f-d62c-289d-80f9-730c260549b0.png)

これは、ホストの8080番ポートと、コンテナの中の80番ポートが繋がっているということです。URLのlocalhostは自身のPCを指していると思ってください。

**ポートフォワーディングをするためのオプションは`-p`ですのでお間違いなく。**


## Bridgeでコンテナ間通信をしてみよう
ではここからはコンテナ間通信をどうやって行うかをみていきましょう。
Webアプリが一番わかりやすい（自分が慣れている）ので、簡単なWebサーバーのコンテナと、データベースのコンテナを立ち上げて、その二つのコンテナ間で通信してみます。

この記事をどんな方が読むかわからないですが、今回はPython（記述量が比較的楽）で作成してみます。

### Flask（Python）とMySQLのコンテナ間通信
:::note warn
Webサーバーとデータベースについて、ある程度理解している前提で進めさせていただきます。
:::

#### MySQLのコンテナを作成
```Bash:MySQLのコンテナを作成・起動
docker run --network my_network --name mysql_container \
    -e MYSQL_ROOT_PASSWORD=root \
    -e MYSQL_DATABASE=test_db \
    -e MYSQL_USER=user \
    -e MYSQL_PASSWORD=password \
    -d mysql:latest
```

```Bash:コンテナの中のデータベースにアクセス
docker exec -it mysql_container mysql -u root -proot
```

```SQL:テーブル一覧をみてみよう
SHOW DATABASES;
```
以下のように表示されていれば、test_dbのデータベースはできています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/c1fd98d7-c080-43a8-41be-383a94fb4c77.png)

ではテーブルを作っていきます。
```SQL:使用するデータベースを選択する
USE test_db;
```

次にSQLを実行しましょう。今回は超シンプルなテーブルにします。
```SQL:テーブルを作成するSQL
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL
);
```

これでデータベースの準備は整いました。次はFlaskですね。

#### Flaskのコンテナを作成
コンテナを作成する前に、ローカルにディレクトリを作成して、そこにPythonのファイルを作成します。あとはrequirements.txtも必要ですね。ディレクトリはどの場所でも大丈夫です。ホームディレクトリに作るのはあまりおすすめしません。
```Bash:作成方法
mkdir flask_app && cd flask_app && touch app.py requirements.txt
```

ディレクトリ構成は以下のようになっていればOKです。
```Bash:コンテナにマウントするためのディレクトリ構成
.
└── flask_app
    ├── app.py
    └── requirements.txt

2 directories, 2 files
```

では次に`app.py`の中身を書いていきます。
```Python:app.pyの中身
from flask import Flask, request, jsonify
import mysql.connector

app = Flask(__name__)

db_config = {
    "host": "mysql_container",
    "user": "user",
    "password": "password",
    "database": "test_db"
}


@app.route('/add', methods=['POST'])
def add_data():
    data = request.json
    name = data.get("name")

    try:
        conn = mysql.connector.connect(**db_config)
        cursor = conn.cursor()
        cursor.execute("INSERT INTO users (name) VALUES(%s)", (name,))
        conn.commit()
        cursor.close()
        conn.close()
        return jsonify({"message": "success"})
    except mysql.connector.Error as err:
        return jsonify({"error": str(err)}), 500


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)
```
中身の簡単な説明をすると
- 今回使うFlaskのライブラリ群をインポート
- PythonでMySQLと接続するためのライブラリをインポート
- 『 http://localhost:5000/add 』というURLにPOSTリクエストでデータを送ると、そのデータをデータベースに登録する処理

でできています。

次に`requirements.txt`の中身ですね。
```Bash:requirements.txtの中身
Flask
mysql-connector-python
```

この二つができたら準備完了です。すでにMySQLのコンテナは動いているので、Flaskのコンテナも同じネットワークを指定して立ち上げます。


```Bash:Flaskコンテナの起動
docker run --network my_network --name flask_container -v "$(pwd)/flask_app:/app" -w /app -p 5000:5000 python:3.9-slim sh -c "ls && pip install -r requirements.txt && python app.py"
```

:::note warn
以下のコマンドを叩くのは`flask_app`ディレクトリのある場所です。
あとMacだと標準で5000番ポートが使われていたり、ローカルで動いているFlaskアプリとポートが競合する可能性があります。その際には`5001:5000`とか、`5002:5000`などポートの調整をするといいかもしれません。
:::

無事サーバーが立ち上がったら、以下のようになるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/8cb4e2cb-39b4-cf1a-84c1-ea0befd774ab.png)

#### `curl`コマンドでリクエストを送ってみる
もう一つターミナルを開いて（もしくはtmuxで分割して）、`curl`コマンドでPOSTリクエストを送ってみましょう。`curl`コマンドはWebサーバーにGETリクエストやPOSTリクエストを送ってレスポンスを受け取ることができるコマンドです。

```Bash:curlコマンドで
curl -X POST -H "Content-Type: application/json" -d '{"name": "TestName"}' http://localhost:5000/add
```

:::note warn
ここも、5001や5002のポートにフォワーディングしているなら、そちらに合わせてください。
:::

今回はTestNameという名前のデータをWebサーバーに送っています。これがデータベースに登録されているなら、二つのコンテナの通信はうまくいっています。

では実際に登録されているか確認してみます。
```Bash:DBのコンテナにSQLを送ってみる
docker exec -it mysql_container mysql -u root -proot -e "SELECT * FROM test_db.users;"
```
これでテーブルが表示されていればOKです。
検証のため、私はいくつかデータを送ってみましたが、問題なく登録されていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3798971/758ed726-4c12-90b4-d9ad-3dedb3a6a154.png)

## おわりに
Dockerのネットワークの話が終わったので、ようやくDockerfileの話に入れそうです。
正直、コマンドの記述量的に非効率すぎて大変なので、もっと簡略化するためにDockerfileを早く使いたかったです。ただ、ここは順番なのでここまでが基礎、ここからが応用的な話として割り切りっていきたいですね。

改めて、基礎は大事です。ただ基礎ばっかりやってるとつまらないのが難点。

次の記事👇

https://qiita.com/ikemura-ren/items/3debbe2ba8997a95e6b5

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

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