Dockerで別ネットワークにあるコンテナをセキュアにリンクする

# 概要
あるホストで動作している Docker コンテナから別のネットワークにある別ホスト上で動作しているコンテナをリンクしたい．しかし，ネットワーク間の通信は信頼できないためセキュアな通信を張る必要がある．本来ならば，[VPNを用意してオーバーレイネットワークを構築](http://blog.namiking.net/post/2016/01/docker-swarm-over-vpn/)すべきかもしれないが，小規模な実験的プロジェクトのため，SSHトンネルと[Ambassadorパターン](http://kyudy.hatenablog.com/entry/2015/08/09/180835)で解決する．

![コンテナ間のセキュアなリンク.png](https://qiita-image-store.s3.amazonaws.com/0/62253/fcd749fa-ffc3-5f82-34d7-f4dd4bccba3a.png)

# AmbassadorS
[AmbassadorS](https://jkawamoto.github.io/ambassadors) は Ambassador パターンを SSH トンネル上で実行するための Docker イメージである．

現在，[Alpine Linux ベースのイメージ](https://hub.docker.com/r/jkawamoto/ambassadors/)と Raspberry Pi 用に [Raspbian ベースのイメージ](https://hub.docker.com/r/jkawamoto/rpi-ambassadors/)の二種類を公開している．以下では，Alpine Linux ベースの `jkawamoto/ambassadors` を使う場合を考える．（Raspberry Pi から利用する場合は `jkawamoto/rpi-ambassadors` を使う．）

AmbassadorS には，サーバ，クライアントそしてトンネルの三つのモードがある．それぞれ次のように接続して使用する．

![各モードの振る舞い.png](https://qiita-image-store.s3.amazonaws.com/0/62253/adf4f0a7-a4fe-7769-c39c-162343fba900.png)

つまり，リンク先のコンテナが走っているホスト上にサーバモードの AmbassadorS コンテナを走らせ，リンク元のコンテナが走っているホスト上でクライアントモードとトンネルモードの AmbassadorS コンテナを走らせる．

リンク先のコンテナが複数ある場合でも，各ホストにサーバモードのコンテナは一つあれば良い．トンネルモードのコンテナは，接続先のホスト一台に付き一つ必要になる．クライアントモードのコンテナは，元々の Ambassadorパターンと同じくリンク先のコンテナ数分必要になる．

AmbassadorS の実行は，基本的に

~~~
$ docker run -dt jkawamoto/ambassadors (server|client|tunnel) [-v]

Options:
  -v           Verbose mode for debugging.
~~~

の形で，第一引数としてモードを指定 (`server`, `client`, `tunnel` のいずれか) する．第二引数に `-v` を渡すとデバッグ用に詳細なログを出力するようになる．

なお，各モードとも上記に Docker へのオプション指定，マウントやリンクの設定が必要になる．具体的な設定方法は次の接続例で説明する．


# 接続例
ホスト A で MySQL と MongoDB のコンテナが走っていて，ホスト B からこれらのコンテナを利用する場合を考える．なお，MySQL と MongoDB のコンテナはそれぞれ mysql, mongo という名前とする．

## サーバモードコンテナの用意
まず始めに，ホスト A で MySQL と MongoDB のコンテナにリンクするサーバモードの AmbassadorS コンテナを用意する．

```sh
$ docker run -d --name ambassadors_server \
             -v ~/.ssh/authorized_keys:/data/authorized_keys -p 10022:22 \
             --link mysql:mysql --link mongo:mongo \
             jkawamoto/ambassadors server
```

サーバモードの AmbassadorS コンテナ はホスト B からの SSH 接続を認証するためにホスト B の公開鍵が必要で，この公開鍵は `/data/authorized_keys` に置く必要がある．上の例では，ホスト A の `~/.ssh/authorized_keys` にホスト B の公開鍵が含まれているものとして，`-v ~/.ssh/authorized_keys:/data/authorized_keys` によってマウントしている．

サーバモードの AmbassadorS コンテナは，ホスト B からの SSH 接続用に 22番ポートを公開している．`-p 10022:22` では，22番の代わりに 10022番を AmbassadorS 用に指定している．

`--link mongo:mongo --link mysql:mysql` では，AmbassadorS コンテナを MySQL と MongoDB のコンテナにリンクさせている．

## トンネルモードコンテナの用意
ホスト B では，まずホスト A へトンネルを張るトンネルモードの AmbassadorS コンテナを用意する．

```sh
$ docker run -dt --name ambassadors_tunnel \
            -v ~/.ssh/id_rsa:/root/.ssh/id_rsa -e PORT=10022 -e HOST=<ホスト A のアドレス> \
            jkawamoto/ambassadors tunnel
```

トンネルモードでは，サーバモードのコンテナに接続するための情報が必要になる．環境変数 `HOST` 及び `PORT` は，それぞれサーバモード AmbassadorS コンテナが走っているホスト A のアドレスと公開しているポート番号を指定する．そして，ホスト A で設定した公開鍵に対応する秘密鍵を `/root/.ssh/id_rsa` に置く．ここでは，ホスト A の `~/.ssh/id_rsa` が秘密鍵だとして `-v ~/.ssh/id_rsa:/root/.ssh/id_rsa` によってマウントしている．

## クライアントモードコンテナの用意
最後に，ホスト B でクライアントモードのコンテナを用意する．これは，リンク先のコンテナ数分，この例では２個用意する．

```sh
$ docker run -d --name mysql_ambassadors \
             --link ambassadors_tunnel:tunnel --expose 3306 -e PORT=3306 \
             jkawamoto/ambassadors client
$ docker run -d --name mongo_ambassadors \
             --link ambassadors_tunnel:tunnel --expose 27017 -e PORT=27017 \
             jkawamoto/ambassadors client
```

クライアントモードのコンテナは，トンネルモードのコンテナを `tunnel` という名前でリンクしなければならない．また，リンク先のコンテナが公開しているポート番号を，`--expose` と環境変数 `PORT` の両方に指定しなければならない．MySQL コンテナの場合，3306番を公開しているため `--expose 3306 -e PORT=3306` を指定している．

## サービスコンテナの利用
最後に，MySQL と MongoDB を利用するコンテナをクライアントモードのコンテナに接続する．

```sh
$ docker run -d --link mysql_ambassadors:mysql --link mongo_ambassadors:mongo some-app
```

## docker-compose
上記の例を [docker-compose](https://docs.docker.com/compose/) で行う場合，各ホストの設定ファイルは次の通り．

**ホスト A 用**

```yaml:docker-compose.yml
mysql:
  image: mysql
mongo:
  image: mongo
ambassadors:
  image: jkawamoto/ambassadors
  command: server
  volumes:
    - ~/.ssh/authorized_keys:/data/authorized_keys
  ports:
    - "10022:22"
  links:
    - mysql
    - mongo
```

**ホスト B 用**

```yaml:docker-compose.yml
tunnel:
  image: jkawamoto/ambassadors
  command: tunnel
  volumes:
    - ~/.ssh/id_rsa:/root/.ssh/id_rsa
  environment:
    HOST: xxx.xxx.xxx.xxx
    PORT: 10022
mysql_ambassadors:
  image: jkawamoto/ambassadors
  command: client
  expose: 3306
  environment:
    PORT: 3306
  links:
    - tunnel
mongo_ambassadors:
  image: jkawamoto/ambassadors
  command: client
  expose: 27017
  environment:
    PORT: 27017
  links:
    - tunnel
app:
  image: some-app-image
  links:
    - mysql_ambassadors:mysql
    - mongo_ambassadors:mongo
```

コンテナ間のリンクが多数必要になるので，上記のように docker-compose の利用を検討したい．

# まとめ
SSH トンネリングと Ambassador パターンを提供する [AmbassadorS](https://jkawamoto.github.io/ambassadors) コンテナを用いると，別のネットワークにある docker コンテナをセキュアにリンクすることができる．SSH トンエリングが切断した場合，トンネルモードのコンテナが終了するので，長期的に運用する場合には再接続する設定が必要である．