GKE Gateway controller に HTTP(S) ロードバランサの Gateway class が追加されたので検証してみた
こんにちは。クラウドエースの阿部です。
こちらの記事では、2022年12月16日に GKE Gateway controllerに追加された新しい HTTP(S) ロードバランサの Gateway class について検証してみます。
はじめに
Kubernetes Gateway API と GKE Gateway controller の概要や基本構成については、下記の記事にて説明しておりますので、よろしければご一読ください。
追加された機能について
現在、Google Cloud で使用可能な外部HTTP(S)ロードバランサは3種類ありますが、前述のブログを記述している時点では、GKE Gateway controllerでは以下のの2つのみをサポートしていました。
- グローバル外部 HTTP(S) ロードバランサ (従来)
- リージョナル外部 HTTP(S) ロードバランサ
この2つに加え、2022年12月16日に「グローバル外部 HTTP(S) ロードバランサ」が GKE Gateway controller で利用可能になりました。
「グローバル外部 HTTP(S) ロードバランサ (従来)」と「グローバル外部 HTTP(S) ロードバランサ」は名前こそ似ていますが、機能としては違いがあり、高度なトラフィック管理(トラフィック分割やミラーリング)の機能は従来のロードバランサには存在しない機能です。
(ちなみに、リージョナル外部 HTTP(S) ロードバランサも高度なトラフィック管理の機能を有しています。そのため、本記事で動作確認している手順は同様に使用可能のはずです。)
これらをどのように使っていくかについて説明していきたいと思います。
使い方
環境準備
環境準備の手順ですが、基本的には以前のブログ記事で説明した手順と変わらないため、下記ブログの「GKEクラスタのセットアップ」を読んで頂ければと思います。
以降の手順は、「GKEクラスタのセットアップ」の手順で作成したクラスタがある前提で説明していきます。
手順通り作成すると、以下のリソースが事前にできあがっているはずです。
- グローバル外部IPアドレス
- Cloud Endpoints によるFQDN (グローバル外部IPアドレスに対応)
- マネージド証明書リソース (名前は
gke-gateway-cert
、上記のFQDNに対応) - GKEクラスタ (クラスタのバージョンはv1.24.5以降)
環境準備が完了したら、下記のコマンドを実行してみましょう。
kubectl get gatewayclass
以下のように、 gke-l7-global-external-managed
が表示されていると思います。これが、今回新しく追加されたロードバランサの Gateway class です。
NAME CONTROLLER ACCEPTED AGE
gke-l7-global-external-managed networking.gke.io/gateway True 5h11m
gke-l7-gxlb networking.gke.io/gateway True 5h11m
gke-l7-rilb networking.gke.io/gateway True 5h11m
デモアプリケーションのデプロイ
デモアプリケーションとして、Google公式リファレンスにあるデモアプリケーションをデプロイします。
kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/app/store.yaml
上記コマンド実行により、default Namespace に store-v1
、store-v2
、store-german
DeploymentとServiceがそれぞれデプロイされます。
Gateway リソースのデプロイ
今回デプロイする Gateway リソースのマニフェストは以下の通りです。
gateway.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: external-https
spec:
gatewayClassName: gke-l7-global-external-managed
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
options:
networking.gke.io/pre-shared-certs: gke-gateway-cert
addresses:
- type: NamedAddress
value: gke-gateway-vip
前回のブログ記事との違いとしては、 gatewayClassName に gke-l7-global-external-managed
を指定している点です。これにより、グローバル外部 HTTP(S) ロードバランサは従来ではなく新しい方が使用されます。
上記のマニフェストをデプロイします。
kubectl apply -f gateway.yaml
トラフィック分割の動作を確認
トラフィック分割とは、重み設定に従って複数のサービスにトラフィックを分割して処理する設定です。
Kubernetes Gateway API では、トラフィックの重みを HTTPRoute リソースで設定します。
今回は、カナリアリリースすることを想定して、 store-v1
に95%、 store-v2
に5%のトラフィックを振り分ける設定を行います。
以下のような store.yaml
を作成します。ポイントとしては、 backendRefs
設定に weight
を追加したことです。
store.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: store
spec:
parentRefs:
- kind: Gateway
name: external-https
hostnames:
- "{SERVICENAME}.endpoints.{PROJECT_ID}.cloud.goog"
rules:
- backendRefs:
- name: store-v1
port: 8080
weight: 95
- name: store-v2
port: 8080
weight: 5
※{SERVICENAME}.endpoints.{PROJECT_ID}.cloud.goog
は Cloud Endpoints で設定したFQDNです
上記のマニフェストをデプロイします。
kubectl apply -f store.yaml
※デプロイ後、マネージドSSL証明書やロードバランサの初期化に時間がかかるのでしばらく待ちましょう。
しばらくしたら、下記のコマンドを実行して store-v1
とstore-v2
にトラフィック分割されているかを観測してみます。
下記のコマンドを実行してみます。
for CNT in $(seq 1 1000); do curl -sS https://{SERVICENAME}.endpoints.{PROJECT_ID}.cloud.goog/ | jq -r .metadata; done | sort | uniq -c
※{SERVICENAME}.endpoints.{PROJECT_ID}.cloud.goog
の部分は Cloud Endpoints で作成したFQDN
上記のコマンドは、以下の内容を実行しています。
- Gateway controller で作成したエンドポイントに
curl
コマンドで1000回リクエスト - 実行結果を
jq
コマンドで受け取り、 metadata の部分だけ抜き出す - 抜き出した結果を
sort
コマンドで整列し、uniq -c
コマンドで件数を集計
結果は以下の通りでした。多少ズレはありますが、だいたい95:5の割合でトラフィックが分割されていることが分かりました。
958 store-v1
42 store-v2
トラフィック分割設定の注意事項
HTTPRoute の weight
にどんな値が設定可能かを確認したところ、 0から1000までの数値でした。これは、Google Cloud の URL Map API における weight
の設定範囲と同一です。Gateway controller が裏で URL Map リソースを作成しているので、当たり前と言えば当たり前ですが、設定するときは注意しましょう。
weight
がAPIの設定範囲を超えている場合は、HTTPRoute リソース自体は作成されますが、リソースの内部状態がエラーになります。
なお、各バックエンドの weight
合計値は100や1000に収まっていなくても問題有りませんでした。内部で合計値を計算した上でトラフィックの重みを設定しているようです。ただし、考え無しに設定してしまうと思ったようなトラフィック分割にならない状況に陥りがちになると思いますので、 weight
の合計値は100または1000で設計した方がよいでしょう。
クリーンアップ
次の検証に行く前に、 store
HTTPRoute リソースを削除しておきましょう。
kubectl delete httproute store
トラフィックミラーリングの動作を確認
トラフィックミラーリングとは、あるサービスへのトラフィックを複製して別のサービスにも送信する機能です。例えば本番のトラフィックを使った動作テストをやりたい場合や、トラフィックの監査目的等で使う機能です。
以下のような store-mirror.yaml
を作成します。ポイントとしては、 backendRefs
設定と並列に filters
を追加して、 RequestMirror
を設定したことです。
store-mirror.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: http-filter-mirroring
spec:
parentRefs:
- kind: Gateway
name: external-https
rules:
- backendRefs:
- name: store-v1
port: 8080
filters:
- type: RequestMirror
requestMirror:
backendRef:
name: store-v2
port: 8080
上記のマニフェストをデプロイします。
kubectl apply -f store-mirror.yaml
デプロイ後、以下のコマンドを実行します。
まず、検証でコンテナのログ件数を確認しますが、各DeploymentのPod数は2になっているため件数がうまく数えられません。以下のコマンドで、Pod数を1に変更します。
kubectl scale --replicas=1 deployment store-v1
kubectl scale --replicas=1 deployment store-v2
少し待ってから kubectl get pod
か kubectl get deployment
を実行して store-v1
とstore-v2
のPod数が1つになっていることを確認します。(Pod数が確認できればよいのでどちらのコマンドを実行してもよいです。)
実際にトラフィックを発行するコマンドを実行します。 store-v1
に対して以下のコマンドを実行します。
for CNT in $(seq 1 100); do curl -sS https://{SERVICENAME}.endpoints.{PROJECT_ID}.cloud.goog/ | jq -r .metadata; done | sort | uniq -c; \
kubectl logs deployment/store-v1 --since=10s | grep -c INFO
上記のコマンドは、以下の内容を実行しています。つまり、 curl
の実行件数とログの件数を比較するような操作です。
- Gateway controller で作成したエンドポイントに
curl
コマンドで100回リクエスト - 実行結果を
jq
コマンドで受け取り、 metadata の部分だけ抜き出す - 抜き出した結果を
sort
コマンドで整列し、uniq -c
コマンドで件数を集計 - 集計直後に
kubectl logs
コマンドで過去10秒分のログを抜き出し、grep
で行数を集計
実行結果は以下の通りでした。件数にずれがあるのは、Gateway controllerが設定したロードバランサのヘルスチェックのリクエストが余計に入ったためと思われますが、だいたい一致します。
100 store-v1
103
同様に、以下のコマンドを実行します。前述のコマンドとの違いは、ログ件数の集計対象を store-v2
にした部分です。
for CNT in $(seq 1 100); do curl -sS https://{SERVICENAME}.endpoints.{PROJECT_ID}.cloud.goog/ | jq -r .metadata; done | sort | uniq -c; \
kubectl logs deployment/store-v2 --since=10s | grep -c INFO
実行結果は以下の通りでした。つまり、 store-v1
に送信されたトラフィックと同じ分だけ store-v2
にも送信されていました。
100 store-v1
100
検証結果を比較するため、以下の様に store-mirror.yaml
の filters
行から下を全てコメントアウトして、デプロイします。
store-mirror.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: http-filter-mirroring
spec:
parentRefs:
- kind: Gateway
name: external-https
rules:
- backendRefs:
- name: store-v1
port: 8080
# filters:
# - type: RequestMirror
# requestMirror:
# backendRef:
# name: store-v2
# port: 8080
その後、前述と同様に curl
実行件数とログ件数を比較しましたが、以下のような差が出ました。
当たり前と言えば当たり前ですが、 store-v2
へのトラフィックミラーリングを止めたため、ログ件数はゼロになっています。
100 store-v1
102
100 store-v1
0
クリーンアップ
今回作成した HTTPRoute リソースや、Gateway リソース等を削除しておきましょう。
# HTTPRoute リソース削除
kubectl delete httproute http-filter-mirroring
# Gateway リソース削除
kubectl delete gtw external-https
# デモアプリケーション削除
kubectl delete -f https://raw.githubusercontent.com/GoogleCloudPlatform/gke-networking-recipes/main/gateway/gke-gateway-controller/app/store.yaml
また、必要に応じてGKEクラスタ、グローバル外部IPアドレスリソース等も不要であれば削除しましょう。
その他の課題
トラフィックミラーリング(requestMirror)でバックエンドを初回設定すると設定に失敗する
トラフィックミラーリングですが、まっさらな状態で今回例示した設定を行うと、以下のようなメッセージが出力されて HTTPRoute リソースの設定がエラーになります。
原因は不明ですがおそらくまだ GKE Gateway controller は requestMirror に指定したバックエンドに対応するNEGリソースがない場合、新規作成せずエラー扱いにしてしまうためと考えられます。
今回のケースでは先に store-v2
を通常の backendRefs
で設定してNEGリソースを作るよう誘導してあげないといけないようです。
message: 'error cause: no-error-isolation: Error GWCER103: Cannot find NEGs
for Service Port default/store-v2/8080.'
今回のブログ記事作成では、トラフィック分割の検証を行ってからトラフィックミラーリングの検証を行うと問題無かったのですが、別日にトラフィイクミラーリングのみ検証したら前述のエラーにより難航したため苦労しました。
RedirectやRewrite機能には未対応
グローバル外部 HTTP(S) ロードバランサの機能としては実装されており、Gateway API としても存在自体はあるのですが、 GKE Gateway controller では以下のドキュメントにあるようなリクエストのRedirectやRewriteは使えないようでした。
調査しきれていないだけかも知れませんが、この機能もあると嬉しいので今後に期待です。
まとめ
前回のブログ記事作成から1週間経たずに新しい機能が追加されたので、 GKE Gateway controller は進歩が早いなという印象です。
今後も、新しい機能追加をウォッチしつつ、適宜検証してブログ化できればと思っております。
Discussion