GKEでkubernetesのnodesをロードバランサーのバックエンドとして使いたいとき with terraform

これがなかなか面倒だったのでメモ。

# やりたいこと
1. terraformでhttp load balancer、gke container clusterを作成する
2. http load balancerのバックエンドとしてclusterのnodeを登録する

# つくりかた

## 1. http load balancerを作成する

googleのhttp load balancerは疎結合な部品の組み合わせで成り立っており、
http load balancer = global forwarding rule + target http proxy + url map
です。
[terraformのdoc](https://www.terraform.io/docs/providers/google/r/compute_global_forwarding_rule.html)を参考にまるっと作ります。
 
## 2. GKE clusterを作成する
これは特に意識せずに[terraformで作成](https://www.terraform.io/docs/providers/google/r/container_cluster.html)して問題ないですが、一点だけ注意しないといけないのは

```
 addons_config {
   http_load_balancing {
     disabled = false
   }
   horizontal_pod_autoscaling {
     disabled = true
   }
 }
```

のように、GKE上でさらにload balancerを作らないようにします。
ここで`true`にしてしまうと、ロードバランサーの下にさらにロードバランサーがぶら下がることになってしまうので。
terraformで作成した時、自動的にnodeとしてインスタンスがいくつか立ち上がります。

## 3. backend serviceにnodesを登録
ここが一番困ったところ。
なぜなら、`google_container_cluster.cluster-sample.instance_group_urls`というreferenceが返してくれるのは
instance groupではなくinstance group managerだからです。
これはgoogleのapiの仕様の問題らしくてhashicorpの[対応の外](https://github.com/hashicorp/terraform/issues/4336)。
apiのリファレンスを読んでいたところ、instanceGroupManager を instanceGroupに書き換えれば
instance groupのuriになることがわかったので、terraformのreplaceで"Manager"を削除しました。

```
resource "google_compute_backend_service" "backend-sample" {
 name        = "backend-sample"
 port_name   = "http"
 protocol    = "HTTP"
 timeout_sec = 5

 backend {
   group = "${replace(element(google_container_cluster.cluster-sample.instance_group_urls, 1), "Manager","")}"
 }
 health_checks = ["${google_compute_http_health_check.hc-sample.self_link}"]
}
```


## 4. ロードバランサーのヘルスチェックが通るように、firewallとhealthcheckを作成する
ここも面倒だったところ。
もちろんセキュリティ的にできるだけnodeにアクセスできるportは弾きたいところですが、
ロードバランサーのhttpだけを許可したいときにどうやるのか？
先ほど書いたように、nodeが自動的に作成される前にinstance templateが自動で作成されます。
nodeは一意のrandomなtagを付与されているので、このtagに対してfirewallルールを作成します。
タイミング的にclusterを作った後にしかrandomなtagがわからないので、terraformのnull resourceを使います。
terraformの意味が。。。

```
resource "null_resource" "cluster-attach-fw" {
 triggers {
   instance-group-url = "${google_container_cluster.cluster-sample.instance_group_urls.0}"
 }

 provisioner "local-exec" {
   command = <<EOT
     CLUSTER_TEMPLATE=$(echo $(gcloud compute instance-templates list | grep cluster-sample | cut -d ' ' -f1))
     TARGET_TAG=$(echo $(gcloud compute instance-templates describe $CLUSTER_TEMPLATE --format json | jq -r .properties.tags.items[0]))
     gcloud compute firewall-rules create fw-allow-http-lb-$TARGET_TAG \
       --source-ranges 130.211.0.0/22 \
       --target-tags $TARGET_TAG --allow tcp:${var.gke-http-port} --network ${var.network-sample}
EOT
 }
}
```

130.211.0.0/22については[参照](https://cloud.google.com/compute/docs/load-balancing/health-checks)
`${var.gke-http-port}`については、load balancer用に好きなportを指定します。（80でも動作は問題ないですが）
このportは、nodes上で動かしているkubernetes内のServiceで用いるNodeportと一致する必要があります。
そのportを使って通信するので、backend serviceのnamed portも同じように設定します。

```
resource "null_resource" "set-cluster-nodeports" {
 triggers {
   be-svc-fingerprint = "${google_compute_backend_service.backend-sample.fingerprint}"
 }

 provisioner "local-exec" {
   command = <<EOT
     INSTANCE_GROUP=$(echo ${google_container_cluster.cluster-revprox.instance_group_urls.0} | sed -e 's/.*\(gke[a-z0-9\-]*\)$/\1/')
     gcloud compute backend-services update backend-sample --port=${var.gke-http-port}
     gcloud compute instance-groups managed set-named-ports $INSTANCE_GROUP \
       --named-ports=http:${var.gke-http-port} --zone=${var.zone}
EOT
 }
}
```

最後にヘルスチェックですが、指定したNodeport向けに作成します。

```
resource "google_compute_http_health_check" "hc-sample" {
  name         = "test"
  request_path = "/health_check"

  port = ${var.gke-http-port}
  timeout_sec        = 1
  check_interval_sec = 1
}
```

だいぶ面倒でしたが、これで目的は果たされました。
kubernetesをterraformで管理するべきかというと…どうなんでしょう。

本記事は[元記事](http://techeten.xyz/948)の転載です。