Ingress の進化版 Gateway API を解説する Part 1 (シングルクラスタ編)

Kazuu
google-cloud-jp
Published in
30 min readDec 16, 2021

--

2022 年 4 月 27 日 追記:
2022 年 3 月末に GKE で Gateway API の v1alpha2 が利用可能になりました。それに伴い本記事の内容も v1alpha2 を前提としたものに更新しています。

重要
2022 年 4 月 27 日時点の既知の問題として、元々 v1alpha1 CRDsをインストールし利用していたクラスタに v1alpha2 CRDs をインストールした場合、新規設定が可能になるまで最大 1 週間程度が掛かる場合があります。元々 v1alpha1 を使っていたクラスタとは別のクラスタを用意して v1alpha2 を試して頂くことを推奨します。

Kubernetes / GKE ファンの皆様、こんにちは。Google Cloud の Kazuu (かずー) です。
さて、2021 年に一番話題に挙がった GKE のアップデートと言えば、GKE Autopilot の登場ですが、もうひとつ私が選ぶなら Gateway API が Preview でサポートされたこと!です。
本記事では Gateway API の概要とGKE での使い方の解説を行います。
それでは進めていきましょう!

TL; DR

  • Gateway API は Ingress (L7 LB で K8s service を公開) の進化版と呼べるものです。より多くのユースケースをサポートします。
  • 実際に下で動く LB は GKE の Ingress と変わらず External / Internal HTTP(S) load balancing ですが、GKE の Ingress では使えなかった機能が使えます。
  • まだまだ進化は続きます、2022 年に乞うご期待!

1. Gateway API 概要

Gateway API とは K8s サービスを外部公開するために用いられる新しい API リソースです。SIG-Network community を中心に開発が行われ、現在、GKE 含めて複数の実装が存在してます。

従来からあるこの手の API リソースとしては、Ingress / Service (type: LoadBalancer, type: NodePort) などがあります。下図のオレンジ色で囲った箇所ですね。

よく使われる K8s API リソース

特に Ingress は L7 LB を構成する際に使われますが、以下のような課題がありました。

  1. 複数 namespace (K8s 内の論理的なテナント) 間で共有できない
    マイクロサービス毎に namespace を割り当てて運用した場合に、代表して 1 つの Ingress (L7LB とその VIP) を持たせる構成が取れない。
  2. サポートする LB の機能が限定的
    例えば、External / Internal HTTP(S) LB が持つ Header based routing などは GKE の Ingress 経由では設定できない。
  3. アプリ開発者に優しくない
    1 つの Ingress というリソースで、プロトコル、IP アドレス、ポート番号、TLS 証明書から URL パスルーティングまでがカバーされており、インフラ知識のない人が運用するのは色々な意味で辛い。

こういった課題を解決するため、Gateway API が開発されました。尚、2022 年 4月 27 日現在 Gateway API のバージョンは v1alpha2 が最新です。
ひとえに Gateway API と言っても単一のリソースがある訳ではありません。以下のような複数の リソースから構成されます。

  • GatewayClass
  • Gateway
  • HTTPRoute
  • TCPRoute
  • TLSRoute
  • UDPRoute

2. Gateway API の主なリソース

この章では、現在、GKE ではサポートしている GatewayClass / Gateway / HTTPRoute について説明します。

  1. GatewayClass
    GatewayClass は実際にトラフィックを転送する LB や Proxy を定義するリソースです。基本的に Infrastructure provider (GKE なら Google) が事前定義しておくものです。考え方としては PersistentVolume における StorageClass と似ていると思います。
  2. Gateway
    Gateway は GatewayClass を参照しつつ、Listener (プロトコル、ポート番号、TLS設定等) や IP アドレス 等を定義するリソースです。
  3. HTTPRoute
    HTTPRoute は予め定義された Gateway を参照しつつ、受信した HTTP トラフィックのルーティング設定を行うリソースです。例えば、HTTP の URL path や Request header の value を元に転送先の指定したり、転送先毎に Weight を指定して Traffic split を行うことが可能です。HTTPRoute が指定する転送先は K8s の Service となります。

上記 3 つのリソースの相関を図にすると以下のようになります。

Gateway API 相関

まず、namespace 間で 1 つの Gateway を共有することが可能になっています。Ingress では 1 つのリソースで表現していたことを、あえて 3 つに分けることによって、インフラ担当者とアプリ開発者の関心事をリソースレベルで綺麗に分けることが出来ています。このように Ingress で課題となっていた点を解決しています。

それでは Gateway API と他の K8s リソースとの関係はどうなるのでしょうか?Ingress が Service を参照していたように、Gateway API では HTTPRoute が Service を参照します。

よく使われる K8s API リソース と Gateway API

3. GKE における Gateway API

2022 年 4月 27 日現在、GKE では Gateway API の v1alpha1 及び v1alpha2をサポートしています。v1alpha1 と v1alpha2 では API scheme に大きな違いがあるため、今から利用する場合はより Beta に近い v1alpha2 を推奨します。利用可能な Gateway Class は以下の 4 つとなり、GKE Standard / Autopilot どちらでも利用可能です。もっと詳しい比較はこちら

GKE で利用可能な Gateway Class 一覧

Gateway API と LB のConfiguration の相関は以下のようになっています。
Ingress にしても、Gateway API にしても External HTTP(S) LB (L7 XLB) と Internal HTTP(S) LB (L7 ILB) の Configuration を抽象化しているリソースであることが分かると思います。

Gateway API と LB と Ingress の相関

4. 主な前提条件と制約

  • GKE のバージョンは 1.20 以降で利用可能です。
  • VPC Native クラスタのみで利用可能です。
  • Gateway API で定義されている全ての機能が使えるわけではありません。詳しくは こちらをご確認ください。
  • Istio や Anthos Service Mesh(ASM) をインストールしたクラスタの場合、Gateway リソースの名前が Conflict するため、kubectl get gateway を実行した場合に Istio / ASM の Gateway リソースを取得出来ない可能性があります。その場合はこちらのワークアラウンドを使ってください。
  • FrontendConfig は利用不可です。
  • BackendConfig は Single-cluster Gateway (gke-l7-rilb && gke-l7-gxlb)では利用可能ですが、Multi-cluster Gateway (gke-l7-rilb-mc && gke-l7-gxlb-mc)では利用不可です。
  • kind: ManagedCertificate を使い GKE を通じて作成した SSL証明書はサポートされませんが、予め手動で作成した Google のマネージド SSL 証明書は利用可能です。

それではここから実際に GKE の Gateway API (v1alpha2)を触りながら使い方の確認をしていきます!

5. 前準備

はじめに Gateway API の CRDs を GKE クラスタにインストールし、Gateway API で Gateway Class / Gateway / HTTPRoute などのリソースを使えるようにします。公式手順に従い、v1alpha2 及び v1alpha1 の CRDs を両方インストールします。少なくとも 2022 年4 月 27 日時点では v1alpha2 だけインストールしても GatewayClass が利用可能になりません。

# v1alpha2 の CRDs をインストール
❯ kubectl apply -k "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.4.2"
customresourcedefinition.apiextensions.k8s.io/gatewayclasses.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/gateways.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/httproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/referencepolicies.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/tcproutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/tlsroutes.gateway.networking.k8s.io created
customresourcedefinition.apiextensions.k8s.io/udproutes.gateway.networking.k8s.io created
# v1alpha1 の CRDs をインストール
❯ kubectl apply -k "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.3.0"
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

ご覧のように Gateway API 関連の CRDs が無事インストール出来ました。
では、Gateway Class が確認出来るか試してみましょう。(CRDs のインストールから数分程度掛かります)

❯ kubectl get gatewayclass
NAME CONTROLLER AGE
gke-l7-gxlb networking.gke.io/gateway 2m26s
gke-l7-rilb networking.gke.io/gateway 2m27s

External Gateway (gke-l7-gxlb)と Internal Gateway (gke-l7-rilb)の 2 つが確認出来ました。あれ、残りの 2 つは?と思うかもしれませんが、それは別の手順で有効化します。

テスト用アプリをデプロイします。テスト用アプリは Pod のホスト名や環境変数に仕込んだ任意のバージョン名を返したりするものです。HTTPRoute を使ったルーティングの検証を行うため、今回は v1, v2, v3 と 3 つのバージョンに分けてデプロイを行いました。

❯ git clone git@github.com:kazshinohara/gateway-demo.git
❯ cd gateway-demo/external-gateway
❯ kubectl apply -f test-app.yaml
deployment.apps/whereami-v1 created
service/whereami-v1 created
deployment.apps/whereami-v2 created
service/whereami-v2 created
deployment.apps/whereami-v3 created
service/whereami-v3 created

これで準備完了です。

6. External Gateway を試す

はじめに、マネージド SSL 証明書を作成します。
今回は x-gw.gcpx.org というドメインネームを使います。

❯ gcloud compute ssl-certificates create x-gw-cert \
--domains=x-gw.gcpx.org \
--global
Created [https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/sslCertificates/x-gw-cert].
NAME TYPE CREATION_TIMESTAMP EXPIRE_TIME MANAGED_STATUS
x-gw-cert MANAGED 2021-12-15T00:41:52.385-08:00 PROVISIONING
x-gw.gcpx.org: PROVISIONING

続いて、IP Address の予約をします。

❯ gcloud compute addresses create x-gw-ip --global
Created [https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/addresses/x-gw-ip].
❯ gcloud compute addresses list
NAME ADDRESS/RANGE TYPE PURPOSE NETWORK REGION SUBNET STATUS
x-gw-ip X.X.X.X EXTERNAL RESERVED

DNS サーバーに A レコードの登録を行います。私は個人で利用している Google Domains の DNS サーバーを使いました。今だと Cloud Domains を使うのも良さそうです。

Gateway のマニフェストを以下の通り作成します。
先程作成した SSL 証明書 ”x-gw-cert” と予約した IP Address “x-gw-ip” の名前を指定します。

作成した Gateway のマニフェストを GKE クラスタに適用します。

❯ kubectl apply -f ./v1alpha2/x-gateway.yaml
gateway.gateway.networking.k8s.io/external-gateway created

DNS の登録と Gateway の作成 (LB 的には Forwarding Rule と Target Proxy の作成) からしばらくすると、証明書のステータスが ACTIVE になります。

❯ gcloud compute ssl-certificates list
NAME TYPE CREATION_TIMESTAMP EXPIRE_TIME MANAGED_STATUS
x-gw-cert MANAGED 2021-12-15T00:41:52.385-08:00 2022-03-15T01:08:14.000-07:00 ACTIVE
x-gw.gcpx.org: ACTIVE

HTTPRoute を作成します。今回は以下のようなルーティング設定を行いました。

  • デフォルトの転送先を v1 service に。
    (下のスニペットの 11 行目)
  • Request header に env:v2 が完全一致である場合は v2 service に
    (同 14 行目)
  • URL path に /version と前方一致がある場合は v3 service に
    (同 22 行目)

作成した HTTPRoute のマニフェストを GKE クラスタに適用します。

❯ kubectl apply -f ./v1alpha2/x-gw-httproute.yaml
httproute.gateway.networking.k8s.io/external-httproute created

動作確認をします。

## 何も指定せずルートパスにアクセス、v1 サービスからレスポンス
❯ curl -s https://x-gw.gcpx.org/\?param\=version | jq
{
"version": "v1"
}
## Header を env:v2 に指定してアクセス、v2 サービスからレスポンス
❯ curl -s https://x-gw.gcpx.org/\?param\=version -H "env:v2" | jq
{
"version": "v2"
}
## /version にアクセス、v3 サービスからレスポンス
❯ curl -s https://x-gw.gcpx.org/version | jq
{
"version": "v3"
}

GKE のIngress だと、URL path based routing しかサポートされていませんでしたが、Gateway API を通じて Header based routing も利用出来ることが確認出来ました。

念の為 LB 側の設定がどうなっているのか見てみましょう。まずは URL Map の名前を作成済みの Gateway の annotations から確認します。

❯ kubectl describe gateway.gateway.networking.k8s.io external-gatewayName:         external-gateway
Namespace: default
Labels: <none>
Annotations:
networking.gke.io/url-maps: gkegw-0qzb-default-external-gateway-u025eh5h3t4i

URL Map の設定を見てみます。

❯ gcloud compute url-maps describe gkegw-0qzb-default-external-gateway-u025eh5h3t4i
hostRules:
- hosts:
- x-gw.gcpx.org
pathMatcher: hostoox1lmf8v2hvq9zh1adzzj1if6xzgm3j
id: '7528610436486766703'
kind: compute#urlMap
name: gkegw-0qzb-default-external-gateway-u025eh5h3t4i
pathMatchers:
- defaultService: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkegw-0qzb-kube-system-gw-serve404-80-7cq0brelgzex
name: hostoox1lmf8v2hvq9zh1adzzj1if6xzgm3j
routeRules:
- matchRules:
- prefixMatch: /version
priority: 1
service: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkegw-0qzb-default-whereami-v3-8080-az4u6tjafqbt
- matchRules:
- headerMatches:
- exactMatch: v2
headerName: env
prefixMatch: /
priority: 2
service: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkegw-0qzb-default-whereami-v2-8080-muarp2l4si2h
- matchRules:
- prefixMatch: /
priority: 3
service: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkegw-0qzb-default-whereami-v1-8080-dk75hnz2o0hz
selfLink: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/urlMaps/gkegw-0qzb-default-external-gateway-u025eh5h3t4i

matchRules が複数設定されており、priority 値がそれぞれ付与されています。URL path を指定したルールが Request header を指定したルールよりも優先されていることがわかります。
このようなルート選択の仕様の詳細についてはこちらを確認してみください。

7. Internal Gateway を試す

注意:
Internal HTTP(S) LB を利用する場合、予め、Proxy-only subnets (LBの実態として動く Proxy が利用)を用意しておく必要があります。詳しくはこちらをご確認ください。以下の手順は Proxy-only subnets が GKE クラスタが存在する VPC の Region に作成済みであることが前提となります。

はじめに Internal Gateway で使う IP Address を予約します。今回は GKE の Node が所属する subnet から払い出します。

❯ gcloud compute addresses create i-gw-ip \
--region asia-northeast1 \
--subnet subnet-05 \
--project kzs-sandbox
Created [https://www.googleapis.com/compute/v1/projects/kzs-sandbox/regions/asia-northeast1/addresses/i-gw-ip].
❯ gcloud compute addresses describe i-gw-ip --region asia-northeast1
address: 192.168.5.34
addressType: INTERNAL

Gateway のマニフェストを以下の通り作成します。前のステップで作成した IP Address の名前を指定しておきます。

作成した Gateway のマニフェストを GKE クラスタに適用します。

❯ cd ../internal-gateway
❯ kubectl apply -f ./v1alpha2/i-gateway.yaml
gateway.networking.x-k8s.io/internal-gateway created

HTTPRoute のマニフェストを作成します。まずは External Gateway と同様のルーティング設定を行いました。(hostname は変えています。)

作成した HTTPRoute のマニフェストを GKE クラスタに適用します。

❯ kubectl apply -f ./v1alpha2/i-gw-httproute.yaml
httproute.gateway.networking.k8s.io/internal-httproute created

GCE のインスタンスから動作確認をします。

## 何も指定せずルートパスにアクセス、v1 サービスからレスポンス
❯ curl -s -H "Host:i-gw.gcpx.org" http://192.168.5.34/\?param\=version | jq
{
"version": "v1"
}
## Header を env:v2 に指定してアクセス、v2 サービスからレスポンス
❯ curl -s -H "Host:i-gw.gcpx.org" -H "env:v2" http://192.168.5.34/\?param\=version | jq
{
"version": "v2"
}
## /version にアクセス、v3 サービスからレスポンス
❯ curl -s -H "Host:i-gw.gcpx.org" http://192.168.5.34/version | jq
{
"version": "v3"
}

Internal Gateway (正確には Internal HTTP(S) LB) は Traffic split をサポートしています、試してみましょう。Traffic split を設定する HTTPRoute のマニフェストを作成します。v1 に 40%, v2 に 30%, v3 に 30% の割合で転送するようにします。

念の為、前のステップで使った HTTPRoute を消して、新たに作成したマニフェストを GKE クラスタに適用しましょう。

❯ kubectl delete-f ./v1alpha2/i-gw-httproute.yaml
httproute.networking.x-k8s.io "internal-httproute" deleted
❯ kubectl apply -f ./v1alpha2/i-gw-httproute-split.yaml
httproute.networking.x-k8s.io/internal-httproute-split created

動作確認を行います。おおよそ期待値通りの割合で Traffic split されています。

❯ while true; do curl -s -H "Host:i-gw.gcpx.org" http://192.168.5.34/version | jq ; sleep 1 ; done
{
"version": "v1"
}
{
"version": "v2"
}
{
"version": "v3"
}
{
"version": "v1"
}
{
"version": "v3"
}
{
"version": "v1"
}

LB の設定は以下のように HTTPRoute で指定した割合で weight が設定されています。

## 長いので一部割愛
❯ gcloud compute url-maps describe gkegw-0qzb-default-internal-gateway-mfrrz1j0tex3 --region asia-northeast1
routeRules:
- matchRules:
- prefixMatch: /
priority: 1
routeAction:
weightedBackendServices:
- backendService: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/regions/asia-northeast1/backendServices/gkegw-0qzb-default-whereami-v1-8080-dk75hnz2o0hz
weight: 40
- backendService: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/regions/asia-northeast1/backendServices/gkegw-0qzb-default-whereami-v2-8080-muarp2l4si2h
weight: 30
- backendService: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/regions/asia-northeast1/backendServices/gkegw-0qzb-default-whereami-v3-8080-az4u6tjafqbt
weight: 30
region: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/regions/asia-northeast1

最後に requestHeaderModifier を試します。これは Request header の追加、削除、上書きを LB で行う設定です。こちらも External Gateway では利用できません。もちろん GKE の Ingress でも利用出来ません。
試した内容としては以下の通りです。

  • “env:v2” header を持つリクエストから “my-header” header を削除します。
    (下のスニペットの 22 行目)
  • ”env:v2” header を持つリクエストに ”my-header:hello” を追加します。
    ( 同 24 行目)

Header の Value の rewrite を行うイメージです。

念の為、前のステップで使った HTTPRoute を消して、
新たに作成したマニフェストを GKE クラスタに適用しましょう。

❯ kubectl delete -f ./v1alpha2/i-gw-httproute-split.yaml
httproute.networking.x-k8s.io "internal-httproute-split" deleted
❯ kubectl apply -f ./v1alpha2/i-gw-httproute-rewrite.yaml
httproute.networking.x-k8s.io/internal-httproute-header-rewrite created

それでは試してみましょう。
このテスト用アプリは /headers/HEADER_NAME にアクセスすることで、HEADER_NAME で指定した Request header の value を返してくれます。
例えば、

❯ curl -s -H "Host:i-gw.gcpx.org" http://192.168.5.34/headers/User-Agent
curl/7.68.0

それでは実際に試してみます。
Request header には “my-header:konnichiwa” をセットします。

❯ curl -s -H "Host:i-gw.gcpx.org" -H "env:v2" -H "my-header:konnichiwa" http://192.168.5.34/headers/my-header
hello

期待どおり、”konnichiwa” が “hello” に書き換わっていることが確認できました。

LB の設定です。マニフェストで設定した通り、Request header の追加と削除が LB にも設定されていることが分かります。

❯ gcloud compute url-maps describe gkegw-0qzb-default-internal-gateway-mfrrz1j0tex3 --region asia-northeast1routeRules:
- headerAction:
requestHeadersToAdd:
- headerName: my-header
headerValue: hello
requestHeadersToRemove:
- my-header
matchRules:
- headerMatches:
- exactMatch: v2
headerName: env
prefixMatch: /
priority: 1
routeAction:
weightedBackendServices:
- backendService: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/regions/asia-northeast1/backendServices/gkegw-0qzb-default-whereami-v2-8080-muarp2l4si2h
weight: 1

8. まとめ

如何でしたしょうか?

Gateway API の登場により、GKE の Ingress では実現出来なかった、Header based routing や Traffic split, Request header の書き換えなどが可能になり、より幅広いユースケースでご利用頂けるようになりました。

また、インフラ担当者やアプリ開発者の皆さんの関心事に合わせてリソースを分割したことにより、これまで以上に皆さんの運用現場に適応させやすくなったのではないでしょうか。

皆さんもお時間許せば是非触ってみてください!
フィードバックもお待ちしています。

尚、今回カバー出来なかった External multi-cluster gateway と Internal multi-cluster gateway は後日、記事を書きますのでお楽しみに!

2022 年 1 月 5 日追記:
Multi-cluster Gateway の解説記事を書きました。是非ご覧ください。

--

--

Kazuu
google-cloud-jp

Customer Engineer at Google Cloud Japan. GKE & Cloud Run enthusiast. Opinions are my own, NOT views of my employer.