しばやん雑記

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

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

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

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

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

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

f:id:shiba-yan:20191015194226p:plain

数が少ないので手作業で 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 だけ以下のように常にリソースの作り直しが要求されるという状態になりました。

f:id:shiba-yan:20191015181627p:plain

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

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

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

f:id:shiba-yan:20191015192256p:plain

これで 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 を組めば完成です。

f:id:shiba-yan:20191015191457p:plain

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

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