CDK で crossRegionReferences を使ったらちょいと冷や汗かいた話
はじめに
AWS で、複数のスタックを同時に作りたい。
しかも、それぞれのスタック間に依存関係がある。
しかも、異なるリージョンにスタックを作りたい。
でも、ツール一発でデプロイも更新もやってもらいたい。
できれば、リージョン間の依存関係も解決してほしい。
ぜひ、複雑なコード無しで。
つまり、`クロスリージョンスタック`をツール一発でよしなにやってほしい、というものが CDK の 'crossRegionReferences' でできました。
が、使ってみてちょっと冷や汗をかいた(ドキュメントを全部読まない自分が100%悪かった) ので共有いたします
ツールを使わない場合のマルチリージョンスタック構成
上記のように、異なるリージョン間で依存関係にあるリソースがある場合、cdk を使わず CloudFormation だけで作ろうとすると以下のようなデプロイ方法が必要です
SNS Stack をデプロイする
Topic Arn を出力して、メモしておくChatbot Stack をデプロイする
Topic Arn をインプットとして入力
Chatbot に us-east-1 の SNS Topic をサブスクライブさせる
SNS Topic 1個で構築するだけならこれでも良いでしょう。
ですが、依存関係にあるリソースが複数あったり、更新も考えると手間です。
CDK の crossRegionReference 機能
ここで CDK の crossRegionReference 機能を使うと
クロスリージョン間の Topic Arn のやり取りをCDKが実施
(正確には ParameterStore + Lambda )cdk deploy 一発で新規追加/更新が可能
コード追加はほぼ無し
という至れり尽くせりな機能を利用できます。
絵に書くと以下のように連携を取ってくれます
内部的には、Systems Manager Parameter Store が利用されています。スタック間でやり取りが必要なリソース(ここでは SNS Topic Arn)が保存されます。
ただし、考慮する点もあります。
強い参照
まだ experimental
仕様変更があっても吸収できるプロトタイピング案件などで使うと良いと思います。
強い参照に関しては、AWS ドキュメントにきちんと書かれています(ドキュメントを全部読まない自分が100%悪かった)
使ってみる
上記の構成で作ってみると以下のようになりました。
.
├── bin
│ └── chatbot.ts
├── lib
│ ├── chatbot-stack.ts
│ └── sns-stack.ts
lib/sns-stack.ts
ここでは SNS Topic を作っています。
[ポイント]
- `public readonly` な変数に、Topic を保存
クラスの外から SNS Topic を読み取れるように しています
export class SnsStack extends cdk.Stack {
public readonly topic: sns.Topic; <-- ポイント
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.topic = new sns.Topic(this, 'Topic', {
topicName: 'cdk-test-crossregion-topic',
displayName: 'cdk-test-crossregion-topic',
});
}
}
lib/chatbot-stack.ts
ここでは、chatbot を構築し、受け取った `snsTopic` を登録しています。
[ポイント]
- ChatbotStack の prop をカスタム化
snsTopic を受け取るようにしています
interface CustomStackProps extends cdk.StackProps { <-- ポイント
snsTopic: sns.Topic;
}
export class ChatbotStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: CustomStackProps) {
super(scope, id, props);
// Chatbot
const chabot = new chatbot.SlackChannelConfiguration(this, 'Chatbot', {
slackChannelConfigurationName: 'cdk-test-crossregion-chatbot',
slackChannelId: slackChannelId,
slackWorkspaceId: slackWorkspaceId,
logRetention: logs.RetentionDays.ONE_WEEK,
loggingLevel: chatbot.LoggingLevel.ERROR,
});
// add SNS topic to Chatbot
chabot.addNotificationTopic(props.snsTopic);
}
}
bin/chatbot.ts
ここでは、リージョンとアカウントを指定して各スタックのデプロイを行っています
[ポイント]
- 各stack に `crossRegionReferences: true` を指定
- ChatbotStack に snsStack.snsTopic を渡す
const app = new cdk.App();
const snsStack = new SnsStack(app, 'SnsStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-east-1',
},
crossRegionReferences: true, <-- ポイント
});
new ChatbotStack(app, 'ChatbotStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-west-2',
},
snsTopic: snsStack.topic, <-- ポイント
crossRegionReferences: true, <-- ポイント
});
デプロイ結果
上記コードを `cdk deploy *Stack` でデプロイすると以下のスタックが作成されました。
- us-east-1 リージョンに、 "SnsStack" が作成
- us-west-2 リージョンに、"ChatbotStack"が作成
デプロイ結果 - SnsStack(us-east-1)
作成されたリソースの中に、 `ExportsWriter`なるLambda が含まれています。これが作成した SNS topic の情報を 対向リージョン(us-west-2) に保存しています。
デプロイ結果 - ChatbotStack(us-west-2)
一方、ChatbotStack の方には`ExportsReader` というカスタムリソースが作成されました。
これは、パラメータストアに含まれる SNS Topic を使った場合、強い参照を満たすために "使用中" フラグを付与しています。
また、パラメータストアが作成されてました。
こちらには、SNS Topic の Arn が書かれており、ChatbotStack のdynamic reference 参照先として利用されています
Topic 名を変更しようとして失敗 (冷や汗)
構築はうまくいったのですが、参照元リソースの編集を行おうとすると困ったことになりました。
lib/sns-stack.ts
SNSTopic 名を変更しようとしています。
CloudFormation リファレンスによると topicName の変更は `Replacement` が発生します。
this.topic = new sns.Topic(this, 'Topic', {
topicName: 'cdk-test-crossregion-topic-renamed',
displayName: 'cdk-test-crossregion-topic-renamed',
});
cdk deploy で失敗
SnsStack が UPDATE_ROLLBACK_FAILED状態に
もう一回 `cdk deploy` してもダメ
コードを戻して `cdk deploy` してもダメ
Events には、ExportWriter からのエラー
なお、参照元スタック(ChatbotStack)は変更が及んでいません。
強い参照がかかっているのに、リソースを再作成しようとしてエラーが発生しています。
直し方
かつてクロススタック参照でも同じことをやったので同一の方法で修正できます。
1. まず深呼吸
2. CloudFormation スタックをロールバック
CloudFormation コンソールより、対象スタックのロールバックを行います。ただし、ExportsWriterのロールバックはスキップします
Stack action
Continue update rollback
Advanced Trouble shooting をクリック
Resources to skip - optional が出るので、
ExportsWriterXXXX にチェック
Continue update rollback
3. コードを戻して cdk deploy
エラーの元になったコードを戻して、再度 cdk deploy を実施。
異常がないことを確認
どうしても参照元リソースを変更したい場合
結局、以下の手順に従ってリソースを変更しました。
できるだけダウンタイムを減らすため、少し面倒な手順を踏んでいます
ここの AWS ドキュメント を参考にしました。(ドキュメントを全部読まない自分が100%悪かった)
1. コード変更-テンポラリ
強い参照 が今回のエラーの原因だったので、リソース変更の間は crossRegionReference を無効化します。
参照先(ChatbotStack) にわたす SNS Topic を Arn で一時的にハードコード
new ChatbotStack(app, 'ChatbotStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-west-2',
},
// snsTopic: snsStack.topic,
snsTopicArn: 'arn:aws:sns:us-east-1:xxxxxx:cdk-test-crossregion-topic',
// crossRegionReferences: true,
});
さらに、参照先/参照元から、`crossRegionReference` をコメントアウトすることで exportReader, expoertWriter を一時的に削除しています
const snsStack = new SnsStack(app, 'SnsStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-east-1',
},
// crossRegionReferences: true,
});
new ChatbotStack(app, 'ChatbotStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: 'us-west-2',
},
// snsTopic: snsStack.topic,
snsTopicArn: 'arn:aws:sns:us-east-1:xxxxxx:cdk-test-crossregion-topic',
// crossRegionReferences: true,
});
2.cdk deploy
肝心のリソースは無傷のままなので、ここでダウンタイムは発生しません。
3. コード変更 - リソース変更
次に、本来加えたかったリソース変更をコードに加えます。
this.topic = new sns.Topic(this, 'Topic', {
topicName: 'cdk-test-crossregion-topic-renamed',
displayName: 'cdk-test-crossregion-topic-renamed',
});
さらに、crossRegionReference も復活させます。Topic Arnのハードコードも合わせて削除します。
4. cdk deploy
以下のような順番でリソースの作成/削除が行われます。
SnsStack更新
新Topic作成
旧Topic削除
! ここでダウンタイム発生 !
ChatbotStack 更新
新 Topic でChatbot 更新
SNS Topic の名前をようやく変更できました。
まとめ
CDK の crossRegionReferences 機能を使うと、以下のような条件を満たすことができます。
クロスリージョン間のリソースのやり取りをCDKが実施
cdk deploy 一発で新規追加/更新が可能
コード追加はほぼ無し
ただし、以下の点も考慮しておいてください。
強い参照
まだ experimental
使う前にここの AWS ドキュメントを読んでおいてください
その他リソース
色々調査した際に参考になった資料を置いておきます。時間のある時に読んでおけば冷や汗の量が少なくなるかと。
弱い参照を使いたい場合はこの手があります
コードを入れるのが少し面倒で、今回は利用しませんでした
CDK の import, include など参照についてまとめられた資料