GKE Gateway Controllerの勘所
GKE Gateway Controller を使ってマイクロサービスを提供するシステムを組んだので、備忘録です。
[おさらい]GKE Gateway Controllerとは何か
Ingressの課題を解決するため、Ingressの後継としてk8sがGateway API を提供していますが、GKE Gateway ControlerはそのGateway APIのGKE版実装です。L7で高度なトラフィック制御ができるk8sのアドオンです。
システムデザインとしては、LBレイヤやトラフィック制御、ポリシー制御などをロール志向で制御できるような思想で作られているようです。自分は、ある程度規模のある開発体制の場合はワークするかもしれないと思いました。
IngressとGatewayの違い
まずIngressは、パスベースルーティングのような非常にシンプルな仕様しか定義されていません。HTTPヘッダなどを使ってサービス間のトラフィックルーティングをしようとした場合、ミドルウェアのインストールなどの対応が必要でした。が、Gatewayではネイティブでそういったルーティング機能をサポートしています。つまり頑張ってServiceMesh系のミドルウェアをインストールする手間なくパスベース以外でもルーティングが実現できます。
また、namespace間のルーティンなども ReferenceGrant を使うことで簡単にできます。
他にも冒頭でお伝えした通りGatewayはロール志向な構成である、などもありますが、多くの開発者的に嬉しいのは上記2点かなと、個人的には考えています。
参照. https://gateway-api.sigs.k8s.io/concepts/api-overview/#extension-points
機能性について
Ingressが備えていた機能はほぼ全て提供しており、Ingress時代はannotationなどを使って実現する必要があったような設定もGatewayではannotationを使わずに実現できたりします。
シングルクラスタ、マルチクラスタ両方対応しており、さらに外部アクセスも内部アクセスも両方制御できます(内部的にLBが立ち上がる)。内部Gatewayでクラスタ内通信も制御できるので、Envoyプロキシなどを意識せず内部トラフィック制御をすることも可能です。ただ、内部トラフィックのアクセス制御をする際、ネットワーク上はプロキシ(Envoy)用のサブネットの定義などはする必要があるため、裏側ではEnvoyを使ったトラフィック制御が行われていそうです。ただ、Envoyの管理をしなくて良いのは楽で嬉しいですね。
参照. https://cloud.google.com/kubernetes-engine/docs/concepts/gateway-api
勘所一覧
基本的な構築方法や設置値は公式ドキュメントを参照いただくとして、ここからは自分が実際にプロダクション環境でGatewayを構築して発見した勘所を解説していきます。
HealthcheckPolicyでの自動Port指定
HealthCheckPolicyでチェック先のportを指定するとき、従来であれば手動でport番号を指定していましたが、HealthCheckPolicyでは portSpecification: USE_SERVING_PORT というように動的に稼働中のportを指定できるようになりました。
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: my-healthcheck
namespace: default
spec:
default:
checkIntervalSec: 5
config:
httpHealthCheck:
portSpecification: USE_SERVING_PORT
requestPath: /
type: HTTP
healthyThreshold: 3
timeoutSec: 1
unhealthyThreshold: 2
targetRef:
group: ''
kind: Service
name: my-service
本家リソースの中でいくつか使えない機能があるので注意
例えば、本家 HTTPRoute ではクエリパラメータを使ったルーティングができますが、Gateway Controller実装のHTTPRouteではできません。設計の際に要注意です。実際、queryParamフィルタを指定したHTTPRouteをGKEに反映してみましたが、特にエラーは出ず反映できるものの、ログ上はルーティング制御が発動しないような挙動になっていました。この辺、サポートされていないパラメータを設定したときはエラーが出るようになってくれると嬉しいです...!

同一ドメインに対して複数のHTTPRouteを定義したときの挙動
おさらい : HTTPRoute では、Gateway が受信した HTTP リクエストと HTTPS リクエストを Service に転送する方法を定義します。
同一ドメインに対して複数のHTTPRouteリソースを定義したときは、こちらのドキュメントに載っている条件の優先でマージされます。
- ホスト名のマージ:最も長い、または最も具体的なホスト名と一致
- パスのマージ : 最も長い、または最も具体的なパスと一致。
- ヘッダーのマージ : 一致する HTTP ヘッダーの最大数。
- 競合: 前述の 3 つのルールが優先されない場合、最も古いタイムスタンプの HTTPRoute リソースが使用されます。
例えば、my-domainというドメインに対して次の2つのHTTPRouteがある場合を考えてみます。後者だけ、HTTPヘッダフィルタの数が多いです。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: route-1
namespace: default
spec:
hostnames:
- my-domain
parentRefs:
- kind: Gateway
name: main-gateway
namespace: gateway
rules:
- backendRefs:
- name: some-service
port: 80
weight: 1
matches:
- headers:
- name: x-some-header
type: Exact
value: some-value
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: route-2
namespace: default
spec:
hostnames:
- my-domain
parentRefs:
- kind: Gateway
name: main-gateway
namespace: gateway
rules:
- backendRefs:
- name: some-service-2
port: 80
weight: 1
matches:
- headers:
- name: x-some-header-a
type: Exact
value: some-value-a
- name: x-some-header-b
type: Exact
value: some-value-b
- backendRefs:
- group: ''
kind: Service
name: stable-service
port: 80
weight: 1
これを同じnamespace上に2個展開した場合、実質以下のように解釈されてトラフィックルーティングされます。ホスト名やHTTPヘッダの長さなどに応じた優先順のルーティングは、yamlのファイル分割設計をする際に意識しておくと良さそうです。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
spec:
hostnames:
- my-domain
parentRefs:
- kind: Gateway
name: main-gateway
namespace: gateway
rules:
# route-2の設定値
- backendRefs:
- name: some-service-2
port: 80
weight: 1
matches:
- headers:
- name: x-some-header-a
type: Exact
value: some-value-a
- name: x-some-header-b
type: Exact
value: some-value-b
# route-1の設定値
- backendRefs:
- name: some-service
port: 80
weight: 1
matches:
- headers:
- name: x-some-header
type: Exact
value: some-value
# default route
- backendRefs:
- group: ''
kind: Service
name: stable-service
port: 80
weight: 1
Nodeレベルでのグレースフルシャットダウンを考慮してdrainingTimeoutを設定する
以下のように connectionDraining を指定することで、GKE Node(Compute Engineインスタンス)がシャットダウンされようとしている場合などに、処理が終わるまで待機(ドレイン)させることができます。これはLoadBalancer側で提供されている機能をGCPBackendPolicyの設定で制御できるようにしたものと捉えられますね。クリティカルなバッチ処理などは、connectionDraining とDeploymentの terminationGracePeriodSeconds を組み合わせて使うとより堅牢になりそうです。
apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
name: my-backend-policy
namespace: lb-service-namespace
spec:
default:
connectionDraining:
drainingTimeoutSec: 3600
targetRef:
group: ""
kind: Service
name: lb-service
LBのロギングレートの指定
GCPBackendPolicyリソースの設定で、LBのBackendServiceのロギングレートを設定できます。
rate方式で、1000000が100%で、0が0%という具合に設定できます。最初はこれサンプル値だと思っていたのですが、どうやら固定値で1000000がMAXなようです。
IPv4とIPv6のデュアルスタック構成にする
事前にStatic IPv4とIPv6を予約しておき、以下のようにGatewayに割り当てることでデュアルスタック構成にできます。
また、証明書はcertmapとして作成して networking.gke.io/certmap アノテーションでアタッチできます。Ingress時代はManagedCertificateリソースか、pre-shared-certアノテーションでDNS01チャレンジ認証で作成した証明書を設定するなどの対応が必要でしたが、certmapを適用できるるようになることで、ワイルドカード証明書なども簡単に設定できて楽ですね。
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
annotations:
networking.gke.io/certmap: app
name: main-gateway
namespace: gateway
spec:
addresses:
- type: NamedAddress
value: gateway-controller-ipv4
- type: NamedAddress
value: gateway-controller-ipv6
gatewayClassName: gke-l7-global-external-managed
listeners:
- allowedRoutes:
namespaces:
from: All
name: http
port: 80
protocol: HTTP
- allowedRoutes:
namespaces:
from: All
name: https
port: 443
protocol: HTTPS
Discussion