Terraform基礎知識

Terrafromの自分用メモ

---

# 参考
- [Terraformバージョンを固定する - Terraformのきほんと応用](https://zenn.dev/sway/articles/terraform_staple_fixversion)
- [terraformが怖くて震えていたかつての自分に捧ぐInfrastructure as Code入門(AWS)](https://qiita.com/Sadalsuud/items/755381d664346b52d52c)
- [Backend Configuration](https://developer.hashicorp.com/terraform/language/settings/backends/configuration)
- [Backend Type: s3 | Terraform](https://developer.hashicorp.com/terraform/language/settings/backends/s3)
- [Module Blocks](https://developer.hashicorp.com/terraform/language/modules/syntax)
- [Data Sources](https://developer.hashicorp.com/terraform/language/data-sources)


# 主なワークフロー
1. Initialize
    - Terraforを実行するための、作業ディレクトリを準備する
    - initコマンドでTerraformやプロバイダのバージョンをアップグレードもできる
1. Plan
    - 変更を適用する前にプレビューする
        - 実環境に反映されることはない
    - Terraformの設定と現状のリソースを照合する
        - 特定のリソースをターゲットにすることも可能
        - 状態ファイルと実際の構成を照合することも可能
1. Apply
    - Terraform の設定によって定義された変更を実行し、リソースを作成、更新、破棄する
    - 部分的に完了した適用はロールバックされない
        - 適用中にエラーになった場合、機能しない構成になっている可能性がある


# 主なファイル
## Initialize
### versions.tf
- Terrafrom本体等のバージョンを制限する
    - 複数人開発時のバージョン差異によるリスクを低減できる
- バージョンは「MAJOR.MINOR.PATCH」と表現される
    - 例えばマイナーバージョンが上がった場合、  
      パッチはそのマイナーバージョンアップに含まれることになる
- バージョン表記は以下
    - 0.15.0：v0.15.0のみ許容する
    - \>= 0.15：v0.15以上（v1.0.0含めて）を許容する
    - \~> 0.15.0：0.15.Xは許容する
        - マイナーおよびメジャーバージョンアップはしないがパッチは適用したい場合
    - \>= 0.15, < 2.0.0：v0.15.0以上、v2.0.0未満を許容する

#### 例
```tf
terraform {
  required_version = ">= 1.0.8"  # Terrafromのバージョン
  required_providers {
    aws = {
      source = "hashicorp/aws"   # 利用するプロバイダを指定
      version = "3.63.0"         # プロバイダのバージョン
    }
  }
}
```

### .terraform.lock.hcl
- プロバイダバージョンを指定するファイル
- 本ファイルが存在しない場合、versions.tfからプロバイダのバージョンを決定する
- versions.tfに記述がなく、本ファイルも存在しない場合は  
  最新のバージョンがダウンロードされる

#### 例
```tf
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/aws" {
  version     = "3.63.0"
  constraints = "3.63.0"
  hashes = [
    "h1:FyU8TUgpwfu+O+k+Uu58I/JWlEZk2PzQLJMluuaIQ=",
    ## ...
    "zh:fd634e973eb2b6483a1ce92518093d04cb496f8e83ffcf3f0c4cad8c18f4c",
  ]
}
```

### .terraform
- プロジェクトのプロバイダーとモジュールを格納するディレクトリ
- plan, applyを実行するときに、これらのコンポーネントは参照される
- 直接変更しないこと

#### 例
```bash
$ tree .terraform/providers
.terraform/providers
└── registry.terraform.io
    └── hashicorp
        └── aws
            └── 3.63.0
                └── linux_amd64
                    └── terraform-provider-aws_v3.63.0_x5

5 directories, 1 file
```

### variables.tf
- 変数の設定ファイル
- 変数の宣言はどこでも問題ないが、通例としてvariables.tfに記述する

#### 使い方例
- 起動するインスタンス数を環境ごとに変える
- IPアドレスレンジが環境ごとに被らないようにする
- タグ付けを環境ごとに分ける
- apply時に変数に任意の値を入力する
    - terraform.tfvarsに入力が必要な値をまとめて定義、読み込ませることも可能

## Apply
### outputs.tf
- 出力の設定ファイル
- 出力の宣言はどこでも問題ないが、通例としてoutputs.tfに記述する
- 別のTerraformワークスペースのデータソースとして利用する

```tf
output "aws_instance_tfer--i-bb85f7df195ffc5_aws-cloud9-bb85f356b29a1f096d8267_id" {
  value = aws_instance.tfer--i-bb85f7df195ffc5_aws-cloud9-bb85f356b29a1f096d8267.id
}

output "aws_instance_tfer--i-d58229a1f09b18_bluegreen_id" {
  value = aws_instance.tfer--i-d5829a1f0923b18_bluegreen.id
}
```

### terraform.tfstate
- インフラに関する情報を状態を格納するファイル
    - apply時に生成されるファイル
    - 状態ファイルに書き込み後、実体のリソースが更新される
- 設定によって作成されたリソースを追跡し、実体のリソースにマッピングする
- 直接変更しないこと
- plan時はtfファイルとterraform.tfstateが比較され、差分が表示される
    - 実環境と比較していないので、実環境とterraform.tfstateに差分があるのはNG

# 変数
1. 入力変数
    - 関数の引数のようなもの
    - 手を加えずにモジュールを別環境で共有できるようになる
1. 出力変数
    - 関数の戻り値のようなもの
    - インフラに関する情報をコマンドラインから利用できる
    - 他のTerraform構成が利用できるようにできる
1. ローカル変数
    - 関数の一時的なローカル変数のようなもの
    - 関連するローカル値のセットは、1つのローカルブロックにまとめて宣言できる
    - 一箇所で簡単に値を変更できることが主な利点
        - 多くの場所で使用されるが、その値が将来変更される可能性がある場合に使用する
        - 実際に使われている値を隠してしまうので使いすぎは注意

## 入力変数
### 定義例
```tf
variable "image_id" {
  type = string
}

variable "availability_zone_names" {
  type    = list(string)
  default = ["us-west-1a"]
}

variable "docker_ports" {
  type = list(object({
    internal = number
    external = number
    protocol = string
  }))
  default = [
    {
      internal = 8300
      external = 8300
      protocol = "tcp"
    }
  ]
}

```

### 各項目
#### variable
- 同じモジュール内のすべての変数の中で一意の値

#### default
- デフォルト値が存在する場合、変数はオプションとみなされる
- 値が設定されていない場合、デフォルト値が使用される
- リテラル値であること
    - 設定内の他のオブジェクトを参照することはできない

#### type
- 変数の値の型宣言
- ない場合は、どのような型の値でも許容する
- 宣言することで間違った型が使われた場合、エラーメッセージが返る

#### description
- 各変数の目的を簡単に説明できる
- 説明文は、その変数の目的と、どのような値が期待されるかを簡潔に説明する
- モジュールを使う人の視点で書かれるべきもの
    - モジュールをメンテする人に対する解説はコメントアウトを使う

##### validation
- 特定の変数に対するカスタム検証ルールを指定することができる

##### sensitive
- 計画や適用の出力にその値を表示するのを防ぐ
- エラー出力等では表示される場合もあるので注意

##### nullable
- 変数に null 値を代入してよいかどうかを制御
- デフォルト値はtrue
- trueの場合、変数値がnullであることを常に考慮する必要がある
- trueの場合、入力引数としてNULLを渡すと、デフォルト値が上書きされる
- falseかつデフォルト値がある場合、入力引数がNULLのとき、デフォルト値が使用される

### 利用方法
- var.<NAME>で利用する
- 環境変数を設定し、利用する
- 変数のロード順序は以下、後勝ち
    1.  環境変数
    1.  terraform.tfvars
    1.  terraform.tfvars.json
    1.  *.auto.tfvars または *.auto.tfvars.json
    1.  コマンドラインのオプション
        - 記載順に処理される

### 環境変数
- TF_VAR_の後に宣言された変数名をつけた環境変数が読み込まれる
- Terraformを自動化やTerraformコマンド実行時に便利
- 大文字小文字は区別される

#### 設定例
```bash
$ export TF_VAR_image_id=ami-abc123
$ terraform plan
...
```

### 変数定義ファイル
- terraform.tfvarsまたはterraform.tfvars.jsonという名前で作成される
- キーバリューのみ記述する
- 定義された変数の値が変数定義ファイルにない場合、エラーにはならない
- 定義されていない変数の値が変数定義ファイルにある場合、エラーになる

#### 設定例
```terraform.tfvars
image_id = "ami-abc123"
availability_zone_names = [
  "us-east-1a",
  "us-west-1c",
]
```

```terraform.tfvars.json
{
  "image_id": "ami-abc123",
  "availability_zone_names": ["us-west-1a", "us-west-1c"]
}
```

### コマンドライン
- plan、apply時に-varオプションとして利用する
    - 環境ごとに値を使い分けるとき便利
- 変数定義ファイルを作成し、plan、apply時に-var-fileオプションとして利用する

#### 設定例
```bash
terraform apply -var="image_id=ami-abc123"
terraform apply -var='image_id_list=["ami-abc123","ami-def456"]' -var="instance_type=t2.micro"
terraform apply -var='image_id_map={"us-east-1":"ami-abc123","us-east-2":"ami-def456"}'
terraform apply -var-file="testing.tfvars"
```

### 使い方例
```tf
resource "aws_instance" "example" {
  instance_type = "t2.micro"
  ami           = var.image_id
}
```

## 出力変数
### 例
```tf
output "instance_ip_addr" {
  value = aws_instance.server.private_ip
}
```

## ローカル変数
### 記載例
```tf
locals {
  # Ids for multiple sets of EC2 instances, merged together
  instance_ids = concat(aws_instance.blue.*.id, aws_instance.green.*.id)
}

locals {
  # Common tags to be assigned to all resources
  common_tags = {
    Service = local.service_name
    Owner   = local.owner
  }
}
```

### 利用例
```tf
resource "aws_instance" "example" {
  # ...

  tags = local.common_tags
}
```

# backend
- Terraformが状態データファイルを保存する場所のこと

## 例
```tf
terraform {
  backend "s3" {
    bucket = "mybucket"
    key    = "path/to/my/key"
    region = "us-east-1"
  }
}
```

# module
- Terraformでは少なくともルートモジュールが1つある
- モジュールは他のモジュールを呼び出すことができる

## モジュールの呼び出しかた
```tf
module "servers" {
  source = "子モジュールへの相対パス"
  xxx = "子モジュールで定義されている変数の値"
}
```

# data source
- Terraformの外部で定義された情報を利用する
- [AWSのdata source](https://registry.terraform.io/providers/hashicorp/aws/latest)を調べられる

## 例
```tf
# Find the latest available AMI that is tagged with Component = web
data "aws_ami" "web" {
  filter {
    name   = "state"
    values = ["available"]
  }

  filter {
    name   = "tag:Component"
    values = ["web"]
  }

  most_recent = true
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.web.id
  instance_type = "t1.micro"
}
```



# 感想
- アプリエンジニアは変数の持ち方など、知見を活かせられそう