docker stack deploy で立ち上げたサービスのポートが分からん件

**INTRODUCING EXPERIMENTAL DISTRIBUTED APPLICATION BUNDLES**
https://blog.docker.com/2016/06/docker-app-bundle/

とか

**What's New in Docker 1.12 (June 20, 2016) by Mike Goelzer & Andrea Luzzardi**
http://www.slideshare.net/MikeGoelzer/whats-new-in-docker-112-goelzerluzzardi

とか

**Docker Stacks and Distributed Application Bundles**
https://github.com/docker/docker/blob/master/experimental/docker-stacks-and-bundles.md

あたりを見ると分かるんですが、 `docker stack` コマンドが新しくできまして、この `stack` をつかうことで「サービスのまとまり」を可搬性高く管理することができるようになります。
例えばマイクロサービスな開発をしてるときに、自分の担当外のサービスとの連携をローカル環境で試したい、みたいな状況のときに、その担当外のサービスの構成がかいてある `foo_service.dab` ( [現状だと `.dsb` 形式になってるけど間違いなのでそのうち `.dab` になるはず](https://github.com/docker/docker/issues/23791) )を受け取るだけで、自分のローカルにそのサービスの環境を構築できる、みたいな未来が待ってるわけです。

現在でも `docker-compose.yml` を使えば似たようなことができはしますが、 `docker-compose` だとポートが重複してたりするので、自分のサービスとそのサービスの両方が80をLISTENしてるような状況の場合に問題が発生するわけですが、この `docker stack` を使うと、その辺のことを何かいい感じにしてくれます。

使い方は比較的簡単で

* `docker-compose bundle` コマンドを実行して `hoge.dab` ファイルを作る
* `docker stack deploy hoge` で `hoge.dab` ファイルが読まれてサービスが展開される
* すごい

これだけなんですが、 `docker-compose` の感覚でいるといくつか失敗します。

## links未対応とその解決策

まず、 `docker-compose.yml` が対応している構文の全てに対応しているわけじゃないです。
実際に `docker-compose bundle` を実行すると以下のようなWARNINGがでてくるはずです。

```
$ docker-compose bundle
WARNING: Unsupported key 'network_mode' in services.web - ignoring
WARNING: Unsupported key 'links' in services.web - ignoring
WARNING: Unsupported key 'network_mode' in services.app - ignoring
```

つまり、 `docker-compose` のウリである勝手にhosts書き換えちゃいましたテヘペロ `links` が使えないという、超残念仕様なのであります。

「え・・・じゃぁどうすんの・・・・コンテナ間のIPとか分からんよ・・・・」と思うんですが、そこは流石に対策があり、 `docker-compose.yml` のルートのコンテナ名の代わりに、サービス名でアクセスすることができます。

たとえば以下みたいな `docker-compose.yml` があったとして（中身は適当なので雰囲気を感じ取ってください）

```
web:
  image: nginx
  ports:
    - 80:80
  expose:
    - 80
  links:
    - app:php
app:
  image: php
  expose:
    - 9000
```

nginx->appへの通信は `tcp://php:9000/` みたいな感じで行えるのが `docker-compose` なわけですが、これに対して `docker stack` の場合は(仮にstack名がpiyoだったとすると)、 `tcp://piyo_app:9000/` でアクセスできるようになってます。

つまり `app:php` のようなかたちで `links` の設定はできませんが、この例でいうと(仮にstack名がpiyoだったとすると) `piyo_web` というサービスと `piyo_app` というサービスが作られ、そのサービス名がDocker内部のDNSに登録されている、というようなイメージです。

## 立ち上がったStackのポートが分からん件

というわけで (仮にpiyoという名前の) stack を `docker stack deploy piyo` みたいにすると、内部でいい感じに `piyo_web` `piyo_app` みたいに `スタック名_サービス名` 形式のサービスが立ち上がります。

これで大勝利かと思いきや、先ほど申したとおり、stackはポートの重複が起こらないように何かいい感じにしてくれるので、たとえ以下のようにまるで80をLISTENしてそうな雰囲気でもLISTENしてくれません。

```piyo.dsbの例（ふんいき）
{
  "services": {
    "app": {
      "Image": "xxxxxx", 
      "Networks": [], 
      "Ports": [
        {
          "Port": 9000, 
          "Protocol": "tcp"
        }
      ]
    }, 
    "web": {
      "Image": "yyyyyy", 
      "Networks": [], 
      "Ports": [
        {
          "Port": 80, 
          "Protocol": "tcp"
        }
      ]
    }
  }, 
  "version": "0.1"
}
```

「OKなるほどね。んじゃ何かいい感じのGatewayが設定されてるんだね。きっとそうだね。」
と思っていろいろ調べたけど全然見当たらなくて、ぐぬぬったんですが、何のことはなくて80じゃないPortをLISTENしてました。

というわけで困ったときのinspectコマンドですので、`docker service inspect piyo_web` みたいな感じでinspectすると以下みたいな結果が返ってきます。（内容は適当ですので雰囲気）

```
$ docker service inspect mystack_web
[
    {
        "ID": "abcdefghijklmn",
        "Version": {
            "Index": 18
        },
        "CreatedAt": "2016-06-25T08:20:01.126619833Z",
        "UpdatedAt": "2016-06-25T08:20:01.141201786Z",
        "Spec": {
            "Name": "piyo_web",
            "Labels": {
                "com.docker.stack.namespace": "piyo"
            },
            "TaskTemplate": {
                "ContainerSpec": {
                    "Image": "yyyyyy",
                }
            },
            "Mode": {
                "Replicated": {
                    "Replicas": 1
                }
            },
            "EndpointSpec": {
                "Mode": "vip",
                "Ports": [
                    {
                        "Protocol": "tcp",
                        "TargetPort": 80
                    }
                ]
            }
        },
        "Endpoint": {
            "Spec": {},
            "Ports": [
                {
                    "Protocol": "tcp",
                    "TargetPort": 80,
                    "PublishedPort": 30001
                }
            ],
            "VirtualIPs": [
                {
                    "NetworkID": "abcdefg",
                    "Addr": "10.255.0.6/16"
                }
            ]
        }
    }
]
```

見るべきは `Endpoint` の `Ports` の `PublishedPort` で、外に露出してるポートが30001なことがわかります。このポートは30001固定なのではなく、`docker task`さんがdeploy時にいい感じに決めてくれるので、例えばこれと同じ設定のstackを複数個デプロイしてもポートの重複は起こりません。

というわけで、今回の例でいうと `http://localhost:30001/` にアクセスすることで `piyo_web` にアクセスすることができました。

## サービスのPublishedPortを取得するコマンド

GoTemplate が独特で覚えられる気がしないんでメモがてら。

`docker service inspect -f '{{with index .Endpoint.Ports 0}}{{.PublishedPort}}{{end}}' piyo_web`

こんな感じで、たぶんサービスのPublishedPortの取得はできると思うので、Dockerネットワークの外から何かしようとした場合も、動的に処理できそうな感じはしてます。

## 感想
マジまだドキュメントとか無いようなもんだし、いろいろ辛いけど夢はあるので、全てのサービスが `*.dab` ファイルを持つ時代がくるといいなと思いました。まる。

あとたぶんデプロイが楽になるんじゃって噂もあるんだけど、それにしては `*.dab` の構文が弱い（レプリカ数とかの設定どうすんの・・・？）んで、早く整備されるといいなと思いましたとさ。