Terraform+GCPを体験する

インフラ構成管理のコード化が可能なTerraformを使って、google cloudのインフラ設定のコード化を体験してみます。

以下のハンズオン記事を実際にやりつつ、記事の内容を流用させて頂きつつ、自分の理解した内容を追記していきます。
https://qiita.com/donkomura/items/6289bb31fecfce2cda79

# 環境
macOS High Sierra
Google Cloud SDK v265.0.0
terraform_0.12.9

# GCPプロジェクト APIの有効化

>cloud pub/sub
>cloud functions
>cloud scheduler
>app engine

以下よりアクセスして、右上の「コンソール」リンクへ。検索バーで上記サービスを検索して有効化していく。
https://cloud.google.com/

cloud pub/subやapp engineなどは、認証情報の設定が必要で、「サービスアカウント」で
設定するのが推奨。


# ダウンロード、パス設定

```bash
% mkdir myterraform; cd myterraform
% curl -sL https://releases.hashicorp.com/terraform/0.12.9/terraform_0.12.9_darwin_amd64.zip
% unzip terraform_0.12.9_darwin_amd64.zip
Archive:  terraform.zip
  inflating: terraform
```

`.zprofile`や`.zshrc`などにパス追加、souceコマンドで反映

```bash
export PATH=$PATH:~/myterraform/  
```

コマンド確認

```bash
% terraform --version                                                                                        [21:23:08]
Terraform v0.12.9
```

# 作成するTerraformの構成

Cloud Scheduler → Coud Pub/Sub → Cloud functions → Slack

Schedulerでジョブ登録、Pub/Subでメッセージの作成・送信を行う、
その送信内容や送信先をCloud functionsで設定する。
ジョブ実行結果をSlackに通知します。

# プロバイダー定義

マネージドサービスのAWSやGCPの認証情報や各種設定情報を定義するための
プロバイダーを作成します。

```json:provider.tf
provider "google" {
  credentials = "${file("<your-credential-file-path>")}"
  project     = "${lookup(var.project_name, "${terraform.workspace}")}"
  region      = "asia-northeast1"
}
```

# Terraformのシンタックス

>TerraforではHCL(HashiCorp Configuration Language)のシンタックスが採用されています。

## リソース設定方法

リソースは、Terraformで管理する対象の基本単位。操作や設定方法を記述します。  

```json
resource <リソース種類> <リソース名>　{
  <設定項目1> = <設定値1>
  <設定項目2> = <設定値2>
}
```

|項目|説明|
|---|---|
|リソース種類|Terraform内でのGCPやAWSなどで定義されているリソースの型。例) google_compute_instance|
|リソース名|Terraformモジュール内でのリソース参照に使用される。|
|設定項目|リソース種類内で使える設定項目の指定。リソース型によって決まります。|
|設定値|設定項目に設定する値。他のリソースの値を参照する際は<リソースの種類>.<リソースの名前>.<設定項目>と指定します。|

※リソース種類、リソース名はTerraformモジュール内で一意である必要があります。

google_cloud_scheduler_jobにおける記述例は以下。
リソースブロックで、リソース型がgoogle_cloud_scheduler_jobの設定が行われています。  

```json
resource "google_cloud_scheduler_job" "slack-notify-scheduler" {
  name        = "slack-notify-daily"
  project     = "${lookup(var.project_name, "${terraform.workspace}")}"
  ・・・
}
```
プロジェクト名の設定に、lookup関数が使われていて、
variableブロックで以下のように定義しておくと、project_nameを外だしできます。

var.<変数名>で参照可能。terraform.workspaceは「production, development, staging」などの
環境名称が入ります。これで設定値を切り替えたりします。

```json
variable "project_name" {
  default = {
    tf-sample = "<your-project>"
  }
}
```

# リソース設定を作成

参考サイトの設定の、リージョン、ロケーションだけ変更して作成しました。

main.tf、variable.tfを以下のサイトから拝借。
https://qiita.com/donkomura/items/6289bb31fecfce2cda79#%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%81%AE%E4%BD%9C%E6%88%90

## Cloud Schedulerに登録するジョブ

定時実行されるソースは、参考にさせて頂いたページでは、Goジョブを実行するとの設定になっていました。
※runtimeの設定の箇所

```
resource "google_cloudfunctions_function" "slack_notification" {
  name        = "SlackNotification"
  project     = "${lookup(var.project_name, "${terraform.workspace}")}"
  region      = "asia-northeast1"
  runtime     = "go111"
```

src以下にhello.go作っておいてみましたが、
動作に必要なモジュールが不足していて、ビルドエラーとなっちゃいました。

参考元ページで共有していただいた[ソース](https://github.com/donkomura/TerraformPractice)のsrc以下をそのまま配置しました。
`go.mod`に必要モジュールがあったのかも。Goも触りぐらいは理解しておかないとなぁ。


```
% ll                                                                                                    
 -rw-r--r--  1 user123  staff    55 Oct 25 23:42 go.mod
 -rw-r--r--  1 user123  staff  1979 Oct 25 23:43 slack-notify.go
```

## Web hookの設定

Slackがなければ、以下よりDLして、Web hookアプリを設定から追加します。

Slackのインストール先
https://slack.com/intl/ja-jp/help/articles/207677868

Web hookのURLをvariable.tfに追記。

## 認可、認証ファイル
GCPのcredentialファイルは、以下の手順で作成します。

- GCPにリソース作成可能なサービスアカウントを「IAMと管理」→「サービスアカウント」で作成
- 「IAM」でサービスアカウントに適切な「役割」を設定。簡単なのオーナーを設定ですが。可能であれば、今回操作するリソースに関係する権限のみを付与する
- 「サービスアカウント」で作成アカウントの操作から「鍵を作成」でjsonを生成
- main.tfが配置されている直下にjsonファイルを配置。※git管理の場合は.gitignoreに追記
- variable.tfにjsonファイル名を追記

```json:variable.tf
variable "credential" {
  default = {
    data = "xxx.json"
  }
}
```

# リソースを作成

設定ファイルが書けたら、設定ファイル内で使われているプラグインのインストールをします。

```bash
% terraform init                                                                                             [17:00:05]

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "google" (hashicorp/google) 2.16.0...
- Downloading plugin for provider "archive" (hashicorp/archive) 1.3.0...
```

次に設定ファイルの内容が文法的に正しいか`terraform validate`コマンドでチェックします。
問題なければ、続けてplanコマンドで、設定内容が意図通りか確認していきます。

```bash
% terraform validate                                                                                         [17:04:53]
Success! The configuration is valid.

% terraform plan                                                                                             [21:34:18]
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.archive_file.function_zip: Refreshing state...
・・・
Plan: 5 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
```

設定が問題なければ、`terraform apply`でGCP環境に適用していきます。


```bash
% terraform apply                                                                                            
data.archive_file.function_zip: Refreshing state...

An execution plan has been generated and is shown below.

・・・

Do you want to perform these actions in workspace "tf-sample"?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_pubsub_topic.slack_notify: Creating...
google_storage_bucket.slack_functions_bucket: Creating...
google_storage_bucket.slack_functions_bucket: Creation complete after 3s [id=projectId-scheduler-bucket]
google_storage_bucket_object.functions_zip: Creating...
google_storage_bucket_object.functions_zip: Creation complete after 1s [id=projectId-scheduler-bucket-functions.zip]
google_pubsub_topic.slack_notify: Creation complete after 5s [id=projects/projectId/topics/slack-notify]
google_cloudfunctions_function.slack_notification: Creating...
google_cloud_scheduler_job.slack-notify-scheduler: Creating...
google_cloud_scheduler_job.slack-notify-scheduler: Creation complete after 2s [id=slack-notify-daily]
google_cloudfunctions_function.slack_notification: Still creating... [10s elapsed]
google_cloudfunctions_function.slack_notification: Still creating... [20s elapsed]
google_cloudfunctions_function.slack_notification: Still creating... [30s elapsed]
google_cloudfunctions_function.slack_notification: Still creating... [40s elapsed]
google_cloudfunctions_function.slack_notification: Creation complete after 46s [id=projectId/asia-northeast1/SlackNotification]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

```

# 作成リソースの確認
リソース作成できてました。OK！

```
% gcloud beta scheduler jobs list                                                                  ID                  LOCATION         SCHEDULE (TZ)           TARGET_TYPE  STATE
slack-notify-daily  asia-northeast1  0 8 * * * (Asia/Tokyo)  Pub/Sub      ENABLED

% gcloud functions list                                                                                      NAME               STATUS  TRIGGER        REGION
SlackNotification  ACTIVE  Event Trigger  asia-northeast1

% gcloud pubsub topics list                                                                                  ---
name: projects/nice-beanbag-257013/topics/slack-notify
```

# ジョブ実行による通知確認
設定した8時にmeshi-botからの通知がきてました！
次はなんのご飯が通知されるんだろう。なんか楽しいw

![スクリーンショット 2019-10-26 11.23.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84228/23ab275a-56d9-c8c3-c786-2d49f0ccd568.png)


# 【参考】 リソース作成時のエラー

@donkomuraさんにフォローして頂き、エラー解消できました。感謝です！！
エラーにの原因は以下でした。

- variableのcredentialにリソース作成が可能な権限が付与されたサービスアカウントのjsonが設定していない
- variableのproject_nameにプロジェクトの「名前」を設定していた。※「id」を設定することで指定プロジェクトにリソースを作成できました。

リソース作成時のエラー内容は以下

```bash
% terraform apply                                                                                            
data.archive_file.function_zip: Refreshing state...

An execution plan has been generated and is shown below.

・・・

Error: googleapi: Error 403: The project to be billed is associated with an absent billing account., accountDisabled

  on main.tf line 16, in resource "google_storage_bucket" "slack_functions_bucket":
  16: resource "google_storage_bucket" "slack_functions_bucket" {

Error: Error creating Topic: googleapi: Error 403: User not authorized to perform this action.

  on main.tf line 31, in resource "google_pubsub_topic" "slack_notify":
  31: resource "google_pubsub_topic" "slack_notify" {
```