34
37

More than 3 years have passed since last update.

6つの演習でだいたい分かるDockerネットワーク

Last updated at Posted at 2020-08-16

序論

本稿は 元市役所職員がWEBプログラマに転職するまでのロードマップ の連載記事の一部です。

前回: Dockerに慣れながら学ぶApacheサーバの基礎

WSL2 + Ubuntu 20.04 + Docker 環境における内部ネットワーク

WSL2 環境では、下図のようにHyper-Vの仮想ネットワーク上に、WindowsカーネルとLinuxカーネルが共存しています。

Hyper-Vでは、IPv4アドレスとして 172.16.0.0172.31.255.255 までのプライベートアドレスが使われており、これを複数に分割して利用することで、独立した TCP/IP スタックとして各カーネル(OS)が動作するという仕組みになっています。

wsl2-docker-network.png

Dockerは、Linuxカーネル上でまた別の仮想ネットワークを構成しており、通常は 172.17.0.0/16 のネットワーク範囲が docker0 の仮想ブリッジとして動作します。

サブネットマスクについて

サブネットマスクはネットワーク範囲を定義するために使用されます。

ネットワークアドレスとサブネットマスクを組み合わせることで、ネットワークのIPv4範囲(サブネット)を指定することができます。

サブネットマスクのIPv4表現は、以下のような対応となっています。

サブネットマスク IPv4表現
/1 128.0.0.0
/2 192.0.0.0
/3 224.0.0.0
/4 240.0.0.0
/5 248.0.0.0
/6 252.0.0.0
/7 252.0.0.0
/8 255.0.0.0
: :
/16 255.255.0.0
/24 255.255.255.0
/32 255.255.255.255

docker0 のネットワーク範囲 172.17.0.0/16 を計算すると以下のようになります。

\ (1) (2) (3) (4)
ネットワークアドレス 172 17 0 0
2進数表記 1010 1100 0001 0001 0000 0000 0000 0000
サブネットマスク(/16) 255 255 0 0
2進数表記 1111 1111 1111 1111 0000 0000 0000 0000
IP IP(1) IP(2) IP(3) IP(4)

ネットワークアドレスは、IPとサブネットマスクの論理積から算出されるため、逆算すると IP(1)~(4) は以下のようになります。

  • IP(1):
    • 1010 1100 以外の答えはない
    • => 10進数表記: 172
  • IP(2):
    • 0001 0001 以外の答えはない
    • => 10進数表記: 17
  • IP(3):
    • 0000 00001111 1111 の範囲が答えとなる
    • => 10進数表記: 0255
  • IP(4):
    • 0000 00001111 1111 の範囲が答えとなる
    • => 10進数表記: 0255
    • ただし、最下位ビットに 0, 255 は使えないため 1254 が答えとなる

以上より docker0 ネットワークでは、172.17.0.1172.17.255.254 のIPv4アドレスが割り当て可能という答えが得られます。

ただし、最下位ビット 1 はゲートウェイとして利用されるため、実際に各コンテナには 172.17.0.2172.17.255.254 のIPv4アドレスが割り振られることになります。

localhost について

localhost は、自分自身を表現する特別なIPアドレスで 127.0.0.1 (IPv6: ::0) と表記されます。

Hyper-V(WSL)環境では、Windows 10 や Linux へのアクセス振り分け用の仮想スイッチが構築されており、この規定スイッチにより localhost の指し示す先が変わります。

通常は、WSL2 環境で最初に作成した Linux カーネル(本稿の環境では Ubuntu 20.04)が規定スイッチに設定されるため、localhost (127.0.0.1) = eth0 (Ubuntu 20.04 の IPv4: 172.X.Y.Z) となります。
wsl --set-default でデフォルトカーネルを変更した場合は、設定されているデフォルトLinuxカーネルの TCP/IP スタックが localhost となります)

WSL2上のLinuxカーネルから外部ネットワークアクセスできる理由

前述の通り、WSL2上のLinuxカーネルは物理イーサネットとは異なる TCP/IP スタック上で動作しているため、通常そのままでは外部ネットワークにアクセスできません。

この問題を解決するために、WSL2は仮想的なNATを構築し、各仮想イーサネットのプライベートIP (172.16.0.0172.31.255.255) をグローバルIP (物理イーサネットIP: 192.168.0.1 など。環境により異なる) に変換しています。

wsl2-nat.png

docker-compose デフォルトネットワーク

docker-compose においては、ネットワークを指定しない場合、新規仮想ネットワークが構築され、任意IPのネットワークアドレスが割り当てられます。

作成された仮想ネットワークは docker network ls コマンドで確認できます。

さらに docker network inspect <network_name> コマンドを使うことで、その仮想ネットワークのIPアドレスや、ネットワークに所属する各コンテナを確認することも可能です。

演習課題 01

  1. 以下のような docker-compose.yml ファイルを作成する
    • busybox イメージを使い、server サービスコンテナと client サービスコンテナを定義
    • どちらのサービスコンテナも tty: true を指定し、ターミナル利用可能に設定する
  2. 上記で作成した docker-compose.yml をビルド&起動した上で、自動作成された仮想ネットワークおよび各コンテナのIPアドレスを確認する
    • docker network ls コマンドで自動作成された(と思われる)仮想ネットワークを特定
    • docker network inspect <network_name> コマンドで仮想ネットワークおよび各コンテナのIPアドレスを確認
  3. server サービスコンテナ内で hostname -i コマンドを使ってコンテナIPを確認
    • docker network inspect <network_name> コマンドで確認したIPと一致することを確認
  4. server サービスコンテナ内で nc コマンドを使って、ポート8888番で簡易サーバを起動する
    • busybox の nc コマンドは -l オプションでリスンモード(サーバモード)
    • busybox の nc コマンドは -p <port> オプションで指定ポートを開く
  5. 上記とは別のターミナルを開き、client サービスコンテナ内で nc コマンドを使って server コンテナに接続する
    • nc <server_ip> <server_port> コマンドで接続可能
      • server_ip: 2, 3 の手順で確認した server サービスコンテナのIP
      • server_port: 4 の手順で開いた 8888 番ポート
    • 接続したら test と入力し、4 のターミナル(server サービスコンテナの簡易サーバ)で test という文字列を受信できるか確認

実装

参照: 4-network/1-default/docker-compose.yml

動作確認

# -- user@localhost

# 4-network/1-default/ ディレクトリに移動
# cd /path/to/4-network/1-default/

# 他のDockerコンテナは停止しておく
$ docker stop $(docker ps -q)

# 1. コンテナビルド&起動
$ docker-compose up -d

# 2-1. docker仮想ネットワークを全て確認
$ docker network ls
NETWORK ID    NAME               DRIVER  SCOPE
95acdcfc0b73  1-default_default  bridge  local # <= 今回自動作成された仮想ネットワーク
1bc6154bf7aa  bridge             bridge  local # <= dockerデフォルトネットワーク(docker0)
995f007b4724  host               host    local # <= Linuxホストネットワーク(localhost)
3390eef98586  none               null

# 2-2. 1-default_default ネットワークと所属するコンテナを一覧表示
$ docker network inspect 1-default_default
[
    {
        "Name": "1-default_default",
        # ...
        "IPAM": {
            # ...
            "Config": [
                {
                    "Subnet": "192.168.112.0/20", # ネットワーク範囲
                    "Gateway": "192.168.112.1"
                }
            ]
        },
        # ...
        "Containers": {
            "3910e5b9cc840bdfba2e91558ca08f5ecbcad8abb63d3a31d6ddcb24b5c29ad1": {
                "Name": "1-default_server_1",
                # ...
                "IPv4Address": "192.168.112.2/20", # serverサービスコンテナIP
            },
            "e58bfe80e657a851e645bae6ca40ced9fc19be13e1fd1496d6e9ebcac1862bfc": {
                "Name": "1-default_client_1",
                # ...
                "IPv4Address": "192.168.112.3/20", # clientサービスコンテナIP
            }
        },
        # ...
    }
]

# 3. serverサービスコンテナ内で hostname -i コマンドを使ってIP確認
$ docker-compose exec server hostname -i
192.168.112.2 # <= docker network inspect 1-default_default で確認したIPと同じはず

# 4. serverサービスコンテナで nc 簡易サーバ(ポート: 8888)起動
$ docker-compose exec server nc -lp 8888

# => アイドル状態になるため、一旦このままにして別のターミナル起動
# -- user@localhost

# 4-network/1-default/ ディレクトリに移動
# cd /path/to/4-network/1-default/

# 5. clientサービスコンテナで serverサービスコンテナの nc 簡易サーバ(ポート: 8888)に接続
## 接続先(serverサービスコンテナ)のIPは、上記で確認したIPを指定
$ docker-compose exec client nc 192.168.112.2 8888

# => 接続されると、入力待ち状態になる

### <stdin>
# test と入力してエンター
test
# => serverコンテナ側のターミナルで「test」という文字列が受信されればOK

# => Ctrl + C で終了(serverサービスコンテナ側も同様)
### </stdin>

docker-server-client.png

1-default_network.png

後片付け

# docker-compose down するとコンテナと一緒に仮想ネットワークも削除される
$ docker-compose down

# 1-default_default ネットワークが削除されているか確認
$ docker network ls
NETWORK ID    NAME               DRIVER  SCOPE
1bc6154bf7aa  bridge             bridge  local # <= dockerデフォルトネットワーク(docker0)
995f007b4724  host               host    local # <= Linuxホストネットワーク(localhost)
3390eef98586  none               null

同一ネットワーク内における名前解決

docker環境では、同一ネットワークに所属しているコンテナ同士は、IPではなくサービス名で通信することができます。

ただしそのためには、各コンテナの所属ネットワーク名を明示する必要があります。

services:
  <service_name>:
    # ↓ 自動作成されるデフォルトの仮想ネットワークに所属していることを明示する場合
    networks:
      - default

演習課題 02

  1. 演習課題 01 で作成した docker-compose.yml を修正し、各コンテナがデフォルトの仮想ネットワークに所属することを明示
  2. コンテナをビルド&起動した上で、server サービスコンテナで nc 簡易サーバ(ポート: 8888)を起動
  3. client サービスコンテナから、server というサービス名で server サービスコンテナに nc 接続できるか確認

実装

参照: 4-network/2-resolv/docker-compose.yml

動作確認

# -- user@localhost

# 4-network/2-resolv/ ディレクトリに移動
# cd /path/to/4-network/2-resolv/

# コンテナビルド&起動
$ docker-compose up -d

# serverサービスコンテナで nc 簡易サーバ(ポート: 8888)起動
$ docker-compose exec server nc -lp 8888

# => アイドル状態になるため、一旦このままにして別のターミナル起動
# -- user@localhost

# 4-network/2-resolv/ ディレクトリに移動
# cd /path/to/4-network/2-resolv/

# clientサービスコンテナから serverサービスコンテナに nc 接続できるか確認
## IPの代わりにサービス名(server)で接続する
$ docker-compose exec client nc server 8888

### <stdin>
# test と入力してエンター
test
# => serverコンテナ側のターミナルで「test」という文字列が受信されればOK

# => Ctrl + C で終了(serverサービスコンテナ側も同様)
### </stdin>

# 後片付け
$ docker-compose down

一方向アクセス用の名前解決

現在ではすでに非推奨の方法となっていますが、links を利用することで一方向の名前解決も可能です。

例えば今回の場合、client サービスコンテナから server サービスコンテナへの一方向アクセスさえできれば良いため、client 側に links として server を定義すれば、明示的にネットワーク所属を指定しなくても名前解決ができます。

version: "3"

# 自動作成される docker-compose ネットワークに所属するコンテナ
services:
  # server サービスコンテナ
  server:
    image: busybox
    tty: true

  # client サービスコンテナ
  client:
    image: busybox
    tty: true
    # links で server サービスコンテナとリンク
    links:
      # <サービス名>:<エイリアス名> で定義
      # <サービス名> のみ記述した場合は エイリアス名 = サービス名 となる
      - server:target

上記のように設定すれば、client サービスコンテナから target というエイリアス名で server サービスコンテナに接続できます。

ネットワークアドレスを指定して仮想ネットワークを作成

これまでは任意のIPアドレスに自動で仮想ネットワークを作成させていましたが、ネットワークアドレスを指定して、各コンテナのIPアドレスの割り当てを手動で行うことも可能です。

手動でIPの割り当てを行うことで、常に同一のIPでコンテナにアクセスできるようになります。

なお、この場合 サービス名による名前解決はできません

# 作成する仮想ネットワークを定義
networks:
  # 仮想ネットワーク名指定
  <network_name>:
    driver: bridge # ドライバは基本的に bridge を指定
    ipam:
     driver: default
     config:
       # 仮想ネットワークのネットワーク範囲を指定
       ## 172.68.0.0/16 の場合、172.68.0.1 ~ 172.68.255.254 のIPアドレスを割り振れる
       ## ただし 172.68.0.1 はゲートウェイに使われる
       - subnet: 172.68.0.0/16

# 作成するサービスコンテナ
services:
  <service_name>:
    # 所属ネットワークを指定
    networks:
      <network_name>:
        # 172.68.0.0/16 のネットワーク範囲で任意のIPアドレスを設定できる
        ipv4_address: "172.68.0.2"

演習課題 03

  1. 以下のような docker-compose.yml ファイルを作成する
    • ネットワーク範囲 178.27.0.0/16 の仮想ネットワーク app_net を作成
    • busybox イメージを使い、server サービスコンテナと client サービスコンテナを定義
      • server サービスコンテナ:
        • tty: true を指定してターミナル利用可能に
        • ネットワーク: app_net に所属
        • IPv4: 178.27.0.10 を指定
      • client サービスコンテナ:
        • tty: true を指定してターミナル利用可能に
        • ネットワーク: app_net に所属
        • IPv4: 178.27.0.20 を指定
  2. 上記で作成した docker-compose.yml をビルド&起動した上で、作成された仮想ネットワークおよび各コンテナのIPアドレスを確認する
  3. コンテナをビルド&起動した上で、server サービスコンテナで nc 簡易サーバ(ポート: 8888)を起動
  4. client サービスコンテナから server サービスコンテナに nc 接続できるか確認
    • IP: 178.27.0.10 で接続

実装

参照: 4-network/3-ip/docker-compose.yml

動作確認

# -- user@localhost

# 4-network/3-ip/ ディレクトリに移動
# cd /path/to/4-network/3-ip/

# コンテナビルド&起動
$ docker-compose up -d

# docker仮想ネットワークを全て確認
$ docker network ls
NETWORK ID    NAME          DRIVER  SCOPE
7e3e3715318c  3-ip_app_net  bridge  local # <= 今回作成した app_net 仮想ネットワーク
1bc6154bf7aa  bridge        bridge  local # <= dockerデフォルトネットワーク(docker0)
995f007b4724  host          host    local # <= Linuxホストネットワーク(localhost)
3390eef98586  none          null

# 3-ip_app_net ネットワークと所属するコンテナを一覧表示
$ docker network inspect 3-ip_app_net
[
    {
        "Name": "3-ip_app_net",
        # ...
        "IPAM": {
            # ...
            "Config": [
                {
                    "Subnet": "178.27.0.0/16" # ネットワーク範囲
                }
            ]
        },
        # ...
        "Containers": {
            "72499eace4d3eaeed6d96ceb2f2c58b06acc5e53a944d25db986529ae01f6bef": {
                "Name": "3-ip_client_1",
                # ...
                "IPv4Address": "178.27.0.20/16", # clientサービスコンテナのIP
            },
            "97bc9ce7083b1331243e165f657bdf71e9d958fa0288a0389ef0c49630fcd4a3": {
                "Name": "3-ip_server_1",
                # ...
                "IPv4Address": "178.27.0.10/16", # serverサービスコンテナのIP
            }
        },
        # ...
    }
]

# serverサービスコンテナで nc 簡易サーバ(ポート: 8888)起動
$ docker-compose exec server nc -lp 8888

# => アイドル状態になるため、一旦このままにして別のターミナル起動
# -- user@localhost

# 4-network/3-ip/ ディレクトリに移動
# cd /path/to/4-network/3-ip/

# clientサービスコンテナで serverサービスコンテナの nc 簡易サーバ(ポート: 8888)に接続
## 接続先: 178.27.0.10 => serverサービスコンテナ
$ docker-compose exec client nc 178.27.0.10 8888

### <stdin>
# test と入力してエンター
test
# => serverコンテナ側のターミナルで「test」という文字列が受信されればOK

# => Ctrl + C で終了
### </stdin>

# 後片付け
$ docker-compose down

docker のデフォルトブリッジネットワーク

最初に説明したとおり、docker デフォルトの仮想ネットワークとして、docker インストール時点で作成される docker0 ブリッジネットワークが存在しています。(大抵の場合は 172.17.0.0/16

この仮想ネットワークは docker network ls コマンドで確認したときに bridge という名前で見つけることができます。

docker-compose で各コンテナをこの bridge ネットワークに所属させたい場合は network_mode: bridge を設定する必要があります。

なお、デフォルト bridge ネットワーク内のコンテナ同士で名前解決を行うことはできないので、links で一方向リンクするか、IPアドレスで通信する必要があります。

※ ただし、デフォルトブリッジネットワークでは、各コンテナに固定のIPを振ることはできません。そのため、基本的にはユーザ定義のネットワークを使うことが望ましいです。

演習課題 04

  1. 以下のような2つのサービスコンテナをそれぞれ別のyamlファイルに定義する
    • server サービスコンテナ:
      • server.yml に定義
      • busybox イメージを利用
      • ターミナル利用可能に(tty: true
      • bridge デフォルトネットワークに所属
    • client サービスコンテナ:
      • client.yml に定義
      • busybox イメージを利用
      • ターミナル利用可能に(tty: true
      • bridge デフォルトネットワークに所属
  2. 上記2つのyamlファイルを docker-compose でビルド&起動する
    • docker-compose -f <yaml_file> up -d
  3. bridge デフォルトネットワークのIPと所属コンテナを確認する
    • 上記のように別のyamlファイルに定義されていたコンテナだとしても、同一のネットワーク範囲に配置されることを確認
    • 各コンテナのIPを確認
  4. servernc 簡易サーバ(ポート: 8888)を起動し、client から接続できるか確認する

実装

参照: 4-network/4-bridge/

動作確認

# -- user@localhost

# 4-network/4-bridge/ ディレクトリに移動
# cd /path/to/4-network/4-bridge/

# server.yml からコンテナビルド&起動
$ docker-compose -f server.yml up -d

# client.yml からコンテナビルド&起動
$ docker-compose -f client.yml up -d

# bridge ネットワークと所属するコンテナを一覧表示
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        # ...
        "IPAM": {
            # ...
            "Config": [
                {
                    "Subnet": "172.17.0.0/16", # ネットワーク範囲
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        # ...
        "Containers": {
            "b483d8f42002a59b8562702c858002a45be0fbb2260f12898a4e73cabf58aa1c": {
                "Name": "4-bridge_client_1",
                # ...
                "IPv4Address": "172.17.0.3/16", # clientサービスコンテナIP
                "IPv6Address": ""
            },
            "b8f8a67df6044a6df9278c41125503954bfa829b42807a2330152f37d5149255": {
                "Name": "4-bridge_server_1",
                # ...
                "IPv4Address": "172.17.0.2/16", # serverサービスコンテナIP
            }
        },
        # ...
    }
]

# serverサービスコンテナで nc 簡易サーバ(ポート: 8888)起動
$ docker-compose -f server.yml exec server nc -lp 8888

# => アイドル状態になるため、一旦このままにして別のターミナル起動
# -- user@localhost

# 4-network/4-bridge/ ディレクトリに移動
# cd /path/to/4-network/4-bridge/

# clientサービスコンテナで serverサービスコンテナの nc 簡易サーバ(ポート: 8888)に接続
## 接続先: 上記で確認したIP 172.17.0.2 => serverサービスコンテナ
$ docker-compose -f client.yml exec client nc 172.17.0.2 8888

### <stdin>
# test と入力してエンター
test
# => serverコンテナ側のターミナルで「test」という文字列が受信されればOK

# => Ctrl + C で終了
### </stdin>

# 後片付け
$ docker-compose -f server.yml down
$ docker-compose -f client.yml down

docker コンテナへのポートフォワーディング

以上のように、基本的に docker コンテナは、ホストLinuxとは別のネットワーク内で稼働するため、localhost から docker コンテナにアクセスすることは通常できません。

しかし、ports でポートフォワーディング設定を行うと、localhost の特定ポートと docker コンテナの特定ポートを接続することができます。

これにより localhost => コンテナ の一方向アクセスが可能です。

services:
  <service_name>:
    # ポートフォワーディング設定
    ports:
      # localhost:1234 と <service_name>コンテナ:80 を接続
      - "1234:80"

演習課題 05

  • 以下のような docker-compose.yml を作成する
    • web サービスコンテナ:
      • nginx:alpine イメージ利用
      • デフォルト bridge ネットワーク所属
      • localhost:1234service://web:80 にポートフォワーディング
      • ./index.html ファイルを service://web:/usr/share/nginx/html/index.html にマウント
        • 中身は Hello, nginx! とする
  • dockerコンテナをビルド&起動し http://localhost:1234 にアクセス確認
    • Hello, nginx! と表示されればOK

実装

参照: 4-bridge/5-port/

動作確認

# -- user@localhost

# 4-network/5-port/ ディレクトリに移動
# cd /path/to/4-network/5-port/

# コンテナビルド&起動
$ docker-compose up -d

# アクセスログ確認用にログフォロー
$ docker-compose logs -f web

# http://localhost:1234 にアクセス
# => 「Hello, nginx!」と表示されればOK
# => アクセスログが記録されることを確認したら Ctrl + C でログフォロー終了

# 後片付け
$ docker-compose down

docker-port_forwarding.png

host ネットワーク

最後に host ネットワークについて解説します。

これは文字通り、Linuxホストと同一のネットワーク(localhost)です。

localhost 上でコンテナを動かすことになるため、ポートフォワーディングは当然利用できません。
(コンテナのポート = localhostのポート になります)

docker-compose で各コンテナをこの host ネットワークに所属させたい場合は network_mode: host を設定する必要があります。

なお、デフォルト bridge ネットワークと同様に、host ネットワーク内のコンテナ同士も名前解決を行うことはできません。

また host ネットワーク上では、各コンテナのIPアドレスは全て 127.0.0.1 になってしまうため、IPアドレスによるアクセスもできません

さらに言えば links を使った 一方向リンク機能も使えません

そのため、host ネットワーク内のコンテナ間の通信を行いたい場合は、ポート番号で通信 するしかありません。

このように色々と制限も多いため、使う機会はあまり多くないかもしれません。

演習課題 06

  • 以下のような docker-compose.yml を作成する
    • web サービスコンテナ:
      • nginx:alpine イメージ利用
      • localhost 上で稼働: http://localhost:80 => service://web:80
      • ./index.html ファイルを service://web:/usr/share/nginx/html/index.html にマウント
        • 中身は Hello, nginx! とする
  • dockerコンテナをビルド&起動し http://localhost にアクセス確認

実装

参照: 4-bridge/6-host/

動作確認

# -- user@localhost

# 4-network/6-host/ ディレクトリに移動
# cd /path/to/4-network/6-host/

# コンテナビルド&起動
$ docker-compose up -d

# アクセスログ確認用にログフォロー
$ docker-compose logs -f web

# http://localhost にアクセス
# => 「Hello, nginx!」と表示されればOK
# => アクセスログが記録されることを確認したら Ctrl + C でログフォロー終了

# 後片付け
$ docker-compose down

docker-localhost.png

34
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
37