Terraform with Azure 入門

# 環境構築
こちらのブログに従い、DevContainerで開発環境を整える。

https://blog.jbs.co.jp/entry/2024/12/26/091915

```devcontainer.json
{
  "name": "Terraform",
  "image": "mcr.microsoft.com/devcontainers/base:jammy",
  "features": {
    "ghcr.io/devcontainers/features/azure-cli:1": {},
    "ghcr.io/devcontainers-extra/features/terraform-asdf:2": {}
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "HashiCorp.terraform",
        "ms-vscode.azurecli",
		    "ms-vscode.azure-account",
		    "ms-azuretools.vscode-azureterraform",
		    "ms-azuretools.vscode-docker"
      ]
    }
  }
}
```

Dev Containerが起動すると、Github Copilotを入れるように促されるので、有効にする。拡張機能は主にTerraform、Azure、Github Copilotが入る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/16998863-5992-91b6-627d-462b323eb352.png)

この状態でmain.tfを作成すると、Github CopilotやTerraformによって以下のように諸々がAuto Completeされるようになる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/a4fc01fb-54e0-34f0-5cb7-dcf754ddd947.png)

# 参考サイト

https://developer.hashicorp.com/terraform/tutorials/azure-get-started

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs

こちらの動画に従ってやってみる。
Deploy Infrastructure to Azure with Terraform - Full Course

https://www.youtube.com/watch?v=HdMB2YCtVr4

# Terraformファイルを作ってみる
まずは細かいことを気にする前に実際に作ってみる。ここでは、Storage AccountでStatic Websiteをホストするようにしてみる。
```main.tf
provider "azurerm" {
  features {}
  subscription_id = "xxx"
}

# create a resource group in japan east
resource "azurerm_resource_group" "rg" {
  name     = "rg-terraform-simari"
  location = "Japan East"
}

# create a storage account in the resource group
resource "azurerm_storage_account" "sa" {
  name                     = "stterraformsimari"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
  account_kind             = "StorageV2"
}

resource "azurerm_storage_account_static_website" "static_website" {
  storage_account_id = azurerm_storage_account.sa.id
  index_document     = "index.html"
}

# create a container in the storage account
resource "azurerm_storage_container" "container" {
  name                  = "$web"
  storage_account_id    = azurerm_storage_account.sa.id
  container_access_type = "private"
}

# Add a index.html file to the storage account
resource "azurerm_storage_blob" "blob" {
  name                   = "index.html"
  storage_account_name   = azurerm_storage_account.sa.name
  storage_container_name = "$web"
  type                   = "Block"
  content_type           = "text/html"
  source_content         = "<html><head><title>Hello, World from Terraform!</title></head><body><h1>Hello, World from Terraform!</h1></body></html>"
  depends_on             = [azurerm_storage_account_static_website.static_website, azurerm_storage_container.container]
}

```

コマンドいライン上でaz loginしてからterraform initをするとこんな感じのフォルダ構造になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/5b64729d-1f18-6c7c-9c2e-85506933ed27.png)

gitignoreはとりあえずここからとってきた。

https://www.toptal.com/developers/gitignore/api/terraform

# Terraformを実行してみる
VSCodeのターミナルでaz loginした状態で以下を実行。
terraformファイルにエラーがある場合は、planでエラーになる。

ちなみにterraformをAzureに認証させるにはいろんな方法がある。今回は簡単に az loginしてしまったが、環境変数にService PrincipalのIDとパスワードを設定する方法などもある。

https://learn.microsoft.com/ja-jp/azure/developer/terraform/authenticate-to-azure?tabs=bash#2-authenticate-terraform-to-azure


実際にAzure環境でStorage Accountができ、Websiteがホストされていることを確認できた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/c4c9f7d6-24db-fd62-ef0e-31ca06ecdfaf.png)


# Azure Provider
Azureとやりとりするよというための機能群。プログラミング言語でのライブラリのようなものと考える。

- 認証: Azure Providerは、AzureのAPIに対する認証を処理する
- リソース管理: ProviderはAzureリソースの実際の作成、更新、削除を行う
- API通信: ModuleはProviderを通じてAzureのAPIと通信する

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret
```
variable "client_secret" {
}

# We strongly recommend using the required_providers block to set the
# Azure Provider source and version being used
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=4.1.0"
    }
  }
}

# Configure the Microsoft Azure Provider
provider "azurerm" {
  features {}
  version         = "=> 1.0"
  client_id       = "00000000-0000-0000-0000-000000000000"
  client_secret   = var.client_secret
  tenant_id       = "10000000-0000-0000-0000-000000000000"
  subscription_id = "20000000-0000-0000-0000-000000000000"
}
```

# Remote StateにAzure Storage Accountを利用する
Azure StorageにTerraform Stateファイルを配置する。以下のMicrosoftの例によるとAccount Keyで接続しているが本番環境ではもっとセキュアにやるべきかもしれない。そもそもStateファイルには機密情報が入る可能性があるので、セキュアに扱うべき。

https://learn.microsoft.com/ja-jp/azure/developer/terraform/store-state-in-azure-storage?tabs=azure-cli

https://rnakamine.hatenablog.com/entry/2020/09/06/161031

Microsoftのサンプルに従うと以下のようにキーを利用して接続することになる。
```
ACCOUNT_KEY=$(az storage account keys list --resource-group $RESOURCE_GROUP_NAME --account-name $STORAGE_ACCOUNT_NAME --query '[0].value' -o tsv)
export ARM_ACCESS_KEY=$ACCOUNT_KEY
export ARM_ACCESS_KEY=$(az keyvault secret show --name terraform-backend-key --vault-name myKeyVault --query value -o tsv)

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
  }
  backend "azurerm" {
      resource_group_name  = "tfstate"
      storage_account_name = "<storage_account_name>"
      container_name       = "tfstate"
      key                  = "terraform.tfstate"
  }

}
```

## Storage Accountの前準備
Storage Accountにtfstateを保管するうえで、セキュアに扱う手順については以下のyoutube動画が参考になった。

https://www.youtube.com/watch?v=iVyKvopGnrQ

- Storage Account上のStateファイルをセキュアに扱う上での考慮事項
    - アクセス
        - RBAC権限でアクセスを絞る
        - ContainerはPrivateにする
        - Storqage AccountにはEntra ID認証（Service Principal）を使い、Storage Accountのキー認証を無効化する
        - NW制限を実施しPrivate Endpoint経由でアクセスするようにする
    - 暗号化
        - Storage Account上のStateファイルを保存時、転送時に暗号化する
    - 保護
        - GRSにして冗長性を高める
        - バージョニングを有効に

Storage Accountの設定のメインのところを抜き出してみる。
```main.tf
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
    azuread = {
      source  = "hashicorp/azuread"
      version = "~>2.0"
    }
  }
  required_version = ">=1.5"
}


provider "azurerm" {
  features {}
  # Set storage access to use Azure AD instead of storage key SAS
  storage_use_azuread = true
}

# Create storage account
resource "azurerm_storage_account" "main" {
  name                = local.naming_string
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

  account_tier                      = "Standard"
  account_kind                      = "StorageV2"
  account_replication_type          = "GRS"
  enable_https_traffic_only         = true
  min_tls_version                   = "TLS1_2"
  shared_access_key_enabled         = false
  default_to_oauth_authentication   = true
  infrastructure_encryption_enabled = false
  blob_properties {
    versioning_enabled            = true
    change_feed_enabled           = true
    change_feed_retention_in_days = 90
    last_access_time_enabled      = true
    delete_retention_policy {
      days = 30
    }
    container_delete_retention_policy {
      days = 30
    }
  }
  sas_policy {
    expiration_period = "00.02:00:00"
    expiration_action = "Log"
  }
}

# Create a service principal to assign role to
data "azuread_client_config" "current" {}

resource "azuread_service_principal" "main" {
  client_id                    = azuread_application.main.client_id
  app_role_assignment_required = false
  owners                       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal_password" "main" {
  service_principal_id = azuread_service_principal.main.id
}

resource "azurerm_role_assignment" "main" {
  scope = azurerm_storage_account.main.id
  role_definition_name = "Storage Blob Data Contributor"
  principal_id         = azuread_service_principal.main.object_id

  skip_service_principal_aad_check = true
}

output "service_principal" {
  value = azuread_service_principal.main
}

output "subscription_id" {
  value = data.azurerm_subscription.main.subscription_id
}

output "service_principal_password" {
  value = nonsensitive(azuread_service_principal_password.main.value)
}
```

Terraform専用のService Principalが作成された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/88f9e840-421f-4eeb-aeb9-b1649ab0bdad.png)

Storage Accountに対してService PrincipalがStorage Blob Data Contributor権限をもっている。更にセキュアにやるなら、カスタムロールを利用すればコンテナレベルで権限を絞れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/e121b755-3096-4d21-bf19-48fa592c39cd.png)

## BackendをService PrinciplでInitializeする
terraformブロックでuse_azuread_authをtrueにする。
```main.tf
terraform {
  backend "azurerm" {
    storage_account_name = "STORAGE_ACCOUNT_NAME"
    container_name       = "tfstate"
    key                  = "tacowagon/terraform.tfstate"
    use_azuread_auth     = true
    client_id            = "client_ID"
    client_secret        = "CLIENT_SECRET"
    tenant_id            = "TENANT_ID"
    subscription_id      = "SUBSCRIPTION_ID"
  }

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
  }
}
```
設定をbackend.tfに保持する。
これはService PrincipalのClient Secretが入っているので、gitignoreに定義してチェックインしないようにする。
```backend.tf
storage_account_name="stname"
container_name="tfstate"
client_id="xxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
client_secret="TiR8Q~I2xxxxxxxx"
key="tacowagon/terraform.tfstate"
tenant_id="xxxxxx-xxxx-xxxx-xxxx-xxxx"
subscription_id="xxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
```

```cli.txt
terraform init -backend-config="backend.tfbackend"
```

tfstateがStorage Account上に作成された。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/c3973f3a-f33c-4087-8475-18e3570a7837.png)

このService PrincipalはCICDなどでも使うことができる。