小西秀和です。
これまで、次の記事のようなAWSの静的ウェブサイトホスティングをテーマにAWS CloudformationやAWS Amplifyの使用例を紹介してきました。
- AWS LambdaカスタムリソースでSSL証明書・基本認証・CloudFrontオリジンフェイルオーバーを作成するAWS Cloudformationスタックを別リージョンにデプロイする
- AWS Amplify CLIとAWS CloudformationでAmplify Console Hostingと同じ機能の再現を試みる - AWS CloudFormationによるAWS Amplify CLIの拡張
今回からはAWS Cloud Development Kit(AWS CDK)でこれまでの静的ウェブサイトホスティングと同様の構成を作成する方法について見ていきたいと思います。
これまでの記事で扱ったAWS CloudformationではAWS Lambdaカスタムリソースを使用してマルチリージョンにスタックをデプロイする方法を試してきました。
参考:AWS LambdaカスタムリソースでAWS Cloudformationスタックを別リージョンにデプロイする
一方、AWS CDKを使用するとマルチリージョンへのスタックのデプロイは簡単に指定できるようになっています。
また、AWS CDKで用意されているカスタムリソース(AwsCustomResource)ではAWS SDK呼び出しが簡単に記述できるため、リージョン間のパラメータの受け渡しにリージョンを指定したAWS CDKカスタムリソースを使用することが可能です。今回はこれらの実装例を紹介します。
なお、AWS CDK V1ではJavaScript、TypeScript、Python、Java、C#のプログラミング言語がサポートされています。
このうち、技術情報が多く、テストツールなど最新の機能の取り込みも充実して一般的に広く使われているのはTypeScriptです。
ただ、Pythonにも一定のニーズがある一方でコード例などの技術情報が少ないため、自主研究ベースの私の記事では敢えてPythonを使ってみたいと思います。
※本記事および当執筆者のその他の記事で掲載されているソースコードは自主研究活動の一貫として作成したものであり、動作を保証するものではありません。使用する場合は自己責任でお願い致します。また、予告なく修正することもありますのでご了承ください。
AWS CDKプロジェクトのスタック毎にリージョンを指定する
AWS CDKではプロジェクトでスタックをリージョン指定で作成することができます。
AWS CDKプロジェクト内のapp.pyでスタックにリージョンを指定した例を記載します。
■app.py
#!/usr/bin/env python3 import os from aws_cdk import core as cdk from aws_cdk import core from s3cf_acm_edge_s3sec.certificate_stack import CertificateStack from s3cf_acm_edge_s3sec.lambda_edge_stack import LambdaEdgeStack from s3cf_acm_edge_s3sec.s3secondary_stack import S3secondaryStack from s3cf_acm_edge_s3sec.s3cloudfront_stack import S3CloudfrontStack app = core.App() certificate_stk = CertificateStack(app, "CdkS3CfAllCertificateStack", env=core.Environment(region='us-east-1')) lambda_edge_stk = LambdaEdgeStack(app, "CdkS3CfAllLambdaEdgeStack", env=core.Environment(region='us-east-1')) s3secondary_stk = S3secondaryStack(app, "CdkS3CfAllS3secondaryStack", env=core.Environment(region='us-east-1')) s3cloudfront_stk = S3CloudfrontStack(app, "CdkS3CfAllS3CloudfrontStack", env=core.Environment(region='ap-northeast-1')) s3cloudfront_stk.add_dependency(certificate_stk) s3cloudfront_stk.add_dependency(s3secondary_stk) s3cloudfront_stk.add_dependency(lambda_edge_stk) app.synth()
この例ではS3CloudfrontStackをap-northeast-1リージョンで作成し、その他のスタックをus-east-1で作成しています。
別リージョンで作成するスタックもadd_dependency
で依存関係を指定して、作成順序を指定することが可能です。
このようにAWS CDKでは簡単にスタックを作成するリージョンを指定できますが、スタック間でパラメータをやり取りするためにはAWS CDKカスタムリソースを使用するなど工夫が必要になります。
スタック間のパラメータの受け渡しはリージョン指定のAWS CDKカスタムリソースを使用する
スタック間のパラメータの受け渡しはAWS CDKのカスタムリソースでAWS SDK呼び出しをリージョンを指定して実行し、ストレージサービスなどにデータを保存するといった方法で実現できます。
今回はAWS Systems ManagerパラメータストアとAWS Secrets Managerに対してパラメータの保存と取得をする機能をAWS CDKカスタムリソース用のConstructを継承したクラスとしてまとめた例を記載します。
■x_region_param.py
from aws_cdk import ( core, custom_resources as cr ) class XRegionParam(core.Construct): def __init__(self, scope: core.Construct, id: str, region, service, action, key, val, description, **kwargs): super().__init__(scope, id, **kwargs) stack = core.Stack.of(self); param_region = region #リージョン指定がない場合は呼出元スタックのリージョンを使用する if not param_region: param_region = stack.region param_service = str(service).upper() param_action = str(action).upper() if param_service == 'SSM': if param_action == 'GET': act = 'SsmGet' res = self.ssm_get_parameter(action + id, param_region, key) elif param_action == 'PUT': act = 'SsmPut' res = self.ssm_put_parameter(action + id, param_region, key, val, description) else: act = None res = None elif param_service == 'ASM' or param_service == 'SECRETSMANAGER': if param_action == 'GET': act = 'AsmGet' res = self.asm_get_secret_string(action + id, param_region, key) elif param_action == 'PUT': act = 'AsmPut' res = self.asm_put_secret_string(action + id, param_region, key, val, description) else: act = None res = None else: act = None res = None self.action = act self.result = res def get_result(self): return {'action': self.action, 'result': self.result} #AWS Systems Managerパラメータストアからリージョン名、パラメータ名を指定してパラメータを取得する def ssm_get_parameter(self, id_name, region, parameter_name): stack = core.Stack.of(self); param_region = region #リージョン指定がない場合は呼出元スタックのリージョンを使用する if not param_region: param_region = stack.region #AWS CDKカスタムリソース内でAWS SDK呼出を実行してパラメータを取得する result_params = cr.AwsCustomResource(self, id_name, policy=cr.AwsCustomResourcePolicy.from_sdk_calls( resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE ), on_update=cr.AwsSdkCall( service='SSM', action='getParameter', parameters={ 'Name': parameter_name }, region=param_region, physical_resource_id=cr.PhysicalResourceId.of(id_name) ) ) #レスポンス内の要素を指定して取得する:{"Parameter":{"Value":"<取得するパラメータ>"}} result = result_params.get_response_field('Parameter.Value') return result #AWS Systems Managerパラメータストアにリージョン名、パラメータ名を指定してパラメータを作成・更新・削除する def ssm_put_parameter(self, id_name, region, parameter_name, string_value, description): stack = core.Stack.of(self); param_region = region #リージョン指定がない場合は呼出元スタックのリージョンを使用する if not param_region: param_region = stack.region #AWS CDKカスタムリソース内でAWS SDK呼出を実行してパラメータを作成・更新する result = cr.AwsCustomResource(self, id_name, policy=cr.AwsCustomResourcePolicy.from_sdk_calls( resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE ), on_update=cr.AwsSdkCall( service='SSM', action='putParameter', parameters={ 'Name': parameter_name, 'Value': string_value, 'Description': description, 'Type': 'String', 'Overwrite': True }, region=param_region, physical_resource_id=cr.PhysicalResourceId.of(id_name) ), on_delete=cr.AwsSdkCall( service='SSM', action='deleteParameter', parameters={ 'Name': parameter_name }, region=param_region, physical_resource_id=cr.PhysicalResourceId.of(id_name) ) ) return result #AWS Systems Managerパラメータストアからリージョン名、パラメータ名を指定してパラメータを取得する def asm_get_secret_string(self, id_name, region, secret_name): stack = core.Stack.of(self); param_region = region #リージョン指定がない場合は呼出元スタックのリージョンを使用する if not param_region: param_region = stack.region #リソースポリシーのリソースに「cr.AwsCustomResourcePolicy.ANY_RESOURCE」を指定するとactionに対してすべてのリソースのアクセス許可をする #AWS CDKカスタムリソース内でAWS SDK呼出を実行してシークレットを取得する result_params = cr.AwsCustomResource(self, id_name, policy=cr.AwsCustomResourcePolicy.from_sdk_calls( resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE ), on_update=cr.AwsSdkCall( service='SecretsManager', action='getSecretValue', parameters={ 'SecretId': secret_name }, region=param_region, physical_resource_id=cr.PhysicalResourceId.of(id_name) ) ) #レスポンス内の要素を指定して取得する:{"SecretString":"<取得するパラメータ>"} result = result_params.get_response_field('SecretString') return result #AWS Systems Managerパラメータストアにリージョン名、パラメータ名を指定してパラメータを作成・更新・削除する def asm_put_secret_string(self, id_name, region, secret_name, secret_string, description): stack = core.Stack.of(self); param_region = region #リージョン指定がない場合は呼出元スタックのリージョンを使用する if not param_region: param_region = stack.region #AWS CDKカスタムリソース内でAWS SDK呼出を実行してパラメータを更新する result = cr.AwsCustomResource(self, id_name, policy=cr.AwsCustomResourcePolicy.from_sdk_calls( resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE ), on_create=cr.AwsSdkCall( service='SecretsManager', action='createSecret', parameters={ 'Name': secret_name, 'SecretString': secret_string, 'Description': description }, region=param_region, physical_resource_id=cr.PhysicalResourceId.of(id_name) ), on_update=cr.AwsSdkCall( service='SecretsManager', action='updateSecret', parameters={ 'SecretId': secret_name, 'SecretString': secret_string, 'Description': description }, region=param_region, physical_resource_id=cr.PhysicalResourceId.of(id_name) ), on_delete=cr.AwsSdkCall( service='SecretsManager', action='deleteSecret', parameters={ 'SecretId': secret_name, 'RecoveryWindowInDays': 7 }, region=param_region, physical_resource_id=cr.PhysicalResourceId.of(id_name) ) ) return result
■x_region_param.pyの呼び出し例
from aws_cdk import core as cdk from x_region_param import XRegionParam from aws_cdk import ( core ) class SampleCdkStack(cdk.Stack): def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) #AWS Systems Managerパラメータストアのパラメータの作成・更新 resut_put_param = XRegionParam(self, 'SsmPut', region='us-east-1', service='SSM', action='PUT', key='parameter_name', val='Nobody', description='SSM Param for Sample' ) #AWS Secrets Managerシークレットの作成・更新 resut_put_secret = XRegionParam(self, 'AsmPut', region='us-east-1', service='ASM', action='PUT', key='Sample/Secret/Value', val='{"Password":"Nobody"}', description='ASM Param for Sample' ) #AWS Systems Managerパラメータストアのパラメータの取得 resut_get_param = XRegionParam(self, 'SsmPut', region='us-east-1', service='SSM', action='GET', key='parameter_name', val='', description='' ) ssm_parameter = resut_get_param.get_result()['result'] #AWS Secrets Managerシークレットの取得 resut_get_secret = XRegionParam(self, 'AsmPut', region='us-east-1', service='ASM', action='GET', key='Sample/Secret/Value', val='', description='' ) asm_parameter = resut_get_secret.get_result()['result']
AWS CDKのカスタムリソースの特徴
AWS CDKのカスタムリソースはAWS CloudFormationのAWS Lambdaカスタムリソースのように一から開発する必要なく、AWS CDKのコードをCloudFormationに変換してデプロイするコマンドであるaws cdk deploy
を実行した際に自動的にAWS SDK呼び出しの内容を実行するAWS Lambda関数を作成してくれます。
ただ、開発が少なく済む一方で柔軟な細かい内部処理やエラーハンドリングができないことに配慮が必要です。
AWS CDKのカスタムリソースはaws cdk deploy
を実行すると各スタック毎にAWS Lambda関数が作成され、そのAWS Lambda関数内でAwsCustomResource
に記述した内容が実行されます。そのため、スタック毎に用意されたAWS Lambda関数にスタックで使用する各カスタムリソースのアクションやリソースポリシーのアクセス権限が追加されていくことを知っておいたほうが良いでしょう。
これを概念図にすると次のようになります。
例えば、あるスタックで前述のConstructを継承したクラスのssm_get_parameter
でパラメータ取得、asm_put_secret_string
でシークレット作成・更新をする場合は、「AwsCustomResourcePolicy.ANY_RESOURCE」のリソースポリシーの指定によってSSMのgetParameter
アクションに対するすべてのリソースへのアクセス権限、SecretsManagerのupdateSecret
・createSecret
アクションに対するすべてのリソースへのアクセス権限が、そのスタックのために用意されたカスタムリソースのAWS Lambda関数に付与されます。
「AwsCustomResourcePolicy.ANY_RESOURCE」を使用せずにリソース毎に細かく権限を指定する場合は、その内容がカスタムリソースのAWS Lambda関数に適用されているIAMロールのインラインポリシーへリソース毎に追加されていくため、扱うリソース数が多数の場合はその点も知っておいたほうがよいでしょう。
aws cdk deploy
によってAWS CloudFormationのCreate、Update、Deleteのイベントが発生すると、AWS CDKのカスタムリソースではそれに対応してon_create
、on_update
、on_delete
が実行されます(on_create
の指定がない場合はCreateイベント発生時にon_update
が実行される)。
前述のConstructを継承したクラスでは各処理に対応するようにAWS CDKカスタムリソースが実行されるように記載しています。
AWS CDKのカスタムリソースの詳細な仕様については次のドキュメントで確認できます。
class AwsCustomResource (construct) · AWS CDK
また、AwsCustomResource内で使用するAwsSdkCallのサービス、アクション、パラメータの記述の仕様は次のAWS SDK for JavaScriptのドキュメントで確認できます。
AWS SDK for JavaScript
参考:
What is the AWS CDK? - AWS Cloud Development Kit (AWS CDK) v1
What is the AWS CDK? - AWS Cloud Development Kit (AWS CDK) v2
Tech Blog with related articles referenced
まとめ
今回はAWS CDKでスタックを複数のリージョンを指定してデプロイし、AWS CDKカスタムリソースのAWS SDK呼び出しでパラメータをリージョン間で受け渡す方法について記載しました。
次回からはこれらの方法を使用して「AWS LambdaカスタムリソースでSSL証明書・基本認証・CloudFrontオリジンフェイルオーバーを作成するAWS Cloudformationスタックを別リージョンにデプロイする」で紹介したようなAmazon S3+Amazon CluodFrontの静的ウェブサイトホスティングに別リージョンで作成したACM証明書、基本認証用Lambda@Edge、Amazon CloudFrontオリジンフェイルオーバーを追加する構成をAWS CDKで作成するとどのようになるかについて見ていきたいと思います。