LoginSignup
55
46

More than 1 year has passed since last update.

【Docker】コンテナとNetworkの関係(Bridgeってなに?名前解決?)【初心者OK!Docker入門+応用シリーズ】

Last updated at Posted at 2022-10-31

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

はじめに

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

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

Docker Networkの概要

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

Docker Networkの一覧表示

$ 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にアサインされます。
それを確認してみましょう。

まずは適当なコンテナを立ち上げます

$ 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の詳細状態を確認してみます。

$ 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

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

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

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

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

つまり、現在のDockerのNetworkは以下のような状態になっているってことです。
image.png

複数コンテナが同じNetworkにつながっていると?

デフォルトでは、bridgeのNetworkにコンテナは接続されると説明しました。
では複数コンテナを接続するとどうなるでしょう?

一旦まっさらな状態から、新しくcontainer-1container-2を作成してみます。

$ 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

bridgeNetworkの状態を確認してみます。

$ 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

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

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

$ 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 hostnoneとは?

先ほど表示したデフォルトで存在するNetwork一覧には、bridge以外にもhostnoneというNetwowrkが存在しました。
こいつらは何者でしょう?

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
cc1741c9feca   bridge    bridge    local
d1b5ab168f8c   host      host      local
ccade9920c46   none      null      local

host

host はその名の通りホストマシン、つまりDockerを起動しているパソコン環境に対して直接接続することを可能にします。
通常、コンテナに対してホストから通信するためには、ホストとコンテナのポートアサイン設定を-pを使ってする必要があります。

# Network: `host`じゃない場合(指定なしなので `bridge`に接続)
$ docker run -p 3000:3000 nginx

こうすることで、http://localhost:8080にアクセスしたら nginxコンテナの3000番ポートにルーティングされるようになります。

ですが、hostNetworkにアサインされていると、このポート設定が不要になります。
コンテナ起動時に利用するNetworkをアサインするには、--networkオプションを使います。

# Network: `host`の場合
$ docker run --network=host nginx

(実際の業務でhostにわざわざ繋げるケースはみたことがありませんが。)

none

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

$ 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があると便利なケースがあります。

そこで、コンテナを3つ以下の図のように通信できるようにしたいと思います。
image.png

つまり、やることは、

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

1. 新しいNetwork(my-network-1, my-network-2)を作成

作ってコンテナ同士が通信できない隔離されたNetworkを作ってみます。

$ 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

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

2. コンテナを起動

次に、先図の通りにNetworkにアサインしながらコンテナを起動しましょう。
起動するコンテナのイメージは何でもいいです。
起動時に--networkオプションをつければOKです。

$ 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": ""
(...中略...)
]
$ 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してみましょう。

$ 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アドレスを直打ちするのは現実的ではありません。

# appコンテナからapiコンテナにHTTPリクエストを送りたい
$ curl http://{apiコンテナのIPアドレス}/...
# -> apiコンテナのIPアドレスってなに???

そんなケースをカバーできる機能として、Dockerには名前解決の機能が用意されています。
先ほどのcontainer-a-1 -> container-b-1にPingした例では、Pingの送信先をIPアドレスで表現しました。
ですが、同じNetworkにいるコンテナ同士であれば、コンテナ名を指定することでDockerデーモンの内臓DNSによって通信が可能です。

# 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で複数コンテナを立ち上げて通信連携する際によく使われる機能です。

これによって、先のappapiコンテナの例で言うと

# 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してみましょう。

$ 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が違うコンテナ同士は直接通信することができないようです。
当たり前ですが、コンテナ名による名前解決もしてくれません。

$ docker exec container-a-1 ping -c 3 container-a-2
ping: bad address 'container-a-2'

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

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

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

Docker講義動画を Udemy で配信中(クーポンあり)

今回の内容を含むUdemyの動画コースを配信しています。

動画の方が理解しやすい!という方も多いと思いますので、ぜひ興味のある方はご覧ください。
動画版の方が丁寧に説明しているのと、動画版にしかないコンテンツも多くあります。
たとえば、動画版ではDockerの基礎部分だけでなく実際の業務に即したアプリ開発を体験するセクションも用意してあります。

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

55
46
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
55
46