🐻

terraform cdk with Go ver. 0.9.0 (experimental) をawsに対して使ってみた

2022/02/12に公開

terraform cdkとは

terraformを独自言語のHCLを使わずに、馴染みのあるプログラミング言語で定義することができます。CloudFormationとAWS CDKの関係と同じようなものです。執筆時点で以下の言語が対応しています。

TypeSrcipt, PythonでAWS CDKの使用経験があることと、普段Goをよく書いていることから、experimentalではあるもののGoのterraform cdkを使ってみました。この記事では使ってみた感想と、躓いた箇所及びその補足を行います。

環境構築

こちらに必要な環境が記載されています。執筆時点では以下のとおりです。

  • Terraform >= v0.12
  • Node.js >= v12.16
  • Go >= 1.16 (CDK for Terraform uses go modules and JSII requires Go 1.16)

それぞれ各自でインストールしてください。
面倒な方は以下のDockerfileとshell scriptをお使いください。Dockerfileはnodeとgoを両方含んだものとなっています。shell scriptには筆者が使っているgo周りのツールも含まれているのでご注意ください。

Dockerfile
FROM node:16.13.1-bullseye as node
FROM golang:1.17.5-bullseye as golang
FROM buildpack-deps:bullseye

# nodejs
COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/npm
COPY --from=node /opt/yarn* /opt/yarn
RUN ln -fs /opt/yarn/bin/yarn /usr/local/bin/yarn && \
    ln -fs /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

# golang
COPY --from=golang /usr/local/go /usr/local/go
ENV PATH $PATH:/usr/local/go/bin

WORKDIR /terraform/cdk

COPY ./script/install_tools.sh ./script/install_tools.sh

RUN ./script/install_tools.sh

COPY go.mod .

RUN go mod tidy
./script/install_tools.sh
# terraform-cdk
apt-get update && apt-get install -y gnupg software-properties-common curl
curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add -
apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
apt-get update && apt-get install terraform=1.1.4
npm install -g cdktf-cli@0.9.0

# go
go install github.com/uudashr/gopkgs/v2/cmd/gopkgs@v2.1.2
go install github.com/ramya-rao-a/go-outline@v0.0.0-20210608161538-9736a4bde949
go install github.com/cweill/gotests/gotests@v1.6.0
go install github.com/fatih/gomodifytags@v1.16.0
go install github.com/josharian/impl@v1.1.0
go install github.com/haya14busa/goplay/cmd/goplay@v1.0.0
go install github.com/go-delve/delve/cmd/dlv@v1.7.2
GOBIN=/tmp/ go install github.com/go-delve/delve/cmd/dlv@v1.7.2 && mv /tmp/dlv $GOPATH/bin/dlv-dap
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43.0
go install golang.org/x/tools/gopls@v0.7.4

使い方

こちらの通りに進めてください。
簡潔に説明すると、

  1. 作業ディレクトリに移動し、cdktf init --template="go" --localを実行します。
  2. cdktf.json"terraformProviders""terraformModules"を指定します。AWSのみを使う場合は、"terraformProviders": ["hashicorp/aws@~> 3.74.0"]とします。
  3. cdktf getを実行します。死ぬほど遅いですが気長に待ちましょう。
  4. cdktf getで生成されたパッケージを使ってコードを書いていきます。
  5. 以下のコマンドでterraformに関する操作を行います。
    Synthesize:
      cdktf synth [stack]   Synthesize Terraform resources to cdktf.out/
    
    Diff:
      cdktf diff [stack]    Perform a diff (terraform plan) for the given stack
    
    Deploy:
      cdktf deploy [stack]  Deploy the given stack
    
    Destroy:
      cdktf destroy [stack] Destroy the given stack
    

使ってみた感想

結果を先に言うと、問題なく使用することができました。が、goはあまりオススメできません。

書きやすさ

awsのcdkと同じような感じで書けたので、学習コストはほぼゼロで書くことができました。awsのcdkを書いたことがなくてterraformを書いたことがある人は、HCLでは出てこない要素がいくつかあるので、そこだけキャッチアップする必要があります(そんな大したものではないです)。どちらも書いたことない人はそもそもの学習コストがかりますが、cdkの方が普段書き慣れている言語でかけるので、とっつきやすいかもしれません。

Goがexperimentalであることに起因するであろう不満点

型定義が曖昧な部分があり、terraformのドキュメントとにらめっこしながら求められている真の型を推測する必要がありました(具体例は躓いた箇所で示します)。コンストラクタの説明にHCLのドキュメントのリンクが貼られているのでそちらを参照しに行き、HCL上の定義と同様の型をgoで探すといったような感じです。型が違ってもsynthを行うまでエラーの検知ができないので、普段のGoの開発よりも体験が悪かったです。ただAWS CDKのGoではちゃんと型定義がされているので、terraformも改善されるかと思います。

そもそもCDKをGoで書く現状のデメリット

AWS CDKも同様なのですが、基本的に全ての値はポインタで渡す必要があります。これにより、必須パラメータかどうかがわからず、また現状terraform側のコードにはコメントでも示されていないため、ドキュメントを見に行くしかありません。コメントにリンクが貼ってあるのですぐに確認できるのですが、ブラウザとの行き来が必要となり面倒です。


subnetのを新規作成する関数。id, configは必須だがポインタ。

// Create a new {@link https://www.terraform.io/docs/providers/aws/r/subnet aws_subnet} Resource.
func vpc.NewSubnet(scope constructs.Construct, id *string, config *vpc.SubnetConfig) vpc.Subnet {

vpc.SubnetConfigの中身。長いので一部割愛。VpcIdは必須だがポインタ。ちなみにAvailabilityZoneAvailabilityZoneIdはどちらか一方が必須だがその旨もコメントにない。terraformはドキュメントが充実しているのと、全てのCDKの仕様はHCLに従う前提でそっちのドキュメントを見ろということかもしれない。

type SubnetConfig struct {
    // Docs at Terraform Registry: {@link https://www.terraform.io/docs/providers/aws/r/subnet#vpc_id Subnet#vpc_id}.
    VpcId *string `json:"vpcId" yaml:"vpcId"`
    // Docs at Terraform Registry: {@link https://www.terraform.io/docs/providers/aws/r/subnet#availability_zone Subnet#availability_zone}.
    AvailabilityZone *string `json:"availabilityZone" yaml:"availabilityZone"`
    // Docs at Terraform Registry: {@link https://www.terraform.io/docs/providers/aws/r/subnet#availability_zone_id Subnet#availability_zone_id}.
    AvailabilityZoneId *string `json:"availabilityZoneId" yaml:"availabilityZoneId"`
    // Docs at Terraform Registry: {@link https://www.terraform.io/docs/providers/aws/r/subnet#cidr_block Subnet#cidr_block}.
    CidrBlock *string `json:"cidrBlock" yaml:"cidrBlock"`
    // Docs at Terraform Registry: {@link https://www.terraform.io/docs/providers/aws/r/subnet#ipv6_cidr_block Subnet#ipv6_cidr_block}.
    Ipv6CidrBlock *string `json:"ipv6CidrBlock" yaml:"ipv6CidrBlock"`
}

これは実はどちらのCDKもTypeScript以外の言語は、jsiiというパッケージによって裏でTypeScriptを動かしていることに起因します。CDKで与えられた値は直接jsiiの関数に与えられており、Goではその引数としてポインタしか受け付けないようです(要出典)。一段ラップしてくれれば引数にポインタ以外も受け付けるようにできそうなんですが難しいんですかね。AWS CDKでは全てポインタの状態でリリースされていることから、terraformでも同様になると思われます。
ちなみにTypeScript, PythonではOptionalの有無で必須かどうかが区別されています。
またjsiiを使っていることからオリジナルはTypeScriptであるため、特段理由がなければどちらのCDKでもTypeScriptを使うのが良いかもしれません。

躓いた箇所とその補足

型の曖昧さ

感想で述べたように、執筆時点では型が曖昧な部分が多数あります。以下は代表的な例です。

terraformの型 goの型
bool interface{}
list interface{}
string hoge.Json()のパターンあり

具体例

iam

field displayed actual
iam.IamRoleConfig.AssumeRolePolicy *string iam.DataAwsIamPolicyDocument.Json()
ian.IamRoleConfig.InlinePolicy interface{} []iam.IamRoleInlinePolicy
iam.IamRoleInlinePolicy.Policy *string iam.NewDataAwsIamPolicyDocument.Json()
iam.DataAwsIamPolicyDocumentConfig.Statement interface{} []iam.DataAwsIamPolicyDocumentStatement
iam.DataAwsIamPolicyDocumentStatement.Condition interface{} []iam.DataAwsIamPolicyDocumentStatementCondition

Discussion