import-infrastructure-as-code について
Azure の既存インフラを Azure Verified Modules ベースの Terraform 構成に変換するスキルです。クラウドエンジニア向けに、リソース探索、依存関係の整理、モジュール選定、差分確認を案内します。
主な機能
- 01サブスクリプション、リソースグループ、IDで探索
- 02依存関係の整理とアーキテクチャ文書の作成
- 03Azure Verified Modulesの選定と確認
- 04Terraform構成とインポートブロックの生成
- 05実環境の値とモジュール既定値の比較
活用シーン
- 01既存のAzure環境をTerraformで再構成
- 02探索したAzureリソース間の関係を記録
- 03Azure Verified Modulesを優先してリソースをインポート
- 04生成したTerraformの構成ドリフトを確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ github/ awesome-copilot --skill import-infrastructure-as-codeインストール後、Claude Code で/import-infrastructure-as-codeを入力して読み込まれたか確認します。
詳細
Infrastructure as Code のインポート(Azure → Terraform と AVM)
検出データと Azure Verified Modules を使用して、既存の Azure インフラストラクチャを保守しやすい Terraform コードに変換します。
このスキルを使用するタイミング
ユーザーから次の依頼があった場合に、このスキルを使用します。
- 既存の Azure リソースを Terraform にインポートする
- 稼働中の Azure 環境から IaC を生成する
- AVM がサポートする Azure のあらゆるリソースタイプに対応する(AVM を使用しない場合は、その妥当性を説明する)
- サブスクリプションまたはリソースグループからインフラストラクチャを再作成する
- 検出した Azure リソース間の依存関係をマッピングする
- 手書きの
azurerm_*リソースではなく、AVM モジュールを使用する
前提条件
- Azure CLI がインストールされ、認証済みである(
az login) - 対象のサブスクリプションまたはリソースグループへのアクセス権がある
- Terraform CLI がインストールされている
- Terraform Registry と AVM インデックスソースへのネットワークアクセスがある
入力
| パラメーター | 必須 | デフォルト | 説明 |
|---|---|---|---|
subscription-id | いいえ | アクティブな CLI コンテキスト | サブスクリプションスコープの検出とコンテキスト設定に使用する Azure サブスクリプション |
resource-group-name | いいえ | なし | リソースグループスコープの検出に使用する Azure リソースグループ |
resource-id | いいえ | なし | 特定リソーススコープの検出に使用する、1 つ以上の Azure ARM リソース ID |
subscription-id、resource-group-name、resource-id のうち、少なくとも 1 つが必要です。
ステップごとのワークフロー
1) 必要なスコープを収集する(必須)
検出コマンドを実行する前に、次のいずれかのスコープを確認します。
- サブスクリプションスコープ:
<subscription-id> - リソースグループスコープ:
<resource-group-name> - 特定リソーススコープ:1 つ以上の
<resource-id>値
スコープの取り扱いルール:
- Azure ARM リソース ID(例:
/subscriptions/.../providers/...)はクラウドリソース識別子として扱い、ローカルファイルシステムのパスとして扱わないでください。 - リソース ID は、Azure CLI の
--ids引数(例:az resource show --ids <resource-id>)でのみ使用してください。 - ユーザーがローカルファイルのパスだと明示しない限り、リソース ID をファイル読み取りコマンド(
cat、ls、read_file、glob 検索)に渡さないでください。 - ユーザーから有効なスコープがすでに提供されている場合、コマンドの失敗により必要となった場合を除き、追加のスコープ入力を求めないでください。
- 提供済みのスコープ値から回答できる追加質問はしないでください。
スコープが指定されていない場合は、明示的に確認し、そこで停止してください。
2) 認証してコンテキストを設定する
選択したスコープで必要なコマンドのみを実行します。
サブスクリプションスコープの場合:
az login
az account set --subscription <subscription-id>
az account show --query "{subscriptionId:id, name:name, tenantId:tenantId}" -o json
想定される出力:subscriptionId、name、tenantId を含む JSON オブジェクト。
リソースグループまたは特定リソースのスコープでは、az login は引き続き必要ですが、アクティブなコンテキストがすでに正しい場合、az account set は省略できます。
特定リソースのスコープを使用する場合は、まず --ids を使った直接のコマンドを優先し、具体的なコマンドに必要な場合を除き、サブスクリプションやリソースグループに関する追加の確認は避けてください。
3) 検出コマンドを実行する
選択したスコープを使用してリソースを検出します。正確な Terraform を生成するために必要な情報をすべて取得してください。
# Subscription scope
az resource list --subscription <subscription-id> -o json
# Resource group scope
az resource list --resource-group <resource-group-name> -o json
# Specific resource scope
az resource show --ids <resource-id-1> <resource-id-2> ... -o json
想定される出力:Azure リソースのメタデータ(id、type、name、location、tags、properties)を含む JSON オブジェクトまたは配列。
4) コード生成前に依存関係を解決する
エクスポートされた JSON を解析し、次の項目をマッピングします。
- 親子関係(例:NIC → Subnet → VNet)
properties内のリソース間参照- Terraform の作成順序
重要:次のドキュメントを作成し、プロジェクトルートの docs フォルダーに保存してください。
- 検出したすべてのリソースと、その依存関係および参照を含むメタデータを記載した
exported-resources.json。 - 検出したリソースとその関連性に基づく、人が読めるアーキテクチャの概要を記載した
EXPORTED-ARCHITECTURE.MDファイル。
5) Azure Verified Modules を選択する(必須)
各リソースタイプで最新の AVM バージョンを使用してください。
Terraform レジストリ
- 「avm」+ リソース名で検索します
- 「Partner」タグで絞り込み、公式 AVM モジュールを見つけます
- 例:「avm storage account」で検索 → Partner で絞り込み
公式 AVM インデックス
Note: 以下のリンクは常に main ブランチ上の CSV ファイルの最新バージョンを指します。想定どおり、これはファイルが時間とともに変更される可能性があることを意味します。特定時点のバージョンが必要な場合は、URL に特定のリリースタグを使用することを検討してください。
- Terraform リソースモジュール:
https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformResourceModules.csv - Terraform パターンモジュール:
https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformPatternModules.csv - Terraform ユーティリティモジュール:
https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformUtilityModules.csv
個々のモジュール情報
モジュール情報が .terraform フォルダー内にローカルで存在しない場合は、web ツールまたは別の適切な MCP の方法を使用して取得してください。
AVM のソースを使用します:
- レジストリ:
https://registry.terraform.io/modules/Azure/<module>/azurerm/latest - GitHub:
https://github.com/Azure/terraform-azurerm-avm-res-<service>-<resource>
AVM モジュールが存在する場合は、手書きの azurerm_* リソースよりも AVM モジュールを優先してください。
GitHub リポジトリからモジュール情報を取得する場合、通常、リポジトリのルートにある README.md ファイルにモジュールに関する詳細情報がすべて含まれています。例: https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res--/refs/heads/main/README.md
5a) コードを書く前にモジュールの README を読む (必須)
このステップは任意ではありません。 モジュールの HCL を 1 行でも書く前に、そのモジュールの README 全体を取得して読んでください。生の azurerm プロバイダーに関する知識や、他の AVM モジュールでの以前の経験に頼らないでください。
選択した各 AVM モジュールについて、その README を取得します:
https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res-<service>-<resource>/refs/heads/main/README.md
または、terraform init の後にモジュールがすでにダウンロードされている場合:
cat .terraform/modules/<module_key>/README.md
README から、コードを書く前に以下を抽出して記録します:
- 必須入力 — モジュールが必須とするすべての入力。ここに記載されている子リソース (NIC、拡張機能、サブネット、パブリック IP) はモジュールの内部で管理されます。それらの リソース用に独立したモジュールブロックを作成してはいけません。
- オプション入力 — 正確な Terraform 変数名と宣言された
type。 それらが生のazurermプロバイダーの引数名やブロックの形状と一致すると仮定しないでください。 - 使用例 — どのリソースグループ識別子が使用されているか (
parent_idとresource_group_nameのどちらか)、子リソースがどのように表現されているか (インラインマップか別モジュールか)、 および各入力がどのような構文を期待するかを確認します。
モジュールのルールはパターンとして適用し、思い込みで判断しない
以下の内容は、インポートの失敗につながりやすいミスマッチの種類を示す例として使用してください。
ここに挙げた正確な名前がすべての AVM モジュールに当てはまるとは限りません。選択したモジュールごとに、必ず README と variables.tf を確認してください。
avm-res-compute-virtualmachine(全バージョン)
network_interfacesは Required Input です。NIC は VM モジュールが管理します。VM モジュールと並べて単独のavm-res-network-networkinterfaceモジュールを作成しないでください。すべての NIC はnetwork_interfacesの下にインラインで定義してください。- TrustedLaunch は、トップレベルのブール値
secure_boot_enabled = trueとvtpm_enabled = trueで指定します。security_type引数は、Confidential VM のディスク暗号化のためにos_diskの下にのみ存在するため、TrustedLaunch には使用しないでください。 boot_diagnosticsはオブジェクトではなくboolです。boot_diagnostics = trueを使用してください。ストレージの URI が必要な場合は、別の変数boot_diagnostics_storage_account_uriを使用してください。- 拡張機能は
extensionsマップを通じてモジュール内で管理されます。単独の拡張機能リソースは作成しないでください。
avm-res-network-virtualnetwork(全バージョン)
- このモジュールは
azurermではなく AzAPI プロバイダーに基づいています。リソースグループの指定にはresource_group_nameではなく、parent_id(リソースグループの完全なリソース ID 文字列)を使用してください。 - README のすべての例は
parent_idを示しており、resource_group_nameを示す例はありません。
すべての AVM モジュールに共通する要点は次のとおりです。
- 兄弟モジュールを作成する前に、Required Inputs から子リソースの所有関係を判断してください。
- 受け付ける変数名と型は、Optional Inputs と
variables.tfから判断してください。 - 識別子のスタイルと入力の形は、README の使用例から判断してください。
- 生の
azurerm_*リソースから引数名を推測しないでください。
6) Terraform ファイルの生成
import ブロックを書く前に — モジュールのソースを確認する(必須)
terraform init でモジュールをダウンロードした後、import {} ブロックを書く前に、各モジュールのソースファイルを確認して、正確な Terraform リソースアドレスを特定してください。import のアドレスを記憶に頼って書かないでください。
ステップ A — プロバイダーとリソースラベルを特定する
grep "^resource" .terraform/modules/<module_key>/main*.tf
これにより、モジュールが azurerm_* と azapi_resource のどちらのラベルを使用しているかがわかります。例えば、avm-res-network-virtualnetwork は azurerm_virtual_network "this" ではなく azapi_resource "vnet" を公開しています。
ステップ B — 子モジュールとネストしたパスを特定する
grep "^module" .terraform/modules/<module_key>/main*.tf
子リソースがサブモジュール(サブネット、拡張機能など)で管理されている場合、import のアドレスには中間のモジュールラベルをすべて含める必要があります。
module.<root_module_key>.module.<child_module_key>["<map_key>"].<resource_type>.<label>[<index>]
ステップ C — count と for_each の違いを確認する
grep -n "count\|for_each" .terraform/modules/<module_key>/main*.tf
count を使用するリソースは、import のアドレスにインデックスが必要です。count = 1 の場合(例:Linux と Windows の条件分岐)、アドレスは [0] で終わる必要があります。for_each を使用するリソースは、数値のインデックスではなく文字列のキーを使用します。
既知のインポートアドレスパターン (教訓から得られた例)
これらはあくまで例です。推論のテンプレートとして使用し、現在のインポートにおけるモジュールの正確なアドレスは、ダウンロードしたソースコードから導き出してください。
| リソース | 正しいインポート to アドレスパターン |
|---|---|
| AzAPI ベースの VNet | module.<vnet_key>.azapi_resource.vnet |
| サブネット (ネスト、count ベース) | module.<vnet_key>.module.subnet["<subnet_name>"].azapi_resource.subnet[0] |
| Linux VM (count ベース) | module.<vm_key>.azurerm_linux_virtual_machine.this[0] |
| VM NIC | module.<vm_key>.azurerm_network_interface.virtualmachine_network_interfaces["<nic_key>"] |
| VM 拡張機能 (デフォルト deploy_sequence=5) | module.<vm_key>.module.extension["<ext_name>"].azurerm_virtual_machine_extension.this |
| VM 拡張機能 (deploy_sequence=1~4) | module.<vm_key>.module.extension_<n>["<ext_name>"].azurerm_virtual_machine_extension.this |
| NSG-NIC 関連付け | module.<vm_key>.azurerm_network_interface_security_group_association.this["<nic_key>-<nsg_key>"] |
生成するもの:
azurermプロバイダーと必要なバージョン制約を含むproviders.tf- AVM モジュールブロックと明示的な依存関係を含む
main.tf - 環境固有の値を指定する
variables.tf - 主要な ID とエンドポイントのための
outputs.tf - プレースホルダー値を含む
terraform.tfvars.example
ライブプロパティとモジュールのデフォルト値の差分比較 (必須)
初期構成を作成した後、検出された各ライブリソースのすべての非ゼロプロパティを、対応する AVM モジュールの variables.tf で宣言されているデフォルト値と比較してください。ライブの値がモジュールのデフォルト値と異なるプロパティは、Terraform 構成で明示的に設定する必要があります。
特に注意を払うべきプロパティカテゴリは以下のとおりです。これらはサイレントな構成ドリフトの一般的な原因です:
- タイムアウト値 (例: パブリック IP の
idle_timeout_in_minutesはデフォルトで4、ライブデプロイでは30がよく使われます) - ネットワークポリシーフラグ (例: サブネットの
private_endpoint_network_policiesはデフォルトで"Enabled"、既存のサブネットでは"Disabled"であることがよくあります) - SKU と割り当て (例: パブリック IP の
sku、allocation_method) - 可用性ゾーン (例: VM ゾーン、パブリック IP ゾーン)
- ストレージおよびデータベースリソースの冗長性とレプリケーション設定
完全なライブプロパティを取得するには、明示的な az コマンドを使用します。例えば:
az network public-ip show --ids <resource_id> --query "{idleTimeout:idleTimeoutInMinutes, sku:sku.name, zones:zones}" -o json
az network vnet subnet show --ids <resource_id> --query "{privateEndpointPolicies:privateEndpointNetworkPolicies, delegation:delegations}" -o json
az resource list の出力だけに依存しないでください。ネストされたプロパティや計算されたプロパティが省略されている可能性があります。
モジュールのバージョンを明示的に固定します:
module "example" {
source = "Azure/<module>/azurerm"
version = "<latest-compatible-version>"
}
7) 生成されたコードの検証
次を実行します:
terraform init
terraform fmt -recursive
terraform validate
terraform plan
期待される出力: 構文エラーなし、検証エラーなし、検出されたインフラストラクチャの意図と一致するプラン。
トラブルシューティング
| 問題 | 考えられる原因 | 対処方法 |
|---|---|---|
az コマンドで認証エラーが発生する | テナント/サブスクリプションが間違っている、または必要な RBAC ロールがない | az login を再実行し、サブスクリプションのコンテキストを確認して、必要な権限があることを確認します |
| 検出結果が空である | スコープが正しくない、またはスコープ内にリソースがない | スコープの入力を再確認し、スコープを指定した一覧表示/表示コマンドをもう一度実行します |
| リソースの種類に対応する AVM モジュールが見つからない | そのリソースの種類がまだ AVM でカバーされていない | その種類にはネイティブの azurerm_* リソースを使用し、未対応の範囲を記録します |
terraform validate が失敗する | 変数が不足している、または依存関係が解決されていない | 必要な変数と明示的な依存関係を追加してから、検証を再実行します |
| モジュール内に不明な引数がある、または変数が見つからない | AVM の変数名が azurerm プロバイダーの引数名と異なる | モジュール README の variables.tf または Optional Inputs セクションで正しい名前を確認します |
| インポートブロックが失敗し、アドレスにリソースが見つからない | プロバイダーのラベル(azurerm_ と azapi_)が間違っている、サブモジュールのパスが不足している、または [0] インデックスが不足している | grep "^resource" .terraform/modules/<key>/main*.tf と grep "^module" を実行して、正確なアドレスを確認します |
terraform plan で、インポートしたリソースに予期しない ~ update が表示される | 実際の値が AVM モジュールのデフォルト値と異なる | az <resource> show で実際のプロパティを取得し、モジュールのデフォルト値と比較して、明示的に値を指定します |
| 子リソースモジュールで「provider configuration not present」と表示される | 親モジュールが所有する子リソースを、独立したモジュールとして宣言している | README の Required Inputs を確認し、誤った独立モジュールを削除して、親モジュールで文書化されている入力構造を使って子リソースをモデル化します |
| 入れ子になった子リソースのインポートで「resource not found」と表示される | 中間のモジュールパスが不足している、マップキーが間違っている、またはインデックスが不足している | ソース内のモジュールブロックと count/for_each を確認し、必要なすべてのモジュールセグメントとキー/インデックスを含む完全な入れ子のインポートアドレスを作成します |
| ツールが ARM リソース ID をファイルパスとして読み込もうとする、またはスコープについて繰り返し質問する | リソース ID が --ids 入力として扱われていない、またはエージェントがすでに提示されたスコープを信頼していない | ARM ID はクラウド識別子としてのみ扱い、az ... --ids ... を使用します。有効なスコープが 1 つ提示されたら、再度質問しないでください |
応答契約
結果を返す際は、次の内容を提示してください。
- 使用したスコープ(サブスクリプション、リソースグループ、またはリソース ID)
- 作成した検出ファイル
- 検出されたリソースの種類
- 選択した AVM モジュールとそのバージョン
- 生成または更新した Terraform ファイル
- 検証コマンドの結果
- ユーザーの入力が必要な未解決事項(ある場合)
エージェントの実行ルール
- スコープが指定されていない場合は、作業を続行しないでください。
- 検出したファイルと検証結果を一覧表示せずに、インポートが成功したと主張しないでください。
- Terraform を生成する前に、依存関係のマッピングを省略しないでください。
- まず AVM モジュールを優先し、AVM 以外の代替手段を使う場合は、その理由を明確に説明してください。
- コードを書く前に、すべての AVM モジュールの README を読んでください。 必須入力項目には、そのモジュールが所有する子リソースが記載されています。オプション入力項目には、正確な変数名と型が記載されています。使用例では、プロバイダー固有の規約(
parent_idとresource_group_nameのどちらを使うかなど)が示されています。README を読まずに進めることは、AVM ベースのインポートでコードエラーが起きる最も一般的な原因です。 - NIC、拡張機能、パブリック IP のリソースが単独で存在すると決めつけないでください。 すべての AVM モジュールについて、README に別のモジュールが必要と明記されていない限り、子リソースは親が所有するものとして扱ってください。兄弟モジュールを作成する前に、必須入力項目を確認してください。
- インポートアドレスを記憶から書かないでください。
terraform initの後、ダウンロードされたモジュールのソースを grep し、実際のプロバイダー(azurermとazapiのどちらか)、リソースラベル、サブモジュールのネスト、およびcountとfor_eachの使い方を確認してから、import {}ブロックを書いてください。 - ARM リソース ID をファイルパスとして扱わないでください。 リソース ID は Azure CLI の
--ids引数や API クエリに使用するものであり、ファイル入出力ツールに渡すものではありません。実際のワークスペースパスが指定されている場合に限り、ローカルファイルを読み込んでください。 - スコープがすでに判明している場合は、確認のための質問を最小限にしてください。 サブスクリプション、リソースグループ、特定のリソース ID がすでに指定されている場合は、コマンドを直接実行し、必要なコンテキストの不足が原因でコマンドが失敗した場合にのみ、追加の質問をしてください。
terraform planで破棄が 0 件となり、不要な変更がないことが確認されるまで、インポートが完了したと宣言しないでください。 テレメトリの+ createリソースは許容されます。実際のインフラストラクチャリソースに対する~ updateや- destroyはすべて解決する必要があります。