見出し画像

CDK で crossRegionReferences を使ったらちょいと冷や汗かいた話


はじめに

AWS で、複数のスタックを同時に作りたい。
しかも、それぞれのスタック間に依存関係がある。
しかも、異なるリージョンにスタックを作りたい。
でも、ツール一発でデプロイも更新もやってもらいたい。
できれば、リージョン間の依存関係も解決してほしい。
ぜひ、複雑なコード無しで。

つまり、`クロスリージョンスタック`をツール一発でよしなにやってほしい、というものが CDK の 'crossRegionReferences' でできました。
が、使ってみてちょっと冷や汗をかいた(ドキュメントを全部読まない自分が100%悪かった) ので共有いたします

ツールを使わない場合のマルチリージョンスタック構成

上記のように、異なるリージョン間で依存関係にあるリソースがある場合、cdk を使わず CloudFormation だけで作ろうとすると以下のようなデプロイ方法が必要です

  1. SNS Stack をデプロイする
    Topic Arn を出力して、メモしておく

  2. 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 の名前をようやく変更できました。

もうワンクッション置けばダウンタイムは完全になくせたかもしれません
旧 Topicの削除は、新TopicでのChatbot更新の後にすれば、よかったかもです

まとめ

CDK の crossRegionReferences 機能を使うと、以下のような条件を満たすことができます。

  • クロスリージョン間のリソースのやり取りをCDKが実施

  • cdk deploy 一発で新規追加/更新が可能

  • コード追加はほぼ無し

ただし、以下の点も考慮しておいてください。

  • 強い参照

  • まだ experimental

使う前にここの AWS ドキュメントを読んでおいてください

その他リソース

色々調査した際に参考になった資料を置いておきます。時間のある時に読んでおけば冷や汗の量が少なくなるかと。