概要
最下部のReferenceに配置した公式サイトを見れば大まかに理解できるわけですが、状況に応じたケーススタディが不足しているように感じたのでまとめてみます
Dockerfile
ncコマンドを使用した疎通確認を行いたいので、下記のようなDockerfileをもとにイメージを作成します
FROM ubuntu RUN apt-get update && apt-get install -y iputils-ping net-tools RUN apt-get update && apt-get -y install netcat
Default Network
docker composeを立ち上げた場合、defaultでbridgeネットワークを作成し、そこに全てのサービスを接続します。
コンテナ名によるアクセス
そのため、docker networkの性質としてコンテナ名で名前解決を行ってアクセスできます。下記ではserverサービスは11111 portで待機し、clientがserverサービスの後に起動し、コンテナ名(server_container)を用いて11111 portへの疎通確認を行います。
version: '3' services: server: image: server_image build: . container_name: server_container entrypoint: ["nc", "-l", "11111"] client: image: client_image build: . container_name: client_container depends_on: - server entrypoint: ["nc", "-vz", "server_container", "11111"]
実行結果
client_container | Connection to server_container (172.21.0.2) 11111 port [tcp/*] succeeded!
以降、同様に疎通確認が確認できた場合には上記の実行結果が得られます。
サービス名によるアクセス
Docker Composeの性質として、サービス名(server)による名前解決も行うことができます。
version: '3' services: server: image: server_image build: . container_name: server_container entrypoint: ["nc", "-l", "11111"] client: image: client_image build: . container_name: client_container depends_on: - server entrypoint: ["nc", "-vz", "server", "11111"]
Default Networkの詳細
Defaultで生成されるNetworkのInspection結果は以下です。driverがdefaultのbridgeであること、server_container, client_containerが接続されていることを確認できます。
{ "Name" : "docker-compose-test_default", "Id" : "a5d2c8626761feafcfd81f42fe6eea3d20b768f3eacdef4eaef2fc401cc8dff3", "Created" : "2023-01-21T14:50:52.293766499Z", "Scope" : "local", "Driver" : "bridge", "EnableIPv6" : false, "IPAM" : { "Driver" : "default", "Options" : null, "Config" : [ { "Subnet" : "172.21.0.0/16", "Gateway" : "172.21.0.1" } ] }, "Internal" : false, "Attachable" : true, "Ingress" : false, "ConfigFrom" : { "Network" : "" }, "ConfigOnly" : false, "Containers" : { "2011ea6af4019ce00b9bbbd37e5cd28a5e148097e6d0897c0bbfe579f02d2f97" : { "Name" : "client_container", "EndpointID" : "3f1ec2588c98829f3ea73a06c691f97535ac85fd0413db0c42244166c9b33973", "MacAddress" : "02:42:ac:15:00:03", "IPv4Address" : "172.21.0.3/16", "IPv6Address" : "" }, "b8ee572c03489c7c67519f44afe517e3fb0f2ae36c704ea9fed1c58e32e81d74" : { "Name" : "server_container", "EndpointID" : "0c3095568f8ca96080ae8535207e3c6dd56677c68fc2130546f73f7ff0057bc7", "MacAddress" : "02:42:ac:15:00:02", "IPv4Address" : "172.21.0.2/16", "IPv6Address" : "" } }, "Options" : { }, "Labels" : { "com.docker.compose.network" : "default", "com.docker.compose.project" : "docker-compose-test", "com.docker.compose.version" : "1.29.2" } }
コンテナ名アクセス vs サービス名アクセス
ネットワークとしての機能的な違いはありませんが、docker-composeを使用する以上はサービスアクセスを利用すべきです。
理由としては、コンテナ名に依存しなくなり、より疎結合となるためです。
例えば、データベースとしてmysqlを利用していた場合、mysql_aのようなコンテナ名とすることが一般的です。もしpostgresqlに置き換えるプロジェクトが進行した場合、mysql_aを指定している全てのサービスにおいて、postgresql_aというコンテナ名に修正する必要があります。しかし、サービス名として指定していた場合(例えばdb)、サービス名は不変なので利用するサービスに対する変更は必要ありません。
レイヤーをどこにかますかという話なので、ネットワーク設定を外出しするというのも選択肢として取れます。
networksを用いたNetworkの作成
最も簡単な例
Docker Composeではnetworksを用いて独自のネットワークを作成できます。以下のようにすることで、server_containerとclient_containerはmy-networkに接続されます。ipアドレスは使用されていないアドレス空間が自動で割り当てられます。ホスト側からもルーティング不可能なアドレス空間が選択されるため、このように作成したnetworkはこのコンテナ間の通信のみに使用されます。(後からホスト側でアドレスを変更した場合は別)
version: '3' services: server: image: server_image build: . container_name: server_container networks: - my-network entrypoint: ["nc", "-l", "11111"] client: image: client_image build: . container_name: client_container depends_on: - server networks: - my-network entrypoint: ["nc", "-vz", "server", "11111"] networks: my-network:
Defaultのネットワークに対する動作
なお、作成したnetworkにコンテナを接続した場合、defaultで作成されるネットワークには接続されません。そのため、上記ではdefaultでネットワークは作成されるものの、そちらのネットワークには一つもコンテナが接続されていない状況となります。下記のcomposeで、片方のコンテナにのみ作成したnetworkを割り当て、動作を確認してみます。
version: '3' services: server: image: server_image build: . container_name: server_container networks: - my-network entrypoint: ["nc", "-l", "11111"] client: image: client_image build: . container_name: client_container depends_on: - server entrypoint: ["nc", "-vz", "server", "11111"] networks: my-network:
実行結果
異なるネットワークに属し、名前解決できないためserverとclient間は通信できません。
client_container | nc: getaddrinfo for host "server" port 11111: Temporary failure in name resolution
client a / bを異なるNetworkに配置したい場合
以下のように設定します。serverはnetwork a / bに接続しているため、client-a / bの両方からアクセスできますが、client-a / b間は異なるnetworkに属するため通信できません。
version: '3' services: server: image: server_image build: . container_name: server_container networks: - my-network-a - my-network-b entrypoint: ["nc", "-kl", "11111"] client-a: image: client_image build: . container_name: client_container_a depends_on: - server networks: - my-network-a entrypoint: ["nc", "-vz", "server", "11111"] client-b: image: client_image build: . container_name: client_container_b depends_on: - server networks: - my-network-b entrypoint: ["nc", "-vz", "server", "11111"] networks: my-network-a: my-network-b:
実行結果
client_container_a | Connection to server (172.24.0.2) 11111 port [tcp/] succeeded! client_container_b | Connection to server (172.25.0.2) 11111 port [tcp/] succeeded!
自動的にnetwork – a / bに異なるネットワーク帯が与えられていることが確認できます。以下がInspection結果です。
network – aのInspection結果
{ "Name" : "docker-compose-test_my-network-a", "Id" : "0df03e1f2c6d174ca32ab84ea041ca25726a1899aa79eb2479746909ef5bde74", "Created" : "2023-01-21T16:51:16.440483507Z", "Scope" : "local", "Driver" : "bridge", "EnableIPv6" : false, "IPAM" : { "Driver" : "default", "Options" : null, "Config" : [ { "Subnet" : "172.24.0.0/16", "Gateway" : "172.24.0.1" } ] }, "Internal" : false, "Attachable" : true, "Ingress" : false, "ConfigFrom" : { "Network" : "" }, "ConfigOnly" : false, "Containers" : { "327d444cd679f92f742b268702d15768c4072fcddc21dada8bd255b6d2ce2cfb" : { "Name" : "client_container_a", "EndpointID" : "ea2dd1515ed3b1682d6dd136052968a01ca55247b87a2f2ed24ed156f7e1b037", "MacAddress" : "02:42:ac:18:00:03", "IPv4Address" : "172.24.0.3/16", "IPv6Address" : "" }, "a838d60170f48257ae303920356d202f887e6ab00903dbffa6cded943a0e961a" : { "Name" : "server_container", "EndpointID" : "1d09d4aa7758c0a278459de7f5bc61d1f83ae60e2dd36870fc98f035675a614e", "MacAddress" : "02:42:ac:18:00:02", "IPv4Address" : "172.24.0.2/16", "IPv6Address" : "" } }, "Options" : { }, "Labels" : { "com.docker.compose.network" : "my-network-a", "com.docker.compose.project" : "docker-compose-test", "com.docker.compose.version" : "1.29.2" } }
Network-bのinspection結果
{ "Name" : "docker-compose-test_my-network-b", "Id" : "2e7eb12aca8fb4d6a67a61ca102cc520db7871791e5f446d2a615d28ce00a1e6", "Created" : "2023-01-21T16:51:17.155554994Z", "Scope" : "local", "Driver" : "bridge", "EnableIPv6" : false, "IPAM" : { "Driver" : "default", "Options" : null, "Config" : [ { "Subnet" : "172.25.0.0/16", "Gateway" : "172.25.0.1" } ] }, "Internal" : false, "Attachable" : true, "Ingress" : false, "ConfigFrom" : { "Network" : "" }, "ConfigOnly" : false, "Containers" : { "64ad06f97848ab668edcb2a1091882f9d99a830e0dff9f5277d5ca49cc2ffa52" : { "Name" : "client_container_b", "EndpointID" : "b8f99cc23e4c36ade484418d59f596f747212c7ecc008804c5840fc714212cf6", "MacAddress" : "02:42:ac:19:00:03", "IPv4Address" : "172.25.0.3/16", "IPv6Address" : "" }, "a838d60170f48257ae303920356d202f887e6ab00903dbffa6cded943a0e961a" : { "Name" : "server_container", "EndpointID" : "57b633d711f23a8b2376a8ecaa65336868a2559d7ddf6a06b30d0f53541a2dad", "MacAddress" : "02:42:ac:19:00:02", "IPv4Address" : "172.25.0.2/16", "IPv6Address" : "" } }, "Options" : { }, "Labels" : { "com.docker.compose.network" : "my-network-b", "com.docker.compose.project" : "docker-compose-test", "com.docker.compose.version" : "1.29.2" } }
IPアドレス空間、IPアドレスの指定
IPアドレス空間・IPアドレスを指定するには以下のように設定します。
server | dockerのdefault ipamによって、 ip_range 172.24.5.0/24内から自動割り当て |
client | ipv4_address: 172.24.5.55 の固定IP |
gateway: 172.24.5.111によりデフォルトルートも指定できます。
version: '3' services: server: image: server_image build: . container_name: server_container networks: - my-network entrypoint: ["nc", "-l", "11111"] client: image: client_image build: . container_name: client_container depends_on: - server networks: my-network: ipv4_address: 172.24.5.55 entrypoint: ["nc", "-vz", "server", "11111"] networks: my-network: driver: bridge ipam: driver: default config: - subnet: 172.24.0.0/16 ip_range: 172.24.5.0/24 gateway: 172.24.5.111
なお、ipamとはIP Address Managementの略称です。通常はdockerに内包されているdefaultのipamを使用したいため、driver: defaultを指定します。
my-network:の直後に指定しているdriverはdocker networkとしてnone / host / bridgeのどれで作成するかという意味で、同じdriverでも意味は異なります
ホストと通信する
ホスト⇒コンテナのみの場合
使用portが決まっている場合は使用portのみをexposeすることで簡単に通信できます。
version: '3' services: server: image: server_image build: . container_name: server_container ports: - "11111:11111" entrypoint: ["nc", "-l", "11111"]
ホスト側から以下のコマンドを送るとserverに到達可能であることが分かります。
nc localhost 11111
ホスト⇔コンテナ通信、かつ、同portを使用しない場合
題目のような条件の時、手っ取り早いのはnetwork modeをhostとすることです。hostを指定することで、ホストが使用しているNICを共有します。よって、コンテナ・ホストのどちらからも、localhost:対象ポートと指定することで、対象サービスにアクセス可能となります。一方で、同IP / Portを使用するサービスをコンテナで立ち上げると、portが既に使用されているというエラーが発生します。
version: '3' services: server: image: server_image build: . container_name: server_container network_mode: host entrypoint: ["nc", "-l", "11111"]
本来、docker0を介してネットワーク分離を実現しているコンテナをホストネットワークと共有するので、ホストネットワークと同様のセキュリティレベルとなります。
ホストマシン上で動作する1サービスとしての位置づけです。
docker for Windows
docker fow Windowsにおいてはhostモードは想定通りの動作となりません。ホスト⇒コンテナ通信には上記のport解放を、コンテナ⇒ホスト通信にはhost nameとしてhost.docker.internalを使用します。
docker.errors.InvalidArgument: “host” network_mode is incompatible with port_bindings
host modeとport bindingは併用することができません。以下のようなdocker-compose.ymlはエラーとなります。そもそもhostの時点ですべてのportを晒しているのと同義なので、bindingする必要はありません。
version: '3' services: server: image: server_image build: . container_name: server_container network_mode: host ports: - "33333:33333" entrypoint: ["nc", "-l", "11111"]
ホスト・対象のIPが変更される可能性がある
ホストのIPが変更される場合がある場合には、コンテナ⇒ホスト通信におけるIP設定をdocker-compose.yml内に外出しできると、コンテナ内部の設定を変更する必要がないので便利です。
そのため、ホストに対してhostnameを与えて、コンテナ内アプリケーションでホストに対する通信にはそのhostnameを使用するようにします。
services: server: image: server_image build: . container_name: server_container network_mode: host extra_hosts: - "somehost:192.168.0.7" entrypoint: ["nc", "-l", "11111"]
これは単純に/etc/hostsの中にホスト名を追加しているだけです。
ホストが所属しているIPアドレス空間に所属する
ホストが192.168.0.2であるとき、コンテナを192.168.0.128/28のIPを使用して立ち上げる手法を考えたいと思います。
XXXXX(いつか更新します)