小西秀和です。
前回の記事、「AWS CDKで別リージョンにAWS Certificate Manager(ACM)証明書スタックをデプロイしてAmazon CloudFrontに設定する」では次の記事で紹介したリージョン間でパラメータを送受信する方法を使ってACM証明書をCloudFrontに設定する方法を紹介しました。
今回はその記事の続編として、前回記事で作成したAWS CDKカスタムリソースを使用して、リージョンの異なる2つのS3バケットを作成し、Amazon CloudFrontオリジンフェイルオーバーを設定する方法を紹介します。
パラメータをクロスリージョンで扱うAWS CDKカスタムリソースについては元記事を参照してください。
※本記事および当執筆者のその他の記事で掲載されているソースコードは自主研究活動の一貫として作成したものであり、動作を保証するものではありません。使用する場合は自己責任でお願い致します。また、予告なく修正することもありますのでご了承ください。
本記事で試す内容の概要図
今回は次の内容をAWS Cloud Development Kit(AWS CDK)とAWS CDKカスタムリソースを中心に試しています。
- us-east-1リージョンでクロスリージョンレプリケーション用のAmazon S3バケットを作成するスタックを実行し、S3バケットARNなどをap-northeast-1リージョンのAWS Systems Manager(SSM)パラメータストアに保存する
- ap-northeast-1リージョンでAmazon CloudFront+Amazon S3のホスティングを構成するスタックを実行し、SSMパラメータストアからS3バケットARNなどを取得して、S3バケットのクロスリージョンレプリケーションとCloudFrontオリジンフェイルオーバーを設定する
AWS CDKプロジェクトの階層構造とファイル概要
AWS CDKのプロジェクトの構造は次のようになります。
主に実装した部分にはコメントを追記しています。
[ho2k_com@ho2k-com s3cf-s3sec]# tree ~不要部分は省略しています~ ├── README.md ├── app.py # 各スタックのリージョンや依存関係などを指定する ├── cdk.json # Contextに静的パラメータを定義する ├── cdk.out ├── requirements.txt ├── s3cf_s3sec │ ├── __init__.py │ ├── s3cloudfront_stack.py # ap-northeast-1リージョンでCloudFront+S3ホスティングを構成するスタック │ ├── s3secondary_stack.py # us-east-1リージョンでレプリケーション用S3バケットを作成するスタック │ └── x_region_param.py # クロスリージョンのパラーメータ受け渡しをするConstruct継承クラス ├── setup.py └── source.bat
各スタック共通設定
各スタックのリージョンや依存関係などを指定するapp.pyは次のようになります。
add_dependencyでACM証明書発行スタックを実行してから、CloudFront+S3ホスティングのスタックを実行する依存関係を指定しています。
■app.py
#!/usr/bin/env python3 import os from aws_cdk import core as cdk from aws_cdk import core from s3cf_s3sec.s3secondary_stack import S3SecondaryStack from s3cf_s3sec.s3cloudfront_stack import S3CloudfrontStack app = core.App() s3secondary_stk = S3SecondaryStack(app, "CdkS3CfS3SecS3secondaryStack", env=core.Environment(region='us-east-1')) s3cloudfront_stk = S3CloudfrontStack(app, "CdkS3CfS3SecS3CloudfrontStack", env=core.Environment(region='ap-northeast-1')) s3cloudfront_stk.add_dependency(s3secondary_stk) app.synth()
cdk.jsonではContextに次の静的パラメータを追記してスタック等で呼び出して使用します。
"PREFIX": "<リージョン間で受け渡すパラメータ名の先頭にプロジェクト固有のプレフィックスを付けるために使用>",
■cdk.json
{ "app": "python3 app.py", "context": { "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, "@aws-cdk/core:enableStackNameDuplicates": "true", "aws-cdk:enableDiffNoFail": "true", "@aws-cdk/core:stackRelativeExports": "true", "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, "@aws-cdk/aws-kms:defaultKeyPolicies": true, "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, "@aws-cdk/aws-lambda:recognizeVersionProps": true, "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, "PREFIX": "s3cf_s3sec_" } }
クロスリージョンレプリケーション用S3バケットを作成するスタック
us-east-1リージョンにレプリケーション用S3バケットを作成するスタックの内容です。
処理のポイントとなる箇所についてコメントを追記しておきます。
■s3secondary_stack.py
from aws_cdk import core as cdk from x_region_param import XRegionParam from aws_cdk import ( core, aws_s3 as s3, aws_iam as iam, aws_cloudfront as cf ) class S3SecondaryStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) #cdk.jsonのContextから静的パラメータを取得 PREFIX = self.node.try_get_context("PREFIX") #CloudFrontにS3バケットを設定するためのOAIを作成 oai = cf.OriginAccessIdentity(self, 'CfOai'); #レプリケーション用S3バケットを作成する bucket = s3.Bucket(self, 'BucketSecondary', access_control=s3.BucketAccessControl.PUBLIC_READ, website_index_document='index.html', versioned=True ) bucket.grant_public_access() #S3へのアクセス権限をOAIに限定するようにS3バケットポリシーを設定 bucket_policy = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['s3:GetObject'], principals=[ iam.CanonicalUserPrincipal( oai.cloud_front_origin_access_identity_s3_canonical_user_id ) ], resources=[bucket.bucket_arn + '/*'] ) bucket.add_to_resource_policy(bucket_policy) #リージョン間値送受信クラスでap-northeast-1リージョンにSSMパラメータ(OAI S3 Canonical User ID)を保存する resut_put_canonical_user_id = XRegionParam(self, 'CfOaiCanonicalUserIdParameter', region='ap-northeast-1', service='SSM', action='PUT', key=PREFIX + 'canonical-user-id', val=oai.cloud_front_origin_access_identity_s3_canonical_user_id, description='' ) #リージョン間値送受信クラスでap-northeast-1リージョンにSSMパラメータ(OAI名)を保存する resut_put_oai_name = XRegionParam(self, 'CfOaiParameter', region='ap-northeast-1', service='SSM', action='PUT', key=PREFIX + 'oai-name', val=oai.origin_access_identity_name, description='' ) #リージョン間値送受信クラスでap-northeast-1リージョンにSSMパラメータ(S3バケットARN)を保存する resut_put_bucket_arn = XRegionParam(self, 'BucketSecondaryArnParameter', region='ap-northeast-1', service='SSM', action='PUT', key=PREFIX + 'bucket-secondary-arn', val=bucket.bucket_arn, description='' )
Amazon CloudFront+Amazon S3を作成し、Amazon CloudFrontにCloudFrontオリジンフェイルオーバーを設定するスタック
ap-northeast-1にCloudFront+S3ホスティングを構成するスタックの内容です。
本記事執筆時点でAWS CDKでS3バケットのレプリケーション設定をするには、ローレベルのL1(Level 1) ConstructであるCfnBucketクラスを使用する必要があります。
その他、処理のポイントとなる箇所についてコメントを追記しておきます。
■s3cloudfront_stack.py
from aws_cdk import core as cdk from x_region_param import XRegionParam from aws_cdk.aws_s3 import CfnBucket from aws_cdk import ( core, aws_s3 as s3, aws_cloudfront as cf, aws_iam as iam ) class S3CloudfrontStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) stack = core.Stack.of(self); #スタックの環境設定からAWSアカウントIDを取得 ACCOUNT_ID = stack.account #cdk.jsonのContextから静的パラメータを取得 PREFIX = self.node.try_get_context("PREFIX") #ホスティング用S3バケットを作成する bucket = s3.Bucket(self, 'BucketPrimary', access_control=s3.BucketAccessControl.PUBLIC_READ, website_index_document='index.html', versioned=True ) bucket.grant_public_access() #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(OAI名)を取得する resut_get_oai_name = XRegionParam(self, 'GetOai', region='ap-northeast-1', service='SSM', action='GET', key=PREFIX + 'oai-name', val='', description='' ) oai_name = resut_get_oai_name.get_result()['result'] #OAI名からOAIを取得する oai = cf.OriginAccessIdentity.from_origin_access_identity_name(self, 'CfOai', origin_access_identity_name=oai_name ) #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(OAI S3 Canonical User ID)を取得する resut_get_canonical_user_id = XRegionParam(self, 'GetCanonicalUserId', region='ap-northeast-1', service='SSM', action='GET', key=PREFIX + 'canonical-user-id', val='', description='' ) cloud_front_origin_access_identity_s3_canonical_user_id = resut_get_canonical_user_id.get_result()['result'] #S3へのアクセス権限をOAIに限定するようにS3バケットポリシーを設定 bucket_policy = iam.PolicyStatement( effect=iam.Effect.ALLOW, actions=['s3:GetObject'], principals=[ iam.CanonicalUserPrincipal( cloud_front_origin_access_identity_s3_canonical_user_id ) ], resources=[bucket.bucket_arn + '/*'] ); bucket.add_to_resource_policy(bucket_policy); #リージョン間値送受信クラスでap-northeast-1リージョンにあるSSMパラメータ(レプリケーション用S3バケットのARN)を取得する resut_get_bucket_arn = XRegionParam(self, 'GetBucketSecondaryArn', region='ap-northeast-1', service='SSM', action='GET', key=PREFIX + 'bucket-secondary-arn', val='', description='' ) secondary_bucket_arn = resut_get_bucket_arn.get_result()['result'] #S3バケットARNとリージョンからレプリケーション用S3バケットを取得する secondary_bucket = s3.Bucket.from_bucket_attributes(self, 'BucketSecondary', bucket_arn=secondary_bucket_arn, region='us-east-1' ) #S3レプリケーション設定用のIAMロールを作成する replication_role = iam.Role(self, "ReplicationRole", assumed_by=iam.ServicePrincipal("s3.amazonaws.com"), path="/service-role/", ) #S3レプリケーション設定用のIAMロールにポリシーを追加していく replication_role.add_to_policy( iam.PolicyStatement( resources=[bucket.bucket_arn], actions=["s3:GetReplicationConfiguration", "s3:ListBucket"] ) ) replication_role.add_to_policy( iam.PolicyStatement( resources=[bucket.arn_for_objects("*")], actions=[ "s3:GetObjectVersion", "s3:GetObjectVersionAcl", "s3:GetObjectVersionForReplication", "s3:GetObjectLegalHold", "s3:GetObjectVersionTagging", "s3:GetObjectRetention" ], ) ) replication_role.add_to_policy( iam.PolicyStatement( resources=[secondary_bucket.arn_for_objects("*")], actions=[ "s3:ReplicateObject", "s3:ReplicateDelete", "s3:ReplicateTags", "s3:GetObjectVersionTagging", "s3:ObjectOwnerOverrideToBucketOwner" ] ) ) #L1 ConstructのCfnBucketを使用してS3のクロスリージョンレプリケーション設定をする bucket.node.default_child.replication_configuration = CfnBucket.ReplicationConfigurationProperty( role=replication_role.role_arn, rules=[ CfnBucket.ReplicationRuleProperty( destination=CfnBucket.ReplicationDestinationProperty( bucket=secondary_bucket.bucket_arn, account=ACCOUNT_ID ), status="Enabled" ) ] ) #CloudFrontのオリジングループを2つのS3バケットで構成し、CloudFrontオリジンフェイルオーバーを設定する cf_behavior = cf.Behavior(is_default_behavior=True) distribution = cf.CloudFrontWebDistribution(self, 'CloudFrontWebDistribution', origin_configs=[ cf.SourceConfiguration( behaviors=[cf_behavior], s3_origin_source=cf.S3OriginConfig( s3_bucket_source=bucket, origin_access_identity=oai ), failover_s3_origin_source=cf.S3OriginConfig( s3_bucket_source=secondary_bucket, origin_access_identity=oai ) ) ] )
AWS CDKカスタムリソースを使用するクラス
次の記事で紹介したAWS CDKカスタムリソースでクロスリージョンのパラメータ受け渡しをするConstructを継承したクラス(x_region_param.py)を各スタックと同じ階層に配置して使用しています。
■x_region_param.py
参考:
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 CDKカスタムリソースでリージョン間のパラメータ受け渡しをするConstruct継承クラスを使用して、リージョンの異なる2つのS3バケットを作成し、Amazon CloudFrontオリジンフェイルオーバーを設定する例を紹介しました。
次回は同様にリージョン間値送受信をするConstruct継承クラスを使用して、us-east-1リージョンに基本認証用Lambda@Edgeを作成し、Amazon CloudFrontに設定する例を紹介しようと考えています。