AwsCustomResourceを使ってAWS CDKでのデプロイ時に別リージョンの値を参照する
はじめに
AWS CDKを触っていて、「ap-northeast-1からus-east-1にあるリソースのARNや値を取得したいな」と思うことはありませんか? 言い換えるならば「AWS CDKの利用時に2つ以上のリージョンにデプロイが必要で、リージョン間の値の相互参照をしたい」というような状況です。
通常、AWS CDKは1回のデプロイでは単一のリージョンへしかできず、デプロイ時に別リージョンの値を取得・更新はできないです。
具体的なユースケースでパッと思いつくのは、Lambda@Edgeをus-east-1に設定した上で、
CloudFrontをap-northeast-1にデプロイするときなどです。
(※:一応、この場合はCDKのaws_cloudfront.experimentalを利用すると、 1回のデプロイでus-east-1と別の1リージョンに同時にデプロイできはします)。
この「AWS CDKの利用時に2つ以上のリージョンにデプロイが必要で、リージョン間の値の相互参照をしたい」という時に有効な方法があります。
それは、AwsCustomResourceを利用する方法です。
やりたいこと・この記事の対象読者・利用時のイメージ図
やりたいこと
やりたいことは、「AWS CDKの利用時に2つ以上のリージョンにデプロイが必要で、リージョン間の値の相互参照をすること」です。
対象読者
以下の2パターンくらいを実行したい人におすすめです。
- 上記の「やりたいこと」をしたい人
- AWS CDKを触ってる人
利用時のイメージ図
「Lambda@Edgeをus-east-1に設定した上で、 CloudFrontをap-northeast-1にデプロイする」時のユースケースで考えてみます。
CloudFrontへ設定するLambda@Edgeが別リージョンにある場合、ARNなどの値は参照できません。そのため、ap-northeast-1のStackに直接パラメータを付与したりなどの処理が必要です。

AwsCustomResourceを利用するとSSMを介してap-northeast-1から擬似的にus-east-1の値を参照できます。

この図の場合、デプロイは2回行っています。
-
us-east-1のデプロイ-
AWS Lambda・Lambda@Edgeをデプロイ -
AwsCustomResourceが動いて、ap-northeast-1のSSMにパラメータを設定
-
-
ap-northeast-1のデプロイ-
ap-northeast-1のSSMからパラメータを読み取る -
Amazon CloudFrontをデプロイ
-
2回のデプロイのうち、us-east-1へデプロイするときにAwsCustomResourceを動かしています。
これによって、us-east-1にあるARNやその他の任意の値をap-northeast-1から読み込めるようになります。
コード
コードは以下の3つの内容を載せます。
この3つの内容で「やりたいこと」が実現できます。
-
AwsCustomResourceを利用したクラスの作成 -
us-east-1にあるStackでの利用方法 -
ap-northeast-1にあるStackでの参照方法
1. AwsCustomResourceを利用したクラスの作成
このクラスでは、SSMのパラメータを作成・更新する処理を担っています。
クラスとして作成しなくても良いのですが、見通しやすくするために作成しています。
コードの内容はここを参考にコードをTypeScriptに変更してあります。
import {
Stack,
custom_resources,
} from 'aws-cdk-lib';
import {Construct} from 'constructs';
export interface IXRegionParam {
region?: string
}
export interface IPutSsmParam {
parameterName: string
parameterValue: string
parameterDataType: string
idName: string
}
export class XRegionParam extends Construct {
private stack: Stack
public region: string
constructor(scope: Construct, id: string, props: IXRegionParam) {
super(scope, id)
this.stack = Stack.of(this)
this.region = props.region ? props.region : this.stack.region
}
putSsmParameter(props: IPutSsmParam) {
const resultParams = new custom_resources.AwsCustomResource(this, props.idName, {
policy: custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE
}),
onUpdate: {
service: "SSM",
action: "putParameter",
parameters: {
Name: props.parameterName,
Value: props.parameterValue,
DataType: props.parameterDataType,
Type: "String", // "String"は定数値
Overwrite: true
},
region: this.region,
physicalResourceId: custom_resources.PhysicalResourceId.of(props.idName)
}},
)
}
}
2. us-east-1にあるStackでの利用方法
us-east-1からap-northeast-1に値を設定する際に、1で作成したXRegionParamを利用します。
regionのパラメータにリージョンを入れると、そのリージョンのSSMの値を作成・更新します。
import {
Stack,
StackProps,
aws_ssm,
} from 'aws-cdk-lib';
import {XRegionParam} from './XRegionParam';
export class UsEast1Stack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
/** ~~ 前略 ~~ */
new XRegionParam(this, "x-region-param", {
region: "ap-northeast-1"
}).putSsmParameter({
parameterName: `/cdk/EdgeFunctionArn/${id}/[パラメータの名前]`,
parameterValue: "[パラメータの値]",
parameterDataType: "text", // "text"は定数値
idName: `x-region-param-id-${id}`
})
/** ~~ 後略 ~~ */
}
}
上記のコードで、ap-northeast-1のSSMへ値を作成・更新をしています。

3. ap-northeast-1にあるStackでの参照方法
設定したリージョンからは、XRegionParam / AwsCustomResourceではなくCDKのSSMのリソースをそのまま利用できます。
ただ、parameterNameは設定した値と同一にする必要があります。
import {
Stack,
StackProps,
aws_ssm,
} from 'aws-cdk-lib';
export class ApNorthast1Stack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
/** ~~ 前略 ~~ */
const someParam = aws_ssm.StringParameter.fromStringParameterAttributes(this, 'ssmParam', {
parameterName: `/cdk/EdgeFunctionArn/[UsEast1Stackで設定したidの値]/[パラメータの名前]`,
}).stringValue;
/** ~~ 後略 ~~ */
}
}
上記のコードで、SSMから値を取得しています。

デプロイ!
上記の3つをそれぞれ反映した上で、us-east-1 / ap-northeast-1のStackをそれぞれ順番にデプロイすれば、「やりたいこと」はできています。
注意点・その他
1. 他のメソッドをAwsCustomResourceを使って呼ぶ方法
今回はSSMのputParameterという関数を利用して、SSMの値を更新しました。
パラメータはAWS SDK for JavaScriptやboto3などが参考になります。
もちろん他のメソッドを呼び出すことができます。
その際は、上記のドキュメントから呼び出したい関数を、
XRegionParamsの以下のservice / action / parameterの引数に合うように設定すれば
任意の関数を呼び出すことができます。
onUpdate: {
service: "SSM",
+ action: "getParameter",
- action: "putParameter",
parameters: {
Name: props.parameterName,
- Value: props.parameterValue,
- DataType: props.parameterDataType,
- Type: "String", // "String"は定数値
- Overwrite: true
},
ただし、あまり複雑な処理をしてしまうとStackの更新がしづらくなるので、
注意して利用した方がいいです。
2. 値をus-east-1からap-northeast-1に設定する理由
理由は「ap-northeast-1のStackからus-east-1の値が更新されたことを検知できず、継続的に適切な更新ができないから」です。
もちろんgetParameter(参考)を利用すれば、ap-northest-1からus-east-1のSSMへのリソースへアクセス可能です。加えてap-northeast-1のStackからus-east-1のSSMの値も利用できます。
ただ、以下のような状態の時に問題が発生します。
-
us-east-1のStackは更新してデプロイ済み -
us-east-1のStackを更新したので、ap-northeast-1のStackも更新したい -
ap-northeast-1そのもののStackの内容は更新されていない
上記のような場合、CDKでap-northeast-1の内容をデプロイする際には(no change)と見なされて、us-east-1への値の再参照をしてくれず、適切にap-northeast-1のStackを更新できないという状況に陥ります。

もちろん、「ap-northeast-1へのデプロイが一度しかない」というような状況だったら問題ないのですが、 それだったら、SSMを介して値をやりとりする理由もあまりないと思います。
上記の状況を考えて、今回紹介したようなus-east-1からap-northeast-1のSSMの値を更新する、という方法を採っています。
この方法だと、ap-northeast-1のStackの更新はなくても、更新されたSSMの内容を読み取って意図した更新処理ができます。
3. $ cdk destroyでSSMが削除されない
この方法で別リージョンにSSMの値を設定した後で、
Stackを削除した場合($ cdk destory {stack})には
別リージョンのSSMは削除されません。
そのため、手動で削除することを忘れないようにしてください。
おわりに
CDKのAwsCustomResourceを使って、リージョン間の値の受け渡しができました。
この方法は、参考記事にも書かれているように多用すると見通しが悪くなる可能性もあります。使う場所や理由を考えて快適なAWS CDKライフをお送りください。
Discussion