Multi-cluster Services を解説する

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

Kubernetes / GKE ファンの皆様、こんにちは。Google Cloud の Kazuu (かずー) です。
本記事では Multi-cluster Services の概要と GKE での使い方の解説を行います。色んな使い道がある機能なので是非試してみてください!

TL; DR

  • Multi-cluster Services は 複数クラスタ間でのサービスディスカバリー及びサービス呼び出しを実現する機能です。
  • 可用性の向上、用途毎のクラスタ分け、クラスタ間でのサービス共有、クラスタ間移行などのユースケースに有用です。
  • GKE での実装が先行していますが、OSS の Kubernetes でも Multi-cluster Services の API 仕様が提案されています。(KEP-1645)
  • Multi-cluster Gateway を利用する上で前提となる機能です。Multi-cluster Gateway については別記事で解説していますので、併せてご覧ください。

1. Multi-cluster Services 概要

Multi-cluster Services (以降、MCSと略します) とは複数クラスタ間でのサービスディスカバリー及びサービスの呼び出しを実現する機能です。GKE では 2021 年 2 月 17 日に GKE バージョン 1.17 以降で GA となっています。

そもそも、Kubernetes (GKE) における Serviceクラスタのローカルでのみ有効なサービスディスカバリーと呼び出し用の VIP (ClusterIP) を提供します。例えばクラスタ A に存在する Pod 1 から同クラスタの Namespace “test” に存在する Service “my-svc” を呼び出す場合 my-svc.test.svc.cluster.localというドメイン名で名前解決を行い、そのクエリの回答として ClusterIP が返ってきます。Pod 1 はその ClusterIP にアクセスすると、Service “my-svc” のいずれかの Pod に転送されるという仕組みです。

一方、MCS では複数クラスタ間で有効なサービスディスカバリーと呼び出し用の VIP (ClusterSetIP) を提供します。Service のドメイン名のサフィックスは clusterset.local となります。例えば、例えばクラスタ A、B、C の 3 クラスタで MCS を構成し、クラスタ A に存在する Pod 1 から、クラスタ B と C 両方のNamespace “test”に存在するService “my-svc” を呼び出す場合、my-svc.test.svc.clusterset.localというドメイン名で名前解決を行い、そのクエリの回答として ClusterSetIP が返ってきます。Pod 1はその ClusterSetIP にアクセスすると、クラスタ B もしくは C の Service “my-svc” のいずれかの Pod に転送されます。この他、MCS は ClusterSetIP とは別に Headless Service もサポートしており、クエリの回答として宛先 Service の 全ての Pod の IP Address が返ってきます。

MCS は以下のようなユースケースがある場合に有用です。

  • 複数クラスタで高可用性・高キャパシティ構成を組む
    複数クラスタで冗長構成を組み可用性とキャパシティの高めつつ、MCS による統一されたサービスディスカバリーにより、複数クラスタを一体的に運用することが可能です。クラスタはリージョンを跨ぐかたちでも構成出来ます。
  • 用途毎にクラスタを分ける
    相互通信が必要だが、運用主体や性質が異なるサービスが複数存在した場合に、クラスタを分けつつも、MCS によりサービス間の相互通信はあたかも同一クラスタ上で行われているかの如く運用出来ます。
  • サービスの共有
    ミドルウェアや認証系など複数のアプリケーションが利用する共有サービスを専用クラスタに配置し、MCS を利用し各利用者側サービスのクラスタに共有サービスを公開することが可能です。
  • クラスタ 移行
    クラスタのアップグレード時など、旧クラスタから新クラスタへ移行を行う際に、MCS を使うことでシームレスな移行に役立ちます。

MCS 自体にはコストは掛かりませんが、内部的に利用している Cloud DNS のコストが掛かります。(どのような使われ方をしているかは後述します) また、Traffic Director も利用しますが、こちらはコストは掛かりません。

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

次に MCS を利用するための API リソースを説明します。

MCS 概要図
  • ServiceExport
    自クラスタの Service (上の図では “my-svc”) を Fleets に登録された他のクラスタに Export (公開) するためのリソースです。
  • ServiceImport
    ServiceExport された際に Fleets に登録されたクラスタにそれぞれ自動的に作成されるリソースです。Export された Service への接続情報 (ClusterSetIP など)が含まれます。ServiceExport したクラスタ自体にも作成されます。

ユーザーが意識して作成するのは ServiceExport のみです。MCS を構成した他のクラスタに自クラスタの Service を公開したい場合に作成します。

2. 主な前提条件と推奨事項

主な前提条件と推奨事項は以下の通りです。

  • VPC Native クラスタのみで利用可能です。
  • クラスタ同士のネットワーク接続性が必要です。MCS を構成する全クラスタが同一 VPC に存在していれば問題ありません。一部クラスタが別 VPC に存在する場合は予め VPC peering されている必要があります。
  • default 及び kube-systemNamespace からの Export は出来ません。
  • Export された Service と同じ Namespace が Import 先のクラスタに存在しない場合、Service は 当該クラスタに Import されません。
  • 各 Service は最大 5 クラスタまで安全に Export することが可能です。
  • Export する各 Service につき 250 Pods 以下に留めることが安全に利用頂く上での推奨値です。これは通常の Service (MCSではない) でも同様です。
  • Export する各 Service あたり、公開する port 番号(e.g. 443, 80)が 50 個以下になることを推奨します。Export 元のクラスタが複数あっても同じ Namespace かつ Service 名 であれば、1 つの Service としてカウントされます。

それではここから実際に MCS を触っていきましょう!

3. 前準備

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

  • GKE Hub API
  • Cloud DNS
  • Traffic Director API
  • Resource Manager API
  • Multi-cluster Services API
❯ gcloud services enable \
gkehub.googleapis.com \
dns.googleapis.com \
trafficdirector.googleapis.com \
cloudresourcemanager.googleapis.com \
multiclusterservicediscovery.googleapis.com
Operation "operations/acf.p2-605899591260-58762d1f-1d33-440d-808b-ba97e7a29f08" finished successfully.

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

準備したクラスタ構成

作成した 3 つのクラスタ を Fleets へ登録します。

❯ 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

続いて 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 のステータスを確認します。state が ACTIVE になっていて、各クラスタの code が OK になっていれば問題ありません。

❯ gcloud container hub multi-cluster-services describe
createTime: '2021-12-27T20:19:52.273169362Z'
membershipStates:
projects/605899591260/locations/global/memberships/osa-cluster-01:
state:
code: OK
description: Firewall successfully updated
updateTime: '2021-12-27T20:20:33.576296597Z'
projects/605899591260/locations/global/memberships/tyo-cluster-01:
state:
code: OK
description: Firewall successfully updated
updateTime: '2021-12-27T20:21:00.161023493Z'
projects/605899591260/locations/global/memberships/tyo-cluster-02:
state:
code: OK
description: Firewall successfully updated
updateTime: '2021-12-27T20:20:44.245939597Z'
name: projects/kzs-sandbox/locations/global/features/multiclusterservicediscovery
resourceState:
state: ACTIVE
spec: {}
updateTime: '2021-12-27T20:21:00.313406079Z'

これで MCS を試す準備が出来ました。

4. MCS を試してみる

はじめに、tyo-cluster-01 と osa-cluster-01 クラスタにテストアプリケーション “whereami” をデプロイします。このアプリケーションは Pod が動作しているクラスタ名やリージョン名を返してくれます。tyo-cluster-02 は接続性を確認するためのクライアントを動かすクラスタとして使います。構成のイメージは以下の通りです。

MCS を試す構成

“test” という名前の Namespace を全クラスタに作成しておきます。併せて、以下のマニフェストを tyo-cluster-01 と osa-cluster-01 に適用します。Pod の replica 数は 2 としています。

#tyo-cluster-01
❯ kubectl create ns test
namespace/test created
❯ kubectl apply -f whereami.yaml
deployment.apps/whereami created
service/whereami created
#osa-cluster-01
❯ kubectl create ns test
namespace/test created
❯kubectl apply -f whereami.yaml
deployment.apps/whereami created
service/whereami created
#tyo-cluster-02
#同じNamespaceが存在しないとServiceがImportされないため
❯ kubectl create ns test
namespace/test created

次に tyo-cluster-01 と osa-cluster-01 の whereami service を Export します。マニフェストは以下の通りです。name は Export したい Service と同じ名前にしてください。namespace も同じものを指定します。

# tyo-cluster-01
❯ 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

初回の sync には少し時間掛かります。約 5 分程度で完了するはずです。

tyo-cluster-02 で状況を見てみましょう。まずは tyo-cluster-01 と osa-cluster-01 で Export された Service “whereami” が Import され、ServiceImport が生成されていることを確認します。ClusterSetIP も出来ていますね。

# osa-cluster-02
❯ kubectl get serviceimport -n test
NAME TYPE IP AGE
whereami ClusterSetIP ["172.16.31.201"] 6m38s

次に Endpoints を見てみます。4 つの Endpoints (Pods) が存在しています。

# osa-cluster-02
❯ kubectl get endpoints
NAME ENDPOINTS AGE
gke-mcs-isu46k25p2 10.5.0.10:8080,10.5.1.13:8080,10.6.1.6:8080 + 1 more... 9m1s
# osa-cluster-02
❯ kubectl describe endpoints gke-mcs-isu46k25p2
Name: gke-mcs-isu46k25p2
Namespace: test
Labels: app.kubernetes.io/managed-by=gke-mcs-importer
Annotations: <none>
Subsets:
Addresses: 10.5.0.10
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 8080 TCP
Addresses: 10.5.1.13
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 8080 TCP
Addresses: 10.6.1.6
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 8080 TCP
Addresses: 10.6.0.9
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 8080 TCP
Events: <none>

これらの Endpoints は tyo-cluster-01 と osa-cluster-01 にデプロイしたテストアプリケーションの Pod の IP Address であることが分かります。

#tyo-cluster-01
❯ kubectl get pods -o json | jq '.items[].status.podIP'
"10.5.0.10"
"10.5.1.13"
#osa-cluster-01
❯ kubectl get pods -o json | jq '.items[].status.podIP'
"10.6.0.9"
"10.6.1.6"

それでは tyo-cluster-02 に Client pod を配置して 、MCS を通じて tyo-cluster-01 及び osa-cluster-01 の wherami Service にアクセスしてみましょう。テストアプリケーションの /cluster パスにアクセスすると当該 Pod が動いているクラスタ名を返し、 /hostname パスにアクセスすると当該 Pod のホスト名を返します。

## 以下のステップは全て tyo-cluster-02 で実施しています## curl が使える busybox を Client pod として起動
❯ kubectl run -it --rm=true busybox --image=yauritux/busybox-curl --restart=Never
If you don't see a command prompt, try pressing enter.
/home #
## MCS のドメイン名が名前解決出来るかの確認、ClusterSetIP が返ってくる
/home # nslookup whereami.test.svc.clusterset.local
Server: 172.16.28.10
Address 1: 172.16.28.10 kube-dns.kube-system.svc.cluster.local
Name: whereami.test.svc.clusterset.local
Address 1: 172.16.31.201 gke-mcs-isu46k25p2.test.svc.cluster.local
## whereami の /cluster に 1 秒ごとにアクセス
/home # while true; do curl -s http://whereami.test.svc.clusterset.local:8080/cluster && echo ; sleep 1; done
{"cluster":"osa-cluster-01"}
{"cluster":"tyo-cluster-01"}
{"cluster":"osa-cluster-01"}
{"cluster":"tyo-cluster-01"}
{"cluster":"osa-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":"osa-cluster-01"}
{"cluster":"osa-cluster-01"}
{"cluster":"tyo-cluster-01"}
{"cluster":"osa-cluster-01"}
{"cluster":"tyo-cluster-01"}
^C
## whereami の /hostname に 1 秒ごとにアクセス
/home # while true; do curl -s http://whereami.test.svc.clusterset.local:8080/hostname && echo ; sleep 1; done
{"hostname":"whereami-86769c7ffb-pcj7b"}
{"hostname":"whereami-86769c7ffb-pcj7b"}
{"hostname":"whereami-86769c7ffb-pcj7b"}
{"hostname":"whereami-86769c7ffb-pcj7b"}
{"hostname":"whereami-86769c7ffb-cbgfd"}
{"hostname":"whereami-86769c7ffb-pcj7b"}
{"hostname":"whereami-86769c7ffb-s8fnf"}
{"hostname":"whereami-86769c7ffb-cbgfd"}
{"hostname":"whereami-86769c7ffb-cbgfd"}
{"hostname":"whereami-86769c7ffb-cbgfd"}
{"hostname":"whereami-86769c7ffb-jzlrc"}
{"hostname":"whereami-86769c7ffb-jzlrc"}
{"hostname":"whereami-86769c7ffb-cbgfd"}
{"hostname":"whereami-86769c7ffb-s8fnf"}
{"hostname":"whereami-86769c7ffb-pcj7b"}
{"hostname":"whereami-86769c7ffb-s8fnf"}
{"hostname":"whereami-86769c7ffb-s8fnf"}
{"hostname":"whereami-86769c7ffb-cbgfd"}
{"hostname":"whereami-86769c7ffb-cbgfd"}
{"hostname":"whereami-86769c7ffb-s8fnf"}
{"hostname":"whereami-86769c7ffb-pcj7b"}
{"hostname":"whereami-86769c7ffb-s8fnf"}
^C

ご覧の通り MCS の ClusterSetIP を通じて、tyo-cluster-02 の Client pod から tyo-cluster-01 及び tyo-cluster-02 の wherami Service にアクセスすることが出来ました。リクエストはリージョン単位でも Pod 単位でもランダムに分散されていることが分かります。この挙動は通常の ClusterIP と同じですね。

またお気付きの方もいらっしゃると思いますが、クラスタ間を跨ぐ通信にも関わらず VPC Firewall で通信を許可する設定は明示的に行っていません。実はMCS は自動的に VPC Firewall の設定をしてくれるのですが、こちらの仕組みについては後の章で説明させて頂きます。

次に osa-cluster-01 の Pod の数が 0 になった場合を試してみましょう。期待値としては、ClusterSetIP に紐付いた endpoints 情報が更新され、tyo-cluster-01 の Pod にのみアクセスが飛ぶはずです。

まず osa-cluster-01 の whereami deployment の replica 数をゼロにします。

#osa-cluster-01
#replica 2 -> 0 へ変更
❯ kubectl edit deployment whereami -n test
deployment.apps/whereami edited
#replica 数が実際に 0 になったことを確認
❯ kubectl get deployment -n test
NAME READY UP-TO-DATE AVAILABLE AGE
whereami 0/0 0 0 80m

続いて tyo-cluster-02 から動作確認を行います。期待値通り Endpoints から osa-cluster-01 のものが消えています。

# tyo-cluster-02
❯ kubectl get endpoints -n test
NAME ENDPOINTS AGE
gke-mcs-isu46k25p2 10.5.0.10:8080,10.5.1.13:8080 66m

念の為、Client pod からアクセス確認を行います。期待値通り tyo-cluster-01 にのみアクセスしています。

# tyo-cluster-02
/home # while true; do curl -s http://whereami.test.svc.clusterset.local:8080/cluster && echo ; 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"}
^C

5. MCS で Headless Service を試してみる

前述の通り MCS では Service 呼び出し用の VIP を提供する ClusterSetIP に加えて、Headless Service をサポートしています。はじめに tyo-cluster-01 及び osa-cluster-01 から一度、前の章で作成したリソースを削除します。

#tyo-cluster-01
❯ kubectl delete serviceexport whereami
serviceexport.net.gke.io "whereami" deleted
❯ kubectl delete service whereami
service "whereami" deleted
❯ kubectl delete deployment whereami
deployment "whereami" deleted
#osa-cluster-01
❯ kubectl delete serviceexport whereami
serviceexport.net.gke.io "whereami" deleted
❯ kubectl delete service whereami
service "whereami" deleted
❯ kubectl delete deployment whereami
deployment "whereami" deleted

次に、Headless Service 用のマニフェストを両クラスタに適用し、改めて ServiceExport (マニフェストは前に使ったものと同じです)を作成します。clusterIPNone になっているところがポイントです。Pod のホスト名から名前解決が出来ることを確認したいので、ワークロードは Deployment ではなく、StatefulSet にしています。

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

それでは tyo-cluster-02 からどう見えるのか確認していきましょう。先程と同様に ServiceImport が作成されていますが、TYPE が ClusterSetIP ではなく、Headless となっています。紐づくEndpoints もありません。

#tyo-cluster-02
❯ kubectl get serviceimport -n test
NAME TYPE IP AGE
whereami Headless 6m44s
❯ kubectl get endpoints -n test
No resources found in test namespace.

それでは Client pod からの挙動を確認します。Headless Service なので、ドメイン名 whereami.test.svc.clusterset.local を正引きすると、全て Pod (tyo-cluster-01とosa-cluster-01の全ての wherami pods) の IP Address が返ってきます。curl によるアクセスはキャッシュが効いているようで 1 つの Pod にのみアクセスし、残念ながら 分散されませんが、クライアントの実装 (e.g. gRPC)によっては DNS ラウンドロビンによる分散も可能です。

#tyo-cluster-02
❯ kubectl run -it --rm=true busybox --image=yauritux/busybox-curl --restart=Never
If you don't see a command prompt, try pressing enter.
/home # nslookup whereami.test.svc.clusterset.local
Server: 172.16.28.10
Address 1: 172.16.28.10 kube-dns.kube-system.svc.cluster.local
Name: whereami.test.svc.clusterset.local
Address 1: 10.5.0.10
Address 2: 10.6.0.10
Address 3: 10.6.1.7
Address 4: 10.5.1.13
/home # while true; do curl -s http://whereami.test.svc.clusterset.local:8080/hostname && echo ; sleep 1; done
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}
{"hostname":"whereami-86769c7ffb-4vgth"}

最後に Pod のホスト名から名前解決が出来るか確認してみましょう。MCS の場合、クラスタ間でPod 名が重複する可能性があるので、 以下のフォーマットで各 Pod のホスト名から名前解決することが可能です。

[Pod名].[Fleetで登録したMembership名].[ServiceExportの名前].[Namespace名].svc.clusterset.local

早速試してみましょう。

#tyo-cluster-02
/home # nslookup whereami-0.tyo-cluster-01.whereami.test.svc.clusterset.local
Server: 172.16.28.10
Address 1: 172.16.28.10 kube-dns.kube-system.svc.cluster.local
Name: whereami-0.tyo-cluster-01.whereami.test.svc.clusterset.local
Address 1: 10.5.1.14
/home # nslookup whereami-1.tyo-cluster-01.whereami.test.svc.clusterset.local
Server: 172.16.28.10
Address 1: 172.16.28.10 kube-dns.kube-system.svc.cluster.local
Name: whereami-1.tyo-cluster-01.whereami.test.svc.clusterset.local
Address 1: 10.5.0.11
/home # nslookup whereami-0.osa-cluster-01.whereami.test.svc.clusterset.local
Server: 172.16.28.10
Address 1: 172.16.28.10 kube-dns.kube-system.svc.cluster.local
Name: whereami-0.osa-cluster-01.whereami.test.svc.clusterset.local
Address 1: 10.6.0.16
/home # nslookup whereami-1.osa-cluster-01.whereami.test.svc.clusterset.local
Server: 172.16.28.10
Address 1: 172.16.28.10 kube-dns.kube-system.svc.cluster.local
Name: whereami-1.osa-cluster-01.whereami.test.svc.clusterset.local
Address 1: 10.6.1.10

ご覧の通り、各 Pod の IP Address を解決することが出来ました。

6. MCS の仕組み

この章では MCS の仕組みについて解説していきます。
MCS は以下はコンポーネントで構成されています。

MCS コンポーネント相関
  • VPC Firewall
    各クラスタの Pod 同士を通信させるためのルールです。尚、MCS が構成されるとこのルールは自動的に設定されます。
  • Traffic Director
    Endpoint (各 Pod の IP Address) に対するヘルスチェックを行います。
  • Cloud DNS
    Export した Service が Headless Service の場合に利用します。具体的にはサービスのドメイン名に対応する全 Pod の IP Address を A レコードとして登録されます。またワークロードが StatefulSet の場合は、Pod のホスト名と IP Address に対応した A レコードも登録されます。
  • gke-mcs-importer
    Export された Service の情報を元に、自クラスタに ServiceImport (ClusterSetIP) を構成したり、ClusterSetIP に紐付く Endpoint (Pod の IP) 情報を Traffic Director から xDS API を通じて受けとる役割です。
  • mcs-core-dns
    GKE クラスタ内で通常使われる DNSサービス (Kube-dns) に代わり、clusterset.local のサフィックスを持つドメイン名を解決する役割です。mcs-core-dns にレコードが存在しないクエリは Cloud DNS に転送されます。名前の通りですが、Core DNS が使われています。

先程試した環境を使って、実際に各コンポーネントの設定がどうなっているのか確認しましょう。

”gcloud container hub multi-cluster-services enable” 実行後、メンバーのクラスタに対して、Firewall rules が自動的に出来ています。

❯ gcloud compute firewall-rules list | grep mcsdgke-osa-cluster-01-86f4022f-mcsd         vpc-01   INGRESS    900       tcp,udp,sctp,icmp,esp,ah            False
gke-tyo-cluster-01-f727b198-mcsd vpc-01 INGRESS 900 tcp,udp,sctp,icmp,esp,ah False
gke-tyo-cluster-02-d6a40b53-mcsd vpc-01 INGRESS 900 tcp,udp,sctp,icmp,esp,ah False
❯ gcloud compute firewall-rules describe gke-tyo-cluster-01-f727b198-mcsdallowed:
- IPProtocol: tcp
- IPProtocol: udp
- IPProtocol: sctp
- IPProtocol: icmp
- IPProtocol: esp
- IPProtocol: ah
creationTimestamp: '2021-12-22T16:48:48.597-08:00'
description: A managed firewall for multi-cluster pod and service ingress.
direction: INGRESS
disabled: false
id: '2400828804041701791'
kind: compute#firewall
logConfig:
enable: false
name: gke-tyo-cluster-01-f727b198-mcsd
network: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/networks/vpc-01
priority: 900
selfLink: https://www.googleapis.com/compute/v1/projects/kzs-sandbox/global/firewalls/gke-tyo-cluster-01-f727b198-mcsd
sourceRanges:
- 35.191.0.0/16 #Traffic Director の Probe source IP range
- 130.211.0.0/22 #Traffic Director の Probe source IP range
- 10.8.0.0/16 #tyo-cluster-02 の Pod IP range
- 10.5.0.0/16 #tyo-cluster-01 の Pod IP range
- 10.6.0.0/16 #osa-cluster-01 の Pod IP range
targetTags:
- gke-tyo-cluster-01-f727b198-node

次に Traffic Director を見てみます。tyo-cluster-01 と osa-cluster-01 にデプロイされた 計 4 つの Endpoints (Pods) が Healthy 扱いになっていることが分かります。

TDでHCされるEndpointsたち

Cloud DNS はどうでしょうか。通常利用時は clusterset.local の SOA/NS レコードとデフォルトで作成される A レコード (今回の記事とは直接関係ないので割愛) があります。

ClusterSet なレコードたち @Cloud DNS

Headless Service + StatefulSet 構成にした場合は、以下のように Pod 毎に対応した A レコードも作成されます。

Headless + StatefulSet なレコードたち @Cloud DNS

Service 自体に対応した A レコードも存在し、全ての Pod の IP Address が含まれていることが分かります。こちらは StatefulSet でなくても、Headless Service であれば作成されます。

Headless Service の A レコード @Cloud DNS

次に MCS を有効化したクラスタの中を覗いてみます。

gke-mcs Namespace に gke-mcs-importer の Pod が作成されています。

❯ kubectl get pods -n gke-mcs
NAME READY STATUS RESTARTS AGE
gke-mcs-importer-79d6dc64fc-w5kdg 1/1 Running 0 5d17h

さらにkube-system Namespace に mcs-core-dns のPod が作成されています。

❯ kubectl get pod -n kube-system | grep mcs
mcs-core-dns-54788767d4-f77xc 1/1 Running 0 32m
mcs-core-dns-54788767d4-ldv27 1/1 Running 0 32m
mcs-core-dns-autoscaler-5c99f6649-mx8h2 1/1 Running 0 32m

mcs-core-dns の Service も作成されています。ClusterIP は 172.16.16.206 ですね。

❯ kubectl get svc mcs-core-dns -n kube-system                                                                                                                  (tyo-cluster-01/default)
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mcs-core-dns ClusterIP 172.16.16.206 <none> 53/UDP 35m

GKE では本来 kube-dns を使って名前解決を行っています。mcs-core-dns と kube-dns との関係はどうなってるのでしょうか? 答えは kube-dns の ConfigMap にあります。

❯ kubectl get configmap kube-dns -n kube-system -o yaml                                                                                                        (tyo-cluster-01/default)
apiVersion: v1
data:
stubDomains: '{"clusterset.local":["172.16.16.206"]}'
kind: ConfigMap
metadata:
creationTimestamp: "2021-12-14T16:52:36Z"
labels:
addonmanager.kubernetes.io/mode: EnsureExists
name: kube-dns
namespace: kube-system
resourceVersion: "5100980"
uid: dbe4fdf5-e332-4e8a-9193-a9e8949cfb86

これまで何度も出てきた通り MCS により export された Service にアクセスするには cluster.local ではなく clusterset.localというサフィックスが付いたドメイン名を使います。kube-dns には Stub Domain の設定がされていて、***.clusterset.local 宛のクエリが来た場合には mcs-core-dns に転送する動きとなります。

では、mcs-core-dns の ConfigMap を見てみましょう。
k8s_mcs という名前の plugin に、 clusterset.local の zone が設定されていますね。forward 先として、GCE の metadata server (169.254.169.254) が設定されており、レコードが存在しない場合は上位の Cloud DNS にクエリを転送する設定になっています。

❯ kubectl get configmap mcs-core-dns -n kube-system -o yaml
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
k8s_mcs clusterset.local in-addr.arpa ip6.arpa {
fallthrough clusterset.local
disablelegacysync
}
prometheus :9153
forward . 169.254.169.254 {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
metadata:
creationTimestamp: "2021-12-23T00:48:56Z"
name: mcs-core-dns
namespace: kube-system
resourceVersion: "5100958"
uid: 5c29fecc-6204-4deb-9292-b8eff91f0064

7. まとめ

如何でしたでしょうか?比較的簡単に複数クラスタ間でのサービスディスカバリーとサービス呼び出しが出来ることがご理解頂けたかと思います。

MCS は通常の Service の対象を複数クラスタに広げた上で、あくまで内部通信に使われるものであり、クラスタの外 (e.g. Internet) からのアクセスの負荷分散に使うことは出来ません。また Path based routing など L7 レベルのルーティングなどは行いません。これらの機能が必要な場合は Multi-cluster IngressMulti-cluster Gateway (解説記事 Part 1, Part2)など LB を使うサービスの利用を検討してください。

また、MCS と同様に複数クラスタ間でのサービスディスカバリーを実現する機能として、Cloud DNS for GKEAnthos Service Mesh などもあります。各機能の紹介やMCS との比較は以前、同僚のうっちーさんと私が出演した Webinar で説明していますので是非視てみてください。(比較説明は 30:41~) 個人的には MCS が一番シンプルに “複数クラスタ間でのサービスディスカバリーとサービス呼び出し” を行える機能だと考えており、おすすめです。

MCS を使えば可用性向上、用途毎のクラスタ分割、サービス共有、クラスタ移行など様々なユースケースに対応すること出来ます。皆様も是非試してみてください!フィードバックもお待ちしてますmm

--

--

Kazuu
google-cloud-jp

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