Dockerのネットワークを調べ直してみた


- 最近 Docker ネットワークについて調べたので、内容をまとめました.
- Docker のバージョンは`20.10.12`です。
- 何かお気づきの点があればコメント頂けるとありがたいです。

# 抑えておきたいポイント・用語

- Docker ネットワークを理解する上で押さえておきたい用語や概念をまとめました

## OSI 参照モデル

- 国際標準化機構（ISO）がコンピュータの通信方法を統一するために策定した規格
- ネットワークを 7 階層に分け、各層のルール（プロトコル）を規定している
- Docker のネットワークを理解する上で「第 2 層：データリンク層」「第 3 層：ネットワーク層 」を抑える必要がある

  | 層                 | 特徴                                | 伝送単位 |
  | ------------------ | ------------------------------------ | ------ |
  | ネットワーク層(L3) | IP アドレスをもとに、通信相手を識別する | パケット |
  | データリンク層(L2) | MAC アドレスをもとに、ネットワーク内の隣接する機器間の通信を制御する | フレーム |

- L3：ネットワーク層のデータの転送単位を「パケット」と呼び、「データ」＋「ヘッダー(IPアドレス等)」から構成される
- L2：データリンク層のデータの転送は「フレーム」と呼び、「パケット」＋「ヘッダー(MACアドレス等)」から構成される

## ブリッジ

- 複数のネットワーク・セグメントの中継器
- データリンク層(L2)で動作する
- どの端末のMACアドレスがどのセグメントに属するかを記憶している
- データを転送する時は、フレームの MAC アドレスを参照し、宛先の端末が属するセグメントにのみそのフレームを転送する。
- この時、送信対象のセグメントに属する全ての端末にフレームをブロードキャストする。送信されたフレームは、宛先の端末のみが処理し、それ以外の端末は無視する

## 仮想ブリッジ

- ホスト上に仮想的な L2 ネットワークを構成する機能
- Docker をインストールすると、ホストに`docker0`とい名前の仮想ブリッジが作成される

## 仮想NIC (veth)

- 仮想的なネットワークインターフェース
- コンテナを起動すると 下記の veth ペアが作成される
  1. ホストの仮想ブリッジに接続される veth
  1. コンテナに作成される NIC (実体は veth)
- コンテナとホストの通信はこのveth間で行われる

## IPマスカレード

- グローバルIPアドレスとプライベートIPアドレスを変換する技術
- 1つのグローバルIPアドレスと複数のポート番号を組み合わせることで、複数のプライベートIPアドレスを変換できる
- NAPTとも呼ばれる

## 名前空間

- 各コンテナのネットワーク空間は独立している。コンテナが属するネットワーク空間を識別する概念。

# Docker ネットワーク

- Dockerネットワークの概念図は以下
- ホストのNICに仮想ブリッジが接続される。Dockerが利用する仮想ブリッジのデフォルトは`docker0`
- ホストの仮想ブリッジに仮想NIC(veth)が接続される
- ホストの仮想NIC(veth)とコンテナのNICが通信する
- 各コンテナのネットワークは分離している(名前空間が異なる)
- コンテナが外部と通信する際、コンテナのプライベートIPが、ホスト側のグローバルIPでIPマスカレードされる

![docker-network-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361228/2a76785b-d094-1f95-fdcd-3517350f67a9.png)


## Dockerネットワークの種類

### bridge

- ホストの仮想ブリッジを利用するネットワーク
- Dockerをインストールすると、デフォルトで`bridge`という名前のbridgeネットワークが作成される
- `docker network create`でDockerネットワークを作成する際、`-d`オプションに未指定 または `bridge`を指定した場合、bridgeネットワークが作成される
- `docker run`でコンテナを起動する際、`--net`に未指定 または `bridge`を指定した場合、デフォルトのbridgeネットワークが利用される。
  - デフォルトの`bridge`に対応する仮想ブリッジは`docker0`

### host

- ホストの物理NICを直接利用して外部と接続するネットワーク (ホストとコンテナのネットワーク設定が同じ)
- Dockerをインストールすると、デフォルトで`host`という名前のhostネットワークが作成される

### none

- コンテナをホストのネットワークに接続しない
- Dockerをインストールすると、デフォルトで`none`という名前で空のネットワークが作成される

### docker-compose を利用する場合

- `docker-compose` でコンテナを起動する際、`networks`を指定しなかった場合、`<Composeファイルの配置先ディレクトリ>_default`という名前のbridgeネットワークが作成される
- この時作成されるbridgeネットワークに対応する仮想ブリッジは、docker-composeでコンテナを起動した際、自動で作成される
- `docker-compose` で起動したコンテナを`host`ネットワークに接続する場合、`network_mode`に`host`を指定する

## 動作検証1( docker コマンドを利用する場合)

### ホストのDocker ネットワーク一覧

- ホストに作成されているDockerネットワークを確認する。`docker network ls`を実行
- 結果は以下。bridge / host / none のネットワークが作成されていることがわかる

  ```
  NETWORK ID     NAME                DRIVER    SCOPE
  64f5cef2d77d   bridge              bridge    local                                                                                                                                                                
  f887f0e6ecc7   host                host      local                                                                                                                                                                
  8d87bf258e89   none                null      local                                                                                                                                                                
  ```

### ホストのネットワークを確認する

- `ip a`を実行。
- 結果は以下。docker0 仮想ブリッジが作成されていることがわかる
- docker0に割り当てられているIPアドレスは` 172.17.0.1/16`

  ```
  6: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:9b:df:82:32 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
    <略>
  ```

### コンテナを起動する

- 下記のコマンドでコンテナを起動する

  ```
  docker run -d --name my-nginx -p 8081:80 nginx
  ```

### ホストのDocker ネットワークの一覧

- 再度、ホストに作成されているDockerネットワークを`docker network ls`で確認する
- 結果は以下。コンテナ起動前と同じ。新たなネットワークは作成されていない

  ```
  NETWORK ID     NAME                DRIVER    SCOPE
  64f5cef2d77d   bridge              bridge    local
  f887f0e6ecc7   host                host      local
  8d87bf258e89   none                null      local
  ```

### Docker ネットワークの確認

- `docker network inspect bridge`で、bridgeネットワークの詳細を確認する
- 結果は以下の通り
    - ゲートウェイアドレスが`172.17.0.1`で、`docker0`に設定されているアドレスと一致している
    - コンテナには`172.17.0.2/16`のアドレスが割り振られている
```
$ docker network inspect bridge                                                                                                                                                                                   [
    {
        "Name": "bridge",
        "Id": "1e38e4596ba1f79f4b29cfd865b0572e0dcda647eddb68d34639f0c6717b2393",
        "Created": "2022-04-09T23:39:22.72754133Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "eab19be6f0a5267829d26c93143214cc9b384383506439a7bbdc067e2a1196c5": {
                "Name": "my-nginx",
                "EndpointID": "6679fdcf5b08cde2019cbeb75cfa5f0a3cae8155087508be5c7c89317689ab1d",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/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": {}
    }
]
```

### コンテナのネットワークを確認する

- `docker exec -it`により、起動したコンテナにログインし、`ip a`によりネットワーク設定を確認する
- 結果は以下の通り。`(docker inspect`の実行結果から確認した)、コンテナに割り振られたIPアドレス`172.17.0.2`がコンテナのNICのIPアドレスに設定されている

```
  10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
    <略>
```

## 動作検証2( docker-compose を利用する場合)

### 確認用の docker-compose の作成

- sample001 ディレクトリを作成し、下記の docker-compose ファイルを直下に配置する

  ```
  services:
    nginx1:
      container_name: my-nginx1
      image: nginx:latest
      ports:
      - 8081:80/tcp
    nginx2:
      container_name: my-nginx2
      image: ngninx:latest
      ports:
      - 8082:80/tcp
  ```

### docker-compose でコンテナを起動する

- `docker-compose up -d --build`を実行
- 結果は以下。
  - デフォルトのドライバ設定(＝bridge)で、`sample001_default`ネットワークを作成していることがわかる
  ``` 
  Creating network "sample001_default" with the default driver                                                                                                                                                      
  Pulling nginx1 (nginx:latest)...                                                                                                                                                                                  
    :
    略
    :
  Status: Downloaded newer image for nginx:latest                                                                                                                                                                   
  Creating my-nginx1 ... done                                                                                                                                                                                       
  Creating my-nginx2 ... done                                                       
  ```

### Docker ネットワークの一覧

- `docker network ls`を実行。
- 結果は以下。`sample001_default` という bridge ネットワークが新たに作成されている
  ```
  NETWORK ID     NAME                DRIVER    SCOPE
  64f5cef2d77d   bridge              bridge    local                                                                                                                                                                
  f887f0e6ecc7   host                host      local                                                                                                                                                                
  8d87bf258e89   none                null      local                                                                                                                                                                
  a2808b008bb9   sample001_default   bridge    local   ★ 新たに作成されたネットワーク
  ```

### ホストのネットワークを確認する

- ホストのネットワークを確認する。`ip a`を実行
- 結果は以下の通り。
  - 仮想ブリッジ(16: br-f03cb12e5aff) と 仮想NICが2つ (18, 20：vetchXXX) 作成されている
  - 各仮想NIC(vethXXXX)は仮想ブリッジ(br-f03cb12e5aff)に接続されている
 
  ```
  16: br-f03cb12e5aff: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:69:48:b3:f7 brd ff:ff:ff:ff:ff:ff
    inet 172.20.0.1/16 brd 172.20.255.255 scope global br-f03cb12e5aff
    略
  18: vethe513542@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f03cb12e5aff state UP group default
    link/ether ca:a3:f2:32:c4:bc brd ff:ff:ff:ff:ff:ff link-netnsid 0
    略
  20: veth251227c@if19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-f03cb12e5aff state UP group default
    link/ether 0a:7a:ad:98:98:b4 brd ff:ff:ff:ff:ff:ff link-netnsid 1
    略
  ```

### ネットワークブリッジを確認する

- `brctl show`で、ホストのネットワークブリッジの設定を確認する
- 結果は以下。
  - 仮想ブリッジ(16: br-f03cb12e5aff)に仮想NIC(vethe513542/veth251227c)が接続されていることがわかる

  ```
  bridge name        bridge id               STP enabled     interfaces                                                                                                                                                
  br-f03cb12e5aff    8000.02426948b3f7       no              veth251227c  vethe513542
  ```

### Dockerネットワークの詳細を確認する

- `sample001_default`の詳細を確認する。`docker network inspect sample001_default`を実行。
- 結果は以下の通り。
  - サブネットは、172.20.0.0
  - ゲートウェイは、172.20.0.1。これは、`docker-compose`で作成された仮想ブリッジ(br-5870201d8e2b)のIPアドレスと一致
  - コンテナには、`172.20.0.3`と`172.20.0.2`が設定されている。
  ```
  [                                                                                                                                                                                                                     
    {                                                                                                                                                                                       
       "Name": "sample001_default",
       "Id": "f03cb12e5affb4261fd8c8959596848b5d2b82efa9e0b7fd777446f74c27b8d7",
       "Created": "2022-04-10T08:58:16.43048963+09:00",
       "Scope": "local",
       "Driver": "bridge",
       "EnableIPv6": false,
       "IPAM": {                                                                                                                                                                                                             
         "Driver": "default",
         "Options": null,                                                                                                                                                                                                  
         "Config": [
           {                                                                                                                                                                                                                     
             "Subnet": "172.20.0.0/16",
             "Gateway": "172.20.0.1"
           }
         ]
       },
       "Internal": false,
       "Attachable": true,
       "Ingress": false,
       "ConfigFrom": {
         "Network": ""
       },
       "ConfigOnly": false,
       "Containers": {
          "c2030004bc1daeb7b266715035fd9bbc4b97d03dc9c39199bb9ed04a00b43896": {
             "Name": "my-nginx1",
             "EndpointID": "9191d6c32b88b3642b95869084133e009e24f48e2c94bf79d670fa98a2295e5a",
             "MacAddress": "02:42:ac:14:00:03",
             "IPv4Address": "172.20.0.3/16",
             "IPv6Address": ""
          },
          "e73a7929bc179c2e308674b1f71c722bf6f88ce7add8cb6db056a72dbd412dba": {
             "Name": "my-nginx2",
             "EndpointID": "590b155e49faab6ea091bb1f32af55241fa9d338250baa3f7dbaead29ce791c9",
             "MacAddress": "02:42:ac:14:00:02",
             "IPv4Address": "172.20.0.2/16",
             "IPv6Address": ""
          }
      },
      "Options": {},
      "Labels": {
         "com.docker.compose.network": "default",
         "com.docker.compose.project": "sample001",
         "com.docker.compose.version": "1.25.0"
      }
    }
  ]
  ```

### コンテナのネットワークを確認する

- `docker exec -it`で各コンテナに入り、`ip a`でネットワークを確認する。
- 結果は以下の通り。
  - `docker inspect` で表示されている通り、各コンテナに割り振られた IP アドレスが NIC に設定されている。
  ```
  # コンテナ1(my-nginx1)
  19: eth0@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:14:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.0.3/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
  ```
  ```
  # コンテナ2(my-nginx2)
  17: eth0@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:14:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.20.0.2/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
  ```


以上