エンジニアの@macs_6です。
AWS上で稼働中のサービスをスケールアウトする際に、DBのパスワード等のセキュアな設定をどこに保存しておくか困ったことはありませんか?
HerokuやKubernetesであれば以下の機能があります。
- Heroku: Config Vars
- Kubernetes: Secrets
AWSではどこで管理すればいいのでしょうか?
KubernetesをAWS上に構築するサンプルを見てみるとSecretsの裏側にはパラメータストアが利用されています。
そこでこのブログではAWS ECS上でのRedash構築を例にして、パラメータストアとKMSを使った秘密情報を含んだ環境変数の管理方法について紹介します。
現在のECS環境については、“会社の本番環境をDocker(ECS)に置き換えるために準備したこと気づいたこと”で紹介しているので、そちらを参照して下さい。
目次
システム全体像と今回使うAWSのサービス
登場するAWSのサービスは以下です。
- ECS (Elastic Container Service)
- Dockerコンテナの実行場所です。
- SSM (Systems Manager) のパラメータストア
- 各種AWSサービスから参照・連携ができる設定管理のためのKVSといった感じ。今回は秘密情報を含めた環境変数の保存に使います。
- KMS (Key Management Service)
- パラメータストアの暗号化に使うキーはKMSで発行します。
- IAM (Identity and Access Management)
- KMSで発行・管理するキーへのアクセス権限の制御に使います。
これらを使って以下のことをします。
- ECS上で稼働するDockerコンテナ(Redash)の用意
- パラメータストアに設定を保存、KMS上の暗号化キーで暗号化
- 暗号化キーにアクセスできるIAM Roleを作成、Dockerコンテナに付与
- Dcokerコンテナはパラメータストアに保存された設定を使って稼働
docker-compose.yml
の用意
早速、ECS上のTask Definitionをecs-cli
を使って、docker-compose.yml
から作成します。
現在、ecs-cli
はv3をサポートしていないためv2を使用します。
# docker-compose-production.yml
version: '2'
services:
server:
image: redash/redash:4.0.0.b3387
command: bash -c 'wget https://path/to/parameter-store-exec -O parameter-store-exec && chmod +x parameter-store-exec && AWS_REGION=ap-northeast-1 PARAMETER_STORE_EXEC_PATH=/prod/redash ./parameter-store-exec bin/docker-entrypoint server'
ports:
- "0:5000"
environment:
PYTHONUNBUFFERED: 0
REDASH_LOG_LEVEL: INFO
REDASH_REDIS_URL: redis://your-redis-host:6379/0
# REDASH_DATABASE_URL: stored in Parameter Store
REDASH_HOST: example.com
REDASH_ALLOW_SCRIPTS_IN_USER_INPUT: true
REDASH_DATE_FORMAT: YY/MM/DD
REDASH_PASSWORD_LOGIN_ENABLED: true
logging:
driver: awslogs
options:
awslogs-group: /your-group
awslogs-region: ap-northeast-1
awslogs-stream-prefix: redash
mem_reservation: 134217728 # 128MB
worker:
image: redash/redash:4.0.0.b3387
command: bash -c 'wget https://path/to/parameter-store-exec -O parameter-store-exec && chmod +x parameter-store-exec && AWS_REGION=ap-northeast-1 PARAMETER_STORE_EXEC_PATH=/prod/redash ./parameter-store-exec bin/docker-entrypoint scheduler'
environment:
PYTHONUNBUFFERED: 0
REDASH_LOG_LEVEL: INFO
REDASH_REDIS_URL: redis://your-redis-host:6379/0
# REDASH_DATABASE_URL: stored in Parameter Store
QUEUES: "queries,scheduled_queries,celery"
WORKERS_COUNT: 2
logging:
driver: awslogs
options:
awslogs-group: /your-group
awslogs-region: ap-northeast-1
awslogs-stream-prefix: redash
mem_reservation: 134217728 # 128MB
今回はAWS上で構築する想定なので、データベースやRedisはRDSやElastiCacheといったマネージドのものを使いましょう。
ローカルで使うdocker-compose.yml
と異なるのは以下の点です。
- commandにてパラメータストアの値を展開するための
parameter-store-exec
を使っている。 - ポートのマッピングで、動的ポートマッピングするためにホスト側を0にしている。
- REDASH_DATABASE_URLがパラメータストアに切り出されている。(DBパスワードを含むため)
- loggingのdriverにawslogsが指定されている。
- mem_reservationでmemoryのsoft limitを指定している。(hard limitを使うならmem_limitにする)
パラメータストアから環境変数を展開するparameter-store-execはGoで書かれているので、事前に対象のアーキテクチャに合わせてビルドしておきます。
parameter-store-exec
に似たツールとしてaws-envというものもあります。
parameter-store-exec
: 引数で渡されたcommandに変数を適用する。entrypointとして使用する想定。aws-env
: 取得した変数を環境変数に展開する。
今回はaws-env
で展開出来ないパラメータがあったため、parameter-store-exec
を使っています。
上記のdocker-compose.yml
ではcommandにparameter-store-exec
のダウンロードと実行を詰め込んでいますが、Dockerfile
の記述してENTRYPOINTにしておくと、docker-compose.yml
の記述量を減らせます。
公式のイメージを基にDockerfile
に書く場合は、元々のENTRYPOINTを上書きすることに注意しましょう。
ecs-cli
向けに、ecs-params.yml
というファイルも用意してdocker-compose.yml
に対応しないフィールドの値を定義します。
# ecs-params.production.yml
version: 1
task_definition:
ecs_network_mode: bridge
task_role_arn: your-role-arn
run_params:
network_configuration:
awsvpc_configuration:
subnets:
- your-subnet-0
- your-subnet-1
ここで大事なのはtask_role_arn
に適切なロールを与えて、docker-compose.yml
でパラメータストアに切り出した秘密情報にアクセスできるようにすることです。task_execution_role
ではないことに注意しましょう。
ロールの作成は後のセクションで紹介します。
パラメータの保存と暗号化
まずは暗号化のためのキーをKMSで作成します。あとでGUI上でも判別できるようにaliasで名前もつけておきます。
$ aws kms create-key --description ecs-redash --region ap-northeast-1
$ aws kms create-alias --alias-name "alias/prod-redash" --target-key-id "your-key-id"
上記のaliasはalias/
で始める必要があります。
次に先ほど作ったキーで値をパラメータストアに暗号化して保存します。
$ aws ssm put-parameter --name /prod/redash/REDASH_DATABASE_URL --value "postgresql://user:passwrod@host/database" --type SecureString --key-id "your-key-id"
パラメータストアは上記の/prod/redash/REDASH_DATABASE_URL
のようにスラッシュで区切って階層化したり、タグを使って管理することが出来ます。
parameter-store-exec
やaws-env
は、特定の階層の値をまとめて展開することが出来るので、今回は/prod/redash/
という階層で値を管理しています。
権限の設定
ロールの作成はこちらを参考に行います。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
上記をecs-tasks-trust-policy.json
という名前で保存してロールを作成。
$ aws iam create-role --role-name ecs-redash --assume-role-policy-document file://ecs-tasks-trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Sid0",
"Effect": "Allow",
"Action": "ssm:DescribeParameters",
"Resource": "*"
},
{
"Sid": "Sid1",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
],
"Resource": [
"arn:aws:ssm:ap-northeast-1:<account-id>:parameter/prod/redash/*"
]
},
{
"Sid": "Sid2",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"arn:aws:kms:ap-northeast-1:<account-id>:key/<key-id-for-prod-redash-key>"
]
}
]
}
次に上記のファイルをredash-secret-access.json
という名前で作成して、以下を実行してIAMポリシーを作成し、先ほど作ったロールに付与します。
$ aws iam create-policy --policy-name ecs-redash --policy-document file://redash-secret-access.json
$ aws iam attach-role-policy --role-name ecs-redash --policy-arn "arn:aws:iam::<account-id>:policy/ecs-redash"
ssm:GetParameters
やssm:GetParametersByPath
等、必要なコマンドへの権限が全て揃っているか注意しましょう。
今回のファイルはparameter-store-exec
の使用に必要な権限を付与しています。
作成したロールをecs-params.yml
で指定しておきます。
Redashの起動
ここまでで以下のものを用意しました。
docker-compose.yml
とecs-params.yml
- パラメータストア上に暗号化されたDBの秘密情報
- パラメータストアと暗号化に使用したキーへの権限
あとはこれらを使ってECSのServiceをservice up
で起動するだけです。
$ ecs-cli compose -c your-cluster-name -p your-project-name service up --target-group-arn your-elb-arn --container-name server --container-port 5000
ecs-cli compose
はデフォルトでdocker-compose.yml
とecs-params.yml
を参照しますが、別名を使っている場合は指定が必要です。
上記のコマンドで指定しているELBの作成作業についてはここでは割愛しています。
Redashについては初回にDBの設定が必要なので、docker-compose run
の要領で以下も実行しておきます。
$ ENTRYPOINT="wget https://path/to/parameter-store-exec -O parameter-store-exec && chmod +x parameter-store-exec && AWS_REGION=ap-northeast-1 PARAMETER_STORE_EXEC_PATH=/prod/redash ./parameter-store-exec bin/docker-entrypoint"
$ ecs-cli compose -c your-cluster-name -p your-project-name run server "bash -c \"$ENTRYPOINT create_db\""
ここで対象のホストにアクセスすると、パラメータストア上のDB情報をRedashが参照して、正しく動作していることが確認できるはずです。
運用時には、docker-compose.yml
やDockerイメージに変更があった場合にservice up
を実行することでTask Definitionが更新され、Serivceが使用しているTask Definitionのrevisionも更新され、最新のDockerイメージでコンテナを起動することが出来ます。
まとめ
一度IAMロールの作成やキーの作成等を済ませてしまえば、ローカルでDockerを使う場合とそれほど大きな違いなく、DBのパスワード等のセキュアな情報を使ってECS上でコンテナを動かすことができます。
今回はecs-redash
というIAMロールを作って、パラメータストアの/prod/redash
以下にセキュアな環境変数を保存しました。
このようにパラメータストアではアプリケーション毎にアクセス権限を分けておくことができるので、担当アプリケーションが細かく分かれているような場合でも使用することが出来ます。
パラメータストアを活用して、アプリケーションも設定もポータブルに管理しておきましょう!
まっくす
Housmartでは不動産業界を変えるカウルを支えるエンジニアを募集しています。
今話題のReTech!業界を変えるカウルを支えるエンジニアをWanted!