Kubernetes Gateway API で高度なトラフィックルーティングを実現する

KubeCon + CloudNativeCon Europe 2021 の [Gateway API: A New Set of Kubernetes APIs for Advanced Traffic Routing](Gateway API: A New Set of Kubernetes APIs for Advanced Traffic Routing) というセッションで `powerful version of Ingress` として紹介されていた **Gateway API** について、調査した内容をまとめてみました。

※ 2021年5月時点では v1alpha1 バージョンの API であり、今後のリリースにおいて変更が加えられることが想定されます。以降の記載内容についても変更が入る可能性がありますので、その点ご留意頂ければ幸いです。

## 目次
<!-- タイトルとアンカー名を編集 -->
- [Gateway API とは](#gateway-api-とは)
- [Gateway API 開発の経緯](#gateway-api-開発の経緯)
- [動作確認](#動作確認)
  - [1. GKE 環境準備](#1-gke-環境準備)
  - [2. GatewayClass リソースの存在確認](#2-gatewayclass-リソースの存在確認)
  - [3. Gateway リソースのデプロイ](#3-gateway-リソースのデプロイ)
  - [4. デモアプリケーションのデプロイ](#4-デモアプリケーションのデプロイ)
  - [5. HTTPRoute リソースのデプロイ](#5-httproute-リソースのデプロイ)
  - [6. アクセス確認](#6-アクセス確認)
- [今後のロードマップ](#今後のロードマップ)
- [おわりに](#おわりに)
- [参考文献](#参考文献)

<a id="#Chapter1"></a>
## Gateway API とは

[Gateway API](https://gateway-api.sigs.k8s.io/) とは、外部からのアクセスやトラフィックを管理するための API やリソース (後述の GatewayClass、Gateway、HTTPRoute、TCPRoute など) の総称であり、[SIG-Network](https://github.com/kubernetes/community/tree/master/sig-network) コミュニティのプロジェクトとして開発が進められています。

Gateway API の実装は、Ingress と同じように Controller (Gateway Controller) 側で実装する必要があり、現在 Contour、Traefik、GKE Gateway Controller など様々なプロジェクトで開発が進められています。それぞれの実装のステータスは [公式ドキュメント - Implementations](https://gateway-api.sigs.k8s.io/references/implementations/) にまとめられています。

従って、実際にサポートされる機能は使用する Gateway Controller にもよりますが、Gateway API として定義されている機能としては以下のようなものがあります。

- サービスの外部公開
- SSL/TLS 終端
- トラフィックルーティング
    - パスベースのトラフィックルーティング
    - ヘッダー情報に基づくトラフィックルーティング
    - 割合ベースのトラフィック分割
- トラフィックミラーリング
- リクエスト・レスポンスヘッダー情報の更新
- 複数の Namespace を対象としたルート選択
- 複数の Kubernetes クラスタを対象としたルート選択　など

現時点では提供されていませんが、Rate Limiting ([issue#326](https://github.com/kubernetes-sigs/gateway-api/issues/326)) や認証機能 ([issue#114](https://github.com/kubernetes-sigs/gateway-api/issues/114)) など、今後も新しい API・機能が追加されていくと予想されます。

以下の図は、Route リソースとして HTTPRoute を使用した場合の Gateway API の利用イメージです。各リソースの詳細については後ほど説明しますが、クライアントから Gateway 宛てに送信されたリクエストが、HTTPRoute に設定したルール (Routing rule) に従って Service へとルーティングされます。
![Screen Shot 2021-05-18 at 0.07.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1240692/eea1b258-59b8-91bc-5c6d-9b5955699fec.png)

<a id="#Chapter2"></a>
## Gateway API 開発の経緯

外部からのアクセス管理や、シンプルなトラフィックルーティングの機能を提供するリソースとしては既に Ingress がありますが、なぜ新たに Gateway API が開発されることになったのでしょうか。主に、以下の課題感をもとに開発が始まったようです。

- Ingress 自体の機能では、[Gateway API とは](#gateway-api-とは) で紹介したような機能が必要となる高度なユースケースに対応していなかった
- Ingress の機能拡張のために、各種 Ingress controller 等で多数のカスタムアノテーションや CRD が実装されることにより、管理の複雑化やポータビリティ低下が発生していた
    - カスタムアノテーションの場合、シンプルな表現しかできず機能も制限される

これらの課題を解決するための新しい機能として、Gateway API (当時の名称は Service APIs) についての議論が KubeCon + CloudNativeCon North America 2019 San Diego で行われ、その後開発が進み、無事 2020年11月 に v1alpha1 バージョンがリリースされました。

なお、Ingress を置き換えることを目的に開発されているわけではなく、ユースケースに応じて Ingress と Gateway API を使い分けることを想定しており、`現時点では Ingress の非推奨化や削除の予定はない`そうです。

<a id="#Chapter3"></a>
## リソースモデル

Gateway API の概要を理解したところで、[リソースモデル](https://gateway-api.sigs.k8s.io/concepts/api-overview/#resource-model) のうち主要な部分について見ていきたいと思います。

- `GatewayClass`
    - Cluster Scoped のリソース
    - 提供する Gateway (ロードバランサ) をテンプレート化したもの
    - マネージド kubernetes サービスの場合はクラウドベンダが提供
- `Gateway`
    - Namespace Scoped のリソース  
      ※ Cross-Namespace をサポートしており、異なる Namespace のサービスに紐づく Gateway も提供できる
    - 指定した GatewayClass をもとに作成されるロードバランサリソース
    - Gateway (ロードバランサ) がトラフィックを受け付ける方法・ポート番号や、Route と紐づけるための条件を指定
- `xRoute`
    - Namespace Scoped のリソース  
    - Service にリクエストをルーティングするためのプロトコル固有のルール (例. HTTPRoute, TCPRoute)
    - トラフィックルーティングの条件を指定 (パスやヘッダ情報、トラフィック分割の割合など)
- `Service`
    - 一般的な Kubernetes [Service](https://kubernetes.io/docs/concepts/services-networking/service/) リソース

補足： 以下の図の通り、Gateway API はロール指向のデザインに基づいており、各リソースは組織のロールに対応するようにして用意されています。

![api-model.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1240692/55c1a43e-72cd-9f08-1038-cdfa82a0c560.png)


<a id="#Chapter3"></a>
## 動作確認

それでは、実際に Gateway API を使用してみたいと思います。動作環境としては Google Kubernetes Engine (GKE) を用いて、各種 Gateway API リソースのデプロイと、[Gateway のデプロイ](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways) をベースにいくつかの基本機能を確認します。

#### 1. GKE 環境準備

[GKE Gateway コントローラの要件](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways#gateway_controller_requirements) を満たすような GKE クラスタを準備します。今回は、以下のような検証環境を作成しました。

`※ GKE の Gateway API は現在プレビュー機能です`

- 検証環境
    - プラットフォーム： Google Kubernetes Engine
    - バージョン： v1.20.6-gke.1000
    - リージョン： us-east1
    - VPC ネイティブ（エイリアス IP）クラスタ
    - alpha 機能を有効化  
      ※ alpha 機能を有効化する場合、`--no-enable-autoupgrade`, `--no-enable-autorepair` のオプション付与が必要

```shell
$ gcloud container clusters create gke1 \
    --cluster-version 1.20.6-gke.1000 \
    --zone us-east1-b \
    --enable-kubernetes-alpha \
    --no-enable-autoupgrade \
    --no-enable-autorepair \
    --enable-ip-alias
```

本投稿では `内部 GatewayClass` を用いた動作確認を行うため、[プロキシ専用サブネット](https://cloud.google.com/load-balancing/docs/l7-internal/proxy-only-subnets) を作成します。

```shell
$ gcloud compute networks subnets create proxy-only-subnet \
    --purpose=INTERNAL_HTTPS_LOAD_BALANCER \
    --role=ACTIVE \
    --region=us-east1 \
    --network=default \
    --range=10.10.0.0/20
```

#### 2. GatewayClass リソースの存在確認

前述の [GKE Gateway コントローラの要件](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways#gateway_controller_requirements) を満たしていれば、[GKE Gateway コントローラ](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways#gateway_controller_requirements) によって自動的に GatewayClass がデプロイされていると思います (クラスタ作成後から1, 2分かかることがあります)。

```shell
$ kubectl get gatewayclass
NAME          CONTROLLER
gke-l7-gxlb   networking.gke.io/gateway
gke-l7-rilb   networking.gke.io/gateway
```

上記のうち、`gke-l7-gxlb` が GCP 上で提供されるグローバル外部 HTTP(S) ロードバランサ、`gke-l7-rilb` がリージョン内部 HTTP(S) ロードバランサです。

#### 3. Gateway リソースのデプロイ

どちらを利用するにしてもそれほど手順は変わらないですが、よりシンプルな `gke-l7-rilb` (内部 GatewayClass) を用いて動作確認を実施していきます。

以下のようなマニフェストを作成し、Gateway リソースをデプロイします。

```config
$ cat gateway.yaml
kind: Gateway
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
  name: internal-http
spec:
  gatewayClassName: gke-l7-rilb # specifies the GatewayClass
  listeners:
  - protocol: HTTP
    port: 80
    routes:
      kind: HTTPRoute
      selector:
        matchLabels:
          gateway: internal-http
```
```shell
$ kubectl apply -f gateway.yaml
gateway.networking.x-k8s.io/internal-http created
```

現状、Gateway によって作成されたロードバランサリソースは Google Cloud Console から確認不可ですが ([プレビューの制限と既知の問題](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways#limitations))、Gateway をデプロイしたことで、GCP 上にリージョン内部 HTTP(S) ロードバランサが作成されているはずです。

下記コマンドの実行結果に、内部 IP アドレスと `SYNC on default/internal-http was a success` のようなメッセージが出力されていればデプロイ成功です。

補足： SYNC が完了する数分前に BackendService が Ready でない旨の Warning が出力されているのが気になりましたが、全てのリソースがデプロイされるまで数分かかる場合があると [Gateway のデプロイ](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways#internal-gateway) に書いてあるため、その範疇だと思われます。

```shell
$ kubectl describe gateway internal-http
  ...
Status:
  Addresses:
    Type:   IPAddress
    Value:  10.142.0.5
  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     5m34s                 regional-gke-gateway-ctlr  default/internal-http
  Warning  SYNC    5m11s                 regional-gke-gateway-ctlr  generic::invalid_argument: error ensuring load balancer: Insert: The resource 'projects/<project_id>/regions/us-east1/backendServices/gk3-gw-bf65-default-gw-serve404-80-mcfti8ucx6x5' is not ready
  Normal   UPDATE  102s (x3 over 5m34s)  regional-gke-gateway-ctlr  default/internal-http
  Normal   SYNC    102s                  regional-gke-gateway-ctlr  SYNC on default/internal-http was a success
```

#### 4. デモアプリケーションのデプロイ

デモ用に提供されている store アプリケーション (store-v1、store-v2、store-german) をデプロイします。 

```shell
$ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/master/gateway/gke-gateway-controller/app/store.yaml
```
```shell
$ kubectl get deploy,svc
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/store-german   2/2     2            2           4m52s
deployment.apps/store-v1       2/2     2            2           4m59s
deployment.apps/store-v2       2/2     2            2           4m55s

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/store-german   ClusterIP   10.100.4.137    <none>        8080/TCP   4m52s
service/store-v1       ClusterIP   10.100.11.236   <none>        8080/TCP   4m57s
service/store-v2       ClusterIP   10.100.11.173   <none>        8080/TCP   4m54s
```

#### 5. HTTPRoute リソースのデプロイ

`internal-http` Gateway が受信したリクエストを各 Service に転送するためのルールを設定した HTTPRoute リソースをデプロイします。

`internal-http` Gateway の matchLabels に設定した `gateway: internal-http` ラベルを付与することで、Gateway リソースとの紐付けを行っています。

```config
$ cat store-route.yaml 
kind: HTTPRoute
apiVersion: networking.x-k8s.io/v1alpha1
metadata:
  name: store
  labels:
    gateway: internal-http
spec:
  hostnames:
  - "store.example.com"
  rules:
  - forwardTo:
    - serviceName: store-v1
      port: 8080
  - matches:
    - headers:
        type: Exact
        values:
          env: canary
    forwardTo:
    - serviceName: store-v2
      port: 8080
  - matches:
    - path:
        type: Prefix
        value: /de
    forwardTo:
    - serviceName: store-german
      port: 8080
```
```shell
$ kubectl apply -f store-route.yaml
httproute.networking.x-k8s.io/store created
```

#### 6. アクセス確認

これで、必要な Gateway API リソースのデプロイが完了しました。早速、`internal-http` に設定したルール (Routing rule) に従って、適切な Service にトラフィックルーティングされるかを確認したいと思います。

今回は gke-l7-rilb (内部 GatewayClass) を用いて Gateway をデプロイしたため、クラスタ内部に作成した Pod から `curl` コマンドでアクセス確認をしたいと思います。

```console
$ kubectl run -it --rm alpine --image=alpine --generator=run-pod/v1 -- sh
If you don’t see a command prompt, try pressing enter.

/ # apk add curl
/ # curl -h
    Usage: curl [options...] <url>
    ...
```

まずは、何も指定しない (`env: canary` HTTP ヘッダーも、`/de` パス指定もしない) 場合の結果からです。設定したルールの通りに、`store-v1` にルーティングされています。

```console
/ # curl -H "host: store.example.com" 10.142.0.5
{
  "cluster_name": "gke1", 
  "host_header": "store.example.com", 
  "metadata": "store-v1", 
  "node_name": "gke-gke1-default-pool-beb2c84d-m3l1.us-east1-b.c.<project_id>.internal", 
  "pod_name": "store-v1-65b47557df-z5ls2", 
  "pod_name_emoji": "🧕🏻", 
  "project_id": "<project_id>", 
  "timestamp": "2021-05-21T09:21:24", 
  "zone": "us-east1-b"
}
```

次に、`env: canary` HTTP ヘッダーを指定した場合の結果です。こちらも設定した通り、`store-v2` にルーティングされています。

```console
/ # curl -H "host: store.example.com" -H "env: canary " 10.142.0.5
{
  "cluster_name": "gke1", 
  "host_header": "store.example.com", 
  "metadata": "store-v2", 
  "node_name": "gke-gke1-default-pool-beb2c84d-fvs6.us-east1-b.c.<project_id>.internal", 
  "pod_name": "store-v2-6856f59f7f-5wmc5", 
  "pod_name_emoji": "🗯️", 
  "project_id": "<project_id>", 
  "timestamp": "2021-05-21T09:32:31", 
  "zone": "us-east1-b"
```

最後に、`/de` パス指定した場合の結果です。設定した通り、`store-german` にルーティングされています。いい感じですね！

```console
/ # curl -H "host: store.example.com" 10.142.0.5/de
{
  "cluster_name": "gke1", 
  "host_header": "store.example.com", 
  "metadata": "Gutentag!", 
  "node_name": "gke-gke1-default-pool-beb2c84d-fvs6.us-east1-b.c.<project_id>.internal", 
  "pod_name": "store-german-66dcb75977-ljqr8", 
  "pod_name_emoji": "🌝", 
  "project_id": "<project_id>", 
  "timestamp": "2021-05-21T09:33:29", 
  "zone": "us-east1-b"
}
```

以上、基本機能のみではありますが、GKE 上で各種 Gateway API リソースをデプロイして、設定したルール (Routing rule) に従ってトラフィックルーティングされることを確認することができました。

今回は gke-l7-rilb (内部 GatewayClass) を使用しましたが、gke-l7-gxlb (外部 GatewayClass) を用いたデプロイ方法についても [Gateway のデプロイ](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways) で紹介されており、手順通りに外部公開用の Gateway を作成することができたので、興味のある方は見て頂けたらと思います。

また、[Gateway API の公式ドキュメント](https://gateway-api.sigs.k8s.io/) をみると、その他の機能についても詳細な説明がありとても参考になります。現時点では Gateway Controller によって動作する機能に制限はあるかもしれませんが、例えば以下のように割合ベースのトラフィック分割の設定をして、カナリアリリースができるかどうかなど、色々試してみるのも面白そうです (GKE 上では以下の設定で動作しました)。

```config
kind: HTTPRoute
  ... <OMITTED>
  - forwardTo:
    - serviceName: store-v1
      port: 8080
      weight: 7
    - serviceName: store-v2
      port: 8080
      weight: 3
```

<a id="#Chapter4"></a>
## 今後のロードマップ

現時点では v1alpha1 バージョンの機能ですが、以下のような目標感で活発に開発が行われているようです。

- 近いうちに v1alpha2 リリース予定
- 可能であれば今年中に v1beta1 をリリースしたい

Gateway Controller の実装 (GKE Gateway Controller, Contour, Traefik など) も現在開発中とのことでしたので、今後の発展に期待したいと思います。


<a id="#Chapter5"></a>
## おわりに

以上、Gateway API の概要や開発経緯、GKE 上で実施した動作確認のまとめでした。
本投稿の内容について、ご指摘やアドバイス・質問等がありましたらぜひコメント欄に記入いただけたらと思います。


<a id="#Chapter6"></a>
## 参考文献

- [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/)
- [Deploying Gateways](https://cloud.google.com/kubernetes-engine/docs/how-to/deploying-gateways)
- [Gateway API: A New Set of Kubernetes APIs for Advanced Traffic Routing](https://kccnceu2021.sched.com/event/iE39?iframe=no) 
- [Kubernetes Blog: Evolving Kubernetes networking with the Gateway API](https://kubernetes.io/blog/2021/04/22/evolving-kubernetes-networking-with-the-gateway-api/)