Ingress のその先: Gateway API という選択

こんにちは。Enabling team の山本です。
Retail AI X では、Google Cloud 上でさまざまなサービスを稼働させており、日々、トライアルの店舗をご利用のお客様へ価値を提供しております。
この記事は、Enabling team の役割の 1 つである「Application stack の ecosystem に関する調査」の活動報告です。
## Application stack
今回は、Ingress API（Ingress）の進化版である Gateway を調査します。

[Kubernetes Gateway API](https://cloud.google.com/kubernetes-engine/docs/concepts/gateway-api?hl=ja)（Gateway）
![api-model.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569971/064b2794-b3f2-0f48-d0fb-68accd24666b.png)
引用：[Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/)

ご存じのとおり、Ingress は、負荷分散・SSL 終端などの機能を提供します。
今回、Gateway が GA になったことで、弊社サービスへの導入に向けた調査を行います。

### 評価
弊社の採用基準としては、[Technology Radar](https://www.thoughtworks.com/radar) でいうところの
**HOLD : 採用する場合は慎重に進める必要がある。**
くらいの位置付けだと思います。

### 理由
Ingress でサポートされていた機能が現時点でサポートされていないためです。
具体的には、以下の機能を必要としています。
* Google Cloud Armor
* SSL ポリシー
* カスタム リクエストとレスポンス ヘッダー

Production への適用は、もう少し待つ必要があると思います。
今後の Update に期待です！
それでは、先々の導入準備として、 Ingress との違いを確認していきます。

## はじめに
弊社のサービスは、 ほぼ全てで Ingress を採用してきました。
Ingress について、Infra と Application の関心ごとが混在することに不満を感じます。
具体的には、ドメイン・証明書と Path rule が同じ manifest に存在するところです。
そもそも、Stream-aligned team は、Production 環境全体に責任を持つため、Infra が、Application がと言う境界は無いはずですが。

## 構築
### GKE Cluster の作成
```
gcloud beta container --project "gw-sandbox-service-a" clusters create "case1a" --zone "asia-northeast1-a" --no-enable-basic-auth --cluster-version "1.24.7-gke.900" --release-channel "regular" --machine-type "e2-medium" --image-type "COS_CONTAINERD" --disk-type "pd-balanced" --disk-size "100" --metadata disable-legacy-endpoints=true --scopes "https://www.googleapis.com/auth/devstorage.read_only","https://www.googleapis.com/auth/logging.write","https://www.googleapis.com/auth/monitoring","https://www.googleapis.com/auth/servicecontrol","https://www.googleapis.com/auth/service.management.readonly","https://www.googleapis.com/auth/trace.append" --max-pods-per-node "110" --num-nodes "1" --logging=SYSTEM,WORKLOAD --monitoring=SYSTEM --enable-ip-alias --network "projects/gw-sandbox-service-a/global/networks/default" --subnetwork "projects/gw-sandbox-service-a/regions/asia-northeast1/subnetworks/default" --no-enable-intra-node-visibility --default-max-pods-per-node "110" --no-enable-master-authorized-networks --addons HorizontalPodAutoscaling,HttpLoadBalancing,GcePersistentDiskCsiDriver --enable-autoupgrade --enable-autorepair --max-surge-upgrade 1 --max-unavailable-upgrade 0 --enable-shielded-nodes --node-locations "asia-northeast1-a" --gateway-api=standard
```
* `--gateway-api=standard` : Gateway を有効にするパラメータ

### SSL 証明書の作成
IP Address の予約は不要です。
* `--domains` : 取得する FQDN
```
gcloud compute ssl-certificates create store-example-com \
    --domains=gw-test.example.com \
    --global
```

### ゲートウェイと HTTPRoute を作成する
Gateway の作成時に、SSL 証明書で指定したドメインを設定します。
```
cat << EOF  | kubectl apply -f -
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
  name: external-http
spec:
  gatewayClassName: gke-l7-gxlb
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      options:
        networking.gke.io/pre-shared-certs: store-example-com    # SSL 証明書名
EOF
```

```
cat << EOF  | kubectl apply -f -
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
  name: store-external
  labels:
    gateway: external-http
spec:
  parentRefs:
  - name: external-http
  hostnames:
  - "gw-test.example.com"    # 取得する FQDN
  rules:
  - backendRefs:
    - name: store-v1
      port: 8080
EOF
```

### IP Address を取得する
```
kubectl get gateway external-http -o=jsonpath="{.status.addresses[0].value}"
```

FQDN を登録します。
ステータスが `ACTIVE` になるまで15分程度待ちます。

### ゲートウェイの動作を確認する
```
yamamoto_daisuke@cloudshell:~ (gw-sandbox-service-a)$ curl https://gw-test.example.com -v
*   Trying 34.120.89.76:443...
* Connected to gw-test.example.com (34.120.89.76) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=gw-test.example.com
*  start date: Jan  6 11:11:22 2023 GMT
*  expire date: Apr  6 11:56:31 2023 GMT
*  subjectAltName: host "gw-test.example.com" matched cert's "gw-test.example.com"
*  issuer: C=US; O=Google Trust Services LLC; CN=GTS CA 1D4
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x5651339d92c0)
> GET / HTTP/2
> Host: gw-test.example.com
> user-agent: curl/7.74.0
> accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 500
< date: Fri, 06 Jan 2023 23:04:10 GMT
< content-length: 14
< content-type: text/plain; charset=utf-8
< via: 1.1 google
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
<
response 500
* Connection #0 to host gw-test.example.com left intact
yamamoto_daisuke@cloudshell:~ (gw-sandbox-service-a)$
```

これで、Gateway の設定が完了しました。

Application を使用した動作確認は、[公式](https://cloud.google.com/kubernetes-engine/docs/how-to/secure-gateway?hl=ja#verify-gateway-certificate-manager)を確認してください。

## まとめ
`Gateway` resource と　`HTTPRoute`　resource　が分かれていることで、SRE と Developer が役割分担を行うことができます。
Ingress の後継ということで違いを見てきましたが、問題となるような設定手順は存在しませんでした。
既存のサービスの Ingress の置き換えも問題なく進めることができると思います。
なお、今後、機能追加は、Gateway が中心になる予定ということです。
Stream-aligned team の Production への適用時のギャップを埋めるため、動向をウォッチしていきます。