terraformの基礎の理解を深めるためにAzureのStrage Accountを構築してみる

業務でTerraformを使う機会が増えて理解が足りていない部分があったので、理解を深めるために今回はAzureストレージをTerraformで構築しながらまとめていきます。

## Terraformとは
- HashiCorp社によって開発されたインフラの設定をコードで管理するためのIaC（Infrastructure as Code）ツール
- HCL（HashiCorp Configration Language）という言語で定義を記述しインフラ構築をおこなうことができる
- AWSやMicrosoft Azure、Google Cloud Plattformなど様々なパブリッククラウドに対応
## tfstateファイル
Terraformで管理しているリソースの現状の構成を記録するためのファイルになります。
定義したリソースの適用（apply）時に適用した構成が記録されます。
ファイルの管理は以下の2つの管理方法があります。
  - ローカル管理
    - 特に保存先を指定していなければデフォルトでローカル環境のterraformを実行したカレントディレクトリにtfstateファイルが生成される
  - バックエンド管理
    - クラウドのストレージ上で管理をおこない開発メンバー間の共有ができる
    - バージョニング管理ができ、何かあった時の復旧が容易
## TerraformでAzureのStrage Accountを構築してみる
### 事前準備
以下のチュートリアルを参考にTerraformのインストールとAzureに対しての認証をおこなっておきます。

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

また、以下を参考にtfstateファイルをストレージで管理できるようにストレージアカウントとコンテナーを作成しておきます。

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


### 構成ファイルの作成
#### provider.tf
まずは使用するプロバイダーの定義を`terraform`ブロック内に記述していきます。
今回はstateファイルをAzureストレージで管理するため、`backend`ブロックにストレージの情報も記述します。
```hcl:provider.tf
terraform {
  required_providers {
    azurerm = {
      version = ">=3.49.0"
    }
  }
  backend "azurerm" {
    resource_group_name  = "backend_rg"  # 事前準備で作成した resource-group-name
    storage_account_name = "xxxxxtfbackend01" # 事前準備で作成した storage-account-name
    container_name       = "backend"     # 事前準備で作成した container-name
    key                  = "terraform.tfstate"
  }
}

provider "azurerm" {
  features {}
}
```
#### variables.tf
次に変数の定義を記述していきます。
```hcl:variables.tf
variable "location" {
  type    = string
  default = "japaneast"
}
variable "allow_nested_items_to_be_public" {
  type    = bool
  default = false
}
variable "container_access_type" {
  type    = string
  default = "private"
}
```
`type`に変数の値の型、`default`にデフォルト値を定義しておきます。
定義したvariableは`var.変数名`という形で参照することができます。
#### main.tf
作成するリソースの定義を記述していきます。
今回はリソースグループとストレージカウント、ストレージコンテナを構築していきます。
```hcl:main.tf
# リソースグループ
resource "azurerm_resource_group" "rg" {
  name     = "rg_strage"
  location = var.location
}

# ストレージアカウント
resource "azurerm_storage_account" "sa" {
  name                            = "xxxxxtf39demo01"
  resource_group_name             = azurerm_resource_group.rg.name
  location                        = azurerm_resource_group.rg.location
  account_tier                    = "Standard"
  account_replication_type        = "LRS"
  allow_nested_items_to_be_public = var.allow_nested_items_to_be_public
}

# ストレージコンテナー
resource "azurerm_storage_container" "sc" {
  name                  = "stragecon"
  storage_account_name  = azurerm_storage_account.sa.name
  container_access_type = var.container_access_type
}
```

### リソースの構築
#### 初期化
`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...
- Finding hashicorp/azurerm versions matching ">= 3.49.0"...
- Installing hashicorp/azurerm v3.53.0...
- Installed hashicorp/azurerm v3.53.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```
初期化が成功すると`Terraform has been successfully initialized!`のメッセージが表示されます。
`-backend-config`オプションを利用して環境ごとに定義したbackendを指定することも可能です。
初期化をおこなうとローカルに以下のディレクトリとファイルが生成されます。
  - .terraform
    - 初期化時にキャッシュしたプロバイダーやモジュールなどの情報を格納するディレクトリ
  - .terraform.lock.hcl
    - 初期化時にキャッシュしたプロバイダーやモジュールのバージョンの依存関係をロックするためのファイル
#### 実行計画の確認
初期化が終わったので、`plan`コマンドを実行して`apply`する前に実行計画の確認をしていきます。
```
$ 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.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "rg_strage"
    }

  # azurerm_storage_account.sa will be created
  + resource "azurerm_storage_account" "sa" {
      + access_tier                       = (known after apply)
      + account_kind                      = "StorageV2"
      + account_replication_type          = "LRS"
      + account_tier                      = "Standard"
      + allow_nested_items_to_be_public   = false
      + cross_tenant_replication_enabled  = true
      + default_to_oauth_authentication   = false
      + enable_https_traffic_only         = true
      + id                                = (known after apply)
      + infrastructure_encryption_enabled = false
      + is_hns_enabled                    = false
      + large_file_share_enabled          = (known after apply)
      + location                          = "japaneast"
      + min_tls_version                   = "TLS1_2"
      + name                              = "xxxxxtf39demo01"
      + nfsv3_enabled                     = false
      + primary_access_key                = (sensitive value)
      + primary_blob_connection_string    = (sensitive value)
      + primary_blob_endpoint             = (known after apply)
      + primary_blob_host                 = (known after apply)
      + primary_connection_string         = (sensitive value)
      + primary_dfs_endpoint              = (known after apply)
      + primary_dfs_host                  = (known after apply)
      + primary_file_endpoint             = (known after apply)
      + primary_file_host                 = (known after apply)
      + primary_location                  = (known after apply)
      + primary_queue_endpoint            = (known after apply)
      + primary_queue_host                = (known after apply)
      + primary_table_endpoint            = (known after apply)
      + primary_table_host                = (known after apply)
      + primary_web_endpoint              = (known after apply)
      + primary_web_host                  = (known after apply)
      + public_network_access_enabled     = true
      + queue_encryption_key_type         = "Service"
      + resource_group_name               = "rg_strage"
      + secondary_access_key              = (sensitive value)
      + secondary_blob_connection_string  = (sensitive value)
      + secondary_blob_endpoint           = (known after apply)
      + secondary_blob_host               = (known after apply)
      + secondary_connection_string       = (sensitive value)
      + secondary_dfs_endpoint            = (known after apply)
      + secondary_dfs_host                = (known after apply)
      + secondary_file_endpoint           = (known after apply)
      + secondary_file_host               = (known after apply)
      + secondary_location                = (known after apply)
      + secondary_queue_endpoint          = (known after apply)
      + secondary_queue_host              = (known after apply)
      + secondary_table_endpoint          = (known after apply)
      + secondary_table_host              = (known after apply)
      + secondary_web_endpoint            = (known after apply)
      + secondary_web_host                = (known after apply)
      + sftp_enabled                      = false
      + shared_access_key_enabled         = true
      + table_encryption_key_type         = "Service"
    }

  # azurerm_storage_container.sc will be created
  + resource "azurerm_storage_container" "sc" {
      + container_access_type   = "private"
      + has_immutability_policy = (known after apply)
      + has_legal_hold          = (known after apply)
      + id                      = (known after apply)
      + metadata                = (known after apply)
      + name                    = "stragecon"
      + resource_manager_id     = (known after apply)
      + storage_account_name    = "xxxxxtf39demo01"
    }

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

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run       
"terraform apply" now.
```
`plan`コマンドは`tfstate`ファイルから現在のリソースの状態を参照し、リソースの追加・変更・削除の差分を表示してくれます。
コマンドの実行が成功したら`Plan 1 to add, 0 to change, 0 to destroy`のように追加・変更・削除の計画サマリが表示されます。
今回は初回の実行でTerraformからまだ何もリソースが作成されていないので、3つのリソースが追加として計画サマリが表示されています。
もし現在のリソースの状態と比較しリソースの追加や変更などがなければ`No changes.`と表示されます。
このあと実行する`apply`コマンドでも同様ですが、以下のようなオプションもあります。
- `-var-file`オプションで`.tfvars`の拡張子のファイルを指定して環境ごとの変数定義ファイルを指定することも可能
- `-target`オプションを使用して特定moduleのみ指定することも可能

#### 適用
```
$ 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.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "japaneast"
      + name     = "rg_strage"
    }

  # azurerm_storage_account.sa will be created
  + resource "azurerm_storage_account" "sa" {
      + access_tier                       = (known after apply)
      + account_kind                      = "StorageV2"
      + account_replication_type          = "LRS"
      + account_tier                      = "Standard"
      + allow_nested_items_to_be_public   = false
      + cross_tenant_replication_enabled  = true
      + default_to_oauth_authentication   = false
      + enable_https_traffic_only         = true
      + id                                = (known after apply)
      + infrastructure_encryption_enabled = false
      + is_hns_enabled                    = false
      + large_file_share_enabled          = (known after apply)
      + location                          = "japaneast"
      + min_tls_version                   = "TLS1_2"
      + name                              = "xxxxxtf39demo01"
      + nfsv3_enabled                     = false
      + primary_access_key                = (sensitive value)
      + primary_blob_connection_string    = (sensitive value)
      + primary_blob_endpoint             = (known after apply)
      + primary_blob_host                 = (known after apply)
      + primary_connection_string         = (sensitive value)
      + primary_dfs_endpoint              = (known after apply)
      + primary_dfs_host                  = (known after apply)
      + primary_file_endpoint             = (known after apply)
      + primary_file_host                 = (known after apply)
      + primary_location                  = (known after apply)
      + primary_queue_endpoint            = (known after apply)
      + primary_queue_host                = (known after apply)
      + primary_table_endpoint            = (known after apply)
      + primary_table_host                = (known after apply)
      + primary_web_endpoint              = (known after apply)
      + primary_web_host                  = (known after apply)
      + public_network_access_enabled     = true
      + queue_encryption_key_type         = "Service"
      + resource_group_name               = "rg_strage"
      + secondary_access_key              = (sensitive value)
      + secondary_blob_connection_string  = (sensitive value)
      + secondary_blob_endpoint           = (known after apply)
      + secondary_blob_host               = (known after apply)
      + secondary_connection_string       = (sensitive value)
      + secondary_dfs_endpoint            = (known after apply)
      + secondary_dfs_host                = (known after apply)
      + secondary_file_endpoint           = (known after apply)
      + secondary_file_host               = (known after apply)
      + secondary_location                = (known after apply)
      + secondary_queue_endpoint          = (known after apply)
      + secondary_queue_host              = (known after apply)
      + secondary_table_endpoint          = (known after apply)
      + secondary_table_host              = (known after apply)
      + secondary_web_endpoint            = (known after apply)
      + secondary_web_host                = (known after apply)
      + sftp_enabled                      = false
      + shared_access_key_enabled         = true
      + table_encryption_key_type         = "Service"
    }

  # azurerm_storage_container.sc will be created
  + resource "azurerm_storage_container" "sc" {
      + container_access_type   = "private"
      + has_immutability_policy = (known after apply)
      + has_legal_hold          = (known after apply)
      + id                      = (known after apply)
      + metadata                = (known after apply)
      + name                    = "stragecon"
      + resource_manager_id     = (known after apply)
      + storage_account_name    = "xxxxxtf39demo01"
    }

Plan: 3 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.rg: Creating...
azurerm_resource_group.rg: Creation complete after 2s [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg_strage]
azurerm_storage_account.sa: Creating...
azurerm_storage_account.sa: Still creating... [10s elapsed]
azurerm_storage_account.sa: Still creating... [21s elapsed]
azurerm_storage_account.sa: Still creating... [31s elapsed]
azurerm_storage_account.sa: Still creating... [41s elapsed]
azurerm_storage_account.sa: Still creating... [51s elapsed]
azurerm_storage_account.sa: Still creating... [1m1s elapsed]
azurerm_storage_account.sa: Still creating... [1m11s elapsed]
azurerm_storage_account.sa: Creation complete after 1m19s [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg_strage/providers/Microsoft.Storage/storageAccounts/xxxxxtf39demo01]
azurerm_storage_container.sc: Creating...
azurerm_storage_container.sc: Creation complete after 0s [id=https://xxxxxtf39demo01.blob.core.windows.net/stragecon]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
```
`apply`コマンドは定義したインフラ構成をクラウドに適用するコマンドです。
コマンド実行後に再度`plan`結果が表示され、`Enter a Value:`で`yes`と入力すると適用されます。
`apply`が完了するとterraform.tfstateファイルに適用したリソースの情報が記録されます。

ではリソースが適用できたか再度`plan`コマンドを実行して確認してみます。
```
terraform plan 
azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg_strage]
azurerm_storage_account.sa: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg_strage/providers/Microsoft.Storage/storageAccounts/xxxxxtf39demo01]
azurerm_storage_container.sc: Refreshing state... [id=https://xxxxxtf39demo01.blob.core.windows.net/stragecon]

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.
```
`No changes. Your infrastructure matches the configuration.`となったので差分がない状態です。
Azureポータルからも確認してリソースが作成されていることが確認できました。

リソースの追加ができたので、今度は別のコンテナーのリソース定義を`main.tf`ファイルに追加して`plan`実行してみます。
```:main.tf
・・・

resource "azurerm_storage_container" "sc02" {
  name                  = "stragecon02"
  storage_account_name  = azurerm_storage_account.sa.name
  container_access_type = var.container_access_type
}
```
Plan実行結果
```
$ terraform plan
azurerm_resource_group.rg: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg_strage]
azurerm_storage_account.sa: Refreshing state... [id=/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg_strage/providers/Microsoft.Storage/storageAccounts/xxxxxtf39demo01]
azurerm_storage_container.sc: Refreshing state... [id=https://xxxxxtf39demo01.blob.core.windows.net/stragecon]

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_storage_container.sc02 will be created
  + resource "azurerm_storage_container" "sc02" {
      + container_access_type   = "private"
      + has_immutability_policy = (known after apply)
      + has_legal_hold          = (known after apply)
      + id                      = (known after apply)
      + metadata                = (known after apply)
      + name                    = "stragecon02"
      + resource_manager_id     = (known after apply)
      + storage_account_name    = "xxxxxtf39demo01"
    }

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

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run       
"terraform apply" now.
```
先程`apply`した3つのリソースは`tfstate`ファイルに記録されているため、planの差分として出ません。
今回、定義に追加したコンテナーが1つ追加として差分が出ます。

## 他の主要なコマンドやブロック構文まとめ
### コマンド
#### format
```
$ terraform fmt
```
- Terraformの構成ファイルのインデントを揃えるためのフォーマットをおこなうコマンド
#### validate
```
$ terraform validate
```
- 定義した構文のチェックを行うコマンド
- `terraform plan`コマンドでもvalidateコマンドの機能が内包されている
#### destroy
```
$ terraform destoroy
```
- Terraformで管理しているリソースの削除コマンド
- `terraform plan -destroy`コマンドで削除対象のリソースの一覧を確認することも可能
#### state
```
$ terraform state list
```
- 作成したリソースの一覧を確認するためのコマンド
```
$ terraform state show リソース
```
- リソースを指定して作成したリソースの詳細を確認するためのコマンド

### ブロック
#### DataSource
- `data`ブロックで定義をおこない、Terraformの外部で定義された情報を参照するために使用
```
data "azurerm_storage_container" "sc" {
  name                 = "stragecon"
  storage_account_name = "xxxxxtf39demo01"
}
```
#### Output Values
- `output`ブロックで定義をおこない、リソースの情報を出力するために使用
- `apply`コマンド実行すると出力される
```
data "azurerm_storage_container" "sc" {
  name                 = "stragecon"
  storage_account_name = "xxxxxtf39demo01"
}

output "azurerm_storage_container_access_type" {
  value = data.azurerm_storage_container.sc.container_access_type
}
```
`apply`コマンド実行後にcontainer_access_typeが出力できています。
```
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

azurerm_storage_container_access_type = "private"
```

## 参考

https://www.terraform.io/

https://developer.hashicorp.com/terraform/docs

## 最後に
今回はすごくシンプルな構成でしたが、実際にTerraformで構築しながら基礎の部分の理解を深めていきました。
今後も実際に構築しながら色々と試していきたいと思います。