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

注目のタグ

    AWS CDKで別リージョンにレプリケーション用S3バケットを作成するスタックをデプロイしてAmazon CloudFrontオリジンフェイルオーバーを設定する

    小西秀和です。
    前回の記事、「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オリジンフェイルオーバーを設定する

    クロスリージョンパラメータによるS3レプリケーションとCloudFrontオリジンフェイルオーバーの設定例
    クロスリージョンパラメータによる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に設定する例を紹介しようと考えています。

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

    執筆者小西秀和

    Japan AWS Top Engineer / Japan All AWS Certifications Engineer(AWS認定全冠)として、知識と実践的な経験を活かし、AWSの活用に取り組んでいます。
    Personal Tech Blog | Web Tools Collection | 
    小西 秀和 - Amazon著者ページ | [B! Bookmark] |