AzureリソースをTerraformのmodulesへ移動

TerraformでセットアップされたAzureのリソースをTerraformモジュールに移動してみました。

普通にvariableをInputするようなところは割愛して、リソースによって何か特徴のあるところをいくつか例を紹介したいと思います。

# Storage Account
```main.tf

resource "azurerm_storage_account" "azfiles" {
  name                     = "${var.sa_name}"
.
.
.
}

resource "azurerm_storage_share" "share" {
  for_each = var.shares
  name                 = each.key
  storage_account_name = azurerm_storage_account.azfiles.name
  quota                = each.value
  depends_on = [
    azurerm_storage_account.azfiles
  ]  
}
```
```variables.tf
variable "shares" {
  type = map(string)
}
```
Azure FileのShareを複数作る場合はvariableでMapを作りそこでKey,ValueでShareの名前とQuotaのサイズを設定します。Modulesでは以下のように設定します。

```storageaccount.tf
module "storage" {
  source                      = "modules/sa/azfiles"
  sa_name                     = "${local.sa_name}"
.
.
.
  shares = { 
    share1 = 512
    share2 = 1024
  }  
}
```
これでQuotaが512GBのshare1と1024GBのshare2ができます。

## Containers
Azure Blob Storageで複数のContainersをセットアップする場合は、VariablesでListを使用します。

```main.tf
resource "azurerm_storage_account" "blob_nfs" {
  name                     = "${var.name}"
  resource_group_name      = "${var.resource_group_name}"
  .
  .
}

resource "azurerm_storage_container" "container" {
  for_each = toset(var.containers)
  name                  = each.value
  storage_account_name = azurerm_storage_account.blob_nfs.name
  container_access_type = "private"
}
```
```variables.tf
variable "containers" {
  type    = list(string)
}
```
こんな感じでListのVariablesを設定して、Modulesでは以下のように呼び出します。

```containers.tf
module "nfs" {
  source                      = "modules/modules/blob_nfs"
  name                        = "mystorage"
  .
  .
  containers                  = ["container1","container2"]
}
```
とすると`mystorage`に`container1`、`container2`が作成されます。

# Endpoint
リソースによってはPrivate Endpointが必要な場合もあります。
```main.tf
resource "azurerm_private_endpoint" "endpoint" {
  count = var.pe_name == null ? 0 : 1

  name                = "${var.pe_name}"
  location            = "${var.location}"
  resource_group_name = "${var.resource_group_name}"
  .
  .
}
```
とすると`pe_name`が存在する場合のだけ作成して、ない場合はendpointを作らないようにもできます。


# WAF
```main.tf
resource "azurerm_web_application_firewall_policy" "policy" {
  name                = "${var.policy_name}"
  resource_group_name      = "${var.resource_group_name}"
  location                 = "${var.location}"
.
.
.
    custom_rules { 
        name      = "${var.customrule_name}"
        priority  = var.customrule_priority
        rule_type = "${var.customrule_type}"

        dynamic match_conditions {
        for_each = var.match_conditions
            content {
                match_variables {
                    variable_name = "${match_conditions.value.variable_name}"
                    selector      = "${match_conditions.value.selector}"
                }

                operator           = "${match_conditions.value.operator}"
                negation_condition = false
                match_values       = ["${match_conditions.value.match_values}"]
            }
        }

        action = "${var.customrule_action}"
    }
  
```
`custom_rules`自身も複数ある場合もありますが、`custom_rules`のなかの`match_variables `が複数ある場合の例です。`dynamic match_conditions`とすることによって`for_each`をattributeで展開しています。
これは`dynamic`を使用して`content{}`の中で`match_conditions.value.variable_name`などとすることでVariableの値を取っています。

https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks

```variables.tf
variable "match_conditions" {
  type = list(object({
    variable_name = string
    selector      = optional(string, "")
    operator      = string
    match_values  = string
  }))
}
```
Variable自体はこのように設定すればObjectのListとして利用できます。

```terraform
  match_conditions = [
                     { 
                      variable_name = "RequestUri"
                      operator      = "Contains"
                      match_values  = "/test/abc"
                     },
                     { 
                      variable_name = "RemoteAddr"
                      operator      = "IPMatch"
                      match_values  = "123.45.67.89"
                     }
```
とかすると特定のIPアドレスから特定のPathへのアクセスを許可するルールができます。

また、WAFのルールで例外を設けるときなどvariableでlistの中にlistを作ります。
```main.tf
  managed_rules {
    managed_rule_set {
      type    = "OWASP"
      version = "3.2"

      dynamic rule_group_override {
        for_each = var.rule_group_override
        content{
          rule_group_name   = "${rule_group_override.value.rule_group_name}"
          dynamic rule {
            for_each = rule_group_override.value.rule_ids
            content {
                id      = "${rule.value}"
                enabled = false
            }
          }
        }

      }
    }
  }
  ```
  ```variables.tf
  variable "rule_group_override" {
  type = list(object({
    rule_group_name = string
    rule_ids      = list(string)
  }))
}
```
モジュール側では以下のように設定すると、指定したWAFルールに例外が設定されます。
```modules.tf
  rule_group_override = [
    {
       rule_group_name   = "REQUEST-920-PROTOCOL-ENFORCEMENT"
       rule_ids          = ["942260","942430"]
    },
    {
       rule_group_name   = "REQUEST-941-APPLICATION-ATTACK-XSS"
       rule_ids          = ["942110","942430"]
    }
]
```
# その他

モジュールにしても、複数のtfファイルで管理することもできます。

```
modules
├── appgw
    ├── main.tf
    ├── variables.tf
    ├── region1.tf
    ├── region2.tf
```
例えば、異なるリージョンでの差分が多い場合、変数で対応するより別のファイルで設定することも可能です。

```region1.tf
resource "azurerm_application_gateway" "region1" {
  count = var.location == "region1" ? 1 : 0
  name                = "${var.appgw_name}"
.
.
```

```region2.tf
resource "azurerm_application_gateway" "region2" {
  count = var.location == "region2" ? 1 : 0
  name                = "${var.appgw_name}"
.
.
```

```modules.tf
module "appgw" {
  source      = "modules/appgw"
  location    = "region1"
}
```

このようにすると、`var.location`変数が`region1`の場合だけ`azurerm_application_gateway.region1`が作成されるようにすることも可能です。ただし、このように条件を入れてリソースを作成すると`azurerm_application_gateway.region1[0]`と配列になって作成されます。
`main.tf`にはどちらのリージョンでも共通に作られるリソースを入れておくと、まとめて管理できます。


# 既存のリソースをモジュールに移動
すでにリソースをTerraformで作成している場合は、`moved`を使うと、Stateをモジュールに移動できます。

```modules.tf
moved {
  from = azurerm_kubernetes_cluster.aks
  to   = module.aks_qa.azurerm_kubernetes_cluster.aks
}
```
# まとめ
このように`moved`を使って既存のリソースをモジュールに移動できます。
この場合、リソース自体は何も変更行われず、Terraformのステートだけが、モジュールに移動します。

`terraform state list`とすると、リソースがモジュールに移動していることが確認できます。
```bash
terraform state list
module.storage.azurerm_storage_account.azfiles
module.nfs.azurerm_storage_account.blob_nfs
...
...
```