Dockerコンテナ上のアプリにSecrets Manager経由でDBの接続情報を設定する

前回、Parameter StoreとSecrets Managerの機能比較を行いました。

[AWSのParameter StoreとSecrets Manager、結局どちらを使えばいいのか？比較 - Qiita](https://qiita.com/tomoya_oka/items/a3dd44879eea0d1e3ef5)

パラメータストアに一度に大量アクセスするとThrottlingExceptionが発生する件は、
別途検証して見極めたいと思います。

ECSでは、Task Definition中の環境変数にDBのユーザ名、パスワードを含めることは推奨されていません。
今回は、**Secrets ManagerからDB接続に必要なユーザ名やパスワード等を取得し、**
**コンテナの環境変数に設定する**方法を検証してみます。

## データストアを作成する
RDSであればなんでもいいのですが、
今回はSecrets Managerで標準サポートされているAurora PostgreSQL-CompatibleでDBを作成します。

作成方法はクラメソさんのブログを参考にさせて頂きましょう。

[PostgreSQL 互換 Amazon Auroraが正式リリースされました ｜ Developers.IO](https://dev.classmethod.jp/cloud/aws/postgresql-amazon-aurora-now-available/)

## Secrets ManagerにDB接続情報を保存する

Secrets Managerにアクセスします。
"Store a secret"をクリック。
![sm_000001.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/c1c7e123-7596-4595-3d62-4da2a7cb1a73.jpeg)

"Credentials for RDS database"を選択し、
Auroraのシークレット情報(ユーザ名とパスワード)を入力します。

![sm_000002.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/be7e3d01-859b-7ae8-12eb-748f603f1612.jpeg)

"Select which RDS database this secret will access"で、
作成したAuroraのインスタンスを選択します。
![sm_000003.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/3ef536f1-c7c6-c751-d849-32d91ebad8a6.jpeg)

次のページでは、シークレット名とDescriptionを入力します。
シークレット名には「/」が使えますので、階層型の名前を定義できます。
![sm_000004.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/472c66d2-751d-a476-d4da-245f9ca01ff2.jpeg)

シークレット情報のローテーションの設定ができますが、
今回はデフォルトのDisableのままとします。
![sm_000005.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/50fc9a7a-6b4a-cfdc-da7f-6024fa1348ac.jpeg)

Reviewで内容を確認し、シークレットを作成します。
![sm_000006.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/fd844cc3-ac41-1059-8277-bf266e6a60ff.jpeg)

シークレットが作成されました。
![sm_000008.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/91927530-6b39-969f-b828-d6e2174b7baf.jpeg)


シークレットが作成されると、前掲の手順で入力したユーザ名、パスワードの他に、
DBエンジン名、RDSエンドポイント、ポート番号、DB識別子が
自動保存されます。
![sm_000009.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/431d800b-68cc-896a-6106-2de67771702a.jpeg)

シークレットを取得するためのコードスニペットも確認可能です。
![sm_000007.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/b873fdbb-a217-1de4-b5e7-5d114f741dc5.jpeg)


## コンテナをビルドする環境を準備

IAMロールを利用すれば、AWS各種サービスへのアクセス制御が簡単且つセキュアに実現できます。
よって、今回はコンテナをビルドする環境にはAmazon Linuxを使います。

### IAMロールを作成

EC2を起動する前にIAMロールを作成しておきます。

サービスの選択では"EC2"を選択し、
名前は"Ec2InstanceRoleForECS"とでもしておきましょう。

ポリシーは、AWS Managed Policyから

- AmazonEC2ContainerServiceforEC2Role
- SecretsManagerReadWrite

をAttachします。

### EC2を起動

EC2を起動します。今回は、
**Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type - ami-06cd52961ce9f0d85**
を利用しました。

起動の手順は割愛しますが、以下にご注意下さい。

- シークレットを保存したSecrets Managerと同じリージョンを利用する。
- 前掲の手順で作成したIAMロールをアタッチする。
- DockerイメージをPullする必要があるため、EC2からインターネットにアクセス可能にしておく。
- セキュリティグループでポート22と80へのインバウンドアクセスを許可する。(アクセス元は絞りましょう)
- **(忘れがち注意)**前掲の手順で作成したAurora(PostreSQL)のセキュリティグループにて、本EC2のセキュリティグループ(or サブネット)からのポート5432へのインバウンドアクセスを許可する。

### Dockerの環境をセットアップ

EC2にec2-userでログインし、dockerとgitをインストールします。

```bash:
$ sudo yum -y install docker
$ sudo yum -y install git
```

dockerとgitがインストールされたことを確認。

```bash:
$ docker --version
Docker version 18.03.1-ce, build 3dfb8343b139d6342acfd9975d7f1068b5b1c3d3
$ git --version
git version 2.14.4
```

ec2-userをdockerグループに追加。

```bash:
sudo usermod -a -G docker ec2-user
```

dockerを起動。

```bash:
$ sudo service docker start
Starting cgconfig service:                                 [  OK  ]
Starting docker:        .                                  [  OK  ]
```

さて、Aurora(PostgreSQL)に接続するほどよい感じのDockerイメージを探していたのですが、
AzureのWeb App for Containersのチュートリアルでいい感じのイメージが紹介されていました。

[GitHub - Azure-Samples/docker-flask-postgres: A Flask application that demonstrates how to build data-driven Python apps in Azure App Service. ](https://github.com/Azure-Samples/docker-flask-postgres)

flaskを使っているのは趣味ですのあまり気にしないで下さい。
AzureやAWSの違いを意識せず、イメージをpullして利用できるところがDockerの素晴らしいところです。

Githubからリソースをクローンします。

```bash:
$ git clone https://github.com/Azure-Samples/docker-flask-postgres.git
```

ディレクトリが作成されるので移動。

```bash:
$ cd docker-flask-postgres
$ ls
app  Dockerfile  LICENSE  README.md  requirements.txt
```

### 設定ファイル修正
README.mdによると

>docker build -t docker-flask-sample .
>docker run -it --env DBPASS="<PASSWORD>" --env DBHOST="<SERVER_HOST_NAME>" --env DBUSER="<USERNAME>" --env DBNAME="<DATABASE_NAME>" -p 5000:5000 docker-flask-sample

でアプリが実行できます。

が、Secrets ManagerからDB接続情報を取得したいのと、
アプリのポートを80に変更したいので、各種設定ファイルを修正します。

コンテナ起動時にENTRYPOINTを活用してDB接続情報をSecrets Managerから取得⇒環境変数に設定したいので、
entrypoint.shを新規作成します。

```bash:entrypoint.sh
#!/bin/bash

set -e

export SECRET=$(aws --region ap-northeast-1 secretsmanager get-secret-value --secret-id dev/docker-flask-postgres/postgres | jq .SecretString | jq fromjson)

export DBUSER=$(echo $SECRET | jq -r .username)
export DBPASS=$(echo $SECRET | jq -r .password)
export DBNAME=$(echo $SECRET | jq -r .dbClusterIdentifier)
export DBHOST=$(echo $SECRET | jq -r .host)

exec "$@"
```

パーミッションも変更しておきましょう。

```bash:
$ chmod +x entrypoint.sh
```

コンテナにAWS CLIが必要になりますので、
requirements.txtの最終行にawscliを追記します。

```diff:requirements.txt
alembic==0.9.1
appdirs==1.4.3
click==6.7
Flask==0.12.1
Flask-Migrate==2.0.3
Flask-Script==2.0.5
Flask-SQLAlchemy==2.2
itsdangerous==0.24
Jinja2==2.9.6
Mako==1.0.6
MarkupSafe==1.0
packaging==16.8
psycopg2==2.7.1
pyparsing==2.2.0
python-editor==1.0.3
six==1.10.0
SQLAlchemy==1.1.9
Werkzeug==0.12.1
+ awscli==1.15.71
```

最後に、Dockerfileを修正します。
ENTRYPOINTの設定、jqのインストール、flaskのリッスンポートを80に変更、
などを行います。

```diff:Dockerfile
FROM tiangolo/uwsgi-nginx-flask:python3.6

COPY requirements.txt /
+COPY entrypoint.sh /

WORKDIR /

+RUN pip install --upgrade pip && \
+    pip install -r ./requirements.txt --no-cache-dir
-RUN pip install -r ./requirements.txt --no-cache-dir
+RUN apt-get -y update && \
+    apt-get -y install jq
+ENTRYPOINT ["/entrypoint.sh"]

COPY app/ /app/

WORKDIR /app

ENV FLASK_APP=app.py
-CMD flask db upgrade && flask run -h 0.0.0.0 -p 5000
+CMD flask db upgrade && flask run -h 0.0.0.0 -p 80
```

### Dockerイメージをビルドして起動
では、ビルドしてみましょう。

```docker:
$ docker build -t flask-postgresql-sample .
```

コンテナを起動。

```docker:
$ docker run -it -p 80:80 flask-postgresql-sample
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
 * Serving Flask app "app"
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
```

### 動作確認
アプリにアクセスしてみましょう。

![app_20180826_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/8882873c-3fcc-3377-fd12-416b52a29218.jpeg)

起動しましたね。
Register!をクリックして、ゲスト情報を登録してみます。

![app_20180826_02.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/3197c43c-385a-f968-589a-23a49b538814.jpeg)

登録できました。
![app_20180826_03.JPG](https://qiita-image-store.s3.amazonaws.com/0/281143/dc0c2590-7d22-66bc-e24b-0c4b4e397a58.jpeg)

### DBを確認してみる
Auroraの方を確認してみます。

```bash:
pythondb=> \dt;
              List of relations
 Schema |      Name       | Type  |  Owner
--------+-----------------+-------+----------
 public | alembic_version | table | pythondb
 public | guests          | table | pythondb
(2 rows)

pythondb=> select * from guests;
 id |   name   |       email        | partysize
----+----------+--------------------+-----------
  1 | hogehoge | hogehoge@qiita.com |         3
(1 row)
```

コンテナが起動時にSecrets ManagerからDB接続情報を取得し、
アプリの処理によってAuroraにデータが保存できたことが確認できました。

## ECS/Fargateでも動きます

Dockerイメージはできましたので、
ECRにPushし、ECSのクラスタとタスク定義、サービスを作成すれば、
ECS上でも同様に動かすことができます。

Dockerアプリケーションのセキュリティレベル向上の参考になれば幸いです。