【Docker】コンテナとNetworkの関係（Bridgeってなに？名前解決？）【初心者OK！Docker入門＋応用シリーズ】

Dockerで複数コンテナを立ち上げて連携させる場合、コンテナのNetwork設定の知識は必須になります。
DockerにおけるNetworkに基本的な知識をまとめました。

# はじめに
記事を書いている間に思いましたが、DockerのNetworkを完全に理解するにはTCP/IPの理解がある程度ないと厳しそうです。
今回の記事をきっかけに、気になるキーワードを深掘りしつつ、TCP/IPの理解もするきっかけになってくれると嬉しいです！

あと、いいねくれると喜びます🙇‍♂️

# Docker Networkの概要
Dockerでは、立ち上げた複数のコンテナ間で干渉しないように、独立なNetworkを構築することができます。

# Docker Networkの一覧表示
```bash
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
cc1741c9feca   bridge    bridge    local
d1b5ab168f8c   host      host      local
ccade9920c46   none      null      local
```
自分でNetworkを作成していなくても、デフォルトでDockerはいくつかのNetworkを持っています。

# コンテナはデフォルトでは `bridge` Networkへ
特に意識せずにDockerコンテナを立ち上げると、**デフォルトでは`bridge` というNetworkにアサインされます。**
それを確認してみましょう。

まずは適当なコンテナを立ち上げます
```bash
$ docker run -d --name=sleeping-alpine --rm alpine sleep 5000

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS     NAMES
dab72b58110a   alpine                 "sleep 5000"             4 seconds ago   Up 3 seconds             sleeping-alpine
```

上記のコンテナは、起動してからSleepで5,000秒間寝続けるだけのコンテナです。
今回は特にアサインするNetworkを指定していませんから、**`bridge`にアサインされるはず**です。

以下の`docker network inspect`コマンドでNetworkの詳細を確認できます。
`bridge`の詳細状態を確認してみます。

```bash
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "xxxxxxxxxxxxxxxx",
        "Created": "2022-10-24T01:14:42.407057138Z",
（...中略...）
        "Containers": {
            "dab72b58110accc88bceb930fba7601134fb8800e051bfc453828f514be442d2": {
                "Name": "sleeping-alpine",
                "EndpointID": "0d28315343cf79d14407b748f49231edc2526f6c7e80862d12dc4e7cb525442d",
                "MacAddress": "02:42:ac:11:00:04",
                "IPv4Address": "172.17.0.4/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]
```
上記の通り、`Containers`のところに先ほど作成した`sleeping-alpine`が存在することがわかります。

もう一つ注目したい点として、このNetworkは **`docker0`という名前のBridgeである**ということです。

# Bridgeとは？
Bridgeとは、Dockerに限らず一般的にNetworkに利用されている機能です。
概要図を以下のように作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181439/5c0273d3-d00c-ae49-9c69-0c53e4d786e2.png)

Bridgeを使うことで、複数のNetwork Namespace間での通信を容易にすることができます。

Network Namespaceも一般的なLinuxに存在するNetworkの機能です。
Namespaceを一つのマシンの中で切ることで、**NamespaceごとにNetwork設定を独立して分割することが可能**にします。

また、veth (virtual ethernet)は、NamespaceやBridgeのインターフェイスとなっている口です。
それぞれが通信をするためには、これらのvethが接続されている必要があります。

そして、**Dockerのコンテナはそれぞれが独立したNetwork Namespaceを構築し、vethも持っています。**

つまり、現在のDockerのNetworkは以下のような状態になっているってことです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181439/ecfdd051-c4af-1543-864d-c0cd4598d996.png)

# 複数コンテナが同じNetworkにつながっていると？
デフォルトでは、`bridge`のNetworkにコンテナは接続されると説明しました。
では複数コンテナを接続するとどうなるでしょう？

一旦まっさらな状態から、新しく`container-1`と`container-2`を作成してみます。
```bash
$ docker run -d --name=container-1 sleep 5000

$ docker run -d --name=container-2 sleep 5000

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS     NAMES
b6a417a96093   alpine                 "sleep 5000"             2 seconds ago   Up 1 second              container-2
18e4693bd2d9   alpine                 "sleep 5000"             6 seconds ago   Up 4 seconds             container-1
```
`bridge`Networkの状態を確認してみます。
```bash
$ docker network inspect bridge
[
(...中略...)
        "Containers": {
            "39ed7fe1d339330d159d05ba720ee99d51e18dfe7ddf93efe74944c68e5bdc99": {
                "Name": "container-2",
                "EndpointID": "9a4083a07d49b8c198162c261a155b34eb19578a092f714818163c84f730d284",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/16",
                "IPv6Address": ""
            },
            "e373938a6cdaec9cbfbf3815c38e566e2bc6f926dfb9316b48faf24ca2bc2b08": {
                "Name": "container-1",
                "EndpointID": "6416c833d88a5e6c8caa12930357718cd68df865805b2237bfc2777c2584d14d",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
(...中略...)
]
```
先ほど起動したコンテナがアサインされていることが分かります。
IPアドレスのアサインも出ていますね。
この情報を図にすると以下の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181439/1c70da86-c930-e5f9-468c-46363f8b9e28.png)

ここで、**同じ`bridge`Networkに紐づいている２つのコンテナは互いに`bridge0`を経由して通信をすることができる**ようになっています。

`container-1` -> `container-2`にPingを投げて応答するかみてみましょう。

```bash
$ docker exec container-1 ping -c 3 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.790 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.407 ms
64 bytes from 172.17.0.3: seq=2 ttl=64 time=0.597 ms

--- 172.17.0.3 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.407/0.598/0.790 ms 
```
このように問題なくPingが通っており、通信できていることが分かりますね。

# 最初から存在するNetwork `host` と `none`とは？
先ほど表示したデフォルトで存在するNetwork一覧には、`bridge`以外にも`host` と `none`というNetwowrkが存在しました。
こいつらは何者でしょう？
```bash
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
cc1741c9feca   bridge    bridge    local
d1b5ab168f8c   host      host      local
ccade9920c46   none      null      local
```
### `host`
`host` はその名の通りホストマシン、つまりDockerを起動しているパソコン環境に対して直接接続することを可能にします。
通常、コンテナに対してホストから通信するためには、ホストとコンテナのポートアサイン設定を`-p`を使ってする必要があります。
```bash
# Network：　`host`じゃない場合(指定なしなので `bridge`に接続)
$ docker run -p 3000:3000 nginx
```
こうすることで、`http://localhost:8080`にアクセスしたら　nginxコンテナの3000番ポートにルーティングされるようになります。

ですが、`host`Networkにアサインされていると、このポート設定が不要になります。
**コンテナ起動時に利用するNetworkをアサインするには、`--network`オプションを使います。**
```bash
# Network：　`host`の場合
$ docker run --network=host nginx
```
（実際の業務で`host`にわざわざ繋げるケースはみたことがありませんが。）


### `none`
`none`はネットワークとして完全に遮断された環境になります。
よって、コンテナ間の通信もできませんし、外のインターネットに通信することもできません。
以下の例では、`8.8.8.8`というIPアドレスを持つ外部のサーバーにPingを試みていますが、失敗しています。
つまり、外部のインターネットにも出れてないことがわかります。

```bash
$ docker run --network=none --rm alpine ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: sendto: Network unreachable
```

# 新しくNetworkを作成する
ここからが本題。
これまではデフォルトで作成されている Bridge Network(`docker0`)を使ってコンテナ同士が通信できることを確認しました。
ですが、Dockerでは自分で独自のNetworkを作成できます。

## 分割されたNetworkにコンテナをアサインする
複数のコンテナを作成した時、たとえばDocker Composeでコンテナのオーケストレーションをするとき、余計なコンテナから通信される恐れがないように、独立したNetworkがあると便利なケースがあります。

そこで、コンテナを３つ以下の図のように通信できるようにしたいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181439/ee5104cd-01c7-8474-9415-555c3d7f5a6f.png)

つまり、やることは、
1. 新しいNetwork(`my-network-1`, `my-network-2`)を作成
1. ３つのコンテナを図のとおりのNetworkにアサインしながら起動
1. 同じNetworkのコンテナ同士は通信できることの確認
1. 違うNetworkのコンテナ同士は通信できないことの確認

## 1. 新しいNetwork(`my-network-1`, `my-network-2`)を作成
作ってコンテナ同士が通信できない隔離されたNetworkを作ってみます。
```bash
$ docker network create my-network-1

$ docker network create my-network-2

$ docker network list

NETWORK ID     NAME           DRIVER    SCOPE
f45339902c65   bridge         bridge    local
d1b5ab168f8c   host           host      local
1a89dd619396   my-network-1   bridge    local
88bbf8181e33   my-network-2   bridge    local
fa986af0498c   myNet1         bridge    local
ccade9920c46   none           null      local
```

無事に２つのNetworkを作成することができました。

## 2. コンテナを起動
次に、先図の通りに**Networkにアサインしながらコンテナを起動しましょう。**
起動するコンテナのイメージは何でもいいです。
起動時に`--network`オプションをつければOKです。
```bash
$ docker run -d --rm --network=my-network-1 --name=container-a-1 alpine sleep 5000

$ docker run -d --rm --network=my-network-1 --name=container-b-1 alpine sleep 5000

$ docker run -d --rm --network=my-network-2 --name=container-a-2 alpine sleep 5000
```
実際にNetworkにアサインされているか確認しましょう。
```
$ docker network inspect my-network-1
[
(...中略...)
        "Containers": {
            "4480600ee74b6014b46d21b27ff3d7791b73d7031ea2b0d546d089017bfbccf2": {
                "Name": "container-b-1",
                "EndpointID": "d004d94aca29735cd2befa3c7776ed1bc43f96bae9e58a076aea773dc87e7d75",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "c78e9f31b951c56c053f9827c1310a0b1a524865670b1165dfde6649fcbd5fe0": {
                "Name": "container-a-1",
                "EndpointID": "edbe76e9127d69d207dc45a0d138ad9d70df58ba1c6fc0e40f49b51b75a93130",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
(...中略...)
]
```

```bash
$ docker network inspect my-network-2
[
(...中略...)
        "Containers": {
            "02f65eb6ad4e69a3930977f0ec53c56bc5365fe2d400cea2a039ce11e87485d5": {
                "Name": "container-a-2",
                "EndpointID": "1b17449196f4260b377b1d733905c587c8a7136476d6af6cf5fac6e75c08bf7a",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
(...中略...)
]
```

良さそうですね！

## 3. 同じNetworkのコンテナ同士は通信できることの確認
これは先ほどやった同じNetworkのコンテナ通信と同じですね。
`container-a-1` -> `container-b-1`(IPAddress: `172.18.0.3`)にPingしてみましょう。
```bash
$ docker exec container-a-1 ping -c 3 172.18.0.3
PING 172.18.0.3 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.560 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.370 ms
64 bytes from 172.18.0.3: seq=2 ttl=64 time=0.280 ms

--- 172.18.0.3 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.280/0.403/0.560 ms
```

OKですね！

### Docker名前解決機能で、コンテナ名で通信可能
同じNetworkのコンテナ間で通信することができることは確認しました。
しかし、ここで疑問が起きます。

**「通信先のIPアドレスを知っていないと通信できないのでは？」**

実際の業務では、コンテナがどのIPアドレスがアサインされているか確認しないと通信できないと非常に不便です。

たとえば、`app`というコンテナと`api`というWeb APIサーバコンテナを立ち上げた時に、`app`からAPIを呼びたいときに、以下のようにIPアドレスを直打ちするのは現実的ではありません。
```bash
# appコンテナからapiコンテナにHTTPリクエストを送りたい
$ curl http://{apiコンテナのIPアドレス}/...
# -> apiコンテナのIPアドレスってなに？？？
```

そんなケースをカバーできる機能として、Dockerには名前解決の機能が用意されています。
先ほどの`container-a-1` -> `container-b-1`にPingした例では、Pingの送信先をIPアドレスで表現しました。
ですが、同じNetworkにいるコンテナ同士であれば、**コンテナ名を指定することでDockerデーモンの内臓DNSによって通信が可能**です。
```bash
# IPアドレスではなく、コンテナ名を送信先に指定
$ docker exec container-a-1 ping -c 3 container-b-1
PING container-b-1 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.118 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.227 ms
64 bytes from 172.18.0.3: seq=2 ttl=64 time=0.187 ms

--- container-b-1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.118/0.177/0.227 ms
```

問題なく通信できていることが分かります。
この機能は、Docker Composeで複数コンテナを立ち上げて通信連携する際によく使われる機能です。

これによって、先の`app`と`api`コンテナの例で言うと
```bash
# appコンテナからapiコンテナにHTTPリクエストを送る
$ curl http://api/...
```
これで通信OKということです。
初見ですと、`http://example.com/`みたいにドメインの形式になっていないのに違和感を感じるかもしれませんが、これがDockerの機能です。

## 4. 違うNetworkのコンテナ同士は通信できないことの確認
逆にNetworkが違うコンテナ同士の通信を確認します。
`container-a-1` -> `container-a-2`(IPAddress: `172.19.0.2`)にPingしてみましょう。
```bash
$ docker exec container-a-1 ping -c 3 172.19.0.2
PING 172.19.0.2 (172.19.0.2): 56 data bytes

--- 172.19.0.2 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss
```

はい、**Networkが違うコンテナ同士は直接通信することができない**ようです。
当たり前ですが、**コンテナ名による名前解決もしてくれません。**
```bash
$ docker exec container-a-1 ping -c 3 container-a-2
ping: bad address 'container-a-2'
```

このようにNetworkを独自に作成することで、

- Networkを分けれることで、不要なコンテナ間通信を防げる
- 同一Network内のコンテナ間通信を名前解決によって容易にできる

といったメリットがあります。

# Docker講義動画を Udemy で配信中（クーポンあり）
今回の内容を含むUdemyの動画コースを配信しています。

動画版の方が丁寧に説明しているのと、**動画版にしかないコンテンツも多くあります。**

以下の問いに答えられますか？少しでも不安がある方はぜひ講義に触れてみてください。
- Dockerビルドコンテキストとは何か？
- VolumeマウントとBindマウントの違いとは？
- マルチステージビルドはどういうシーンで使う？

以下リンクからだと割引で買えます！

https://dockercourse.page.link/coupon-qiita