GemFire for KubernetesでTKGを使ってマルチサイト構成（WAN構成）で接続する。

'23/11現在、GemFire for Kubernetesでマルチサイト構成（WAN構成）を組もうとしても、ドキュメントの記載が[GKEに関するものしかなく](https://docs.vmware.com/en/VMware-GemFire-for-Kubernetes/2.3/gf-k8s/wan-replication.html)、イマイチやり方が分からない。
実際、作ろうとした時にハマり箇所があったのでメモを残す。

## GemFireの前提知識
GemFireで相手方と接続する場合、DNSの逆引きが動いて名前解決できる必要がある。
この辺の仕組みはよく分からないのだが、接続先IPの逆引きしたDNS名が接続元からも参照できる必要がある。
以下の図のケースで考える。
![1700373542673.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/03eb0948-e761-4d43-32f4-dc20979aca5e.png)
LocatorのPodに繋がるServiceをExternalIPで公開してgfshで繋ごうとしても、gfshのコマンド側で`locator.gem.svc.cluster.local`が解決できないと、以下のようなエラーになる。
```sh
gfsh>connect --locator 10.20.30.40[10334]
Connecting to Locator at [host=10.20.30.40, port=10334] ..
Connecting to Manager at [host=locator.gem.svc.cluster.local, port=1099] ..
Could not connect to : [host=locator.gem.svc.cluster.local, port=1099]. Failed to retrieve RMIServer stub: javax.naming.ConfigurationException [Root exception is java.rmi.UnknownHostException: Unknown host: locator.gem.svc.cluster.local; nested exception is:
    java.net.UnknownHostException: locator.gem.svc.cluster.local]
```
なので、接続元で相手方クラスタ内のサービスに紐づく名前を解決できるようにする必要がある。

## 事前準備
### GemFireClusterの構築
今回、ここではTKGクラスタを2面用意し、それぞれにGemFireClusterを作成した。
![1700375787692.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/f1e269d1-3002-d6e5-e83a-75c3b32e6ba0.png)
各GemFireClusterはLocator1台、Server2台を持つ構成とし、以下のManifestで作成した。
```yaml:cluster1
apiVersion: gemfire.vmware.com/v1
kind: GemFireCluster
metadata:
  name: gemfire1
  namespace: gemfire-cluster-1
spec:
  serialization:
    pdx:
      readSerialized: true
  image: registry.tanzu.vmware.com/pivotal-gemfire/vmware-gemfire:10.0.1
  security:
    tls: {}
  locators:
    overrides:
      gemFireProperties:
      - name: "distributed-system-id"
        value: "10"
      - name: "remote-locators"
        value: "gemfire2-locator-0.gemfire2-locator.gemfire-cluster-2.svc.cluster.local[10334]"
```

```yaml:cluster2
apiVersion: gemfire.vmware.com/v1
kind: GemFireCluster
metadata:
  name: gemfire2
  namespace: gemfire-cluster-2
spec:
  serialization:
    pdx:
      readSerialized: true
  image: registry.tanzu.vmware.com/pivotal-gemfire/vmware-gemfire:10.0.1
  security:
    tls: {}
  locators:
    overrides:
      gemFireProperties:
      - name: "distributed-system-id"
        value: "11"
      - name: "remote-locators"
        value: "gemfire1-locator-0.gemfire1-locator.gemfire-cluster-1.svc.cluster.local[10334]"
```
Remote Locatorは相手のServiceを指定している（後述するが実際は若干異なる経路で繋ぐ）。

また、クラスタ外から繋げられるようにExternalIPでServiceを公開する。これはGemFireClusterが作成するServiceとは別に作成する。
```yaml:cluster1
apiVersion: v1
kind: Service
metadata:
  name: locator-lb
spec:
  ports:
  - name: locator
    port: 10334
    protocol: TCP
  - name: locator-management-api
    port: 7070
    protocol: TCP
  - name: locator-metrics
    port: 4321
    protocol: TCP
  - name: manager
    protocol: TCP
    port: 1099
  type: LoadBalancer
  selector:
    statefulset.kubernetes.io/pod-name: gemfire1-locator-0
```
```yaml:cluster2
apiVersion: v1
kind: Service
metadata:
  name: locator-lb
spec:
  ports:
  - name: locator
    port: 10334
    protocol: TCP
  - name: locator-management-api
    port: 7070
    protocol: TCP
  - name: locator-metrics
    port: 4321
    protocol: TCP
  - name: manager
    protocol: TCP
    port: 1099
  type: LoadBalancer
  selector:
    statefulset.kubernetes.io/pod-name: gemfire2-locator-0
```
Manager用にPort1099を公開する必要がある点に注意。

また、WAN構成を取った場合、GatewaySenderとGatewayReceiverはServer上で動くため、最終的にはServer間で通信できる＝ServerもExternalIP等で公開する必要がある。そのため、Locatorと同様に、Serverも公開する。
```yaml:cluster1
apiVersion: v1
kind: Service
metadata:
  name: server-lb-0
spec:
  type: LoadBalancer
  ports:
  - name: server
    port: 40404
  - name: server-dev-rest-api
    port: 7070
  - name: server-metrics
    port: 4321
  - name: receiver
    port: 8888
  selector:
    statefulset.kubernetes.io/pod-name: gemfire1-server-0
---
apiVersion: v1
kind: Service
metadata:
  name: server-lb-1
spec:
  type: LoadBalancer
  ports:
  - name: server
    port: 40404
  - name: server-dev-rest-api
    port: 7070
  - name: server-metrics
    port: 4321
  - name: receiver
    port: 8888
  selector:
    statefulset.kubernetes.io/pod-name: gemfire1-server-1
```
```yaml:cluster2
apiVersion: v1
kind: Service
metadata:
  name: server-lb-0
spec:
  type: LoadBalancer
  ports:
  - name: server
    port: 40404
  - name: server-dev-rest-api
    port: 7070
  - name: server-metrics
    port: 4321
  - name: receiver
    port: 8888
  selector:
    statefulset.kubernetes.io/pod-name: gemfire2-server-0
---
apiVersion: v1
kind: Service
metadata:
  name: server-lb-1
spec:
  type: LoadBalancer
  ports:
  - name: server
    port: 40404
  - name: server-dev-rest-api
    port: 7070
  - name: server-metrics
    port: 4321
  - name: receiver
    port: 8888
  selector:
    statefulset.kubernetes.io/pod-name: gemfire2-server-1
```
なお、Receiverで使うポートを明示的に書く必要がある点に注意。

### CoreDNSの設定変更
相手のServiceの名前解決が出来るよう、CoreDNSに設定変更を行う。
各クラスタで`kubectl edit cm -n kube-system coredns`を実行し、以下のように相手のServiceのFQDNとExternalIPを追記する。
```diff:cluster1
@@ -7,6 +7,12 @@
            lameduck 5s
         }
         ready
+        hosts {
+           10.220.28.88 gemfire2-locator-0.gemfire2-locator.gemfire-cluster-2.svc.cluster.local
+           10.220.28.92 gemfire2-server-0.gemfire2-server.gemfire-cluster-2.svc.cluster.local
+           10.220.28.93 gemfire2-server-1.gemfire2-server.gemfire-cluster-2.svc.cluster.local
+           fallthrough
+        }
         kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
```
```diff:cluster2
@@ -7,6 +7,12 @@
            lameduck 5s
         }
         ready
+        hosts {
+           10.220.28.87 gemfire1-locator-0.gemfire1-locator.gemfire-cluster-1.svc.cluster.local
+           10.220.28.90 gemfire1-server-0.gemfire1-server.gemfire-cluster-1.svc.cluster.local
+           10.220.28.91 gemfire1-server-1.gemfire1-server.gemfire-cluster-1.svc.cluster.local
+           fallthrough
+        }
         kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure
            fallthrough in-addr.arpa ip6.arpa
```
保存後、CoreDNSを再起動して設定をリロードする。
```sh
kubectl delete pod -n kube-system -l k8s-app=kube-dns
```

## WAN構成の動作確認
ここでは以下のような作業用Podを立ち上げて、そこから各クラスタのLocatorに繋いで作業していく。
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: debug-gemfire
spec:
  containers:
    - name: gemfire
      image: registry.tanzu.vmware.com/pivotal-gemfire/vmware-gemfire:10.0.0
  imagePullSecrets:
    - name: image-pull-secret
```
また構成は以下とする。
![1700379502149.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/26566477-0527-4166-df48-cc8ebf16a4b7.png)

作業用Podに接続し、gfshを立ち上げる。
```sh
kubectl exec -it debug-gemfire -- bash
gfsh
```
Cluster1のLocatorに接続する。
```sh
connect --locator 10.220.28.87[10334]
```
GatewaySenderを作成する。
```sh
create gateway-sender --id=gemfire-cluster-1 --parallel=true --remote-distributed-system-id=11
```
Regionを作成する。
```sh
create region --name=regionA --type=PARTITION --gateway-sender-id=gemfire-cluster-1
```
GatewayReceiverを作成する。デフォルト値で作ると待受ポートが一意にならないため、Serviceで公開したPortを指定する。
```sh
create gateway-receiver --start-port 8888 --end-port 8888
```
確認用のデータを入れておく。
```sh
put --region=regionA --key="1" --value="one"
put --region=regionA --key="2" --value="two"
```
Cluster1側の作業は以上なので、Cluster1から切断する。
```
disconnect
```
次にCluster2の方でもGatewaySender、GatewayReceiverを作成する。
Cluster2のLocatorに接続する。
```sh
connect --locator 10.220.28.88[10334]
```
Cluster1と同じようにGatewaySender、Region、GatewayReceiverを作成する。
```sh
create gateway-sender --id=gemfire-cluster-2 --parallel=true --remote-distributed-system-id=10
create region --name=regionA --type=PARTITION --gateway-sender-id=gemfire-cluster-2
create gateway-receiver --start-port 8888 --end-port 8888
```
ここで`status gateway-sender`でGatewaySenderの設定を確認する。
```sh
Cluster-11 gfsh>status gateway-sender --id gemfire-cluster-2
                     Member                      |   Type   | Status
------------------------------------------------ | -------- | ---------------------
gemfire2-server-1(gemfire2-server-1:1)<v1>:59668 | Parallel | Running and Connected
gemfire2-server-0(gemfire2-server-0:1)<v1>:58072 | Parallel | Running and Connected
```
`Status`が`Connected`になっているため、相手のクラスタと繋がっていることが分かる。
また、クエリを発行してデータが取れるか確認する。
```sh
query --query="select * from /regionA"
Result : true
Limit  : 100
Rows   : 2

Result
------
two
one
```
無事、Cluster1でputした内容が確認できた。

## まとめ
一応WAN構成自体は作れることを確認できた。
ただ、スケールイン・アウトには対応出来ないし、IPが変わった場合も追従出来ない。
この辺はOperatorで実装して欲しい部分であり、GemFire for Kubernetesが将来的に改善されることを期待したい。


## 失敗談
### CoreDNSのStubDomain
CoreDNSのStubDomainで解決できないか、CoreDNSのConfigMapに以下を追加して試してみた。
```diff
  Corefile: |
+   gemfire-cluster-2.svc.cluster.local:53 {
+     forward . 10.220.28.89
+   }
    .:53 {
```
この場合、PodのIPは引けるようになるが、Private IPを返してくるため上手く動かない。
接続は以下のような感じでタイムアウトとなる。
```sh
gfsh>connect --locator 10.220.28.88[10334]
Connecting to Locator at [host=10.220.28.88, port=10334] ..
Connecting to Manager at [host=gemfire2-locator-0.gemfire2-locator.gemfire-cluster-2.svc.cluster.local, port=1099] ..
Could not connect to : [host=gemfire2-locator-0.gemfire2-locator.gemfire-cluster-2.svc.cluster.local, port=1099]. Failed to retrieve RMIServer stub: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: gemfire2-locator-0.gemfire2-locator.gemfire-cluster-2.svc.cluster.local; nested exception is:
    java.net.ConnectException: Connection timed out (Connection timed out)]
```