🌊

GKE Gateway Controllerの勘所

に公開

GKE Gateway Controller を使ってマイクロサービスを提供するシステムを組んだので、備忘録です。

[おさらい]GKE Gateway Controllerとは何か

Ingressの課題を解決するため、Ingressの後継としてk8sがGateway API を提供していますが、GKE Gateway ControlerはそのGateway APIのGKE版実装です。L7で高度なトラフィック制御ができるk8sのアドオンです。

システムデザインとしては、LBレイヤやトラフィック制御、ポリシー制御などをロール志向で制御できるような思想で作られているようです。自分は、ある程度規模のある開発体制の場合はワークするかもしれないと思いました。

IngressとGatewayの違い

まずIngressは、パスベースルーティングのような非常にシンプルな仕様しか定義されていません。HTTPヘッダなどを使ってサービス間のトラフィックルーティングをしようとした場合、ミドルウェアのインストールなどの対応が必要でした。が、Gatewayではネイティブでそういったルーティング機能をサポートしています。つまり頑張ってServiceMesh系のミドルウェアをインストールする手間なくパスベース以外でもルーティングが実現できます。

また、namespace間のルーティンなども ReferenceGrant を使うことで簡単にできます。

他にも冒頭でお伝えした通りGatewayはロール志向な構成である、などもありますが、多くの開発者的に嬉しいのは上記2点かなと、個人的には考えています。

参照. https://gateway-api.sigs.k8s.io/concepts/api-overview/#extension-points

機能性について

Ingressが備えていた機能はほぼ全て提供しており、Ingress時代はannotationなどを使って実現する必要があったような設定もGatewayではannotationを使わずに実現できたりします。

シングルクラスタ、マルチクラスタ両方対応しており、さらに外部アクセスも内部アクセスも両方制御できます(内部的にLBが立ち上がる)。内部Gatewayでクラスタ内通信も制御できるので、Envoyプロキシなどを意識せず内部トラフィック制御をすることも可能です。ただ、内部トラフィックのアクセス制御をする際、ネットワーク上はプロキシ(Envoy)用のサブネットの定義などはする必要があるため、裏側ではEnvoyを使ったトラフィック制御が行われていそうです。ただ、Envoyの管理をしなくて良いのは楽で嬉しいですね。

参照. https://cloud.google.com/kubernetes-engine/docs/concepts/gateway-api

勘所一覧

基本的な構築方法や設置値は公式ドキュメントを参照いただくとして、ここからは自分が実際にプロダクション環境でGatewayを構築して発見した勘所を解説していきます。

HealthcheckPolicyでの自動Port指定

HealthCheckPolicyでチェック先のportを指定するとき、従来であれば手動でport番号を指定していましたが、HealthCheckPolicyでは portSpecification: USE_SERVING_PORT というように動的に稼働中のportを指定できるようになりました。

apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
  name: my-healthcheck
  namespace: default
spec:
  default:
    checkIntervalSec: 5
    config:
      httpHealthCheck:
        portSpecification: USE_SERVING_PORT
        requestPath: /
      type: HTTP
    healthyThreshold: 3
    timeoutSec: 1
    unhealthyThreshold: 2
  targetRef:
    group: ''
    kind: Service
    name: my-service

本家リソースの中でいくつか使えない機能があるので注意

参照. https://docs.cloud.google.com/kubernetes-engine/docs/how-to/gatewayclass-capabilities?hl=ja#gatewayclass_capabilities

例えば、本家 HTTPRoute ではクエリパラメータを使ったルーティングができますが、Gateway Controller実装のHTTPRouteではできません。設計の際に要注意です。実際、queryParamフィルタを指定したHTTPRouteをGKEに反映してみましたが、特にエラーは出ず反映できるものの、ログ上はルーティング制御が発動しないような挙動になっていました。この辺、サポートされていないパラメータを設定したときはエラーが出るようになってくれると嬉しいです...!

SpecTable

同一ドメインに対して複数のHTTPRouteを定義したときの挙動

おさらい : HTTPRoute では、Gateway が受信した HTTP リクエストと HTTPS リクエストを Service に転送する方法を定義します。

同一ドメインに対して複数のHTTPRouteリソースを定義したときは、こちらのドキュメントに載っている条件の優先でマージされます。

  • ホスト名のマージ:最も長い、または最も具体的なホスト名と一致
  • パスのマージ : 最も長い、または最も具体的なパスと一致。
  • ヘッダーのマージ : 一致する HTTP ヘッダーの最大数。
  • 競合: 前述の 3 つのルールが優先されない場合、最も古いタイムスタンプの HTTPRoute リソースが使用されます。

例えば、my-domainというドメインに対して次の2つのHTTPRouteがある場合を考えてみます。後者だけ、HTTPヘッダフィルタの数が多いです。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: route-1
  namespace: default
spec:
  hostnames:
    - my-domain
  parentRefs:
    - kind: Gateway
      name: main-gateway
      namespace: gateway
  rules:
    - backendRefs:
        - name: some-service
          port: 80
          weight: 1
      matches:
        - headers:
            - name: x-some-header
              type: Exact
              value: some-value
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: route-2
  namespace: default
spec:
  hostnames:
    - my-domain
  parentRefs:
    - kind: Gateway
      name: main-gateway
      namespace: gateway
  rules:
    - backendRefs:
        - name: some-service-2
          port: 80
          weight: 1
      matches:
        - headers:
            - name: x-some-header-a
              type: Exact
              value: some-value-a
            - name: x-some-header-b
              type: Exact
              value: some-value-b
    - backendRefs:
        - group: ''
          kind: Service
          name: stable-service
          port: 80
          weight: 1

これを同じnamespace上に2個展開した場合、実質以下のように解釈されてトラフィックルーティングされます。ホスト名やHTTPヘッダの長さなどに応じた優先順のルーティングは、yamlのファイル分割設計をする際に意識しておくと良さそうです。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  namespace: default
spec:
  hostnames:
    - my-domain
  parentRefs:
    - kind: Gateway
      name: main-gateway
      namespace: gateway
  rules:
    # route-2の設定値
    - backendRefs:
        - name: some-service-2
          port: 80
          weight: 1
      matches:
        - headers:
            - name: x-some-header-a
              type: Exact
              value: some-value-a
            - name: x-some-header-b
              type: Exact
              value: some-value-b
    # route-1の設定値
    - backendRefs:
        - name: some-service
          port: 80
          weight: 1
      matches:
        - headers:
            - name: x-some-header
              type: Exact
              value: some-value
    # default route
    - backendRefs:
        - group: ''
          kind: Service
          name: stable-service
          port: 80
          weight: 1

Nodeレベルでのグレースフルシャットダウンを考慮してdrainingTimeoutを設定する

https://docs.cloud.google.com/kubernetes-engine/docs/how-to/configure-gateway-resources?hl=ja

以下のように connectionDraining を指定することで、GKE Node(Compute Engineインスタンス)がシャットダウンされようとしている場合などに、処理が終わるまで待機(ドレイン)させることができます。これはLoadBalancer側で提供されている機能をGCPBackendPolicyの設定で制御できるようにしたものと捉えられますね。クリティカルなバッチ処理などは、connectionDraining とDeploymentの terminationGracePeriodSeconds を組み合わせて使うとより堅牢になりそうです。

apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
  name: my-backend-policy
  namespace: lb-service-namespace
spec:
  default:
    connectionDraining:
      drainingTimeoutSec: 3600
  targetRef:
    group: ""
    kind: Service
    name: lb-service

LBのロギングレートの指定

参照. https://docs.cloud.google.com/kubernetes-engine/docs/how-to/configure-gateway-resources?hl=ja#http_access_logging

GCPBackendPolicyリソースの設定で、LBのBackendServiceのロギングレートを設定できます。

rate方式で、1000000が100%で、0が0%という具合に設定できます。最初はこれサンプル値だと思っていたのですが、どうやら固定値で1000000がMAXなようです。

IPv4とIPv6のデュアルスタック構成にする

事前にStatic IPv4とIPv6を予約しておき、以下のようにGatewayに割り当てることでデュアルスタック構成にできます。

また、証明書はcertmapとして作成して networking.gke.io/certmap アノテーションでアタッチできます。Ingress時代はManagedCertificateリソースか、pre-shared-certアノテーションでDNS01チャレンジ認証で作成した証明書を設定するなどの対応が必要でしたが、certmapを適用できるるようになることで、ワイルドカード証明書なども簡単に設定できて楽ですね。

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  annotations:
    networking.gke.io/certmap: app
  name: main-gateway
  namespace: gateway
spec:
  addresses:
    - type: NamedAddress
      value: gateway-controller-ipv4
    - type: NamedAddress
      value: gateway-controller-ipv6
  gatewayClassName: gke-l7-global-external-managed
  listeners:
    - allowedRoutes:
        namespaces:
          from: All
      name: http
      port: 80
      protocol: HTTP
    - allowedRoutes:
        namespaces:
          from: All
      name: https
      port: 443
      protocol: HTTPS

Discussion