TL;DR
-
Stack
にenv
を渡すことでアカウントとリージョンを指定できる-
env
の違うStack
を1Appに紐付けることが可能
-
- クロスアカウント参照するリソースの
name
を固定する- CDKに自動で決めさせたければ
PhysicalName.GENERATE_IF_NEEDED
を指定する
- CDKに自動で決めさせたければ
- クロスリージョン参照をする/されるリソースを持つ
Stack
はcrossRegionReferences = 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
Stack
に env
で、アカウント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
あたりを使ってプログラムすれば良い。
クロスリージョン参照
クロスリージョン参照する/される Stack
の props
で、 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つのアプリケーションを複数のアカウント(ステージ)にデプロイする方法は結構転がっているが、
今回のような利用方法についてはあまり情報がなかったので、まとめてみた。
そもそも「そういう」アカウント構成じゃないと使えない知識ではあるが、どなたかのお役に立てば幸いである。