🌏

IaC(Terraform / Terragrunt)を1年以上運用してきた所感

に公開

こんにちは、KANNA の SRE チームの okenak です。

インフラの運用を続けていると、
「この設定、どこで入れたんだっけ?」
「本番を触るのが怖い」
といった状況に直面することがあると思います。

本記事では、Terraform / Terragrunt を使った IaC を1年以上運用してきた中で、
そうした状況がどう変わったか、またどんなつらさがあったかを整理しています。

IaC / Terraform / Terragrunt について(補足)

IaC はインフラ構成をコードで管理する考え方で、Terraform はそれを実現する代表的なツールです。
Terragrunt は Terraform をマルチプロダクト構成でも運用しやすくするための補助ツールです。


IaCに慣れると、もう戻れない

IaC を本格的に運用するようになってから、正直なところ IaC なしのインフラ運用には戻れない と感じています。

コードを見れば、現在のインフラ構成や設定内容を把握できる、という状態は、想像以上に心理的な安心感があります。

一方、手動オペレーション中心の運用では、

  • どこにどの設定が入っているのか分からなくなる
  • 環境ごとに設定が微妙にズレる
  • 「触ってはいけない雰囲気」だけが蓄積される

といった状態になりがちで、結果として運用がカオス化し、うかつに手を入れること自体がリスクになっていました。


IaCとAIの相性は思っていたより良かった

IaC はコードである以上、生成AIとの相性はかなり良いと感じています。

Claude Code などの生成AIツールを使って Terraform / Terragrunt のコードを書かせることもありますが、ペアプロ的な使い方であれば十分実用範囲だと感じています。

もちろん、ベストプラクティス的にはツッコミどころのあるコードが出てくることも多いです。ただ、

  • 公式ドキュメントや最新仕様をコンテキストとして与える
  • 生成されたコードを前提に、人間がレビューする

という前提であれば、設計のたたき台としてはかなり助けられています。

一方で、気を抜くと普通に変なコードやセキュリティ上問題のある設定は生まれるので、IaCにおいてレビューが不要になることはない、という点は強く感じています。


IaCならではのつらさ

IaCを使っていると、ダッシュボード操作では意識しなくてよかった部分まで、きちんと理解する必要が出てきます。

  • 各リソースのパラメータ
  • リソース同士の依存関係
  • 暗黙的に設定されている挙動

正直、最初はつらいです。ただ、長期的に見ると「ちゃんと理解した方がいい領域」でもあるので、必ずしもデメリットとは言い切れないとも感じています。

また、以下のような特殊なケースでは IaC だけで完結できない場面も発生します。

  • スナップショットからの DB リストア
  • ALB リスナールールの一時的な切り替え
  • 緊急対応時の一時的なスケール変更や設定変更

これらは一度手動で操作した後、terraform import などで state を合わせ直す必要があります。

手順を誤ると destroy / create が走ってしまうリスクもあるため、特に prod 環境では慎重に手順を検証してから行う必要があります。


lifecycle ignore_changes の使いどころ

IaC で管理しにくい「動的に変化する値」については、lifecycle.ignore_changes を活用しています。

resource "aws_ecs_service" "this" {
  # ... 省略 ...

  lifecycle {
    ignore_changes = [
      desired_count,   # AutoScaleで動的に変更されるため
      load_balancer,   # Blue/Green時にCodeDeploy側で切り替わるため
      task_definition, # CodeDeploy側で動的に切り替わるため
    ]
  }
}

このパターンを使わないと、apply のたびに差分が出たり、意図しない変更が入ったりします。

「IaC で管理する領域」と「動的に変化させる領域」を明確に分けることが、運用上かなり重要だと感じています。


複数人運用と認知負荷

複数人で同じ IaC を触る場合、コンテキスト理解のコストはそれなりに高くなります。
誰が、どの範囲を、どういう意図で変更しているのかを把握しないと、安全に手を入れられません。

その点では、Terragrunt で state を分離できているおかげで、作業範囲の衝突はかなり減りました。
完全に楽とは言えませんが、「致命的に怖い」状態からは脱却できた感覚があります。


モジュール分割設計はいまだに難しい

モジュール設計については、正直いまでも明確な正解は分かっていません。

汎用化しすぎると引数や条件分岐が増えて見通しが悪くなり、
特化しすぎると変更に弱くなります。

そのため現在は、モジュールで無理に DRY を担保しない という割り切りをしています。
環境差分や細かな設定値は Terragrunt の階層的な設定に寄せ、
Terraform モジュールは「構造が本当に共通な部分」だけを切り出す方針です。

modules/
  ecs/
    service/
      alb-blue-green/
      alb-inplace/
      without-alb/
    task-definition/
      kanna-project/
      kanna-payment/
      kanna-report/
  alb/
    kanna-project/
    kanna-payment/

ECS Service は構成がほぼ共通なため汎用モジュール化し、
Task Definition は差分が大きいためプロダクト別にしています。

この「汎用 vs 特化」の判断は変更頻度と差分の性質を見ながら行っていますが、
今後も揺れ続けるポイントだと思っています。


Terragruntは「規模が出た瞬間」に効いてくる

環境やプロダクトの数が増えてくると、
「どの設定がどこで定義されているのか」を把握するだけでも大きな負担になります。

Terragrunt では、設定を階層的に分離することで、
環境ごと・プロダクトごとの違いを一箇所に集約し、
各リソースでは必要最小限の設定だけを書くことができます。

階層的な設定ファイルによるDRY

aws/kanna-platform/
  root.hcl
  environment/
    aldagram-prod/
      ap-northeast-1/
        prod/
          environment.hcl
          kanna-project/
            product.hcl
            ecs/
              service/
                api/
                  terragrunt.hcl

この構成では、

  • root.hcl:全環境共通の設定
  • environment.hcl:環境ごとに異なる設定
  • product.hcl:プロダクト固有の設定
  • terragrunt.hcl:リソース固有の設定

という役割分担になっており、
root.hcl → environment.hcl → product.hcl → terragrunt.hcl の順で設定を積み上げています。

その結果、各 terragrunt.hcl には「そのリソースで本当に必要な設定」だけを書けばよくなり、環境数が増えても管理コストが大きく増えません。

ワークスペース名の自動生成

Terraform Cloud を使った運用では、ワークスペース名の管理が地味に効いてきます。

環境やプロダクトの数が増えると、
「このワークスペースはどの環境・どのリソースに対応しているのか」を
人間が名前で管理するのは現実的ではなくなります。

そこで、Terragrunt ではディレクトリ構造からワークスペース名を自動生成しています。

locals {
  workspace_name = join("_", compact(split("/", get_path_from_repo_root())))
}

この仕組みにより、ディレクトリとワークスペースが常に 1 対 1 で対応し、
名前の付け忘れや命名ルールのブレを気にせずに済むようになりました。

環境やリソースが増えても、
「どのワークスペースを触っているのか」を迷いにくくなる点は、
規模が出たときに特に効いています。

依存関係の明示的な管理

dependency "vpc" {
  config_path = "${get_parent_terragrunt_dir("product")}/../none-product/vpc"
}

dependency "ecs_cluster" {
  config_path = "${get_parent_terragrunt_dir("product")}/ecs/cluster"
}

dependency "alb" {
  config_path = "${get_parent_terragrunt_dir("product")}/alb/api"
}

環境やリソース数が増えると、「どの順番で apply すべきか」を人間が意識するのは現実的ではなくなります。
run-all 実行時に依存関係が解決され、正しい順序で apply されます。


環境数が多くても回せている理由

現在は、以下のような環境構成で運用しています。

  • prod / pre-prod / loadtest
  • dev / qa1〜qa15 / shared

shared には RDS や ElastiCache など、複数 QA 環境で共有するリソースを配置しています。
環境数は多いですが、Terragrunt のおかげで運用コストはそこまで増えていません。


Terraform Cloud(Free Plan)+Terragruntのつらみ

Terraform Cloud(Free Plan)と Terragrunt を組み合わせた運用では、つらい点もありました。

Terragrunt で多数の workspace を一括実行する構成では、
Terraform の実行ごとに provider の初期化処理が走り、
その際に取得される discovery document の回数が短時間に集中します。
結果として rate limit に達し、実行が不安定になるケースがありました。

また、

  • 旧 Free Plan を継続利用している
  • 既存リソース数が新 Free Plan の上限を超えており、移行できない
  • 有料プランは月額が高く、現実的ではない

といった背景もあり、現在は S3 backend への移行を検討しています。
正直なところ、最初から S3 を選んでおいてもよかったかもしれません。


おわりに

IaC(Terraform / Terragrunt)は、学習コストや運用上のつらさは確実にあります。
それでも、1年以上運用してきた今の感覚としては、「それを差し引いても十分に価値がある」と感じています。

20環境・5プロダクトという規模を Terraform 単体で管理しようとすると、かなり厳しかったと思います。
Terragrunt による state 分離と階層的な設定管理があってこそ、現実的な運用ができています。

ただし、今のやり方が最適かどうかは分かりません。
このあたりは、これからも試行錯誤しながらアップデートしていくつもりです。

アルダグラム Tech Blog

Discussion