【2019年4月版】Terraform + GKE でのいろいろ小技メモ

# Terraform にだいぶ馴染んでまいりました。

[前回の記事(【2019 年 4 月版】Terraform+Spinnakerがよくね!? → 闇が深い。)](https://qiita.com/koinori/items/3e1988de28473e328251)で Spinnaker のインストールを Terraform でやってみましたが、Spinnaker 以上に Terraform でアレコレやりましたのでむしろ Terraform + GCP の闇を垣間見てしまいました。

前回の説明の流れではご紹介していない小技というか注意点をアラカルトでメモっておきます。

でもやっぱり、**敵はロール**。

## GKE から Cloud SQL Proxy を通して SQL にアクセスする

基本的には以下の公式ページの手順でOKです。

- [Google Kubernetes Engineから接続する](https://cloud.google.com/sql/docs/container-engine-connect?hl=ja)

ただし、以下のコマンドを Terraform でどうやるか、が問題です。

```
$ kubectl create secret generic cloudsql-oauth-credentials --from-file=credentials.json=[PATH_TO_CREDENTIAL_FILE]
```

手作業であればサービスアカウント作って json ファイルをダウンロードして上記のコマンドでOKですが、Terraform でそれをどうやるのか？です。

いろいろ頑張った結果、以下のようになりました。

```tf:main.tf
# サービスアカウント作成
resource "google_service_account" "k8s-sql-proxy-connect" {
  depends_on   = ["google_container_node_pool.cluster"]
  account_id   = "k8s-sql-proxy-connect"
  display_name = "SQL Proxy Connection from k8s by terraform"
}
# 役割に"編集者"を設定
resource "google_project_iam_member" "k8s-sql-proxy-connect-iam-projectEditor" {
  depends_on = ["google_service_account.k8s-sql-proxy-connect"]
  role       = "roles/editor"
  member     = "serviceAccount:${google_service_account.k8s-sql-proxy-connect.email}"
}
# サービスアカウントキーを取得
resource "google_service_account_key" "k8s-sql-proxy-connect-cred" {
  service_account_id = "${google_service_account.k8s-sql-proxy-connect.email}"
}
# kubernetes_secret で base64decodeしたprivate_keyを渡す！
resource "kubernetes_secret" "cloudsql-instance-credentials" {
  depends_on = ["google_project_iam_member.k8s-sql-proxy-connect-iam-projectEditor"]

  metadata {
    name = "cloudsql-instance-credentials"
  }

  data {
    "credentials.json" = "${base64decode(google_service_account_key.k8s-sql-proxy-connect-cred.private_key)}"
  }
}
```
ヘルプにはちゃんと明記されてましたね。。。

- https://www.terraform.io/docs/providers/google/r/google_service_account_key.html#private_key

> The private key in JSON format, base64 encoded. 

ちゃんと読め、ということで。。。

## GKE の Pod で Google Container Repository から pull

GKE 使っているので GCR から pull したい、ということはよくあると思います。

普通に使っている分にはあまり意識しなかったのですが Terraform だと一つの設定ミスでハマるので気を付けないとね、ということです。
`node_pool` の `oauth_scopes` に `storage-ro` を追加すればOKです。

```tf:main.tf
    oauth_scopes = [
      "storage-ro",
      "logging-write",
      "monitoring",
    ]
```

・・・はい。

## Cloud SQL のインスタンス名にランダム文字列を追加する

Terrform のスクラップ＆ビルドしていて一番、悩ましかったのが以下の仕様。

- [Cloud SQL: インスタンスを削除する](https://cloud.google.com/sql/docs/mysql/delete-instance?hl=ja)

> 削除したインスタンスのインスタンス名は、削除してから最大 1 週間は再利用できません。

お～。。。Terraform でCloud SQLインスタンスの設定変えて `apply` してエラー出たのでびっくりして調べたら公式の挙動ですよ。。。

`apply` & `destroy` をテスト環境で行うのに一々、名前の設定変えてられないのでランダムの文字列を追加することにいたしました。

以下のようになります。

```tf:main.tf
resource "random_id" "db_suffix" {
  byte_length = 5
}

resource "google_sql_database_instance" "cloudsql-api" {
  name             = "mysql-${lower(random_id.db_suffix.b64)}"
  database_version = "${var.mysql_version}"
  region           = "${var.gke_region}"

  settings {
...
  lifecycle {
    ignore_changes = ["disk_size", "name"]
  }
}
```

で、`mysql-jr5ghe` みたいな適当な文字列が入ります。また、インスタンス名には**小文字**しか使えなかったので `lower` 関数入れてます。

また、何かのタイミングで `name` が変わるたびにインスタンス再作成されるのは困るので `ignore_changes` に `disk_size` と `name` を入れてます。

## Cloud SQL のインスタンス、属性だけ変えて `apply` たらエラー

属性によっては強制的にインスタンスを作り替えるものがあります。というかだいたいクリティカルな属性なんで作り直しになります。

そんな時・・・**名前が変わらない**んですね！

ランダム文字列は `random_id.db_suffix` リソースが提供しますので、このリソースが再生成されない限りはランダム文字列も変わらないのです。。。

そんな時は `taint` コマンドで、このリソースを指定します。

```
$ terraform taint random_id.db_suffix
```

`taint` コマンドで指定したリソースは、次回の`apply`時に**強制的に再作成**されます。

で、`random_id.db_suffix`も次回の `apply` 時に強制的に再作成されてランダム文字列が再度、生成されます。

## 設定ファイルのフォーマット

terraform は `*.tf` ファイルの自動整形してくれる `fmt` コマンドがあります。

```
$ terraform fmt
```

で、インデントなど揃えてくれます。

VSCode の Terraform プラグインではファイル保存時に `fmt` してくれる設定があるらしいですが・・・

## kubernetes のネームスペース、その他リソースが消せなくなった。

GKE のクラスターが削除された後は kubernetes provider で操作する一切のリソースの状態は取得エラーとなってしまいます。
当たり前ですがこのあたりの連携がいまいちなんですよね。

とくにnamespaceはどうも消せるタイミングが絶妙なのでほぼ間違いなく削除時にエラーになります。
そんな時は状態(state)から削除してしまう以外にやりようがありません。
以下のコマンドで状態データから指定したリソースの状態を強制的に削除します。

```
$ terraform state rm kubernetes_namespace.default
```
次回の`apply`時には新規作成が行われます。
GKEのクラスタが消えたらkubernetes配下のstateは全削除してほしいけどなぁ。。。

## Google PubSub の Topic と Scheduler Job について

公式サイトの作り方、そのまんまなのですが・・・

- https://www.terraform.io/docs/providers/google/r/cloud_scheduler_job.html

初見で`${google_pubsub_topic.topic.id}`とあるので以下のページで確認したところ・・・

- https://www.terraform.io/docs/providers/google/r/pubsub_topic.html

`id`というフィールドは載ってないわけです。

おいおい、公式ヘルプがタイポですかい・・・と思って `${google_pubsub_topic.topic.name}`を使ってみたところ・・・

```
google_cloud_scheduler_job.job: Error creation Job googleapi: Error 400 Job name must be formatted: "projects/<PROJECT_ID>/locations/<LOCATION_ID>/jobs/<JOB_ID>".
```
とかエラーが出たわけです。

いや、このエラーメッセージは明らかにjobのnameがおかしいっていうメッセージだし、公式のREST APIのページにもJobのnameのフォーマットについて同じことが書いてあるわけです。

- https://cloud.google.com/scheduler/docs/reference/rest/v1beta1/projects.locations.jobs#Job

いや～いろいろ試してみましたが全然このエラーが解消できず・・・ふと、見つけたissueで・・・

- https://github.com/terraform-providers/terraform-provider-google/issues/3086#issuecomment-469266173

あれ？よく見ると `projects/$PROJECT_ID/topics/cron-topic` の話になっててクローズ＝解決してる・・・？
というわけでまず初めに `pubsub_target`の`topic_name`に`projects/${var.project_id}/topics/${google_pubsub_topic.topic.name}`を指定してみたところ・・・

ちゃんと動くのです。

あらま、こっちだったの？！でもわざわざ`projects/${var.project_id}/topics/${google_pubsub_topic.topic.name}`って書くのおかしくないか？

というところで、最初の `id` を思い出したのです。`id`ってそういうあれか？
試しに`pubsub_target`の`topic_name`に`${google_pubsub_topic.topic.id}`を指定したところ・・・

普通に動くー

最初から公式ドキュメント通りでよかったのね～、ということでした。


今のところ以上です。
~~**Terraform + GKE はやっぱり闇が深いな。。。**~~