Terraform の整合性問題を解決!3つのシナリオ別対処法
2025-12-18
azblob://2025/12/18/eyecatch/2025-12-18-terraform-resolve-integrity-000_0.jpg

はじめに

Infrastructure as Code(以下IaC)を実現するための一つのソフトウェアとして Terraform が挙げられます。  
Terraform は、宣言的な記述方式と豊富なプロバイダーサポートにより、多くの組織で標準的な IaC ソフトウェアとして採用されています。  
IaC として Terraform を導入するメリットとして、例えばインフラ構成の再現性確保やバージョン管理を行えることです。  
しかし、Terraform を導入してこれらの恩恵を享受できる一方で、これを運用していく上で新たな課題も生じます。

本ブログでは、IaC を実現するためのソフトウェアである Terraform とそれを運用するうえで発生する「整合性を失った状態」という課題に焦点を当て、その課題が発生する代表的なシナリオに沿って、それらの解決策を提示することを目的とします。

前提

本ブログでは下記を前提とします。

  • IaC を実現するためのソフトウェアを Terraform に限定します  
  • Terraform で管理するインフラストラクチャーを Azure のリソースに限定します  
  • Terraform の操作は適切な権限を持ったセキュリティープリンシパルによる操作とします  
  • Terraform の設定ファイルに必要に Provider や Backend の定義、init 操作などは省略します。  
  • 使用するTerraform のバージョン: v1.14.2  
  • 使用するリソースプロバイダー azurerm のバージョン: v4.56.0

※ 本ブログの内容は上記のバージョンでの動作を前提としており、他のバージョンでは動作が異なる場合があります。

整合性とは

Terraform でインフラストラクチャーを運用する上で重要な概念の一つが「整合性」です。  
まず、Terraform における整合性とは何かを確認しましょう。

Terraform における3つの要素

Terraform の整合性を理解する上で重要な3つの要素が存在します:  

  1. Terraform の設定ファイル(.tf ファイル)
    1. 望ましいインフラ構成を宣言的に記述したファイル  
  2. State ファイル(.tfstate) 
    1. Terraform が管理するリソースの現在の状態を記録したファイル
  3. 実際のクラウドリソース(Azure上のリソース)
    1. クラウドプロバイダー上に実際に存在するインフラストラクチャー

整合性が保たれた状態

これら3つの要素に対して下記2つの状態が満たされている状態が、「整合性が保たれた状態」です:

  • Terraform の設定ファイルで定義された内容 = State ファイルに記録された内容
  • State ファイルに記録された内容 = 実際のクラウドリソースの定義

※ Terraform のロジック的に Terraform の設定ファイルで定義された内容と実際のクラウドリソースの定義の差分を直接確認する処理はないため、意図的にこの2つの評価は記述していません。  
これらの整合性評価は必ず State ファイルを媒介して行います。

これらの条件を満たした状態では、`terraform plan` を実行しても差分が検出されず、「No changes. Your infrastructure matches the configuration.」というメッセージが表示されます。

整合性を失った状態

逆に「整合性を失った状態」とは、これら3つの要素のうち一つ以上に差分が発生している状態を指します。

具体的には以下のようなパターンが考えられます:

  1. Terraform の設定ファイルで定義された内容 ≠ State ファイルに記録された内容
  2. State ファイルに記録された内容 ≠ 実際のクラウドリソースの定義
  3. 1,2 の両方

これらに差分が発生し、整合性を失った状態になると、Terraform による適切なインフラ管理ができなくなり、予期しない動作や設定の不整合が生じる可能性があります。

次章では、このような整合性を失う具体的なシナリオと解決策について詳しく見ていきます。

整合性を失うシナリオと解決策

本章では、整合性を失う代表的な3つのシナリオを整理し、それぞれの解決策を体系的に説明します。

シナリオの分類と整理

この章で取り扱うシナリオは、以下のように分類できます:

シナリオ発生箇所主な原因
Terraform による操作以外でのリソース変更State ⇔ 実際のリソース緊急対応での手動変更
既存リソースのインポート設定ファイル ⇔ State既存リソースの Terraform 化
外部ポリシーによる変更設定ファイル ⇔ StateAzure Policy などの自動適用

では、実際に整合性を失うシナリオについて具体例と解決策を確認します。

シナリオ1: Terraform による操作以外でのリソース変更

発生状況

このシナリオは下記のようなきっかけにより発生します。  

  • 緊急対応でAzure Portalから直接リソースを変更  
  • 誤って Terraform 以外(例えば Azure CLI など)でリソースを修正  
  • 自動スケーリングなどによる予期しない変更

具体例:App Service Plan のスケールアップ  

Terraform で管理している App Service Plan において、CPU 使用率のアラートが発生し、緊急対応として Azure Portal からスケールアップを実施した直後の状況を想定します。  
変更前と変更後のサイズは S1 → S2 とします。

初期状態の Terraform のソースコードは以下の通りです。

resource "azurerm_resource_group" "this" {  
 name     = "rg-blog"  
 location = "japaneast"  
}
resource "azurerm_app_service_plan" "this" {  
 name                = "asp-blog"  
 location            = "japaneast"  
 resource_group_name = azurerm_resource_group.this.name
 sku {  
   tier = "Standard"  
   size = "S1"  
 }  
}  

解決手順  

このような状況下では以下のような手順を踏んで整合性を整えます。  

  1. `terraform plan -refresh-only` を実行してクラウド上のリソースと State ファイルの差分を確認する。  
  2. 1 の内容に問題がなければ `terraform apply -refresh-only -auto-approve` を実行してクラウド上のリソースの設定変更内容を State ファイルに取り込む。  
  3. `terraform plan -refresh-only -detailed-exitcode` を実行してクラウド上のリソースと State ファイルに差分がないことを確認する。  
  4. `terraform plan` を実行して全体の差分を確認し、Terraform の設定ファイルを適切な値に書き換える。  
  5. `terraform plan -detailed-exitcode` を実行して差分がないことを確認する。

以下、各手順の実行例を示します。

まず、`terraform plan -refresh-only` を実行すると下記の差分(一部抜粋)が表示されます。

# azurerm_app_service_plan.this has changed  
~ resource "azurerm_app_service_plan" "this" {  
     id                           = "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/rg-blog/providers/Microsoft.Web/serverFarms/asp-blog"  
     name                         = "asp-blog"  
     tags                         = {}  
     # (10 unchanged attributes hidden)
   ~ sku {  
       ~ size     = "S2" -> "S1"  
         # (2 unchanged attributes hidden)  
     }  
 }  

この状態では Azure 上のリソースと State ファイルに差分が発生しており、sku の size に差分が発生していることが確認できます。  
App Service Plan の sku の size は S2 が期待される値となるため、Azure 上にあるリソースの設定値と等しくなるように State ファイルの更新を受け入れるために `terraform apply -refresh-only -auto-approve` を実行します。  
実行が完了したら、再度 `terraform plan -refresh-only -detailed-exitcode` を実行して State ファイルとクラウド上のリソースの設定に差分がないことを確認します。

続いて、Terraform の設定ファイルを期待される状態に変更します。  
まず、`terraform plan` を実行して全体の差分を確認します。  
コマンド実行時には以下の通りが差分(一部抜粋)が出力されます。

~ update in-place
# azurerm_app_service_plan.this will be updated in-place  
~ resource "azurerm_app_service_plan" "this" {  
     id                           = "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/rg-blog/providers/Microsoft.Web/serverFarms/asp-blog"  
     name                         = "asp-blog"  
     tags                         = {}  
     # (10 unchanged attributes hidden)
   ~ sku {  
       ~ size     = "S2" -> "S1"  
         # (2 unchanged attributes hidden)  
     }  
 }
Plan: 0 to add, 1 to change, 0 to destroy.  

差分が発生している箇所に対して望ましい状態となるように Terraform の設定ファイルを修正します。  
修正が完了したら、`terraform plan -detailed-exitcode` を実行して差分がないことを確認し、差分が無くなれば差分の取り込みが完了です。

変更後の Terraform の設定ファイル  

resource "azurerm_resource_group" "this" {  
 name     = "rg-blog"  
 location = "japaneast"  
}
resource "azurerm_app_service_plan" "this" {  
 name                = "asp-blog"  
 location            = "japaneast"  
 resource_group_name = azurerm_resource_group.this.name
 sku {  
   tier = "Standard"  
   + size = "S2"  
   - size = "S1"  
 }  
}  

シナリオ2: 既存リソースの Terraform 管理への移行

発生状況  

このシナリオは以下のようなきっかけにより発生します:

  • 緊急作成したリソースを後から Terraform で管理したい場合  
  • 手動で作成済みのリソースを Terraform 管理に移行したい場合  
  • 他のツールで管理していたリソースを Terraform に統合する場合

具体例:Log Analytics Workspace の Terraform 化  

現在、Azure 上に Terraform で管理している リソースグループがあり、何らかの暫定対応でこのリソースグループの配下に Log Analytics Workspace を Azure Portal から作成した状況を想定します。  
作成した Log Analytics Wrokspace の設定は画像の通りとし、これを Terraform で管理できるようにします。

Log Analytice Workspace の設定

初期状態の Terraform のソースコードは以下の通りです。

resource "azurerm_resource_group" "this" {  
 name     = "rg-blog"  
 location = "japaneast"  
}  

解決手順  

このような状況下では以下のような手順を踏んで整合性を整えます。  

  1. 新しく作成したリソースの型と名前を Terraform の設定ファイルに記述する。  
  2. `terraform import` コマンドを実行して、tfstate にクラウド上のリソース定義を取り込む。  
  3. `terraform plan` コマンドを実行して、Terraform の設定ファイルと State ファイルの差分を確認する。  
  4. 差分の内容を解消する実装を Terraform の設定ファイルに加える。  
  5. `terraform plan -detailed-exitcode` を実行して差分が発生しないことを確認する。

以下、各手順の実行例を示します。

まず、Terraform の設定ファイルに型と名前を定義します。  
型は azurerm で定義されている Log Analytics Workspace の型を使用し、名前は this とします。

resource "azurerm_resource_group" "this" {  
 name     = "rg-blog"  
 location = "japaneast"  
}
+ resource "azurerm_log_analytics_workspace" "this" {}  

続いて、下記のコマンドを実行します。※ ${SUBSCRIPTION_ID}, ${RESOURCE_GROUP_NAME}, ${WORKSPACE_NAME} は適切な値に置き換える必要があります。  
 

terraform import azurerm_log_analytics_workspace.this /subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP_NAME}/providers/Microsoft.OperationalInsights/workspaces/${WORKSPACE_NAME}  

実行が完了すると、State ファイルに Terraform 外で作成したリソースの定義と設定が記述されます。

ここまで完了すればあとは `terraform plan` コマンドを駆使して、Terraform の設定ファイルを State ファイルと差分が発生しないようになるように修正すれば完了となります。

最終的な Terraform の設定ファイルは下記のようになります。

resource "azurerm_resource_group" "this" {  
 name     = "rg-blog"  
 location = "japaneast"  
}
+ resource "azurerm_log_analytics_workspace" "this" {  
+   name                = "log-blog-xyz"  
+   location            = azurerm_resource_group.this.location  
+   resource_group_name = azurerm_resource_group.this.name  
+   sku                 = "PerGB2018"  
+   retention_in_days   = 30
+   cmk_for_query_forced                    = false  
+   immediate_data_purge_on_30_days_enabled = false
+   tags = {}  
+ }  

シナリオ3: クラウドサービスのポリシーによる差分

発生状況 

このシナリオは以下のようなきっかけにより発生します:  

  • Azure Policy によるタグの自動付与  
  • セキュリティポリシーによる設定の強制変更  
  • コンプライアンス要件による自動修正

具体例:タグ継承ポリシーによる差分  

Terraform で管理しているリソースグループに Azure Policy の「Inherit a tag from the resource group」ポリシーが適用されており、そのリソースグループ配下に Log Analytics Workspace を作成した状況を想定します。  
この場合、ポリシーによりリソースグループのタグが自動的に継承され、Terraform 設定ファイルとの間に差分が発生します。

初期状態の Terraform のソースコードは以下の通りです。

resource "azurerm_resource_group" "this" {  
 name     = "rg-blog"  
 location = "japaneast"  
 tags = {  
   purpose = "blog"  
 }  
}
data "azurerm_policy_definition_built_in" "this" {  
 display_name = "Inherit a tag from the resource group"  
}
resource "azurerm_resource_group_policy_assignment" "this" {  
 name                 = "Inherit a tag from the resource group"  
 resource_group_id    = azurerm_resource_group.this.id    
 policy_definition_id = data.azurerm_policy_definition_built_in.this.id 
 parameters = jsonencode({  
   "tagName" = {  
     value = "purpose"  
   }  
 })
 identity {  
   type = "SystemAssigned"  
 }  
 location = azurerm_resource_group.this.location  
}
# Azure Policy の修復タスクを実施するために必要なロール  
resource "azurerm_role_assignment" "this" {  
 scope                = azurerm_resource_group.this.id  
 role_definition_name = "Tag Contributor"  
 principal_id         = azurerm_resource_group_policy_assignment.this.identity[0].principal_id  
}
resource "azurerm_log_analytics_workspace" "this" {  
 name                = "log-blog-xyz"  
 location            = azurerm_resource_group.this.location  
 resource_group_name = azurerm_resource_group.this.name    
 sku                 = "PerGB2018"  
 retention_in_days   = 30
 cmk_for_query_forced                    = false  
 immediate_data_purge_on_30_days_enabled = false  
 # タグは明示的に設定していない  
}  

解決手順

このような状態では以下のような手順を踏んで整合性を整えます。

  1. `terraform plan` を実行して、差分を確認します。  
  2. 差分の内容を Terraform の設定ファイル上で管理する必要があるかを判断し、管理する必要があれば差分の解消を、管理する必要がなければ、対象の設定を ignore_changes とします。

以下、各手順の実行例を示します。

まず、`terraform plan` を実行すると下記のような差分(一部抜粋)が発生しています。

~ update in-place
# azurerm_log_analytics_workspace.this will be updated in-place  
~ resource "azurerm_log_analytics_workspace" "this" {  
     id                                      = "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/rg-blog/providers/Microsoft.OperationalInsights/workspaces/log-blog-xyz"  
     name                                    = "log-blog-xyz"  
   ~ tags                                    = {  
       - "purpose" = "blog" -> null  
     }  
     # (16 unchanged attributes hidden)  
 }
Plan: 0 to add, 1 to change, 0 to destroy.  

差分を確認したところ、今回は下記の理由から対象の差分を ignore_changes とします。  

  • tag が Azure Policy によって確実に付与されること  
  • tag に追加・更新・削除があった場合、修正の範囲が広範になること(今回はリソースが1つだが、リソースが増えると大変)

方針が決まったので、Terraform の設定ファイルを下記のように設定し、`terraform plan -detailed-exitcode` を実行して差分がないことを確認すれば完了です。

resource "azurerm_resource_group" "this" {  
 name     = "rg-blog"  
 location = "japaneast"  
 tags = {  
   purpose = "blog"  
 }  
}
data "azurerm_policy_definition_built_in" "this" {  
 display_name = "Inherit a tag from the resource group"  
}
resource "azurerm_resource_group_policy_assignment" "this" {  
 name                 = "Inherit a tag from the resource group"  
 resource_group_id    = azurerm_resource_group.this.id  
 policy_definition_id = data.azurerm_policy_definition_built_in.this.id
 parameters = jsonencode({  
   "tagName" = {  
     value = "purpose"  
   }  
 })
 identity {  
   type = "SystemAssigned"  
 }  
 location = azurerm_resource_group.this.location  
}
# Azure Policy の修復タスクを実施するために必要なロール  
resource "azurerm_role_assignment" "this" {  
 scope                = azurerm_resource_group.this.id  
 role_definition_name = "Tag Contributor"  
 principal_id         = azurerm_resource_group_policy_assignment.this.identity[0].principal_id  
}
resource "azurerm_log_analytics_workspace" "this" {  
 name                = "log-blog-xyz"  
 location            = azurerm_resource_group.this.location  
 resource_group_name = azurerm_resource_group.this.name  
 sku                 = "PerGB2018"  
 retention_in_days   = 30
 cmk_for_query_forced                    = false  
 immediate_data_purge_on_30_days_enabled = false
+ lifecycle {  
+   ignore_changes = [tags]  
+ }  
}  

整合性を失わないための運用  

ここまで、Terraform のリソース管理状態に整合性を失った場合のシナリオや解決策について触れてきました。  
ただし、これは整合性が失われた場合による対処法にとどまるものであり、Terraform を用いた運用を進めていくうえでは下記を留意する必要があります。  

  • 可能な限り整合性が失われた状態を発生させないようにする。  
  • 整合性が失われた状態が発生した場合、あらかじめ決められた時間間隔で検知して修復することで、整合性が失われた状態が長期間放置されることを防ぐようにする。

これらの運用を実現するために、Terraform による運用のルールを決めることが最善です。  
例えば以下のような運用ルールが挙げられます。

運用ルール  

  1. Terraform 管理リソースの手動変更禁止  
    1. Terraform で管理しているリソースは、原則として Terraform 経由でのみ変更する  
    2. 緊急時の手動変更は事後に必ず Terraform 設定に反映  
  2. 定期的な差分チェックによる監視  
    1. 定期的に `terraform plan -detailed-exitcode` を実行し、予期しない差分がないか確認  
    2. CI/CD パイプラインでの自動チェックの導入  
    3. リソース変更の監視設定  
    4. 予期しない変更が発生した際の対応フローや手順の文書化  
  3. State ファイルの適切な管理  
    1. リモート State の使用(Azure Storage、Terraform Cloud など)  
    2. State ファイルのバックアップとバージョン管理  
  4. 適切な lifecycle 設定  
    1. prevent_destroy による重要リソースの削除防止  
    2. ignore_changes による意図的な差分の無視
  5. 周知と理解  
    1. チームメンバーへの Terraform 運用ルールの醸成  
    2. 手動変更のリスクと影響の共有  
    3. Terraform の内部処理内容の詳細理解  
  6. Policy as Code の導入  
    1. Terraform Sentinel を活用した、変更内容の強制  
    2. 不適切な設定変更の事前防止

まとめ  

本ブログでは、Terraform でクラウドリソースを管理するために必要な整合性についてをはじめ、Terraform の整合性を失う3つの主要なシナリオとその解決策について解説しました。

これらの知識を活用することで、Terraform を使用したインフラ管理をより安全かつ効率的に行うことができます。

Terraform の背後の処理に対する理解も深めつつ、適切な運用を行い、 Terraform で安全な IaC 運用ライフを送りましょう!

余談  

Terraform のソースコードは GitHub 上で公開されています。  
このブログで取り扱った差分チェックに関する処理は、例えば context_plan.goevaluate.go などで実装されています。

コードリーティングを進めていくにあたって、Terraform Core が Terraform の設定ファイルと State ファイルの差分を、Resource Provider が State ファイルとクラウド上にあるリソースの差分を確認するという責任分離が理解できて面白かったりします。  
(後者はクラウドサービス固有の API を叩く必要があるので、Resource Provider の債務になっているのは必然でしょうか。)

これらのソースコードを読むことで、Terraform の内部動作についてより深く理解することができるので、興味がある方はコードリーティングしてみましょう!