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


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

AWSのParameter StoreとSecrets Manager、結局どちらを使えばいいのか?比較 - Qiita

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

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

データストアを作成する

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

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

PostgreSQL 互換 Amazon Auroraが正式リリースされました | Developers.IO

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

Secrets Managerにアクセスします。
"Store a secret"をクリック。

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

"Select which RDS database this secret will access"で、
作成したAuroraのインスタンスを選択します。

次のページでは、シークレット名とDescriptionを入力します。
シークレット名には「/」が使えますので、階層型の名前を定義できます。

シークレット情報のローテーションの設定ができますが、
今回はデフォルトのDisableのままとします。

Reviewで内容を確認し、シークレットを作成します。

シークレットが作成されました。

シークレットが作成されると、前掲の手順で入力したユーザ名、パスワードの他に、
DBエンジン名、RDSエンドポイント、ポート番号、DB識別子が
自動保存されます。

シークレットを取得するためのコードスニペットも確認可能です。

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

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をインストールします。

$ sudo yum -y install docker
$ sudo yum -y install git

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

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

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

sudo usermod -a -G docker ec2-user

dockerを起動。

$ 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.

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

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

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

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

$ 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="" --env DBHOST="" --env DBUSER="" --env DBNAME="" -p 5000:5000 docker-flask-sample

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

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

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

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 "$@"

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

$ chmod +x entrypoint.sh

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

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に変更、
などを行います。

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 build -t flask-postgresql-sample .

コンテナを起動。

$ 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)

動作確認

アプリにアクセスしてみましょう。

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

登録できました。

DBを確認してみる

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

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 | [email protected] |         3
(1 row)

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

ECS/Fargateでも動きます

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

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