Swarm モードにおけるルーティングメッシュの利用
読む時間の目安: 3 分
Docker Engine の Swarm モードでは、サービスに対してのポート公開が簡単にできるので、Swarm 外部にあるリソースがサービスに対してアクセスすることを可能にします。 ノードはすべて、ingress の ルーティングメッシュ(routing mesh)に参加します。 ルーティングメッシュがあることによって Swarm 内の各ノードは、同じく Swarm 内で稼動するどのようなサービスに対しても、公開ポートを通じて接続することができます。 たとえノード上にタスクが実行されていなくてもかまいません。 ルーティングメッシュは、利用可能ノードの公開ポートに入ってきたリクエストすべてを、アクティブなコンテナーにルーティングします。
Swarm において ingress ネットワークを利用するには、Swarm モードを有効にする前に、Swarm ノード間において以下のポートを開放しておく必要があります。
- ポート
7946TCP/UDP、コンテナーのネットワーク検出のため。 - ポート
4789UDP、コンテナーの ingress ネットワークのため。 
これに加えて、たとえば外部のロードバランサーなどの外部リソースが、特定ポートへのアクセスを必要とする場合には、Swarm ノード間においてその公開ポートを開放しておくことも必要です。
あるいは指定したサービスに対しては ルーティングメッシュの無効化 を実施することもできます。
サービスにおけるポート公開
サービス生成時にポートを公開するには--publishフラグを利用します。
その際にはコンテナー内部のポート指定にtargetを用い、ルーティングメッシュ上に割り当てるポートの指定にpublishedを用います。
publishedの指定がなかった場合は、各サービスタスクにおいてランダムに高位のポート番号が割り振られます。
ポート番号がどの番号に割り振られたかを知るには、タスクの確認が必要です。
$ docker service create \
  --name <サービス名> \
  --publish published=<公開ポート>,target=<コンテナーポート> \
  <イメージ>
メモ
上のコマンドの古い書式として、コロン区切りの文字列を用いるものがあります。 その場合、1 つめが公開ポート、2 つめがターゲットとなるポートとなり、たとえば
-p 8080:80と指定します。 好ましいのは新たな書式です。 その方が読みやすく、より柔軟性があるからです。
<公開ポート>は、Swarm がサービスを利用可能とするポートです。
これを省略すると、高位のポート番号が割り振られます。
<コンテナーポート>は、コンテナーが待ち受けるポートです。
このパラメーターは必須です。
たとえば以下のコマンドは nginx コンテナー上のポート 80 を、Swarm 内の全ノード上のポート 8080 に向けて公開します。
$ docker service create \
  --name my-web \
  --publish published=8080,target=80 \
  --replicas 2 \
  nginx
どのノードにおいてもポート 8080 へのアクセスが行われると、Docker はそのリクエストをアクティブコンテナーに転送します。 Swarm 内のノードそのものには、実際にはポート 8080 が割り振られていない場合もあります。 しかしルーティングメッシュは、トラフィックをどこに転送すべきかがわかっているので、ポートの競合は発生しません。
ルーティングメッシュは、ノードに割り当てられているどのような IP アドレスに対しても、公開ポートを待ち受けます。 外部にルーティングできる IP アドレスの場合、そのポートはホスト外部から利用できます。 これ以外の IP アドレスの場合は、すべてホスト内部からしかアクセスできません。

既存のサービスに対しては、以下のコマンドによってポートを公開することができます。
$ docker service update \
  --publish-add published=<公開ポート>,target=<コンテナーポート> \
  <サービス>
サービスの公開ポートの確認にはdocker service inspectを使います。
たとえば以下のとおりです。
$ docker service inspect --format="{{json .Endpoint.Spec.Ports}}" my-web
[{"Protocol":"tcp","TargetPort":80,"PublishedPort":8080}]
上の出力結果では、コンテナー側に<コンテナーポート>(ラベルTargetPortの部分)、サービスへのリクエストを待ち受けるノード側に<公開ポート>(ラベルPublishedPortの部分)があるのがわかります。
TCP のみ、UDP のみのポート公開
ポートを公開するとデフォルトでは TCP ポートとなります。
このかわりに、明示的に UDP ポートを指定するか、TCP ポートに UDP ポートを加えた指定とすることができます。
TCP と UDP の両方を公開するとします。
プロトコルの指定を省略してしまうと、TCP ポートとしてしか公開されません。
長い文法(推奨)を使って、protocolキーにtcpまたはudpを設定します。
TCP のみの指定
長い文法の場合
$ docker service create --name dns-cache \
  --publish published=53,target=53 \
  dns-cache
短い文法の場合
$ docker service create --name dns-cache \
  -p 53:53 \
  dns-cache
TCP と UDP の指定
長い文法の場合
$ docker service create --name dns-cache \
  --publish published=53,target=53 \
  --publish published=53,target=53,protocol=udp \
  dns-cache
短い文法の場合
$ docker service create --name dns-cache \
  -p 53:53 \
  -p 53:53/udp \
  dns-cache
UDP のみの指定
長い文法の場合
$ docker service create --name dns-cache \
  --publish published=53,target=53,protocol=udp \
  dns-cache
短い文法の場合
$ docker service create --name dns-cache \
  -p 53:53/udp \
  dns-cache
ルーティングメッシュの無効化
ルーティングメッシュは無効化することができます。
無効化した場合、特定のノード上に割り当てられているポートにアクセスすると、常にそのノード上に稼動しているサービスインスタンスにアクセスすることになります。
これはhostモードと呼ばれます。
この場合には注意しておくべき点がいくつかあります。
- 
    
アクセスしたノードにサービスタスクが稼動していない場合、そのポートは待ち受けされていないことになります。 この場合、そのポートが待ち受けされていないか、あるいはまったく別のアプリケーションがそのポートを待ち受けている可能性があります。
 - 
    
各ノードにおいて複数のサービスタスクを実行したい場合(たとえば 5 つのノードに対して 10 個のレプリカを実行させたい場合)、対象ポートを固定することはできません。 この場合には、ランダムな高位のポート番号を割り当てるようにする(
publishedを指定しない)か、または対象ノード上では単一のサービスインスタンスのみが稼動しているようにするかのいずれかを行います。 つまり、サービスのレプリカは行わずにグローバルとするか、あるいはノード配置に関する制約(placement constraint)を利用します。 
ルーティングメッシュを無効化するには、サービス生成時に長い文法による--publishを使って、modeをhostに設定する必要があります。
modeキーを省略するか、ingressに設定した場合、ルーティングメッシュが有効になります。
以下のコマンドでは、hostモードを使ってグローバルなサービスを生成し、ルーティングメッシュは無効化します。
$ docker service create --name dns-cache \
  --publish published=53,target=53,protocol=udp,mode=host \
  --mode global \
  dns-cache
外部ロードバランサーの設定
Swarm サービスに対して、外部のロードバランサーを設定することができます。 その場合には、ルーティングメッシュと組み合わせる方法と、ルーティングメッシュを利用しない方法があります。
ルーティングメッシュを利用する方法
外部のロードバランサーを利用して Swarm サービスに対するリクエストの転送設定を行うことができます。 たとえば HAProxy を用いた設定により、nginx サービスの公開ポートを 8080 へのリクエストとして分散することができます。

この場合ロードバランサーと Swarm 内ノード間において、ポート 8080 が解放されている必要があります。 Swarm ノードは、プロキシーサーバーにアクセスできるのであれば、プライベートネットワーク内に置くことができます。 ただし外部からアクセスすることはできません。
ロードバランサーによって Swarm ノード間にリクエストを分散する際には、タスクがスケジューリングされていないノードであってもかまいません。
たとえば以下のように/etc/haproxy/haproxy.cfgに HAProxy 設定を行うことができます。
global
        log /dev/log    local0
        log /dev/log    local1 notice
...省略...
# HAProxy がポート 80 を待ち受ける設定
frontend http_front
   bind *:80
   stats uri /haproxy?stats
   default_backend http_back
# HAProxy がリクエストを Swarm ノードのポート 8080 にルーティングする設定
backend http_back
   balance roundrobin
   server node1 192.168.99.100:8080 check
   server node2 192.168.99.101:8080 check
   server node3 192.168.99.102:8080 check
HAProxy ロードバランサーに対してポート 80 でアクセスすると、Swarm 内のポートにリクエストが送信されます。 Swarm のルーティングメッシュは、そのリクエストをアクティブタスクに対して転送します。 このとき、何らかの理由により Swarm スケジューラーが、別のノードにタスクを移動させたとしても、ロードバランサーを再設定する必要はありません。
Swarm ノードへのリクエストを送信するロードバランサーは、どのような種類のものでも設定できます。 HAProxy に関する詳細は HAProxy のドキュメント を参照してください。
ルーティングメッシュを利用しない方法
ルーティングメッシュは利用せずに外部のロードバランサーを用いるには、--endpoint-modeに設定する値を、デフォルトのvipではなくdnsrrにします。
この場合、単一の仮想 IP はありません。
その代わりに Docker は、サービスの DNS エントリーを作り出します。
そしてサービス名に対する DNS クエリーが IP アドレス一覧を返すので、クライアントはその中の 1 つに直接接続するようになります。
ロードバランサーには、その IP アドレス一覧とポートを設定することが必要になります。
サービス検出の設定 を参照してください。