スポンサーリンク

【Docker Compose】network【ハンズオン】

スポンサーリンク

概要

最下部の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アドレスを指定するには以下のように設定します。

serverdockerの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 Desktop for Windows のネットワーク機能
ネットワーク機能。

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(いつか更新します)

Reference

Docker コンテナ・ネットワークの理解 — Docker-docs-ja 19.03 ドキュメント

Compose の ネットワーク機能(networking) — Docker-docs-ja 20.10 ドキュメント
タイトルとURLをコピーしました