動作検証しながら理解する「Kubernetes Gateway API」と「GKE Gateway Controller」

ogp

はじめに

こんにちは、技術本部 データサイエンス部 MLOpsブロックの鹿山(@Ash_Kayamin)です。

みなさんは2021年4月にGCPから「GKE Gateway コントローラによる Kubernetes ネットワーキングの進化」という記事が投稿されたのを覚えていますでしょうか。 cloud.google.com

この記事は、Kubernetesコミュニティが発表したKubernetes Gateway APIに対し、そのGKE(Google Kubernetes Engine)版実装であるGKE Gateway Controllerのリリースをアナウンスするものでした。

それから半年が経ち、本番導入の可能性を模索するためにKubernetes Gateway APIとGKE Gateway Controllerを調査、動作検証しました。本記事では、Kubernetes Gateway APIの概要と、APIで定義されるトラフィックのルーティングがGKE Gateway ControllerによってどのようにGCP上で実現されるのかを、動作検証の流れに沿って解説します。

なお、2021年11月時点で、Kubernetes Gateway APIの最新バージョンはv1alpha2、GKE Gateway ControllerがサポートするGateway APIのバージョンはv1alpha1であり、今後仕様が大きく変わる可能性がある点にご注意ください。

目次

Kubernetes Gateway APIの開発背景と特徴

よりスムーズに理解していただくために、Kubernetes Gateway APIが作成された背景から順にご紹介します。

Kubernetes Gateway APIが開発された背景

Gateway API(Kubernetes Gateway API)はIngress API(Kubernetes Ingress API)の課題を解消するために開発されました1

そのIngress APIは、Kubernetesクラスタ外部からクラスタ内Service(Kubernetes Service)に対し、アプリケーション層でHTTPやHTTPSを用いたルーティングを制御するAPIです。多数のプロバイダーでIngress APIの仕様に則ったIngress Controllerが実装されています。また、Ingress Controllerの実装によっては負荷分散やSSL終端といった機能も提供します。MLOpsブロックでもGKEでコンテナネイティブな負荷分散を利用するために、GCPが提供するIngress ControllerであるGLBCを利用しています。GLBCはIngress APIを通して、GCLB(Google Cloud Load Balancing)を用いたルーティングの設定が可能です。

実は、Ingress APIでは非常にシンプルな機能を実現するための仕様しか定義されていません。そのため、Ingress Controllerのプロバイダー、Ingress Controllerの利用者それぞれに以下の負担が発生していました。

  • Ingress APIでは定義されていないトラフィックの荷重ルーティング等の機能を追加するには、プロバイダーはIngressのManifestに独自のannotationを定義する必要がある
  • 開発者はプロバイダー毎にannotationが大きく異なるManifestを書かなくてはならない

例えば、各Ingress Controller毎にannotationへ定義可能な設定項目数を比較すると以下のように大きな差があります。

これは、対応している機能や、設定項目の表現方法(annotationのみを使うのか、annotationとCR(Custom Resource)を組み合わせるのか等)が異なるため、結果としてannotationの数に大きな差が生じています。

例えば、L7外部ロードバランサーを定義して、ロードバランサーをSSL終端とし、バックエンドのサービスへのヘルスチェックを設定することを考えてみます。

AWS Load Balancer Controllerを用いる場合のManifestは以下のように定義します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: zozo-techblog
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:xxxx
    alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
    alb.ingress.kubernetes.io/healthcheck-port: '80'
    alb.ingress.kubernetes.io/healthcheck-path: /health
spec:
  rules:
    - http:
        paths:
          - path: /*
            backend:
              serviceName: zozo-techblog
              servicePort: 80

一方、GLBCを用いる場合のManifestは以下のように定義します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: zozo-techblog-ingress
  annotations:
    kubernetes.io/ingress.allow-http: "false"
    ingress.gcp.kubernetes.io/pre-shared-cert: "api-cert"
spec:
  defaultBackend:
    service:
      name: zozo-techblog-service
      port:
        number: 80
---
apiVersion: v1
kind: Service
metadata:
  name: zozo-techblog-service
  annotations:
    cloud.google.com/neg: '{"ingress": true}' # ref. https://cloud.google.com/kubernetes-engine/docs/how-to/container-native-load-balancing
    # BackendConfigを用いてヘルスチェック等をサービス毎にカスタマイズする
    cloud.google.com/backend-config: '{"default": "zozo-techblog-backendconfig"}'
spec:
  selector:
    app: zozo-techblog-pod
  ports:
    - port: 80
      protocol: TCP
      targetPort: 8080
---
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: zozo-techblog-backendconfig
spec:
  healthCheck:
    checkIntervalSec: 15
    type: HTTP
    port: 8080
    requestPath: /health

annotationが異なるのはもちろんですが、ヘルスチェックの指定方法が大きく異なっていることが分かります。AWS Load Balancer ControllerではIngressのannotationにヘルスチェックの定義を記載します。一方、GLBCを用いる場合はBackendConfigというCRにヘルスチェックの定義を記載します。そこでは、Ingressで指定するServiceのannotationでBackendConfigを指定する必要があります。

また、L7ロードバランサーの機能への対応状況も大きく差があります。例えば、AWS Load Balancer ControllerではService毎に割合を指定することでトラフィックの細かな分割ができます。一方、GLBCではトラフィックの分割割合の指定はできません。GLBCで設定をするL7ロードバランサーGCLBにはトラフィックの分割割合を指定する機能は存在します。しかしながら、現状GLBCにはGCLBでのトラフィック分割割合を設定する機能は実装されていません。L7ロードバランサーを構成するなら当然利用できるはずだと思う機能も、現状では利用できるかどうかはプロバイダー次第となってしまっています。

このように、Ingress Controller毎に対応している機能や設定項目の表現方法が大きく異なります。そのため、開発者が普段とは異なるIngress Controllerを利用する際には、Manifestを書くことに苦労します。

Gateway APIは、この問題を解消するために開発されました。Gateway APIはL4/L7ロードバランサーで実現可能なルーティングをできる限り共通の仕様で実現できるように配慮しています。

また、Ingress APIでは、L7でのServiceへのルーティングの定義を1つのリソースで定義していました。一方、Gateway APIではルーティングの定義を責務毎に、3種類のリソースに分割しています。リソースを分割することで権限管理の対象が細分化されるため、RBAC(Role-based access control)を用いて「最小権限の原則」に基づいた安全な運用が可能です。

Kubernetes Gateway APIを構成する3種類のリソース

Gateway APIは、Kubernetesクラスタ外部からクラスタ内のServiceへのL4/L7でのルーティングを、3種類リソース GatewayClassGatewayRouteを用いて定義するAPIです。

  • GatewayClass
    • Gatewayを構成するためのテンプレートを示すリソース
    • Gatewayを構成するために使用するGateway Controllerをパラメータと共に指定する
    • このパラメータでGateway構成時に構築されるロードバランサーの設定項目(L4、L7、外部、内部等)を指定する
  • Gateway
    • リクエストをクラスタ内へルーティングするルールを定義するリソース
    • 指定したGatewayClassの定義を元にロードバランサーやプロキシ等を実際に構築する
    • クラスタ内のどこにルーティングするかはRouteによって定義する
  • Route
    • GatewayからServiceに対するルーティングのルールを定義するリソース
    • ロードバランサーでのパスベースのルーティングの指定等に対応
    • 対応するプロトコル毎にHTTPRouteTCPRouteTLSRouteUDPRouteが存在する

以下、公式ドキュメントにある図に描かれているように、GatewayClassGatewayRoute(図ではHTTPRoute)の3つのリソースを組み合わせることで、Serviceへのルーティングを定義します。

api-model
引用 https://gateway-api.sigs.k8s.io/

2021年11月時点で、GKEやIstioを含む複数のプロジェクトがGateway APIで定義された挙動を実現するGateway Controllerを実装しています。

Gateway APIを用いる利点

Gateway APIにはIngress APIと比べて以下の利点があります。

  1. ルーティングの設定に必要最小限な権限をRBACで付与できる
  2. プロバイダー依存性の低いManifestでルーティングの設定を定義できる
  3. 拡張性が高い

順に説明します。

利点1:ルーティングの設定に必要最小限な権限をRBACで付与できる

前述の通り、Gateway APIではServiceへのルーティングを、3種類の責務に対応したリソースGatewayClassGatewayRouteで定義します。リソースが分かれていることで、RBACを用いて「誰が何をできるのか」をリソース毎に管理できます。つまり、開発者の責務に対して必要なルーティング設定を行う権限のみを付与できます。こうすることで、Kubernetesのリソースを通して行うルーティング設定に「最小権限の原則」に基づいた運用を導入できます。Ingress APIでは、1つのリソースでロードバランサーとServiceヘのルーティングの定義を兼ねており、権限の分離はできませんでした。

例として、下図の公式ドキュメントの図が示すような、1つのロードバランサーに対して複数のNamespaceに存在する異なるアプリケーションを紐づけたシステムを考えます。このシステムで、クラスタ管理者とアプリケーション開発者の権限を分けてみましょう。

gateway-roles
引用 https://gateway-api.sigs.k8s.io/

  • Step1
    • クラスタの管理者にはクラスタ内のアプリケーションが共通で利用するロードバランサーを管理できるように、Gatewayリソースを閲覧、作成、編集、削除できる権限を与える
    • 一方、アプリケーション開発者の権限はGatewayリソースの閲覧のみに絞ることで、サービス全体で利用するロードバランサーを誤って削除できないようにする
  • Step2
    • 各アプリケーション開発者へは、特定のNamespaceでのみRouteリソースの閲覧、作成、編集、削除できる権限を与える
    • その結果、開発者が管理している特定のNamespace配下のアプリケーションに対してのみ、ロードバランサーからトラフィックをどのように割り振るのかを管理できるようになる

このように権限を分離することで、各アプリケーション開発者に必要最小限の権限を与え、安全に開発を進めることができます。

利点2:プロバイダー依存性の低いManifestでルーティングの設定を定義できる

Gateway APIでは、3種類の実装サポートレベル COREEXTENDEDOPTIONAL が定義されています。そして、このサポートレベルは機能毎に設定されています。

  • CORE
    • 全てのGateway Controllerで実装される重要な機能
    • Gateway APIでManifestの仕様が定義されている
  • EXTENDED
    • 全てのGateway Controllerで実装されるわけではないが、重要な機能
    • Gateway APIでManifestの仕様が定義されている
  • CUSTOM
    • プロバイダー依存のオプショナルな機能
    • Gateway APIではCRを指定できるようにManifestの仕様が定義されており、プロバイダーは任意のCRを用いた機能を実装できる

上記の説明で用いている「重要な機能」とは、一般にL7ロードバランサーに備わっている機能(ヘッダーベースのルーティング等)で、プロバイダーに依らず可搬性のある機能を指します。Ingressでは独自のannotationを用いる手段しかサポートしていませんでした。なお、どういった機能がどのサポートレベルまで対応するのか、明確な判断基準は示されていません。機能のサポートレベルについて詳しく知りたい方はAPI仕様のドキュメントをご参照ください。

Gateway APIのリソースでは、Serviceへのルーティングを管理するのに必要な機能が一通りCOREEXTENDEDで定義されています。例えば、Gatewayにおける静的IPやSSL証明書の指定、HTTPRouteにおけるヘッダーベース・パスベースのルーティングやトラフィック分割の指定等が含まれています。そのため、これらの必要となる機能は各プロバイダーで、ある程度等しく実装されることが期待できます。その結果、Gateway API利用時に使用するManifestを汎用的に使えるようになることも期待できます。必要な機能が汎用的なManifestの仕様として定義されていれば、Manifestを見れば実現されている機能が一目で分かる利点があります。

一方、従来のIngress APIでは非常にシンプルな機能を実現するための仕様しか定義されておらず、各プロバイダーがManifestに独自のannotationを定義して機能を拡張する必要がありました。その結果、プロバイダー毎に提供される機能、必要なManifestのフォーマットが大きく異なっていました。

これに対して、Gateway APIでは、より高度なルーティング管理機能をAPIで最初から定義することと、以下で説明するCRを用いた拡張方法を提供することで、このIngress APIの問題を解消しようとしています。

利点3:拡張性が高い

Gateway APIはCRを用いた拡張ができるように設計されています。拡張のために、Gateway APIで定義されているManifestの中にはCRを指定できるポイントがいくつか用意されています。Ingress APIでは、annotationで拡張するしか手段がありませんでした。annotationは単なる文字列であり、必要な項目が設定されているかの確認等のバリデーションはできません。また、Manifestにどんなannotationを設定できるのかを知るにはドキュメントを確認する必要がありました。一方、CRによる拡張はManifestのバリデーションが可能です。kubectl get crdkubectl explain等でSpecを確認できるため、設定可能な項目を知るのも容易になっています。Gateway APIがサポートしているCRによる拡張は、Controller開発者、利用者双方にとって、より好ましい拡張方法と言えます。

Gateway APIのバージョンgateway.networking.k8s.io/v1alpha2における、各種Routeではspec.rules[].backendrefs[].kindServiceの代わりににCRを指定できます。例えば、Cloud FunctionsへルーティングするためのCRを作成し、指定することを考えてみます。Gateway Controllerの実装者はCloud Functionsへのルーティングに必要な情報をCRD(Custom Resource Definition)として定義しておきます。CRDに指定した通りにCRが作成され、Routeリソースで指定された際には、CRの情報を元にCloud Functionsへルーティングできるよう、Gateway Controllerを実装します。それにより、利用者がCRを作成し、RouteリソースにCRを指定すれば、Cloud Functionsへのルーティングを実現できるようになります。このように、Gateway APIを拡張し、Cloud Functionsへのルーティングを設定する機能を実現できます。

この他にも、Gatewayではspec.listener.allowedRoutes[].kinds[]でCRを指定でき、既存のRoute以外の独自のRouteリソースも使用できます。このように、Gateway APIではCRを用いた拡張がしやすいように配慮されています。

# HTTPRouteでspec.rules[].backendrefs[]を指定する例
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
  name: example-route
  namespace: gateway-api-example-ns2
spec:
  parentRefs:
    - name: prod-gateway
  hostnames:
    - "example.com"
  rules:
    - backendRefs:
        - kind: Service # kindを指定できるのでCRを指定することも可能。 デフォルトではServiceを指定するようになっている。 
          name: example-svc # 対象とするkindのmetadata.nameを指定
          port: 80 # kind: Serviceの場合は必須

GCPにおけるGateway APIの実装

ここまでのGateway APIに関する説明は、2021年11月時点でのGateway APIの最新バージョンgateway.networking.k8s.io/v1alpha2に対するものです。本章で説明する、GKE Gateway Controllerはバージョンnetworking.x-k8s.io/v1alpha1への対応となっており、動作検証ではバージョンnetworking.x-k8s.io/v1alpha1で定義されたManifestを利用しているのでご注意ください。

GCPでは、そのnetworking.x-k8s.io/v1alpha1に対応した、GKE Gateway Controllerがプレビュー機能として公開されています。GKE Gateway Controllerを利用することで、単一または複数のGKEクラスタにまたがる内部、外部HTTP(S)負荷分散を管理できます。2021年11月時点で、GatewayClassGatewayHTTPRouteリソースのみがサポートされています。また、4種類のGatewayClassが定義されており、各GatewayClass毎にサポートする機能が異なっています。

4種類のGatewayClassは以下の通りです。

  • gke-l7-rilb
    • シングルクラスタ内部ロードバランサー
  • gke-l7-rilb-mc
    • マルチクラスタ内部ロードバランサー
  • gke-l7-gxlb
    • シングルクラスタ外部ロードバランサー
  • gke-l7-gxlb-mc
    • マルチクラスタ外部ロードバランサー

GKE Gateway Controllerの動作検証

ここまで、Gateway APIと、その実装であるGKE Gateway Controllerを紹介しました。本章では、GCP公式ドキュメントにある「Gateway のデプロイ」に従って、実際にGKE上でGateway APIを利用し、APIで定義されるトラフィックのルーティングがGKE Gateway Controllerによって、どのようにGCP上で実現されるのかを見ていきます。

1. GKE Gateway Controllerに対応したシングルクラスタを構築する

GKEで単一のKubernetesクラスタを構築し、Gateway APIを用いて、下図に示す内部負荷分散を実現します。コンテナネイティブ負荷分散を行うため、GKEクラスタはVPCネイティブクラスタである必要があります。

verification-network-diagram
検証で実現する内部負荷分散

以下のサンプルのように、TerraformでGCP上に検証環境を構築します。なお、2021年11月時点で公式ドキュメント記載のGKE Gateway Controller利用可能リージョンにはasia-northeast1は含まれていませんでしたが、試してみたところasia-northeast1に構築したGKEクラスタでも利用できました。

# VPC作成
resource "google_compute_network" "gke_vpc" {
  name = "gke-vpc"
  auto_create_subnetworks = false
}

# 内部LBを作成する場合に必要なproxy only subnetを作成
# ref. https://cloud.google.com/load-balancing/docs/l7-internal/proxy-only-subnets
resource "google_compute_subnetwork" "proxy_only_subnet" {
name          = "proxy-only-subnetwork"
ip_cidr_range = "10.0.3.0/24" # cider for gke node
region        = "asia-northeast1"
network       = google_compute_network.gke_vpc.id
purpose       = "INTERNAL_HTTPS_LOAD_BALANCER"
role          = "ACTIVE"
}

# VPCネイティブクラスタを作成する際に指定するサブネットを雑に作成
# 本来は下記リンク先を参考にCIDRを要件に応じて設計するべきです
# ref. https://cloud.google.com/kubernetes-engine/docs/how-to/flexible-pod-cidr
resource "google_compute_subnetwork" "gke_subnet" {
  name          = "gke-subnetwork"
  ip_cidr_range = "10.0.1.0/24" # cider for gke node
  region        = "asia-northeast1"
  network       = google_compute_network.gke_vpc.id
  secondary_ip_range {
    range_name    = "gke-pod"
    ip_cidr_range = "10.0.0.0/24"
  }
  secondary_ip_range {
    range_name    = "gke-service"
    ip_cidr_range = "10.0.2.0/24"
  }

  private_ip_google_access = true
}


# GKEのノードに割り当てるサービスアカウントを作成
resource "google_service_account" "gke_node_pool" {
  account_id = "gke-node-pool"
  display_name = "gke-node-pool"
  description = "A service account for GKE node"
}

# サービスアカウントに必要最低限のIAMロール(権限)を付与
resource "google_project_iam_member" "gke_node_pool" {
  for_each = toset([
    "roles/logging.logWriter",
    "roles/monitoring.metricWriter",
    "roles/monitoring.viewer",
    "roles/datastore.owner",
    "roles/storage.objectViewer",
  ])

  role = each.value
  member = "serviceAccount:${google_service_account.gke_node_pool.email}"
}

# GKEクラスタを定義、VCP-Native、公開クラスタ
resource "google_container_cluster" "main" {
  name = "gke-cluster"
  location = "asia-northeast1-a"
  
  # デフォルトノードプールは削除して別途ノードプールを作成する
  remove_default_node_pool = true
  initial_node_count = 1

  # クラスタを作成するVPC、subnetを指定
  network = google_compute_network.gke_vpc.self_link
  subnetwork = google_compute_subnetwork.gke_subnet.self_link

  # vpc native clusterにするための設定
  networking_mode = "VPC_NATIVE"
  ip_allocation_policy {
    cluster_secondary_range_name = "gke-pod"
    services_secondary_range_name = "gke-service"
  }
}

# GKEクラスタのノードを定義
resource "google_container_node_pool" "primary_nodes" {
  name = "node-pool"
  location = "asia-northeast1-a"
  cluster = google_container_cluster.main.name
  node_count = 1

  node_config {
    machine_type = "e2-medium"

    metadata = {
      disable-legacy-endpoints = "true"
    }

    # アクセススコープでは全てのサービスへの権限を付与し,サービスアカウント側で付与する権限を絞る
    service_account = google_service_account.gke_node_pool.email
    oauth_scopes = [
      "https://www.googleapis.com/auth/cloud-platform"
    ]
  }
}

2. Gateway APIのCRDをインストールし、GatewayClassが作成されることを確認する

手順 1. でGKEクラスタを作成したら、下記のようにkubectlコマンドを用い、クラスタにGateway APIのCRD(Custom Resource Definition)をインストールします。CRDをインストールすると、GKE Gateway Controllerによって、GKEクラスタ内に自動的にシングルクラスタ用のGatewayClassが作成されます。

$ CLUSTER_NAME=gke-cluster
$ ZONE=asia-northeast1-a
$ gcloud container clusters get-credentials $CLUSTER_NAME --zone $ZONE

$ kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.3.0" \
| kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/backendpolicies.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/tcproutes.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/tlsroutes.networking.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/udproutes.networking.x-k8s.io created

$ kubectl get gatewayclass

NAME          CONTROLLER                  AGE
gke-l7-gxlb   networking.gke.io/gateway   11s
gke-l7-rilb   networking.gke.io/gateway   11s

$ kubectl  describe gatewayclass gke-l7-ril
~~~
Events:
  Type    Reason  Age   From                   Message
  ----    ------  ----  ----                   -------
  Normal  ADD     20s   sc-gateway-controller  gke-l7-rilb

3. Gatewayリソースを作成することで、GCLBが作成されることを確認する

次に、Gatewayリソースを作成し、GKE Gateway Controller経由で内部ロードバランサーを作成します。

Gatewayでは、spec.gatewayClassNameGatewayClassを指定します。そこでは、作成したいロードバランサーの種別に応じて適切なGatewayClassを選択します。今回は、シングルクラスタ内部ロードバランサーを作成したいので、gke-l7-rilbを指定します。そして、spec.listeners で利用するプロトコル、ポート番号、Gatewayとの紐付けを許可するRouteの条件を指定します。

また、Gatewayで紐付けを許可するRouteの条件を指定しますが、逆にRoute側でも紐付けを許可するGatewayの条件を指定できます。そして、双方向に条件が満たされた場合にのみ、該当のGatewayRouteが紐づけられます。なお、Gatewayでは、許可するRouteの条件として、RoutekindlabelhostnamesRouteを作成するNamespacelabelを指定できます。つまり、この条件を満たすRouteを作成する権限があれば自由にルーティングルールを追加できることを意味します。Gateway APIでは、ルーティングルールを一元管理する仕組みが定義されていないので、ルーティングルールの適切な管理は運用でカバーする必要があります。

kind: Gateway
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
  name: internal-http
spec:
  gatewayClassName: gke-l7-rilb
  listeners:
    - protocol: HTTP
      port: 80
      routes: # 紐づけを許可するRouteの条件を指定
        kind: HTTPRoute 
        selector: 
          matchLabels:
            gateway: internal-http

Gatewayリソースを作成すると、GKE Gateway Controllerにより、GCLB及びGCLBに紐づけられたバックエンドサービスヘルスチェックが新規に作成されます。

$ kubectl apply -f gateway.yaml
gateway.networking.x-k8s.io/internal-http created

$ kubectl get gateway
NAME            CLASS         AGE
internal-http   gke-l7-rilb   39s

$ kubectl describe gateway internal-http
~~~
Status:
  Addresses:
    Type:   IPAddress
    Value:  10.0.1.4
  Conditions:
    Last Transition Time:  1970-01-01T00:00:00Z
    Message:               Waiting for controller
    Reason:                NotReconciled
    Status:                False
    Type:                  Scheduled
Events:
  Type     Reason  Age                 From                   Message
  ----     ------  ----                ----                   -------
  Normal   ADD     4m1s                sc-gateway-controller  default/internal-http
  Warning  SYNC    3m42s               sc-gateway-controller  generic::invalid_argument: error ensuring load balancer: Insert: The resource 'projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5' is not ready
  Normal   UPDATE  17s (x3 over 4m1s)  sc-gateway-controller  default/internal-http
  Normal   SYNC    17s                 sc-gateway-controller  SYNC on default/internal-http was a success
# GCLBが作成されている
$ gcloud compute url-maps list
NAME                                           DEFAULT_SERVICE
gkegw-8r5w-default-internal-http-2jzr7e3xclhj

# バックエンドサービスが作成されている
$ gcloud compute backend-services list
NAME                                            BACKENDS  PROTOCOL
gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5            HTTP

# ヘルスチェックが作成されている
$ gcloud compute health-checks list
NAME                                            PROTOCOL
gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5  HTTP

Gatewayリソースから作成されたロードバランサーはUI及びCLIから確認できます。しかしながら、公式ドキュメントには「Gatewayによって作成されたGoogle CloudロードバランサのリソースはGoogle Cloud Console UIに表示されません」と記載されています。正しい値が表示される保証はないのでご注意ください。

verification-network-diagram
GKE Gateway Controllerによって作成されたロードバランサーのGoogle Cloud Console上での表示

4. HTTPRouteリソースを作成して、GCLBにルーティングのルールが追加されることを確認する

次に、DeploymentServiceを作成した上で、ServiceGatewayを紐づけるHTTPRouteを作成します。

まず、以下のManifestで4組のDeploymentServiceを追加します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: store-v1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: store
      version: v1
  template:
    metadata:
      labels:
        app: store
        version: v1
    spec:
      containers:
        - name: whereami
          image: gcr.io/google-samples/whereami:v1.1.3
          ports:
            - containerPort: 8080
          env:
            - name: METADATA
              value: "store-v1"
---
apiVersion: v1
kind: Service
metadata:
  name: store-v1
spec:
  selector:
    app: store
    version: v1
  ports:
    - port: 8080
      targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: store-v2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: store
      version: v2
  template:
    metadata:
      labels:
        app: store
        version: v2
    spec:
      containers:
        - name: whereami
          image: gcr.io/google-samples/whereami:v1.1.3
          ports:
            - containerPort: 8080
          env:
            - name: METADATA
              value: "store-v2"
---
apiVersion: v1
kind: Service
metadata:
  name: store-v2
  annotations:
    # BackendConfigを用いてヘルスチェック等をサービス毎にカスタマイズする
    cloud.google.com/backend-config: '{"default": "store-v2-backendconfig"}'
spec:
  selector:
    app: store
    version: v2
  ports:
    - port: 8080
      targetPort: 8080
---
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: store-v2-backendconfig
spec:
  healthCheck:
    checkIntervalSec: 15
    port: 8080
    type: HTTP
    requestPath: /v2
  connectionDraining:
    drainingTimeoutSec: 60
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: store-german
spec:
  replicas: 2
  selector:
    matchLabels:
      app: store
      version: german
  template:
    metadata:
      labels:
        app: store
        version: german
    spec:
      containers:
        - name: whereami
          image: gcr.io/google-samples/whereami:v1.1.3
          ports:
            - containerPort: 8080
          env:
            - name: METADATA
              value: "Gutentag!"
---
apiVersion: v1
kind: Service
metadata:
  name: store-german
  annotations:
    # BackendConfigを用いてヘルスチェック等をサービス毎にカスタマイズする
    cloud.google.com/backend-config: '{"default": "store-german-backendconfig"}'
spec:
  selector:
    app: store
    version: german
  ports:
    - port: 8080
      targetPort: 8080
---
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: store-german-backendconfig
spec:
  healthCheck:
    checkIntervalSec: 15
    port: 8080
    type: HTTP
    requestPath: /healthz
  connectionDraining:
    drainingTimeoutSec: 60
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: store-mirror-target
spec:
  replicas: 2
  selector:
    matchLabels:
      app: store
      version: mirror-target
  template:
    metadata:
      labels:
        app: store
        version: mirror-target
    spec:
      containers:
        - name: whereami
          image: gcr.io/google-samples/whereami:v1.1.3
          ports:
            - containerPort: 8080
          env:
            - name: METADATA
              value: "store-mirror-target"
---
apiVersion: v1
kind: Service
metadata:
  name: store-mirror-target
spec:
  selector:
    app: store
    version: store-mirror-target
  ports:
    - port: 8080
      targetPort: 8080

このマニフェストをstore-deployment-service.yamlというファイル名で保存し、applyします。

$ kubectl apply -f store-deployment-service.yaml
deployment.apps/store-v1 created
service/store-v1 created
deployment.apps/store-v2 created
service/store-v2 created
deployment.apps/store-german created
service/store-german created
deployment.apps/store-mirror-target created
service/store-mirror-target created

$ kubectl get pod --show-labels
NAME                                  READY   STATUS    RESTARTS   AGE   LABELS
store-german-66dcb75977-4lnkf         1/1     Running   0          86m   app=store,pod-template-hash=66dcb75977,version=german
store-german-66dcb75977-plqtx         1/1     Running   0          86m   app=store,pod-template-hash=66dcb75977,version=german
store-mirror-target-c6b945fdf-4tqj9   1/1     Running   0          86m   app=store,pod-template-hash=c6b945fdf,version=mirror-target
store-mirror-target-c6b945fdf-9lnbt   1/1     Running   0          86m   app=store,pod-template-hash=c6b945fdf,version=mirror-target
store-v1-65b47557df-5m6xc             1/1     Running   0          86m   app=store,pod-template-hash=65b47557df,version=v1
store-v1-65b47557df-65p42             1/1     Running   0          86m   app=store,pod-template-hash=65b47557df,version=v1
store-v2-6856f59f7f-cczqb             1/1     Running   0          86m   app=store,pod-template-hash=6856f59f7f,version=v2
store-v2-6856f59f7f-dsbnc             1/1     Running   0          86m   app=store,pod-template-hash=6856f59f7f,version=v2

$ kubectl get service
NAME                  TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
kubernetes            ClusterIP   10.0.2.1     <none>        443/TCP    101m
store-german          ClusterIP   10.0.2.204   <none>        8080/TCP   86m
store-mirror-target   ClusterIP   10.0.2.165   <none>        8080/TCP   86m
store-v1              ClusterIP   10.0.2.35    <none>        8080/TCP   86m
store-v2              ClusterIP   10.0.2.89    <none>        8080/TCP   86m

このstore-deployment-service.yamlに記載の通り、GKE Gateway Controllerでは、GLBC同様にBackendConfigリソースを用いて、Service毎にヘルスチェックやコネクションドレイニングの設定を変更できます。しかし、この機能はGA前に別のリソースに置き換えられることがドキュメントに明記されています。そして、10月にリリースされ、GKE Gateway ControllerではまだサポートされていないGateway API Version gateway.networking.k8s.io/v1alpha2においては、ヘルスチェック等を定義するための仕組みとしてPolicy Attachmentが定義されています。BackendConfigリソースはこの仕組みを利用するリソースに置き換えられると考えられます。

また、HTTPRouteでは、spec.gatewaysで処理を担当するホスト名、spec.gatewaysで紐付けを許可するGatewayの条件、spec.rulesでリクエストをどのように処理するかのルールを指定できます。

  • spec.rules[].matches
    • リクエストのパス、ヘッダー、クエリパラメータでルールを適用する対象のリクエストを指定
  • spec.rules[].forwardTo
    • spec.rules[].matchesで指定した条件に合致するリクエストをルーティングする先を指定
    • ルーティング先として、複数のサービス、portの組みを指定可能
    • 複数サービス間でのルーティングの分割割合も指定可能
  • spec.rules[].filter
    • spec.rules[].matchesで指定した条件に合致するリクエストのヘッダー修正、ミラーリングを指定

以下、Manifestで定義されるHTTPRoute store によって、再掲する下図に示すルーティングルールを設定します。

verification-network-diagram
再掲:検証で実現する内部負荷分散

kind: HTTPRoute
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
  name: store
  labels:
    gateway: internal-http
spec:
  hostnames:
    - "store.example.com"
  rules:
    - matches:
        - path:
            type: Prefix
            value: /de
      forwardTo:
        - serviceName: store-german
          port: 8080
      filters:
        # /deへのリクエストをService store-mirror-targetにミラーリングする
        - type: RequestMirror
          requestMirror:
            serviceName: store-mirror-target
            port: 8080
    - matches:
        - path:
            type: Prefix
            value: /mirror
      forwardTo:
        - serviceName: store-mirror-target
          port: 8080
    - matches:
        - headers:
            type: Exact
            values:
              env: canary
      forwardTo:
        - serviceName: store-v2
          port: 8080
    # matches未指定のルールは、合致するmatchesが存在しないリクエストに対して適用される
    - forwardTo:
        - serviceName: store-v1
          port: 8080
          # このルールが適用されるリクエストの9割をService store-v1にルーティングする
          weight: 90
        - serviceName: store-v2
          port: 8080
          # このルールが適用されるリクエストの1割をService store-v2にルーティングする
          weight: 10

HTTPRouteリソースを作成し、しばらく待ちます。すると、GKE Gateway Controllerによって、ルーティング先に指定した4つのサービス毎にバックエンドサービス、ヘルスチェックならびにNEG(Network Endpoint Group)が新規に作成されます。

NEGはKubernetesクラスタ内に動的に作成されるServicePodと直接通信できるエンドポイントを管理し、VPC内に提供する仕組みです。NEGの詳細は、以下記事が分かりやすいのでご参照ください。 medium.com

$ kubectl apply -f store-route.yaml
httproute.networking.x-k8s.io/store created

$ kubectl get httproute
NAME    HOSTNAMES               AGE
store   ["store.example.com"]   25s

$ kubectl describe httproute store
Name:         store
Namespace:    default
Labels:       gateway=internal-http
Annotations:  <none>
API Version:  networking.x-k8s.io/v1alpha1
Kind:         HTTPRoute
~~~
Status:
  Gateways:
    Conditions:
      Last Transition Time:  2021-11-10T06:47:18Z
      Message:
      Reason:                RouteAdmitted
      Status:                True
      Type:                  Admitted
      Last Transition Time:  2021-11-10T06:47:18Z
      Message:
      Reason:                ReconciliationSucceeded
      Status:                True
      Type:                  Reconciled
    Gateway Ref:
      Name:       internal-http
      Namespace:  default
Events:
  Type    Reason  Age   From                   Message
  ----    ------  ----  ----                   -------
  Normal  ADD     90s   sc-gateway-controller  default/store
  Normal  SYNC    7s    sc-gateway-controller  Bind of HTTPRoute "default/store" to Gateway "default/internal-http" was a success
  Normal  SYNC    7s    sc-gateway-controller  Reconciliation of HTTPRoute "default/store" bound to Gateway "default/internal-http" was a success

そして、GCLBにはHTTPRouteで指定した各サービスへのルーティングのルールが追加されます。GCLBに機能はあるものの、GKE Ingress Controllerでは実現できなかった、ルーティングの分割割合の指定等が設定できていることが確認できます。

$ gcloud compute url-maps list
NAME                                           DEFAULT_SERVICE
gkegw-8r5w-default-internal-http-2jzr7e3xclhj

# バックエンドサービスが4つ追加されており、それぞれのBACKENDSにNEGが指定されている
$ gcloud compute backend-services list
NAME                                                      BACKENDS                                                                                         PROTOCOL
gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5                                                                                                             HTTP
gkegw-8r5w-default-store-german-8080-o9g73h4mk3ob         asia-northeast1-a/networkEndpointGroups/k8s1-8db9299d-default-store-german-8080-e803f15f         HTTP
gkegw-8r5w-default-store-mirror-target-8080-zcxtgvjcck1r  asia-northeast1-a/networkEndpointGroups/k8s1-8db9299d-default-store-mirror-target-8080-de687243  HTTP
gkegw-8r5w-default-store-v1-8080-t7d6vxl1jy1d             asia-northeast1-a/networkEndpointGroups/k8s1-8db9299d-default-store-v1-8080-52e6fd60             HTTP
gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c             asia-northeast1-a/networkEndpointGroups/k8s1-8db9299d-default-store-v2-8080-70e3804f             HTTP

# ヘルスチェックも新たに4つ追加されている
$ gcloud compute health-checks list
NAME                                                      PROTOCOL
gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5            HTTP
gkegw-8r5w-default-store-german-8080-o9g73h4mk3ob         HTTP
gkegw-8r5w-default-store-mirror-target-8080-zcxtgvjcck1r  HTTP
gkegw-8r5w-default-store-v1-8080-t7d6vxl1jy1d             HTTP
gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c             HTTP

# store-deployment-service.yamlで追加した、Serviceに対応するNEGが新たに4つ追加されている
$ gcloud compute network-endpoint-groups list
NAME                                                     LOCATION           ENDPOINT_TYPE   SIZE
k8s1-8db9299d-default-store-german-8080-e803f15f         asia-northeast1-a  GCE_VM_IP_PORT  2
k8s1-8db9299d-default-store-mirror-target-8080-de687243  asia-northeast1-a  GCE_VM_IP_PORT  2
k8s1-8db9299d-default-store-v1-8080-52e6fd60             asia-northeast1-a  GCE_VM_IP_PORT  2
k8s1-8db9299d-default-store-v2-8080-70e3804f             asia-northeast1-a  GCE_VM_IP_PORT  2

そして、HTTPRouteで指定したルーティングのルールが、GCLBに追加されていることが確認できます。

$ gcloud compute url-maps describe gkegw-8r5w-default-internal-http-2jzr7e3xclhj --region asia-northeast1
creationTimestamp: '2021-11-12T01:46:24.204-08:00'
defaultRouteAction:
  faultInjectionPolicy:
    abort:
      httpStatus: 404
      percentage: 100.0
  weightedBackendServices:
  - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5
    weight: 1
fingerprint: AczSXReW744=
hostRules:
- hosts:
  - store.example.com
  pathMatcher: hostffxyqcv3l2rgbj3v3jakx7trkfuw01ei
id: '1800894912999426335'
kind: compute#urlMap
name: gkegw-8r5w-default-internal-http-2jzr7e3xclhj
pathMatchers:
- defaultRouteAction:
    faultInjectionPolicy:
      abort:
        httpStatus: 404
        percentage: 100.0
    weightedBackendServices:
    - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5
      weight: 1
  name: hostffxyqcv3l2rgbj3v3jakx7trkfuw01ei
  routeRules:
  - matchRules:
    - prefixMatch: /mirror
    priority: 1
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-mirror-target-8080-zcxtgvjcck1r
        weight: 1
  - matchRules:
    - prefixMatch: /de
    priority: 2
    routeAction:
      requestMirrorPolicy:
        backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-mirror-target-8080-zcxtgvjcck1r
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-german-8080-o9g73h4mk3ob
        weight: 1
  - matchRules:
    - prefixMatch: /de
    priority: 3
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c
        weight: 1
  - matchRules:
    - headerMatches:
      - exactMatch: canary
        headerName: env
      prefixMatch: /
    priority: 4
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c
        weight: 1
  - matchRules:
    - prefixMatch: /
    priority: 5
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v1-8080-t7d6vxl1jy1d
        weight: 90
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c
        weight: 10
region: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1
selfLink: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/urlMaps/gkegw-8r5w-default-internal-http-2jzr7e3xclhj

実際に、HTTPRouteで指定した条件に合致するリクエストを飛ばすと、パスベース、ヘッダーベースのルーティング、トラフィック分割ルールに従ってルーティングされることが確認できます。

# Gateway(内部GCLB)に付与されたIPを確認
$ kubectl get gateway internal-http -o=jsonpath="{.status.addresses[0].value}"
10.0.1.4

$ kubectl run curlpod --image curlimages/curl:7.78.0 --command -- sleep 3600

$ kubectl exec curlpod -it -- /bin/sh

# トラフィック分割によって、store-v1、時折store-v2にルーティングされることを確認
/ $ curl -H "host: store.example.com" 10.0.1.4
"pod_name": "store-v1-65b47557df-5m6xc"

/ $ curl -H "host: store.example.com" 10.0.1.4
"pod_name": "store-v1-65b47557df-65p42"
~~~
/ $ curl -H "host: store.example.com" 10.0.1.4
"pod_name": "store-v2-6856f59f7f-cczqb"

# ヘッダーベースのルーティングでstore-v2にルーティングされることを確認
/ $ curl -H "host: store.example.com" -H "env: canary " 10.0.1.4
"pod_name": "store-v2-6856f59f7f-cczqb"

# パスベースのルーティングでstore-germanにルーティングされることを確認
/ $ curl -H "host: store.example.com" 10.0.1.4/de
"pod_name": "store-german-66dcb75977-plqtx"

# パス/deへのリクエストがService store-mirror-targetにミラーリングされていることを、Podで出力しているアクセスログから確認 
$ kubectl logs store-mirror-target-c6b945fdf-9lnbt
~~~
2021-11-12 11:00:25,291 - werkzeug - INFO - 10.0.3.37 - - [12/Nov/2021 11:00:25] "GET /de HTTP/1.1" 200 -
~~~

5. HTTPRouteで定義したルーティングルールの優先順位とGCLB上でのルールの優先順位の定義を確認する

また、GKE Gateway Controllerではspec.rules[].matchesドキュメント記載の以下の基準に従って優先順位付けします。

  1. ホスト
    • 最も長い、または最も具体的なホスト名と一致するものを優先
  2. パス
    • 最も長い、または最も具体的なパスと一致するものを優先
  3. ヘッダー
    • 一致するHTTPヘッダーの数が多いものを優先

リクエストに合致するルーティングルールが複数ある場合、より優先度の高いものが適用されます。また、spec.rules[].matchesが全く同じルーティングルールが存在する場合は、作成されたタイムスタンプがより古いルーティングルールが適用されます。

以下が検証のサンプルです。既存のHTTPRouteに定められたパスベースによるルーティングに競合するルールを追加し、ルーティングルールの優先順位を検証します。

kind: HTTPRoute
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
  name: store-conflict
  labels:
    gateway: internal-http
spec:
  hostnames:
    - "store.example.com"
  rules:
    # /deでのパスベースのルーティングはHTTPRoute storeで既に定義されているため、競合する
    - matches:
        - path:
            type: Prefix
            value: /de
      forwardTo:
        - serviceName: store-v2
          port: 8080
# 競合するルーティングルールを持つHTTPRouteでも正常にapplyできる
$ kubectl apply -f store-route-conflict.yaml
httproute.networking.x-k8s.io/store-conflict created

$ kubectl get httproute
NAME             HOSTNAMES               AGE
store            ["store.example.com"]   66m
store-conflict   ["store.example.com"]   13s

ヘッダー、パスを両方指定した場合、パスベースの条件の方がヘッダーベースの条件よりも優先されることが確認できます。また、競合するルーティングルールが存在するパス/deを指定した場合は、競合するルール群の中で最初に作成されたものが適用されることを確認できます。

# ヘッダー、パスを両方指定した場合、パスベースの条件の方がヘッダーベースの条件よりも優先され、
# store-germanにルーティングされることを確認
$ curl -H "host: store.example.com" -H "env: canary " 10.0.1.4/de
"pod_name": "store-german-66dcb75977-plqtx"

# 競合するルーティングルールがあるパスを指定した場合、競合するルールの内、先に作成したstore-germanへのルーティングルールが適用され、
# 後から作成したstore-v2へのルーティングルールは適用されないことを確認
$ curl -H "host: store.example.com" 10.0.1.4/de
"pod_name": "store-german-66dcb75977-4lnkf"

次に、GCLBに設定されたルーティングルールの優先順位を確認します。ルールの優先順位はGCLBに定義されたrouteRulepriorityに設定されていることが分かります。

$ gcloud compute url-maps describe gkegw-8r5w-default-internal-http-2jzr7e3xclhj --region asia-northeast1
creationTimestamp: '2021-11-12T01:46:24.204-08:00'
defaultRouteAction:
  faultInjectionPolicy:
    abort:
      httpStatus: 404
      percentage: 100.0
  weightedBackendServices:
  - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5
    weight: 1
fingerprint: AczSXReW744=
hostRules:
- hosts:
  - store.example.com
  pathMatcher: hostffxyqcv3l2rgbj3v3jakx7trkfuw01ei
id: '1800894912999426335'
kind: compute#urlMap
name: gkegw-8r5w-default-internal-http-2jzr7e3xclhj
pathMatchers:
- defaultRouteAction:
    faultInjectionPolicy:
      abort:
        httpStatus: 404
        percentage: 100.0
    weightedBackendServices:
    - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-gw-serve404-80-mcfti8ucx6x5
      weight: 1
  name: hostffxyqcv3l2rgbj3v3jakx7trkfuw01ei
  routeRules:
  - matchRules:
    - prefixMatch: /mirror
    priority: 1
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-mirror-target-8080-zcxtgvjcck1r
        weight: 1
  - matchRules:
    - prefixMatch: /de
    priority: 2
    routeAction:
      requestMirrorPolicy:
        backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-mirror-target-8080-zcxtgvjcck1r
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-german-8080-o9g73h4mk3ob
        weight: 1
  - matchRules:
    - prefixMatch: /de
    priority: 3
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c
        weight: 1
  - matchRules:
    - headerMatches:
      - exactMatch: canary
        headerName: env
      prefixMatch: /
    priority: 4
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c
        weight: 1
  - matchRules:
    - prefixMatch: /
    priority: 5
    routeAction:
      weightedBackendServices:
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v1-8080-t7d6vxl1jy1d
        weight: 90
      - backendService: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/backendServices/gkegw-8r5w-default-store-v2-8080-sau4ah4scq2c
        weight: 10
region: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1
selfLink: https://www.googleapis.com/compute/v1/projects/techblog/regions/asia-northeast1/urlMaps/gkegw-8r5w-default-internal-http-2jzr7e3xclhj

競合するルーティングルールがあった場合でも、Gateway,HTTPRouteリソースのStatusがエラー等になることはありません。GatewayRouteは多対多の紐付けが可能なため、複数箇所でHTTPRouteを定義した結果、気づかないうちに競合するルーティングルールを定義しないように注意が必要です。しかし、Gateway APIではルーティングルールを一元管理する仕組みは特に定義されていません。そのため、ルーティングルールの競合への対応方針としてGateway APIのドキュメントに以下の記載があります。

Where possible, this should be communicated by setting appropriate status conditions on relevant resources.

GKE Gateway ControllerがGAになる際には、Gateway APIで定義されるリソースのStatusに警告が表示されるようになるかもしれません。

おわりに

本記事では、Kubernetes Gateway APIの概要と、APIで定義されるトラフィックのルーティングがGKE Gateway ControllerによってどのようにGCP上で実現されるのかの仕組みを紹介しました。Kubernetes Gateway APIとRBACを組み合わせることで、よりセキュアなマルチテナント構成を実現できます。そして、GKE Ingress ControllerではなかなかサポートされなかったGCLBの各種機能がGKE Gateway Controllerでサポートされるようなので、GAになるのが非常に楽しみです。

ZOZOでは一緒にサービスを作り上げてくれる仲間を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください!

corp.zozo.com hrmos.co

カテゴリー