CDKで作成したスタックをCloudFormation StackSetsで展開する

2023.05.02

こんにちは。たかやまです。

こちらのブログにある通り、CDKを使ってCloudFormation(CFn) StackSetsを展開することができます。

ブログでは事前に準備したCFnテンプレートをCfnStackSetで展開する方法をご紹介しています。

CDKを使っている方にはCDKで作成したスタックをCFn StackSetsで展開したいという方もいると思うので、今回はCDKで作成したスタックをCFn StackSetsで展開する方法ご紹介したいと思います。

さきにまとめ

  • 展開したいCDKスタックをstage.synth().stacks[0].templateでCFnテンプレート変換することでL1コンストラクトCfnStackSetで展開可能
  • CDK Bootstrapを利用するL2コンストラクトの拡張機能は展開先アカウントで利用できない
    • S3のautoDeleteObjectsオプションなど
  • 複数のCFn StackSetsを管理する場合はテンプレートスタックにプレフィックスをつけて配列制御する

やってみた

サンプルコードはこちらです。

CDKコード紹介

ディレクトリ構成はこちらです。

.
├── bin
│   └── stacksets.ts
├── lib
│   ├── template
│   │   └── cloudtrail-stack.ts
│   └── stacksets.ts
├── test
│   └── stacksets.test.ts
├── README.md
├── cdk.json
├── jest.config.js
├── package-lock.json
├── package.json
└── tsconfig.json

lib/stacksets.ts

lib配下のスタックについてご紹介します。

lib/stacksets.tsはCFn StackSetsを作成するスタックです。

lib/stacksets.ts

import * as cdk from 'aws-cdk-lib';
import * as cfn from 'aws-cdk-lib/aws-cloudformation';
import { Construct } from 'constructs';

export interface props extends cdk.StackProps {
  stackSetsName: string;
  regions: string[];
  accounts?: string[];
  organizationalUnitIds?: string[];
  templateBody: string;
}

export class StackSets extends cdk.Stack {
  constructor(scope: Construct, id: string, props: props) {
    super(scope, id, props);

    new cfn.CfnStackSet(this, 'Stackset', {
      permissionModel: props.accounts !== undefined ? 'SELF_MANAGED' : 'SERVICE_MANAGED',
      stackSetName: `${props.stackSetsName}`,
      autoDeployment:
        props.accounts !== undefined
          ? undefined
          : {
              enabled: true,
              retainStacksOnAccountRemoval: false,
            },
      capabilities: ['CAPABILITY_NAMED_IAM'],
      stackInstancesGroup: [
        {
          regions: props.regions,
          deploymentTargets:
            props.accounts !== undefined ? { accounts: props.accounts } : { organizationalUnitIds: props.organizationalUnitIds },
        },
      ],
      templateBody: props.templateBody,
    });
  }
}

CFn StackSetsはOrganizations配下以外にデプロイするSELF_MANAGEDと、Organizations配下にデプロイするSERVICE_MANAGEDがあります。SERVICE_MANAGEDとSELF_MANAGEDで指定する必須パラメーターが変わります。ここではaccountsパラメータの指定によってSELF_MANAGEDかSERVICE_MANAGEDかを切り替えています。

lib/template/cloudtrail-stack.ts

lib/tmplate配下にはCFn StackSetsで展開するスタックのテンプレートを定義しています。

ここでは各アカウントにCloudTrailとS3を作成するテンプレートを定義しています。

lib/template/cloudtrail-stack.ts

import * as cdk from 'aws-cdk-lib';
import * as cloudtrail from 'aws-cdk-lib/aws-cloudtrail';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

export class OrganizationsCloudtrailStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    /**
     * Create S3
     */
    const bucket = new s3.Bucket(this, 'Bucket', {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      bucketName: `cloudtrail-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`,
      encryption: s3.BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });

    /**
     * Create CloudTrail
     */
    new cloudtrail.Trail(this, 'CloudTrail', {
      bucket: bucket,
      enableFileValidation: true,
      isMultiRegionTrail: true,
      sendToCloudWatchLogs: false,
      trailName: `cloudtrail-${cdk.Aws.ACCOUNT_ID}`,
    });
  }
}

CFn StackSetsでは展開するテンプレートのリソース名に重複を避けるため、アカウントIDやリージョン名をリソース名に含めることがあると思います。CDKではcdk.Aws.ACCOUNT_IDcdk.Aws.REGIONを使うことで、デプロイ時に動的に値を設定することができます。

生成されるテンプレートにCFnでいう疑似パラメータが設定されます。

一点注意点として、CDKを使っているとコンストラクトで拡張された機能を使いたくなると思いますがこちらは利用できません。(例えばS3のオブジェクトを自動削除するautoDeleteObjectsオプションなど)

コンストラクトで拡張された機能は管理アカウントのCDK Bootstrapのリソースを利用する必要があるため、CFn StackSetsで展開するアカウントのようにCDK Bootstrapを利用できないアカウントには適用できないためご注意ください。

bin/stacksets.ts

bin配下に実際にデプロイするCDKアプリを定義していきます。

bin/stacksets.ts

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import 'source-map-support/register';
import { StackSets } from '../lib/stacksets';
import { CloudtrailStack } from '../lib/template/cloudtrail-stack';

const app = new cdk.App();
const stage = new cdk.Stage(app, 'template');
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};

new CloudtrailStack(stage, 'CloudTrailStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});
const template1 = stage.synth().stacks[0].template;

new StackSets(app, 'CloudTrailStackSet', {
  stackSetsName: 'cloudtrail-stack',
  env: env,
  organizationalUnitIds: ['ou-omcc-xxxxxxxx'],
  regions: ['ap-northeast-1'],
  templateBody: JSON.stringify(template1),
});

CFn StackSetsのCDKアプリケーションを定義する前に、14-17行目でCDKでコーディングしたlib/template/cloudtrail-stack.tsをCDKのStage環境として定義し、CFnテンプレートに変換(synth)しています。

bin/stacksets.ts

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import 'source-map-support/register';
import { StackSets } from '../lib/stacksets';
import { CloudtrailStack } from '../lib/template/cloudtrail-stack';

const app = new cdk.App();
const stage = new cdk.Stage(app, 'template');
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};

new CloudtrailStack(stage, 'CloudTrailStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});
const template1 = stage.synth().stacks[0].template;

new StackSets(app, 'CloudTrailStackSet', {
  stackSetsName: 'cloudtrail-stack',
  env: env,
  organizationalUnitIds: ['ou-omcc-xxxxxxxx'],
  regions: ['ap-northeast-1'],
  templateBody: JSON.stringify(template1),
});

また、CFn StackSetsで展開するテンプレートはgenerateBootstrapVersionRuleをfalseに設定してください。こちらを設定しないとCFnテンプレート変換時にCDK BootstrapのバージョンチェックのRulesが追加されます。

CFn StackSetsの展開先アカウントはCDK Bootstrapを利用できないため、バージョンチェックを行うとデプロイが失敗するので、こちらのオプションを追加してバージョンチェックのRulesが追加されないようにしておきます。

CDK Bootstrapのバージョンチェックを行うRulesと設定したときのエラーメッセージ
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.

エラーメッセージ

Resource handler returned message: "Resource of type 'Stack set operation [50c1c093-936b-4715-a7da-032bab5a20ee] was unexpectedly stopped or failed. status reason(s): [Unable to fetch parameters [/cdk-bootstrap/hnb659fds/version] from parameter store for this account.]' with identifier 'cloudtrail-stack:e8c388fb-d905-4000-a13d-78532277d1eb' did not stabilize." (RequestToken: ba151e54-f653-c612-13dc-35815c21d9fa, HandlerErrorCode: NotStabilized)

展開するテンプレートの準備ができたら、CFn StackSetsのtemplateBodyに値を渡すことで、CFn StackSetsのテンプレートとして利用できます。

bin/stacksets.ts

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import 'source-map-support/register';
import { StackSets } from '../lib/stacksets';
import { CloudtrailStack } from '../lib/template/cloudtrail-stack';

const app = new cdk.App();
const stage = new cdk.Stage(app, 'template');
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};

new CloudtrailStack(stage, 'CloudTrailStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});
const template1 = stage.synth().stacks[0].template;

new StackSets(app, 'CloudTrailStackSet', {
  stackSetsName: 'cloudtrail-stack',
  env: env,
  organizationalUnitIds: ['ou-omcc-xxxxxxxx'],
  regions: ['ap-northeast-1'],
  templateBody: JSON.stringify(template1),
});

デプロイ

こちらのCDKアプリケーションをデプロイしてみます。

cdk lsをするとテンプレートとして利用されるStageのスタックとCFn StackSetsとして機能するAppのスタックが表示されます。

> npx cdk ls
CloudTrailStackSet
template/CloudTrailStack

デプロイはCFn StackSetsとして機能するAppのスタックをデプロイします。

npx cdk deploy CloudTrailStackSet

デプロイすると管理アカウントに、CFn StackSetsを展開するためのCFnが展開されます。

StackSetsコンソールにデプロイしたいスタックが展開されていればデプロイ完了です。

複数のCFn StackSetsをデプロイ

CFn StackSetsを複数展開する場合は、bin/stacksets.tsに複数のStackSetsを定義します。

ここではVPCを展開するテンプレートをlib/templateに追加してみます。(お金はかけたくないのでNAT Gatwayは0で...)

lib/template/vpc-stack.ts

import * as cdk from 'aws-cdk-lib';
import * as vpc from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';

export class VpcStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    /**
     * Create VPC
     */
    new vpc.Vpc(this, 'VPC', {
      natGateways: 0,
    });
  }
}

bin/stacksets.tsに追加したVPCテンプレートを展開するCFn StackSetsの定義を追加していきます。

bin/stacksets.ts

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import 'source-map-support/register';
import { StackSets } from '../lib/stacksets';
import { CloudtrailStack } from '../lib/template/cloudtrail-stack';
import { VpcStack } from '../lib/template/vpc-stack';

const app = new cdk.App();
const stage = new cdk.Stage(app, 'template');
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};

new CloudtrailStack(stage, 'CloudTrailStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});
new VpcStack(stage, 'VpcStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});

const template1 = stage.synth().stacks[0].template;
new StackSets(app, 'CloudTrailStackSet', {
  stackSetsName: 'cloudtrail-stack',
  env: env,
  organizationalUnitIds: ['ou-omcc-xxxxxxxx'],
  regions: ['ap-northeast-1'],
  templateBody: JSON.stringify(template1),
});

const template2 = stage.synth().stacks[1].template;
new StackSets(app, 'VpcStackSet', {
  stackSetsName: 'vpc-stack',
  env: env,
  organizationalUnitIds: ['ou-omcc-xxxxxxxx'],
  regions: ['ap-northeast-1'],
  templateBody: JSON.stringify(template2),
});

定義を見ていただくとわかる通り、CFn StackSetsに渡すテンプレート内容はstage.synth().stacksの配列を指定して制御しています。

ただ、この配列の順番ですが、bin/stacksets.tsの定義順に格納されているかというとそうではなく、Stage環境のスタック名の辞書式順序に格納されています。ここでは都合よくCloudTrailStackVpcStackの定義順序と辞書式順序が一致しているので問題ありませんが、なるべくStageのスタック名はプレフィックスでナンバリングしておくと良いと思います。

bin/stacksets.ts

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import 'source-map-support/register';
import { StackSets } from '../lib/stacksets';
import { CloudtrailStack } from '../lib/template/cloudtrail-stack';
import { VpcStack } from '../lib/template/vpc-stack';

const app = new cdk.App();
const stage = new cdk.Stage(app, 'template');
const env = {
  account: process.env.CDK_DEFAULT_ACCOUNT,
  region: process.env.CDK_DEFAULT_REGION,
};

new CloudtrailStack(stage, '01-CloudTrailStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});
new VpcStack(stage, '02-VpcStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});

const template1 = stage.synth().stacks[0].template;
new StackSets(app, 'CloudTrailStackSet', {
  stackSetsName: 'cloudtrail-stack',
  env: env,
  organizationalUnitIds: ['ou-omcc-xxxxxxxx'],
  regions: ['ap-northeast-1'],
  templateBody: JSON.stringify(template1),
});

const template2 = stage.synth().stacks[1].template;
new StackSets(app, 'VpcStackSet', {
  stackSetsName: 'vpc-stack',
  env: env,
  organizationalUnitIds: ['ou-omcc-xxxxxxxx'],
  regions: ['ap-northeast-1'],
  templateBody: JSON.stringify(template2),
});

stage.synth().stacksの配列サンプルを載せておくので、気になる方はご覧ください。

スタック名01-CloudTrailStack/02-VpcStackのときのstage.synth().stacksの配列
[
  CloudFormationStackArtifact {
    assembly: CloudAssembly {
      directory: 'cdk.out/assembly-template',
      manifest: [Object],
      version: '31.0.0',
      artifacts: [Array],
      runtime: [Object]
    },
    id: 'template01CloudTrailStack6486F08A',
    manifest: {
      type: 'aws:cloudformation:stack',
      environment: 'aws://unknown-account/unknown-region',
      properties: [Object],
      dependencies: [Array],
      metadata: [Object],
      displayName: 'template/01-CloudTrailStack'
    },
    messages: [],
    _dependencyIDs: [ 'template01CloudTrailStack6486F08A.assets' ],
    environment: {
      account: 'unknown-account',
      region: 'unknown-region',
      name: 'aws://unknown-account/unknown-region'
    },
    templateFile: 'template01CloudTrailStack6486F08A.template.json',
    parameters: {},
    tags: {},
    assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}',
    assumeRoleExternalId: undefined,
    cloudFormationExecutionRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}',
    stackTemplateAssetObjectUrl: 's3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b2c6ad7f1f26184db44be45c55d65a916b0c6cf35ed157f1282eacae24a68aef.json',
    requiresBootstrapStackVersion: 6,
    bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version',
    terminationProtection: undefined,
    validateOnSynth: false,
    lookupRole: {
      arn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}',
      requiresBootstrapStackVersion: 8,
      bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version'
    },
    stackName: 'template-01-CloudTrailStack',
    assets: [],
    displayName: 'template/01-CloudTrailStack (template-01-CloudTrailStack)',
    name: 'template-01-CloudTrailStack',
    originalName: 'template-01-CloudTrailStack',
    _deps: [ [AssetManifestArtifact] ]
  },
  CloudFormationStackArtifact {
    assembly: CloudAssembly {
      directory: 'cdk.out/assembly-template',
      manifest: [Object],
      version: '31.0.0',
      artifacts: [Array],
      runtime: [Object]
    },
    id: 'template02VpcStack20E16860',
    manifest: {
      type: 'aws:cloudformation:stack',
      environment: 'aws://unknown-account/unknown-region',
      properties: [Object],
      dependencies: [Array],
      metadata: [Object],
      displayName: 'template/02-VpcStack'
    },
    messages: [],
    _dependencyIDs: [ 'template02VpcStack20E16860.assets' ],
    environment: {
      account: 'unknown-account',
      region: 'unknown-region',
      name: 'aws://unknown-account/unknown-region'
    },
    templateFile: 'template02VpcStack20E16860.template.json',
    parameters: {},
    tags: {},
    assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}',
    assumeRoleExternalId: undefined,
    cloudFormationExecutionRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}',
    stackTemplateAssetObjectUrl: 's3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/687f13f0cd648f71831293dc979635b3392b87adc4359a290f2cf8a9c720bec5.json',
    requiresBootstrapStackVersion: 6,
    bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version',
    terminationProtection: undefined,
    validateOnSynth: false,
    lookupRole: {
      arn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}',
      requiresBootstrapStackVersion: 8,
      bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version'
    },
    stackName: 'template-02-VpcStack',
    assets: [],
    displayName: 'template/02-VpcStack (template-02-VpcStack)',
    name: 'template-02-VpcStack',
    originalName: 'template-02-VpcStack',
    _deps: [ [AssetManifestArtifact] ]
  }
]
スタック名01-VpcStack/02-CloudTrailStackのときのstage.synth().stacksの配列
[
  CloudFormationStackArtifact {
    assembly: CloudAssembly {
      directory: 'cdk.out/assembly-template',
      manifest: [Object],
      version: '31.0.0',
      artifacts: [Array],
      runtime: [Object]
    },
    id: 'template01VpcStack40FB5329',
    manifest: {
      type: 'aws:cloudformation:stack',
      environment: 'aws://unknown-account/unknown-region',
      properties: [Object],
      dependencies: [Array],
      metadata: [Object],
      displayName: 'template/01-VpcStack'
    },
    messages: [],
    _dependencyIDs: [ 'template01VpcStack40FB5329.assets' ],
    environment: {
      account: 'unknown-account',
      region: 'unknown-region',
      name: 'aws://unknown-account/unknown-region'
    },
    templateFile: 'template01VpcStack40FB5329.template.json',
    parameters: {},
    tags: {},
    assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}',
    assumeRoleExternalId: undefined,
    cloudFormationExecutionRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}',
    stackTemplateAssetObjectUrl: 's3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/be394ee471b065d48e13675e069a147352dcf222e7f4185c7dcee67852f1f464.json',
    requiresBootstrapStackVersion: 6,
    bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version',
    terminationProtection: undefined,
    validateOnSynth: false,
    lookupRole: {
      arn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}',
      requiresBootstrapStackVersion: 8,
      bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version'
    },
    stackName: 'template-01-VpcStack',
    assets: [],
    displayName: 'template/01-VpcStack (template-01-VpcStack)',
    name: 'template-01-VpcStack',
    originalName: 'template-01-VpcStack',
    _deps: [ [AssetManifestArtifact] ]
  },
  CloudFormationStackArtifact {
    assembly: CloudAssembly {
      directory: 'cdk.out/assembly-template',
      manifest: [Object],
      version: '31.0.0',
      artifacts: [Array],
      runtime: [Object]
    },
    id: 'template02CloudTrailStackA2668286',
    manifest: {
      type: 'aws:cloudformation:stack',
      environment: 'aws://unknown-account/unknown-region',
      properties: [Object],
      dependencies: [Array],
      metadata: [Object],
      displayName: 'template/02-CloudTrailStack'
    },
    messages: [],
    _dependencyIDs: [ 'template02CloudTrailStackA2668286.assets' ],
    environment: {
      account: 'unknown-account',
      region: 'unknown-region',
      name: 'aws://unknown-account/unknown-region'
    },
    templateFile: 'template02CloudTrailStackA2668286.template.json',
    parameters: {},
    tags: {},
    assumeRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}',
    assumeRoleExternalId: undefined,
    cloudFormationExecutionRoleArn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}',
    stackTemplateAssetObjectUrl: 's3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/da4a7edb538f1f6fa31d42f718b0b040b925253321763a2cabb50985ea6b6c90.json',
    requiresBootstrapStackVersion: 6,
    bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version',
    terminationProtection: undefined,
    validateOnSynth: false,
    lookupRole: {
      arn: 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}',
      requiresBootstrapStackVersion: 8,
      bootstrapStackVersionSsmParameter: '/cdk-bootstrap/hnb659fds/version'
    },
    stackName: 'template-02-CloudTrailStack',
    assets: [],
    displayName: 'template/02-CloudTrailStack (template-02-CloudTrailStack)',
    name: 'template-02-CloudTrailStack',
    originalName: 'template-02-CloudTrailStack',
    _deps: [ [AssetManifestArtifact] ]
  }
]

cdk lsをしてみるとこのようになります。

> npx cdk ls
CloudTrailStackSet
VpcStackSet
template/01-CloudTrailStack
template/02-VpcStack

あとはデプロイしたいAppのスタックをデプロイしていきます。

npx cdk deploy VpcStackSet

最後に

CFn StackSetsを展開する場合、L1コンストラクトのCfnStackSetしか用意されておらず、ぱっとみCDKで定義したコードの展開はできないように思えますが、今回ご紹介した手法を用いることで、CDKで定義したコードを展開することができました。

展開元のCFnもCDKで定義することでコーディング工数を大幅に削減できると思いますので、ぜひStackSetsの管理する手法としてCDKご活用いただければと思います。

以上、たかやま(@nyan_kotaroo)でした。