【Terraform de Azure】 Azureで事前に準備しておくべきこと



### 概要
「Infrastructure as Code」ということで、Terraform を用いて Azureを構成するときに、あらかじめ Azureに仕込んでおくべきことを記載しています。 おおまかな流れは以下となります。

1. Terraform の構成ファイル「tfstate」の保存場所の作成（ローカルに保存する場合は不要）
2. Terraform でAzure上に環境構築するためのサービスプリンシパルの作成
3. 確認のため、Terraform でリソースグループを作成してみる

### ローカル環境

- macOS Monterey 12.0.1 
- Azure CLI 2.28.0
- terraform v1.0.11

### 前提条件

1. Azure環境がすでに用意されていること（テナント/サブスクリプション）
2. ローカル環境に「azure cli」がインストールされていること
3. ローカル環境に「terraform」環境が構成されていること

---

## 準備

### Terraform の構成ファイル 「tfstate」 の保存場所の作成 （ローカルに保存する場合は不要）

```
## ローカル環境変数の定義
export RG_TF_NAME=rg-ituru-tfstate
export SUBS_NAME=PSG2-01
export STORAGE_TF_ACCOUNT=iturutfstate
export STORAGE_TF_CONTAINER=tfstate

## 使用するテナントへのログイン
$ az login --tenant <tenant_id>

## 使用サブスクリプションの定義
$ az account set --subscription $SUBS_NAME

## teffarom tfstate 用のリソースグループ作成
$ az group create --name $RG_TF_NAME --location japaneast

## teffarom tfstate 用のストレージアカウントの作成
$ az storage account create --name $STORAGE_TF_ACCOUNT --resource-group $RG_TF_NAME -l japaneast --sku Standard_LRS --enable-hierarchical-namespace true

## teffarom tfstate ファイルを保存するコンテナの作成
$ az storage container create --name $STORAGE_TF_CONTAINER --account-name $STORAGE_TF_ACCOUNT

## teffarom tfstate 用のストレージアカウントの認証情報の取得
$ az storage account keys list --account-name $STORAGE_TF_ACCOUNT --subscription $SUBS_NAME --resource-group $RG_TF_NAME --output table
CreationTime                      KeyName    Permissions    Value
--------------------------------  ---------  -------------  ----------------------------------------------------------------------------------------
2021-12-04T16:11:49.888094+00:00  key1       FULL           pppppppppppppppppppppppppppp....ppppp
2021-12-04T16:11:49.888094+00:00  key2       FULL           qqqqqqqqqqqqqqqqqqqqqqqqqqqq....qqqqq

## Terraform のためのローカル環境変数の定義
export ARM_ACCESS_KEY="<storage_account_key1>"
```

### Teffarom でAzure上に環境構築するためのサービスプリンシパルの作成

```
## ローカル環境変数の定義
export SP_TF_NAME=sp_itutu_terraform

## サービスプリンシパルの有効期限を２年で作成します（ロール割当なしで）
$ az ad sp create-for-rbac --name $SP_TF_NAME --skip-assignment --years 2
{
  "appId": "xxxxxxxx-xxxx-4444-9922-xxxxxxxxxxxx",      --> AZURE_CLIENT_ID として terraform実行ローカル環境変数として登録
  "displayName": "<ServicePrincial名>",
  "name": "xxxxxxxx-xxxx-4444-9922-xxxxxxxxxxxx",
  "password": "hogehogehogehogehogehogehogehogege",     --> AZURE_CLIENT_SECRET として terraform実行ローカル環境変数として登録
  "tenant": "zzzzzzzz-cccc-4645-5757-zzzzzzzzzzzz"      --> AZURE_TENANT_ID として terraform実行ローカル環境変数として登録
}

## 必要なスコープに必要なロールを割り与えるための appId の取得
$ APP_ID=$(az ad sp list --display-name $SP_TF_NAME --query '[].{ID:appId}' --output tsv)

## 必要なスコープに必要なロールを割り与えるための サブスクリプションID の取得
$ SUBS_ID=$(az account show --query "id" --output tsv)

## 必要なスコープに必要なロールの割当て
## 今回はSubscription各種リソースを定義するので、スコープ：Subscription ロール：Contributor（共同作成者）とします
$ az role assignment create --assignee $APP_ID --scope /subscriptions/$SUBS_ID --role Contributor
```

---

## Terraform でリソースグループを作成してみる

### terraform 定義ファイルの作成

メイン定義ファイル

```main.tf
# プロバイダーの定義
terraform {
  required_providers {
    azurerm =  "~> 2.33"
  }

  # tfstate ファイルの保存先（ローカルに保存の場合、不要）
  backend "azurerm" {
    storage_account_name = "iturutfstate"
    container_name       = "tfstate"
    key                  = "test01/terraform.tfstate"
  }
}

# Azureプロバイダ
provider "azurerm" {
  features {}
  tenant_id     = var.ARM_TENANT_ID
  client_id     = var.ARM_CLIENT_ID
  client_secret = var.ARM_CLIENT_SECRET
}

# リソースグループ
resource "azurerm_resource_group" "this" {
  name     = var.resource_group_name
  location = var.region
  tags     = var.tags_def
}
```

パラメータ定義ファイル

```variables.tf
# 環境変数（Azureサービスプリンシパル）
variable ARM_TENANT_ID {}
variable ARM_CLIENT_ID {}
variable ARM_CLIENT_SECRET {}

# タグ情報
variable tags_def {
  default = {
    owner      = "ituru"
    period     = "2022-03-31"
    CostCenter = "psg2"
  }
}

# 各種パラメータ 
variable resource_group_name {}
variable region {}
```

パラメータ値定義ファイル

```terraform.tfvars
# 環境変数の定義（Azureサービスプリンシパル）
ARM_TENANT_ID       = "zzzzzzzz-cccc-4645-5757-zzzzzzzzzzzz"
ARM_CLIENT_ID       = "xxxxxxxx-xxxx-4444-9922-xxxxxxxxxxxx"
ARM_CLIENT_SECRET   = "hogehogehogehogehogehogehogehogege"

# パラメータ値の定義
resource_group_name = "rg_ituru_test"     // リソースグループ名
region              = "japaneast"         // 利用リージョン
```

### terraform の実行

```
## init
$ terraform init

Initializing the backend...

Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Using previously-installed hashicorp/azurerm v2.88.1

Terraform has been successfully initialized!


## plan
$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_resource_group.this will be created
  + resource "azurerm_resource_group" "this" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "rg-ituru-test"
      + tags     = {
          + "CostCenter" = "psg2"
          + "owner"      = "ituru"
          + "period"     = "2022-03-31"
        }
    }

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


## apply
$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_resource_group.this will be created
  + resource "azurerm_resource_group" "this" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "rg-ituru-test"
      + tags     = {
          + "CostCenter" = "psg2"
          + "owner"      = "ituru"
          + "period"     = "2022-03-31"
        }
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

azurerm_resource_group.this: Creating...
azurerm_resource_group.this: Creation complete after 0s [id=/subscriptions/xxxxxxxx-1717-4343-9779-zzzzzzzzzzzz/resourceGroups/rg-ituru-test]

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

```

### terraform 実行後の確認

```
## 作成されたリソースグループの確認
$ az group show --name rg-ituru-test
{
  "id": "/subscriptions/xxxxxxxx-1717-4343-9779-zzzzzzzzzzzz/resourceGroups/rg-ituru-test",
  "location": "japaneast",
  "managedBy": null,
  "name": "rg-ituru-test",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": {
    "CostCenter": "psg2",
    "owner": "ituru",
    "period": "2022-03-31"
  },
  "type": "Microsoft.Resources/resourceGroups"
}


## tfstateファイルの確認
## コンテナー内の BLOB を一覧表示する
$ az storage blob list --account-name $STORAGE_TF_ACCOUNT --container-name $STORAGE_TF_CONTAINER --output table
Name                            Blob Type    Blob Tier    Length    Content Type      Last Modified              Snapshot
------------------------------  -----------  -----------  --------  ----------------  -------------------------  ----------
test01                          BlockBlob    Hot                                      2022-01-05T10:26:47+00:00
test01/terraform.tfstate        BlockBlob    Hot          1037      application/json  2022-01-05T10:38:36+00:00

```

ローカルの作業ディレクトの状況

```
$ tree -a         
.
├── .terraform
│   ├── providers
│   │   └── registry.terraform.io
│   │       └── hashicorp
│   │           └── azurerm
│   │               └── 2.88.1
│   │                   └── darwin_amd64
│   │                       └── terraform-provider-azurerm_v2.88.1_x5
│   └── terraform.tfstate
├── .terraform.lock.hcl
├── main.tf
├── terraform.tfvars
└── variables.tf
```

### terraform による作成したリソースの削除

```
$ terraform destroy

azurerm_resource_group.this: Refreshing state... [id=/subscriptions/xxxxxxxx-1717-4343-9779-zzzzzzzzzzzz/resourceGroups/rg-ituru-test]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # azurerm_resource_group.this will be destroyed
  - resource "azurerm_resource_group" "this" {
      - id       = "/subscriptions/xxxxxxxx-1717-4343-9779-zzzzzzzzzzzz/resourceGroups/rg-ituru-test" -> null
      - location = "japaneast" -> null
      - name     = "rg-ituru-test" -> null
      - tags     = {
          - "CostCenter" = "psg2"
          - "owner"      = "ituru"
          - "period"     = "2022-03-31"
        } -> null
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

azurerm_resource_group.this: Destroying... [id=/subscriptions/xxxxxxxx-1717-4343-9779-zzzzzzzzzzzz/resourceGroups/rg-ituru-test]
azurerm_resource_group.this: Still destroying... [id=/subscriptions/xxxxxxxx-1717-4343-9779-zzzzzzzzzzzz/resourceGroups/rg-ituru-test, 10s elapsed]
azurerm_resource_group.this: Destruction complete after 15s

Destroy complete! Resources: 1 destroyed.
```

```
$ az group show --name rg-ituru-test
(ResourceGroupNotFound) Resource group 'rg-ituru-test' could not be found.

$ az storage blob list --account-name $STORAGE_TF_ACCOUNT --container-name $STORAGE_TF_CONTAINER --output table
Name                            Blob Type    Blob Tier    Length    Content Type      Last Modified              Snapshot
------------------------------  -----------  -----------  --------  ----------------  -------------------------  ----------
test01                          BlockBlob    Hot                                      2022-01-05T10:26:47+00:00
test01/terraform.tfstate        BlockBlob    Hot          156       application/json  2022-01-05T10:49:52+00:00
```

---

### まとめ

これで、Terraform でAzure環境を構成できる準備が整いました。terraform の tfstateファイル は上記で作成したストレージアカウントのコンテナに保存されるようになります。