マニュアルでLBを作成し、GKEとGCSをバックエンドとして指定した時のハマりポイント

どうも。しみちゃんでございます。
今回は、マニュアルで作成したLBのバックエンドにGKEやGCSを指定しよう、というパターンの紹介です。
Webコンソールや Ingress などを使用してLBを立てていると、普段なかなか意識しないリソースの設定などでハマったりするので、そういったポイントをお話したいと思います。

はじめに

GKE, k8s, またそれにまつわるリソースの説明については割愛します。
他の記事で、GKEについて紹介しているものもあるので、よかったらそちら(記事のハッシュタグ#gkeをクリック)を参照ください。

前提

今回の記事は下記の部分まで進んだ前提でお話を進めます。 また、構築には terraform を使用します。

  • 公開用GCSバケットを作成していること
  • GKEクラスタを作成していること(今回はリージョナルで立てています)
  • Terraform v0.11.14 + provider.google: version = “~> 3.7”

GKEでサービスを作成する

マニフェスト

クラスタ上に、合計2つのサービス(今回はshimizuapp, grasysappという名前)をデプロイします。
コンテナの中身は nginx をほぼそのままのせているだけです。
マニフェストは片方だけしか載せていませんが、nameやportなど一意にすべきものはのぞいて、同じ設定でデプロイしています。

  1. Deployment (spec.templateを抜粋)

    ...
      template:
        metadata:
          name: shimizuapp
          labels:
            app: shimizuapp
        spec:
          containers:
          - name: nginx
            image: nginx:stable-alpine
            env:
            - name: NGINX_HOST
              value: "hogehoge.com"
            - name: NGINX_PORT
              value: "8080"
            ports:
            - containerPort: 8080
              protocol: TCP
            readinessProbe:
              httpGet:
                scheme: HTTP
                path: /check
                port: 8080
            volumeMounts:
            - name: conf-volume
              mountPath: /etc/nginx/conf.d
          ...
    
  2. ConfigMap

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: nginx
    data:
      virtualHost.conf: |
        log_format ltsv   'time:$time_iso8601\t'
                          'remote_addr:$remote_addr\t'
                          'request_method:$request_method\t'
                          'request_uri:$request_uri\t'
                          'https:$http_x_forwarded_proto\t'
                          'uri:$uri\t'
                          'status:$status\t'
                          'referer:$http_referer\t'
                          'forwardedfor:$http_x_forwarded_for\t';
       
        server {
            listen       8080;
            server_name  _;
            access_log  /var/log/nginx/access.log ltsv;
       
            location / {
                root   /usr/share/nginx/html;
                index  index.html index.htm;
            }
            location /check {
                return 200 ;
            }
        }
    ---
    
  3. Service

apiVersion: v1
kind: Service
metadata:
  name: shimizuapp
  namespace: default
  labels:
    app: shimizuapp
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 8080
    protocol: TCP
    nodePort: 30080
  selector:
    app: shimizuapp

メモ

初めの注意点として、 Service では spec.ports.nodePort で port を指定してあげてください。あとで、 名前付きポート の設定を付与する時に必要です。
クラスタ内で同じ port は作成できないので、異なる port で設定します。

できたものがこちら。

# kubectl  get svc -o wide
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE     SELECTOR
grasysapp    NodePort    10.7.240.186   <none>        8080:30081/TCP   6m50s   app=grasysapp
shimizuapp   NodePort    10.7.243.77    <none>        8080:30080/TCP   18m     app=shimizuapp
# kubectl get po
NAME                         READY   STATUS    RESTARTS   AGE
grasysapp-869cc776ff-f45r9   1/1     Running   0          8m51s
grasysapp-869cc776ff-xgfv4   1/1     Running   0          8m51s
shimizuapp-c9889d84c-qz6h9   1/1     Running   0          10m
shimizuapp-c9889d84c-tlg7k   1/1     Running   0          10m

LBを作る前に

ここが普段あまり触らないリソースがゆえに、ハマりやすいポイントです。
GKEによってできたインスタンスグループに、先ほど作成した Service の nodePort を 名前付きPort として設定してあげます。

ここで、設定をしっかり行っておかないと ヘルスチェックは通っているのに、status:502 errorが返ってくる といった現象が発生したりします。

これは Internet -> LB -> Service(k8s) と接続する時に、どの(Serviceが待ち受けている) port を使用するかを認識させるために必要です。

まずは、GKEによって作成されたインスタンスグループを gcloud compute instance-groups managed list で確認します。

# gcloud compute  instance-groups managed list
gke-shimizu-webapp-dev-app-8c257106-grp         asia-northeast1-b  zone   gke-shimizu-webapp-dev-app-8c257106         1     1            gke-shimizu-webapp-dev-app-8c257106         no
gke-shimizu-webapp-dev-app-9521b1ca-grp         asia-northeast1-c  zone   gke-shimizu-webapp-dev-app-9521b1ca         1     1            gke-shimizu-webapp-dev-app-9521b1ca         no
gke-shimizu-webapp-dev-app-d177c78a-grp         asia-northeast1-a  zone   gke-shimizu-webapp-dev-app-d177c78a         1     1            gke-shimizu-webapp-dev-app-d177c78a         no

gcloud compute instance-groups managed set-named-ports を使用して、 named-port を設定します。
設定は gcloud compute instance-groups managed get-named-ports で確認できます。

#!/bin/bash
named_port='shimizuapp30080:30080,grasysapp30081:30081'

gcloud compute  instance-groups managed list | grep shimizu \
  | while read list
    do
      group=""
      zone=""

      group=$(echo $list | awk '{print $1}' || echo "null")
      zone=$(echo $list | awk '{print $2}' || echo "null")
      echo "group: $(echo $list | awk '{print $1}') zone: $(echo $list | awk '{print $2}')"

      gcloud compute instance-groups managed set-named-ports $group --named-ports=$named_port --zone=$zone
      gcloud compute instance-groups managed get-named-ports $group --zone=$zone
    done

exit
# ./upate_named-port.sh
group: gke-shimizu-webapp-dev-app-8c257106-grp zone: asia-northeast1-b
Updated [https://www.googleapis.com/compute/v1/projects/grasys-dev/zones/asia-northeast1-b/instanceGroups/gke-shimizu-webapp-dev-app-8c257106-grp].
NAME             PORT
shimizuapp30080  30080
grasysapp30081   30081
group: gke-shimizu-webapp-dev-app-9521b1ca-grp zone: asia-northeast1-c
Updated [https://www.googleapis.com/compute/v1/projects/grasys-dev/zones/asia-northeast1-c/instanceGroups/gke-shimizu-webapp-dev-app-9521b1ca-grp].
NAME             PORT
shimizuapp30080  30080
grasysapp30081   30081
group: gke-shimizu-webapp-dev-app-d177c78a-grp zone: asia-northeast1-a
Updated [https://www.googleapis.com/compute/v1/projects/grasys-dev/zones/asia-northeast1-a/instanceGroups/gke-shimizu-webapp-dev-app-d177c78a-grp].
NAME             PORT
shimizuapp30080  30080
grasysapp30081   30081

それでは、LB を作成していく!

まずは、 外部IPとヘルスチェックをを定義します
以下、 2つのサービス(shimizuapp, grasysapp) のうち、片方の tf ファイルしか記載していませんが、 もう1つのリソースを作成するには名前や変数をそれぞれ置き換えてください。

また、ヘルスチェックの port やパスを間違えていると、 LB から正常とみなされなくなるので、注意が必要です。

ここで、最後まで作成しても、ヘルスチェックが通らない、といった場合は

  • パスはあっているか
  • コンテナ port がちゃんと空いているか
  • nginx.conf などで、LBからのアクセスを拒否していないか

などを疑ってみてください。

####################
# External IP
resource "google_compute_global_address" "gke-lb-dev-shimizuapp" {
  name         = "gke-lb-dev-shimizuapp"
}

####################
# Health Check
resource "google_compute_health_check" "gke-lb-dev-shimizuapp" {
  name                = "gke-healthcheck-https-lb-dev-shimizuapp"
  description         = "health check for shimizuapp"

  http_health_check {
    port                = 30080
    request_path        = "/check"
  }
}

バックエンドサービスを設定します。

名前付きポートは先ほど設定した named-port の名前を、
backend.group についてはそれぞれのインスタンスグループのリンクを、
細かなパラメータについては ingress で LB を立てた時に import した設定をそのまま反映しま す。

GKEでオートスケーリングをする予定であれば、特に分散周りのパラメータについては注意が必要です。
どこか特定の場所にトラフィックが偏り続けると、不必要にスケールアウトしたり、ピークタイムをすぎてもスケール前の状態に戻りずらくなったりします。

負荷試験の実施前であれば、 Ingress で自動で設定されたものを import してそのまま使用するのが無難かと思います。

#####################
## Backend Service
resource "google_compute_backend_service" "gke-lb-dev-shimizuapp-backend-service" {
  name        = "gke-lb-dev-shimizuapp-backend-service"
  # 先ほど設定した named-port の名前を指定する
  port_name   = "shimizuapp30080"
  protocol    = "HTTP"
  timeout_sec = 30             # default
  enable_cdn  = false

  backend {
  
    # asia-northeast1-a
    description     = "GKE Backend for shimizuapp aisa-northeast1-a"
    # gcloud compute  instance-groups managed list --format=json でインスタンスグループを確認する
    group           = "https://www.googleapis.com/compute/v1/projects/grasys-dev/zones/asia-northeast1-a/instanceGroups/gke-shimizu-webapp-dev-app-d177c78a-grp"
    
    balancing_mode  = "RATE"  # UTILIZATION(default) or RATE # imported parameter gke backed generated by ingress
    capacity_scaler = 1            # # imported parameter gke backed generated by ingress
    max_rate                     = 1 # imported parameter gke backed generated by ingress
    max_connections              = 0 # imported parameter gke backed generated by ingress
    max_connections_per_endpoint = 0 # imported parameter gke backed generated by ingress
    max_connections_per_instance = 0 # imported parameter gke backed generated by ingress
    max_rate_per_endpoint        = 0 # imported parameter gke backed generated by ingress
    max_rate_per_instance        = 0 # imported parameter gke backed generated by ingress
    max_utilization              = 0 # imported parameter gke backed generated by ingress
  }
  backend {
    description     = "GKE Backend for shimizuapp aisa-northeast1-b"
    group           = "https://www.googleapis.com/compute/v1/projects/grasys-dev/zones/asia-northeast1-b/instanceGroups/gke-shimizu-webapp-dev-app-8c257106-grp"
    balancing_mode  = "RATE"  # UTILIZATION(default) or RATE
    capacity_scaler = 1            # # imported parameter gke backed generated by ingress
    max_rate                     = 1 # imported parameter gke backed generated by ingress
    max_connections              = 0 # imported parameter gke backed generated by ingress
    max_connections_per_endpoint = 0 # imported parameter gke backed generated by ingress
    max_connections_per_instance = 0 # imported parameter gke backed generated by ingress
    max_rate_per_endpoint        = 0 # imported parameter gke backed generated by ingress
    max_rate_per_instance        = 0 # imported parameter gke backed generated by ingress
    max_utilization              = 0 # default 0.8 # imported parameter gke backed generated by ingress
  }
  backend {
    description     = "GKE Backend for shimizuapp aisa-northeast1-c"
    group           = "https://www.googleapis.com/compute/v1/projects/grasys-dev/zones/asia-northeast1-c/instanceGroups/gke-shimizu-webapp-dev-app-9521b1ca-grp"
    balancing_mode  = "RATE"  # UTILIZATION(default) or RATE # imported parameter gke backed generated by ingress
    capacity_scaler = 1            # # imported parameter gke backed generated by ingress
    max_rate                     = 1 # imported parameter gke backed generated by ingress
    max_connections              = 0 # imported parameter gke backed generated by ingress
    max_connections_per_endpoint = 0 # imported parameter gke backed generated by ingress
    max_connections_per_instance = 0 # imported parameter gke backed generated by ingress
    max_rate_per_endpoint        = 0 # imported parameter gke backed generated by ingress
    max_rate_per_instance        = 0 # imported parameter gke backed generated by ingress
    max_utilization              = 0 # default 0.8 # imported parameter gke backed generated by ingress
  }

  health_checks = [ "${google_compute_health_check.gke-lb-dev-shimizuapp.self_link}" ]
}

GCS のバックエンド設定もしてあげます。

####################
# Backend Bucket
resource "google_compute_backend_bucket" "gke-lb-static-images" {
  name        = "gke-lb-dev-static-images"
  description = "GCS Backdend for GKE"
  #bucket_name = "${google_storage_bucket.static.name}"
  bucket_name = "shimizu-gcs-public"
  enable_cdn  = false
}

最後に、 target http proxy と forwarding rule を設定します。

####################
# HTTP Proxy
resource "google_compute_target_http_proxy" "gke-http-lb-dev-shimizuapp" {
  name        = "gke-lb-dev-shimizu-target-proxy-shimizuapp"
  url_map     = "${google_compute_url_map.gke-lb-dev-shimizuapp.self_link}"
}

####################
# Forwarding Rules
resource "google_compute_global_forwarding_rule" "gke-http-lb-dev-shimizuapp" {
    name        = "gke-http-lb-dev-shimizuapp"
    target      = "${google_compute_target_http_proxy.gke-http-lb-dev-shimizuapp.self_link}"
    ip_address  = "${google_compute_global_address.gke-lb-dev-shimizuapp.address}"
    ip_protocol = "TCP"
    port_range  = "80"
}

作成したものがこちら。
うまく設定できていれば、こんな感じでそれぞれのバックエンドが紐づいているはずです。

終わりに

どうだったでしょうか。うまく設定できたでしょうか。

今回のパターンはどうしても、 GKE と GKE以外のリソースを使いながらLBを運用したいといった場合など、使うケースはかなり限られています。
LB 周りは一通り動かすのに、ただでさえ操作するリソースが多いのに、GKEを絡めていくと更に設定が必要になるので、ちょっと面倒だなと感じてしまいますね。
( GCS なら、バケットをマウントする GCSFuse などがありますし。。。)

とは言え、マニュアルで細かく設定できるようになれば、
GCEなどでも併用して使えるようになるため、選択肢としては一応もっていて損はないと思います。

今回はここまで。ではでは。