NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

AWS CDKで別リージョンにスタックをデプロイしてパラメータをリージョン間で受け渡す方法 -AWS CDKカスタムリソースの実装例

小西秀和です。
これまで、次の記事のようなAWSの静的ウェブサイトホスティングをテーマにAWS CloudformationやAWS Amplifyの使用例を紹介してきました。

今回からは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

  1. #!/usr/bin/env python3
  2. import os
  3. from aws_cdk import core as cdk
  4. from aws_cdk import core
  5. from s3cf_acm_edge_s3sec.certificate_stack import CertificateStack
  6. from s3cf_acm_edge_s3sec.lambda_edge_stack import LambdaEdgeStack
  7. from s3cf_acm_edge_s3sec.s3secondary_stack import S3secondaryStack
  8. from s3cf_acm_edge_s3sec.s3cloudfront_stack import S3CloudfrontStack
  9. app = core.App()
  10. certificate_stk = CertificateStack(app, "CdkS3CfAllCertificateStack", env=core.Environment(region='us-east-1'))
  11. lambda_edge_stk = LambdaEdgeStack(app, "CdkS3CfAllLambdaEdgeStack", env=core.Environment(region='us-east-1'))
  12. s3secondary_stk = S3secondaryStack(app, "CdkS3CfAllS3secondaryStack", env=core.Environment(region='us-east-1'))
  13. s3cloudfront_stk = S3CloudfrontStack(app, "CdkS3CfAllS3CloudfrontStack", env=core.Environment(region='ap-northeast-1'))
  14. s3cloudfront_stk.add_dependency(certificate_stk)
  15. s3cloudfront_stk.add_dependency(s3secondary_stk)
  16. s3cloudfront_stk.add_dependency(lambda_edge_stk)
  17. 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

  1. from aws_cdk import (
  2. core,
  3. custom_resources as cr
  4. )
  5. class XRegionParam(core.Construct):
  6. def __init__(self, scope: core.Construct, id: str, region, service, action, key, val, description, **kwargs):
  7. super().__init__(scope, id, **kwargs)
  8. stack = core.Stack.of(self);
  9. param_region = region
  10. #リージョン指定がない場合は呼出元スタックのリージョンを使用する
  11. if not param_region:
  12. param_region = stack.region
  13. param_service = str(service).upper()
  14. param_action = str(action).upper()
  15. if param_service == 'SSM':
  16. if param_action == 'GET':
  17. act = 'SsmGet'
  18. res = self.ssm_get_parameter(action + id, param_region, key)
  19. elif param_action == 'PUT':
  20. act = 'SsmPut'
  21. res = self.ssm_put_parameter(action + id, param_region, key, val, description)
  22. else:
  23. act = None
  24. res = None
  25. elif param_service == 'ASM' or param_service == 'SECRETSMANAGER':
  26. if param_action == 'GET':
  27. act = 'AsmGet'
  28. res = self.asm_get_secret_string(action + id, param_region, key)
  29. elif param_action == 'PUT':
  30. act = 'AsmPut'
  31. res = self.asm_put_secret_string(action + id, param_region, key, val, description)
  32. else:
  33. act = None
  34. res = None
  35. else:
  36. act = None
  37. res = None
  38. self.action = act
  39. self.result = res
  40. def get_result(self):
  41. return {'action': self.action, 'result': self.result}
  42. #AWS Systems Managerパラメータストアからリージョン名、パラメータ名を指定してパラメータを取得する
  43. def ssm_get_parameter(self, id_name, region, parameter_name):
  44. stack = core.Stack.of(self);
  45. param_region = region
  46. #リージョン指定がない場合は呼出元スタックのリージョンを使用する
  47. if not param_region:
  48. param_region = stack.region
  49. #AWS CDKカスタムリソース内でAWS SDK呼出を実行してパラメータを取得する
  50. result_params = cr.AwsCustomResource(self, id_name,
  51. policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
  52. resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE
  53. ),
  54. on_update=cr.AwsSdkCall(
  55. service='SSM',
  56. action='getParameter',
  57. parameters={
  58. 'Name': parameter_name
  59. },
  60. region=param_region,
  61. physical_resource_id=cr.PhysicalResourceId.of(id_name)
  62. )
  63. )
  64. #レスポンス内の要素を指定して取得する:{"Parameter":{"Value":"<取得するパラメータ>"}}
  65. result = result_params.get_response_field('Parameter.Value')
  66. return result
  67. #AWS Systems Managerパラメータストアにリージョン名、パラメータ名を指定してパラメータを作成・更新・削除する
  68. def ssm_put_parameter(self, id_name, region, parameter_name, string_value, description):
  69. stack = core.Stack.of(self);
  70. param_region = region
  71. #リージョン指定がない場合は呼出元スタックのリージョンを使用する
  72. if not param_region:
  73. param_region = stack.region
  74. #AWS CDKカスタムリソース内でAWS SDK呼出を実行してパラメータを作成・更新する
  75. result = cr.AwsCustomResource(self, id_name,
  76. policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
  77. resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE
  78. ),
  79. on_update=cr.AwsSdkCall(
  80. service='SSM',
  81. action='putParameter',
  82. parameters={
  83. 'Name': parameter_name,
  84. 'Value': string_value,
  85. 'Description': description,
  86. 'Type': 'String',
  87. 'Overwrite': True
  88. },
  89. region=param_region,
  90. physical_resource_id=cr.PhysicalResourceId.of(id_name)
  91. ),
  92. on_delete=cr.AwsSdkCall(
  93. service='SSM',
  94. action='deleteParameter',
  95. parameters={
  96. 'Name': parameter_name
  97. },
  98. region=param_region,
  99. physical_resource_id=cr.PhysicalResourceId.of(id_name)
  100. )
  101. )
  102. return result
  103. #AWS Systems Managerパラメータストアからリージョン名、パラメータ名を指定してパラメータを取得する
  104. def asm_get_secret_string(self, id_name, region, secret_name):
  105. stack = core.Stack.of(self);
  106. param_region = region
  107. #リージョン指定がない場合は呼出元スタックのリージョンを使用する
  108. if not param_region:
  109. param_region = stack.region
  110. #リソースポリシーのリソースに「cr.AwsCustomResourcePolicy.ANY_RESOURCE」を指定するとactionに対してすべてのリソースのアクセス許可をする
  111. #AWS CDKカスタムリソース内でAWS SDK呼出を実行してシークレットを取得する
  112. result_params = cr.AwsCustomResource(self, id_name,
  113. policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
  114. resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE
  115. ),
  116. on_update=cr.AwsSdkCall(
  117. service='SecretsManager',
  118. action='getSecretValue',
  119. parameters={
  120. 'SecretId': secret_name
  121. },
  122. region=param_region,
  123. physical_resource_id=cr.PhysicalResourceId.of(id_name)
  124. )
  125. )
  126. #レスポンス内の要素を指定して取得する:{"SecretString":"<取得するパラメータ>"}
  127. result = result_params.get_response_field('SecretString')
  128. return result
  129. #AWS Systems Managerパラメータストアにリージョン名、パラメータ名を指定してパラメータを作成・更新・削除する
  130. def asm_put_secret_string(self, id_name, region, secret_name, secret_string, description):
  131. stack = core.Stack.of(self);
  132. param_region = region
  133. #リージョン指定がない場合は呼出元スタックのリージョンを使用する
  134. if not param_region:
  135. param_region = stack.region
  136. #AWS CDKカスタムリソース内でAWS SDK呼出を実行してパラメータを更新する
  137. result = cr.AwsCustomResource(self, id_name,
  138. policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
  139. resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE
  140. ),
  141. on_create=cr.AwsSdkCall(
  142. service='SecretsManager',
  143. action='createSecret',
  144. parameters={
  145. 'Name': secret_name,
  146. 'SecretString': secret_string,
  147. 'Description': description
  148. },
  149. region=param_region,
  150. physical_resource_id=cr.PhysicalResourceId.of(id_name)
  151. ),
  152. on_update=cr.AwsSdkCall(
  153. service='SecretsManager',
  154. action='updateSecret',
  155. parameters={
  156. 'SecretId': secret_name,
  157. 'SecretString': secret_string,
  158. 'Description': description
  159. },
  160. region=param_region,
  161. physical_resource_id=cr.PhysicalResourceId.of(id_name)
  162. ),
  163. on_delete=cr.AwsSdkCall(
  164. service='SecretsManager',
  165. action='deleteSecret',
  166. parameters={
  167. 'SecretId': secret_name,
  168. 'RecoveryWindowInDays': 7
  169. },
  170. region=param_region,
  171. physical_resource_id=cr.PhysicalResourceId.of(id_name)
  172. )
  173. )
  174. return result

■x_region_param.pyの呼び出し例

  1. from aws_cdk import core as cdk
  2. from x_region_param import XRegionParam
  3. from aws_cdk import (
  4. core
  5. )
  6. class SampleCdkStack(cdk.Stack):
  7. def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None:
  8. super().__init__(scope, construct_id, **kwargs)
  9. #AWS Systems Managerパラメータストアのパラメータの作成・更新
  10. resut_put_param = XRegionParam(self, 'SsmPut',
  11. region='us-east-1',
  12. service='SSM',
  13. action='PUT',
  14. key='parameter_name',
  15. val='Nobody',
  16. description='SSM Param for Sample'
  17. )
  18. #AWS Secrets Managerシークレットの作成・更新
  19. resut_put_secret = XRegionParam(self, 'AsmPut',
  20. region='us-east-1',
  21. service='ASM',
  22. action='PUT',
  23. key='Sample/Secret/Value',
  24. val='{"Password":"Nobody"}',
  25. description='ASM Param for Sample'
  26. )
  27. #AWS Systems Managerパラメータストアのパラメータの取得
  28. resut_get_param = XRegionParam(self, 'SsmPut',
  29. region='us-east-1',
  30. service='SSM',
  31. action='GET',
  32. key='parameter_name',
  33. val='',
  34. description=''
  35. )
  36. ssm_parameter = resut_get_param.get_result()['result']
  37. #AWS Secrets Managerシークレットの取得
  38. resut_get_secret = XRegionParam(self, 'AsmPut',
  39. region='us-east-1',
  40. service='ASM',
  41. action='GET',
  42. key='Sample/Secret/Value',
  43. val='',
  44. description=''
  45. )
  46. 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関数にスタックで使用する各カスタムリソースのアクションやリソースポリシーのアクセス権限が追加されていくことを知っておいたほうが良いでしょう。
これを概念図にすると次のようになります。

AWS CDKカスタムリソース用AWS Lambda関数とスタックの関係
AWS CDKカスタムリソース用AWS Lambda関数とスタックの関係

例えば、あるスタックで前述のConstructを継承したクラスのssm_get_parameterでパラメータ取得、asm_put_secret_stringでシークレット作成・更新をする場合は、「AwsCustomResourcePolicy.ANY_RESOURCE」のリソースポリシーの指定によってSSMのgetParameterアクションに対するすべてのリソースへのアクセス権限、SecretsManagerのupdateSecretcreateSecretアクションに対するすべてのリソースへのアクセス権限が、そのスタックのために用意されたカスタムリソースのAWS Lambda関数に付与されます。
「AwsCustomResourcePolicy.ANY_RESOURCE」を使用せずにリソース毎に細かく権限を指定する場合は、その内容がカスタムリソースのAWS Lambda関数に適用されているIAMロールのインラインポリシーへリソース毎に追加されていくため、扱うリソース数が多数の場合はその点も知っておいたほうがよいでしょう。

aws cdk deployによってAWS CloudFormationのCreate、Update、Deleteのイベントが発生すると、AWS CDKのカスタムリソースではそれに対応してon_createon_updateon_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で作成するとどのようになるかについて見ていきたいと思います。

Written by Hidekazu Konishi
Hidekazu Konishi (小西秀和), a Japan AWS Top Engineer and a Japan AWS All Certifications Engineer

執筆者小西秀和

Japan AWS Top Engineer, Japan AWS All Certifications Engineer(AWS認定全冠)として、知識と実践的な経験を活かし、AWSの活用に取り組んでいます。
NRIネットコムBlog: 小西 秀和: 記事一覧
Amazon.co.jp: 小西 秀和: books, biography, latest update
Personal Tech Blog | [B! Bookmark]