【Terraform】depends_onのベストプラクティスと使用例

Terraformを使ってAzureのインフラをコードで管理していると、「このリソースは、あのリソースが作成された後に作られてほしいんだけど、Terraformがうまく認識してくれない…」といった状況に遭遇することがあります。多くの場合、Terraformはリソース間の参照関係から依存関係を推測してくれますが、時には明示的にその順序を指示する必要が出てきます。

そんな時に役立つのが、depends_onメタ引数です。しかし、この機能はその便利さの裏で、使い方を誤ると予期せぬ問題を引き起こす可能性も秘めています。

この記事では、まずdepends_onを利用する上での大原則と注意点を説明し、その上で具体的な使い方やAzure環境での活用シナリオを解説します。 depends_onを正しく理解し、本当に必要な場面でのみ最小限に活用することで、より安定したTerraform運用を目指しまします。

(記事中のコード例では、一部のネットワークリソース(VNet, Subnet, NIC)やリソースグループの定義は、説明を簡潔にするために最小限に留めるか、既に存在するものとして扱っています。実際の利用時は、環境に合わせてこれらのリソース定義を適切に行ってください。)

Terraform

JSON
# 事前に定義されていると想定するリソースグループ
resource "azurerm_resource_group" "rg" {
  name     = "example-rg-depends-on"
  location = "japaneast"
}

# 以降の例で使用する最小限のネットワークリソース
resource "azurerm_virtual_network" "vnet_example" {
  name                = "example-vnet-deps"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_subnet" "snet_example" {
  name                 = "example-snet-deps"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet_example.name
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_network_interface" "nic_example" {
  name                = "example-nic-deps"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.snet_example.id
    private_ip_address_allocation = "Dynamic"
  }
}

depends_onを使う前に知っておくべきポイント

Terraformのdepends_onは、リソース間の実行順序を明示的に制御できる便利な機能ですが、その利用は慎重に行うべきです。原則として、depends_onは「最終手段」であり、可能な限り使用を避けるべきです。不必要にdepends_onを記述することは、Terraformのメリットを損なうことになりかねません。

なぜなら、主に以下の理由が挙げられます。

  1. 最終手段として使用する – なぜ避けるべきか?
    • Terraformの計画が保守的になる: Terraformは通常、リソース間の属性参照(式参照 resource.type.name.attribute など)から依存関係を自動的に、かつ正確に推測します。depends_onを多用すると、Terraformはこの賢い推測能力を十分に活かせなくなり、実行計画が過度に保守的になることがあります。結果として、実際には変更が不要なリソースまで再作成されたり、多くの値が「(known after apply)」として扱われたりする可能性があります。
    • パフォーマンスへの影響: depends_onはリソースの作成や破棄の処理を直列化します。Terraformは可能な限りリソースを並列に処理しようとしますが、depends_onはその並列性を妨げ、結果としてインフラのプロビジョニング全体の時間が長くなる可能性があります。
  2. 最優先: 暗黙的な依存関係を最大限に活用する
    • Terraformの最大の強みの一つは、コード内のリソース間の参照関係を解析し、自動的に正しい実行順序を決定することです。例えば、あるVMが特定のサブネットIDを参照していれば、TerraformはそのサブネットがVMより先に作成されるべきだと理解します。可能な限り、このような「暗黙的な依存関係」を利用してリソースの順序をTerraformに自然に解決させるべきです。 これにより、コードの可読性が向上し、Terraformの計画もより最適化されます。
  3. 使用する場合の必須事項: コメントで理由を明記する
    • それでもdepends_onを使用せざるを得ない場合は、なぜその明示的な依存関係が必要なのか、コード内に必ずコメントを残しましょう。 「このポリシーが完全にAzure ADに伝播するまで待つため」や「アプリケーションが起動時にこのファイルを読み込むため、ファイルの存在を保証する」など、具体的な理由を記述することで、将来の自分や他のチームメンバーがコードの意図を正確に理解し、不必要な変更や混乱を避けることができます。
  4. 設計上の注意: 循環依存を避ける
    • リソースAがリソースBにdepends_onで依存し、かつリソースBがリソースAにdepends_onで依存するような「循環依存」は、Terraformの計画時にエラーを引き起こします。これはdepends_onに限らず、暗黙的な依存関係でも発生し得ますが、depends_onを多用すると意図せず循環依存を作り込んでしまうリスクが高まります。リソース間の関係性を事前に整理し、一方向の依存関係になるよう設計することが重要です。
  5. 影響の理解: 破棄時の順序にも影響
    • depends_onはリソースの作成順序だけでなく、terraform destroy時の破棄の順序にも影響を与えます。依存されているリソース(depends_onで指定された側)が、依存しているリソースよりも後に破棄されるようになります。この挙動を理解しておくことは、安全なインフラ管理において重要です。

結論として、depends_onは「Terraformがどうしても依存関係を自動で解決できない、隠れた依存関係が存在する場合」に限定して使用するべき機能です。 使う際には、上記のリスクと影響を十分に理解し、その必要性を慎重に検討してください。

depends_onを使わないといけない時とは

上記の「大原則」を踏まえた上で、depends_onが具体的にどのようなもので、どんな時に「最終手段」として必要になるのかを見ていきましょう。

depends_onは、Terraformが自動的に推測できないリソースやモジュール間の「隠れた依存関係」を明示的に定義するためのメタ引数です。

Terraformは通常、設定ファイル内でリソースAがリソースBの属性を参照している場合(例:azurerm_network_interfacesubnet_idazurerm_subnet.snet_example.idを参照)、これを「暗黙的な依存関係」として認識し、リソースBをリソースAより先に作成します。

しかし、リソース間の依存関係が、設定ファイル上の直接的な属性参照ではなく、リソースの「振る舞い」に依存する場合(例えば、特定のスクリプトがAzure Blob Storageにアップロードされた後にVM拡張機能でそのスクリプトを実行するなど)、Terraformはその依存関係を自動で検出できません。このような、Terraformから見えない依存関係を「隠れた依存関係」と呼び、これを解決するためにdepends_onが必要になるのです。

depends_onの主な利点・提供価値 (正しく使われた場合):

  • 実行順序の正確な制御: Terraformが自動で推測できない「隠れた依存関係」を明示し、リソースの作成・破棄の順序を正確に制御できます。
  • インフラの整合性確保: 特定のリソースの処理が完了し、利用可能になるまで後続のリソースの処理を待機させることで、デプロイメント中のエラーや不整合を防ぎます。

注意点: depends_onはTerraform 0.13からモジュールに対しても使用できるようになりました。それ以前のバージョンではリソースに対してのみ使用可能です。

depends_onの基本的な使い方

Azureのシナリオとして、VM拡張機能(Custom Script Extension)が、事前にAzure Blob Storageに格納されたスクリプトファイルを利用するケースを考えます。この場合、Blobファイルが作成された後にVM拡張機能が実行されるように保証する必要があります。

Terraform

JSON
resource "azurerm_storage_account" "sa_example" {
  name                     = "examplestaccdeps" # 実際にはグローバルでユニークな名前が必要
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

resource "azurerm_storage_container" "container_example" {
  name                  = "scripts"
  storage_account_name  = azurerm_storage_account.sa_example.name
  container_access_type = "private"
}

resource "azurerm_storage_blob" "script_blob_example" {
  name                   = "myscript.sh"
  storage_account_name   = azurerm_storage_account.sa_example.name
  storage_container_name = azurerm_storage_container.container_example.name
  type                   = "Block"
  source_content         = "#!/bin/bash\necho 'Hello from Blob Script! $(date)' > /tmp/blob_script_output.txt"
}

resource "azurerm_linux_virtual_machine" "vm_example" {
  name                  = "example-vm-deps"
  resource_group_name   = azurerm_resource_group.rg.name
  location              = azurerm_resource_group.rg.location
  size                  = "Standard_B1s"
  admin_username        = "azureuser"
  admin_ssh_key {
    username   = "azureuser"
    public_key = file("~/.ssh/id_rsa.pub") # ご自身の公開鍵パスに置き換えてください
  }
  network_interface_ids = [azurerm_network_interface.nic_example.id] # 事前定義されたNICを参照

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }
}

resource "azurerm_virtual_machine_extension" "custom_script_example" {
  name                 = "RunMyScript"
  virtual_machine_id   = azurerm_linux_virtual_machine.vm_example.id
  publisher            = "Microsoft.Azure.Extensions"
  type                 = "CustomScript"
  type_handler_version = "2.0"

  settings = jsonencode({
    "script" = base64encode(<<-EOF
      #!/bin/bash
      echo "Custom script extension started."
      # azurerm_storage_blob.script_blob_example に格納されたスクリプトを
      # ダウンロードして実行する想定(実際のダウンロード処理は別途記述)。
      # この拡張機能がBlob作成後に実行されることを保証したい。
      echo "Script from blob (myscript.sh) would be executed here if downloaded."
      EOF
    )
  })

  # script_blob_example が作成された後にこの拡張機能が実行されるようにする
  depends_on = [
    azurerm_storage_blob.script_blob_example
  ]
  # 理由: この拡張機能は、script_blob_example の内容に依存するため、
  #       Blobが確実に存在してから実行する必要がある。
}

この例では、azurerm_virtual_machine_extensionazurerm_storage_blob.script_blob_exampleに依存することをdepends_onで明示しています。これにより、TerraformはBlobの作成が完了してからVM拡張機能のプロビジョニングを開始します。

depends_onの具体的な効果

depends_onを使わない場合と使った場合で、どのような違いが生まれるか見ていきましょう。上記のVM拡張機能とBlobのシナリオを流用します。

depends_onなしで隠れた依存関係がある場合

設定ファイル(例: script_blob_example)がBlob Storageにアップロードされる前に、VM拡張機能が実行されてしまうと、スクリプトが期待するファイルを見つけられずエラーになります。

Terraform

JSON
# (azurerm_resource_group "rg", azurerm_storage_account "sa_example",
#  azurerm_storage_container "container_example", azurerm_storage_blob "script_blob_example",
#  azurerm_linux_virtual_machine "vm_example" は上記で定義済みと仮定)

resource "azurerm_virtual_machine_extension" "custom_script_before" {
  name                 = "RunMyScriptBefore"
  virtual_machine_id   = azurerm_linux_virtual_machine.vm_example.id # 上記で定義したVMを参照
  publisher            = "Microsoft.Azure.Extensions"
  type                 = "CustomScript"
  type_handler_version = "2.0"

  settings = jsonencode({ "script" = base64encode("#!/bin/bash\necho 'Script executed (Before depends_on). Expecting myscript.sh to exist.'") })
  # depends_on がないので
  ので
  # もしこのスクリプトが azurerm_storage_blob.script_blob_example の内容に
  # 依存している場合、Blob作成前に実行されると問題が発生する。
}

本当に必要なケースか吟味を

depends_onが「最終手段として」役立つ可能性のあるAzure環境でのシナリオをいくつか示します。ただし、これらのシナリオでも、まずは暗黙的な依存関係で解決できないか、depends_onなしで済む設計にできないかを十分に検討してください。

  1. 外部システムの変更伝播待ち: Azure Key Vaultにシークレットを作成し、そのシークレットに対するアクセスポリシーをVMのマネージドIDに割り当てた後、VM内のアプリケーションがそのシークレットを即座に読み取る必要がある場合。アクセスポリシーがAzureのIAM基盤全体に完全に伝播し、有効になるまでのわずかなタイムラグが問題となるケースでは、VMやVM拡張機能のdepends_onでアクセスポリシーリソースを指定することを検討するかもしれません。
    • 検討ポイント: 多くの場合、VMのマネージドID (principal_id) をアクセスポリシーのobject_idに指定するため、基本的な作成順序はTerraformが推測できます。問題は「伝播時間」であり、これをdepends_onだけで完全に解決できるとは限りません。アプリケーション側でのリトライ処理なども併用すべきでしょう。
  2. 外部で実行されるプロセスの完了待ち: App Serviceをデプロイする際、依存するデータベースのスキーママイグレーションが、Terraform管理外のCI/CDパイプラインや手動スクリプトで実行される場合。その外部プロセスの完了をTerraformが知る術がないため、もし次のステップ(例: App Serviceの起動やトラフィックスワップ)がその完了を待つ必要があるなら、null_resourcelocal-execプロビジョナー、そしてdepends_onを組み合わせるトリッキーな方法が考えられますが、これは非常に高度で注意深い設計が必要です。
    • 検討ポイント: このようなケースは、可能な限りTerraformの管理範囲内で完結させるか、外部プロセスの完了を別の方法で検知し、Terraformの実行を制御する方が望ましいです。
  3. モジュール間の「隠れた」実行順序制御:基本的なネットワーク関連モジュールの作成が完了した後、そのネットワーク上に展開されるApp ServiceやAKSなどのアプリケーションモジュールを実行したい場合。多くはモジュールの入力/出力変数(例: app_service_plan_idやinstrumentation_keyの受け渡し)で暗黙的な依存関係が形成されます。しかし、もしモジュールAの完了後に、Terraform管理外の何らかの初期化処理(例: 手動でのDNS設定など)が行われ、モジュールBがその結果を期待するような「隠れた」依存がある場合のみ、depends_onの使用を検討します。
JSON
# (azurerm_resource_group "rg" は定義済みと仮定)

module "network" {
  source              = "./modules/azure-network"
  resource_group_name = azurerm_resource_group.rg.name
  # ... 他の必須変数 ...
}

module "app_service" {
  source                     = "./modules/azure-app-service"
  resource_group_name        = azurerm_resource_group.rg.name
  app_service_plan_id        = module.network.app_service_plan_id # 暗黙的な依存

  # : networkモジュール完了後に手動で特定のNSGルールをポータルから追加し、
  #     そのルールがないとApp Serviceが正常に動作しない、などの特殊ケース。
  #     このようなTerraform外の操作への依存は極力避けるべき設計です。
  depends_on = [
    module.network # networkモジュールの全リソース完了を待つ
  ]
  # コメント: networkモジュール完了後、手動でNSGルール 'manual-rule-xyz' 
  #         追加する必要があるため、それを待機する。
}

まとめ: depends_onは最小限に

Terraformのdepends_onメタ引数は、Azureインフラストラクチャのプロビジョニング順序において、Terraformが自動的に解決できない「隠れた依存関係」を明示的に制御するための、いわば「最後の手段」です。

最も重要なことは、depends_onの必要性を慎重に吟味し、可能な限り暗黙的な依存関係(リソース間の属性参照)によってTerraformに自然な実行順序を解決させることです。 depends_onは、その便利さの反面、Terraformの計画を不必要に保守的にしたり、パフォーマンスに影響を与えたりする可能性があることを常に念頭に置く必要があります。

もしdepends_onを使用する場合は、その理由を明確にコメントし、その影響範囲を十分に理解した上で、本当に必要な箇所に限定して適用することをお勧めします。