KubernetesのGateway APIとは


![1711495961417.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/bf1ba48b-1f8b-7b7c-ae66-f70180181627.png)
KubernetesのGateway APIが'23/10にGAとなり、[v1.0としてリリースされた](https://kubernetes.io/blog/2023/10/31/gateway-api-ga/)。
ベンダーでもサポートの動きがあり、例えば'24/2にリリースされたVMwareのTanzu Kubernretes Gridのv2.5からGateway APIが[サポートされるようになった](https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/2.5/tkg-deploy-mc/mgmt-release-notes.html#tanzu-kubernetes-grid-v250-2)。
今後Gateway APIを使う場面も増えてくると思われるので、どういうリソースかを改めて整理してみる。
また、Mac上でK8sクラスタを構築し、サクっと動作確認してみる。

## Gateway APIとは
Kubernetesクラスタ内のサービスを外部に公開する場合、一般的には`Ingress`が利用されると思う。
ただ、`Ingress`では以下のような課題があった。
- インフラ管理者、クラスタ管理者、アプリ開発者の間での責任分界点が不明瞭
- L7ロードバランサのみ提供され、L4が足りていない
- トラフィックの細かなルーティング管理がIngress Controller独自の拡張機能(annotation利用や[独自リソースによる拡張](https://projectcontour.io/docs/main/config/fundamentals/))でしか提供されず、環境依存が発生する

Gateway APIでは`Ingress`の標準機能では提供出来ていなかった以下のような機能が提供される。

#### 責任分界点の明確化
Gateway SIGの中で、どのリソースを誰が責任を持つかが明確化された。
![1711497306787.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/faeb350a-c077-5420-c382-7449f6091c26.png)
（Gatway SIGの[Introduction](https://gateway-api.sigs.k8s.io/)より引用）  

これにより、以下のような分担が標準ロールとして定義された。
|ロール|役割|
|---|---|
|インフラ管理者|`Gateway`リソースを管理するための`GatewayClass`(`IngressClass`みたいなもので、コントローラを指定)を提供・管理|
|クラスタ管理者|`GatewayClass`に紐づくロードバランサ（`Gateway`リソース）を提供・管理|
|アプリケーション開発者|ロードバランサとアプリケーションを紐づけるためのルーティング(`HTTPRoute`、`TLSRoute`など)を提供・管理|

#### 様々な種類のルーティングの提供
`Ingress`ではL7のみだったが、Gateway APIでは以下のルーティング用のリソースが提供される。
- `HTTPRoute`
- `TLSRoute`
- `GRPCRoute`
- `TCPRoute`
- `UDPRoute`

なお、'24/3時点では`HTTPRoute`以外は正式GAされていなかったと思うので、利用する際にサポート等が必要な場合は要確認である。
（例えばTKG2.5だと[HTTPRouteのみサポート](https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/2.5/tkg-deploy-mc/mgmt-reqs-network-nsx-alb-service.html#configure-gateway-api-v1beta1-for-nsx-alb-4)）

#### 細かなルーティング制御の実現
今までは`Ingress`内のannotationsで設定したり、Servie Mesh製品を入れて実現していたような細かなルーティングが設定可能である。
詳細は`HTTPRoute`の[Rules](https://gateway-api.sigs.k8s.io/api-types/httproute/#rules)を見ると分かるが、ヘッダーでフィルタリングしたり、カナリアリリースやタイムアウトをパスや条件ごとに個別に設定することが出来るようになった。

以下はカナリアリリースの例であり、`spec.rules[].backendRefs[].weight`で重み付けを行っている事が分かる。
```yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: foo-route
  labels:
    gateway: prod-web-gw
spec:
  hostnames:
  - foo.example.com
  rules:
  - backendRefs:
    - name: foo-v1
      port: 8080
      weight: 90
    - name: foo-v2
      port: 8080
      weight: 10
```

### Gateway APIが提供するリソース

APIの種類については[公式リファレンス](https://gateway-api.sigs.k8s.io/reference/spec/)で確認できるが、ここでは代表的な3つのリソース(`GatewayClass`、`Gateway`、`HTTPRoute`)を掘り下げる。

#### `GatewayClass`
[`GatewayClass`](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass)は`IngressClass`と同様に`Gateway`リソースを作るための型みたいなもので、基本的にはソフトウェアの中に梱包されていて利用者がManifestを編集することはほとんどないと思われる。
クラスタリソースであり、全Namespaceで利用できる。
Manifestの例は以下となる。
```yaml
kind: GatewayClass
metadata:
  name: cluster-gateway
spec:
  controllerName: "example.net/gateway-controller"
```
Gateway Controllerによっては`spec.paramtersRef`などを使ってパラメータをコントローラに渡すこともあるが、利用者はこのリソースはあまり意識しないので書き方は知らなくても問題ないかと思われる。

#### `Gateway`
クラスタ管理者はどういうパケットをクラスタ内に流すのを許可するか、を[`Gateway`](https://gateway-api.sigs.k8s.io/api-types/gateway/)リソースを使って設定・管理する。
`Ingress`でいうとエンドポイントのプロトコルやポートを管理するようなリソースで、AWSのSecurityGroupの設定に近いかもしれない。
以下は実際の`Gateway`リソースの例である。
```yaml
spec:
  gatewayClassName: kong
  listeners:
  - allowedRoutes:
      namespaces:
        from: Same
    name: http
    port: 80
    protocol: HTTP
  - name: proxy-ssl
    port: 443
    protocol: HTTPS
  - name: proxy-tcp-9901
    port: 9901
    protocol: TCP
  - name: proxy-udp-9902
    port: 9902
    protocol: UDP
```
`gatewayClassName`は`ingressClassName`のGateway版で`GatewayClass`を指定する。
`lisnters`でホスト名、ポート、TLS設定等を定義する。
また例には載せていないが、`spec.addresses`で`Gateway`がListenするアドレスも指定する事が出来る。
詳細な仕様は[こちら](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.Gateway)。

#### `HTTPRoute`
[`HTTPRoute`](https://gateway-api.sigs.k8s.io/api-types/httproute/)ではルーティング時の振る舞いを定義する事が出来る。
ここでパスごとの重み付け（B/Gやカナリアリリースで使用）、タイムアウト、ヘッダの書き換えなど柔軟なリクエスト処理が行えるようになる。

##### タイムアウトの例
以下では`timeouts.request`でクライアントに対する応答時間のタイムアウト値を設定し、`timeouts.backendRequest`でGatewayからバックエンドへのタイムアウト値を設定している。
```yaml
spec:
  parentRefs:
  - name: example-gateway
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /timeout
    timeouts:
      request: 10s
      backendRequest: 2s
    backendRefs:
    - name: timeout-svc
      port: 8080
```
##### ヘッダ書き換えの例
以下ではリクエストヘッダに`my-header: foo`を追加している。
```yaml
spec:
  hostnames:
    - my.filter.com
  rules:
    - filters:
        - type: RequestHeaderModifier
          requestHeaderModifier:
            add:
              - name: my-header
                value: foo
```


### Gateway API利用時のサービスへのアクセスの流れ
クライアントからPodが提供するサービスへアクセスするまでの流れは以下となる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/3ff47e1d-3f5a-3263-b0ab-6d071f23d2b0.png)
（[Request flow](https://kubernetes.io/docs/concepts/services-networking/gateway/#request-flow)より引用）

1. クライアントがURLにリクエストを投げる
2. DNSリゾルバがURLに対応する`Gateway`に割り当てられたIPを引っ張り出す
3. クライアントは`Gateway`のIPにリクエストを送信する（"Host:"ヘッダでURLアクセス名も渡す） 
4. `Gateway`側で対応する`HTTPRoute`を確認し、`HTTPRoute`の処理に移る
5. `HTTPRoute`で`Service`に転送する。この際ヘッダによるフィルタリングやヘッダの加工などリクエストの修正をしたり、流すパスを変えたりする事が出来る。
6. `Service`経由で`Pod`が提供するサービスに到達する


## ローカルPCでGateway APIを利用する
ここではMacにMicroK8sでKubernetes環境を作成し、そこにGateway APIを入れて動作確認する。
### Kubernetes環境の構築
[microk8sのインストール手順](https://microk8s.io/docs/install-macos)に従って構築・起動する。
```sh
brew install ubuntu/microk8s/microk8s
microk8s install
```
`multipass`をセットアップするか？みたいな質問が出るので`y`を押して併せてセットアップする。
また途中で`sudo`で動くためにパスワードを聞かれるので、それも素直に入力する。
インストールが終わったらMetalLBを使って`type: LoadBalancer`が使えるようにする。
`multipass`が使っているネットワークをNodeのIPから確認する。
```
microk8s kubectl get node microk8s-vm -o jsonpath={.status.addresses[0].address}
```
ここでは以下の結果となった。
```
192.168.64.3
```
なので、192.168.64.xxxであれば使えそうなので、ここをMetalLBが割り当てるレンジとしてMetalLBをデプロイする。
```sh
microk8s enable metallb:192.168.64.201-192.168.64.210
```
動作確認する。
```sh
microk8s kubectl run nginx --image nginx --port 80
microk8s kubectl expose pod nginx --port 80 --type LoadBalancer
NGINX_IP=$(microk8s kubectl get svc nginx -o jsonpath={.status.loadBalancer.ingress[0].ip})
curl $NGINX_IP
```
curlで以下が取得できればOK。
```html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
：（省略）
```
また、これ以降の作業はMicroK8s以外のKubernetes環境と手順を共通化させるため、`microk8s`コマンドにエイリアスを貼って`kubectl`と`helm`を`microk8s`を意識せずに叩けるようにする。
```sh
alias 'kubectl=microk8s kubectl'
alias 'helm=microk8s helm'
```


### Gateway APIの導入：インフラ管理者作業
Gateway APIを利用するためにはGateway Controllerが必要となる。
これはKubernetes標準では提供されておらず、各種ベンダから提供されている。
Gateway Controllerの一覧は[こちら](https://gateway-api.sigs.k8s.io/implementations/)。
どれを使ってもそれほど手順は変わらないと思うが、ここではどこのインフラでも使えそうな[Kong Ingress Controller](https://docs.konghq.com/kubernetes-ingress-controller/3.1.x/)で試す。
最初にGateway APIで提供されるリソース定義をインストールする。
```
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
```
導入すると以下のリソースが見えるようになる。
```sh
$ kubectl api-resources |grep gateway
gatewayclasses                    gc           gateway.networking.k8s.io/v1           false        GatewayClass
gateways                          gtw          gateway.networking.k8s.io/v1           true         Gateway
httproutes                                     gateway.networking.k8s.io/v1           true         HTTPRoute
referencegrants                   refgrant     gateway.networking.k8s.io/v1beta1      true         ReferenceGrant
```
次にGateway Controllerを導入する。
```sh
helm repo add kong https://charts.konghq.com
helm repo update
helm install kong kong/ingress -n kong --create-namespace 
```
しばらくすると、以下のような感じでGateway Controllerを含む各種コンポーネントが立ち上がる。
```sh
$ kubectl get all -n kong
NAME                                   READY   STATUS    RESTARTS   AGE
pod/kong-controller-55b777f995-b7zsg   1/1     Running   0          37s
pod/kong-gateway-868d48556c-m9vdf      1/1     Running   0          37s

NAME                                         TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)                         AGE
service/kong-gateway-admin                   ClusterIP      None             <none>           8444/TCP                        38s
service/kong-controller-validation-webhook   ClusterIP      10.152.183.107   <none>           443/TCP                         38s
service/kong-gateway-proxy                   LoadBalancer   10.152.183.96    192.168.64.202   80:31493/TCP,443:32243/TCP      38s
service/kong-gateway-manager                 NodePort       10.152.183.34    <none>           8002:32171/TCP,8445:32729/TCP   38s

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kong-controller   1/1     1            1           38s
deployment.apps/kong-gateway      1/1     1            1           38s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/kong-controller-55b777f995   1         1         1       37s
replicaset.apps/kong-gateway-868d48556c      1         1         1       37s
```
`GatewayClass`を作成する。[公式サイトの記述](https://docs.konghq.com/kubernetes-ingress-controller/latest/get-started/#install-the-gateway-apis)をそのまま適用する。
```yaml
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: kong
  annotations:
    konghq.com/gatewayclass-unmanaged: 'true'

spec:
  controllerName: konghq.com/kic-gateway-controller
EOF
```
なお、annotationの記述の意味は[こちら](https://docs.konghq.com/kubernetes-ingress-controller/latest/concepts/gateway-api/#binding-kong-gateway-to-a-gateway-resource)によると`Gateway`リソースをコントローラに監視させるためのKong Ingress Controller固有の設定となるため、他のGateway Controllerだと変わる。
他のGatway Controllerを使う場合、基本的に`GatewayClass`は各種ベンダから作成方法の指示があるので、そちらの指示に従って作成する。
以上でインフラ管理者作業は終了となり、クラスタ管理者に引き渡す。

### クラスタへのインバウンドルーティングの設定（`Gateway`リソースの作成）：クラスタ管理者作業
ここではクラスタ管理者として、HTTPのみクラスタ内に流すことを許可すると想定して、以下の`Gateway`リソースを作成する。
```yaml
kubectl create ns demo
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway
  namespace: demo
spec:
  gatewayClassName: kong
  listeners:
  - name: http
    port: 80
    protocol: HTTP
EOF
```
作成すると`ADDRESS`にMetalLBによって払い出されたIPが表示される。
```sh
$ kubectl get gateway  -n demo
NAME      CLASS   ADDRESS          PROGRAMMED   AGE
gateway   kong    192.168.64.202   True         18m
```
このIPを使ってアプリケーション開発者はアプリケーションとGatewayをつなぐ。

### アプリケーションへのルーティングの設定（`HTTPRoute`リソースの作成）：アプリケーション開発者作業

ここではサンプルとして、クラスタ内のnginx Podとクラスタ外の[httpbin.org](https://httpbin.org/)に`Service`経由でアクセス出来るようにし、`HTTPRoute`を使ってそれらに分散してアクセスできるかを試す。
最初にサンプルとなるnginxの`Pod`を立て、`Service`を作成する。
```sh
kubectl run nginx --image nginx --port 80 -n demo
kubectl expose pod nginx --port 80 -n demo
```
次に外部サービスにつなぐためのExternalNameな`Service`を作成する。
```sh
kubectl create svc externalname httpbin --external-name httpbin.org -n demo
```
サービスにアクセスするためのホスト名を定義する。DNSサーバがある人は先程のGatewayのアドレスを向いたドメインを用意する。
ここではDNSサーバがなかったので、フリーのワイルドカードDNSサービスの[nip.io](https://nip.io/)を使って代用する。
```sh
PROXY_IP=$(kubectl get gateway gateway -n demo -o jsonpath={.status.addresses[].value})
export HOSTNAME="my-service.$(sed "s/\./-/g" <<< $PROXY_IP).nip.io"
```
`${HOSTNAME}/service`にアクセスすると、先ほどの2つのサービスに分散するような`HTTPRoute`を作成する。
```yaml
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: route
  namespace: demo
  annotations:
    konghq.com/strip-path: 'true'
spec:
  hostnames: 
  - $HOSTNAME
  parentRefs:
  - name: gateway
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /service
    backendRefs:
    - name: nginx
      kind: Service
      port: 80
      weight: 50
    - name: httpbin
      kind: Service
      port: 80
      weight: 50
EOF
```
ちなみに`ExternalName`ではポート番号は指定しないが、`HTTPRoute`作成時は必須となっているのでとりあえず80を入れておく。
問題なければ以下のように`HTTPRoute`が作成される。
```
$ kubectl get httproute -n demo
NAME    HOSTNAMES                              AGE
route   ["my-service.192-168-64-202.nip.io"]   8m4s
```
アクセスしてみる。
```
curl -s my-service.192-168-64-202.nip.io/service
```
何度か実行すると、nginxとhttpbin.orgのそれぞれにアクセスする。
これにより、`weight`が適切に働き分散していることが確認できる。

## 所感
自分の経験として`Ingress`やServiceMesh製品を使ってルーティングの設定をする場合、製品や環境個々の設定方法を知る必要があり、環境が変わるたびにキャッチアップするのが大変だった。
Gateway APIがあればこの辺の環境差異が容易に吸収できそうなので、Gateway APIが普及すれば少し楽が出来そうな気がする。
また、ServiceMesh製品を入れないと難しかった機能も提供されており、構成がシンプルかつサポート利用時の問い合わせルートを簡素化出来るのもありがたい。
その他機能以外にはなるが、責任分界点をコミュニティ側で用意してくれたのもいい感じだと思う。
Kubernetes環境では責任分界点で揉めることも多いので、揉めそうな時はデファクトとして提示できればと思う。


## 参考文献
- [Gateway API SIG](https://gateway-api.sigs.k8s.io/)
- [kubernetes.ioのドキュメント](https://kubernetes.io/docs/concepts/services-networking/gateway/)
- [Ingressを強化したKubernetes Gateway APIを試してみる](https://developer.mamezou-tech.com/blogs/2022/07/24/k8s-gateway-api-intro/)
- [Gateway API の現在地 〜これまでとこれから〜](https://amsy810.hateblo.jp/entry/2023/12/01/090000)