Google Cloud とオンプレミス環境間でマルチクラスタ サービスメッシュ (ハイブリッドメッシュ) を構成する
はじめに
Anthos Service Mesh (以降 ASM) で異なる環境間 (ハイブリッド・マルチクラウド) でのマルチクラスタ サービスメッシュの構成がサポートされました。2022.05 現在 Preview 機能としてご利用できます。
GKE (on Google Cloud) のマルチクラスタメッシュは比較的簡単に構成できるのですが、 ハイブリッド・マルチクラウド環境間でのマルチクラスタメッシュは少し工夫が必要だったので備忘も兼ねて記事にします。
ちなみに ASM とは、簡単に言うと Google Cloud が提供するマネージド版 Istio で、Google Cloud 環境だけでなくオンプレミスや他社クラウド上の Kubernetes クラスタでも動作します。
(本記事では、サービスメッシュとは? ASM とは?という点にはあまり触れませんので、気になる方は以下のブログ記事もご覧ください)
tl;dr
- 複数クラスタ間でサービスメッシュを構成することにより、可用性の向上やセキュアなクラスタ間通信等を実現可能になります
- マルチクラスタメッシュでは、サービス単位でのリージョンを跨いだ failover やクラスタ間でのトラフィック分散など、高度なトラフィックコントロールも可能です
- 異なる環境間でのマルチクラスタメッシュでは、ネットワーク周りなど考慮が必要な点がいくつか出てくるので少し注意が必要です
マルチクラスタでメッシュを組むと何が嬉しいのか
マルチクラスタメッシュとは、複数クラスタ間で 1 つのサービスメッシュを組む構成のことです。
複数クラスタ間でサービスメッシュを組む目的として一番多いと思うのが、可用性の向上です。例えば東京と大阪にそれぞれ GKE クラスタを構築しマルチクラスタ サービスメッシュを構成することで、東京クラスタ上のサービスが利用不可になった場合も大阪クラスタ上の正常なサービスに自動的に接続する、といったことが実現できるようになります。(他にもクラスタ間でのサービスディスカバリやセキュアな通信 (mTLS 等)、可観測性など色々な目的があると思います)
クラスタレベルでの障害に関しては、Multi-cluster Ingress や Multi-cluster Gateway といった機能や DNS を用いて外部クライアントから来るトラフィックをコントロールすることでクラスタ単位で failover することはできますが、サービス単位というさらに細かい粒度で failover をしたい場合などにマルチクラスタメッシュの機能が役に立ちます。
ちなみに Multi-cluster Ingress / Gateway とマルチクラスタメッシュ機能の併用も可能です。Multi-cluster Ingress / Gateway でクラスタ外 (外部クライアント等) からのトラフィック (North-South Traffic) をコントロールし、マルチクラスタメッシュでサービス間通信 (East-West Traffic) をコントロールする形になります。
今回はいわゆるハイブリッドクラウド環境間でのマルチクラスタメッシュを試してみます。
イメージとしては 2019 年の KubeCon で Walmart 社が発表していた事例のように、ハイブリッドクラウド環境間でサービス単位での Failover ができるようにマルチクラスタメッシュでサービス間通信をコントロールすることを目指します。
ASM でサポートしているマルチクラスタメッシュ構成
ASM では 2022.05 現在、以下 3 種類のマルチクラスタメッシュ構成をサポートしています:
- 複数の GKE クラスタ間でのマルチクラスタメッシュ (Single Cloud)
- GKE クラスタと Anthos on-prem クラスタ (on Bare Metal / on VMware) 間でのマルチクラスタメッシュ (Hybrid Cloud)
- GKE クラスタと EKS クラスタ間でのマルチクラスタメッシュ (Multi Cloud)
今回はこの中の 2. GKE クラスタと Anthos on-prem クラスタ (on Bare Metal / on VMware) 間でのマルチクラスタメッシュ (Hybrid Cloud)
を試してみたいと思います。ハイブリッドメッシュは異なるネットワーク間でサービスメッシュを組むことになるので、 Istio のデプロイメントパターンとしては Multi-primary / Multi-network
の構成となります。(現状、ASM では各クラスタで istiod を持つ Multi-primary のみサポートしています)
ちなみに 1. 複数の GKE クラスタ間でのマルチクラスタメッシュ (Single Cloud)
の場合は Multi-primary / Single-network
の構成をサポートしています。クラスタが同一ネットワーク上にあり、ネットワーク的にサービス間で相互に通信可能であるため Multi-network
で利用されているような East-West Traffic 用 Ingress Gateway は不要になります。
ASM でサポートしているマルチクラスタ構成については、本記事の最後に参考資料として掲載している公式ドキュメントをご参照ください。
マルチクラスタ間でのトラフィックコントロール
実際に試す前に、マルチクラスタメッシュでトラフィックをコントロールする方法の例をご紹介します。もっと細かく分けることもできると思いますが、今回は大きく以下 3 種類のユースケースに分けてみます。
- ローカルのサービスが利用できない場合に他クラスタ上のサービスに Failover する
- トラフィックをクラスタ間で分散させる
- トラフィックをローカルのクラスタ内に閉じる (他クラスタ上のサービスにアクセスさせない)
1. ローカルのサービスが利用できない場合に他クラスタ上のサービスに Failover する
マルチクラスタメッシュを構成するとデフォルトでは複数クラスタ間でトラフィックを分散させる挙動となります。
この設定で問題ない場合はそのままでも良いのですが、例えばレイテンシや通信コスト最適化の観点から、なるべく地理的に近い (= 同一クラスタ / リージョン) エンドポイントに優先して接続させたいという要望もあるかと思います。そのような場合は Locality Failover を有効化することで実現可能です。
エンドポイントの Locality は K8s クラスタやノードの地理的な場所を示し、topology.kubernetes.io/region
や topology.kubernetes.io/zone
といった Label をベースに定義されています。
Loacality Failover を有効化するためには DestinationRule で Outlier Detection を設定し、接続先エンドポイントの異常を検知するため (どういう状況になったらエンドポイントが異常であるとマークするか) の基準を定義する必要があります。また、localityLbSetting
の enabled
を true
にします。
これにより通常時は同一 Zone / Region 上のエンドポイントに優先的にアクセスし、それら優先エンドポイントが異常と判断されると別ロケーションのエンドポイントにアクセスする動きとなります。
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: helloworld
spec:
host: helloworld.sample.svc.cluster.local
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
localityLbSetting:
enabled: true
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 1m
また failover
のフィールドを設定することにより、failover 先を明示的に定義することもできます。以下の例だと、us-east 内のエンドポイントが異常 (unhealthy) と判断されると eu-west 内のエンドポイントに failover します。同様に us-west のエンドポイントが異常になると us-east に failover します。
loadBalancer:
simple: ROUND_ROBIN
localityLbSetting:
enabled: true
failover:
- from: us-east
to: eu-west
- from: us-west
to: us-east
さらに failoverPriority
というフィールドで failover する際に評価するラベルの優先順位を定義することもできます。 以下の例だと、Network / Region / Zone が同一のエンドポイントを最優先とし、次に Network / Region が同じエンドポイント、その次が Network が同じエンドポイント、一番優先度が低いのが全ての Label が異なるエンドポイント、となります。
failoverPriority:
- "topology.istio.io/network"
- "topology.kubernetes.io/region"
- "topology.kubernetes.io/zone"
ちなみにデフォルトでは以下の優先順位となっているようです (Doc)
The hierarchy of prioritization matches in the following order:
1. Region
2. Zone
3. Sub-zone
2. トラフィックをマルチクラスタ間で分散させる
先述した通り、マルチクラスタメッシュを構成するとデフォルトでは複数クラスタ間でトラフィックを分散させる挙動となりますが、例えばこのリージョンのクラスタにこれくらいの割合でトラフィックを分散させる等高度なトラフィック制御をすることもできます。(Locality weighted distribution)
Locality weighted distribution を有効化するためには、Locality failover と同様に DestinationRule で Outlier Detection を設定し、localityLbSetting
の enabled
を true
にします。そして、localityLbSetting.distribute
に重み付けの設定を入れていきます。
以下の例だと、us-west/zone1 からのトラフィックは us-west/zone1 上のエンドポイントに 80%、us-west/zone2 に残り 20% を流します。同様に、us-west/zone2 からのトラフィックは us-west/zone2 上のエンドポイントに 80%、us-west/zone1 に残り 20% を流すように構成しています。
distribute:
- from: us-west/zone1/*
to:
"us-west/zone1/*": 80
"us-west/zone2/*": 20
- from: us-west/zone2/*
to:
"us-west/zone1/*": 20
"us-west/zone2/*": 80
3. トラフィックをローカルのクラスタ内に閉じる (他クラスタ上のサービスにアクセスさせない)
マルチクラスタ間でトラフィックを分散させたりするのは便利な一方、トラフィックを別クラスタに流したくないという要件もあるかと思います。その場合は、MeshConfig.ServiceSettings.Settings
で clusterLocal
を true
にすることで、トラフィックがクラスタ内に閉じるように構成することができます。
ローカルに閉じたい対象のサービスをワイルドカードを使って指定することにより、サービス・Namespace ・全てのトラフィックという単位で制御することができます。
以下は Namespace 単位で設定する例です (ワイルドカードを使わず特定サービスを指定することもできますし、*
とだけ指定し全てのサービスを対象にすることもできます)。
serviceSettings:
- settings:
clusterLocal: true
hosts:
- "*.myns.svc.cluster.local"
他にも、クラスタを特定できるように Label を付与しておきクラスタ単位で Subset を作成し、Virtual Service 側でクラスタ内にトラフィックが閉じるように設定する方法等もあります。
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: mysvc-per-cluster-dr
spec:
host: mysvc.myns.svc.cluster.local
subsets:
- name: cluster-1
labels:
topology.istio.io/cluster: cluster-1
- name: cluster-2
labels:
topology.istio.io/cluster: cluster-2
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: mysvc-cluster-local-vs
spec:
hosts:
- mysvc.myns.svc.cluster.local
http:
- name: "cluster-1-local"
match:
- sourceLabels:
topology.istio.io/cluster: "cluster-1"
route:
- destination:
host: mysvc.myns.svc.cluster.local
subset: cluster-1
- name: "cluster-2-local"
match:
- sourceLabels:
topology.istio.io/cluster: "cluster-2"
route:
- destination:
host: mysvc.myns.svc.cluster.local
subset: cluster-2
ハイブリッドメッシュを実際に試してみる
前置きが長くなってしまったのですが、ここから実際にハイブリッドメッシュを試してみます。今回は以下のような環境を作っていきます。
環境構築の前提としては以下の通りです。基本的にデフォルト構成をそのまま使っています。
- GKE / Anthos on Bare Metal クラスタは構築済み
- GKE ver: 1.21.10-gke.2000 , Anthos クラスタ ver: v1.21.5-gke.1300
- GKE は Public Cluster (現状 Private Cluster はハイブリッドメッシュをサポートしていないため)
- 同一 Project 上にクラスタを構築
- GKE / Anthos クラスタに ASM 導入済み
- ASM ver: 1.13.2-asm.2
- ASM は In-cluster Control Plane (現状 Managed Control Plane はハイブリッドメッシュをサポートしていないため)
- CA として MeshCA を採用
- オンプレミスと Google Cloud 環境を Cloud VPN で接続
- VPN トンネル構築時に GKE の Node / Pod IP レンジをオンプレミス側に広告するよう構成 (IP マスカレード等の設定入れていない場合、GKE Pod IP レンジの広告ができていないと Endpoint discovery 時の通信が上手くいかないはず)
- オンプレ→Google Cloud の方向は Anthos ノードの IP レンジを広告 (Anthos はノード IP レンジで SNAT するので)
その他要件などについては以下ドキュメントもご参照ください。
サンプルアプリケーションのデプロイ
まずはサンプルアプリケーションを GKE クラスタの default Namaespace 上にデプロイします。
$ kubectx gke
$ kubectl label namespace default istio.io/rev=asm-1132-2 --overwrite
$ kubectl apply -f echo-gke.yaml
マニフェストの中身はこんな感じです。"GKE!!!"
と返すだけのアプリケーションを echo-svc
というサービスとしてデプロイしています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver
spec:
replicas: 2
selector:
matchLabels:
app: echoserver
template:
metadata:
labels:
app: echoserver
spec:
containers:
- name: echoserver
image: hashicorp/http-echo
args:
- -listen=:8080
- -text="GKE!!!"
---
apiVersion: v1
kind: Service
metadata:
name: echo-svc
spec:
type: ClusterIP
selector:
app: echoserver
ports:
- protocol: TCP
port: 80
targetPort: 8080
次に同様のサンプルアプリケーションを Anthos on Bare Metal クラスタの default Namaespace 上にデプロイします。
$ kubectx abm
$ kubectl label namespace default istio.io/rev=asm-1132-2 --overwrite
$ kubectl apply -f echo-anthos.yaml
マニフェストの中身は先ほど GKE クラスタにデプロイしたものとほぼ同じで、返す文字列のみ "Anthos!!!"
に変更しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: echoserver
spec:
replicas: 2
selector:
matchLabels:
app: echoserver
template:
metadata:
labels:
app: echoserver
spec:
containers:
- name: echoserver
image: hashicorp/http-echo
args:
- -listen=:8080
- -text="Anthos!!!"
---
apiVersion: v1
kind: Service
metadata:
name: echo-svc
spec:
type: ClusterIP
selector:
app: echoserver
ports:
- protocol: TCP
port: 80
targetPort: 8080
以下のように Proxy が injection された Pod が各クラスタにデプロイされていれば OK です。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
echoserver-865484cd65-mc8f4 2/2 Running 0 3m25s
echoserver-865484cd65-sxds2 2/2 Running 0 3m24s
ハイブリッドメッシュの構成
ここからハイブリッドメッシュの構成をします。まずは諸々必要な情報を環境変数として設定します。
NETWORK_1
には GKE クラスタのネットワークラベルの値を入力します。GKE の場合は {PROJECT ID}-{VPC名}
というフォーマットになっているはずです。
NETWORK_2
には Anthos クラスタのネットワークラベルの値を入力します。Anthos の場合はデフォルトだと default
と設定されていると思います。
export PROJECT_NUMBER=$(gcloud projects describe kuchima-demo --format="value(projectNumber)")
export MESH_ID="proj-${PROJECT_NUMBER}"
export NETWORK_1="kuchima-demo-vpc01"
export NETWORK_2="default"
実際に設定されている値を見たい場合は、istio-system namespace や先ほどデプロイしたサンプルアプリの Pod に付与されている topology.istio.io/network
ラベルの値を確認いただくのが良いと思います。
$ kubectl describe pods echoserver-64f897b98c-c7gjg
Name: echoserver-64f897b98c-c7gjg
Namespace: default
Priority: 0
Node: gke-gke-cluster-default-pool-655cd558-tfn3/10.100.10.2
Start Time: Thu, 05 May 2022 10:39:58 +0000
Labels: app=echoserver
pod-template-hash=64f897b98c
security.istio.io/tlsMode=istio
service.istio.io/canonical-name=echoserver
service.istio.io/canonical-revision=latest
topology.istio.io/network=kuchima-demo-vpc01
まずは GKE クラスタ用の East-West Gateway (異なるネットワーク間を通信させるための Gateway) マニフェストを用意します。ASM インストール時に --output_dir
で指定したディレクトリ配下にある asm/istio/expansion/gen-eastwest-gateway.sh
を使って雛形のマニフェストを作成します。ちなみに先述した通り、複数 GKE クラスタ (Single Cloud のケース) の場合はこの East-West Gateway のデプロイはしなくても大丈夫です。ハイブリッド環境など Multi-Network なデプロイメントモデルの場合に必要になります。
$ asm/istio/expansion/gen-eastwest-gateway.sh --mesh ${MESH_ID} --network ${NETWORK_1} --revision asm-1132-2 > eastwest-gateway-gke.yaml
生成したマニフェストに少し手を加えていきます。今回の構成ではオンプレと Google Cloud 間を VPN で接続していることもあり、East-West Gateway はパブリックなサービスとして公開したくないため、Internal LB で公開するように k8s.erviceAnnotations.cloud.google.com/load-balancer-type
に "internal"
と設定します。
また、Ingress Gateway は istio-system とは別の Namespace にデプロイするのが最近のベストプラクティスなので、gateway
という namespace 上にデプロイするようにします。(なんなら istio opeator も使わず k8s manifest でデプロイしてしまっても良いとは思いますが、今回はそのまま istio operator を利用します)
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: eastwest
spec:
revision: "asm-1132-2"
profile: empty
components:
ingressGateways:
- name: istio-eastwestgateway
namespace: gateway
label:
istio: eastwestgateway
app: istio-eastwestgateway
topology.istio.io/network: kuchima-demo-vpc01
enabled: true
k8s:
serviceAnnotations:
cloud.google.com/load-balancer-type: "internal"
env:
# traffic through this gateway should be routed inside the network
- name: ISTIO_META_REQUESTED_NETWORK_VIEW
value: kuchima-demo-vpc01
service:
ports:
- name: status-port
port: 15021
targetPort: 15021
- name: tls
port: 15443
targetPort: 15443
- name: tls-istiod
port: 15012
targetPort: 15012
- name: tls-webhook
port: 15017
targetPort: 15017
values:
gateways:
istio-ingressgateway:
injectionTemplate: gateway
global:
network: kuchima-demo-vpc01
マニフェストが用意できたら GKE クラスタ上に East-West Gateway をデプロイします。
※istioctl のバージョンが古いとマニフェスト apply 時にエラーが出力される可能性ありますのでご注意ください。
$ kubectx gke
$ kubectl create namespace gateway
$ kubectl label namespace gateway istio.io/rev=asm-1132-2 --overwrite
$ istioctl install -f eastwest-gateway-gke.yaml
同様に Anthos クラスタにも East-West Gateway をデプロイします。まずは先ほどと同様にマニフェストを生成します。
$ asm/istio/expansion/gen-eastwest-gateway.sh --mesh ${MESH_ID} --network ${NETWORK_2} --revision asm-1132-2 > eastwest-gateway-anthos.yaml
gateway
という namespace 上に East-West Gateway をデプロイするようにマニフェストを修正します。Anthos クラスタの場合は元々プライベートな IP アドレスレンジを使ってサービス公開される設定にしているため、Service 周りは特に変更しません。
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: eastwest
spec:
revision: "asm-1132-2"
profile: empty
components:
ingressGateways:
- name: istio-eastwestgateway
namespace: gateway
label:
istio: eastwestgateway
app: istio-eastwestgateway
topology.istio.io/network: default
enabled: true
k8s:
env:
# traffic through this gateway should be routed inside the network
- name: ISTIO_META_REQUESTED_NETWORK_VIEW
value: default
service:
ports:
- name: status-port
port: 15021
targetPort: 15021
- name: tls
port: 15443
targetPort: 15443
- name: tls-istiod
port: 15012
targetPort: 15012
- name: tls-webhook
port: 15017
targetPort: 15017
values:
gateways:
istio-ingressgateway:
injectionTemplate: gateway
global:
network: default
修正したマニフェストを使って Anthos クラスタ上に East-West Gateway をデプロイします。
$ kubectx abm
$ kubectl create namespace gateway
$ kubectl label namespace gateway istio.io/rev=asm-1132-2 --overwrite
$ istioctl install -f eastwest-gateway-anthos.yaml
以下のように各クラスタに East-West Gateway がデプロイされていれば OK です。
$ kubectl get pods -n gateway
NAME READY STATUS RESTARTS AGE
istio-eastwestgateway-cc9ff9b65-sfcv9 1/1 Running 0 25s
$ kubectl get svc -n gateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-eastwestgateway LoadBalancer 10.96.7.102 172.16.10.152 15021:32410/TCP,15443:32526/TCP,15012:31330/TCP,15017:31722/TCP 31s
デプロイできたら East-West Gateway 用の Gateway リソース (ややこしいですね) を作成し、全サービス (*.local
) を他クラスタに公開できるようにします。
asm/istio/expansion/expose-services.yaml
というマニフェストの namespace のみ gateway
に変えておきます。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: cross-network-gateway
namespace: gateway
spec:
selector:
istio: eastwestgateway
servers:
- port:
number: 15443
name: tls
protocol: TLS
tls:
mode: AUTO_PASSTHROUGH
hosts:
- "*.local"
修正したマニフェストを各クラスタに apply します。
$ kubectx gke
$ kubectl apply -f asm/istio/expansion/expose-services.yaml
gateway.networking.istio.io/cross-network-gateway created
$ kubectx abm
$ kubectl apply -f asm/istio/expansion/expose-services.yaml
gateway.networking.istio.io/cross-network-gateway created
クラスタ間のサービスディスカバリ設定
最後に各クラスタ間でサービスディスカバリができるように istioctl x create-remote-secret
を実行します。これでマルチクラスタメッシュの構成自体は完了です。
$ export CTX_CLUSTER1=gke
$ export CTX_CLUSTER2=abm
$ istioctl x create-remote-secret \
--context="${CTX_CLUSTER1}" \
--name=gke | \
kubectl apply -f - --context="${CTX_CLUSTER2}"
$ istioctl x create-remote-secret \
--context="${CTX_CLUSTER2}" \
--name=anthos | \
kubectl apply -f - --context="${CTX_CLUSTER1}"
動作確認
疎通確認用の sleep pod を各クラスタにデプロイします。
# GKE
$ kubectx gke
$ kubectl create ns sleep
$ kubectl label namespace sleep \
istio.io/rev=asm-1132-2 --overwrite
$ kubectl apply -f istio-1.13.2-asm.2/samples/sleep/sleep.yaml -n sleep
# Anthos
$ kubectx abm
$ kubectl create ns sleep
$ kubectl label namespace sleep \
istio.io/rev=asm-1132-2 --overwrite
$ kubectl apply -f istio-1.13.2-asm.2/samples/sleep/sleep.yaml -n sleep
sleep pod からは echo-svc サービスのエンドポイントとして自クラスタのエンドポイント 2 つと、対向クラスタの East-West Gateway が登録されていることが分かります。(15443 で待ち受けているのが対向クラスタの East-West Gateway です)
# GKE
$ kubectx gke
$ export SLEEP_POD=$(kubectl get pod -n sleep -l app=sleep -o jsonpath='{.items[0].metadata.name}')
$ istioctl pc ep $SLEEP_POD.sleep | grep echo-svc
10.24.1.9:8080 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
10.24.2.10:8080 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
172.16.10.152:15443 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
# GKE の East-West Gateway の IP アドレス確認
$ kubectl get svc -n gateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-eastwestgateway LoadBalancer 10.28.3.185 10.100.10.6 15021:31339/TCP,15443:30870/TCP,15012:30502/TCP,15017:32405/TCP 18h
# Anthos
$ kubectx abm
$ export SLEEP_POD=$(kubectl get pod -n sleep -l app=sleep -o jsonpath='{.items[0].metadata.name}')
$ istioctl pc ep $SLEEP_POD.sleep | grep echo-svc
10.10.2.59:8080 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
10.10.3.80:8080 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
10.100.10.6:15443 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
# Anthos の East-West Gateway の IP アドレス確認
$ kubectl get svc -n gateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-eastwestgateway LoadBalancer 10.96.7.102 172.16.10.152 15021:32410/TCP,15443:32526/TCP,15012:31330/TCP,15017:31722/TCP 18h
現在の状態で実際にアクセスを試してみます。デフォルト設定なので各クラスタ上のエンドポイントに分散される動きになるはずです。
Sleep pod から echo-svc 宛てに curl を何回か打ってみます。
# GKE
$ kubectx gke
$ export SLEEP_POD=$(kubectl get pod -n sleep -l app=sleep -o jsonpath='{.items[0].metadata.name}')
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
# Anthos
$ kubectx abm
$ export SLEEP_POD=$(kubectl get pod -n sleep -l app=sleep -o jsonpath='{.items[0].metadata.name}')
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
GKE とオンプレミス両方から実行した場合も各クラスタに分散しアクセスしていることが分かります。
Locality Load Balancing を試す
個人的には、ハイブリッドメッシュな環境ではクラスタ間でトラフィックを常に分散させるよりも、ローカルサービスが利用不可になった場合に Failover をするような構成の方が需要があるのではないかと思うので (もちろんケースバイケースだと思いますが)、Locality Failover を試してみます。
まずは以下の Destination Rule マニフェストを用意します。localityLbSetting
を有効化し、outlierDetection
も設定されています(パラメーターは適当です)。また、オンプレミス tokyo-onprem
の failover 先として asia-northeast1
リージョンを指定しています。
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: echo-svc
spec:
host: echo-svc.default.svc.cluster.local
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
localityLbSetting:
enabled: true
failover:
- from: tokyo-onprem
to: asia-northeast1
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 1m
ではこの Destination Rule を適用し動作を確認してみます。実は Anthos クラスタのノードには topology.kubernetes.io/region
や topology.kubernetes.io/zone
ラベルがデフォルトで付与されていないため、先ほど Destination Rule 内で指定した Region label もこのタイミングで追加しておきます。
$ kubectx abm
# Region label の追加
$ kubectl label nodes node01 topology.kubernetes.io/region=tokyo-onprem
$ kubectl label nodes node02 topology.kubernetes.io/region=tokyo-onprem
$ kubectl label nodes node03 topology.kubernetes.io/region=tokyo-onprem
# Destination Rule の作成
$ kubectl apply -f echo-svc-dr.yaml
destinationrule.networking.istio.io/echo-svc created
# 動作確認
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
オンプレミス環境上の sleep pod からは、オンプレミスの echo-svc に優先的に接続していることが分かります。
ではオンプレミス側の echo pod の Proxy を Drain し、疑似的にオンプレミス側サービスで異常が発生した際の挙動もみてみます。
# Proxy の Drain
$ kubectl exec \
"$(kubectl get pod -l app=echoserver \
-o jsonpath='{.items[0].metadata.name}')" \
-c istio-proxy -- curl -sSL -X POST 127.0.0.1:15000/drain_listeners
OK
# Drain したエンドポイントが削除されている
$ istioctl pc ep $SLEEP_POD.sleep | grep echo-svc
10.10.2.111:8080 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
10.100.10.6:15443 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
# まだオンプレミス側にアクセスしている
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
# もう 1 つの Pod の Proxy も Drain する
$ kubectl exec \
"$(kubectl get pod -l app=echoserver \
-o jsonpath='{.items[1].metadata.name}')" \
-c istio-proxy -- curl -sSL -X POST 127.0.0.1:15000/drain_listeners
# GKE 側 East-West Gateway のエンドポイントのみ残っている
$ istioctl pc ep $SLEEP_POD.sleep | grep echo-svc
10.100.10.6:15443 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
# オンプレミス側が利用不可になっても GKE 側にアクセスを逃すことができている
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"GKE!!!"
以上より、Locality Failover が構成されていることを確認できました。同様の設定を GKE 側でも行うことでクラウド側でも locality を意識したルーティングが設定されます。
(おまけ) clusterLocal 設定
おまけですが、Destination Rule を消し、下記のように default namespace 配下のサービスを対象に clusterLocal
を有効化してみたところ、これまでは見えていた他クラスタの echo-svc エンドポイントが載らなくなっていました。(default 以外の namespace のエンドポイントはちゃんと拾っている)
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
serviceSettings:
- settings:
clusterLocal: true
hosts:
- "*.default.svc.cluster.local"
# 自クラスタのエンドポイントしか見えていない
$ istioctl pc ep $SLEEP_POD.sleep | grep echo-svc
10.10.2.240:8080 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
10.10.3.73:8080 HEALTHY OK outbound|80||echo-svc.default.svc.cluster.local
# default namespace 以外のサービスは他クラスタのエンドポイント情報を拾ってきている
$ istioctl pc ep $SLEEP_POD.sleep | grep httpbin-svc
10.10.3.248:80 HEALTHY OK outbound|80||httpbin-svc.sleep.svc.cluster.local
10.100.10.6:15443 HEALTHY OK outbound|80||httpbin-svc.sleep.svc.cluster.local
# 自クラスタ上のエンドポイントにのみアクセスしている
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
$ kubectl exec -n sleep -c sleep $SLEEP_POD -- curl -sS echo-svc.default
"Anthos!!!"
最後に
異なる環境間でサービスメッシュを組む場合には色々と検討ポイントがあり、少しとっつきにくく感じた方もいらっしゃったかもしれませんが、使いこなせれば強い武器にもなる機能ではないかと思います。ちなみにマルチクラウドの環境ではまだ試せていませんが概ね同様の手順で構成できるかと思います。
この記事を読んでいただいて興味を持ってくれた方、要件的にマルチクラスタメッシュの検討が必要そうな方がいらっしゃいましたらぜひ試してみてください。
参考資料
ASM のマルチクラスタメッシュ関連ドキュメント
Istio のマルチクラスタメッシュ関連ドキュメント
Envoy ドキュメント
Discussion