
Infrastructure as Code(IaC)は一筋縄ではいきません。インフラを構築する必要に迫られ(最近ではTerraformを使うのが一般的です)、目の前のユースケースに合わせてコードを書き始める——そんな場面が多いはずです。
たいていは時間に追われ、デフォルト値はすべてハードコーディング、作業はまるごと1〜2個の巨大なファイルに押し込む、という結末になりがちです。「とりあえず今動かしておいて、余裕ができたら直そう」——多くの人がそう考えるのではないでしょうか。
今度こそ、正しいやり方でインフラをTerraform化する。
そのうち別のタスクが舞い込んできます。その頃には「とりあえず動かす」ために何をしたか、自分でもすっかり忘れています。あるいは、別のメンバーがコードを読み解き、これまでのタスクと新しいタスクの両方をこなせるよう手を入れる必要が出てきます。
そして「もっと良くしたい」という気持ちの裏には、いま動いているものを壊してしまうのではないかという、強烈な恐怖が常につきまといます。
私自身、何度もこの道を歩んできましたが、Terraformをもう少し扱いやすく、デバッグしやすくする術はなかなか見つかりませんでした。Yevgeniy Birkman氏のブログ記事「5 lessons learned from writing over 300,000 lines of infrastructure code」に出会うまでは。
この記事のおかげで、Terraformをより堅牢に、よりクリーンに、より扱いやすくする方法、そして何より、必要な変更や改善に踏み切る自信を得る方法が、はっきりと見えてきました。
本記事では、Terraformのリファクタリングについて、私自身のアドバイスと経験をお伝えします。以下は、記事末尾に掲載するスライドの要約です。
モノリシックなTerraform
巨大なTFファイルでの作業は、ささいなミス1つですべてが崩壊しかねません。デバッグもぐっと難しくなり、手を入れるべき箇所を探すだけでも膨大な時間がかかります。最終的にはコードの重複が増え、開発サイクルが目に見えて遅くなってしまうのです。
1万フィートの俯瞰アプローチ
開発中であれ、重大な問題のデバッグ中であれ、目当てのものをすぐ見つけられるかどうかは、Terraform運用の生命線です。
Terraform planはすべてのファイルを1回の実行にまとめてくれます。これを活かさない手はありません。ファイルを小さく分けることで、見通しは格段に良くなります。こうした小さなファイルは、自然と再利用性が高く組み合わせやすいモジュールへと育っていきます。
モジュールの構造
「30万行のインフラコード」は素晴らしい出発点でしたが、私はそこからさらに一歩進めて、すべてのモジュールで共通して使うスキャフォールド(雛形)を用意しました。このスキャフォールドにより、モジュール開発の見通しと指針がはっきりします。
ハードコーディングされた値は一切なし。元のハードコード値はすべてデフォルト変数になり、モジュールの属性はすべて変数化します(必須のものもあれば、デフォルト値付きのものもあります)。すべてのモジュールは、次の構造に従うべきです。

要素は、あるべき場所に置く——これが基本方針です。メインの.tfファイルが30行を超えたら、ec2.tf、autoscaling.tfのように論理的な単位でファイルを分割しましょう。
おすすめなのは、'examples'フォルダをモジュール開発に活用し、同時に将来のための利用例として残しておくことです。新しいモジュールを始めるときは、私は次のスニペットでスキャフォールドを生成しています。
➜ export module_name="sample"➜ mkdir -p $module_name/examples $module_name/test➜ cd $module_name && touch \ main.tf \ versions.tf \ default-variables.tf \ required-variables.tf \ outputs.tf \ data.tf3層モジュール構造
まずは、プリミティブなビルディングブロック(terraform-resources)のライブラリを自前で構築し、育てていきます。

次に、そのプリミティブを組み合わせてサービスを作ります(terraform-services)。

そして、サービスを束ねてエンドツーエンドの環境をデプロイします(terraform-live-envs)。

狙いは、各(ライブ)環境(dev、staging、production)をきっちり分離し、その中の各コンポーネントを汎用的なサービスモジュールに切り出すこと。さらに、各サービスモジュールをリソースモジュールに分解します。1つのモジュールには1つの役割だけを持たせ、可能な限り疎結合に保つためです。
具体例を見てみましょう。
Google Cloud SQL上にデプロイされたmySQLインスタンス(terraform-live-envs)があるとします。これは「sql」という汎用サービスの上に構築されており、その中には2つのリソースモジュール(instanceとuser)が含まれています。

Terraformコードのリファクタリング
まず、新しいTerraform stateを保存するためのバケットを作成します。次に、上述の図とスライドに沿って、コードを3層モジュール構成に書き換えます。そして、各リソースをlive-envsのTerraformコードにインポートしましょう。
するとTerraformは、インポート操作の実行プランを表示します。
[1] デプロイ済みのバージョンには存在するが、コードには存在しない値は、削除を意味するマイナス記号付きで表示されます。
[2] デプロイ済みのバージョンには存在しないが、コードには存在する値は、追加を意味するプラス記号付きで表示されます。
目指すべきゴールは、プランに変更が一切表示されない状態に到達することです。
スライド
以下のスライドでは、Terraformの簡単な紹介と、既存のTerraformモジュールをリファクタリングするための提案をまとめています。