オーバーレイネットワークのチュートリアル

読む時間の目安: 10 分

ここに示すチュートリアルは、Swarm サービスに対するネットワークを扱います。 スタンドアロンコンテナーに対するネットワークについては、スタンドアロンコンテナーのネットワークチュートリアル を参照してください。 Docker ネットワークの全般的なことを確認したい場合は ネットワーク概要 を参照してください。

このトピックには 4 つのチュートリアルがあります。 それぞれは Linux、Windows、Mac 上において実行することができます。 ただし Windows と Mac の場合は、2 つめの Docker ホストを、どこか別に用意することが必要になります。

前提条件

最低でも単一ノードからなる Swarm が必要です。 つまり Docker ホスト上にデーモンが起動している状態でdocker swarm initを実行します。 もちろん複数ノードの Swarm 上でも、利用例を試すことができます。

デフォルトのオーバーレイネットワーク利用

以下の例ではalpineサービスを起動し、コンテナーの個々から見たネットワークの特徴を確認していきます。

このチュートリアルでは、オーバーレイネットワークがどのように実装されているかといった、システム詳細については立ち入った説明はしません。 ただしサービスの観点から、オーバーレイ機能がどのようなものかは、詳しく見ていきます。

前提条件

このチュートリアルでは、物理ホスト、仮想ホストは問わず Docker ホストを 3 つ利用して、互いに通信を行うようにします。 この 3 つは、同一のネットワーク上にファイアウォールなしに稼動しているものとします。

各ホストはmanagerworker-1worker-2とします。 managerホストは、マネージャーとワーカーの両方の役割を持つものです。 つまりこれは、サービスタスクが稼動すると同時に、Swarm の管理も行います。 worker-1worker-2は、ワーカーとしてのみ動作します。

3 つのホストを手元で自由に使えないといったときには、簡単な策として Amazon EC2 などのクラウドプロバイダー上に 3 つの Ubuntu ホストを設定するという方法があります。 そうすれば同一のネットワーク上において、各ホストが確実に通信できるようになります(EC2 のセキュリティグループなどの機能を利用して実現されます)。 これを実行するなら、Ubuntu 向け Docker Engine - Community のインストール の手順に従ってください。

ウォークスルー

Swarm の生成

この手順を進めることで、最終的には 3 つの Docker ホストが Swarm に参加し、ingressというオーバーレイネットワークを使って互いに通信できる状態になります。

  1. managerにおいて Swarm を初期化します。 このホストがただ 1 つのネットワークインターフェースしかない場合は--advertise-addrフラグの指定は任意です。

    $ docker swarm init --advertise-addr=<managerのIPアドレス>
    

    出力される文字列は書きとめておいてください。 そこにはトークンが出力されます。 この後にworker-1worker-2を Swarm に参加させる際に必要となります。 パスワード管理ツールがあれば、そこにトークンを保存しておくのでもよいでしょう。

  2. worker-1上から、これを Swarm に参加させます。 このホストがただ 1 つのネットワークインターフェースしかない場合は--advertise-addrフラグの指定は任意です。

    $ docker swarm join --token <トークン> \
      --advertise-addr <worker-1のIPアドレス> \
      <managerのIPアドレス>:2377
    
  3. worker-2上から、これを Swarm に参加させます。 このホストがただ 1 つのネットワークインターフェースしかない場合は--advertise-addrフラグの指定は任意です。

    $ docker swarm join --token <トークン> \
      --advertise-addr <worker-2のIPアドレス> \
      <managerのIPアドレス>:2377
    
  4. managerにおいてノード一覧を確認します。 このコマンドはマネージャーからしか実行することができません。

    $ docker node ls
    
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
    d68ace5iraw6whp7llvgjpu48 *   ip-172-31-34-146    Ready               Active              Leader
    nvp5rwavvb8lhdggo8fcf7plg     ip-172-31-35-151    Ready               Active
    ouvx2l7qfcxisoyms8mtkgahw     ip-172-31-36-89     Ready               Active
    

    --filterフラグを使って役割(role)を絞ることができます。

    $ docker node ls --filter role=manager
    
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
    d68ace5iraw6whp7llvgjpu48 *   ip-172-31-34-146    Ready               Active              Leader
    
    $ docker node ls --filter role=worker
    
    ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
    nvp5rwavvb8lhdggo8fcf7plg     ip-172-31-35-151    Ready               Active
    ouvx2l7qfcxisoyms8mtkgahw     ip-172-31-36-89     Ready               Active
    
  5. managerworker-1worker-2のそれぞれにおいて、Docker ネットワークの一覧を表示します。 そこにはいずれもingressというオーバーレイネットワークがあり、docker_gwbridgeというブリッジネットワークがあることを確認してください。 なお以下ではmanagerにおけるネットワーク一覧のみを示します。

    $ docker network ls
    
    NETWORK ID          NAME                DRIVER              SCOPE
    495c570066be        bridge              bridge              local
    961c6cae9945        docker_gwbridge     bridge              local
    ff35ceda3643        host                host                local
    trtnl4tqnc3n        ingress             overlay             swarm
    c8357deec9cb        none                null                local
    

docker_gwbridgeingressネットワークを Docker ホストのネットワークインターフェースに接続するものであり、これがあることで、Swarm マネージャーとワーカーとの間でのトラフィックが通信できるようになります。 Swarm サービスを生成する際にネットワーク指定を行わない場合は、そのサービスはingressネットワークに接続されます。 いくつかのアプリケーションがまとまって動作するような場合には、各アプリケーションごとにオーバーレイネットワークを用いることが推奨されます。 次の手順では、オーバーレイネットワークを 2 つ生成して、サービスをそこに接続します。

サービスの生成

  1. managerにおいて、nginx-netという名前の新たなオーバーレイネットワークを生成します。

    $ docker network create -d overlay nginx-net
    

    このオーバーレイネットワークは、他のノード上で生成する必要はありません。 それぞれのノードにおいて、このネットワークを必要とするサービスタスクが起動した際に、ネットワークは自動生成されます。

  2. managerにおいて、nginx-netに接続する Nginx サービスをレプリカ数 5 で生成します。 このサービスは外部に対してはポート 80 を公開します。 サービスタスクを実行するコンテナーは、ポートを公開しなくても互いに通信できるようになっています。

    メモ: サービスはマネージャー上からしか生成できません。

    $ docker service create \
      --name my-nginx \
      --publish target=80,published=80 \
      --replicas=5 \
      --network nginx-net \
      nginx
    

    ingressのデフォルトの公開モードは、--publishフラグに対してmodeを指定しなかった場合に用いられます。 そしてmanagerworker-1worker-2のポート 80 にアクセスしたときに、5 つのサービスタスクのどれか 1 つのポート 80 に接続できるものであり、たとえ接続したノードそのものにおいて、その時点でサービスタスクが稼動していなくても接続が可能なものです。 hostモードを利用してポートを公開したい場合は、--publishにおいてmode=hostを追加します。 ただしその場合は、--replicas=5を指定するのではなく--mode globalとしなければなりません。 そのときには、指定されたノード上の指定されたポートに割り当てることができるサービスタスクは、ただ 1 つになるからです。

  3. docker service lsを実行して、サービスの稼働状況を確認してみます。 これには数秒かかります。

  4. managerworker-1worker-2上のnginx-netネットワークを確認します。 worker-1worker-2においては、ネットワークを手動で生成する必要はなく、Docker が生成してくれるものであることは、前に説明しました。 出力結果は長いものになりますが、ContainersPeersという項をよく確認してください。 Containersには、ホストからオーバーレイネットワークに接続されたサービスタスク(あるいはスタンドアロンコンテナー)の一覧が出力されています。

  5. managerにおいて、docker service inspect my-nginxを実行してサービスを確認します。 サービスによって利用されているポートとエンドポイントの情報を確認してください。

  6. 新たにnginx-net-2というネットワークを生成します。 そしてそれまでのnginx-netの代わりとして、このネットワークをサービスが利用するようにアップデートします。

    $ docker network create -d overlay nginx-net-2
    
    $ docker service update \
      --network-add nginx-net-2 \
      --network-rm nginx-net \
      my-nginx
    
  7. docker service lsを実行して、サービスがアップデートされたことと、タスクがすべて再デプロイされたことを確認します。 そしてdocker network inspect nginx-netを実行して、このネットワークに接続しているコンテナーは 1 つもないことを確認します。 同様のコマンドをnginx-net-2に対しても実行し、サービスタスクコンテナーがすべて、そのネットワークに接続されていることを確認します。

    メモ: Swarm ワーカーノードにおいては、処理状況に応じてオーバーレイネットワークが自動生成されますが、これは自動的には削除されません。

  8. サービスとネットワークを削除します。 manager上から、以下のコマンドを実行します。 マネージャーからは、ワーカーに対してネットワークを削除するよう、自動的に指示が送られます。

    $ docker service rm my-nginx
    $ docker network rm nginx-net nginx-net-2
    

ユーザー定義のオーバーレイネットワーク利用

前提条件

このチュートリアルでは、Swarm がすでに設定済みであり、マネージャーにアクセスできているものとします。

ウォークスルー

  1. ユーザー定義のオーバーレイネットワークを生成します。

    $ docker network create -d overlay my-overlay
    
  2. そのオーバーレイネットワークを使ってサービスを起動します。 ポート 80 を Docker ホストのポート 8080 に公開します。

    $ docker service create \
      --name my-nginx \
      --network my-overlay \
      --replicas 1 \
      --publish published=8080,target=80 \
      nginx:latest
    
  3. docker network inspect my-overlayを実行して、my-nginxサービスタスクがmy-overlayに接続されていることを確認します。 確認はContainersの項を見ます。

  4. サービスとネットワークを削除します。

    $ docker service rm my-nginx
    
    $ docker network rm my-overlay
    

スタンドアロンコンテナーに対するオーバーレイネットワーク利用

この例では、DNS によるコンテナー検出を試してみます。 特に、オーバーレイネットワークを利用する複数の Docker デーモン上に、スタンドアロンコンテナーが稼動していて、その間での通信情報を確認していきます。

  • host1では、Swarm としてノードを初期化します。(マネージャー)
  • host2では、Swarm に対してノード参加します。(ワーカー)
  • host1では、アタッチ可能なオーバーレイネットワークを生成します。(test-net
  • host1では、対話的な alpine コンテナー(alpine1)をtest-net上で実行します。
  • host2では、対話的な alpine コンテナー(alpine2)を、デタッチモードによりtest-net上で実行します。
  • host1では、alpine1のセッション内部からalpine2に対して ping を行います。

前提条件

このテストでは、Docker ホストが 2 つ、互いに通信できるものとして用意する必要があります。 両ホスト間では、以下のポートが公開されていることが必要です。

  • TCP ポート 2377
  • TCP と UDP のポート 7946
  • UDP ポート 4789

このような環境を準備する簡単な方法として、2 つの VM(ローカルなもの、あるいは AWS のようなクラウドプロバイダー上のもの)を利用することが考えられます。 そこで Docker をインストールし起動させます。 AWS や同様のクラウドコンピューティングプラットフォームを利用している場合は、セキュリティグループを利用すれば、ごく簡単に設定ができます。 これを使えば、2 つのホスト間での入力ポートはすべて開放され、手元のクライアントからは IP アドレスを使って SSH 接続することができます。

この例では、Swarm 上に配置する 2 つのノードをhost1host2とします。 またこの例では Linux ホストを用いますが、同じコマンドは Windows 上でも動作します。

ウォークスルー

  1. Swarm を設定します。

    a. host1において Swarm を初期化します。 (プロンプトが出る場合は--advertise-addrを指定します。 これを使って、Swarm 内の別ホストの間で通信を行うインターフェースの IP アドレスを指定します。 たとえば AWS 上であれば、プライベート IP アドレスを指定します。)

    $ docker swarm init
    Swarm initialized: current node (vz1mm9am11qcmo979tlrlox42) is now a manager.
    
    To add a worker to this swarm, run the following command:
    
        docker swarm join --token SWMTKN-1-5g90q48weqrtqryq4kj6ow0e8xm9wmv9o6vgqc5j320ymybd5c-8ex8j0bc40s6hgvy5ui5gl4gy 172.31.47.252:2377
    
    To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
    

    b. host2において、上のコマンド実行により出力された Swarm への参加を行います。

    $ docker swarm join --token <トークン> <IPアドレス>:2377
    This node joined a swarm as a worker.
    

    Swarm へのノード参加に失敗すると、docker swarm joinコマンドはタイムアウトします。 これを解消するにはhost2上でdocker swarm leave --forceを実行して、ネットワークとファイアウォールの設定を正しくした上で、再度コマンド実行を試してみてください。

  2. host1においてtest-netという名前の、アタッチ可能なオーバーレイネットワークを生成します。

    $ docker network create --driver=overlay --attachable test-net
    uqsof8phj3ak0rq9k86zta6ht
    

    ここで出力される ネットワーク ID を覚えておいてください。 同じものがhost2から接続した際にも表示されます。

  3. host1において、test-netに接続する対話形式(-it)のコンテナー(alpine1)を起動します。

    $ docker run -it --name alpine1 --network test-net alpine
    / #
    
  4. host2において利用可能なネットワーク一覧を表示します。 なおtest-netはこの時点ではまだ存在しません。

    $ docker network ls
    NETWORK ID          NAME                DRIVER              SCOPE
    ec299350b504        bridge              bridge              local
    66e77d0d0e9a        docker_gwbridge     bridge              local
    9f6ae26ccb82        host                host                local
    omvdxqrda80z        ingress             overlay             swarm
    b65c952a4b2b        none                null                local
    
  5. host2において、test-netに接続するデタッチモード(-d)かつ対話形式(-it)のコンテナー(alpine2)を起動します。

    $ docker run -dit --name alpine2 --network test-net alpine
    fb635f5ece59563e7b8b99556f816d24e6949a5f6a5b1fbd92ca244db17a4342
    

    DNS のコンテナー自動検出の機能は、一意のコンテナー名に対してのみ動作します。

  6. host2においてtest-netが生成されていることを確認します。 (そしてhost1上から見たtest-netと同じように、同一のネットワーク ID となっていることを確認します。)

     $ docker network ls
     NETWORK ID          NAME                DRIVER              SCOPE
     ...
     uqsof8phj3ak        test-net            overlay             swarm
    
  7. host1において、alpine1の対話型ターミナル内からalpine2に向けて ping を行います。

    / # ping -c 2 alpine2
    PING alpine2 (10.0.0.5): 56 data bytes
    64 bytes from 10.0.0.5: seq=0 ttl=64 time=0.600 ms
    64 bytes from 10.0.0.5: seq=1 ttl=64 time=0.555 ms
    
    --- alpine2 ping statistics ---
    2 packets transmitted, 2 packets received, 0% packet loss
    round-trip min/avg/max = 0.555/0.577/0.600 ms
    

    2 つのコンテナーは、2 つのホストに接続しているオーバーレイネットワークを使って、互いに通信します。 host2からもう一つ別の alpine コンテナーを、デタッチモードではなく 起動すると、hostsからalpine1へ ping を行うことができます。 (そしてここでは rm オプション を指定に加えて、コンテナーの自動削除を行うことにします。)

    $ docker run -it --rm --name alpine3 --network test-net alpine
    / # ping -c 2 alpine1
    / # exit
    
  8. host1においてalpine1のセッションを閉じます。 (さらにコンテナーも停止します。)

    / # exit
    
  9. コンテナーとネットワークを削除します。

    コンテナーの停止と削除は、個々のホスト上においてそれぞれ行うことが必要です。 Docker デーモンはそれぞれに動作しているからであり、コンテナーはすべてスタンドアロンだからです。 ネットワークを削除するのはhost1上だけで十分です。 host2上にてalpine2を停止したらtest-netはなくなります。

    a. host2においてalpine2を停止させます。 そしてtest-netが削除されていることを確認した上でalpine2を削除します。

    $ docker container stop alpine2
    $ docker network ls
    $ docker container rm alpine2
    

    a. host1においてalpine1test-netを削除します。

    $ docker container rm alpine1
    $ docker network rm test-net
    

コンテナー、Swarm サービス間の通信

この例では、2 つのalpineコンテナーを同一の Docker ホスト上に稼動させます。 そしてテストを行って、コンテナー間の通信がどのように行われるかを確認します。 Docker がインストール済みであり起動していることを確認いてください。

  1. ターミナル画面を開きます。 まず初めに、現在のネットワーク一覧を確認しておきます。 ネットワークをまったく追加せず、Docker デーモン上において Swarm の初期化も行っていなければ、以下のような表示になるはずです。 複数のネットワークが表示されるはずであり、最低で以下のものがあるはずです。 (ネットワーク ID は異なります。)

    $ docker network ls
    
    NETWORK ID          NAME                DRIVER              SCOPE
    17e324f45964        bridge              bridge              local
    6ed54d316334        host                host                local
    7092879f2cc8        none                null                local
    

    デフォルトのbridgeネットワークが一覧に表示されます。 これとともにhostnoneがあります。 この 2 つは完全なネットワークではありませんが、コンテナーを起動して Docker デーモンホストのネットワークに直接接続するために、あるいはネットワークデバイスのないコンテナーを起動するために必要となります。 このチュートリアルでは、2 つのコンテナーをbridgeネットワークに接続します。

  2. alpineコンテナーを 2 つ起動してashを実行します。 Alpine のデフォルトシェルがbashではなくashです。 -ditフラグは、コンテナーをデタッチモードで(バックグラウンドで)実行し、対話を行い(入力を可能とし)、TTY を利用する(入出力が確認できる)ことを意味します。 デタッチモードで起動するため、コンテナーに即座に接続されるわけではありません。 その前にコンテナー ID が出力されます。 --networkフラグを何も指定しなかったので、コンテナーはデフォルトのbridgeネットワークに接続されます。

    $ docker run -dit --name alpine1 alpine ash
    
    $ docker run -dit --name alpine2 alpine ash
    

    2 つのコンテナーが実際に開始されたことを確認します。

    $ docker container ls
    
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    602dbf1edc81        alpine              "ash"               4 seconds ago       Up 3 seconds                            alpine2
    da33b7aa74b0        alpine              "ash"               17 seconds ago      Up 16 seconds                           alpine1
    
  3. bridgeネットワークを参照して、どのコンテナーがこれに接続しているかを確認します。

    $ docker network inspect bridge
    
    [
        {
            "Name": "bridge",
            "Id": "17e324f459648a9baaea32b248d3884da102dde19396c25b30ec800068ce6b10",
            "Created": "2017-06-22T20:27:43.826654485Z",
            "Scope": "local",
            "Driver": "bridge",
            "EnableIPv6": false,
            "IPAM": {
                "Driver": "default",
                "Options": null,
                "Config": [
                    {
                        "Subnet": "172.17.0.0/16",
                        "Gateway": "172.17.0.1"
                    }
                ]
            },
            "Internal": false,
            "Attachable": false,
            "Containers": {
                "602dbf1edc81813304b6cf0a647e65333dc6fe6ee6ed572dc0f686a3307c6a2c": {
                    "Name": "alpine2",
                    "EndpointID": "03b6aafb7ca4d7e531e292901b43719c0e34cc7eef565b38a6bf84acf50f38cd",
                    "MacAddress": "02:42:ac:11:00:03",
                    "IPv4Address": "172.17.0.3/16",
                    "IPv6Address": ""
                },
                "da33b7aa74b0bf3bda3ebd502d404320ca112a268aafe05b4851d1e3312ed168": {
                    "Name": "alpine1",
                    "EndpointID": "46c044a645d6afc42ddd7857d19e9dcfb89ad790afb5c239a35ac0af5e8a5bc5",
                    "MacAddress": "02:42:ac:11:00:02",
                    "IPv4Address": "172.17.0.2/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": {}
        }
    ]
    

    上の方にbridgeネットワークに関する情報が一覧表示されます。 Docker ホストとbridgeネットワーク間のゲートウェイに対する IP アドレス(172.17.0.1)も表示されています。 Containersキーの配下に、接続されているコンテナーがそれぞれ表示されています。 そこには IP アドレスの情報もあります(alpine1172.17.0.2alpine2172.17.0.3となっています)。

  4. コンテナーはバックグラウンドで実行しています。 docker attachコマンドを使ってalpine1に接続してみます。

    $ docker attach alpine1
    
    / #
    

    プロンプトが#に変わりました。 これはコンテナー内のrootユーザーであることを意味します。 ip addr showコマンドを使って、コンテナー内部からalpine1のネットワークインターフェースを見てみます。

    # ip addr show
    
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
        inet6 ::1/128 scope host
           valid_lft forever preferred_lft forever
    27: eth0@if28: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
        link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.2/16 scope global eth0
           valid_lft forever preferred_lft forever
        inet6 fe80::42:acff:fe11:2/64 scope link
           valid_lft forever preferred_lft forever
    

    1 つめのインターフェースはループバックデバイスです。 今はこれを無視します。 2 つめのインターフェースの IP アドレスは172.17.0.2となっています。 前の手順で確認したalpine1のアドレスと同じです。

  5. alpine1の内部からgoogle.comへの ping を行って、インターネットに接続してみます。 -c 2フラグにより 2 回だけpingを行います。

    # ping -c 2 google.com
    
    PING google.com (172.217.3.174): 56 data bytes
    64 bytes from 172.217.3.174: seq=0 ttl=41 time=9.841 ms
    64 bytes from 172.217.3.174: seq=1 ttl=41 time=9.897 ms
    
    --- google.com ping statistics ---
    2 packets transmitted, 2 packets received, 0% packet loss
    round-trip min/avg/max = 9.841/9.869/9.897 ms
    
  6. そこで 2 つめのコンテナーに対して ping してみます。 最初は IP アドレス172.17.0.3を使って ping します。

    # ping -c 2 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.086 ms
    64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.094 ms
    
    --- 172.17.0.3 ping statistics ---
    2 packets transmitted, 2 packets received, 0% packet loss
    round-trip min/avg/max = 0.086/0.090/0.094 ms
    

    成功しました。 次にalpine2コンテナーに向けて、コンテナー名により ping をしてみます。 これは失敗します。

    # ping -c 2 alpine2
    
    ping: bad address 'alpine2'
    
  7. alpine1を停止させることなくデタッチします。 これはデタッチを行うキー操作、つまりCTRL+pCTRL+qにより行います(CTRLを押したまま、pqを順に押します)。 この後alpine2に対して同じことをするなら、手順の 4、5、6 をもう一度行います。 alpine1のところはalpine2に変えて実施します。

  8. 2 つのコンテナーを停止させ削除します。

    $ docker container stop alpine1 alpine2
    $ docker container rm alpine1 alpine2
    

デフォルトのbridgeネットワークは、本番環境向けとしては推奨されない点を覚えておいてください。 ユーザー定義のブリッジネットワークについては、次のチュートリアル に進んでください。

その他のネットワークチュートリアル

オーバーレイネットワークのチュートリアルを終えたので、以下に示すような別のネットワークチュートリアルも見てください。

networking, bridge, routing, ports, swarm, overlay