5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CDKで複数アカウント・リージョンにまたがるAppをデプロイする

Last updated at Posted at 2023-12-13

TL;DR

  • Stackenv を渡すことでアカウントとリージョンを指定できる
    • env の違う Stack を1Appに紐付けることが可能
  • クロスアカウント参照するリソースの name を固定する
    • CDKに自動で決めさせたければ PhysicalName.GENERATE_IF_NEEDED を指定する
  • クロスリージョン参照をする/されるリソースを持つ StackcrossRegionReferences = true にする
  • CDKはブートストラップロールに assumeRole する権限さえあれば、自動でアカウントやリージョンをまたいでくれる
    • cdk bootstrap--trust オプションで、ブートストラップロールが他のアカウントからの assumeRole を受け入れるよう設定できる
  • Stack 毎に適切な --profile を指定してもデプロイはできる

やりたいこと

以下のような条件のCDKアプリを記述したい。

  • アプリケーションアカウント( appAccount:111111111111 )とデータ集積用アカウント( lakeAccount:222222222222 )を運用している
    • 基本的に appAccount にアプリを配置して、ログを lakeAccount に送りたい
  • 基本的には東京リージョンで運用したいが、東京リージョンで未提供の機能を使いたい(ex. SESの受信)

たとえばこんな感じ。

CDKアプリ

今回はTypeScriptで書いてみたが、他の言語でも同じように書けるはず。

import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import * as ses from "aws-cdk-lib/aws-ses";
import * as ses_actions from "aws-cdk-lib/aws-ses-actions";
import * as lambda_nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as logs from "aws-cdk-lib/aws-logs";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ecr_assets from "aws-cdk-lib/aws-ecr-assets";
import * as kinesis from "aws-cdk-lib/aws-kinesis";
import * as logs_destinations from "aws-cdk-lib/aws-logs-destinations";

const app = new cdk.App();

// アプリケーション(us-east-1)
const appVirginia = new cdk.Stack(app, "AppVirginia", {
+ env: {
+   account: "111111111111",
+   region: "us-east-1",
+ },
+ crossRegionReferences: true,
});

const func = new lambda_nodejs.NodejsFunction(appVirginia, "Function");
const receiptRuleSet = new ses.ReceiptRuleSet(appVirginia, "ReceiptRuleSet");
const receiptRule = new ses.ReceiptRule(appVirginia, "ReceiptRule", {
  ruleSet: receiptRuleSet,
  actions: [new ses_actions.Lambda({ function: func })],
});

// アプリケーション(ap-northeast-1)
const appTokyo = new cdk.Stack(app, "AppTokyo", {
+ env: {
+   account: "111111111111",
+   region: "ap-northeast-1",
+ },
+ crossRegionReferences: true,
});

const logGroup = new logs.LogGroup(appTokyo, "LogGroup");
const taskDefinition = new ecs.TaskDefinition(appTokyo, "TaskDefinition", {
  compatibility: ecs.Compatibility.FARGATE,
  cpu: "1024",
  memoryMiB: "2048",
});
taskDefinition.addContainer("Container", {
  image: ecs.ContainerImage.fromDockerImageAsset(
    new ecr_assets.DockerImageAsset(appTokyo, "ContainerImage", {
      directory: ".",
    })
  ),
  logging: new ecs.AwsLogDriver({ streamPrefix: "main", logGroup: logGroup }),
});

// データ集積(ap-northeast-1)
const logTokyo = new cdk.Stack(app, "LogTokyo", {
+ env: {
+   account: "222222222222",
+   region: "ap-northeast-1",
+ },
});

const stream = new kinesis.Stream(logTokyo, "Stream", {
+ streamName: cdk.PhysicalName.GENERATE_IF_NEEDED,
});

// スタックをまたいでも参照操作は同じ
taskDefinition.grantRun(func);
logGroup.addSubscriptionFilter("SubscriptionFilter", {
  destination: new logs_destinations.KinesisDestination(stream),
  filterPattern: logs.FilterPattern.allEvents(),
});

肝となるのは3つ。

Stackのenv

Stackenv で、アカウントIDとリージョンを渡せる。
複数の Stack を同じ App に紐づければ、マルチスタックアプリケーションになるが、 Stack 間の env が相違していても問題ない。

// アプリケーション(us-east-1)
const appVirginia = new cdk.Stack(app, "AppVirginia", {
+ env: {
+   account: "111111111111",
+   region: "us-east-1",
+ },
  crossRegionReferences: true,
});

// ...

// アプリケーション(ap-northeast-1)
const appTokyo = new cdk.Stack(app, "AppTokyo", {
+ env: {
+   account: "111111111111",
+   region: "ap-northeast-1",
+ },
  crossRegionReferences: true,
});

// ...

// データ集積(ap-northeast-1)
const logTokyo = new cdk.Stack(app, "LogTokyo", {
+ env: {
+   account: "222222222222",
+   region: "ap-northeast-1",
+ },
});

ステージ別でアカウントを切り替えたい場合は、 env に与える値を context あたりを使ってプログラムすれば良い。

クロスリージョン参照

クロスリージョン参照する/される Stackprops で、 crossRegionReferences = true に設定する。

// アプリケーション(us-east-1)
const appVirginia = new cdk.Stack(app, "AppVirginia", {
  env: {
    account: "111111111111",
    region: "us-east-1",
  },
+ crossRegionReferences: true,
});

// ...

// アプリケーション(ap-northeast-1)
const appTokyo = new cdk.Stack(app, "AppTokyo", {
  env: {
    account: "111111111111",
    region: "ap-northeast-1",
  },
+ crossRegionReferences: true,
});
  • くどいようだが、する/される側の両方に設定が必要
  • クロスリージョン参照を実現するために、CDKはカスタムリソースを生成する
    • 余分なリソースを嫌う場合は、クロスアカウント参照の手法(後述)も要検討
  • 同一アカウントに属する Stack 間でしか機能しないので注意
  • 執筆(2023/12)時点ではexperimentalなので一応注意

クロスアカウント参照

クロスアカウント参照されるリソースのリソースネームに相当するプロパティに、固定値を使用する。

ただし、明示的にリソースネームに固定値を与えることは、ベストプラクティスでは推奨されていない。

CDKに名前を決めさせつつ、クロスアカウント参照をしたい場合は、リソースネームに PhysicalName.GENERATE_IF_NEEDED を指定する。

const stream = new kinesis.Stream(logTokyo, "Stream", {
+ streamName: cdk.PhysicalName.GENERATE_IF_NEEDED,
});
  • こちらは参照される側だけでOK
  • PhysicalName.GENERATE_IF_NEEDED はクロスリージョン参照でも使えるが、そもそもリージョンをまたげないものには使用してもエラーになる
    • 今回の例で使っているECSのタスク定義が該当している
  • リソースが内部でサブリソースを自動生成する場合、リソースに PhysicalName.GENERATE_IF_NEEDED を指定してもサブリソースには波及しない
    • 例: lambdaのサービスロール
    • このような場合 PhysicalName.GENERATE_IF_NEEDED を命名したリソースを明示的に与える必要がある
      • 例:
        new lambda_nodejs.NodejsFunction(stack, "Function", {
          role: new iam.Role(stack, "FunctionRole", {
            assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
        +   roleName: cdk.PhysicalName.GENERATE_IF_NEEDED,
          }),
        });
        
        このように他に必須パラメータがあると煩わしい(公式対応してくれないかな)

デプロイする

条件さえ満たしていれば cdk deploy --all でデプロイ可能。

その条件とは、実行ユーザorロールが、以下のロールに assumeRole できること。

  • arn:aws:iam::${利用したいアカウントID}:role/cdk-${QUALIFIER}-file-publishing-role-${利用したいアカウントID}-${利用したいリージョン}
  • arn:aws:iam::${利用したいアカウントID}:role/cdk-${QUALIFIER}-image-publishing-role-${利用したいアカウントID}-${利用したいリージョン}
  • arn:aws:iam::${利用したいアカウントID}:role/cdk-${QUALIFIER}-lookup-role-${利用したいアカウントID}-${利用したいリージョン}
  • arn:aws:iam::${利用したいアカウントID}:role/cdk-${QUALIFIER}-deploy-role-${利用したいアカウントID}-${利用したいリージョン}

これらのロールは、CDKのブートストラップによって作成され、CDKから利用されている。
特にいじらすブートストラップした場合は、 QUALIFIER=hnb659fds

先述した env をもとに、CDKは自動的に Stack にあったロールにassumeしてデプロイを実行してくれる。

  • cdk bootstrap--trust <アカウントID> を渡すことで、ブートストラップロール側のPrincipalは設定してくれる
    • 複数のアカウントを trust したい場合はカンマで区切る
    • trust が済んでいれば、デプロイに使うユーザ/ロールにブートストラップロールへの iam:assumeRole を設定するだけで良い
  • 具体的な仕組みは、公式のSecurity And Safety Dev Guideがわかりやすい

もちろん、 Stack の名前と、その env にあった --profile を指定してもデプロイは可能。
どちらを使うかは、アカウントの運用方法に依ると思う。

蛇足

使用するロールは、厳密には Stack に渡す synthesizer によって決まる。
今回紹介したものは、これのデフォルト値である DefaultStackSynthesizer を、これまたデフォルトの設定で使用した場合の挙動である。

使用するクラスや与える設定によってかなり動作を変えられるので、気になる方は調べてみてほしい。

おわりに

1つのアプリケーションを複数のアカウント(ステージ)にデプロイする方法は結構転がっているが、
今回のような利用方法についてはあまり情報がなかったので、まとめてみた。
そもそも「そういう」アカウント構成じゃないと使えない知識ではあるが、どなたかのお役に立てば幸いである。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?