codingecho

日々の体験などを書いてます

Docker Swarm modeでservice間のアクセスを可能にする

Docker Swarmのservice間で名前解決して互いにネットワークアクセスできるように設定する方法です。

使用したDocker engineのバージョンは 1.13.0 です。

service discoveryを使用する

service discovery を使用するとservice名で名前解決できるようになります。例えば appdb という2つのserviceを作成した時、app から jdbc:mysql://db:3306/mydatabase のように db に接続できるようになります。このように名前解決できる機能のことを service discovery と言います。

service discoveryは、例えばWebアプリケーションでHTTPSを使いため、WebアプリケーションのserviceとNginxのserviceを立ち上げて、Nginxをリバースプロキシとして使いたい場合を考えてみます。

Webアプリケーションのservice名は myapp、 Nginxのservice名は proxy とします。 Webアプリケーションはport 9000で動作していて、Nginxは外部のHTTPSのport 443を受け取ってport 9000にリバースプロキシします。

myapp(9000) <- (9000)proxy(443) <- Webブラウザ

この時Nginxをリバースプロキシとして使いたいので、以下のような設定をします。

upstream my-backend {
  server myapp:9000;
}

server myapp:9000の部分でmyappのポート9000に対してリクエストを転送していることがわかります。

このservice discoveryを使用するには、新規にoverray networkを作成する必要があります。 Attach services to an overlay network

overlay networkを作成する

以下はoverlay networkを作成する例です。

$ docker network create --driver overlay my-network

次にこのネットワークを追加したserviceを作成します。 以下はNginxと自分で作成したアプリケーションのserviceを作成しています。

Nginx

$ docker service create --replicas 1 -p 80:80 --name nginx --network my-network my-nginx

Myapp

$ docker service create --replicas 1 --name app --network my-network my-app

このように同じoverlay networkを設定してserviceを作成します。同じネットワークに存在するserviceはVIPとDNSが設定されて、service名に名前解決できるようになります。

By default, when you create a service attached to a network, the swarm assigns the service a VIP. The VIP maps to a DNS alias based upon the service name. Containers on the network share DNS mappings for the service via gossip so any container on the network can access the service via its service name.

Use swarm mode service discovery

実際に app serviceで作成したコンテナに入ってdig nginxなどすると名前解決されていることがわかります。

service discoveryのハマりどころ

動作に必要なポートが開いているか確認する

service discoveryを使用するにはPort 7946 TCP/UDP とPort 4789 UDP が開いていることが必要です。 もし同じoverlay networkを設定しているのに名前解決できない場合は、netstat -antupしてみます。上記のポートが見つからなければdocker daemonをrestartしてみると直ることがあります。 このportがlistenされない原因を調べてみましたが、結局よくわかりませんでした。新しいバージョンだと解決されているかもしれません。 このportがlistensされていなくてもDocker Swarmは警告を出さない(たぶん)ので気づきにくいです。

また、portは問題ないのにうまくservice discoveryできない場合はホストマシン間のportが拒否されていないか確認します。もしES2のようなserviceを使用している場合は、そちらのポートの設定も確認します。

serviceを起動する順番

serviceに依存している別のserviceを立ち上げる場合、立ち上げる順番に気をつける必要があります。

先の例だと、myapp を立ち上げる前に proxy を立ち上げてしまうと、myapp がまだservice discoveryに登録されていないので、 proxy service起動時に名前解決されずにエラーで起動できません。先に myapp serivceを立ち上げてから proxy serviceを立ち上げる必要があります。

このようにservice名に依存したserviceがある場合は、serviceを立ち上げる順番に気をつける必要があります。

また、Nginxの場合だとservice名を変数にして、service discoveryのDNSで名前解決する方法があります。この時resolver 127.0.0.11 valid=2s;でDNSのTTLに関わらず、2秒で更新するようにします。

serviceを起動する順番に依存しないようにする

resolver 127.0.0.11 valid=2s;
set $upstream "myapp";

upstream my-backend {
  server myapp:9000;
}