1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraform (Terragrunt) スタック分割運用 (1/3) dependency宣言とCI実行順序の自動生成

1
Last updated at Posted at 2026-07-29

0. はじめに ~背景と課題~

Terraformを使い続けていると、stateはどんどん大きくなっていきます。
planを実行するたびに全体がrefreshされ、一箇所直したいだけなのに関係のないリソースまで対象に入ってきます。そこでstateを分割します。ライフサイクルや責務が異なるものを切り離していけば、一つひとつは小さくなります。

私たちの部署は複数の業務を担当しています。業務ごとにAWS環境を分けて管理するため、OUを分割しました。環境ごとに独立した構成を組めて、OU単位でコストも把握できます。

ただ、アカウントを分けただけでは足りませんでした。同じアカウントの中でも、ネットワークとアプリケーションが同じタイミングで変わることはありません。そこでstateをさらに分割しました。運用を重ねた結果、ドメインで切り出したものが14個、アプリケーションをアプリ単位でさらに分けたものが5個、合わせて19個になっています。

stateが一つのうちは単純ですが、分割すると新たに二つの課題が生まれます。スタック間で値を渡すことと、どの順序でapplyするかを決めることです。

値を渡すほうにはterraform_remote_stateという標準的な手段があります。しかし実行順序を定義するほうには、標準と呼べる手段がありません。

この記事では、その順序をコードに落とし込むまでに検討した過程と、たどり着いた結論を説明します。いくつかの案を比べたうえで私たちのチームが選んだのは、Terragruntのdependencyで宣言し、その宣言からGitLab CIパイプラインを生成する方法でした。

リポジトリの構成は次のとおりです。

terraform/
├── root.hcl                      backend・providerの生成ルール
├── common.hcl                    全アカウント共通の変数
├── modules/                      再利用モジュール
└── environments/
    ├── common-ou/
    │   ├── env.hcl               アカウント固有の変数
    │   ├── 01-network-security/
    │   │   ├── terragrunt.hcl    依存の宣言と入力値
    │   │   ├── main.tf
    │   │   ├── variables.tf
    │   │   ├── outputs.tf
    │   │   ├── data.tf
    │   │   ├── backend.tf        Terragruntが生成
    │   │   └── provider.tf       Terragruntが生成
    │   ├── ...
    │   └── 06-applications/
    │       ├── gitlab-ec2/
    │       ├── sonarqube/
    │       └── ...
    └── o11y-ou/
        └── ...

1スタックを1ディレクトリで構成し、その中のterragrunt.hclがこの記事の中心になります。backend.tfprovider.tfは人が書くのではなく、Terragruntが生成したものです。生成物ではありますが、このリポジトリではコミットしています。

一部のOUの具体的なスタック構成が次の図です。おおまかな依存順序は番号で管理しており、アプリケーションのようにさらに分ける必要があるところは、下位スタックとしてもう一段分けています。

stateの構成

1. state分割にともなう実行順序の問題

terraform_remote_stateは次のように書きます。

data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "..."
    key    = "environments/common-ou/01-network-security/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

このデータソースが読むのは、すでに存在するstateです。ネットワークスタックがまだapplyされていなければ、読む先そのものがありません。読み取りが成立するには順序が先に守られている必要がありますが、その順序を保証する仕組みはコードのどこにもありません。

コードが表しているのは「このstateを読む」ことであって、「このスタックを先にapplyしなければならない」ことではありません。

terraform_remote_stateが運ぶもの

スタックを分けたのは、互いに絡み合う範囲を狭めるためでした。ところが19個のスタックをこの形で運用するうちに、分けたことでかえって次の四つの問題を抱えることになりました。

症状 原因
どのスタックを先に実行すべきかが担当者の頭の中にしかない 順序がコードにない
変更に着手する前に影響範囲を見積もれない どのスタックがどのスタックを参照しているかが機械可読な形で存在しない
無関係に見える変更が稼働中のものに影響する 同上
変更の大きさに関係なくパイプラインが一時間を超える 影響範囲を計算できず全量を実行するため

そのため、担当者がコード全体を把握したうえでapply順序を手で決めるしかなく、CIで自動化するには限界がありました。結局CIパイプラインは、毎回全体をplanしてapplyする形で回すことになります。IAM権限を一つ追加するために一時間以上待ったこともありました。

2. 既存事例の調査

同じ問題を扱った資料を、値の受け渡しと実行順序という二つの軸で整理します。

2.1 値の受け渡し

公開されている事例のほとんどはterraform_remote_stateを使っています。

事例 分割の基準 値の受け渡し
Terraform stateを4分割した 責務。lifecycleが異なるもの、依存方向が一方向のもの、一緒に変わるもの terraform_remote_state
tfstateを分割管理するためのTips ライフサイクル terraform_remote_state。id / arnなど最小限の情報だけを公開
terraform stateのちょうどいい単位は? 分割単位の議論 terraform_remote_state、または命名規則を統一してdata sourceで取得

ただし、HashiCorpの公式ドキュメントはこの方式を推奨していません。

When possible, we recommend explicitly publishing data for external consumption to a separate location instead of accessing it via remote state.

理由は権限の範囲にあります。

any user or server which has enough access to read the root module output values will also always have access to the full state snapshot data by direct network requests.

terraform_remote_stateが公開するのはoutputだけに見えますが、実際には消費する側がstateスナップショット全体への読み取り権限を持ちます。stateの中にはDBのパスワードや鍵がそのまま入っています。公式ドキュメントは代替として、SSM Parameter Store、Consul KV、S3、Route53、Kubernetes ConfigMapといった別の保管先を挙げています。

Beyond terraform_remote_stateは、選択肢を五つに整理しています。

方式 特徴 代償
terraform_remote_state Terraform標準で使える state全体への読み取り権限。バックエンドの座標が変わると消費側がすべて壊れる
provider data source タグや名前でクラウドAPIを直接照会する 対象がなければ失敗する。順序依存がかえって生まれる
tfe_outputs state全体を公開しない HCP Terraform専用
SSM Parameter Store / Consul KV 細かい権限制御と監査ログ 共有する値ごとにリソースが増える
命名規則モジュール 照会そのものが不要になる 組織での合意が必要。生成される識別子には使えない

ここで注目したいのは、どの方式も実行順序を作らないという点です。provider data sourceにいたっては、順序依存をむしろ新たに生みます。値の受け渡し手段を変えたところで、1節の問題は解決しません。

2.2 実行順序

同じ記事は、順序の問題をこう書いています。

That ordering dependency lives in your head, in a wiki, or in a CI/CD pipeline. Terraform doesn't track it for you.

頭の中であれWikiであれCIパイプラインであれ、Terraformの外のどこかにあり、Terraform自身は追跡しないということです。この記事は限界を指摘するにとどまり、解法までは示していません。

GruntworkのTerragruntアーキテクチャの議論も、stateを分割する基準(性能・信頼性・セキュリティ)は扱っていますが、CIパイプラインの順序には触れていません。

順序を実際に扱うツールは、依存関係をDAG(Directed Acyclic Graph、有向非巡回グラフ)で表現します。スタック一つを点として置き、「AはBより先」を矢印でつないだ図です。矢印をたどり続けても出発した点に戻ってこないことが条件で、これが守られているときにだけ実行順序を一つに定められます。互いが互いを待っていては、どちらも先にはなれないからです。

ツールによって違うのは、このグラフをどこに書いておくかです。

ツール 順序の決め方 宣言の場所
Terragrunt dependencyでDAGを組み立て、自動で解決する コード
Spacelift 依存先スタックのrunが終わるまで待機し、失敗するとチェーン全体をキャンセルする UIのDependenciesタブ
Terramate ファイルシステムの階層。親スタックが子より先 ディレクトリ構造
Atlantis execution order groupを人が指定する 設定ファイル

同じ依存グラフ、異なる保管場所

Spaceliftは出力値を環境変数として渡すところまで面倒を見てくれますが、依存の宣言がコードではなくUIにあります。Terramateは順序がディレクトリ階層に縛られるため、階層と依存が食い違う場合には別途設定が必要です。そして、どちらも専用プラットフォームの導入が前提になります。

2.3 三つの選択肢

整理すると、選択肢は三つです。

  1. CIに順序を人が書く — 先に挙げた事例、Atlantis
  2. 専用プラットフォームに順序を管理させる — Spacelift、Terramate
  3. コードの依存宣言からCIパイプラインを生成する

一つ目は、依存関係が二箇所にできてしまいます。コードのterraform_remote_stateと、CIのジョブ順序です。スタックを追加するたびに両方を直すことになり、片方だけ直せば食い違います。しかもその食い違いは、applyが失敗して初めて表に出ます。

二つ目はプラットフォームの導入が前提です。すでにGitLab CIで他のパイプラインを回している以上、IaCのためだけに別のプラットフォームを足す選択はしませんでした。

残る三つ目を選びました。値の受け渡しと順序をdependency一つにまとめ、その宣言をCIが読んでパイプラインを組み立てます。順序を書く場所が一箇所に減ります。

dependencyで値を受け取るようになったことで、terraform_remote_stateの出番はほとんどなくなりました。2.1で見た公式の推奨に、結果として近づいたことになります。

3. dependency宣言による値の受け渡しと順序の統合

実際のスタック設定が次のコードです。

# 02-vpc-endpoints/terragrunt.hcl
include "root" {
  path = find_in_parent_folders("root.hcl")
}

locals {
  common = read_terragrunt_config(find_in_parent_folders("common.hcl"))
  env    = read_terragrunt_config(find_in_parent_folders("env.hcl"))
}

dependency "network" {
  config_path = "../01-network-security"
}

inputs = {
  aws_region      = local.common.locals.aws_region
  assume_role_arn = local.env.locals.assume_role_arn
  tags            = local.common.locals.tags

  vpc_id             = dependency.network.outputs.vpc_id
  private_subnet_ids = dependency.network.outputs.private_subnet_ids
  vpc_endpoint_sg_id = dependency.network.outputs.vpc_endpoint_sg_id
}

ここで使っているincludefind_in_parent_folders()read_terragrunt_config()dependencyは、いずれもTerragruntが提供するものでTerraformにはありません。前の三つは上位ディレクトリの共通設定を探して取り込むためのもので、最後のdependencyがこの記事の主題です。

このdependencyブロック一つが、二つの仕事をこなします。他のスタックの出力値を取得することと、そのスタックを先に実行させることです。

1節ではコードに残らなかった「このスタックを先にapplyしなければならない」という情報が、値を受け取るコードの中に同居します。順序を別の場所に書く必要がなくなりました。

dependencyブロック一つが生む二つのもの

現在、19個のスタックにこの宣言が32箇所あり、これがそのまま実行順序を決めています。

ローカルであれば--all一つで済みます。

cd terraform/environments/common-ou
terragrunt plan --all

なお、旧バージョンのterragrunt run-allは廃止されました。現在はterragrunt run --all planが正式な形で、上に書いたterragrunt plan --allはその短縮形です。

ただし、この方式にも制約があります。dependencyはDAGを組み立てるため、互いを参照し合うスタック同士には使えません。実際に二件が該当しており、これは6節で扱います。

4. トポロジカルソートによるCIパイプライン生成

ローカルであればここまでで済みますが、CIはそうはいきません。GitLab CIはterragrunt.hclを読まないからです。

まず思いつくのは、ジョブ一つで--allを実行する方法です。しかしこれでは19個のスタックが一かたまりになってしまいます。どのスタックで失敗したのかログを追う羽目になり、一つのスタックだけを再実行することもできず、ジョブ一つの時間制限に全体が縛られます。

次に考えられるのは、スタックごとにジョブを分ける方法です。今度は、順序をCIが知っていなければならないという問題が出てきます。先に見た事例がここで採ったのは、ワークフローにスタックの順序を人が直接並べる方法でした。ただしこの形では、スタックを追加するたびにCI定義も直すことになります。

GitLab CIには、この問題を回避できる機能があります。パイプラインが別のパイプラインを生成して起動するもので、子パイプライン(child pipeline)と呼びます。親パイプラインのジョブがパイプライン定義をYAMLファイルとして出力しアーティファクトに置くと、そのアーティファクトを指すトリガージョブが子パイプラインを起動します。子のジョブ構成はリポジトリのどこにも書かれておらず、パイプラインが走るたびに組み立てられます。

親パイプラインと子パイプライン

こうなると、CI側に残る仕事は一つに絞られます。何をどの順序で実行するかを計算し、YAMLとして出力することです。

私たちが選んだのは、その順序を人が書かずにdependency宣言から生成する方法です。スクリプトが各terragrunt.hclからconfig_pathを抽出し、グラフを組み立てます。

pattern = r'dependency\s+"[^"]*"\s*\{[^}]*config_path\s*=\s*"([^"]+)"'

そしてこのグラフをKahnのアルゴリズムでトポロジカルソートします。動作は単純です。

  1. 入ってくる辺がないノードをすべて探します。何も待つ必要のないスタックです。
  2. そのノードを結果に入れ、そこから出ていく辺を削除します。
  3. 辺を削除した結果、入ってくる辺が0になったノードをあらためて集めます。
  4. 取り出せるノードがなくなるまで繰り返します。

Kahnのアルゴリズムの動作

一度に取り出したノードのかたまりが、そのまま一つのレイヤーになります。互いに待つ必要がないので同時に実行できます。このレイヤーをそのままGitLabのステージに変換します。

実際に19個のスタックをこの方式で並べると、6つのレイヤーになりました。

トポロジカルソートで分かれた実行レイヤー

辺が残っているのに取り出せるノードがなければ、そこに循環があるということです。トポロジカルソートは循環のないグラフでしか成立しないため、この場合アルゴリズムはエラーを返します。3節の終わりで触れた制約が、ここでもそのまま現れます。

pipeline = {
    "stages": [f"layer-{i}" for i in range(len(layers))],
}

job = {
    "stage": f"layer-{layer_idx}",
    "extends": f".terragrunt-{mode}",
    "variables": {"STACK_PATH": path, "STACK_NAME": name},
    "resource_group": name,
}

needs = [f"{mode}:{dep}" for dep in dep_map.get(name, []) if dep in stacks]
if needs:
    job["needs"] = sorted(needs)

こうして組み立てた定義をアーティファクトとして出力すると、先ほどのトリガージョブがそれを受け取って子パイプラインを起動します。

trigger:
  include:
    - artifact: .generated-plan-pipeline.yml
      job: detect-changes
  strategy: depend

この形にしておけば、スタックを追加するときに手を入れるファイルはterragrunt.hcl一つだけです。CI定義には触れません。

4.1 実装でつまずいた点

ステージだけでは並列性が活きません。 GitLabのステージは、前のステージがすべて終わらないと次が始まりません。レイヤーをステージに移すと、同じレイヤーの中では同時に走る一方、レイヤーとレイヤーの間では関係のないスタックまで待つことになります。そこで各ジョブに、自分が直接依存するスタックだけをneedsで指定しました。DNSスタックはロードバランサースタックが終わり次第動きだし、同じレイヤーにいた残りのスタックを待つことはありません。

同じスタックが同時に二度走るとstate lockで止まります。 複数のパイプラインが重なるタイミングで同じスタックのジョブが並ぶと、片方が取得したlockのせいでもう片方は開始できずに失敗します。Terraformはデフォルトでlockを再試行しないため、待つことなくそのまま終了します。そこでスタック名をresource_groupに指定し、同じスタックのジョブが一度に一つしか走らないようにしました。

planジョブに前のジョブのアーティファクトが付いてきてはいけません。 GitLabはデフォルトで前のステージのアーティファクトをすべてダウンロードします。すると他のスタックのtfplanが作業ディレクトリに紛れ込みます。そこへ、Terragruntがコードを.terragrunt-cacheにコピーしてから実行する動作が重なり、意図しないplanファイルを読んでしまうことがありました。そこで各planジョブにdependencies: []を置き、アーティファクトを一切ダウンロードしないようにしています。

CIパイプライン生成でつまずいた三つの点

順序はこのように自動で決まりますが、applyだけはwhen: manualにしてあります。人が手動で承認する形にして、インフラ側の安全性を担保しています。

5. 分割境界の基準

ここまでは順序問題の解法を説明してきました。では、スタック自体はどの基準で分けるべきでしょうか。

5.1 ディレクトリ名による依存方向の固定

スタックディレクトリの先頭に番号を付け、依存順序を名前に持たせました。ネットワークが01-network-security、ストレージが03-storage-registry-cache、アプリケーションが06-applicationsといった具合です。

こうしておくと、参照の向きがファイルパスだけで見えます。03-storage-registry-cache01-network-securityのVPCを受け取って使うのは、番号の大きい側が小さい側を参照する向きなので正常です。逆向きがあれば、コードを開かなくてもパスだけで目に留まります。state分割を扱った先の事例も、依存が一方向にだけ流れるように切ることを勧めています。番号は、そのルールを名前に埋め込む方法だといえます。

ディレクトリ番号と依存方向

ただし、この規約が自動的に守られるわけではありません。現在も番号に逆らう参照が五箇所あります。07-observability08-dns09-load-balancingを後から切り出したときに付けた番号なので、先にあったスタックが後ろの番号を参照する形になってしまいました。4.1で例に挙げた08-dns09-load-balancingもその一つです。

番号は境界を引いた時点の判断を記録するだけで、その後の変更まで追いかけてはくれません。レビューでもう一度目を向けさせる目印として置いておけば、それだけでも十分に役目は果たします。

5.2 依存先の違いによる追加分割

06-applicationsの下だけは、アプリごとのスタックにもう一段分けました。アプリによって、待つべきスタックの数が違うからです。

アプリ dependency宣言の数
GitLab (EC2) 7
GitLab Runner Manager 4
SonarQube 3
mailcow 2
wiki 1

五つのアプリを一つのスタックに入れてしまうと、この違いが消えます。五つ分のリソースが同じstateに入るため、依存が一つしかないwikiを直すときにも、依存が七つあるGitLabのリソースまで一緒に更新されます。最も重いアプリが、最も軽い変更のコストを決めてしまうわけです。

先の事例が挙げる分割基準は、ライフサイクルと責務でした。ここに「何をどれだけ待つのか」という軸をもう一つ加えています。リソースの種類が同じでデプロイの周期が同じでも、待つ相手が違うなら分けたほうがよい、という判断です。

6. 循環依存とその回避

dependencyでは表現できない関係が、実際に二件ありました。二つのスタックが互いの値を必要とするケースです。

循環は、すでに稼働している共有リソースの後ろにアプリを新しく足すときに生まれます。アプリはロードバランサーが作ったアドレスを受け取って自分のDNSレコードを作る必要があり、ロードバランサーはそのアプリが作ったターゲットグループを指す必要があります。どちらを先にapplyしても、相手の値がまだ存在しません。

循環 やり取りする値
01-network-security06-applications/sonarqube アプリはVPC・サブネット・ALBのセキュリティグループを受け取り、ネットワークはアプリのセキュリティグループIDを受け取ってHTTPSインバウンドルールに入れる
09-load-balancing06-applications/sonarqube アプリはALBのDNS名とゾーンIDを受け取ってDNSレコードを作り、ロードバランサーはアプリのターゲットグループARNを受け取って転送ルールに入れる

dependencyは循環を許さないため、片方向だけをdata.terraform_remote_stateに戻しました。

循環依存と回避

# 09-load-balancing/data.tf
data "terraform_remote_state" "sonarqube" {
  backend = "s3"
  config = {
    bucket = "tfstate-777788889999-ap-northeast-1"
    key    = "environments/common-ou/06-applications/sonarqube/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

動作自体は問題ありません。ただし、この参照はdependencyには現れません。Terragruntが組み立てる実行順序にも、スクリプトが生成するパイプラインにも拾われないということです。この二つのスタックに限っては、順序を人が知っていなければならない状態に戻ってしまいました。消費する側が相手のstate全体への読み取り権限を持つという問題も、ここにはそのまま残ります。

この二件は、第3回でCIが変更を検知できない死角としてあらためて扱います。

7. 運用の結果

7.1 スタック一つを作るときに書くもの

変わった点が最もよく見えるのは、スタックを新しく作るときです。ここでは02-vpc-endpointsを例にします。ネットワークスタックからVPCとサブネットを受け取り、VPCエンドポイントを作るスタックです。

導入前のディレクトリは次のような構成でした。ここにあるものはすべて人が直接書いています。

02-vpc-endpoints/
├── backend.tf           terraform { backend "s3" {} }
├── backend-config.hcl   バケット・stateパス・リージョン・lockテーブル
├── provider.tf          AWS providerとAssumeRoleの設定
├── data.tf              参照するスタックごとのremote_stateブロック
├── main.tf
├── variables.tf
└── outputs.tf

スタックによってはterraform.tfvarsまで置いているところもありました。

backend-config.hclには、このスタックのstateがどこに置かれるかを手で書きます。

bucket = "tfstate-777788889999-ap-northeast-1"
key    = "environments/common-ou/02-vpc-endpoints/terraform.tfstate"
region = "ap-northeast-1"

そしてネットワークスタックの値を受け取るために、data.tfに同じ形式をもう一度書きます。今度は他のスタックのパスです。

data "terraform_remote_state" "network_security" {
  backend = "s3"
  config = {
    bucket = "tfstate-777788889999-ap-northeast-1"
    key    = "environments/common-ou/01-network-security/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

同じバケット名と同じパス規則が、リポジトリのあちこちに散らばっていました。スタック一つを移すには、自分のbackend-config.hclと、自分を参照しているすべてのスタックのdata.tfをまとめて探して直さなければなりません。使っていないディレクトリを除いて数えると、こうしたremote_stateブロックが21個ありました。

現在は、人が直接書く設定ファイルがterragrunt.hcl一つに減りました。3節で示したファイルです。

02-vpc-endpoints/
├── terragrunt.hcl       依存の宣言と入力値
├── main.tf
├── variables.tf
├── outputs.tf
├── backend.tf           Terragruntが生成
└── provider.tf          Terragruntが生成

backend-config.hclterraform.tfvarsはなくなり、data.tfremote_stateブロックもほとんど消えました。ツリーの最後にある二つのファイルは、導入前と名前が同じだけで性格がまったく違います。以前は人が書いていましたが、現在はTerragruntが実行のたびに生成し、バケット名とstateのパスもそのタイミングで書き込まれます。参照するスタックは、相対パス一行で指すだけです。

dependency "network" {
  config_path = "../01-network-security"
}

7.2 実行方法

導入前は、スタックごとにbackend設定を渡して初期化し、順序を守って一つずつ実行していました。

cd terraform/environments/common-ou/01-network-security
terraform init -backend-config=backend-config.hcl
terraform apply
# 終わったら次のスタックへ。順序はコードではなく人が知っていました。

現在、ローカルでは一行です。順序はdependencyが持っているので、人が決めることはありません。

cd terraform/environments/common-ou
terragrunt plan --all

CIでは、コミットをプッシュすればパイプラインが自動的に組み上がります。スタックを追加しても.gitlab-ci.ymlには触れません。新しいスタックのterragrunt.hcldependencyを書いておけば、その宣言がそのままパイプラインのステージとneedsになります。

もっとも、CI側が一度で今の形になったわけではありません。最初はTerragruntに移したコードを、CIでそのまま全量実行していました。順序がdependencyに入っているので動きはしましたが、何を直しても19個のスタックがすべて対象になります。1節で触れた一時間はここから出ています。その後、この記事のトポロジカルソートとneedsでレイヤーに分けて並列実行し、さらに影響を受けるスタックだけを絞り込む方向へと、パイプライン生成スクリプトを直し続けてきました。

7.3 改善された点

1節で挙げた四つのうち、二つが解消されました。

症状 この記事の範囲まで適用した後
どのスタックを先に実行すべきかが担当者の頭の中にしかない 順序がdependency宣言に入っている
変更に着手する前に影響範囲を見積もれない 宣言からグラフを組み立てられるため、レイヤーとして見える
無関係に見える変更が稼働中のものに影響する 残っている
変更の大きさに関係なくパイプラインが一時間を超える 残っている

残る二つは順序ではなく、何が変わったのかを見分ける問題なので、ここでは解けません。順序が分かっても、どのスタックを実行すべきか分からなければ、結局すべてを実行することになります。そのためCIの変更検知にも別途手を入れており、その話は第3回で扱います。

7.4 改善によって生じた課題

順序をコードに移した代わりに、新しく抱えることになったものが三つあります。

一つ目は、ディレクトリ名を簡単に変えられなくなったことです。

ルート設定は、stateファイルが置かれる位置を次のように組み立てます。

key = "${path_relative_to_include()}/terraform.tfstate"

path_relative_to_include()もTerragruntの関数です。各スタックのincludeブロックが指すルート設定を基準に、そこからそのスタックまでの相対パスを返します。スタックがcommon-ou/01-network-securityにあれば、stateもバケット内の同じパスに置かれるということです。

ディレクトリパスとstateキー

7.1で手書きしていたパスが消えたのはこの仕組みのおかげですが、代償もついてきます。ディレクトリを移したり名前を変えたりすると、stateがあるべき場所も一緒に変わります。すでに作られたstateを新しいパスへ移す作業が、そのたびに発生します。

実際にハマったことがあります。もともと05-compute-balancing-dnsという一つのスタックが、名前のとおりコンピュートとロードバランシングとDNSをすべて抱えていました。このうちロードバランシングを09-load-balancingへ、DNSを08-dnsへ切り出しましたが、残ったディレクトリの名前はそのままにしています。名前を直した瞬間にstateを移すことになるからです。そのため現在その中にあるのはECSだけですが、名前は三つを抱えていた頃のままです。

二つ目は、dependencyで書けなかった依存がどこにも見えないことです。

6節で循環のためにdata.terraform_remote_stateへ戻した二件がそれにあたります。09-load-balancingがSonarQubeを読むものと、01-network-securityがSonarQubeを読むものです。

リポジトリ全体にある32箇所のdependency宣言は、ファイルを開けばそのスタックが何を待っているのか分かります。ところがこの二件はdata.tfの中にあるため、コードを読んでも、生成されたパイプラインを見ても、依存関係としては現れません。スタック間の依存で人が覚えていなければならないのは、この二つだけです。

remote_stateはもう一箇所残っており、04-identity-secrets-certsが管理アカウントのIAM設定を読む部分です。19個のスタックの外にあるstateなので、そもそもdependencyに置き換えられる対象ではありません。

三つ目は、使っていないスタックディレクトリが誤解を生むことです。

二つのアカウントに同じ番号規約を当てはめましたが、片方にはすべてのスタックが必要だったわけではありません。そのため、ディレクトリとmain.tfはあるのにterragrunt.hclだけがない場所が三つできました。

TerragruntとCIはterragrunt.hclがあるディレクトリだけをスタックとして数えるため、実行には影響しません。ただし、リポジトリを初めて開く人には管理中のスタックに見えてしまいます。

8. 今後の課題

逆転した依存方向の解消。 7.4の二つ目の課題をなくす作業です。循環は、ネットワークとロードバランサーのスタックがアプリスタックの値を逆向きに参照するところから生まれます。参照方向を一方に整理すればdata.terraform_remote_stateの二件が消え、スタック間の依存はすべてdependencyで表現できます。

番号と実際の依存方向の整合。 5.1で見た逆行参照の五件です。後から切り出した070809が、先にあったスタックより後ろの番号に置かれたことで生まれました。番号を振り直すには、次の項目と同じ理由でstateの移設がついてきます。

名前と中身が食い違ったスタックの整理。 05-compute-balancing-dnsを、今の中身に合った名前に変える作業です。stateを新しいパスへ移すことになるため、後回しにしています。

使っていないディレクトリの削除。 terragrunt.hclがない三つのディレクトリを消せば、リポジトリの見た目と実際の運用範囲が一致します。

9. 次回

第2回は、すでに引いた境界を引き直す話です。

一つは、モジュールがアプリ名で分かれていて、アプリを追加するたびにモジュールも一緒に増えていく構造です。もう一つは、先ほど出てきた05-compute-balancing-dnsです。一つのスタックがコンピュートとロードバランシングとDNSをすべて抱えていたため、DNSレコードを一行直すだけでECSまで更新されていました。この二つを、稼働状態を保ったままどう移したのかを扱います。moved / import / removedブロックと、apply前に0 destroy / 0 replaceを確認するゲートがその方法です。


参考

公式ドキュメント

値の受け渡し

分割の基準

実行順序

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?