チュートリアル「Terraform、Cloud Build、GitOps を使用してインフラストラクチャをコードとして管理」の実施

# お題
以下の記事をトレースするだけ。
https://cloud.google.com/solutions/managing-infrastructure-as-code?hl=ja

# 前提
以下の記事でのセットアップが完了していること。
https://qiita.com/sky0621/items/c488e8adb2a51870ad22

以下については知識があるものとしてやり方や概念など説明しない。

- GitHubでのフォークのやり方
- [GCS(Cloud Storage)](https://cloud.google.com/storage?hl=ja)

# 開発環境
## # OS - Linux(Ubuntu)
```
$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.4 LTS (Bionic Beaver)"
```

## # Git
```
$ git version
git version 2.26.2
```

## # Terraform
```
$ terraform version
Terraform v0.12.25
```

## # gcloud
```
$ gcloud version
Google Cloud SDK 294.0.0
```

# 実践

## ■ アーキテクチャ
以下に記載の通り、「`dev`」と「`prod`」という（実際の開発現場ならもうひとつ「`stg`」もあるかも）構成。
https://cloud.google.com/solutions/managing-infrastructure-as-code?hl=ja#architecture

## ■ 使用リポジトリ
以下をフォーク。
https://github.com/GoogleCloudPlatform/solutions-terraform-cloudbuild-gitops

で、ローカル（作業端末）にクローンしておく。
ディレクトリ構成は以下の通り。

```
$ tree
.
├── cloudbuild.yaml
├── environments
│   ├── dev
│   │   ├── backend.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── terraform.tfvars
│   │   ├── variables.tf
│   │   └── versions.tf
│   └── prod
│       ├── backend.tf
│       ├── main.tf
│       ├── outputs.tf
│       ├── terraform.tfvars
│       ├── variables.tf
│       └── versions.tf
└── modules
    ├── firewall
    │   ├── main.tf
    │   ├── outputs.tf
    │   ├── variables.tf
    │   └── versions.tf
    ├── http_server
    │   ├── main.tf
    │   ├── outputs.tf
    │   ├── variables.tf
    │   └── versions.tf
    └── vpc
        ├── main.tf
        ├── outputs.tf
        ├── variables.tf
        └── versions.tf
```

以降、上記のディレクトリ構造であることを前提とする。

## ■ 状態の保存先をGCSバケットに変更
`terraform.tfstate`（インフラ構築状態が保存されているファイル）の格納先をローカルからGCSバケット内に変更。

### GCSバケット作成

GCPプロジェクトIDを名前の一部に含むバケット名を作るとバケット名の重複が防げる。

```
$ env | grep GOOGLE
GOOGLE_CREDENTIALS=/home/sky0621/.config/gcloud/my-gcp-prj-01-terraform-credential.json
GOOGLE_PROJECT=my-gcp-prj-01
$
$ gsutil mb -l asia-northeast1 gs://${GOOGLE_PROJECT}-tfstate
Creating gs://my-gcp-prj-01-tfstate/...
```

あと、デプロイ履歴を保持できるようにしておく。

```
$ gsutil versioning set on gs://${GOOGLE_PROJECT}-tfstate
Enabling versioning for gs://my-gcp-prj-01-tfstate/...
```

![screenshot-console.cloud.google.com-2020.05.30-17_38_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/374e2096-a51c-a88e-4b62-98b13a62c418.png)

### `backend.tf` ファイルの修正
`terraform.tfstate`の格納先の指定は`backend.tf`ファイルで行っているので、dev と prod それぞれの環境別に置いてある当該ファイルを修正。
修正後は以下のようになる。

```:environments/dev/backend.tf
terraform {
  backend "gcs" {
    bucket = "${GOOGLE_PROJECT}-tfstate"
    prefix = "env/dev"
  }
}
```

※ `environments/prod/backend.tf` の方も同様に修正。

### `terraform.tfvars` ファイルの修正
dev と prod それぞれの環境において、定義時にGCPプロジェクトIDの指定が必要になる箇所が複数ある。
そのため、変数として定義しておいて、必要になる箇所ではその変数を参照する記述にしてある。
変数が定義されているのは、`terraform.tfvars` ファイル。
この中も以下のように環境変数からGCPプロジェクトIDを参照するように修正する。

```:environments/dev/terraform.tfvars
project="${GOOGLE_PROJECT}"
```

### ここまでの状況をGitHub上のリポジトリにPush

```
$ git status
ブランチ dev
Your branch is up to date with 'origin/dev'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   environments/dev/backend.tf
	modified:   environments/dev/terraform.tfvars
	modified:   environments/prod/backend.tf
	modified:   environments/prod/terraform.tfvars
```

```
$ git add -A
```

```
$ git status
ブランチ dev
Your branch is up to date with 'origin/dev'.

コミット予定の変更点:
  (use "git restore --staged <file>..." to unstage)
	modified:   environments/dev/backend.tf
	modified:   environments/dev/terraform.tfvars
	modified:   environments/prod/backend.tf
	modified:   environments/prod/terraform.tfvars
```

```
$ git commit -m "Update project IDs and buckets"
[dev 6c54e5f] Update project IDs and buckets
 5 files changed, 8 insertions(+), 5 deletions(-)
```

```
$ git status
ブランチ dev
このブランチは 'origin/dev' よりも1コミット進んでいます。
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
```

```
$ git push origin dev
Enumerating objects: 17, done.
Counting objects: 100% (17/17), done.
Delta compression using up to 4 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (9/9), 750 bytes | 375.00 KiB/s, done.
Total 9 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), completed with 4 local objects.
To github.com:sky0621/solutions-terraform-cloudbuild-gitops.git
   bf83e89..6c54e5f  dev -> dev
```

## ■ Cloud Build 関連の設定

### API使えるようにしておく

```
$ gcloud services enable cloudbuild.googleapis.com
Operation "operations/xxx.99999999-xxxx-999-xxxx-99999999" finished successfully.
```

### Cloud Build サービスアカウントに権限を付与
[元記事](https://cloud.google.com/solutions/managing-infrastructure-as-code?hl=ja#granting_permissions_to_your_cloud_build_service_account)でも記載があるけど、「とりあえず `roles/editor` をつけているものの本来なら[必要最小限なロール](https://cloud.google.com/docs/enterprise/best-practices-for-enterprise-organizations?hl=ja#control-access)を付与すべき」とあるので実際の開発現場では留意すべき。

#### サービスアカウントの情報を得る

プロジェクト自体の情報

```
$ gcloud projects describe ${GOOGLE_PROJECT}
createTime: '2020-05-27T15:19:59.167Z'
lifecycleState: ACTIVE
name: My Project 19162
projectId: my-gcp-prj-01
projectNumber: '999999999999'
```

プロジェクトナンバー部分を抜粋

```
$ gcloud projects describe ${GOOGLE_PROJECT} --format 'value(projectNumber)'
999999999999
```

上記ナンバーを含むCloud Build サービスアカウントのメアドを環境変数にセット

```
$ export CLOUDBUILD_SA="$(gcloud projects describe ${GOOGLE_PROJECT} --format 'value(projectNumber)')@cloudbuild.gserviceaccount.com"
$
$ env | grep CLOUDBUILD_SA
CLOUDBUILD_SA=999999999999@cloudbuild.gserviceaccount.com
```

#### ロール付与

```
$ gcloud projects add-iam-policy-binding ${GOOGLE_PROJECT} --member serviceAccount:${CLOUDBUILD_SA} --role roles/editor
Updated IAM policy for project [my-gcp-prj-01].
bindings:
　　　〜〜〜
  - serviceAccount:999999999999@cloudbuild.gserviceaccount.com
  role: roles/editor
　　　〜〜〜
```
![screenshot-console.cloud.google.com-2020.05.30-22_55_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/1b515694-c44e-3034-0aaa-39b22b2a99a9.png)

### Cloud Build を GitHub リポジトリに接続

以下を参照しながら。
https://cloud.google.com/solutions/managing-infrastructure-as-code?hl=ja#directly_connecting_cloud_build_to_your_github_repository

#### Cloud Build アプリの GitHub Marketplace ページにアクセス
https://github.com/marketplace/google-cloud-build

![screenshot-github.com-2020.05.30-23_05_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/1e4cf56c-9e44-452d-5155-9b70f0b18486.png)
![screenshot-github.com-2020.05.30-23_08_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/c04f1048-74b9-9311-7db0-d7f01bb4171e.png)

ちなみに、下記とのこと。

> Cloud Buildは、無料枠を超えるビルド1分あたりの料金です。
> 1日120分の無料ビルド分。その後$ 0.003 /分。
> 最大10の同時ビルドが含まれます。
> ビルドされたイメージをGoogle Container Registryに自動的にプッシュします（ストレージコストがかかる場合があります）。
> 詳細については、Cloud Buildの料金ページをご覧ください。 
> https://cloud.google.com/cloud-build/pricing

![screenshot-github.com-2020.05.30-23_09_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/90346c30-a2d2-a145-9a2d-1c1530fdf706.png)

![screenshot-github.com-2020.05.30-23_11_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/7b9c8f4a-3393-0577-788b-12af61fdd841.png)

![screenshot-accounts.google.com-2020.05.30-23_12_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/e942e7a0-7d66-f568-c85e-58ea4317e497.png)

![screenshot-github.com-2020.05.30-23_13_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/db385e95-6b45-1251-7456-90254e545878.png)

![screenshot-console.cloud.google.com-2020.05.30-23_15_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/61d6eff3-8e04-29d2-a143-b7db19bc2990.png)

![screenshot-console.cloud.google.com-2020.05.30-23_16_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/b235406d-bfe7-e8d9-4a5d-ee1060553bac.png)

![screenshot-console.cloud.google.com-2020.05.30-23_17_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/6be612c8-2913-bc0e-db07-f75e9ca2df37.png)

![screenshot-console.cloud.google.com-2020.05.30-23_19_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/45e7367e-3720-c0e4-8c2a-8a95c972f2d1.png)

![screenshot-github.com-2020.05.30-23_20_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/180ae8c1-a083-8266-9b6e-bd7670eae308.png)

終わった様子。

![screenshot-github.com-2020.05.30-23_20_57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/e195601d-532f-f215-8bb9-7daf58bb4856.png)

## ■ ソースの変更によるCloud Build発動を確認

### ローカルでソース修正しリモートにPush

```
$ pwd
/home/sky0621/work/src/github.com/sky0621/solutions-terraform-cloudbuild-gitops
$
$ git branch
* dev
$
```

フォーク元リポジトリで（学習用に）タイプミスされていたファイルを修正。

```
$ git diff
diff --git a/modules/firewall/main.tf b/modules/firewall/main.tf
index 5e40f70..1d2e549 100644
--- a/modules/firewall/main.tf
+++ b/modules/firewall/main.tf
@@ -27,6 +27,6 @@ resource "google_compute_firewall" "allow-http" {
     ports    = ["80"]
   }
 
-  target_tags   = ["http-server2"]
+  target_tags   = ["http-server"]
   source_ranges = ["0.0.0.0/0"]
 }
```

上記バグ修正のコミット用にブランチを切って、上記の内容を反映。

```
$ git checkout -b bugfix origin/dev
M	modules/firewall/main.tf
Branch 'bugfix' set up to track remote branch 'dev' from 'origin'.
Switched to a new branch 'bugfix'
$
$ git branch
* bugfix
  dev
$
$ git add .
$
$ git status
ブランチ bugfix
Your branch is up to date with 'origin/dev'.

コミット予定の変更点:
  (use "git restore --staged <file>..." to unstage)
	modified:   modules/firewall/main.tf
$
$ git commit -m "http ファイアウォール ターゲットの修正"
[bugfix 82e283a] http ファイアウォール ターゲットの修正
 1 file changed, 1 insertion(+), 1 deletion(-)
$
$ git status
ブランチ bugfix
このブランチは 'origin/dev' よりも1コミット進んでいます。
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
$
$ git push -u origin bugfix
Enumerating objects: 9, done.
　　〜〜〜
To github.com:sky0621/solutions-terraform-cloudbuild-gitops.git
 * [new branch]      bugfix -> bugfix
Branch 'bugfix' set up to track remote branch 'bugfix' from 'origin'.
```

### 作成したバグ修正ブランチを `dev` ブランチにマージ

![screenshot-github.com-2020.05.30-23_33_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/9bb8361b-9df8-9611-7b2f-6db184e7b656.png)

![screenshot-github.com-2020.05.30-23_36_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/c467bb9c-d348-cdef-c91e-378926e4441d.png)

![screenshot-github.com-2020.05.30-23_39_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/8e5592d0-74d9-115d-ac17-2e0eca4058ca.png)

あれっ、Cloud Buildのとこで失敗してる？
というわけで、詳細を見に行く。

![screenshot-github.com-2020.05.30-23_41_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/2cd10043-6102-7964-7b57-05414fdd73cf.png)

これでもよくわからないので、Cloud Build側のページへ飛ぶ。

![screenshot-console.cloud.google.com-2020.05.30-23_42_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/ca141bcd-edc3-9455-907a-4a54e7449677.png)

`terraform init` で失敗してるのかな。
「生データを表示」してみると、

```
Step #1 - "tf init": [0m[1mInitializing the backend...[0m
Step #1 - "tf init": [31mError loading backend config: 1 error occurred:
Step #1 - "tf init": 	* terraform.backend: configuration cannot contain interpolations
Step #1 - "tf init": 
Step #1 - "tf init": The backend configuration is loaded by Terraform extremely early, before
Step #1 - "tf init": the core of Terraform can be initialized. This is necessary because the backend
Step #1 - "tf init": dictates the behavior of that core. The core is what handles interpolation
Step #1 - "tf init": processing. Because of this, interpolations cannot be used in backend
Step #1 - "tf init": configuration.
Step #1 - "tf init": 
Step #1 - "tf init": If you'd like to parameterize backend configuration, we recommend using
Step #1 - "tf init": partial configuration with the "-backend-config" flag to "terraform init".
Step #1 - "tf init": 
Step #1 - "tf init": [0m[0m
Finished Step #1 - "tf init"
ERROR
ERROR: build step 1 "hashicorp/terraform:0.11.14" failed: step exited with non-zero status: 1
```

「`terraform.backend: configuration cannot contain interpolations`」とのこと。
ググる。
https://qiita.com/aoshimash/items/f0be7dd81c856d335460
`backend.tf`で変数使えない様子。。。
しょうがないので変数使わず、再度Push。

![screenshot-github.com-2020.05.30-23_58_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/db26ec7c-cc34-01ea-d52b-18b0d74e9b3e.png)

お、先ほどはすぐに失敗したけど、しばらく継続してる様子。

![screenshot-github.com-2020.05.31-00_01_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/b3cecd48-3fea-1d68-3888-17bc99fbe294.png)

成功した。
詳細を見てみる。

![screenshot-github.com-2020.05.31-00_04_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/f2755e2b-7976-8dc7-d86d-930b5efe6ffd.png)
![screenshot-console.cloud.google.com-2020.05.31-00_05_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/2300c2ae-ecae-aaa3-1be0-9db68056513c.png)

失敗時にも出ていたけど、以下に記載の４つのステップが実行されたログとなっている。
https://github.com/GoogleCloudPlatform/solutions-terraform-cloudbuild-gitops/blob/master/cloudbuild.yaml
つまり、`terraform apply` までされているので、うまくいっているなら、適用対象のGCPプロジェクトにTerraformの定義が反映されているはず。

なのだけど、よくよくログを見ると、
![screenshot-console.cloud.google.com-2020.05.31-00_21_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/44f13b84-8455-c64b-c9b6-7635483d5111.png)

そう、今回、`bugfix` ブランチから `dev` ブランチへのプルリクは作ったけど、まだマージしてない。
このCloud Buildログは、あくまで `bugfix` ブランチが出来たから流されたもので、設定としては `dev` と `prod` 以外は `terraform apply` はしないものとなっているので、実際にGCP環境には適用されていない。

では、プルリクをマージして、`dev` ブランチを更新してみる。
![screenshot-github.com-2020.05.31-00_24_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/ded63058-7932-b6f1-c249-eefae7a1234b.png)
![screenshot-github.com-2020.05.31-00_25_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/2acce0cc-491f-173b-8b69-ef18e549f713.png)

はい、マージ完了。
Cloud Build側でビルド状況を確認してみる。
![screenshot-console.cloud.google.com-2020.05.31-00_26_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/4f007e28-6d4e-5794-8514-1301d685f0b5.png)
すると、`dev`ブランチに更新がかかったゆえの実行中ビルドが増えている。

完了後のログ詳細を見てみると、
![screenshot-console.cloud.google.com-2020.05.31-00_28_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/d15afb73-dac6-589e-b3e9-281a868a007e.png)
今度は、スキップされずに `terraform apply` が実行されている。

## ■ 反映結果の確認
まずは、Terraform実行による状態管理をCloud Storage上で行う設定にしたので、その確認。
![screenshot-console.cloud.google.com-2020.05.31-00_17_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/e1d35b07-8bb3-6e76-b3f5-b13cbb0223d8.png)

うん、`tfstate` ファイルが格納されている。


うまくいっているなら、以下が出来ているはず。

- VPC
- Firewall
- Compute Engine

![screenshot-console.cloud.google.com-2020.05.31-00_30_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/6205e84b-6ce1-d79d-0331-749433bcc464.png)
![screenshot-console.cloud.google.com-2020.05.31-00_31_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/485f43b2-72b0-5e51-074d-8f96f3a6f132.png)
![screenshot-console.cloud.google.com-2020.05.31-00_31_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180599/2e451a8e-0fb9-9637-3f02-3ab9b82996e1.png)

それぞれ出来ておる。

# まとめ
参考記事では、このあと、「Cloud Build の実行が成功した場合にのみマージを適用できるようにする」対応や本番環境の方への適用などもやっているけど、ちょっと力尽きたので今回はこのへんでおしまい。