Terraform × GCP （GCSバケット作成~削除）

# やりたいこと
普段GCPを使っていて、コンソールからぽちぽちインスタンスなどを作っていくのが面倒になってきました。そこでIaCの出番です。
GCPのIaC「Cloud Deployment Manager」を使ってもよかったのですが、今回はより汎用性の高いTerraformを勉強したいと思います。
ここでは、TerraformでGCSのバケットを作り、削除するという簡単な例で入門していきます。



# Terraformとは
クラウドのインフラ環境をコードで管理できるようにするのがIaC（Infrastructure as Code）。

コードでインフラを構築することで、コンソールでぽちぽち手作業でインフラを構築するとどうしても発生しうる作業ミスを防ぎます。
ソースコードをGitHub等で管理することにより、インフラのバージョン管理も可能となります。

IaCを実現するためのツールの一つが、HashiCorp社のTerraformです。
IaCツールとしては他にも、AWS専用の「CloudFormation」、Azure専用の「Azure Resource Manager」、GCP専用の「Cloud Deployment Manager」、様々なクラウドで汎用的に使える「Ansible」「Chef」などがあります。
各種IaCツールの比較は[こちら](https://qiita.com/cocoa-maemae/items/8595246f444b1c08e479)の記事がわかりやすかったです。

Terraformでは「tf」という拡張子のファイルにインフラの設定を書いていきます。
現状のリソース（Terraformで管理されるインフラ）の状態は「tfstate」という拡張子のファイルで管理されます。
tfファイルは「HCL」（HashiCorp Configuration Language）という言語で記述します。
各クラウドの各サービスにおける書き方は[こちら](https://registry.terraform.io/namespaces/hashicorp)からクラウドを選択し、Documentationから確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320845/4c1488e6-3626-3954-bc54-a91552580991.png)


# 環境
windows10 pro
GCPのCloudSDKのインストールと認証は完了しているものとします。

# インストール
（Windows）
[こちら](https://www.terraform.io/downloads)をダウンロードし、適当なフォルダに保存します。そのフォルダのパスを通せばOKです。
（macOS）
terraformのバージョン管理ツール「tfenv」をhomebrewでインストールし、そこから欲しいバージョンのterraformをインストールするのが良いそうです。
[こちら](https://takelg.com/terraform_isntall_mac/)の記事がわかりやすかったです。


`terrform -v`を実行してバージョン情報が表示されれば、インストールは完了です。

# 手順
## tfファイルの作成
今回はすべて`main.tf`に書いていきます。
Terraformはカレントディレクトリにあるすべてのtfファイルを見に行くため、複数ファイルに分割して書いても、一つのtfファイルにまとめて書いても挙動は一緒とのことでした。

```tf:main.tf
terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "3.5.0"
    }
  }
  backend "gcs" {
    bucket  = "tf-state-hoge"
    prefix  = "terraform/state"
  }
}

provider "google" {
  credentials = file("./sample_key.json")
  project     = "sample-project"
}

resource "google_storage_bucket" "hoge" {
  name          = "sample_bucket_20220628"
  location      = "asia-northeast1"
  storage_class = "STANDARD"
}
```

tfファイルの構成要素をブロックといいます。今回使用したブロックをざっと解説します。
（他にどんなブロックがあるかは[公式ドキュメント](https://www.terraform.io/language)がわかりやすいです）

#### terraformブロック
Terraformの設定を行います。
`required_providers`ブロックではプロバイダーのバージョンを指定できます。
（バージョンを指定する理由については[こちら](https://zenn.dev/sway/articles/terraform_staple_fixversion)の記事がわかりやすかったです。）
`backend`ブロックでは、tfstateファイルを保管する場所を指定します。一般的にクラウドストレージを指定します。
今回は「tf-state-hoge」というGCSバケットに保管することにします。（あらかじめバケットを作成しておきましょう。これはコンソールでするしかない...？笑）


#### providerブロック
どのクラウドのインフラを作りたいのか記述します。
`credentials`には、適切なロールを付与したterraform用のサービスアカウントの鍵JSONファイルのパスを指定します。（サービスアカウントについては[こちら](https://cloud.google.com/iam/docs/service-accounts?hl=ja)をどうぞ。）

#### resourceブロック
作成するリソースを記述します。
GCP各種サービスの作成方法は[こちら](https://registry.terraform.io/providers/hashicorp/google/latest/docs)の公式ドキュメントにまとまっています。
今回は「sample_bucket_20220628」という名前の、[ストレージクラス](https://cloud.google.com/storage/docs/storage-classes?hl=ja)「STANDARD」のGCSバケットを東京リージョンに作成します。


### tfファイルの整形

フォーマット機能が用意されているので、実行します。
```bash
$ terraform fmt
main.tf
```


## 初期化
実行したいtfファイルがあるディレクトリで初期化を行います。
```bash
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/google versions matching "3.5.0"...
- Installing hashicorp/google v3.5.0...
- Installed hashicorp/google v3.5.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```

`terraform show`で、Terraformで管理しているリソース一覧を確認できます。
今はまだ何も管理していないことがわかります。
```bash
$ terraform show
No state.
```

## 変更内容の確認
`terraform plan`で、今回の変更内容を確認します。
```bash
$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # google_storage_bucket.hoge will be created
  + resource "google_storage_bucket" "hoge" {
      + bucket_policy_only = (known after apply)
      + force_destroy      = false
      + id                 = (known after apply)
      + location           = "ASIA-NORTHEAST1"
      + name               = "sample_bucket_20220628"
      + project            = (known after apply)
      + self_link          = (known after apply)
      + storage_class      = "STANDARD"
      + url                = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if
you run "terraform apply" now.
```

## 変更の実行
想定通り「GCSバケットが1個作られる」と教えてくれているので、`terraform apply`で作成を実行します。

```bash
$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # google_storage_bucket.hoge will be created
  + resource "google_storage_bucket" "hoge" {
      + bucket_policy_only = (known after apply)
      + force_destroy      = false
      + id                 = (known after apply)
      + location           = "ASIA-NORTHEAST1"
      + name               = "sample_bucket_20220628"
      + project            = (known after apply)
      + self_link          = (known after apply)
      + storage_class      = "STANDARD"
      + url                = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_storage_bucket.hoge: Creating...
google_storage_bucket.hoge: Creation complete after 1s [id=sample_bucket_20220628]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

GCSコンソール画面を確認すると...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320845/112e635e-2eba-d01e-9df7-2a949aa3bf5d.png)


無事GCSバケットが作られました。

## リソース確認
`terraform show`で、Terraformで管理するリソースを確認できます。

```bash
$ terraform show

# google_storage_bucket.hoge:
resource "google_storage_bucket" "hoge" {
    bucket_policy_only = false
    force_destroy      = false
    id                 = "sample_bucket_20220628"
    location           = "ASIA-NORTHEAST1"
    name               = "sample_bucket_20220628"
    project            = "sample-project"
    requester_pays     = false
    self_link          = "https://www.googleapis.com/storage/v1/b/sample_bucket_20220628"
    storage_class      = "STANDARD"
    url                = "gs://sample_bucket_20220628"
```
先ほど作ったバケットが表示されました。

ちなみに、このタイミングで再度 `terraform plan`を実行すると...

```bash
$ terraform plan

google_storage_bucket.hoge: Refreshing state... [id=sample_bucket_20220628]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are
needed.
```

tfファイルの内容とGCPの内容が同じなので変更するものはないよ、と言ってくれてます。

## リソース削除
tfファイルから削除したいリソースの記述を消し`terraform plan`を行うと...
```bash
google_storage_bucket.hoge: Refreshing state... [id=sample_bucket_20220628]

Terraform used the selected providers to generate the following execution plan. Resource actions  
are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # google_storage_bucket.hoge will be destroyed
  - resource "google_storage_bucket" "hoge" {
      - bucket_policy_only = false -> null
      - force_destroy      = false -> null
      - id                 = "sample_bucket_20220628" -> null
      - labels             = {} -> null
      - location           = "ASIA-NORTHEAST1" -> null
      - name               = "sample_bucket_20220628" -> null
      - project            = "sample-project" -> null
      - requester_pays     = false -> null
      - self_link          = "https://www.googleapis.com/storage/v1/b/sample_bucket_20220628" -> null
      - storage_class      = "STANDARD" -> null
      - url                = "gs://sample_bucket_20220628" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.
```
１個削除（破壊）しますよ、と教えてくれました。
削除を実行するには `terraform apply`を行います。

```bash
(中略)
Plan: 0 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_storage_bucket.hoge: Destroying... [id=sample_bucket_20220628]
google_storage_bucket.hoge: Destruction complete after 1s

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
```

# コマンドまとめ

```bash
# 初期化
terraform init

# tfファイルのフォーマット
terraform fmt

# 何を作る・変更する・削除するか確認
terraform plan

# 実際に作成・変更・削除を実行
terraform apply

# terraform管理下のオブジェクトを確認
terraform show
```


# 参考URL
- [公式ドキュメント　Terraform Language Documentation](https://www.terraform.io/language)
- [公式ドキュメント　Google Cloud Platform Provider](https://registry.terraform.io/providers/hashicorp/google/3.5.0/docs)
- [2022年度最新版構成管理ツール(IaCツール)の比較](https://qiita.com/cocoa-maemae/items/8595246f444b1c08e479)
- [Terraform × GCP 入門](https://qiita.com/donko_/items/6289bb31fecfce2cda79)
- [Terraformバージョンを固定する - Terraformのきほんと応用](https://zenn.dev/sway/articles/terraform_staple_fixversion)
- [tfstateはS3などの共有ストレージに保存する - Terraformのきほんと応用](https://zenn.dev/sway/articles/terraform_staple_sharestate)
- [Terraformのtfstateをざっと理解する](https://qiita.com/masaaania/items/0ed291b267032a7699ad)
- [Terraformで構築するAWS](https://y-ohgi.com/introduction-terraform/handson/syntax/)