接続がVPC内部に限定されたリソース ( RDS, ElastiCache等) に、Docker Compose 内のコンテナからアクセス

# 接続がVPC内部に限定されたリソース ( RDS, ElastiCache, 他... ) に、Docker Compose 内のコンテナからのアクセス

(ちょっといじればWindowsとかでも利用できるとおもいますが、主にMacなりLinux向けです)

私が担当しているサービスのWebアプリケーションは、`docker-compose` を利用して開発し、運用環境はECRで稼働させています。
開発時、サンプルデータを作成するのが面倒なので、RDSは開発環境と検証環境は、同一のデータベースを利用しています。
つい最近まで、パブリックアクセシビリティをONにしていたのですが、セキュリティ的によろしくないので、非許容(OFF)にする事にしました。
RDSは、パブリックアクセシビリティをOFFにすると、VPC外からの接続ができなくなります。
会社などのネットワークから接続できなくなるわけですから、開発時にはRDSに直接接続できなくなりますが、SSH Tunnelingで接続できます。
つまり、RDSと同じVPC内にEC2インスタンスを立てておき、このEC2インスタンスを踏み台にしてアクセスするということです。

開発端末からRDSに、開発端末のmysqlクライアントで接続したい場合、多くの情報が見つかります。
以下がその例になります。

[RDSにSSHポートフォワーディングを利用して接続してみた](https://dev.classmethod.jp/cloud/aws/rds-portforward/)

しかし今回の場合、開発にはDocker Composeを利用しているので、ローカルでトンネルを開通しても、ブリッジなど噛ますなどしなければ、
Docker Composeのネットワークから、ローカルに貼られたトンネルを利用できません。
そこらへんの手順は結構ややこしいので、なるべく少ない手順でSSH Tunnel を開通し、接続できないかと考えました。

理想は、普段どおり `docker-compose up` だけでアプリケーションがRDSに接続できるようになることです。
新規に開発メンバーがジョインしたときなどに、なるべく開発開始までに時間がかからないようにしたいからです。
また、アプリケーションそのものに影響を与えないような設計が望ましいと考えました。

## デザイン

今回はPublic AccessibilityのないRDSへのアクセスを例に採ります。

以下の記事を大変参考にさせていただきました。

[コンテナからSSHトンネルパターン (1)](https://takuro.ws/2019/02/05/container-ssh-tunnelling/)
[コンテナからSSHトンネルパターン (2)](https://takuro.ws/2019/02/14/container-ssh-tunnelling-2/)

開発端末、（Dockerを起動するホスト）側でSSHトンネルを開通して通信するのではなく、
Docker ComposeでSSHトンネリング専用のコンテナ(アンバサダーコンテナ)を立ち上げて、アプリケーションからのMySQLの向き先をアンバサダーコンテナに向ける方法です。

![DockerCompose SSH Tunneling.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85797/a45d335c-1a91-3529-4009-6aaab948fe8b.jpeg)


このやり方なら、SSHトンネリング開通コマンドをアンバサダーコンテナのエントリーポイントに指定しておけば、docer-compose実行時に自動でトンネリングが開始されますし、同じDockerComposeネットワーク内の通信なので、ブリッジ等かませる必用はありません。

なお、証明書をボリュームマウントで利用しているのは、言わずもがなそちらのほうがセキュアだからです。
セキュリティ向上のためにVPC外のアクセスを断っているわけですから、DockerfileのADDなどでコンテナ内部に複製してしまうのは望ましくないと考えました。



## 手順

**セキュリティグループとかの設定は、省略します。**
各自で開発端末からEC2インスタンスにSSHで接続して、
EC2インスタンスにmysqlクライアントをインストールしてRDSに接続できる所くらいまでは確認しておいてください。


### ディレクトリ構成

以下の様なディレクトリ構成を例に採り、説明します

```bash

web-app/
    .env # 環境変数ファイル(きっとバージョン管理外, ECRでもタスク定義とかで設定するマスタ)
    .env.local # 環境変数ファイルその2 開発端末以外は利用しない環境変数はこちらに分けておく事にする
    docker-compose.yml
    dockerfiles/
        app/ # アプリケーションコンテナ関連 (今回はさわらない)
            Dockerfile
            ... # アプリケーションコンテナ起動時に利用するファイルなど
        vpc-ssh-tunnel/ # 今回作成するアンバサダーコンテナ関連
            Dockerfile
            run-ssh.sh # コンテナでCMDとして渡されるファイル

  ....他、アプリケーションのソースコードなど
```

### docker-compose.yml

```yaml:docker-compose.yml
version: '3'

services:
  webapp: # アプリケーション
    build:
      context: ./
      dockerfile: dockerfiles/app/Dockerfile # まあ、各自そのままでおｋ
    env_file:
      - .env
    # ~~~~~~~~~~~
    # 略
    # ~~~~~~~~~~~
    depends_on:
      - vpc-ssh-tunnel # runするときなども、vpc-ssh-tunnelコンテナを先に起動させる
  vpc-ssh-tunnel: # アンバサダーコンテナ
    build:
      context: ./
      dockerfile: dockerfiles/vpc-ssh-tunnel/Dockerfile
    env_file:
      - .env # アプリケーションで利用している環境変数
      - .env.local # SSH Tunnelingで利用する環境変数
    volumes:
      - ~/.ssh:/cred:ro # Read Onlyで、Macの秘密鍵ディレクトリをマウント

```

### Dockerfile


OpenSSHクライアントのインストール、起動時に実行するスクリプトをコピーして、エントリーポイントに指定

```Dockerfile:vpc-ssh-tunnel/Dockerfile
FROM alpine:latest

RUN apk add --no-cache openssh-client

WORKDIR /app
ADD dockerfiles/vpc-ssh-tunnel/run-ssh.sh /app

ENTRYPOINT ["/bin/sh", "/app/run-ssh.sh"]

```

### 環境変数

#### .env

この環境変数は、アプリケーションが利用する環境変数を想定しています。
現在、何らかのアプリケーションを動かしている場合、すでにDB接続先を環境変数などで指定していると思います。
それに合わせる感じでいいのですが、とにかくDockerComposeを実行する際の、接続先ホストとポートを変更します


```bash:.env
.
.
.
MYSQL_HOST=vpc-ssh-tunnel
MYSQL_PORT=33062 # 影響ないポートを任意で指定
.
.
.
略
```

#### .env.local

トンネリング用の環境変数です。
AWS上で稼働する運用環境では利用しないので、一応ファイルを分けました。
(現状、 `.env` は、ECRに環境変数を登録する際のマスタ的にも利用しているのです。。。)

```bash:.env.local
SSH_USER={EC2インスタンスのSSH接続ユーザ名}
SSH_HOST={EC2のGlobal IPアドレス}
SSH_KEY_NAME={秘密鍵のファイル名}
MYSQL_OUTBOUND_HOST={RDSのエンドポイント}
MYSQL_OUTBOUND_PORT=3306　# RDS接続ポート。基本3306だと思うけど変えてるならここも変更
```


### エントリーポイント

ENTRYPOINTで指定しているシェルスクリプトです。
上述の2つの環境変数を利用して、SSHのトンネルを開通します

```bash:vpc-ssh-tunnel/run-ssh.sh
#!/bin/sh
ssh ${SSH_USER}@${SSH_HOST} -p 22 -i /cred/${SSH_KEY_NAME} -o "StrictHostKeyChecking no" -4 -fNL 0.0.0.0:${MYSQL_PORT}:${MYSQL_OUTBOUND_HOST}:${MYSQL_OUTBOUND_PORT}
while true; do sleep 30; done;
```



## おしまい

あとは、 `docker-compsoe up --build`などで、アンバサダーコンテナをビルドしつつオーケストレーションしましょう。
あ、`docker-compose.yml` で、トンネル入口のポート番号(例だと33062)とかを空けておくなりすれば、開発端末のmysqlクライアントから接続もできるのではないかなと思います。

課題として、SSH接続が数十分で切れる問題が起きているので、それも解決できたら追記しようと思います。