1クール続けるブログ

とりあえず1クール続けるソフトウェアエンジニアの備忘録

CDK for TerraformがGoをサポートしたので試してみた

記事一覧はこちら

背景・モチベーション

CDKには元々興味がありました!CloudFormationでのyaml記述はやはり辛いものがあるし、プログラミング言語を利用することでコード補完によるサポートを得られたり、抽象化やvalidateを入れたり出来るのではと期待があります。
CDK for Terraformのv0.0.4がリリースされ、Goのサポート(experimental)を開始したことをこの記事から知り、良い機会なのかなと思い試してみました。

参考文献

CDK For Terraform とは

ここにあるとおり、プログラミング言語でインフラストラクチャを記述したいというDeveloperの要望に応える形で、AWS CDKのチームと協力し、AWS CDKの2つの主要なコンポーネントを活用してTerraform用のCDKをサポートし始めたとのことです。

AWS CDKに関しては、こちらが分かりやすいです。
AWS CDKはCloudFormationテンプレートをjson形式で吐くのに対し、Terraform用CDKはTerraformの構成ファイルをjson形式で吐きます。元々、HCLだけでなくjsonもサポートしているのはあんまり意識したことなかったなあと思ったり。

CDKは多言語をサポートしてます。そこにはjsiiというライブラリとかが絡んできていて、掘るのが面白そうなのですがそれはまた別の記事にしようと思います。

Getting Started

リポジトリはこちら

github.com

※ Terraformはインストール済の前提

実装の初期段階まで

$ brew install cdktf
$ mkdir cdk-for-terraform-sample

# 雛形の作成
$ cdktf init --template="go" --local

# moduleの宣言が自動生成されているので修正する
$ vi go.mod 

プロジェクトルートにある cdktf.json に利用するTerraform ProviderとModuleを宣言します
例えば、AWSのProviderとEKSのModuleを利用するのであれば下記のように宣言する

{
    "language": "go",
    "app": "go run main.go",
    "codeMakerOutput": "generated",
    "terraformProviders": [
        "hashicorp/aws@~> 3.40.0"
    ],
    "terraformModules": [
        {
            "name": "aws-eks-module",
            "source": "terraform-aws-modules/eks/aws",
            "version": "~> 16.0"
        }
    ]
}

下記のコマンドを実行すると、上で設定した内容を元にコードを自動生成します
codeMakerOutput で設定したディレクトリに生成したコードが配置されます

$ cdktf get
⠇ downloading and generating modules and providers...  # めっちゃ時間かかりますし、メモリを6-7GB食います

Goのコードで実装

main.go に宣言された、NewMyStack 関数にてリソースの定義を行っていきます。
基本的な関数の構造としては下記のような形で、第一引数と第二引数は各関数で共通で、第三引数は各リソースごとのinputのパラメータを持った構造体を渡してあげるイメージ。殆どが参照渡しになるので、適宜jsiiパッケージのヘルパー関数を利用する。

vpc := aws.NewCamelCaseResourceName(stack cdktf.TerraformStack, id *string, config *aws.CamelCaseResourceNameConfig)

例えば、簡単なAWSのスタックを作成するとなると下記のようになると思います。

func NewMyStack(scope constructs.Construct, id string) cdktf.TerraformStack {
    stack := cdktf.NewTerraformStack(scope, &id)

    aws.NewAwsProvider(stack, jsii.String("aws"), &aws.AwsProviderConfig{
        Region: jsii.String(region),
    })

    vpc := aws.NewVpc(stack, jsii.String("isucon_vpc"), &aws.VpcConfig{
        CidrBlock: jsii.String("172.16.0.0/16"),
        Tags: &map[string]*string{
            "Name": jsii.String("isucon-training"),
        },
    })

    igw := aws.NewInternetGateway(stack, jsii.String("isucon_vpc_igw"), &aws.InternetGatewayConfig{
        VpcId: vpc.Id(),
    })

    subnet := aws.NewSubnet(stack, jsii.String("isucon9_subnet"), &aws.SubnetConfig{
        VpcId:            vpc.Id(),
        CidrBlock:        jsii.String("172.16.10.0/24"),
        AvailabilityZone: jsii.String("ap-northeast-1d"),
        Tags: &map[string]*string{
            "Name": jsii.String("isucon-training"),
        },
        DependsOn: &[]cdktf.ITerraformDependable{
            igw,
        },
    })

    // 他にもリソースを作成(詳しくはリポジトリをご覧ください)

    return stack
}

デプロイしてみる

下記のコマンド実行時に cdktf synth も実行されて、cdktf.outディレクトリにjson形式でterraformの構成ファイルが生成されます。
注意点としては、cdktfのv0.4.0の時点では cdktf destroy時にエラーが発生します(Issue)。

# terrafrom planとほぼ同じような出力が得られる
$ cdktf diff
Stack: cdk-for-terraform-sample
Resources
 + AWS_DEFAULT_ROUTE_TA isucon9_subnet_rout aws_default_route_table.isucon9_subnet_
   BLE                  e_table             route_table
 + AWS_EC2_MANAGED_PREF default_prefix      aws_ec2_managed_prefix_list.default_pre
   IX_LIST                                  fix
 + AWS_EIP              isucon9_qualify_ins aws_eip.isucon9_qualify_instance_eip
                        tance_eip
 + AWS_INSTANCE         isucon9_qualify_ins aws_instance.isucon9_qualify_instance
                        tance
 + AWS_KEY_PAIR         developer_keypair   aws_key_pair.developer_keypair
 + AWS_ROUTE_TABLE_ASSO isucon9_subnet_rout aws_route_table_association.isucon9_sub
   CIATION              e_table_d           net_route_table_d
 ~ AWS_SECURITY_GROUP   isucon9_qualify_ins aws_security_group.isucon9_qualify_inst
                        tance_sg            ance_sg
 + AWS_SUBNET           isucon9_subnet      aws_subnet.isucon9_subnet

Diff: 7 to create, 1 to update, 0 to delete.

# デプロイ
$ cdktf deploy

# お片付け(エラーが発生します)
$ cdktf destroy
ypeError: Cannot read property 'startsWith' of undefined

使ってみての所感

文字列や数値の型をJSに合わせるために関数を毎回使っているせいでスッキリ見えないのがあまり良くないかもというのはあります。
また、この段階だからだと思うのですが、Terraformのresourceやmoduleを元にコード生成する過程でかなり時間とリソースを食います。これはネックになりそうです。
規模感が大きくならないと、中々恩恵は受けづらいのかなと思う面もありました。
(でもyaml書くより楽しい…)
以上!