AWS CDK(cdk-remote-stack)でACMとCloudFrontのクロスリージョン参照を実装する

AWS CDKでdk-remote-stackパッケージを使ってACMとCloudFrontをクロスリージョン参照して構築する機会があったのでその手順を共有します。
2022.01.06

AWS CDKでクロスリージョン参照できなくて、悔しい思いをしたことはありませんか? 私はよくあります。

そんなときはcdk-remote-stackパッケージを利用すると解決できます! 詳しくはこちらの弊社ブログをご覧ください。

今回、次の図のようなCloudFront+S3の環境を東京(ap-northeast-1)リージョンで構築したかったのですが、 これにカスタムドメインをつけようと思うと、CloudFrontの仕様上バージニア(us-east-1)リージョンでACMを構築する必要があります。

通常、AWS CDKではクロスリージョン参照ができないため、 東京リージョンで作成するCloudFrontからバージニアリージョンのACMを参照しようとすると、 ARNをベタ書きするとか手作業でパラメータストアに保存して参照する必要がありました。

今回、cdk-remote-stackパッケージを使ってクロスリージョン参照して構築する機会があったのでその手順を共有します。

サンプルコード

今回cdkで構築する環境のサンプルコードは以下に保存しています。細部が気になる方はこちらを御覧ください。

前提条件

AWS CDKはv2を使用しています。

$ cdk --version
2.3.0 (build beaa5b2)

Route53のパブリックホストゾーンを事前に作成しています。

解説

主なファイル構成はこんな感じです。

  • bin/
    • cdk-cloudfront-cross-region-sample.ts
  • lib/
    • acm-for-cloudfront-stack.ts
    • cloudfront-stack.ts
    • remote-output-stack.ts
    • s3/
      • index.html

bin/cdk-cloudfront-cross-region-sample.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AcmForCloudfrontStack } from "../lib/acm-for-cloudfront-stack";
import { RemoteOutputStack } from "../lib/remote-output-stack";
import { CloudfrontStack } from "../lib/cloudfront-stack";

const account = '<<YOUR_AWS_ACCOUNT_NO>>';
const domainName = '<<YOUR_PUBLIC_DOMAIN_NAME>>';
const hostName = 'cf-cross';

const envJP: cdk.Environment = {
    account,
    region: 'ap-northeast-1',
};

const envUS: cdk.Environment = {
    account,
    region: 'us-east-1',
};

const app = new cdk.App();

const acmForCloudfront = new AcmForCloudfrontStack(app, 'AcmForCloudfrontStack', {
    env: envUS,
    domainName,
    hostName,
});

const remoteOutput = new RemoteOutputStack(app, 'RemoteOutput', {
    env: envJP,
    acm: acmForCloudfront,
});

new CloudfrontStack(app, 'CloudFront', {
    env: envJP,
    domainName,
    hostName,
    acmArn: remoteOutput.acmArn,
});

cloudfront.addDependency(acmForCloudfront);

自分で使用するときは、環境に合わせて <<YOUR_AWS_ACCOUNT_NO>><<YOUR_PUBLIC_DOMAIN_NAME>> を書き換えてください。

まず最初に24行目でバージニアリージョンのACMを作成しています。

次に30行目でcdk-remote-stackパッケージを利用して、東京リージョンでACMのARNを読み込める形にしています。

最後に35行目でバージニアリージョンのACMのARNを参照しつつ、東京リージョンでCloudFront+S3を作成しています。

lib/acm-for-cloudfront-stack.ts

import { Construct } from 'constructs'
import {
    Stack,
    StackProps,
    CfnOutput,
} from 'aws-cdk-lib'
import * as route53 from 'aws-cdk-lib/aws-route53';
import * as certificatemanager from 'aws-cdk-lib/aws-certificatemanager';

interface AcmForCloudfrontStackProps extends StackProps {
    hostName: string
    domainName: string
}

export class AcmForCloudfrontStack extends Stack {
    constructor(scope: Construct, id: string, props: AcmForCloudfrontStackProps) {
        super(scope, id, props);

        const hostedZone = route53.HostedZone.fromLookup(
            this,
            'HostedZone',
            {
                domainName: props.domainName,
            }
        );

        const certificate = new certificatemanager.DnsValidatedCertificate(
            this,
            'Certificate',
            {
                domainName: `${props.hostName}.${props.domainName}`,
                hostedZone: hostedZone,
                validation:
                    certificatemanager.CertificateValidation.fromDns(hostedZone),
            }
        );

        new CfnOutput(this, 'AcmArn', {
            value: certificate.certificateArn,
        });
    }
}

cdk-remote-stackでACMのARNを読み込む際に重要なのは、38行目のCfnOutputです。 このCfnOutputで出力した値を AcmArn をキーにして参照します。

lib/remote-output-stack.ts

import { RemoteOutputs } from "cdk-remote-stack";
import {
    App,
    Stack,
    StackProps,
} from 'aws-cdk-lib'
import { AcmForCloudfrontStack } from "./acm-for-cloudfront-stack";

interface RemoteOutputStackProps extends StackProps {
    acm: AcmForCloudfrontStack;
}

export class RemoteOutputStack extends Stack {
    public readonly acmArn: string;

    constructor(scope: App, id: string, props: RemoteOutputStackProps) {
        super(scope, id, props);

        this.addDependency(props.acm)

        const outputs = new RemoteOutputs(this, "Outputs", {
            stack: props.acm,
            alwaysUpdate: false,
        });

        this.acmArn = outputs.get("AcmArn");
    }
}

出力された AcmArn を参照して acmArn 変数に格納しているのが26行目の outputs.get("AcmArn") です。

ここまでできたら後はこの acmArn を参照して、いつもどおりCloudFrontのcdkを書くだけです。

CloudFrontのcdkでの書き方は、こちらのブログをご覧ください。

実際のプログラムはこちらです。

cdk-cloudfront-cross-region-sample/cloudfront-stack.ts at main · rednes/cdk-cloudfront-cross-region-sample

このcdkをデプロイすると、S3を東京リージョンに配置しながらカスタムドメインのCloudFrontが構築できます。

終わりに

今までcdkではクロスリージョンの参照ができずに諦めて手作業でやっていたことが、cdkで完結できるようになりました。 いろいろなパターンで使えそうなので、クロスリージョン参照で困っている方はぜひ試してみてください。