【初学者向け】Terraformの基本

## Terraformとは
* IaCを実現するツール
* インフラの初期プロビジョニング、更新、破棄をコード管理できる

* メリット
    * バージョン管理ができる
    * 他者との共有、再利用ができる
    * 構成をスムーズに変更できる
    * 他のプロバイダへの移行がしやすい

## 初期準備
### Terraformのインストール (macOSの場合です。適宜各環境にあった方法でインストールしてください)
* `brew install tfenv`
  * Terraformのバージョンに追随するためにもtfenvを使うのがおすすめです。
* `tfenv list-remote`
  * 現在利用可能なTerraformのバージョンを列挙します
* `tfenv install x.xx.x`
  * リストから対象のバージョンを選択します。
* `tfenv use x.xx.x`
  * インストールしたものの中で使うTerraformのversionを指定します。

### providerの準備
* プロパイダーとは
    * AWSやGoogleCloudなどのプラットフォームやソフトウェア、ハードウェアのAPIと対話するためのプラグイン。
        * 変更したい対象のものを指定する。
* どんなプロバイダーがあるかは下記を参照してください
    * https://registry.terraform.io/browse/providers

今回は使用ケースの多い、AWSを中心に説明していきます。

#### AWSの場合
AWSでの変更を行うために、予め認証情報を取得しておく必要があるので、先に対応を済ませておきます。
##### IAMユーザーの作成

下記、公式を参考にIAMユーザーを作成します。
権限は適宜必要なものに絞ってください。
**シークレットキーが漏れると大変なことになります。**
**慎重に操作してください。**

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_users_create.html

##### AWS CLIの設定

まだaws cliを取得していない方は下記コマンドでインストールします。

`brew install awscli`

続いて、Terraformでawsを使用するためのprofileを設定します。
YOUR_PROFILE_NAMEは何でもいいです。わかりやすい名前にしましょう。
今回はtestとします。(後半のtfファイルでprofile = test と指定しています。)

```terminal
$ aws configure --profile YOUR_PROFILE_NAME
AWS Access Key ID[None]: IAMで作成したアクセスキーID
AWS Secret Access Key[None]: IAMで作成したシークレットアクセスキー
Default region name[None]: ap-northeast-1
Default output format[None]: json

# 下記のコマンドで接続ができるか確認してください。
$ aws sts get-caller-identity --profile YOUR_PROFILE_NAME

# Terraformで使用するAWS_PROFILEを環境変数に設定
$ export AWS_PROFILE=YOUR_PROFILE_NAME
```

## Terraform構築
これで、すぐにでもTerraformを使っていく準備は整いました。
ですが、適当にリソースを作っていくと、管理が複雑になってしまうので、
管理がしやすくなるディレクトリ構成について、紹介しながら、Terraformの使い方を説明していきます。

### ディレクトリ構成について
例
```
  - service (例: サービス毎にディレクトリを区切る)
    - backend.tf (Terraformのstate管理用)
    - outputs.tf (他のディレクトリや他のリポジトリで現ディレクトリのリソース設定を使いたい場合に定義する)
    - provider.tf (どこのプロバイダーを指定するか。 awsやgcpなど)
    - remote_state.tf (他のディレクトリやリポジトリのTerraformのstate情報を取得する)
    - variables.tf (変数を定義。全体で共通して使うものなど。)
    - xxx.tf (s3やecsなどAWSのサービス毎にファイルを区切る) 
```

* backend.tfに記載されたstateの保存先と見比べて、記載のコードとの変化を見る
  * `terraform plan`時にでてくる変更点はstateで保持されている内容とコードの差分

* 上記ファイルの準備ができたら`terraform init`を実行する(初回だけ)
  * ワークスペースを初期化するコマンドで、aws providerなどのダウンロード処理が走る


### ファイルの記述方法
#### backend.tf

```tf
terraform {
  backend "s3" {
    bucket  = "terraform-state-bucket"
    key     = "terraform.state.test"
    region  = "ap-northeast-1"
    profile = "test"
  }
}
```

Terraformではリソースを作成や変更したときに、stateというインフラの状態を管理しているファイルを作成します。
そのstateファイルは、個人で使う分にはローカルに保存しておいても問題ないのですが、複数人で使用する場合、
共有フォルダを作成してどこかで管理する必要が出てきます。
その共有フォルダの指定を行うために、このbackendというものがあリます。

今回はawsのs3で管理するので、s3のバケット名を指定し、keyに保存するファイル名を指定しています。

#### outputs.tf

```tf
output "alb_dns" {
  value = aws_alb.alb.dns_name
}
```

別のプロジェクトなどで使う値を定義するために使います。詳しくは[こちらの記事](https://qiita.com/kyntk/items/2cdd38c2438ac257ac4e#2-%E5%AD%90%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E5%B1%9E%E6%80%A7%E3%82%92%E8%A6%AA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AB%E5%85%AC%E9%96%8B%E3%81%A7%E3%81%8D%E3%82%8B)がおすすめです。
output `""`の名前はなんでもよい。外部で取得するときの名前になる。わかりやすい名前にするのがいい 。  
valueに入るのは通常のリソースの指定と同じで、対象リソースを指定する(詳しくはxxx.tfで後述)  。
上の例はawsのalbでの例。


#### provider.tf

```tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "3.65"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region  = "ap-northeast-1"
  profile = "test"
}

provider "aws" {
  alias   = "virginia"
  region  = "us-east-1"
  profile = "test"
}
```


provider `""` required_providersで指定した対象のプロバイダーを指定する。
regionやprofileを変えたい場合はブロックの中にaliasを使うことで、他のファイルでも変更できる。
各リソースの設定でproviderの指定がない場合はデフォルトの設定が適用される。
`provider = aws.virginia`といった形で各リソースで宣言することで適用される。

#### remote_state.tf

```tf
data "terraform_remote_state" "hogehoge" {
  backend = "s3"

  config = {
    bucket  = "terraform-state-bucket"
    key     = "terraform.state.test"
    region  = "ap-northeast-1"
    profile = "test"
  }
}

```

* 外部から参照したいstateを取得する
    * s3でstate管理している場合はs3のバケットを指定すればOK

#### variables.tf
```tf
locals {
  project_name = "hoge"
}
```

共通して使いたい変数を定義。下記のような形で他のファイルで呼び出し可能  
`local.project_name`

#### xxx.tf(サービス毎のファイル)
* Terraformの公式ドキュメントを参照するのが良い。
  * https://registry.terraform.io/providers/hashicorp/aws/latest/docs

以下はvpcの例。
この場合はファイル名をvpc.tfとして作る
```
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}
```

`resource "aws_vpc" "example"` 最初のaws_vpcは決められた値。利用するサービス毎に固定。後ろのexampleはリソースの定義名。好きに決めて良いです。

ブロックの中身は利用するサービス毎に異なるので、公式ドキュメントを参考にしつつ記載すればOK


## terraformコマンド
### よく使うもの
* `terraform plan` 実行前に、記載したコードでどんな変更があるかを確認できる
* `terraform apply` コードで記載したものを実際に反映する
* `terraform destroy` リソースを削除します。
### 使うと便利なもの
* `terraform fmt` コードの自動整形をしてくれる
* `terraform import` すでに作られているもの(AWSコンソール上等で)をコード管理できる
その他、全容については`terraform -h`で確認してください。

### その他tips

+ どのリソースに対しても付けられるオプションに、lifecycleというのがあります。
    + リソースの変更時に特定の挙動を指定できるオプション。
    + 例えば`prevent_destroy`をつけておくと、消すときにエラーが出るようになるので、誤った操作が起きないように、とりあえずつけておくと安心です。
    + 詳しくは公式ドキュメントを参照してください！[lifecycleについて](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle)

```terraform
  lifecycle {
    prevent_destroy = true
  }
```



## 参考
+ [Terraformのディレクトリパターン集](https://qiita.com/reireias/items/253529c889cafb3fa4c7)
+ [Terraformにおけるディレクトリ構造のベストプラクティス](https://dev.classmethod.jp/articles/directory-layout-bestpractice-in-terraform/)
+ [Terraformのメリットや仕組み、用語など基本情報をまとめてみた](https://gmor-sys.com/2021/04/28/terraform-japanese/)