AWS CDKでAWS::CloudFormation::Interfaceを設定してみた

2023.05.31

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

普段CDKを使ってリソース構築を行っていますが最終的な運用としてCloudFormationテンプレートを利用したいときがあります。

CloudFormationテンプレートで運用する場合、テンプレートへ自由に値を渡せるParametersを利用すると思います。このParametersはCDKでもCfnParameter*1としてサポートされていて定義することができます。

1通常のCDK利用の場合は、CDKアプリ内でのパラメータ定義が推奨されます。

ただ、こちらのParametersですがデフォルト状態だとパラメータ名で昇順に並べ替えされてしまいます。

こちらを任意の順番に並び変える場合、CloudFormationではAWS::CloudFormation::Interfaceを利用することで並び順を変更することができます。ただ、こちらのAWS::CloudFormation::InterfaceはCDKのL1でサポートされていません。

ただ、Issue内でも上げられているtemplateOptions.metadataを利用することで、CDKでもAWS::CloudFormation::Interfaceを設定できるので今回こちらを試していきたいと思います。

やってみた

AWS::CloudFormation::Interfaceの設定

templateOptions.metadataを利用して、AWS::CloudFormation::Interfaceを設定していきたいと思います。

ここではAWS::CloudFormation::Interfaceのドキュメントで書かれているサンプルをCDKで設定してみたいと思います。

AWS::CloudFormation::Interface - AWS CloudFormation

こちらのParametersとInterfaceをCDKで設定すると以下のようになります。

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

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

    // Parameters
    const vpcId = new cdk.CfnParameter(this, 'VpcId', {
      type: 'AWS::EC2::VPC::Id',
      description: 'VpcId of you existing VPC',
    });
    const subnetId = new cdk.CfnParameter(this, 'SubnetId', {
      type: 'AWS::EC2::Subnet::Id',
      description: 'The list of SubnetIds for the VPC',
    });
    const securityGroupId = new cdk.CfnParameter(this, 'SecurityGroupId', {
      type: 'AWS::EC2::SecurityGroup::Id',
      description: 'The list of SecurityGroupIds for the VPC',
    });
    const instanceType = new cdk.CfnParameter(this, 'InstanceType', {
      type: 'String',
      description: 'EC2 instance type to use for the instance',
      default: 't3.micro',
    });
    const keyPairName = new cdk.CfnParameter(this, 'KeyPairName', {
      type: 'AWS::EC2::KeyPair::KeyName',
      description: 'Name of an existing EC2 KeyPair to enable SSH access to the instance',
    });

    // Interface
    this.templateOptions.metadata = {
      'AWS::CloudFormation::Interface': {
        ParameterGroups: [
          {
            Label: { default: 'Network Configuration' },
            Parameters: [vpcId.logicalId, subnetId.logicalId, securityGroupId.logicalId],
          },
          {
            Label: { default: 'Amazon EC2 Configuration' },
            Parameters: [instanceType.logicalId, keyPairName.logicalId],
          },
        ],
        ParameterLabels: {
          VpcId: { default: 'Which VPC should this be deployed to?' },
        },
      },
    };

  }
}

見ていただくとわかる通り、templateOptions.metadataにCloudFormationで定義するJson形式の構文と同様の内容を渡してあげることで定義することができます。

やりたいことは上記のコードで実現できたのですが、せっかくCDKを利用しているのでメソッドを利用してInterfaceを定義していきたいと思います。

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

export class CdkInterfaceStack extends cdk.Stack {
  private paramGroups: any[] = [];
  private paramLabels: any = {};

  private addToParamGroups(label: string, ...param: string[]) {
    this.paramGroups.push({
      Label: { default: label },
      Parameters: param,
    });
  }

  private addToParamLabels(label: string, param: string) {
    this.paramLabels[param] = {
      default: label,
    };
  }
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Parameters
    const vpcId = new cdk.CfnParameter(this, 'VpcId', {
      type: 'AWS::EC2::VPC::Id',
      description: 'VpcId of you existing VPC',
    });
    const subnetId = new cdk.CfnParameter(this, 'SubnetId', {
      type: 'AWS::EC2::Subnet::Id',
      description: 'The list of SubnetIds for the VPC',
    });
    const securityGroupId = new cdk.CfnParameter(this, 'SecurityGroupId', {
      type: 'AWS::EC2::SecurityGroup::Id',
      description: 'The list of SecurityGroupIds for the VPC',
    });
    const instanceType = new cdk.CfnParameter(this, 'InstanceType', {
      type: 'String',
      description: 'EC2 instance type to use for the instance',
      default: 't3.micro',
    });
    const keyPairName = new cdk.CfnParameter(this, 'KeyPairName', {
      type: 'AWS::EC2::KeyPair::KeyName',
      description: 'Name of an existing EC2 KeyPair to enable SSH access to the instance',
    });

    // Interface
    this.addToParamGroups('Network Configuration', vpcId.logicalId, subnetId.logicalId, securityGroupId.logicalId);
    this.addToParamGroups('Amazon EC2 Configuration', instanceType.logicalId, keyPairName.logicalId);
    this.addToParamLabels('Which VPC should this be deployed to?', vpcId.logicalId);
    this.templateOptions.metadata = {
      'AWS::CloudFormation::Interface': {
        ParameterGroups: this.paramGroups,
        ParameterLabels: this.paramLabels,
      },
    };
  }
}

Jsonを扱わなくてよくなり多少見栄えがよくなりました。

CloudFormationテンプレートの生成

では定義したCDKからCloudFormationテンプレートを生成(cdk synth)してみます。

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Network Configuration
        Parameters:
          - VpcId
          - SubnetId
          - SecurityGroupId
      - Label:
          default: Amazon EC2 Configuration
        Parameters:
          - InstanceType
          - KeyPairName
    ParameterLabels:
      VpcId:
        default: Which VPC should this be deployed to?
Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VpcId of you existing VPC
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: The list of SubnetIds for the VPC
  SecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id
    Description: The list of SecurityGroupIds for the VPC
  InstanceType:
    Type: String
    Default: t3.micro
    Description: EC2 instance type to use for the instance
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instance
  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]
Resources:
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/yWMMQ6AIBDA3uIOJzq58wGDL0A4kxM5EkAdjH9X49R2aQ9DB6qxZ5HOB7nRDNdUrQtCLzzabCNWzF8YLGnPDj/XiT1VSnwLTh5hLe3RDaDe01qIZN65UkQwPx/hXa3cZQAAAA==
    Metadata:
      aws:cdk:path: CdkInterfaceStack/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - af-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2
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.

生成されたCloudFormationテンプレートを見ていただくと分かる通り、CloudFormationテンプレートとして利用する分にはMetadataやBootstrapなどの不要な情報が入ってしまっています。

こちらの情報は以下のオプションを設定することで削除することができます。

  • CDKアプリでgenerateBootstarapVersionRuleをfalseに設定する
  • cdk synthの際に--no-version-reporting--no-path-metadataを設定する

実際の例で見ると以下のようになります。

bin/cdk.ts

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import 'source-map-support/register';
import { CdkInterfaceStack } from '../lib/cdk-interface-stack';

const app = new cdk.App();
new CdkInterfaceStack(app, 'CdkInterfaceStack', {
  synthesizer: new cdk.DefaultStackSynthesizer({ generateBootstrapVersionRule: false }),
});

bin配下のCDKアプリにgenerateBootstarapVersionRuleにfalseに設定したあと、
cdk synth --no-version-reporting --no-path-metadataを実行すると以下のようなテンプレートが生成されます。

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Network Configuration
        Parameters:
          - VpcId
          - SubnetId
          - SecurityGroupId
      - Label:
          default: Amazon EC2 Configuration
        Parameters:
          - InstanceType
          - KeyPairName
    ParameterLabels:
      VpcId:
        default: Which VPC should this be deployed to?
Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: VpcId of you existing VPC
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: The list of SubnetIds for the VPC
  SecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id
    Description: The list of SecurityGroupIds for the VPC
  InstanceType:
    Type: String
    Default: t3.micro
    Description: EC2 instance type to use for the instance
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instance

CDKのMetadataやBootstrapの情報が削除されスッキリしましたね。あとは、普段通りCDKでリソースを定義していけばOKです!

最後に

今回の手法を利用することで、CloudFormationで運用する必要がある環境のテンプレートの作成でもCDKを活用することができます。

CDKを利用することでコーディング工数を削減できるので、CloudFormationの作成でもCDKを活用していきたいですね!

小ネタでしたが、こちらの記事がどなたかの助けになれば幸いです。

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