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に利用されている機能です。
概要図を以下のように作成しました。
Bridgeを使うことで、複数のNetwork Namespace間での通信を容易にすることができます。
Network Namespaceも一般的なLinuxに存在するNetworkの機能です。
Namespaceを一つのマシンの中で切ることで、NamespaceごとにNetwork設定を独立して分割することが可能にします。
また、veth (virtual ethernet)は、NamespaceやBridgeのインターフェイスとなっている口です。
それぞれが通信をするためには、これらのvethが接続されている必要があります。
そして、Dockerのコンテナはそれぞれが独立したNetwork Namespaceを構築し、vethも持っています。
つまり、現在のDockerのNetworkは以下のような状態になっているってことです。
複数コンテナが同じNetworkにつながっていると?
デフォルトでは、bridge
のNetworkにコンテナは接続されると説明しました。
では複数コンテナを接続するとどうなるでしょう?
一旦まっさらな状態から、新しくcontainer-1
とcontainer-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
bridge
Networkの状態を確認してみます。
$ 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アドレスのアサインも出ていますね。
この情報を図にすると以下の通り。
ここで、同じbridge
Networkに紐づいている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 host
と none
とは?
先ほど表示したデフォルトで存在するNetwork一覧には、bridge
以外にもhost
と none
という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番ポートにルーティングされるようになります。
ですが、host
Networkにアサインされていると、このポート設定が不要になります。
コンテナ起動時に利用する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つ以下の図のように通信できるようにしたいと思います。
つまり、やることは、
- 新しいNetwork(
my-network-1
,my-network-2
)を作成 - 3つのコンテナを図のとおりのNetworkにアサインしながら起動
- 同じNetworkのコンテナ同士は通信できることの確認
- 違う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で複数コンテナを立ち上げて通信連携する際によく使われる機能です。
これによって、先のapp
とapi
コンテナの例で言うと
# 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ビルドコンテキストとは何か?
- VolumeマウントとBindマウントの違いとは?
- マルチステージビルドはどういうシーンで使う?
以下リンクからだと割引で買えます!