Terraformメモ1

ただのTerraformの個人用メモ。

# ファイル
terraformでは、以下のファイルを自動的に読み込む。

- ディレクトリ内の.tfファイル
- terraform.tfvars
- auto.tfvars

# terraform基本コマンド
1. terraform init
1. terraform fmt (-recursive)
    - フォーマット修正、不要な空白や改行などがあれば修正してくれる、子ディレクトリもやる場合はrecursiveをつける
1. terraform validate
    - terraformの文法チェックをしてくれる
1. terraform plan -out plan.tfplan
    - ドライラン、ドライラン結果を
1. terraform apply plan.tfplan
    - 実際のデプロイ
1. terraform destroy
    - デプロイしたリソースの削除


# Terraformのvariable, local
基本的にvariableはモジュール間でのデータの受け渡し、localは受け取ったデータなどを編集して利用したいときに使うと理解した。variableは受け渡し用のパラメータ、localはスコープ変数。

```variables.tf
variable "variable_name" {
    type = string
    description = "this is a exmple description"
    sensitive = true
    default = "default_value"
}
```

```locals.tf
locals {
    key0 = "value0"
    keymap = {
        key1 = "value1",
        key2 = "value2"
    }
}
```

```main.tf
resource "resource_type" "resource_name" {
  　  attribute1 = var.variable_name
    attribute2 = "${var.variable_name}_suffix" # interapolation、変数を編集できる

    attribute3 = local.key0
    attribute4 = local.keymap.key1
}
```

## variableの設定方法色々
- 実行時の -var
- 実行時の -var-file FILE_NAME.tfvars
- variableのdefault
- terraform.tfvars
    - この名前であれば自動的に読み込まれる
- 環境変数のTF_VAR_{VARIABLENAME}

通常のtfvarsとterraform.tfvarsの違いとして、terraform.tfvarsファイルは実行時に-var-fileで指定しなくても自動的に変数ファイルとして読み込まれる。ということは、環境に関係ないデフォルト値を設定するのに便利。逆に通常のtfvarsファイルは環境特有の値を定義するのに使う。tfvarsファイルはなのでgitignoreに定義しチェックインしない。

# terraform version
.tfファイルを作成した時のバージョンより高いバージョンを必要とするようにする。

https://dev.classmethod.jp/articles/about-terraform-version-required-constraints/

例えば、現在のバージョンがv1.10.5なのでそれ以上のバージョンを必要とするようにする。

```
vscode ➜ /workspaces/FirstProject (main) $ terraform --version
Terraform v1.10.5
on linux_amd64
```

```main.tf
terraform {
  required_version = ">= v1.10.5"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "2.46.0"
    }
  }
}
```

# Terraformの配列、オブジェクト型
## 配列
- List
順序付きの値の集合。
重複する要素を含むことが可能。
インデックスを使用して要素にアクセスする。
```
variable "example_list" {
  type = list(string)
  default = ["apple", "banana", "orange"]
}
```

- Set
重複のない値の集合。
順序は保証されない。
同じ型の要素のみを含むことができる。
```
variable "example_list" {
  type = set(string)
  default = ["apple", "banana", "orange"]
}
```

toset関数を使用してlistをsetに変換することができる。これにより、重複する値を削除し、ユニークな値のみを保持することができる。また、for_each文はmap型またはset型のみを受け付けるため、リストをsetに変換して使用する。

- Tuple
異なる型の値を順序ありで格納する。
```
variable "person_info" {
  type = tuple([string, number, bool])
  default = ["Alice", 30, true]
}
```

## オブジェクト型
- Map
キーと値のペアの集合。
キーは一意である必要がある。
重複するキーが追加されると、後から追加された値で上書きされる。
```
variable "example_map" {
  type = map(string)
  default = {
    key1 = "value1",
    key2 = "value2"
  }
}
```

- Object
キーと値のペアの集合。
Mapに似ているが、異なる型のデータを混ぜることができる。
```
variable "example_object" {
  type = object({
    name    = string
    age     = number
    is_admin = bool
  })
  default = {
    name    = "John",
    age     = 30,
    is_admin = true
  }
}
```
# Data
dataを利用して既存のリソースの情報を取得する。
以下はSubscriptionの情報を取得する例。

```
provider "azurerm" {
  features {}
}

data "azurerm_subscription" "main" {}

resource "azurerm_role_definition" "main" {
  name        = "write-accessrole"
  scope       = data.azurerm_subscription.main.id

  assignable_scopes = [
    data.azurerm_subscription.main.id
  ]
}
```

# ループ
##  loop
https://zenn.dev/wim/articles/terraform_loop

loopで回せるのはmap, set。
foreachによってmapを回してみると以下のようになる。

```
resource "instance" "web" {
    for_each = {
        aaa = "aaa.txt"
        bbb = "bb.txt"
    }
    key = each.value
    value = "test-${each.value}"
    tags = {
        Name = each.key
    }
}
```

## count
countを使えば複数のリソースを一括で作成可能。
```
resource "instance" "web" {
    count = 3
    tags = {
        Name = "test-web-${count.index}"
    }
}
```

# terraform function
## terraform console
Functionは動作確認するときには、terraform consoleでテストすることができる。

```
vscode ➜ /workspaces/FirstProject/day3 (main) $ terraform console
> min(1,2,3)
1
> cidrsubnet("10.0.0.0/16",12,1)
"10.0.0.16/28"
> cidrsubnet("10.0.0.0/16",12,2)
"10.0.0.32/28"
> cidrsubnets("10.0.0.0/16", 12, 12, 12)
tolist([
  "10.0.0.0/28",
  "10.0.0.16/28",
  "10.0.0.32/28",
])
> exit
```

ちなみにIPレンジを指定する際に変数とcidrsubetどちらを利用するべきかPerplexityに質問してみた。

> TerraformでAzure上にVNETとSubnetを作成する際、IPレンジの指定方法については、以下のポイントを考慮するとよいでしょう。
> 
> 選択肢1: 変数でIPレンジを指定
> メリット:
> 明確に管理可能で、環境ごとに異なるCIDR範囲を簡単に設定できる。
他のツールや手動計算で事前にCIDR範囲を計画し、競合を避けることができる。
> 
> デメリット:
> 手動でCIDR範囲を計算する必要があり、大規模なネットワークではミスが発生しやすい。動的な割り当てには対応しづらい。
> 
> 選択肢2: cidrsubnet関数を使用
> メリット:
> Terraformの組み込み関数であるcidrsubnetやcidrsubnetsを利用することで、動的にIPレンジを計算可能。
ネットワーク設計が柔軟になり、複数のサブネットを簡単に生成できる。
> 
> デメリット:
cidrsubnetは既存のIPアドレスの使用状況を検証しないため、重複が発生する可能性がある。IPアドレスの競合エラーを防ぐには、事前に範囲の利用状況を確認する仕組みが必要。
> 
> 推奨事項
小規模なネットワークや固定された環境では、変数でCIDR範囲を指定する方法が簡便で安全です。
> 大規模なネットワークや動的なサブネット構成が必要な場合は、cidrsubnet関数を使用しつつ、既存のIPアドレス範囲との競合を避けるために事前検証を行う仕組み（例えばTerraform外部スクリプトや手動確認）を導入することが推奨されます。
> 
> どちらの方法でも、設計段階でCIDR範囲の計画を十分に行うことが重要です。

# Module
## 普通のModuleを作って使う
Moduleを作成するには、最低限以下の3つのファイルをModulesフォルダ配下に作成する。
- main.tf
- output.tf
- variable.tf

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/3387d70f-4567-4da6-8d05-10afe7363ff7.png)

main.tfにはprovidersの指定は不要。それは呼び出す方の親側で定義する。
```main.tf
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "4.25.0"
    }
  }
}

locals {
  tags = {
    "Environment" = var.environment
  }
}

resource "azurerm_storage_account" "securestorage" {
  resource_group_name           = var.resource_group_name
  name                          = var.name
  account_kind                  = "StorageV2"
  location                      = var.location
  account_tier                  = "Standard"
  account_replication_type      = var.environment == "prod" ? "LRS" : "GRS"
  public_network_access_enabled = false
  tags                          = local.tags
}
```

```variables.tf
variable "environment" {
  description = "The environment to deploy to (e.g., dev, staging, prod)"
  type        = string
  default     = "dev"
}

variable "resource_group_name" {
  description = "The name of the resource group to create"
  type        = string
  default     = "myResourceGroup"
}

variable "location" {
  description = "The Azure region to deploy the resources in"
  type        = string
  default     = "East US"
}

variable "name" {
  description = "The name of the storage account"
  type        = string
  default     = "securestorage"
}
```
```output.tf
output "name" {
  description = "The name of the storage account"
  value       = azurerm_storage_account.securestorage.name
}

output "id" {
  description = "The ID of the storage account"
  value       = azurerm_storage_account.securestorage.id
}

output "primary_blob_endpoint" {
  description = "The primary blob endpoint of the storage account"
  value       = azurerm_storage_account.securestorage.primary_blob_endpoint
}
```

呼び出すには以下のように定義する。
```main_parent.tf
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}

provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
}

resource "azurerm_resource_group" "rg" {
  name     = "myResourceGroup1"
  location = "Japan East"
}

# use module to cretate secure storage account
module "samplemodule" {
  source              = "./Modules/securestorage"
  resource_group_name = azurerm_resource_group.rg.name
  name                = "simastorageaccount1"
  location            = "Japan East"
  environment         = "dev"
}


output "name" {
  description = "The name of the storage account"
  value       = module.samplemodule.name
}
```

## Terraform moduleを Private Registryに登録して組織内で共有できるようにする
GithubでRepositoryを作成。Repository名称はterraform-{providername}-{modulename}にする。

今回はterraform-azurerm-securestorageで作成。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/b03346be-66dd-44c0-86c0-e674c0bcca23.png)

先ほどのコードをそのままチェックインした状態にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/48f21504-8800-43a8-ad33-762296f825a1.png)

TagsからCreate a new release。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/ada2d774-2175-4370-a2c2-451e5ee0b4a7.png)

Terraform CloudでRegistryからPublish。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/f5f58723-8553-44cd-bdf0-2514a1e622eb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/12f1c880-1f2b-4a0d-a72c-c6705e876c05.png)

Terraform CloudがGitHubにアクセスできるように許可。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/0bfed962-3d15-479f-8b80-3696e2591916.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/87b9da1b-c5ea-4d19-a0fc-f1b7c90de902.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/3bd90a58-5ab2-43d8-a3bc-cb94fbb4fffe.png)

Terraform Cloud上のPrivate Repository上にPublishできた。なんかかっこいい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654538/3005b187-2626-41c2-b8ea-df210050893c.png)

使ってみる。terraform login, terraform init, terraform apply。
```main.tf
terraform {
  # use azurerm provider version 3.0.0 or later
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}

# Configure the Azure Provider
provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
}

resource "azurerm_resource_group" "rg" {
  name     = "myResourceGroup1"
  location = "Japan East"
}

module "securestorage" {
  source  = "app.terraform.io/SimaTeraOrg/securestorage/azurerm"
  version = "1.0.0"
  name = "stsimastoaccount1"
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
}
```