🛕

Azure Export for Terraform で既存の Azure 環境を Terraform にエクスポートする

2024/04/12に公開

はじめに

Azure の既存リソースは、[テンプレートのエクスポート] メニューから ARM テンプレートの形でダウンロードすることができます。テンプレートと言いつつそのまま流用できるわけではないので、転用する場合には編集が必要なのですが、エクスポート自体はできます。ふと、Terraform でエクスポートするツールはないのか探していたら、Azure Export for Terraform[1] というツールを見つけたので試してみます。

Azure Export for Terraform

このツールはこちらの GitHub リポジトリ[2] で公開されているので、環境に合わせてインストールします。

使用方法

Terraform コードとしてエクスポートする範囲を「リソースグループ/リソース/リソースグラフのクエリ結果」から選択できます。シンプルなのはリソースグループ単位でしょう。この場合、次のように記述します。

自前の環境に適用してみる

ハンズオン用に作っていた VNet に VM がいるだけのシンプルなリソースグループがあったので、そのリソースグループから Terraform を作成してみます。

既存環境のインポート

作業ディレクトリ単位で Terraform のプロジェクトが initialize されるので、専用のディレクトリを用意してそこで実行します。手元の環境は Winodws なので PowerShell から実行します。

aztfexport resource-group 202401-handson

このような形で抽出された情報が確認されます。w キー押下にてインポートに進みます。

   Microsoft Azure Export for Terraform

     20240401-handson

    10 items

  │ 💡/subscriptions/xxxx/resourceGroups/20240401-HANDSON/providers/Microsoft.Compute/v
  │ azurerm_virtual_machine_extension.res-0

    💡/subscriptions/xxxx/resourceGroups/20240401-HANDSON/providers/Microsoft.Compute/vi
    azurerm_virtual_machine_extension.res-1

    💡/subscriptions/xxxx/resourceGroups/20240401-handson
    azurerm_resource_group.res-2

    💡/subscriptions/xxxx/resourceGroups/20240401-handson/providers/Microsoft.Compute/vi
    azurerm_windows_virtual_machine.res-3

    💡/subscriptions/xxxx/resourceGroups/20240401-handson/providers/Microsoft.Network/ne
    azurerm_network_interface.res-4

    💡/subscriptions/xxxx/resourceGroups/20240401-handson/providers/Microsoft.Network/ne
    azurerm_network_security_group.res-5



    ••

    ↑/k up • ↓/j down • / filter • delete skip • e show error • r show recommendation • w import • s save • q

w 押下後、Importing ~ というメッセージが表示されます。

   Microsoft Azure Export for Terraform

  ⣻   Importing /subscriptions/xxxx/resourceGroups/20240401-HANDSON/providers/Microsoft.

しばらく待つと、ローカルの Terraform プロジェクトに対する Azure 環境のインポートが終了します。

   Microsoft Azure Export for Terraform

  Terraform state and the config are generated at: C:<PATH_TO_WORKING_DIR>

  Press any key to quit

code . で VSCode を開いて中身を確認してみます。以下のようなファイルに分割されて作成されています。

このまま plan をしてみましょう。

$ terraform plan
╷
│ Error: "admin_password" has to fulfill 3 out of these 4 conditions: Has lower characters, Has upper characters, Has a digit, Has a special character other than "_", fullfiled only 2 conditions
│ 
│   with azurerm_windows_virtual_machine.res-3,
│   on main.tf line 37, in resource "azurerm_windows_virtual_machine" "res-3":
│   37:   admin_password                                         = "ignored-as-imported"

VM の管理者パスワードはセキュアな文字列であるため、Terraform 側にインポートされておらず、仮置きの文字列(ignored-as-imported)が入っていました。そして、その文字列が、VM に設定できる管理者パスワードの要件を満たしていなかったわけですね。一旦エラーの解消のためにパスワードをべた書きで設定します。

main.tf
  # admin_password = "ignored-as-imported"
  admin_password   = "AzureAdmin123!"

この状態で plan してみましょう。現在の Azure 環境と差分がない状態になりました。一応これで既存の Azure インフラをローカルの Terraform コードとして表現できている状態にはなったわけですね。

$ terraform plan
No changes. Your infrastructure matches the configuration.

管理者パスワードのべた書きが気になるので、一旦変数として読み込ませる形にしました。

main.tf
  # admin_password = "ignored-as-imported"
  admin_password   = var.admin_password
variables.tf
variable vm_admin_password {
	sensitive = true
}

この状態で再度 plan してみましょう。差分があるといわれました。ちゃんと読んでいないのですが、Microsoft Q&A のこのあたり[3] が参考になるかもしれません。

$ terraform plan
var.vm_admin_password
  Enter a value:
  ...
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # azurerm_windows_virtual_machine.res-3 will be updated in-place
  ~ resource "azurerm_windows_virtual_machine" "res-3" {
        id                                                     = "/subscriptions/xxxx/resourceGroups/20240401-handson/providers/Microsoft.Compute/virtualMachines/vm-module03"
        name                                                   = "vm-module03"
        tags                                                   = {}
        # (26 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

ちなみに、.tfstate 側では、パスワードはこのように認識されています。

terraform.tfstate
...(略)
 {
      "mode": "managed",
      "type": "azurerm_windows_virtual_machine",
      "name": "res-3",
      "provider": "provider[\"registry.terraform.io/hashicorp/azurerm\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "additional_capabilities": [],
            "additional_unattend_content": [],
            "admin_password": "ignored-as-imported",
            "admin_username": "AzureAdmin",
...(略)

パスワード部分の書き方の影響かと思われるためそのまま apply します。

$ terraform apply
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

apply 後も .tfstate 側の admin_password は変更されませんでした。そして、apply 時に渡した admin_password で Azure VM に接続しに行ってもはじかれました。Azure portal 側での VM 作成時に指定した admin_password で認証するとログインできました。パスワードのリセットは、Azure portal でも特別なメニューとして用意されているため、単にリソースのプロパティで変更するだけでは、管理者ユーザーのパスワードは変更できない可能性があります(要確認)。

リソースを追加してみる

既存環境が手元に用意できたので、ここにリソースを追加してみます。たとえば、ストレージアカウントを追加してみましょう。

main.tf
resource "azurerm_storage_account" "test" {
  name                     = "zukakostrgaccttest"
  resource_group_name      = azurerm_resource_group.res-2.name
  location                 = azurerm_resource_group.res-2.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

plan してみます。追加したストレージ アカウントが差分として確認できました。

$ terraform plan
Terraform will perform the following actions:

  # azurerm_storage_account.test will be created
  + resource "azurerm_storage_account" "test" {
      + access_tier                       = (known after apply)
      + account_kind                      = "StorageV2"
      + account_replication_type          = "LRS"
      + account_tier                      = "Standard"
      + allow_nested_items_to_be_public   = true
      + cross_tenant_replication_enabled  = true
      + default_to_oauth_authentication   = false
      + enable_https_traffic_only         = true
      + id                                = (known after apply)
      + infrastructure_encryption_enabled = false
      + is_hns_enabled                    = false
      + large_file_share_enabled          = (known after apply)
      + location                          = "japaneast"
      + min_tls_version                   = "TLS1_2"
      + name                              = "zukakostrgaccttest"
      + nfsv3_enabled                     = false
      + primary_access_key                = (sensitive value)
      + primary_blob_connection_string    = (sensitive value)
      + primary_blob_endpoint             = (known after apply)
      + primary_blob_host                 = (known after apply)
      + primary_connection_string         = (sensitive value)
      + primary_dfs_endpoint              = (known after apply)
      + primary_dfs_host                  = (known after apply)
      + primary_file_endpoint             = (known after apply)
      + primary_file_host                 = (known after apply)
      + primary_location                  = (known after apply)
      + primary_queue_endpoint            = (known after apply)
      + primary_queue_host                = (known after apply)
      + primary_table_endpoint            = (known after apply)
      + primary_table_host                = (known after apply)
      + primary_web_endpoint              = (known after apply)
      + primary_web_host                  = (known after apply)
      + public_network_access_enabled     = true
      + queue_encryption_key_type         = "Service"
      + resource_group_name               = "20240401-handson"
      + secondary_access_key              = (sensitive value)
      + secondary_blob_connection_string  = (sensitive value)
      + secondary_blob_endpoint           = (known after apply)
      + secondary_blob_host               = (known after apply)
      + secondary_connection_string       = (sensitive value)
      + secondary_dfs_endpoint            = (known after apply)
      + secondary_dfs_host                = (known after apply)
      + secondary_file_endpoint           = (known after apply)
      + secondary_file_host               = (known after apply)
      + secondary_location                = (known after apply)
      + secondary_queue_endpoint          = (known after apply)
      + secondary_queue_host              = (known after apply)
      + secondary_table_endpoint          = (known after apply)
      + secondary_table_host              = (known after apply)
      + secondary_web_endpoint            = (known after apply)
      + secondary_web_host                = (known after apply)
      + sftp_enabled                      = false
      + shared_access_key_enabled         = true
      + table_encryption_key_type         = "Service"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

問題なさそうなので apply します。

$ terraform apply
Plan: 1 to add, 0 to change, 0 to destroy.
azurerm_storage_account.test: Creating...
azurerm_storage_account.test: Still creating... [11s elapsed]
azurerm_storage_account.test: Still creating... [21s elapsed]
azurerm_storage_account.test: Creation complete after 28s [id=/subscriptions/42edd95d-ae8d-41c1-ac55-40bf336687b4/resourceGroups/20240401-handson/providers/Microsoft.Storage/storageAccounts/zukakostrgaccttest]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

リソースグループにストレージ アカウントが追加されました!

おわりに

Azure Export for Terraform を使って現行リソースの Terraform 表現を手元に持ってきて、必要に応じて変更を加えていくという流れを試してみました。別環境に構築するためのリードタイム短縮的な使い方をする場合でも、ARM テンプレートよりは可読性が高いため、利用できるユースケースは多いかもしれません。

脚注
  1. https://learn.microsoft.com/ja-jp/azure/developer/terraform/azure-export-for-terraform/export-terraform-overview ↩︎

  2. https://github.com/Azure/aztfexport/releases ↩︎

  3. https://learn.microsoft.com/en-us/answers/questions/1433179/how-to-change-the-admin-password-of-a-vm-created-u ↩︎

GitHubで編集を提案
Microsoft (有志)

Discussion