Docker コンテナに bind (PortForward) されたポート番号をコンテナ内から取得する方法

## 経緯

```
0.0.0.0:49168->8888/tcp
```

この `49168` を **コンテナ内から** 知りたかった

## 結論から言うと

[Docker Remote API](https://docs.docker.com/reference/api/docker_remote_api/) を Unix domain socket 経由で叩いて [jq](http://stedolan.github.io/jq/) で json パースして port 番号をゲットしました

## 方法はいろいろあるはず

docker run の時に etcd か何かに port 番号を登録しておいてコンテナ内からアクセスするとか、それなら [registrator](https://github.com/progrium/registrator) を使えばいいじゃねとか、他にもいろいろあると思います。

ひとまず準備するものを最小限にしてみたつもりですが、更にシンプルな案があれば教えてください！

## ポート番号ゲットまでの流れ

簡単にお試しできる環境書いていきます。


### 1. Dockerfile

```docker
FROM ubuntu:trusty

RUN apt-get update && apt-get install -y jq
```

### 2. ビルドします

```
$ docker build -t hoge .
```

### 3. 起動します

起動する時に Docker の Unix domain socket を volume オプションでコンテナ内にマウントします。

```
$ docker run -it --rm \
             -v /var/run/docker.sock:/var/run/docker.sock \
             -p 8888 hoge /bin/bash
```

`docker ps` で確認するとこんな感じです。

```
7b4e590705e1  hoge:latest   "/bin/bash"    17 hours ago   Up 2 seconds   0.0.0.0:49168->8888/tcp   sad_wright
```

ここで `0.0.0.0:49168->8888/tcp` となっているのを覚えていてください。

### 4. ポートをゲットしてみましょう

起動したコンテナ内で下記コマンドを実行してください。

```sh
$ echo -e "GET /containers/${HOSTNAME}/json HTTP/1.0\r\n" | \
  nc -U /var/run/docker.sock | \
  sed '1,/^\r$/d' | \
  jq '.NetworkSettings.Ports[][0].HostPort | tonumber'
49168
```

取れた！！

## コマンド解説

簡単に

### 1. Docker Remote API を叩く

```sh
$ echo -e "GET /containers/${HOSTNAME}/json HTTP/1.0\r\n" | nc -U /var/run/docker.sock
```

Docker Remote API に対して `GET /containers/コンテナID/json` を叩くと `docker inspect` と同じ json が取得できます。

[Docker Remote API v1.15 - Docker Documentation](https://docs.docker.com/reference/api/docker_remote_api_v1.15/#inspect-a-container)

> Inspect a container

今回は Unix domain socket 経由で取得するので、 `nc -U` で socket ファイルを指定してそいつに HTTP Request を投げる感じです。

ちなみに `$HOSTNAME` は各コンテナに初期で定義されている環境変数で、コンテナID が格納されています。

```sh
root@7b4e590705e1:/# echo $HOSTNAME
7b4e590705e1
```

衝突の可能性を考え、より長いコンテナIDを取得したい場合は `docker run` の `--cidfile` オプションを使用すると良いでしょう。

```sh
$ docker run -it --rm --cidfile /tmp/host.txt -v /tmp/host.txt:/tmp/host.txt hoge /bin/bash
root@ad4eed179fa0:/# echo $HOSTNAME
ad4eed179fa0
root@ad4eed179fa0:/# cat /tmp/host.txt 
ad4eed179fa00edb9b0c045822f882808e5c77bb4e2a2c61c9ac4e9fe0afed7e
```

### 2. HTTP Response で Body だけ取る

```sh
$ sed '1,/^\r$/d'
```

`nc -U` で取れる HTTP Response はヘッダーもくっついています。今回はボディだけ欲しいので先頭からボディの開始前まで削除、みたいな感じです(多分)

### 3. json パースする

```sh
$ jq '.NetworkSettings.Ports[][0].HostPort | tonumber'
```

ここはそのままです。最後の `tonumber` を付けないと `"49168"` みたいな返り値になるので、まあそれでもいいけどとりあえず変換しておきました。

複数の Port Forwarding 設定している場合はこれだと駄目なので、そこらへんは各自がんばってください。


## まとめ

1. Docker Remote API 初めて使った
1. `nc -U` で socket と遊べるのも初めて知った
1. やっぱり jq 便利

「docker daemon を tcp アクセス許可して curl でやればいいのでは?」とか jq 無くても sed でがんばるとかいろいろあると思いますが、それはそれで。

## 参考

- [Docker Remote API v1.15 - Docker Documentation](https://docs.docker.com/reference/api/docker_remote_api_v1.15/)
- [regex - Bash: Remove headers from HTTP response - Stack Overflow](http://stackoverflow.com/questions/20179623/bash-remove-headers-from-http-response)
    - body だけ取り出すやり方ググりました
- [Docker: How to get image size? - Unix & Linux Stack Exchange](http://unix.stackexchange.com/questions/134186/docker-how-to-get-image-size)
    - Docker の Unix domain socket + `nc -U` について知りました
- [Docker, how to get container information from within the container? - Stack Overflow](http://stackoverflow.com/questions/20995351/docker-how-to-get-container-information-from-within-the-container#answer-21000924)
    - `--cidfile` について知った記事です