はじめに

本ブログは Azure の Azure Static Web Apps という PaaS でホスティングしています。 今までは Azure Portal で画面ポチポチしながら設定変更していましたが、なにかあったときに備えてリソースを Terraform で管理するようにします。 いわゆる Infrastructure as Code です。

今回は Azure Static Web Apps の既存リソースを Terraform 構成ファイルに落とし込んで、Terraform 自体を Terraform Cloud に管理してもらう形にします。 どちらかと言うと Terraform Cloud を使う方便だったり。

Azure Static Web Apps

既存リソースの Terraform 化

まず、Azure Static Web Apps のリソースを Terraform で管理できるように構成ファイルを作成します。

事前にプロバイダの設定ファイルを作成してから terraform init しておきます。

terraform {
  required_version = ">= 0.12"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

そして、staticsite.tf ファイルにリソースの箱だけ用意しておきます。 Azure Static Web Apps のリソースはサイト本体を管理する [azurerm_static_site] と、サイトアクセスに利用するカスタムドメインを管理する [azurerm_static_site_custom_domain] の2種類です。

resource "azurerm_static_site" "blog" {
}

resource "azurerm_static_site_custom_domain" "blog" {
}

Azure CLI で対象のサブスクリプションにログインしてから terraform import コマンドを実行して、既存リソースの情報を取得します。

SUBSCRIPTION=00000000-0000-0000-0000-000000000000
RG=static-web-apps
SITE=blog
DOMAIN=blog.nnstt1.dev

# azurerm_static_site
terraform import azurerm_static_site.blog \
    /subscriptions/$SUBSCRIPTION/resourceGroups/$RG/providers/Microsoft.Web/staticSites/$SITE

# azurerm_static_site_custom_domain
terraform import azurerm_static_site_custom_domain.blog \
    /subscriptions/$SUBSCRIPTION/resourceGroups/$RG/providers/Microsoft.Web/staticSites/$SITE/customDomains/$DOMAIN

terraform.tfstate ファイル内に Azure Static Web Apps のリソース情報が出力されるので、必要なものだけ staticsite.tf ファイルに書き写して terraform plan コマンドで差分が出ないか確認します。

注意点

ここで、「terraform import 後のカスタムドメインは必ず差分がある」という注意点について説明します。

terraform import で取得した [azurerm_static_site_custom_domain] リソースの引数 validation_type は null になっています。 この情報をもとに構成ファイルを作成して terraform plan を実行しても「`validation_type` can’t be empty string」というエラーになります。

Azure Static Web Apps でカスタムドメインを設定する際、カスタムドメイン名がユーザの管理下に置かれたものかチェックするために CNAME レコード または TXT レコードを使って検証します。

引数 validation_type はドメインの検証方法を定義する項目になってるおり、カスタムドメイン利用時は CNAME または TXT のどちらかの値を設定する必要がありますが、CNAME または TXT のどちらで検証したかの情報はリソースに残らないようです。

そのため、terraform import で取得した [azurerm_static_site_custom_domain] の引数 validation_type は null として状態ファイルに格納されてしまいます。 この状態で構成ファイルの引数 validation_type に値を設定すると、差分として検出されてしまい、初回の terraform apply ではカスタムドメインが置換されてしまいます。

ただし、一度 terraform apply をしてしまえば構成ファイルの情報で terraform.tfstate ファイルが更新されるので、2回目以降は差分を検出しなくなります。 言い換えれば、terraform import 後に直接 terraform.tfstate を書き換えてしまえば初回でも置換せずに済みそうです。

このような「クラウドリソース側に残らない情報を Terraform で管理する」ってことはよくあるんですかね、Terraform の経験値が少なく初めて遭遇しました。

Terraform Cloud

Terraform の構成ファイルと状態ファイルを用意できたら、次に状態ファイルを Terraform Cloud で管理できるようにします。

Terraform Cloud とはなんぞや、というのは公式ドキュメントを参照ください。

Home - HCP Terraform | Terraform | HashiCorp Developer

HCP Terraform is an application that helps teams use Terraform to provision infrastructure.


Terraform Cloudは、Terraformをチームで共同利用するためのアプリケーションです。 Terraformの実行を一貫性のある信頼できる環境で管理し、共有された状態やシークレットデータへの簡単なアクセス、インフラへの変更を承認するためのアクセスコントロール、Terraformモジュールを共有するためのプライベートレジストリ、Terraformの設定内容を管理するための詳細なポリシーコントロールなどが含まれています。

Terraform Cloud はホスティングサービスとして https://app.terraform.io で提供されています。 小規模なチームは無料でサインアップし、Terraform をバージョンコントロールに接続し、変数を共有し、安定したリモート環境で Terraform を実行し、リモートの状態を安全に保存することができます。 有料版では、5人以上のユーザーの追加、異なるレベルの権限を持つチームの作成、インフラストラクチャの作成前のポリシーの適用、より効率的なコラボレーションが可能になります。

初期設定

Terraform Cloud を使うためにはアカウント登録が必要です。

HashiCorp Cloud Platform (HCP) のアカウントを使うか、Terraform Cloud にアカウントを作ることで利用できるようです。

今回は HCP アカウントを使うことにします。 しかし、HashiCorp の SaaS 自体を使ったことがなかったので HCP アカウントも作らないといけません。 HCP アカウントのほうは GitHub のアカウントと連携できるようなので、GitHub アカウントを使うことします。

アカウントを登録して Getting Started の画面が表示されたら、Terraform Cloud を利用開始できます。

アカウント登録後は3通りのセットアップ手順が表示されます。 今回は既に Terraform 状態ファイルを作成しているので「Import local state」を選択します。

「Import local state」を選択すると、自動的に「組織 (Organization)」が作成されて、状態ファイルを Terraform Cloud に移行する手順が表示されます。 ワークスペース名は任意で設定できます。

手順どおり terraform login のあとに terraform init を実行すると、Terraform Cloud のワークスペースに状態ファイルが移されて、ローカルの状態ファイルは空っぽになります。

注意点

セットアップ画面の「Import local state」で大量に「組織 (Organization)」が作成されるパターンがありました。

「Import local state」で “Organization xxx is ready.” を表示したあと、選択画面に戻って「Start from scratch」を選択します。 そして選択画面にもどって再度「Import local state」を選択します。 すると、“Organization xxx1 is ready.” のように先ほどとは異なる連番のついた組織名が作成されます。

下図は同じ動作を繰り返して、連番 3 が割り当てられた組織が作られたとこです。

このように、セットアップ画面を行ったり来たりしているうちに、組織が作られてしまうことがあります。

組織はあとから削除可能なので大量に作ってしまっても問題ないですが、気づかないうちに異なる組織で作業していた、なんてことがないように注意は必要です。

サービスプリンシパル作成

この状態では Terraform Cloud から Azure へのアクセスができないためエラーになります。

そこで、Azure AD にサービスプリンシパルを作成して Terraform Cloud に Azure のリソースを操作するための権限を与えます。 Azure Portal でも作成できますが、ここでは Azure CLI でサービスプリンシパルを作ります。

az ad sp create-for-rbac \
    --name="Terraform Cloud" \
    --role="Contributor" \
    --scopes="/subscriptions/SUBSCRIPTION_ID"

作成したサービスプリンシパルの情報 (appId, password, tenant) が出力されるので、Terraform Cloud のワークスペースにある変数に設定します。

変数には「Terraform variable」と「Environment variable」という2種類のカテゴリがありますが、ここでは「Environment variable」を選択します。 また、変数 ARM_CLIENT_SECRET は「Sensitive」というチェックをいれます。

変数名
ARM_SUBSCRIPTION_ID サブスクリプション ID
ARM_CLIENT_ID サービスプリンシパルの appId
ARM_CLIENT_SECRET サービスプリンシパルの password
ARM_TENANT_ID サービスプリンシパルの tenant

下図が設定後の Terraform Cloud の画面です。

サービスプリンシパルの設定をしたあとに terraform apply を実行してみると、ローカルでの実行と異なり「Running apply in Terraform Cloud」と表示されているのが確認できます。

Running apply in Terraform Cloud. Output will stream here. Pressing Ctrl-C
will cancel the remote apply if it's still pending. If the apply started it
will stop streaming the logs, but will not stop the apply running remotely.

Preparing the remote apply...

To view this run in a browser, visit:
https://app.terraform.io/app/nnstt1/blog/runs/run-3GoanbaVZRQpeRaA

Waiting for the plan to start...

Terraform v1.2.7
on linux_amd64
Initializing plugins and modules...
azurerm_static_site.blog: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/static-web-apps/providers/Microsoft.Web/staticSites/blog]
azurerm_static_site_custom_domain.blog: Refreshing state... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/static-web-apps/providers/Microsoft.Web/staticSites/blog/customDomains/blog.nnstt1.dev]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

以上で、状態ファイルを Terrafrom Cloud に移行できました。

このように Terraform Cloud では実行履歴も確認できます。

おわりに

今回は既存の Azure Static Web Apps のリソースを Terraform で管理できるようにしました。 また、Terraform の状態と実行は Terraform Cloud 側で管理するように対応しました。

Azure Static Web Apps には GitHub のどのリポジトリのソースコードを利用するか連携する機能があるのですが、現時点の Terraform プロバイダでは GitHub 連携の設定項目はありませんでした。 そのため、リソースのすべての情報を管理できていない点は注意が必要です。

次は GitHub と Terraform Cloud を連携して CI/CD パイプラインの構築をやっていこうと思います。