【ふりかえり】Terraformの基礎

# 目的
Terraformの基本的な部分を学び整理する。
（AWSリソースの作成方法については、別記事でまとめようと思います）

# Terraform
システム基盤をコード管理するためのツール。
開発主体は、HashiCorp。
設定ファイルを準備し、リソースをHCL形式(HashiCorp構成言語)で記述する。

# 基本操作

## 1.terraform init
実行するときに必要となるバイナリファイルをダウンロードする。
**一番初めに実行するコマンド**

## 2.terraform validate
validate=検証
作成した「設定ファイルの記述が正しいか」構文チェックする。
`terraform plan`でも構文チェックできるが、切り分けする時に原因が「構文」か「システム側」どちら側か確認するためにvalidateコマンドを使うほうが良い。

## 3.terraform plan
「これから何が実行されるのか」実行計画が出力される。

## 4.terraform apply
「terraform plan」のように、改めてplan結果が表示される。
確認後リソースの作成を実行する。

## 5.terraform destroy
作成したリソースを削除する。

# ファイル

## 1.tfファイル
作成するリソースを定義するファイル
## 2.tfvarsファイル
tfファイルに読み込ませる変数を定義する。
## 3.tfstateファイル
現在の状態を記録する（自動的に作成される）。
tfstateのファイルの状態と、HCLのコードに差分があるとき、その差分だけを変更する。


# 基本構文
リソース定義以外の、基本的な構文をまとめる。

## 変数
**variable**を使う。
（例）`test_instance_type`変数を定義
```python:
variable "test_instance_type"{
    default = "t3.micro"
}
```

変数は実行するときに上書きできる。
コマンドを実行するときに`-var`オプションを使う。
```python:
terraform plan -var 'test_instance_type=t3.nano terraform plan'
```

環境変数でも上書きできる。
`TF_VAR_<name>`で、自動的に上書きされる。
```python:
TF_VAR_test_instance_type=t3.nano terraform plan
```
## ローカル変数
**locals**を使う。
変数`variable`と異なり、**コマンド実行時に上書きができない。**
```python:
locals {
    test_instance_type = "t3.micro"
}
```
## 出力値
**output**を使う。出力値を定義する。
`terraform apply`を実行したときに、ターミナルで値を確認できる。
```python:
output "test_instance_id" {
    value = aws_instance_test_id
```
```python:(例)applyで確認したときの出力
terraform apply
Outputs:

test_instance_id = i-xxxx
```
## データソース
外部データを参照するときに使う。

(例)最新のAmazon Linux2のAMIを参照したいとき
```python:
data "aws_ami" "recent_amazon_linux_2" {
    most_recent = true
    owners      = ["amazon"]

　　#検索条件を指定する
    filter {
    name   = "name"
    values = ["amzn-ami-hvm-2.0.xxxx-x86_64-gp2"]
    }
}

resource "aws_instance" "test" {
    ami           = data.aws_ami.recent_amazon_linux_2.image_id
    instance_type = "t3.micro"
}
```
## プロバイダ
※TerraformはAWS、GCP、Azureなどにも対応している。
プロバイダを明示的に定義する。
設定変更が可能。下記の例では、リージョンを指定している。
```python:
provider "aws" {
    region = "ap-northeast-1"
}
```

プロバイダはTerraform本体とは分離している。
`terraform init`でバイナリファイルをダウンロードする必要あり。

## セキュリティグループ
（例）EC2インスタンスを作成した後、セキュリティグループを作成する場合
```python:EC2向けにセキュリティグループ作成
resource "aws_security_group" "test_ec2" {
    name = "testz_ec2"

    ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    }

    engress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    }
}
```

```python:EC2に関連付け
resource "aws_instance" "test" {
    ami                    = "ami-xxxx"
    instance-type          = "t3.micro"
    #[TYPE.NAME.ATTRIBUTE]形式で記述
    vpc_security_group_ids = [aws_security_group.test_ec2.id]

    
```
## 組み込み関数
用意されている組み込み関数は、[公式ドキュメント](https://developer.hashicorp.com/terraform/language/functions/file)で確認します。

```:例
> file("${path.module}/hello.txt") 
Hello World
```

* `user_data.sh`を同階層に作り、`file`関数を用いてApacheのインストールスクリプトを実行する例
```:user_data.sh
#!/bin/bash
yum -y install httpd
systemctl start httpd.service
```

```python:
resource "aws_instance" "test" {
    ami               = "ami-xxxx"
    instance_type     = "t3.micro"
    user_data         = file("./user_data.sh")
```
## モジュール
[モジュール](https://zenn.dev/sway/articles/terraform_biginner_modules)についてまとめると
* 親となるソースコードから呼び出して使う
* 異なる親から同じモジュールを呼び出すことができる
* パラメータを使うことで、ふるまい制御ができる

```python:moduleを呼び出すコード
module "resource name" {
    source = "モジュール定義したフォルダパス"
    パラメーター名 = "パラメータ値"
}
```
モジュールの構造は以下の3つで成り立っている。
* パラメータ　variable
* 本体　resource
* 戻り値　output

※それぞれのモジュール定義の例は以下の通り
### 1.パラメータの定義
```python:
variable "parameter_name" {
}
```
### 2. 本文
```python:
#resource "定義名" "リソース名"
 resource "aws_instance" "test"
    instance_type = var.instance_type
```
### 3.戻り値
```python:
output "instance_id" {
    value = aws_instance.test.instance_id
}
```

### モジュール利用側の`main.tf`ファイル
下記のように利用するモジュールを`source`とし、実装したディレクトリを指定します。
```python:
module "server" {
    source = "./server"
```

# まとめ

Terraformを使うためには、
* `.tf`ファイルの作成（変数を定義するときは`.tfvars`ファイルも作成）
* `terraform varidate`で記述チェック
* `terraform plan` で「どのリソースを追加・変更・削除するか」を画面表示
* 問題なければ`terraform apply`を実行、リソースを作成する
* 環境が不要になったときは`terraform destroy`で削除する

Terraformを使うことで
* 誰が構築しても同じ環境を構築できる
作成する環境の一貫性を保つことができる
* 工数を削減できる
一度作った環境をもとに、同じ環境を構築する時の工数削減が期待できる
* Gitによるバージョン管理
管理システム(GitOpsなど)を使い、テスト環境・本番環境を管理できる

# ドキュメント
* [公式ドキュメント](https://registry.terraform.io/)
* [モジュールの使い方](https://zenn.dev/sway/articles/terraform_biginner_modules#%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)