こちらは [CDK Advent Calendar 2021] (https://qiita.com/advent-calendar/2021/aws-cdk) の 19 日目の記事です。
こんにちは。 藤原 [@twingo-b] (https://qiita.com/twingo-b) です。
2021/11/20-21 に開催された JAWS PANKRATION 2021 にて cdk-remote-stackとAWS Edge Networking Servicesを使ったクロスリージョンアプリケーション のセッション発表を行いました。発表時間の都合上概念説明がメインで、実際の CDK コードを交えたお話をもっとしたかったなーと思いましたので、この記事で補足します。
セッション発表の後に 「[こちら] (https://dev.classmethod.jp/articles/try-cdk-remote-stack/)」や「[こちら] (https://dev.classmethod.jp/articles/cdk-remote-stack-webacl-cloudfront/)」でも [cdk-remote-stack] (https://github.com/pahud/cdk-remote-stack) の詳しい解説が行われていますので、ぜひご参照ください。
Amazon CloudFront を使用したクロスリージョンアプリケーション
CDK コードのディレクトリ構成はこちらです。
├── README.md
├── bin
│ └── cross-region-app.ts
├── cdk.json
├── jest.config.js
├── lib
│ ├── canary
│ │ └── nodejs
│ │ └── node_modules
│ │ └── screen-canary.js
│ ├── cloudfront-stack.ts
│ ├── origin-stack.ts
│ ├── parameter-stack.ts
│ └── synthetics-stack.ts
├── package-lock.json
├── package.json
└── tsconfig.json
CDK で定義した Stack は以下のように分けました。
- AWS Systems Manager Parameter Store 関連の
ParameterStack
: ap-northeast-3 - Application Load Balancer, Amazon ECS on AWS Fargate 関連の
OsakaOriginStack
: ap-northeast-3,TokyoOriginStack
: ap-northeast-1 - Amazon CloudFront, AWS WAF 関連の
CloudFrontStack
: us-east-1 - Amazon CloudWatch Synthetics 関連の
SyntheticsStack
: us-west-2
CDK コードで実現していることを改めて説明します。
Stack 間の依存関係を定義
OsakaOriginStack
, TokyoOriginStack
で出力した ALB Domain Name を CloudFrontStack
で参照
CloudFrontStack
で出力した CloudFront Distribution Domain Name を SyntheticsStack
で参照
ParameterStack
で事前共有キーを定義し CloudFront origin custom header で追加した x-pre-shared-key
を ALB の custom rule で評価し、 ALB へのダイレクトアクセスを拒否する
bin/cross-region-app.ts
上記を定義したコード全体像を下に記載しています。 v2.2.0 で動作確認しています。
クロスリージョンの Stack 間の依存関係、パラメータ参照をシンプルなコードで実現できていることがわかります。
※ Stack 内での RemoteOutputs
, RemoteParameters
利用方法は発表内で解説していますので [こちら] (https://speakerdeck.com/twingob/using-cdk-remote-stack-and-aws-edge-networking-services-for-cross-regional-applications) をご確認ください。
-
RemoteOutputs
をCloudFrontStack
,SyntheticsStack
で利用しますので、引数で依存する Stack を参照しています。-
CfnOutput
は Stack 内で定義するのではなく、エントリポイント側で定義したほうが全体の見通しが良いですね。
-
-
RemoteParameters
をOsakaOriginStack
,TokyoOriginStack
,CloudFrontStack
で利用しますが、こちらはParameterStack
で定義したリージョンとパスを引数として渡しています。
#!/usr/bin/env node
import 'source-map-support/register';
import {
App,
CfnOutput,
} from 'aws-cdk-lib';
import { ParameterStack } from '../lib/parameter-stack';
import { OriginStack } from '../lib/origin-stack';
import { CloudFrontStack } from '../lib/cloudfront-stack';
import { SyntheticsStack } from '../lib/synthetics-stack';
const app = new App();
const appName = app.node.tryGetContext('appName');
const parameterRegion = 'ap-northeast-3';
const parameterPath = `/${appName}`;
const hostedZoneId = process.env.CDK_HOSTED_ZONE_ID ?? "";
const hostedZoneName = process.env.CDK_HOSTED_ZONE_NAME ?? "";
const cpu = 512;
const memory = 1024;
// Osaka Parameter resources
const parameterStack = new ParameterStack(app, 'ParameterStack', {
env: {region: parameterRegion},
appName: appName,
parameterPath: parameterPath,
});
// Osaka Origin resources
const osakaOriginStack = new OriginStack(app, 'OsakaOriginStack', {
env: {region: 'ap-northeast-3'},
appName: appName,
parameterRegion: parameterRegion,
parameterPath: parameterPath,
hostedZoneId: hostedZoneId,
hostedZoneName: hostedZoneName,
cpu: cpu,
memory: memory
});
new CfnOutput(osakaOriginStack, "AlbDomainName", {
value: osakaOriginStack.domainName
});
osakaOriginStack.addDependency(parameterStack);
// Tokyo Origin resources
const tokyoOriginStack = new OriginStack(app, 'TokyoOriginStack', {
env: {region: 'ap-northeast-1'},
appName: appName,
parameterRegion: parameterRegion,
parameterPath: parameterPath,
hostedZoneId: hostedZoneId,
hostedZoneName: hostedZoneName,
cpu: cpu,
memory: memory
});
new CfnOutput(tokyoOriginStack, "AlbDomainName", {
value: tokyoOriginStack.domainName
});
tokyoOriginStack.addDependency(parameterStack);
// N.Virginia CloudFront related resources
const cloudFrontStack = new CloudFrontStack(app, 'CloudFrontStack', {
env: {region: 'us-east-1'},
appName: appName,
parameterRegion: parameterRegion,
parameterPath: parameterPath,
hostedZoneId: hostedZoneId,
hostedZoneName: hostedZoneName,
primaryOriginStack: osakaOriginStack,
fallbackOriginStack: tokyoOriginStack,
});
new CfnOutput(cloudFrontStack, 'CloudFrontDistributionDomainName', {
value: cloudFrontStack.domainName
});
cloudFrontStack.addDependency(parameterStack);
cloudFrontStack.addDependency(osakaOriginStack);
cloudFrontStack.addDependency(tokyoOriginStack);
// Oregon Synthetics resources
const syntheticsStack = new SyntheticsStack(app, 'SyntheticsStack', {
env: {region: 'us-west-2'},
appName: appName,
cloudFrontStack: cloudFrontStack,
});
syntheticsStack.addDependency(cloudFrontStack);
まとめ
私が cdk-remote-stack を初めて利用したときに感じたシンプルさと便利さを、皆さんにも感じていただければ幸いです。
そして、クロスリージョンアプリを作るときは、大阪リージョンをプライマリリージョンにできることを思い出してくださいね!