Terraform でやってる個人的工夫

# はじめに

仕事で Terraform を使った Azure のリソース管理をしていた経験を元に、Terraform を触りだす前に知っていたらよかったなと思う点をまとめます。
なお、説明のため、実際に書いたコードをかなり簡略化しています。

# （前提としての）ディレクトリ構成

module は1階層にしてネットワークとかストレージ等のリソースの種類毎にざっくり設計しました。

```
.
├── main.tf
├── variable.tf
├── output.tf
└── modules
    ├── storage
    │   ├── main.tf
    │   ├── variable.tf
    │   └── output.tf
    └── compute
        ├── main.tf
        ├── variable.tf
        └── output.tf
```

# リソース毎に設定をマップ化する

特に目新しくはないですが、各 module で使う設定値は最初から `variable.tf` でマップ化しておくとよいです。最初はあまり変更する気がなくても、結局後から変更することが多いからです。マップのキーは、呼び出し側の属性名をそのまま使うとわかりやすいです。

```variable.tf
variable "ubuntu_setting" {
    default = {
        size = "Standard_D4_v3",
        source_image_reference = {
            publisher = "Canonical",
            offer     = "UbuntuServer",
            sku       = "18.04-LTS",
            version   = "latest",
        },
        os_disk = {
            storage_account_type = "StandardSSD_LRS",
            disk_size_gb = 128,
        },
    }
}
```

呼び出し側は、変数の中身を気にすることなく、変数名も `variable.tf` を参照することなく書けます。

```modules/compute/main.tf
resource "azurerm_linux_virtual_machine" "vm" {
    name = var.vm_name
    size = var.vm_setting.size

    source_image_reference {
        publisher = var.vm_setting.source_image_reference.publisher
        offer     = var.vm_setting.source_image_reference.offer
        sku       = var.vm_setting.source_image_reference.sku
        version   = var.vm_setting.source_image_reference.version
    }

    os_disk {
        name                      = "${var.vm_name}-disk-os"
        storage_account_type      = var.vm_setting.os_disk.storage_account_type
        disk_size_gb              = var.vm_setting.os_disk.disk_size_gb
        caching                   = "ReadWrite"
        write_accelerator_enabled = false
    }

    // etc.
}
```

# module の output をマップ化する

今度は `variable.tf` でのマップ化ではなく、output のマップ化です。
Terraform を触りたての頃は module の output をバラして書いていました。

```(before)modules/storage/output.tf
output "name" {
    value = azurerm_storage_account.sa.name
}
output "object_id" {
    value = azurerm_storage_account.sa.id
}
output "primary_blob_endpoint" {
    value = azurerm_storage_account.sa.primary_blob_endpoint
}
output "primary_access_key" {
    value = azurerm_storage_account.sa.primary_access_key
}
```

こうすると `main.tf` で別の module に変数を渡すときに縦に長くなってしまいます。

```(before)main.tf
module "compute" {
    source                = "./modules/compute"
    storage_name          = module.storage.name
    storage_object_id     = module.storage.object_id
    primary_blob_endpoint = module.storage.primary_blob_endpoint
    primary_access_key    = module.storage.primary_access_key
}
```

今はどうしているかと言うと、module の output を profile というマップにまとめて返すようにしています。

```(after)modules/storage/output.tf
output "profile" {
    value = {
        name                  = azurerm_storage_account.sa.name,
        object_id             = azurerm_storage_account.sa.id,
        primary_blob_endpoint = azurerm_storage_account.sa.primary_blob_endpoint,
        primary_access_key    = azurerm_storage_account.sa.primary_access_key,
    }
}
```

こうすると `main.tf` で別の module に変数を渡すときに1行しか書く必要がなくなります。また、profile の中身が増えても `main.tf` 側は変更不要になります。

```(after)main.tf
module "compute" {
    source  = "./modules/compute"
    storage = module.storage.profile
}
```

`modules/compute/main.tf` で呼び出すときは `var.storage.name` 等と書くだけです。

# 複数行テキストを output する

Terraform の実行ディレクトリ直下のファイルに output を書くと `terraform apply` の実行終了時に output の内容が標準出力されますが、変数名と値しか出力しないと、何を出力しているのか第三者に伝わりづらいですし、なぜ出力しているのかも伝わりづらいことがあります。

`readme.md` で丁寧に説明することもできますが、私の場合、output を複数行にして説明を日本語で書くようにしています。

```output.tf
output "output" {
    value = <<-EOT

        管理者のユーザー名とパスワードを利用者に渡してください。
        ユーザー名: ${var.admin_name}
        パスワード : ${random_string.admin_pass.result}
    EOT
}
```

# フラグで ON/OFF を切り替える

Terraform のコードは、一度書いてしまえばかなりの程度、別用途に流用できます。とはいえ環境が変われば必要なリソースも当然変わります。そんなときは boolean 型のフラグを使います。

```variable.tf
variable "is_target_module" {
    default = {
        storage = true,
        compute = false,
    }
}
```

`count` で実行回数をコントロールできます。`var.is_target_module.compute` が `true` の場合に1回実行させるには、下記のようにします。

```main.tf
module "compute" {
    count = var.is_target_module.compute ? 1 : 0
        source = "./modules/compute"
}
```

ちなみに、`count` とそれ以外の変数は同じインデントで書かれる方が多いみたいです。私の場合は、for 文のような制御構文と見なして、`count` 以下はインデントを1レベル下げるようにしています。

# リストと繰り返し構文で ON/OFF を切り替える

同じ module を2回実行して同種のリソースを2つ構築したいが、片方には一部不要なリソースがある場合、変数にフラグを追加することでも対応できますが、そのために変数が増えるのは嫌なので、私の場合、不要なリソースの設定をリスト化しています。

例えば Azure Storage を2つ構築したいが、File Share は片方にしか要らない場合を考えます。まず `main.tf` で module に File Share の設定をリストで渡します。

```main.tf
# ストレージ（「ファイル共有」なし）
module "storage01" {
    source               = "./modules/storage"
    storage_account_name = "numshstorage01"
    file_share_setting   = []
}

# ストレージ（「ファイル共有」あり）
module "storage02" {
    source               = "./modules/storage"
    storage_account_name = "numshstorage02"
    file_share_setting   = [{
        name  = "fileshare01",
        quota = 4,
    }]
}
```

module 側は、繰り返し構文で File Share 構築の ON/OFF を切り替えます。`count` で実行回数をコントロールできます。

```modules/storage/main.tf
resource "azurerm_storage_share" "ss" {
    count = length(var.smb_setting)
        storage_account_name = azurerm_storage_account.sa.name
        name                 = var.smb_setting[count.index].name
        quota                = var.smb_setting[count.index].quota
}
```

なお、フラグを使うなら `count = var.is_file_share ? 1 : 0` みないな書き方になります。


以上