CDK for Terraformを試してみた所感

はじめに

  • Terraformの利用経験はありますが、v0.12のころなので、前提知識が古いです。
  • CDK for Terraformの正式名称がterraform-cdkなのかCDK for Terraformなのかよくわからないので以下CDKTFに統一します。
  • CDKTF を試しに少し利用してみた程度で感じた所感なので、あまり鵜呑みにしないことをおすすめします。

CDKTF

上記の通り、わたしが Terraform を利用していたのはv0.12のころでした(2 年前ぐらい?)。

つい最近、Terraform を改めて触る機会があり、調べてみると

ということで、進化に驚きつつも過去に Terraform を触った時に感じた様々な辛みを思い出し、何も迷うことなく CDKTF を選択したのでした。

Terraform がつらかったこと

こんな記事をご覧の方はすでにご存知かと思いますが、Terraform は「Infrastructure as Code」を実現するためのツールで、クラウドのリソース構成をソースコードで管理できる優れものです。

しかも OSS として提供されており、

  • ベンダーロックインなく
  • AWS や GCP といったクラウドプラットフォームだけでなく様々な SaaS にも対応している
  • その気になれば自力でプロバイダも作成できる

といった、とんでもないツールです。これが OSS として提供されていることがすごい。

……のですが、実際にこれで管理してみると思いのほか落とし穴が多く、

  • 最初に決めたディレクトリ構成が今後の管理・メンテナンスコストに大きく影響する
  • ディレクトリ構成についてベストプラクティスと称されるドキュメントがあるものの、本当にこれがベストプラクティスかは疑問が残る
  • ベンダーロックインしない代わりにデプロイされるまで無事にデプロイできるかわからない

というように、インフラの構成がソースコードで見える代わりにその管理手法については初期の頃からずっと先人たちが試行錯誤を重ねています(実際、「terraform ベストプラクティス」で検索するといまでも新しい記事がバンバン出てくる上に意見がバラバラです)。

なぜディレクトリ構成が重要かと言えば、Terraform はディレクトリごとにリソースの塊を持つ仕組みになっているので(デプロイもディレクトリごとに実行します)、運用後のメンテナンスも見据えたディレクトリ構成を考え抜かないとせっかくコード化したのにメンテできない代物が仕上がってしまいます。

実際、わたしもv0.12のころにサーバーレスなアプリを作るのに Terraform を利用しましたが、このあたりがかなり大変だった記憶があります。

もちろん当時も上記のTerraform Best Practicesは目を通していて、環境ごとにリソース差異が発生するのは予想できたので、Workspace は使わず環境ごとにディレクトリを分ける ↓ の構成を選択しました。

.
├── environments
│   ├── dev
│   ├── prod
│   └── stg
└── modules
    ├── module1
    ├── module2
    └── ...

実際、コーディングしている間はこれでも全然良かったんです。

ですが、環境ごとに同じソースコードが分離するため、サーバーレスなのでわりと小規模なアプリだったにも関わらず、次第にワケが分からなくなってしまいました。

また、モジュールについても使いどころが難しく、モジュールは再利用のために定義するものですが、これに慣れるとリファクタリングしたくなる病が発生し、再利用性を高めようとがんばればがんばるほど意味不明なソースコードができあがってしまいます。

こういったことから、当時、「変数定義がもうちょっと柔軟になって、関数が定義できればもうちょっとやりやすくなりそうなのになあ……」と思ったことが強く印象に残っています。

なので CDKTF を見た時、Terraform を TypeScript で書けるのであれば、当時感じたつらい部分は大きく解消できると思いました。

幸い、TypeScript を書くのはそこまで不自由しないので、選ばない理由がありませんでした。

あらためて CDKTF を使ってみて

めちゃくちゃ感動しました。

Terraform は宣言的と言われ、とにかくインフラの定義を書き連ねていくので画面で設定していく作業を文字列に落とし込んでいくのですが、CDKTF ではプログラミングの感覚で書けるので、まさにこういうものがほしかった! と思いながら書いていました。実際、めちゃ楽しかったです

TypeScript になったことで変数もかなり柔軟に定義できますし、当然、関数も定義できます。

Stack 定義はクラスで書く必要があるものの、各リソースはクラスの中でないと定義できないわけではないので、そのあたりは特に気になりませんでした。

ところが「あれ?」と思ったのはある程度、ソースコードが肥大化してからでした。

いざ使ってみると、非常に利便性が高まった部分もあれば、結局 Terraform と何ら変わらなかったり、むしろ悪化しているところもあったりと、これはこれで難しいと感じるようになってしまいました。

ここからがようやく本題ですが、わたしが CDKTF を使ってみて感じたことを列挙します。

CDKTF でよくなったこと、解決できそうなこと

まずはポジティブな面から挙げていきます。

TypeScript で書ける

とにかく TypeScript やその他の汎用言語で書けることが一番大きなメリットでしょう。

Terraform だと HCL2 という専用言語を使いますが、配列だったりマップ変数を定義したり取り扱うのに書き方がわからなくなってドキュメントを探しに行くことがしょっちゅうありました。

その点、 CDKTF だと使い慣れた言語で構築できるので、書き方がわからずに困ることはありません。

Terraform を使い上はリソースの定義に注力したいので、HCL2 の文法で引っかかりたくはないのが本音です。

変数や関数が HCL2 より柔軟に定義できる

そもそも HCL2 は関数定義ができませんし、条件分岐もありません(countを使うという手段はありますが……)。変数定義はできますが、自由度はそこまで高くありません。

それに比べ、TypeScript は型定義が柔軟なので安全な変数定義もできますし、もちろん関数定義もできるので、HCL2 と比べてかなり柔軟なコーディングを実現できます。

(こういった事情から CDKTF を使うのに動的型付け言語はおすすめしづらいです。それなら HCL2 でいいのでは? と率直に思います)

ディレクトリ構成を気にしなくて良い

Terraform でリソース管理する時に付きまとってくる大きな問題のひとつがやはりディレクトリ構成でしょう。

環境ごとに差異が発生するリソースをどのように管理するか、という問題もありますし、仕組み上、サブディレクトリも切れないので煩雑になってしまいます。

それに比べ、CDKTF であれば.envも使えますし、変数や関数、条件分岐で環境ごとのリソースの差異はかんたんに吸収できます。

ディレクトリを環境ごとに分離する必要もなくなり、かつソースコードをひとつにまとめられるので、非常にスッキリします。

したがって、CDKTF を活用することで、冒頭で挙げたディレクトリ構成が運用に大きく影響する問題は完全に解消されます。

テストが書ける

CDKTF の大きなメリットで、かつ Terraform では実現できないものがテストコードです。

といっても結局はデプロイできるかどうかは実際にapplyしなければわかりませんので絶対の安全を担保してくれるものではありませんが、それでもテストコードがあることで

  • 必要なリソースの有無が確認できる
  • テストコードを仕様にできる

などなど、他のメンテナにも有用な情報を共有できるようになります。これは Terraform にはない大きな利点でしょう。

CDKTF でも解決できないと感じたこと

反面、いくつかあまり解決に至らなかったものもありました。

凝りだすとわけのわからないものができあがる

TypeScript だったり他の汎用言語だったとしても Terraform でリソース管理をしていることに違いはなく、結局はリソースの定義をつらつら書き連ねていくことになります。

ところが汎用言語で書けるせいか、HCL2 で書くよりもリファクタリングしたくなる病に陥りやすく、その調子で分離していくと最終的には何がどうつながっているのかまったくわからないものができあがってしまいます。

しかも汎用言語のほうが HCL2 より表現力が高いために、ひとによって書き方が大きく異なってしまう可能性もあります。

CDKTF を選択した場合、ディレクトリ構成を検討する比重が大きく下がる代わりにコーディングルールをしっかり定めないとだれも理解できない代物ができあがるかもしれません。

型定義がビミョー

わたしは TypeScript で CDKTF を利用していましたが、CDKTF の型定義はビミョーすぎます(と言ってもこれはプロバイダの問題だと思いますが……)。

特定の文字列を設定する属性が結構多いですが、すべて string 型でした。Union 型にしてくれればドキュメントを見る頻度も下げられて最高なのですが……。

なので、対象のプロバイダのドキュメントを見て、何を入れられるのか確認した上で記述しなければならないのは Terraform と変わりません。

むしろ(言語次第ですが)コーディングスタイル(camelCase と snake_case)で検索しづらくなるという問題も地味につらいです。

CDKTF で悪化したこと

残念ながら、逆に大きく悪化したと感じた部分もありました。

CDKTF ではデプロイ周りが整備されていないのか、そのあたりが Terraform と比べて大きく利便性が劣ると感じました。

デプロイまでが遅すぎる

TypeScript だからかもしれませんが、めちゃくちゃ遅いです。

TypeScript の場合、tscでコンパイルしてから Terraform コマンドが実行されるので、

  1. コンパイルに数十秒〜数分 → ここで TypeScript のエラーが発生すれば書き直し
  2. コンパイル完了後に Terraform コマンド → ここでようやく Terraform のエラーがわかる

ということで Terraform と比べて尋常じゃない時間が取られます。

tsc自体が遅いので仕方ないのかもしれませんが……。

トライアンドエラーなく Terraform でがんがんリソース定義できる達人もいらっしゃると思いますが、少なくともわたしはトライアンドエラーで実装しますので「planはうまくいったのにapplyでコケた」みたいなことが頻発するとやる気がなくなります。

エラーが表示されない

バグなのか仕様なのかわかりませんが、エラー対象のリソースが複数ある場合、ターミナルの描画でエラーメッセージが削除され、何が原因で失敗したのかわかりません。

これについては現状、どうもログファイルを出力して確認するしかないらしく、

{
  "scripts": {
    "deploy": "npm run compile && CDKTF_DISABLE_LOGGING=false cdktf --log-level DEBUG deploy"
  }
}

とデプロイ用のコマンドを追加して、ログファイルを出力してエラーチェックしていました。

まとめ

以上のことから

  • Terraform に精通し、あまりトライアンドエラーをしない
  • HCL2 の表現力に不満が高まっている

といった方にはものすごくメリットがあると思いますが、わたしにはメリットよりもデメリットのほうが影響度合いが強いと感じたため、CDKTF の利用は中断し、Terraform に切り替える決断をしました。

とはいえ CDKTF はまだまだ発展途上であり、現在進行系でものすごい勢いでバージョンアップされているので、今後に期待したいと思います!

もし CDKTF か Terraform かで悩んでいる方がいらっしゃるなら、参考になれば幸いです。