AWS CDKのConstructをCDK for Terraformで使用できるAWS Adapterを使ってみた

2022.08.12

「CDKTFでAWS Adapterを使えばAWS CDKのConstructを使用できる!?」

CDKTFのドキュメントを見ていたら、AWS Adapter というものを見つけました。(2022/8現在は、technical previewです)

»[technical preview] AWS Adapter

CDKTFはTerraformをラップするだけではなく、AWS Adapterを使うことでAWS CDKのConstructを使用できるようです。

ざっくり紹介と試した内容をブログにします。

AWS Adapterとは

通常CDKTFでAWSリソース作成する際は、AWSプロパイダ(hashicorp/cdktf-provider-aws)を使用します。

AWS Adapterを使用することで、AWS CDKのライブラリ(aws/aws-cdk)を使用することができます。

見てもらった方が理解しやすいかと思うので、公式ドキュメントのコードを引用します。

import {
  Duration,
  aws_lambda,
  aws_events,
  aws_events_targets,
} from "aws-cdk-lib";
import { AwsTerraformAdapter, AwsProvider } from "@cdktf/aws-cdk";

export class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new AwsProvider(this, "aws", { region: "us-west-2" });

    const awsAdapter = new AwsTerraformAdapter(this, "adapter");

    // aws-cdkでリソース作成
    // scopeの部分にはawsAdapterを入れる
    const lambdaFn = new aws_lambda.Function(awsAdapter, "lambda", {
      code: new aws_lambda.InlineCode(
        `def main(event, context):
    print("I'm running!")`
      ),
      handler: "index.main",
      timeout: Duration.seconds(300),
      runtime: aws_lambda.Runtime.PYTHON_3_6,
    });

    const rule = new aws_events.Rule(awsAdapter, "rule", {
      schedule: aws_events.Schedule.expression("cron(0 18 ? * MON-FRI *)"),
    });

    rule.addTarget(new aws_events_targets.LambdaFunction(lambdaFn));
  }
}

CDKTFでAWS CDKを使えると、L2 L3 ConstructやConstruHubのCDKライブラリを使ってより簡単にインフラ定義ができるというメリットがあると思います。

AwsTerraformAdapterはAWS Cloud Control API 使用しています。 その関係で、AWS Cloud Control APIが対応していないリソースに関しては使用するにあたりloudFormationとTerraformの手動Mapping必要です。

Mappingの定義方法についても、上記ドキュメント上で紹介されていました。 気になる方は、そちらをご参照ください。

やってみた

CDKTFでAWS CDKのL2Constructを使ってVPC作成

CDKTF用のファイルを用意します。

$ npx cdktf init --template=typescript --local

その後、AWS CDKを使用するために必要なパッケージをインストールします。

$ npm install aws-cdk-lib @cdktf/aws-cdk

以下のファイルを用意します。

main.ts

import { Construct } from "constructs";
import { App, TerraformStack, TerraformOutput } from "cdktf";
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { AwsTerraformAdapter, AwsProvider } from "@cdktf/aws-cdk";

export class VpcStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new AwsProvider(this, "aws", { region: "ap-northeast-1" });

    const awsAdapter = new AwsTerraformAdapter(this, "adapter");

    new ec2.Vpc(awsAdapter, 'VPC', {
      cidr: "10.0.0.0/16",
      natGateways: 0,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'public',
          subnetType: ec2.SubnetType.PUBLIC,
        },
      ]
    });
  }
}

const app = new App();
new VpcStack(app, "vpc", );

app.synth();

コマンドでスタックやリソースの状態を確認します。

diffの結果は抜粋しています。

diffを見たところ、L2 Constructを使用しているためVPC以外にもSubnetやRouteTable、IGWなど関連リソースを作成してくれているのがわかります。

$ npx cdktf list # スタックの確認
Stack name                      Path
vpc                             cdktf.out/stacks/vpc
$ npx cdktf diff # 作成されるリソースの確認
### 抜粋 ###
vpc    # aws_subnet.vpc_adapter_VPCpublicSubnet2Subnet2B2DFF71_3903EC71 (adapter/VPCpublicSubnet2Subnet2B2DFF71) will be created
       + resource "aws_subnet" "vpc_adapter_VPCpublicSubnet2Subnet2B2DFF71_3903EC71" {
     + arn                                            = (known after apply)
     + assign_ipv6_address_on_creation                = false
     + availability_zone                              = "ap-northeast-1c"
     + availability_zone_id                           = (known after apply)
     + cidr_block                                     = "10.0.1.0/24"
     + enable_dns64                                   = false
     + enable_resource_name_dns_a_record_on_launch    = false
     + enable_resource_name_dns_aaaa_record_on_launch = false
     + id                                             = (known after apply)
     + ipv6_cidr_block_association_id                 = (known after apply)
     + ipv6_native                                    = false
     + map_public_ip_on_launch                        = true
     + owner_id                                       = (known after apply)
     + private_dns_hostname_type_on_launch            = (known after apply)
     + tags_all                                       = (known after apply)
     + vpc_id                                         = (known after apply)
     }

       # aws_vpc.vpc_adapter_VPCB9E5F0B4_256A8F5A (adapter/VPCB9E5F0B4) will be created
       + resource "aws_vpc" "vpc_adapter_VPCB9E5F0B4_256A8F5A" {
     + arn                                  = (known after apply)
     + cidr_block                           = "10.0.0.0/16"
     + default_network_acl_id               = (known after apply)
     + default_route_table_id               = (known after apply)
     + default_security_group_id            = (known after apply)
     + dhcp_options_id                      = (known after apply)
     + enable_classiclink                   = (known after apply)
     + enable_classiclink_dns_support       = (known after apply)
     + enable_dns_hostnames                 = true
     + enable_dns_support                   = true
     + id                                   = (known after apply)
     + instance_tenancy                     = "default"
     + ipv6_association_id                  = (known after apply)
     + ipv6_cidr_block                      = (known after apply)
     + ipv6_cidr_block_network_border_group = (known after apply)
     + main_route_table_id                  = (known after apply)
     + owner_id                             = (known after apply)
     + tags_all                             = (known after apply)
     }

     Plan: 10 to add, 0 to change, 0 to destroy.
     
     ─────────────────────────────────────────────────────────────────────────────

     Saved the plan to: plan

     To perform exactly these actions, run the following command to apply:
     terraform apply "plan"

deployで実際にリソース作成します。

$ npx cdktf deploy

マネジメントコンソールからもリソース作成が確認できました。

検証が終わったらdestroyでリソース削除します。

$npx cdktf destroy

おわりに

AWS CDKのConstructが使えるのはかなり便利なのでAWS AdpterのGAが待ち遠しいです。

小ネタなんですが、cdktfのコマンド deployにはapplyが、diffにはplanがそれぞれエイリアスとして設定されていました。

(cdktf deployと打とうとしたところ、Terraformの癖でcdktf applyと打っても動いたのて気づきました)

Terraformに慣れている方には、嬉しいですね。

$ npx cdktf help
cdktf

コマンド:
  cdktf init                Create a new cdktf project from a template.
  cdktf get                 Generate CDK Constructs for Terraform providers and modules.
  cdktf convert             Converts a single file of HCL configuration to CDK for Terraform. Takes the file to be converted on stdin.
  cdktf deploy [stacks...]  Deploy the given stacks                                                                              [エイリアス: apply]
  cdktf destroy [stacks..]  Destroy the given stacks
  cdktf diff [stack]        Perform a diff (terraform plan) for the given stack                                                   [エイリアス: plan]