Ingress の進化版 Gateway API を解説する Part 2 (マルチクラスタ編)

Kazuu
google-cloud-jp
Published in
49 min readJan 5, 2022

2022 年 4 月 27 日 追記:
2022 年 3 月末に GKE で Gateway API の v1alpha2 が利用可能になりました。それに伴い本記事の内容も v1alpha2 を前提としたものに更新しています。

重要
2022 年 4 月 27 日時点の既知の問題として、元々 v1alpha1 CRDsをインストールし利用していたクラスタに v1alpha2 CRDs をインストールした場合、新規設定が可能になるまで最大 1 週間程度が掛かる場合があります。元々 v1alpha1 を使っていたクラスタとは別のクラスタを用意して v1alpha2 を試して頂くことを推奨します。

Kubernetes / GKE ファンの皆様、明けましておめでとうございます。Google Cloud の Kazuu (かずー) です。
前回の Part1 では Gateway API の概要とシングルクラスタでの利用方法について解説を行いました。本記事ではマルチクラスタで利用する Gateway API を解説します。

GKE でサポートする Gateway Class

1. Multi-cluster Gateway 概要

Multi-cluster Gateway は 複数の GKE クラスタ間で負荷分散を行う仕組みです。External multi-cluster Gateway は Internet からのリクエストをクラスタ間で負荷分散する、Internal multi-cluster Gateway は内部ホスト発のトラフィックをクラスタ間で負荷分散する用途で用いられます。

GKE における Gateway API の実装としては、以下の通り 2 種類の controller が存在します。どちらも Google が管理しているため、ユーザーからアクセスしたりすることは出来ません。GKE の Control plane と同じ扱いと考えてください。Part1 で解説した External Gateway / Internal Gateway は Single-cluster Gateway controller を裏側で使っていました。今回は Multi-cluster Gateway controller を使うことになります。

GKE における Gateway API の実装

Multi-cluster Gateway では、Single-cluster Gateway と比較して利用手順が少々複雑になります。理由は Multi-cluster Gateway は controller と GKE クラスタ、LB に加えて、Multi-cluster Services という機能に依存するからです。この Multi-cluster Services については別記事で解説していますので、是非読んでみてください。

GKE には既に Multi-cluster Ingress (MCI)というサービスが存在し、Multi-cluster Gateway と同様に “クラスタ間で負荷分散する” ことが可能です。2022 年 4 月 27日現在の MCI と External multi-cluster Gateway との違いについて、以下の通り纏めます。

MCI vs Multi-cluster Gateway

一番の違いはステータスで MCI は GA 、Multi-cluster Gateway は Preview です。GKE がサポートしている Gateway API は v1alpha1 とまだ未成熟なバージョンであり、今後 API 仕様の大幅な変更もありえます。本番環境での利用を前提にすると、GA である MCI を選択する方が現時点では安全かと思います。ただし、MCI にはない機能 (e.g. Internal のクラスタ間負荷分散) を利用したい場合は Multi-cluster Gateway を選択する必要があります。

概要の説明としては以上です。それでは次の章から実際に Multi-cluster Gateway を試していきましょう。

2. 前準備

前提条件はシングルクラスタのものとほぼ同じですが、マルチクラスタ環境の場合、Workload Identity を各 GKE クラスタで有効化する必要があります。もし有効化していない場合はしておきましょう。(手順)

はじめに、 Multi-cluster Gateway に必要な API の有効化を行います。

  • GKE Hub API
  • Multi-cluster Services API
  • Multi-cluster Ingress API
  • Traffic Director API
❯ gcloud services enable \
gkehub.googleapis.com \
multiclusterservicediscovery.googleapis.com \
multiclusteringress.googleapis.com \
trafficdirector.googleapis.com
Operation "operations/acf.p2-605899591260-50a6ec66-c5a4-47d4-be96-2ac8ce779a00" finished successfully.

続いて、 GKE クラスタの準備を行います。クラスタ作成の手順は割愛しますが、以下のような構成を今回は作りました。全て VPC Native クラスタ で regular channel を選択しています。本記事を執筆している時点のバージョンは 1.21.5-gke.1302 です。

準備したクラスタ構成

作成した 3 つのクラスタ を Fleets (a.k.a GKE Hub) へ登録します。Fleets とは GKE 及び Anthos クラスタを集合管理するための機能です。ここでは “GKE のクラスタをグルーピングして Multi-cluster 系の機能を利用するためのもの” と覚えておいて頂ければ十分です。

❯ gcloud container hub memberships register tyo-cluster-01 \
--gke-cluster asia-northeast1/tyo-cluster-01 \
--enable-workload-identity
kubeconfig entry generated for tyo-cluster-01.
Waiting for membership to be created...done.
Created a new membership [projects/kzs-sandbox/locations/global/memberships/tyo-cluster-01] for the cluster [tyo-cluster-01]
Generating the Connect Agent manifest...
Deploying the Connect Agent on cluster [tyo-cluster-01] in namespace [gke-connect]...
Deployed the Connect Agent on cluster [tyo-cluster-01] in namespace [gke-connect].
Finished registering the cluster [tyo-cluster-01] with the Hub.

出力されるログを見てみると登録の過程で Connect Agent がクラスタにデプロイされます、こちらは Fleets によるクラスタの集中管理に必要なものです。後で消さないよう注意してください。

同様に、tyo-cluster-02 / osa-cluster-01 も登録します。

❯ gcloud container hub memberships register tyo-cluster-02 \
--gke-cluster asia-northeast1/tyo-cluster-02 \
--enable-workload-identity
❯ gcloud container hub memberships register osa-cluster-01 \
--gke-cluster asia-northeast2/osa-cluster-01 \
--enable-workload-identity

最後に全クラスタが Fleets に登録されたか確認します。

❯ gcloud container hub memberships list
NAME EXTERNAL_ID
osa-cluster-01 28f055f7-3eb3-4ed7-9955-bd29d4b7513c
tyo-cluster-02 88974e64-96e5-43f4-b9b6-cbc3b9a157d4
tyo-cluster-01 0115b89a-17ac-4af5-939a-ad2f5acba07d

Multi-cluster Services (以降、MCS と呼びます) の有効化を行います。

❯ gcloud container hub multi-cluster-services enable
Waiting for Feature Multi-cluster Services to be created...done.

次に gke-mcs-importer (MCS の内部コンポーネントです) が利用するサービスアカウントに必要な IAM ロールを割り当てます。

❯ gcloud projects add-iam-policy-binding kzs-sandbox \
--member "serviceAccount:kzs-sandbox.svc.id.goog[gke-mcs/gke-mcs-importer]" \
--role "roles/compute.networkViewer"

MCS の設定状況の確認を行います。先程 Fleets に登録した 3 つのクラスタが全て Membership に含まれており、code: OK になっていれば問題ありません。

❯ gcloud container hub multi-cluster-services describe                                                                                                   (tyo-cluster-01/default)
createTime: '2021-12-23T01:00:58.528329842Z'
membershipStates:
projects/605899591260/locations/global/memberships/osa-cluster-01:
state:
code: OK
description: Firewall successfully updated
updateTime: '2021-12-23T01:01:26.345221277Z'
projects/605899591260/locations/global/memberships/tyo-cluster-01:
state:
code: OK
description: Firewall successfully updated
updateTime: '2021-12-23T01:01:42.484271485Z'
projects/605899591260/locations/global/memberships/tyo-cluster-02:
state:
code: OK
description: Firewall successfully updated
updateTime: '2021-12-23T01:01:37.069796597Z'
name: projects/kzs-sandbox/locations/global/features/multiclusterservicediscovery
resourceState:
state: ACTIVE
spec: {}
updateTime: '2021-12-23T01:01:42.680542695Z'

Gateway API CRDs のインストールを行います。Multi-cluster Gateway では、複数クラスタのうち、代表して Gateway API の設定を行う Config cluster を選択する必要があり、今回は tyo-cluster-01 を Config cluster として扱います。こちらの手順は Part1 のシングルクラスタ環境で行ったものと同じなので、実際には実行していませんが、念の為コマンドのみ掲載しておきます。

#tyo-cluster-01 に Gateway API CRDs をインストール# v1alpha2 の CRDs をインストール
❯ kubectl apply -k "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.4.2"
# v1alpha1 の CRDs をインストール
❯ kubectl apply -k "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v0.3.0"

前準備の最後のステップとして、Multi-cluster Gateway controller を有効化します。有効化を行う際、3 つのクラスタのうち任意の 1 つを Config cluster として指定します。前述の通り、今回は tyo-cluster-01 を Config cluster として選択します。この設定はいつでも変更可能です。

❯ gcloud container hub ingress enable --config-membership=/projects/kzs-sandbox/locations/global/memberships/tyo-cluster-01
Waiting for Feature Ingress to be created...done.
Waiting for controller to start......done.

Multi-cluster Gateway controller の設定状況を確認します。Fleets に登録した 3 つのクラスタが確認出来、code が OK になっていれば問題ありません。configMembership も tyo-cluster-01 になっていますね。

❯ gcloud container hub ingress describe
createTime: '2021-12-23T02:22:46.863060373Z'
membershipStates:
projects/605899591260/locations/global/memberships/osa-cluster-01:
state:
code: OK
updateTime: '2021-12-23T02:23:54.972163838Z'
projects/605899591260/locations/global/memberships/tyo-cluster-01:
state:
code: OK
updateTime: '2021-12-23T02:23:54.972163631Z'
projects/605899591260/locations/global/memberships/tyo-cluster-02:
state:
code: OK
updateTime: '2021-12-23T02:23:54.972164038Z'
name: projects/kzs-sandbox/locations/global/features/multiclusteringress
resourceState:
state: ACTIVE
spec:
multiclusteringress:
configMembership: projects/kzs-sandbox/locations/global/memberships/tyo-cluster-01
state:
state:
code: OK
description: Ready to use
updateTime: '2021-12-23T02:23:54.000663808Z'
updateTime: '2021-12-23T02:23:59.492500765Z'

次に Multi-cluster Gateway controller が使うサービスアカウントに必要なロールを割り当てます。アカウントの数字部分はプロジェクトナンバーです。適宜置き換えてください。

❯  gcloud projects add-iam-policy-binding kzs-sandbox \
--member "serviceAccount:service-605899591260@gcp-sa-multiclusteringress.iam.gserviceaccount.com" \
--role "roles/container.admin" \
--project=kzs-sandbox

最後に Config cluster の tyo-cluster-01 で Gateway Class を見てみます。gke-l7-gxlb-mcgke-l7-rilb-mc があることが確認出来ました。

❯ kubectl get gatewayclasses
NAME CONTROLLER AGE
gke-l7-gxlb networking.gke.io/gateway 5d
gke-l7-gxlb-mc networking.gke.io/gateway 2m26s
gke-l7-rilb networking.gke.io/gateway 5d
gke-l7-rilb-mc networking.gke.io/gateway 2m26s

これで、Multi-cluster Gateway を使う準備が整いました!

3. External multi-cluster Gateway を試す

はじめに今回の検証用に、”test” という名前の Namepsace を全クラスタに作成します。

#tyo-cluster-01
❯ kubectl create ns test
namespace/test created
#tyo-cluster-02
❯ kubectl create ns test
namespace/test created
#osa-cluster-01
❯ kubectl create ns test
namespace/test created

次にテストアプリケーション “whereami” をデプロイします。このアプリケーションは Pod が動作しているクラスタ名やリージョン名を返してくれます。Part1 同様、使用しているマニフェストは全てこちらのレポジトリにありますので参考にして頂ければ幸いです。

Replica 数は 2 、Service の type は ClusterIP にしています。
ではデプロイしていきましょう。

❯ git clone git@github.com:kazshinohara/gateway-demo.git
❯ cd gateway-demo/external-multi-cluster-gateway
#tyo-cluster-01
❯ kubectl apply -f test-app.yaml
deployment.apps/whereami created
service/whereami created
#tyo-cluster-02
❯ kubectl apply -f test-app.yaml
deployment.apps/whereami created
service/whereami created
#osa-cluster-01
❯ kubectl apply -f test-app.yaml
deployment.apps/whereami created
service/whereami created

次に MCS の ServiceExport を作成します。こちらも全クラスタに適用します。

#tyo-cluster-01
❯ kubectl apply -f export.yaml
serviceexport.net.gke.io/whereami created
#tyo-cluster-02
❯ kubectl apply -f export.yaml
serviceexport.net.gke.io/whereami created
#osa-cluster-01
❯ kubectl apply -f export.yaml
serviceexport.net.gke.io/whereami created

ServiceExport を作成すると自動的に ServiceImport というリソースが MCS を構成する全クラスタに作成されます。ServiceImport は各クラスタにデプロイした “whereami” Service を纏めて表したリソースです。Multi-cluster Gateway では、この ServiceImport を HTTPRoute からポイントすることで複数クラスタ間での負荷分散を実現しています。

#tyo-cluster-01
❯ kubectl get serviceimport -n test
NAME TYPE IP AGE
whereami ClusterSetIP ["172.16.16.167"] 4m28s

この仕様はむしろ LB の設定を見ると理解しやすいかもしれません。ServiceImport が作成されると、それに対応した Backend Service が作成されています。(ログが長いので一部割愛しています)

❯ gcloud compute backend-services describe gkemcs-test-whereami-tcp-8080 --globalbackends:
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast1-a/networkEndpointGroups/k8s1-1778c3dc-test-whereami-8080-d0b0f2eb
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast1-b/networkEndpointGroups/k8s1-1778c3dc-test-whereami-8080-d0b0f2eb
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast1-c/networkEndpointGroups/k8s1-1778c3dc-test-whereami-8080-d0b0f2eb
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast1-a/networkEndpointGroups/k8s1-aa6cdfd4-test-whereami-8080-d61af54d
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast1-b/networkEndpointGroups/k8s1-aa6cdfd4-test-whereami-8080-d61af54d
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast1-c/networkEndpointGroups/k8s1-aa6cdfd4-test-whereami-8080-d61af54d
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast2-a/networkEndpointGroups/k8s1-c093cc3a-test-whereami-8080-84dd9ce5
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast2-b/networkEndpointGroups/k8s1-c093cc3a-test-whereami-8080-84dd9ce5
maxRate: 10
- balancingMode: RATE
capacityScaler: 1.0
group: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/zones/asia-northeast2-c/networkEndpointGroups/k8s1-c093cc3a-test-whereami-8080-84dd9ce5
maxRate: 10
healthChecks:
- https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/healthChecks/gkemcs-test-whereami-tcp-8080
id: '2979123116537640180'
kind: compute#backendService
loadBalancingScheme: INTERNAL_SELF_MANAGED
name: gkemcs-test-whereami-tcp-8080
port: 8080
portName: http
protocol: HTTP
selfLink: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkemcs-test-whereami-tcp-8080
sessionAffinity: NONE
timeoutSec: 30

1 つの Backend Service に合計 9 つの Network Endpoint Group(NEG) が Backend として設定されています。これは今回使っている 3 つの GKE クラスタ * 各リージョンの 3 zone の NEG を意味します。
つまり、MCS を使って何をしているのかというと、複数クラスタに存在する Service を束ねて 1 つの Backend Service として LB の URL Maps から転送先として設定することが出来るようにしているということです。

補足: 厳密にはこの Backend Services をそのまま LB で使う訳ではありません。HTTPRoute を作成したタイミングで適切な loadBalancingScheme (External or Internal)を指定した Backend Service が別途作成され使われます。

Part 1でも説明しましたが、Gateway API では HTTP Route を使って LB の URL Maps を設定しています。何となくイメージがついたでしょうか?

次に、マネージド SSL 証明書を作成します。
今回は x-mc-gw.gcpx.org というドメインネームを使います。

❯ gcloud compute ssl-certificates create x-mc-gw-cert \
--domains=x-mc-gw.gcpx.org \
--global
Created [https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/sslCertificates/x-mc-gw-cert].
NAME TYPE CREATION_TIMESTAMP EXPIRE_TIME MANAGED_STATUS
x-mc-gw-cert MANAGED 2021-12-29T13:23:40.589-08:00 PROVISIONING
x-mc-gw.gcpx.org: PROVISIONING

続いて、IP Address の予約をします。

❯ gcloud compute addresses create x-mc-gw-ip --global
Created [https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/addresses/x-mc-gw-ip].
❯ gcloud compute addresses list
NAME ADDRESS/RANGE TYPE PURPOSE NETWORK REGION SUBNET STATUS
x-mc-gw-ip X.X.X.X EXTERNAL RESERVED

DNS サーバーに A レコードの登録を行います。

Gateway のマニフェストを以下の通り作成します。Gateway Class は gke-l7-gxlb-mc を指定します。また、先程作成した SSL 証明書 ”x-mc-gw-cert” と予約した IP Address “x-mc-gw-ip” の名前を指定します。

作成した Gateway のマニフェストを Config cluster の tyo-cluster-01 に適用します。

#tyo-cluster-01
❯ kubectl apply -f v1alpha2/x-mc-gateway.yaml
gateway.gateway.networking.k8s.io/external-multi-cluster-gateway create

DNS の登録と Gateway の作成 (LB 的には Forwarding Rule と Target Proxy の作成) からしばらくすると、証明書のステータスが ACTIVE になります。

❯ gcloud compute ssl-certificates list                                                                                                                      (tyo-cluster-01/test)
NAME TYPE CREATION_TIMESTAMP EXPIRE_TIME MANAGED_STATUS
x-mc-gw-cert MANAGED 2021-12-29T13:23:40.589-08:00 2022-03-29T13:23:42.000-07:00 ACTIVE
x-mc-gw.gcpx.org: ACTIVE

HTTPRoute を作成します。今回は以下のようなルーティング設定を行いました。デフォルトで “whereami” ServiceImport へ転送という内容になります。

Config cluster の tyo-cluster-01 にマニフェストを適用します。

#tyo-cluster-01
❯ kubectl apply -f v1alpha2/x-mc-gw-httproute.yaml
httproute.gateway.networking.k8s.io/external-multi-cluster-httproute created

動作確認をします。テストアプリケーションの /cluster パスにアクセスすると当該 Pod が動いているクラスタ名を返します。

# 東京のクライアントからアクセス
❯ while true; do curl -s https://x-mc-gw.gcpx.org/cluster | jq; sleep 1; done
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
^C
# 大阪のクライアントからアクセス
❯ while true; do curl -s https://x-mc-gw.gcpx.org/cluster | jq; sleep 1; done
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
^C

東京のクライアントからは asia-northeast1 region の tyo-cluster-01 クラスタへ、大阪のクライアントからは asia-northeast2 region の osa-cluster-01 へそれぞれアクセスしていることが分かります。External multi-cluster Gateway では External HTTP(S) LB を利用しているため、クライアントからのリクエストは近接リージョンの Backend に転送されます。期待通りの挙動を確認出来ました。

External multi-cluster Gateway による近接リージョンへの転送

何らかの理由により osa-cluster-01 の Pod が 0 になったり、クラスタ自体が落ちてしまった場合には、大阪のクライアントからのリクエストは tyo-cluster-01 もしくは tyo-cluster-02 へ転送されることになります。(逆もまた然りです。) こちらも試してみましょう。

まず osa-cluster-01 の whereami Deployment の replica 数を 2 から 0 に変更します。

❯ kubectl edit deployment whereami
deployment.apps/whereami edited

次に大阪のクライアントからアクセスしてみます。すると、tyo-cluster-01 もしくは tyo-cluster-02 に転送されていることが分かります。大阪のクライアントの近接リージョンである asia-northeast2 に存在する osa-cluster-01 には LB の Backend となる Pod が存在しないため、asia-northeast1 のクラスタに Failover しているような挙動ですね。

# 大阪のクライアントからアクセス
❯ while true; do curl -s https://x-mc-gw.gcpx.org/cluster | jq; sleep 1; done
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
^C
External multi-cluster Gateway によるリージョン間 Failover

全てのクラスタに同じアプリケーションが動いている場合は上述の設定で良いですが、明示的にアクセス先のクラスタやサービスを分けたいケースもあるかと思います。その場合、どのように設定を行えば良いでしょうか?試してみましょう。

まず、既存のテストアプリケーションのService と Deployment 、更に ServiceExport を全クラスタから削除します。

#tyo-cluster-01
❯ kubectl delete -f test-app.yaml
deployment.apps "whereami" deleted
service "whereami" deleted
❯ kubectl delete -f export.yaml
serviceexport.net.gke.io "whereami" deleted
#tyo-cluster-02
❯ kubectl delete -f test-app.yaml
deployment.apps "whereami" deleted
service "whereami" deleted
❯ kubectl delete -f export.yaml
serviceexport.net.gke.io "whereami" deleted
#osa-cluster-01
❯ kubectl delete -f test-app.yaml
deployment.apps "whereami" deleted
service "whereami" deleted
❯ kubectl delete -f export.yaml
serviceexport.net.gke.io "whereami" deleted

Config cluster の tyo-cluster-01 から既存の HTTPRoute を削除します。

#tyo-cluster-01
❯ kubectl delete -f v1alpha2/x-mc-gw-httproute.yaml
httproute.gateway.networking.k8s.io "external-multi-cluster-httproute" deleted

次に、クラスタを識別出来るかたちで Service / Deployment を作成します。例として、tyo-cluster-01 のものは以下の通りです。Service 名などの prefix として tyo1- を入れています。

これを各クラスタ毎にデプロイします。

#tyo-cluster-01
❯ kubectl apply -f tyo1-test-app.yaml
deployment.apps/tyo1-whereami created
service/tyo1-whereami created
#tyo-cluster-02
❯ kubectl apply -f tyo2-test-app.yaml
deployment.apps/tyo2-whereami created
service/tyo2-whereami created
#osa-cluster-01
❯ kubectl apply -f osa1-test-app.yaml
deployment.apps/osa1-whereami created
service/osa1-whereami created

次に ServiceExport も各クラスタ毎に作成します。例として、tyo-cluster-01 のものは以下の通りです。

#tyo-cluster-01
❯ kubectl apply -f tyo1-export.yaml
serviceexport.net.gke.io/tyo1-whereami created
#tyo-cluster-02
❯ kubectl apply -f tyo2-export.yaml
serviceexport.net.gke.io/tyo2-whereami created
#osa-cluster-01
❯ kubectl apply -f osa1-export.yaml
serviceexport.net.gke.io/osa1-whereami created

最後に HTTPRoute です。Header based routing を使って以下の通りルーティングします。

  • target: tyo1 なら tyo-cluster-01 へ転送
  • target: tyo2 なら tyo-cluster-02 へ転送
  • target: osa1 なら osa-cluster-01 へ転送

マニフェストは以下の通りです。

Config cluster の tyo-cluster-01 に適用します。LB への反映には数分掛かります、ご注意を。

#tyo-cluster-01
❯ kubectl apply -f v1alpha2/x-mc-gw-httproute-hbr.yaml
httproute.gateway.networking.k8s.io/external-multi-cluster-httproute-hbr created

私の手元の端末(東京都内)から動作確認をしてみます。

❯ while true; do curl -s -H "target:tyo1" https://x-mc-gw.gcpx.org/cluster | jq; sleep 1; done 
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
^C

❯ while true; do curl -s -H "target:tyo2" https://x-mc-gw.gcpx.org/cluster | jq; sleep 1; done
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
^C

❯ while true; do curl -s -H "target:osa1" https://x-mc-gw.gcpx.org/cluster | jq; sleep 1; done
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
{
"cluster": "osa-cluster-01"
}
^C

期待通り Header “target” の値に応じて転送先を制御することが出来ました。尚、Multi-cluster Ingress では Header based routing は対応していません。External multi-cluster Gateway を選択する理由になりそうですね。

External multi-cluster Gateway による Header based routing

ちなみに Header を指定しないリクエストを投げると以下のような結果となります。

❯ curl -s https://x-mc-gw.gcpx.org/cluster
default backend - 404

これはデフォルトで kube-system Namespace にデプロイされている “gw-serve404” Service が返しているレスポンスです。HTTPRoute で定義されたルーティングルールにマッチしない場合、リクエストはこちらに転送されます。

❯ kubectl get svc -n kube-system | grep gw
gw-serve404 ClusterIP None <none> 80/TCP 14d
❯ kubectl get deployment -n kube-system | grep gw
gw-serve404 1/1 1 1 14d

これは LB に設定されている URL-Map でも確認出来ます。 defaultService として、 kube-system-gw-serve404 という文字列が入った Backend Service が指定されていることが分かるかと思います。

#一部割愛
❯ gcloud compute url-maps describe gkemcg-test-external-multi-cluster-gateway-ki6web2ry82m --global

hostRules:
- hosts:
- x-mc-gw.gcpx.org
pathMatcher: hostn828ljd75u0qu9nkbura3plt788xliz0
id: '306644987219635371'
kind: compute#urlMap
name: gkemcg-test-external-multi-cluster-gateway-ki6web2ry82m
pathMatchers:
- defaultService: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkemcg-kube-system-gw-serve404-80-7cq0brelgzex
name: hostn828ljd75u0qu9nkbura3plt788xliz0
routeRules:
- matchRules:
- headerMatches:
- exactMatch: tyo1
headerName: target
prefixMatch: /
priority: 1
service: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkemcg-test-tyo1-whereami-8080-wc1x0qr67kvi
- matchRules:
- headerMatches:
- exactMatch: tyo2
headerName: target
prefixMatch: /
priority: 2
service: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkemcg-test-tyo2-whereami-8080-fz3hduf2qojo
- matchRules:
- headerMatches:
- exactMatch: osa1
headerName: target
prefixMatch: /
priority: 3
service: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/backendServices/gkemcg-test-osa1-whereami-8080-endjwsgdz0l2

4. Internal multi-cluster Gateway を試す

注意:
Internal HTTP(S) LB を利用する場合、予め、Proxy-only subnets (LBの実態として動く Proxy が利用)を用意しておく必要があります。詳しくは
こちらをご確認ください。以下の手順は Proxy-only subnets が GKE クラスタが存在する VPC の Region に作成済みであることが前提となります。

この章では tyo-cluster-01 と tyo-cluster-02 を使って Internal multi-cluster Gateway を試します。Internal multi-cluster Gateway は同一リージョンのクラスタのみをサポートします。これは Internal HTTP(S) LB が同一リージョンの Backend のみをサポートする仕様のためです。

はじめに Internal muti-cluster Gateway で使う IP Address を予約します。tyo-cluster-01 の Node が所属する subnet から払い出します。

❯ gcloud compute addresses create i-mc-gw-ip \
--region asia-northeast1 \
--subnet subnet-05
Created [https://www.googleapis.com/compute/v1/projects/kzs-sandbox/regions/asia-northeast1/addresses/i-mc-gw-ip].
❯ gcloud compute addresses describe i-mc-gw-ip --region asia-northeast1
address: 192.168.5.36
addressType: INTERNAL

次に Gateway のマニフェストを以下の通り作成します。前のステップで作成した IP Address の名前を指定しておきます。

作成した Gateway のマニフェストを Config cluster である tyo-cluster-01 に適用します。

❯ cd ../internal-multi-cluster-gateway#tyo-cluster-01
❯ kubectl apply -f v1alpha2/i-mc-gateway.yaml
gateway.gateway.networking.k8s.io/internal-multi-cluster-gateway created

最後に HTTPRoute を作成します。ちょうど External multi-cluster Gateway の検証でデプロイしたワークロードがそのまま残っているので、Internal multi-cluster Gateway でも Header based routing を試してみましょう。

❯ kubectl apply -f v1alpha2/i-mc-gw-httproute-hbr.yaml
httproute.gateway.networking.k8s.io/internal-multi-cluster-httproute-hbr created

asia-northeast1 にデプロイした踏み台サーバー(GCE の Instance) から動作確認をします。宛先のアドレスは先程予約した 192.168.5.36 になります。Host Header を忘れずに付けましょう。

#tyo-cluster-01 へ Header を指定してアクセス
❯ while true; do curl -s -H "Host:i-mc-gw.gcpx.org" -H "target:tyo1" http://192.168.5.36/cluster | jq; sleep 1; done
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-01"
}
^C
#tyo-cluster-02 へ Header を指定してアクセス
❯ while true; do curl -s -H "Host:i-mc-gw.gcpx.org" -H "target:tyo2" http://192.168.5.36/cluster | jq; sleep 1; done
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
^C

期待通り Internal multi-cluster Gateway で Header based routing を行うことが出来ました。

Internal multi-cluster Gateway による Header based routing

Internal multi-cluster Gateway では Traffic split が可能なので試してみます。

はじめに、前の項で使った HTTPRoute を Config cluster の tyo-cluster-01 から削除します。

#tyo-cluster-01
❯ kubectl delete -f v1alpha2/i-mc-gw-httproute-hbr.yaml
httproute.gateway.networking.k8s.io "internal-multi-cluster-httproute-hbr" deleted

テストアプリケーションはそのまま使えるので弄らずに、HTTPRoute のみ以下の通り作成します。tyo-cluster-01 に 10%、tyo-cluster-02 に 90% のリクエストを転送する Traffic split を設定しています。

上記マニフェストを適用します。

❯ kubectl apply -f v1alpha2/i-mc-gw-httproute-split.yaml
httproute.gateway.networking.k8s.io/internal-multi-cluster-httproute-split created

先程と同じ踏み台サーバー(GCE の Instance) から動作確認をします。10% の確率で tyo-cluster-01 へ転送されています。

❯ while true; do curl -s -H "Host:i-mc-gw.gcpx.org" http://192.168.5.36/cluster | jq; sleep 1; done
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-02"
}
{
"cluster": "tyo-cluster-01"
}
{
"cluster": "tyo-cluster-02"
}

期待通り クラスタ間で Traffic split 出来ることが確認出来ました。

Internal multi-cluster Gateway による Traffic split

色々なユースケースが思い付きますが、クラスタのバージョンアップグレード時に新旧クラスタ間での Blue / Green や Canary に使うと便利そうです。願わくば External multi-cluster Gateway でも Traffic split が使えれば良いのですが、それは今後にご期待頂ければと思います。

5. まとめ

最後までお読み頂きありがとうございました。Part1 からだと結構な長丁場だったかと思いますmm

Multi-cluster Gateway は如何でしたでしょうか? 途中に出てきた Multi-cluster Services が少しとっつきにくいかもしれませんが、慣れると自然に Service を Export してるので大丈夫です。Multi-cluster Services については、別記事で解説していますので、是非読んでみてください。

Multi-cluster Gateway が今後成熟してくると GKE 周りのアーキテクチャや運用のベストプラクティスも徐々に変わってくることが想定されます。私も引き続き最新情報をキャッチアップして、皆様になるべく分かりやすく共有出来るようがんばります。

2022 年の GKE にご期待ください!!

--

--

Kazuu
google-cloud-jp

Customer Engineer at Google Cloud Japan. GKE & Cloud Run enthusiast. Opinions are my own, NOT views of my employer.