【Terraform】depends_onのベストプラクティスと使用例
Terraformを使ってAzureのインフラをコードで管理していると、「このリソースは、あのリソースが作成された後に作られてほしいんだけど、Terraformがうまく認識してくれない…」といった状況に遭遇することがあります。多くの場合、Terraformはリソース間の参照関係から依存関係を推測してくれますが、時には明示的にその順序を指示する必要が出てきます。
そんな時に役立つのが、depends_on
メタ引数です。しかし、この機能はその便利さの裏で、使い方を誤ると予期せぬ問題を引き起こす可能性も秘めています。
この記事では、まずdepends_on
を利用する上での大原則と注意点を説明し、その上で具体的な使い方やAzure環境での活用シナリオを解説します。 depends_on
を正しく理解し、本当に必要な場面でのみ最小限に活用することで、より安定したTerraform運用を目指しまします。
(記事中のコード例では、一部のネットワークリソース(VNet, Subnet, NIC)やリソースグループの定義は、説明を簡潔にするために最小限に留めるか、既に存在するものとして扱っています。実際の利用時は、環境に合わせてこれらのリソース定義を適切に行ってください。)
Terraform
# 事前に定義されていると想定するリソースグループ
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のメリットを損なうことになりかねません。
なぜなら、主に以下の理由が挙げられます。
- 最終手段として使用する – なぜ避けるべきか?
- Terraformの計画が保守的になる: Terraformは通常、リソース間の属性参照(式参照
resource.type.name.attribute
など)から依存関係を自動的に、かつ正確に推測します。depends_on
を多用すると、Terraformはこの賢い推測能力を十分に活かせなくなり、実行計画が過度に保守的になることがあります。結果として、実際には変更が不要なリソースまで再作成されたり、多くの値が「(known after apply)」として扱われたりする可能性があります。 - パフォーマンスへの影響:
depends_on
はリソースの作成や破棄の処理を直列化します。Terraformは可能な限りリソースを並列に処理しようとしますが、depends_on
はその並列性を妨げ、結果としてインフラのプロビジョニング全体の時間が長くなる可能性があります。
- Terraformの計画が保守的になる: Terraformは通常、リソース間の属性参照(式参照
- 最優先: 暗黙的な依存関係を最大限に活用する
- Terraformの最大の強みの一つは、コード内のリソース間の参照関係を解析し、自動的に正しい実行順序を決定することです。例えば、あるVMが特定のサブネットIDを参照していれば、TerraformはそのサブネットがVMより先に作成されるべきだと理解します。可能な限り、このような「暗黙的な依存関係」を利用してリソースの順序をTerraformに自然に解決させるべきです。 これにより、コードの可読性が向上し、Terraformの計画もより最適化されます。
- 使用する場合の必須事項: コメントで理由を明記する
- それでも
depends_on
を使用せざるを得ない場合は、なぜその明示的な依存関係が必要なのか、コード内に必ずコメントを残しましょう。 「このポリシーが完全にAzure ADに伝播するまで待つため」や「アプリケーションが起動時にこのファイルを読み込むため、ファイルの存在を保証する」など、具体的な理由を記述することで、将来の自分や他のチームメンバーがコードの意図を正確に理解し、不必要な変更や混乱を避けることができます。
- それでも
- 設計上の注意: 循環依存を避ける
- リソースAがリソースBに
depends_on
で依存し、かつリソースBがリソースAにdepends_on
で依存するような「循環依存」は、Terraformの計画時にエラーを引き起こします。これはdepends_on
に限らず、暗黙的な依存関係でも発生し得ますが、depends_on
を多用すると意図せず循環依存を作り込んでしまうリスクが高まります。リソース間の関係性を事前に整理し、一方向の依存関係になるよう設計することが重要です。
- リソースAがリソースBに
- 影響の理解: 破棄時の順序にも影響
depends_on
はリソースの作成順序だけでなく、terraform destroy
時の破棄の順序にも影響を与えます。依存されているリソース(depends_on
で指定された側)が、依存しているリソースよりも後に破棄されるようになります。この挙動を理解しておくことは、安全なインフラ管理において重要です。
結論として、depends_on
は「Terraformがどうしても依存関係を自動で解決できない、隠れた依存関係が存在する場合」に限定して使用するべき機能です。 使う際には、上記のリスクと影響を十分に理解し、その必要性を慎重に検討してください。
depends_on
を使わないといけない時とは
上記の「大原則」を踏まえた上で、depends_on
が具体的にどのようなもので、どんな時に「最終手段」として必要になるのかを見ていきましょう。
depends_on
は、Terraformが自動的に推測できないリソースやモジュール間の「隠れた依存関係」を明示的に定義するためのメタ引数です。
Terraformは通常、設定ファイル内でリソースAがリソースBの属性を参照している場合(例:azurerm_network_interface
のsubnet_id
がazurerm_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
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_extension
がazurerm_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
# (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
なしで済む設計にできないかを十分に検討してください。
- 外部システムの変更伝播待ち: Azure Key Vaultにシークレットを作成し、そのシークレットに対するアクセスポリシーをVMのマネージドIDに割り当てた後、VM内のアプリケーションがそのシークレットを即座に読み取る必要がある場合。アクセスポリシーがAzureのIAM基盤全体に完全に伝播し、有効になるまでのわずかなタイムラグが問題となるケースでは、VMやVM拡張機能の
depends_on
でアクセスポリシーリソースを指定することを検討するかもしれません。- 検討ポイント: 多くの場合、VMのマネージドID (
principal_id
) をアクセスポリシーのobject_id
に指定するため、基本的な作成順序はTerraformが推測できます。問題は「伝播時間」であり、これをdepends_on
だけで完全に解決できるとは限りません。アプリケーション側でのリトライ処理なども併用すべきでしょう。
- 検討ポイント: 多くの場合、VMのマネージドID (
- 外部で実行されるプロセスの完了待ち: App Serviceをデプロイする際、依存するデータベースのスキーママイグレーションが、Terraform管理外のCI/CDパイプラインや手動スクリプトで実行される場合。その外部プロセスの完了をTerraformが知る術がないため、もし次のステップ(例: App Serviceの起動やトラフィックスワップ)がその完了を待つ必要があるなら、
null_resource
とlocal-exec
プロビジョナー、そしてdepends_on
を組み合わせるトリッキーな方法が考えられますが、これは非常に高度で注意深い設計が必要です。- 検討ポイント: このようなケースは、可能な限りTerraformの管理範囲内で完結させるか、外部プロセスの完了を別の方法で検知し、Terraformの実行を制御する方が望ましいです。
- モジュール間の「隠れた」実行順序制御:基本的なネットワーク関連モジュールの作成が完了した後、そのネットワーク上に展開されるApp ServiceやAKSなどのアプリケーションモジュールを実行したい場合。多くはモジュールの入力/出力変数(例: app_service_plan_idやinstrumentation_keyの受け渡し)で暗黙的な依存関係が形成されます。しかし、もしモジュールAの完了後に、Terraform管理外の何らかの初期化処理(例: 手動でのDNS設定など)が行われ、モジュールBがその結果を期待するような「隠れた」依存がある場合のみ、depends_onの使用を検討します。
# (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
を使用する場合は、その理由を明確にコメントし、その影響範囲を十分に理解した上で、本当に必要な箇所に限定して適用することをお勧めします。
ディスカッション
コメント一覧
まだ、コメントがありません