Terraform Introduction

## 記事の概要
ゴール: Terraformの仕組みと使い方を習得する
対象者: Terraform初学者

## Terraformについて
### Terraformとは
Terraformとは、IaC(Infrastructure as Code)を実現するツールのことである。
もう少し噛み砕くと、インフラの作成・更新・破棄をソースコードにより宣言して実行することができる。

コード化することができることはたくさんの利点を開発者にもたらす。
例えば、
- インフラ環境がGitによりバージョン管理できるようになる
- 一度環境を作ってしまえば、他の環境にも設定を使い回すことができる
- コードを見るだけで環境を把握できる
- 社内でインフラの状態をコードレビューできる
- コードを数行変更するだけで繰り返しインフラ構成を変更できる

### Terraformの仕組み
#### Terraformとプロバイダーの関係
Terraformは各クラウドプロバイダー(AWS・Azure・GCPなど)のAPIと通信をするためにプロバイダープラグインを使用する

#### .tfファイル
インフラの設定ファイル
- この拡張子で作成されたファイルにインフラを定義する

#### .tfstateファイル
Terraformには.tfstateという状態ファイルが存在する
- .tfstateファイルはクラウド上のリソースの現在の状態を表現するものであり、リソースのIDや属性、メタデータを含んでいる
- このファイルを用いて状態管理を行なっている

#### Terraformの実行フロー
1. `terraform init`
- .terraformディレクトリを作成し、プロバイダープラグインをダウンロードする
- モジュールを新規で定義したときもこのときにダウンロードされる
    - モジュールとはライブラリのようなもの、今回は触れない
- この時状態ファイルは変更されない

2\. `terraform plan`
- 作成した.tfファイルを解析し、必要なリソースを特定する
- .tfstateファイルを読み込み、現在の状態を把握する
- プロバイダーのAPIを呼び出し実際のリソースの状態を確認
    - ここで検出されるのは以下の要素
        - .tfstateファイルの情報が最新か
        - .tfstateファイルの情報が最新かを確認するために、そこで定義された内容に関して手動で行われた変更を確認
        - 動的に変化する属性(IPアドレスなど)の最新値を取得
- 差分を計算し以下を示す実行計画(リスト)を作成する
    - 作成される新しいリソース
    - 更新される既存のリソース
    - 削除されるリソース
    - 変更されないリソース

3\. `terraform apply`
- 実行計画に基づき、プロバイダーのAPIを呼び出してリソースを作成・更新・削除する
- 操作が成功した場合、新しい状態を.tfstateに反映
- 基本的にはplanをしたときにエラーは検出されるが、planが通ってもapply時にエラーが発生する場合はあるので注意
    - 作成する権限の不足
    - 新規作成しようとしたところ、AWS側にすでに同じ名前のものが存在していた

#### インポート機能
- `terraform import`で既存のAWSリソースを.tfstateに取り込む
- これによりTerraform外で作成されたリソースも管理下における

#### リフレッシュ機能
- `terraform refresh`で.tfstateをAWS上の実際の状態と同期


## 演習
今回はTerraformを使ってAWSのs3 bucketを作成してみる
- 環境
    - MacOS
    - Appleチップ
### Terraform環境構築
[公式の環境構築](https://developer.hashicorp.com/terraform/install)
#### AWSの準備
AWSをマネジメントコンソール以外から操作するために、アクセスキーとシークレットキーを作成する
1. サービス検索でIAMを探してクリック
2\. 左メニュー一覧から「ユーザー」をクリック
3\. 使用するユーザーのユーザー名をクリック
4\. 認証情報をクリック
5\. アクセスキーの作成ボタンをクリック
    - AWSアクセスの種類を選択でプログラムによるアクセスにチェックを入れたい
6\. ここで出てきた情報をメモするか、csvをダウンロードしてアクセスキーとシークレットアクセスキーを参照できるようにしておく

#### Terraformの準備
```
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform  --version # ちゃんとインストールができているか確認
```
#### AWS CLIの準備
```
brew install awscli
aws --version # ちゃんとインストールができているか確認
cd ~ # homeディレクトリへ移動
aws configure
  AWS Access Key ID [None]: xxxxxxxx
  AWS Secret Access Key [None]: xxxxxxxxxxxxxxxx
  Default region name [None]: ap-northeast-1
  Default output format [None]: 
aws sts get-caller-identity # AWSの設定がうまくいっているか確認
```

### Terraformでs3 bucketを作成する
今回は以下のようなディレクトリ構成を取る
```
root
┣ main.tf
┣ variables.tf
┗ s3.tf
```

なお、実際に自分で作る時には公式の[ベストプラクティス](https://www.terraform-best-practices.com/code-structure)を参考されたし
#### main.tf
main.tfには全体で共通な設定を書く(プロバイダの設定など)
- providerの後に宣言指定いるのはプロバイダのタイプ
```main.tf
# AWS プロバイダの設定
provider "aws" {
  access_key = var.access_key
  secret_key = var.secret_key
  region     = var.region
}
```

#### variables.tf
valiables.tfには全ソース共通の変数を書く
- variableの後に宣言しているのが変数名
- defaultにあるのが変数の初期値
```variables.tf
variable "region" {
  default = "ap-northeast-1" # 東京region(東京にあるAWSのデータセンターやサーバーの集まりのこと)
}

variable "access_key" {
  default = "自分のaccess_key"
}

variable "secret_key" {
  default = "自分のsecret_key"
}
```
#### s3.tf
一つのs3バケットを構築するコードをs3.tfに書いていく
- "aws_s3_bucket"はリソースタイプを示す
    - これは[terraform document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket)にて定義されている
    - aws s3 bucketを作ることを指定している

- 一つ目の"terraform-hanson"はリソースの論理名である
    - Terraformの状態管理やリソース参照に使用される
    - このコード内や他のリソースからこのバケットを参照する際に使用する

- 二つ目の"terraform-hanson"は作成するS3バケットの実際の名前である

```s3.tf
resource "aws_s3_bucket" "terraform-hanson" {
  bucket = "terraform-hanson"
}
```

#### 実際にAWSへデプロイしてみよう
```
terraform init
terraform plan
terraform apply
```

作成されているかAWSのマネジメントコンソールを確認してみよう

### Terraformでs3 bucketにオブジェクトをアップロードする
次に、実際にs3 bucketにオブジェクトをアップロードしてみる。
s3 bucketには静的ページなどのリソースを置くことが多いので、今回はhtmlファイルをおいてみる。
ディレクトリ構成は以下の通り
```
root
┣ www
┃  ┗ index.html
┣ main.tf
┣ variables.tf
┗ s3.tf
```

#### index.html
正直なんでも良い！とりあえず以下のようなindex.htmlを作成する
```index.html
<html>
  <head>
    <title>Hello world !</title>
  </head>
  <body>
    <h1>Hello world !</h1>
  </body>
</html>
```

#### s3.tfの変更
s3オブジェクトリソースの定義を追記する
- s3にはフォルダという概念がなく、key-valueでオブジェクトを格納している
    - そのため、s3にオブジェクトをアップロードする時にはkeyを設定しフォルダのようなプレフィックスをつけてファイルを格納している

```diff_terraform
resource "aws_s3_bucket" "terraform-hanson" {
  bucket = "terraform-hanson"
}

+ resource "aws_s3_object" "index_page" {
+   bucket = aws_s3_bucket.terraform-hanson.id
+   # S3へアップロードするときのkey値
+   key = "www/src/index.html"
+   # ファイルのローカルパス
+   source = "www/index.html"
+   content_type = "text/html"
+ }
```

#### 再度デプロイ
```
terraform plan
terraform apply
```

aws側でちゃんとindex.htmlが作成されているか確認しよう

### お片付け
terraformで作ったものは全てこのコマンドで消し去ることができる
```
terraform destroy
```

### Apple Silicon Macをお使いの方向け
terraformが動いたり動かなかったりする場合にはこの環境変数を設定してみてほしい
```
export GODEBUG=asyncpreemptoff=1
```

[参考サイト1](https://zenn.dev/bun913/articles/m1-mac-terraform-unstable)
[参考サイト2](https://github.com/hashicorp/terraform/issues/27350#issuecomment-751269666)