しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

既存の Azure Resource を Terraform での管理に切り替える

最初から Terraform を使って Azure のリソースを作成できれば問題ないですが、多かれ少なかれ既に手動で作成済みのリソースがあって、それを Terraform 管理下に入れたいケースが多いと思います。

既に Azure と Azure Pipelines での Terraform の利用については前回書いたので省略します。

今回は作成済みリソースを Terraform の管理下に入れる手順を試しておいたので、手順とはまったポイントをメモとして残します。今回のターゲットは 8 年前ぐらいに作ったリソースです。

リソースグループを丸ごと Terraform で管理するようにしますが、古いリソースはロケーションがぐちゃぐちゃなので上手くパラメータ化が出来ませんでした。今回、そこは妥協しました。

数が少ないので手作業で tf ファイルを書くのはギリセーフという感じです。本当なら terraformer とかを使って tf ファイルと tfstate を自動生成する方が安心です。

一応 terraformer は Azure にも対応していましたが、対応しているリソースが Resource Group だけなので今のところ使いものにならないです。

List of supported Azure resources:

  • resource_group
    • azurerm_resource_group

とはいえサポートが始まったばかりなので、今後対応リソースが増えるとは思います。地味に tf ファイルを書く部分がしんどいので、コントリビュートしようかなという気持ちになってきました。

とりあえず今回は手作業で Azure のリソースを Terraform での管理に切り替えていきました。

terraform import で管理下に入れる

terraform には既存のリソースをインポートするコマンドが用意されているので、これを使って tfstate を更新していきます。これだけで管理下に入れることができます。

terraform import を実行する前に、tf ファイルにインポートするリソースの定義を追加しておきます。中身はこれから書いていくので空のままで良いです。

resource "azurerm_resource_group" "default" {
}

resource "azurerm_app_service_plan" "default" {
}

resource "azurerm_app_service" "shibayan" {
}

resource "azurerm_storage_account" "shibayan" {
}

tf ファイルにリソースを追加したら、後は terraform import で 1 つずつインポートしていきます。

terraform import azurerm_resource_group.default /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/Default-Web-JapanEast

Azure は全てのリソースが一意な ID を持っているので、何も考えずにリソース ID を指定すれば良いです。

リソース ID は Azure CLI や ARM Explorer を使って確認しても良いですし、Azure Portal の Properties を開いてもリソース ID を確認できます。

tf ファイルを頑張って書く

リソースのインポートが終わったら、後はひたすら tf ファイルを書いていきます。terraform state show を使うと tfstate ファイルからある程度生成できるので、上手いこと使っていきましょう。

\shibayan-terraform>terraform state show azurerm_resource_group.default
# azurerm_resource_group.default:
resource "azurerm_resource_group" "default" {
    id       = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/Default-Web-JapanEast"
    location = "southcentralus"
    name     = "Default-Web-JapanEast"
    tags     = {}
}

ただし全てを追加するのは NG なので、この場合は id は除外して tf ファイルに追加します。

resource "azurerm_resource_group" "default" {
  name     = "Default-Web-JapanEast"
  location = "southcentralus"
}

App Service の場合はリソースグループ名や App Service Plan の ID を指定する必要がありますが、べた書きせずにちゃんと Terraform 内のリソース参照で解決するようにします。

terraform plan で差分を確認

tf ファイルが書けたら terraform plan を実行して差分を確認します。今回の場合は既に存在するリソースが正なので、差分が出ないようにプロパティの追加や修正をします。

闇雲に tf ファイルのプロパティを増やしたくなかったので、ある程度デフォルト値を使いながら書きました。

大体は問題なく Terraform 管理下に切り替えできていましたが、App Service Plan だけ以下のように常にリソースの作り直しが要求されるという状態になりました。

大半のプロパティが追加になっていて明らかにおかしいです。調べてみると以下の Issue が見つかりました。

Azure Portal からコピーしたリソース ID は serverFarms と camel case になっていますが、Terraform Provider 側は serverfarms というように全て小文字で扱っているため、terraform import 時に tfstate が壊れるらしいです。最悪の挙動にあたりました。

terraform state rm を使って tfstate から削除した後、修正したリソース ID を使って再度 terraform import を実行すると解消します。

これで terraform plan での差分が出なくなったので、管理下に入れることが出来ました。

完成した tf ファイルの例と terraform apply

今回作成した tf ファイルは以下のようになりました。リソースグループが South Central US に居るので、リソースグループのロケーションを使って共通化することが出来ませんでした。

ファイルのインデントは terraform fmt を使うと綺麗に整えてくれます。

resource "azurerm_resource_group" "default" {
  name     = "Default-Web-JapanEast"
  location = "southcentralus"
}

resource "azurerm_app_service_plan" "default" {
  name                = "Default2"
  location            = "japaneast"
  resource_group_name = "${azurerm_resource_group.default.name}"
  kind                = "app"

  sku {
    tier = "Standard"
    size = "S1"
  }
}

resource "azurerm_app_service" "shibayan" {
  name                = "shibayan"
  location            = "${azurerm_app_service_plan.default.location}"
  resource_group_name = "${azurerm_resource_group.default.name}"
  app_service_plan_id = "${azurerm_app_service_plan.default.id}"
  https_only          = true

  app_settings = {
    "WEBSITE_RUN_FROM_PACKAGE" = "1"
  }

  site_config {
    default_documents = [
      "index.html",
    ]
    ftps_state                = "Disabled"
    http2_enabled             = true
    min_tls_version           = "1.2"
    scm_type                  = "VSTSRM"
    use_32_bit_worker_process = true
  }
}

resource "azurerm_storage_account" "shibayan" {
  name                     = "shibayan"
  resource_group_name      = "${azurerm_resource_group.default.name}"
  location                 = "japaneast"
  account_kind             = "StorageV2"
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

何か適当に App Service の設定を変更して terraform apply を実行すると、Azure 上のリソースにちゃんと反映されます。後は Git で管理するようにして、Azure Pipelines で CI を組めば完成です。

次は Staging / Production といったように、環境毎にリソースを作れるような Terraform 定義を作って試してみようかと思っています。後は同じ設定で別名のリソースをループで作ってみるとか。

Azure Pipelines の Approval と組み合わせれば、かなり使い勝手が良いと思うので楽しみです。