EC2 Systems Manager のパラメータストアを利用したアプリケーション環境設定の管理

2017.02.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、藤本です。

AWS re:Invent 2016 で EC2 Systems Manager がリリースされ、EC2 の運用がより便利になりました。今回はその一機能のパラメータストアで実際の開発現場を想定して、アプリケーションの環境設定を管理する方法を考えてみました。

アプリケーションの環境設定管理

アプリケーションが動作する上で環境に依存する設定は多くの場合、存在します。例えば、Web アプリケーションであれば、データベース、キャッシュシステム、マイクロサービス化が増えてきている昨今では他マイクロサービスコンポーネントなど、多くの他システムとの接続情報があります。データベースへ接続するにはデータベースのエンドポイント、ポート番号、ユーザ名、パスワード、データベース名、キャッシュシステムへ接続するにはエンドポイントの情報が必要となります。これらはアプリケーションが動作する環境によって値が変動します。開発環境がローカル端末であれば、ローカル環境のIPアドレスや、テスト用のユーザー名を利用します。本番環境が AWS 上であれば、RDS や ElastiCache で払い出されたDNS名を利用したりします。これらの設定はアプリケーション内に環境に応じた設定ファイルに持たせるなどして解決することができていました。

application-config

The Twelve-Factor App

先日の AWS Black Belt Docker on AWS でも紹介があった The Twelve-Factor App では設定は設定ファイルではなく、環境変数に格納することを推奨されています。

Docker は環境変数を設定しやすい仕組みがありますが、EC2 は外部から環境変数を設定する仕組みがないような気がしています。もちろんファイルベースで設定値を環境変数に記述することは可能です。ただファイルベースで設定値を管理すると、AutoScaling 下ではステージング、本番で同じ AMI を利用することができません。他にもコードから設定を分離する方法として EC2 のタグを利用することでインスタンス単位で外部から設定を注入することができますが、クレデンシャル情報を注入する場合、IAM で EC2 のタグにアクセスする権限を持つ人が閲覧できてしまい、権限区分が異なるような気がします。

パラメータストア

そこで EC2 ではパラメータストアを利用することでセキュアでコードベース、OS から設定情報を分離が可能だと考えました。パラメータストアに関しては下記エントリをご参照ください。

今回考えてみた利用方法は以下のような感じです。

parameter-store(1)

インスタンスに割り当てられたタグからグループを持ってきて、必要な設定値をパラメータストアから取得します。それにより OS レイヤではなく、AWS レイヤから設定値を注入することができ、環境毎に OS、アプリケーションを設定する必要がありません。ステージング、本番で同じ AMI を利用することもできます。(The Twelve-Factor App の設定ではグループ名の払い出しも非推奨ですが、いい方法が思いつかず、、)

試してみた

それでは今回は Django アプリケーションを例に同じコードベースで環境毎(EC2インスタンスのタグによって)に異なるデータベースを参照する実装を実施してみました。

上で書いたような構成通り、開発環境はローカル端末、ステージング、本番環境は AWS で構成しています。AWS 環境ではタグから環境名を、パラメータストアから環境名に応じたキーによって MySQL の DNS名、ユーザー名、パスワードを取得します。パスワードは KMS により暗号化して格納しています。ローカル端末では個別に環境変数を設定します。

パラメータストア登録

今回は RDS for MySQL の DNS 名、ユーザー名、パスワードをステージング、本番それぞれ格納します。

# aws ssm put-parameter --name stg.db_host --type String --value stg.xxxxxxxxxxx.us-east-1.rds.amazonaws.com
# aws ssm put-parameter --name stg.db_user --type String --value fujimoto
# aws ssm put-parameter --name stg.db_password --type SecureString --value password
# aws ssm put-parameter --name prd.db_host --type String --value prd.xxxxxxxxxxx.us-east-1.rds.amazonaws.com
# aws ssm put-parameter --name prd.db_user --type String --value fujimoto
# aws ssm put-parameter --name prd.db_password --type SecureString --value Password

EC2_Management_Console

SecureString で登録したパスワードは KMS によって暗号化されています。

設定ファイル

Django は環境設定情報をsettings.pyという Python ファイルで管理します。環境変数から取得するように設定します。

$ cat settings.py
(略)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': os.environ.get('DB_NAME') if os.environ.get('DB_NAME') else 'project',
'HOST': os.environ.get('DB_HOST'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'PORT': '3306',
}
}

動作確認

Systemテーブルからenvカラムの値を取得して、JSON で返すだけのシンプルなアプリケーションです。開発環境のデータベースにはdev、ステージング環境はstg、本番環境はprdを登録しています。

view.py
def env(request):
env = System.objects.all()[0].env
return HttpResponse(json.dumps({
'env': env
}))

ステージング環境

インスタンスにEnvタグにstgを設定します。

Apache と Django を mod_wsgi で連携しているため、httpd の環境設定ファイルにパラメータストアから設定値を取得するスクリプトを記述します。参考までにパラメータストアから設定値を取得するサンプルコマンドを以下に記載します。

# vi /etc/sysconfig/httpd
INSTANCE_ID=$(curl 169.254.169.254/latest/meta-data/instance-id)
ZONE=$(curl 169.254.169.254/latest/meta-data/placement/availability-zone)
REGION=$(echo ${ZONE/%?/})
APP_ENV=$(aws --region ${REGION} ec2 describe-instances --instance-ids ${INSTANCE_ID} --query "Reservations[0].Instances[0].Tags[?Key=='Env'].Value" --output text)
export DB_HOST=$(aws --region ${REGION} ssm get-parameters --name ${APP_ENV}.db_host --query "Parameters[0].Value" --output text)
export DB_USER=$(aws --region ${REGION} ssm get-parameters --name ${APP_ENV}.db_user --query "Parameters[0].Value" --output text)
export DB_PASSWORD=$(aws --region ${REGION} ssm get-parameters --name ${APP_ENV}.db_password --with-decryption --query "Parameters[0].Value" --output text)

# /etc/init.d/httpd start
Starting httpd: [ OK ]

# curl localhost/app/env/
{"env": "stg"}

ステージング用のデータベースに登録したstgが返ってきました。

本番環境

ステージング環境との違いはインスタンスのタグだけです。Envタグにprdを設定しています。

# curl localhost/app/env/
{"env": "prd"}

本番用のデータベースに登録したprdが返ってきました。

ローカル環境

ローカル環境では個別に環境変数を設定して、アプリケーションを起動します。

$ DB_HOST=10.255.0.131 DB_USER=fujimoto DB_PASSWORD=password ./manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
February 19, 2017 - 12:03:09
Django version 1.10.5, using settings 'project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

$ curl localhost:8000/app/env/
{"env": "dev"}

ローカルのデータベースに登録したdevが返ってきました。

まとめ

いかがでしたでしょうか? The Twelve-Factor App は SaaS アプリケーションのベストプラクティスとして定義されていますが、多くのアプリケーションのベストプラクティスになると思いますので、ぜひご一読ください。パラメータストアを利用することで設定値をセキュアで OS から分離でき、無料で非常に簡単に利用できます。