Kubernetes Service Hatoba上でIstioを使用したMultiCluster Service Meshを構築してみた


この記事は[ニフクラ](https://www.nifcloud.com/)等を提供している[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct)の22日目の記事です。

前日は[natsumo](https://qiita.com/natsumo)さんの[続・Scratchにデータベースが使える拡張ブロックを追加して活用してみた話](https://qiita.com/natsumo/items/55e2436fa53847333da2)でした。ニフクラ mobile backendを使用してゲームを作ってみたいけど、プログラムやったことないから不安です。という方にはピッタリのコンテンツかと思います。そうではない方も、カニさんゲームが面白いので是非やってみてください。

ちなみにですが、私は無事ランキング１位をとれました！ よければ記録に挑戦してみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/967883/879c177c-8983-a585-b191-9d5dd2f80397.png)


それでは本題に入っていきます。目次はこんな感じです。
## ToC

- [はじめに](#はじめに)
  - [MultiCluster ServiceMeshとは](#multicluster-servicemeshとは)
  - [環境](#環境)
- [Let's 構築](#lets-構築)
  - [Cluster間の信頼関係(Trust)の設定の構築](#cluster間の信頼関係trustの設定の構築)
  - [Istioのインストール](#istioのインストール)
  - [SingleClusterでの動作確認](#singleclusterでの動作確認)
    - [Bookinfoのインストール](#bookinfoのインストール)
    - [動作確認](#動作確認)
  - [MultiClusterでの動作確認](#multiclusterでの動作確認)
    - [Meshの結合](#meshの結合)
      - [East-West Gatewayの構築](#east-west-gatewayの構築)
      - [Endpoint Discoveryの有効化](#endpoint-discoveryの有効化)
      - [Serivceのexporse](#serivceのexporse)
    - [Bookinfoのインストール](#bookinfoのインストール-1)
    - [動作確認](#動作確認-1)
- [最後に](#最後に)

## はじめに

富士通クラウドテクノロジーではKubernetes as a Serviceである[Kubernetes Service Hatoba](https://pfs.nifcloud.com/service/kubernetes-service-hatoba.htm)（以下、Hatoba）を提供しています。本記事ではHatobaで構築したクラスター上でIstioを使用したMultiClsuter Service Meshを構築してみます。クラスターはEastリージョンとWestリージョンに分散させ、アプリケーションは[BookInfo](https://istio.io/latest/docs/examples/bookinfo/)を使用します。

いろいろ省略してますが、構築するシステムのイメージ図はこんな感じです。最終的にはWest側のProduct PageからReviewsへの通信をWest側とEast側に負荷分散させてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/967883/8f2eb6e2-7996-a79c-1e61-a3756d89892d.png)



### MultiCluster ServiceMeshとは

MultiCluster Service MeshはIstioを使用することで構築できます。[Istio](https://istio.io/)はオープンソースのServiceMeshになります。通常Istioをインストールする際には下記のような設定要素があり、さまざまな種類のDeployment Modelが存在します。
  - Clusterの数
  - Networkの数
  - Control planeの数
  - Meshの数

Deployment Model毎の特徴については[Deployment Models](https://istio.io/latest/docs/ops/deployment/deployment-models/)にまとまっています。MultiCluster Service Meshとは、複数のClusterが単一のMeshに接続するDeployment Modelであり、単一のClusterで構成するMeshと比較して以下のような利点があります。
  - 障害の分離
  - 位置情報を考慮したルーティング
  - Controle plane modelの選択肢の増加
  - チームやプロジェクトでClusterを分離可能

### 環境

詳細は割愛しますが、下記環境で構築作業を進めていきます。

- Hatobaを使用しEastリージョンとWestリージョンにそれぞれkubernetesクラスターを構築済み
  - クラスターの構築手順は[Kubernetes Service Hatoba：クイックスタート](https://pfs.nifcloud.com/guide/kubernetes-service-hatoba/quickstart.htm)参照
  - 一方のクラスター上のpodから他方のAPI Serverにアクセス可能な状態に設定しておいてください
    - 基本的にニフクラではIPベースの制御となるので、KubernetesクラスターのWorker NodeのグローバルIPをFWで許可しておけば良いです
- 使用するコマンド等のバージョン
  - istioctl: v1.16.0
  - kubectl: v1.25.4
  - helm: v3.10.2
  - kubernetes: v1.24.3
- istio v1.16.0のAssetsの取得
  ```bash
  curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.16.0 sh -
  ```
- aliasの設定
  ```bash
  alias kw='kubectl --kubeconfig=${WEST_KUBECONFIG_PATH}'
  alias ke='kubectl --kubeconfig=${EAST_KUBECONFIG_PATH}'
  alias iw='istioctl --kubeconfig=${WEST_KUBECONFIG_PATH}'
  alias ie='istioctl --kubeconfig=${EAST_KUBECONFIG_PATH}'
  alias hw='helm --kubeconfig=${WEST_KUBECONFIG_PATH}'
  alias he='helm --kubeconfig=${EAST_KUBECONFIG_PATH}'
  ```

## Let's 構築

まずはEast側とWest側のクラスターそれぞれにIstioをインストールします。その後、West側のクラスターに[BookInfo](https://istio.io/latest/docs/examples/bookinfo/)をインストールしSingleClusterでMeshを作成します。イメージ図は下記です。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/967883/39fcd26f-6e71-ea8d-fe1a-17f8a750c6ec.png)


次にEast側のクラスターにもBookInfoをインストールします。West側にはProductPage/Reviews-v2/Ratingsをインストールしましたが、East側にはReviews-v3のみをインストールします。そして、West側のMeshとEast側のMeshを連結しMultiCluster Service Meshを構築します。Meshを連結するために下記設定を行います。
- Cluster間の信頼関係(Trust)の設定
- East-West Gatewayの構築
- Endpoint Discoveryの有効化

上記のTrustの設定ではIstioのCAの変更作業が発生します。通常、Istioで使用するCAを変更するとIstioの再インストールが必要となります。そのため、クラスター構築の前にまずTrustの設定を行う流れとします。

まとめるとこんな感じです。

- Cluster間の信頼関係(Trust)の設定
- Istioのインストール
- SingleClusterでの動作確認
  - BookInfoのインストール
  - 動作確認
- MultiClusterでの動作確認
  - ServiceMeshの連結
    - East-West Gatewaの構築
    - Endpoint Discoveryの有効化
  - BookInfoのインストール
  - 動作確認

### Cluster間の信頼関係(Trust)の設定の構築

Mesh内の通信はIstioによってmTLS認証が適用されます。そのため、Meshを共有するClsuter間の通信にもmTLS認証を適用できるように信頼関係（Trust）を設定する必要があります。Trustの設定は、[Istioの証明書管理](https://istio.io/latest/docs/tasks/security/cert-management/)で説明される下記のどちらかの方法を利用することで設定可能です。
- Custom CA Integration using Kubernetes CSR
- Plug in CA Certificates

今回は[Plug in CA Certificates](https://istio.io/latest/docs/tasks/security/cert-management/plugin-ca-cert/)に記載されている方法を利用してTrustの設定を行います。この方法では、まずRootCAを作成し、そのRootCAを使用して各クラスター用の中間CAを生成します。生成した中間CAはKubernetes上にSecretリソースとして配置します。この設定を行うことで、生成したCAでMesh内のWorkloadの証明書に署名が行われるようになります。

各CAはIstioのtoolで提供されている[Makefile](https://github.com/istio/istio/blob/master/tools/certs/Makefile.selfsigned.mk)を利用することで簡単に生成可能です。

```bash
mkdir certs
cd certs
# Root CAの作成
make  -f ./../istio-1.16.0/tools/certs/Makefile.selfsigned.mk root-ca
# west-cluster用の中間CAの作成
make  -f ./../istio-1.16.0/tools/certs/Makefile.selfsigned.mk west-cluster-cacerts
# east-clsuter用の中間CAの作成
make  -f ./../istio-1.16.0/tools/certs/Makefile.selfsigned.mk east-cluster-cacerts
cd ..
```

certs配下に以下のようなファイル群が生成されてるかと思います。

```bash
$ tree certs
certs
├── east-cluster
│   ├── ca-cert.pem
│   ├── ca-key.pem
│   ├── cert-chain.pem
│   └── root-cert.pem
├── root-ca.conf
├── root-cert.csr
├── root-cert.pem
├── root-key.pem
└── west-cluster
    ├── ca-cert.pem
    ├── ca-key.pem
    ├── cert-chain.pem
    └── root-cert.pem
```

次に、作成した中間CAをSecretリソースとしてデプロイします。

```bash
# west cluster
kw create ns istio-system
kw create secret generic cacerts -n istio-system \
       --from-file=certs/west-cluster/ca-cert.pem \
       --from-file=certs/west-cluster/ca-key.pem \
       --from-file=certs/root-cert.pem \
       --from-file=certs/west-cluster/cert-chain.pem
# east cluster
ke create ns istio-system
ke create secret generic cacerts -n istio-system \
       --from-file=certs/east-cluster/ca-cert.pem \
       --from-file=certs/east-cluster/ca-key.pem \
       --from-file=certs/root-cert.pem \
       --from-file=certs/east-cluster/cert-chain.pem
```

これでTrustの設定は終わりです。

### Istioのインストール

Kubernetes上にIstioをインストールする際の選択肢は基本的には下記の４種類になります。
- [istioctl install](https://istio.io/latest/docs/setup/install/istioctl/)
- [istioctl manifest generate](https://istio.io/latest/docs/setup/install/istioctl/#generate-a-manifest-before-installation)
- [install using Helm](https://istio.io/latest/docs/setup/install/helm/)
- [istio operator](https://istio.io/latest/docs/setup/install/operator/)

各インストールのメリット・デメリットは[Which Istio installation method should I use?](https://istio.io/latest/about/faq/#install-method-selection)にまとめられています。今回は個人的な趣味でHelmを使用してインストールします。

:::note info
__Helmを利用したIstioのインストールについて__
2022/12/22時点ではHelmを使用したインストールはalpha機能となっています。使用する際には[Feature Status](https://istio.io/latest/docs/releases/feature-stages/)を一読しておいた方が良いかもしれません。
:::

まずHelmにリポジトリを追加します。

```bash
hw repo add istio https://istio-release.storage.googleapis.com/charts
hw repo update
he repo add istio https://istio-release.storage.googleapis.com/charts
he repo update
```

最終的にはEast側とWest側のクラスターを単一のMeshに接続するため、下記設定でIstioのインストールを行います。

|              | west cluster | east cluster |
| ------------ | ------------ | ------------ |
| meshID       | mesh         | mesh         |
| cluster name | west-cluster | east-cluster |
| network name | west-network | east-network |

インストールコマンドはこちらです。
```bash
# west clsuter
kw label ns istio-system topology.istio.io/network=west-network
hw install -n istio-system  istio-base istio/base
hw install -n istio-system  istiod istio/istiod --set global.meshID=mesh --set global.multiCluster.clusterName=west-cluster --set global.network=west-network
hw install -n istio-ingress istio-ingress istio/gateway --create-namespace --set service.annotations."service\.beta\.kubernetes\.io/hatoba-load-balancer-name"=westingress --set labels.istio=ingressgateway
# east clsuter
ke label ns istio-system topology.istio.io/network=east-network
he install -n istio-system  istio-base istio/base
he install -n istio-system  istiod istio/istiod --set global.meshID=mesh --set global.multiCluster.clusterName=east-cluster --set global.network=east-network
he install -n istio-ingress istio-ingress istio/gateway --create-namespace --set service.annotations."service\.beta\.kubernetes\.io/hatoba-load-balancer-name"=eastingress --set labels.istio=ingressgateway
```

`Type:LoadBalancer`に`EXTERNAL-IP`が割り当てられるまで待機です。

```bash
$ kw get svc -n istio-ingress -w
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)                                           AGE
istio-ingress    LoadBalancer   10.111.160.181   <pending>         15021:32079/TCP,80:32182/TCP,443:30333/TCP        17s
istio-ingress    LoadBalancer   10.111.160.181   222.158.213.21    15021:32079/TCP,80:32182/TCP,443:30333/TCP        106s
```

:::note warn
__HatobaのType:LoadBalancerの名前について__
Hatobaの`Type:LoadBalancer`には利用できる名前が英数字15文字以内という仕様があります。また、[istio/gateway chart](https://github.com/istio/istio/tree/master/manifests/charts/gateway)でインストールされる`Type:LoadBalancer`の名前はリリース名となります。そのため、リリース名が英数字15文字以内でない場合、`Type:LoadBalancer`の作成処理はエラーとなります。この場合は、上記手順のようにannotationに`service.beta.kubernetes.io/hatoba-load-balancer-name`を付与し実際にインストールされるLoadBalancerの名前を変更することでエラーを回避できます。その他のannotationで設定可能な項目は[技術仕様/Kubernetes Service Hatoba/ロードバランサー](https://pfs.nifcloud.com/spec/kubernetes-service-hatoba/load_balancer.htm)に記載があります。
:::

### SingleClusterでの動作確認

Westリージョンに構築したクラスターにBookinfoをインストールし動作確認を行います。

#### Bookinfoのインストール

Istioではさまざまな種類の[サンプルアプリケーション](https://github.com/istio/istio/tree/master/samples)が提供されており、今回はその中の[BookInfo](https://istio.io/latest/docs/examples/bookinfo/)を使用します。BookInfoのReviewsアプリにはv1とv2、v3の3種類のバージョンが存在していますが、West側にはv2のみをインストールしたいと思います。

手順はこちらです。

```bash
kw create ns bookinfo
kw label ns bookinfo istio-injection=enabled
kw apply -n bookinfo -f https://raw.githubusercontent.com/istio/istio/release-1.16/samples/bookinfo/platform/kube/bookinfo.yaml
kw apply -n bookinfo -f https://raw.githubusercontent.com/istio/istio/release-1.16/samples/bookinfo/networking/bookinfo-gateway.yaml
# v1とv3の削除
kw delete -n bookinfo -f https://raw.githubusercontent.com/istio/istio/release-1.16/samples/bookinfo/platform/kube/bookinfo.yaml -l app=reviews,version=v1
kw delete -n bookinfo -f https://raw.githubusercontent.com/istio/istio/release-1.16/samples/bookinfo/platform/kube/bookinfo.yaml -l app=reviews,version=v3
```

全てのPodがRunningになることを確認します。

```bash
$ kw get pod -n bookinfo
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-7d4d9d5fcb-smdx6       2/2     Running   0          13s
productpage-v1-66756cddfd-tgcs4   2/2     Running   0          12s
ratings-v1-85cc46b6d4-wqxpc       2/2     Running   0          13s
reviews-v2-cdd8fb88b-89kvj        2/2     Running   0          12s
```

#### 動作確認

West側のIngress GatewayのIPを取得します。

```bash
WEST_INGRESS=$(kw get svc istio-ingress -n istio-ingress -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
```

curlコマンドでWest側にアクセスし、文字列`reviews-v2`を含む応答が返ってくればOKです。

```bash
$ curl -s "http://${WEST_INGRESS}/productpage" | grep reviews
        <u>reviews-v2-cdd8fb88b-89kvj</u>
```

ちなみにですが、productpage podのEndpointを確認してみると、`outbound|9080||reviews.bookinfo.svc.cluster.local`通信が`10.233.0.75:9080`に送られ、そのIPアドレスがReviews-v2 podのものであることが分かります。

```bash
$ iw pc -n bookinfo endpoints deploy/productpage-v1 --cluster 'outbound|9080||reviews.bookinfo.svc.cluster.local' -o json | jq '[.[].hostStatuses[] | { socketAddress: .address.socketAddress } ]'
[
  {
    "socketAddress": {
      "address": "10.233.0.79",
      "portValue": 9080
    },
    "locality": {
      "region": "jp-west-1",
      "zone": "west-13"
    }
  }
]
$ kw get pod -n bookinfo -o wide -l app=reviews
NAME                        READY   STATUS    RESTARTS   AGE     IP            NODE         NOMINATED NODE   READINESS GATES
reviews-v2-cdd8fb88b-89kvj   2/2     Running   0          2m27s   10.233.0.79   pool-zis5n   <none>           <none>
```



### MultiClusterでの動作確認

本題のMultiCluster Service Meshを構築します。East側とWest側に作成したMeshを結合します。Meshを結合するためには下記の設定を行います。
- East-West Gatewayの構築
- Endpoint Discoveryの有効化
- SerivceのExporse

最後にBookinfoをインストールし、Product PageからEastリージョンとWestリージョンに配置したReviewsへ負荷分散が行われていることを確認します。

#### Meshの結合
##### East-West Gatewayの構築

各クラスター間のIngress PointとなるEast-West Gatewayをインストールします。East-West GatewayもIngress gatewayと同じようにistio/gateway chartを利用することでインストールできます。

インストールの前に使用するvalueファイルを下記コマンドで生成します。

```bash
for REGION in west east
do
cat << EOF > ${REGION}-values.yaml
labels:
  topology.istio.io/network: "${REGION}-network"
  istio: "eastwestgateway"
env:
  ISTIO_META_REQUESTED_NETWORK_VIEW: "${REGION}-network"
service:
  annotations:
    service.beta.kubernetes.io/hatoba-load-balancer-name: "eastwest"
  ports:
  - name: status-port
    port: 15021
    targetPort: 15021
  - name: tls
    port: 15443
    targetPort: 15443
  - name: tls-istiod
    port: 15012
    targetPort: 15012
EOF
done
```

次にHelmを使用してインストールします。

```bash
he install -n istio-ingress istio-eastwest istio/gateway -f east-values.yaml
hw install -n istio-ingress istio-eastwest istio/gateway -f west-values.yaml
```

:::note warn
__HatobaのType:LoadBalancerの利用可能ポート数について__
本来であれば[istio/gateway chart](https://github.com/istio/istio/tree/master/manifests/charts/gateway)の[networkGateway](https://github.com/istio/istio/tree/master/manifests/charts/gateway#multi-networkvm-gateway)を設定することでEast-West Gatewayを簡単に構築することができます。このnetworkGatewayの設定を使用した場合、`Type:LoadBalancer`のポートは4つ設定される動作となります（[service.yaml](https://github.com/istio/istio/blob/b0ea177c02ca9c11cbb9479f54fe866374ff2959/manifests/charts/gateway/templates/service.yaml#L27-L39)参照）。しかし、Hatobaの`Type:LoadBalancer`では利用できるポートが3つまでという制限があり、ポートを4つ確保する設定はエラーとなります。そのため、networkGateway経由ではなく、上記のようなvalue.yamlでユースケースに応じた設定を行う必要があります。
:::

##### Endpoint Discoveryの有効化

クラスターからピアクラスターへのアクセスを可能にするため、サービスアカウントトークンと証明書を含む情報をsecretとして設定します。

```bash
ie create-remote-secret --name="east-cluster" | kw apply -f -
iw create-remote-secret --name="west-cluster" | ke apply -f -
```

##### SerivceのExporse

East West Gatewayで公開したいServiceをGatewayリソースとして作成します。下記は`.local`で終わるサービスを公開する設定になります。

```bash
cat <<EOF > cross-network-gateway.yaml
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: cross-network-gateway
  namespace: istio-ingress
spec:
  selector:
    istio: eastwestgateway
  servers:
    - port:
        number: 15443
        name: tls
        protocol: TLS
      tls:
        mode: AUTO_PASSTHROUGH
      hosts:
        - "*.local"  
EOF
ke apply -f cross-network-gateway.yaml
kw apply -f cross-network-gateway.yaml
```

これでMultiCluster Service Meshを構築することができました。
次にBookinfoを使用して動作確認を行ってみます。

#### Bookinfoのインストール

EastリージョンにはReviews-v3のみをインストールしたいと思います。

```bash
ke create ns bookinfo
ke label ns bookinfo istio-injection=enabled
ke apply -n bookinfo -f https://raw.githubusercontent.com/istio/istio/release-1.16/samples/bookinfo/platform/kube/bookinfo.yaml -l app=reviews,version=v3
ke apply -n bookinfo -f https://raw.githubusercontent.com/istio/istio/release-1.16/samples/bookinfo/platform/kube/bookinfo.yaml -l service=reviews
ke apply -n bookinfo -f https://raw.githubusercontent.com/istio/istio/release-1.16/samples/bookinfo/platform/kube/bookinfo.yaml -l account=reviews
```

PodがRunningになることを確認します。

```bash
$ ke get pod -n bookinfo
NAME                        READY   STATUS    RESTARTS   AGE
reviews-v3-58b6479b-jc92w   2/2     Running   0          25s
```

#### 動作確認

curlコマンドでWest側にアクセスし、`reviews-v2`と`reviews-v3`の文字列を含む応答が返ってくればOKです。これでEast側にあるReviews v2とWest側にあるReviews v3に分散してアクセスが行われていることが分かります。

```bash
$ curl -s "http://${WEST_INGRESS}/productpage" | grep reviews
        <u>reviews-v3-58b6479b-jc92w</u>
$ curl -s "http://${WEST_INGRESS}/productpage" | grep reviews
        <u>reviews-v2-cdd8fb88b-89kvj</u>
```

ちなみにproductpage-v1のendpointを確認してみると、`111.64.95.151:15443`が新たなEndpointとして設定されてあり、East側のEast-West GatewayのIPアドレスが設定されています。

```bash
$ iw pc -n bookinfo endpoints deploy/productpage-v1 --cluster 'outbound|9080||reviews.bookinfo.svc.cluster.local' -o json | jq '[.[].hostStatuses[] | { socketAddress: .address.socketAddress, locality: .locality} ]'
[
  {
    "socketAddress": {
      "address": "111.64.95.151",
      "portValue": 15443
    },
    "locality": {
      "region": "jp-east-1",
      "zone": "east-14"
    }
  },
  {
    "socketAddress": {
      "address": "10.233.0.79",
      "portValue": 9080
    },
    "locality": {
      "region": "jp-west-1",
      "zone": "west-13"
    }
  }
]
$ ke get svc -n istio-ingress -l istio=eastwestgateway
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                                           AGE
istio-eastwest   LoadBalancer   10.100.138.193   111.64.95.151   15021:31761/TCP,15443:30224/TCP,15012:30564/TCP   3h13m
```

また、East-West Gatewayでは`outbound|9080||reviews.bookinfo.svc.cluster.local`の宛先としてReviews-v2 podのIPアドレスが設定されています。

```bash
$ ie pc endpoints -n istio-ingress deploy/istio-eastwest --cluster 'outbound|9080||reviews.bookinfo.svc.cluster.local' -o json | jq '[.[].hostStatuses[] | { socketAddress: .address.socketAddress, locality: .locality } ]'
[
  {
    "socketAddress": {
      "address": "10.233.0.27",
      "portValue": 9080
    },
    "locality": {
      "region": "jp-east-1",
      "zone": "east-14"
    }
  }
]
$ ke get pod -n bookinfo -o wide -l app=reviews
NAME                        READY   STATUS    RESTARTS   AGE     IP            NODE         NOMINATED NODE   READINESS GATES
reviews-v3-58b6479b-jc92w   2/2     Running   0          2m11s   10.233.0.27   pool-8tt3u   <none>           <none>
```

つまり、productpage pod内の`outbound|9080||reviews.bookinfo.svc.cluster.local`の通信はWest内のReviews-v2 podかEast側のEast-West Gatewayにルーティングされ、East側のEast-West GatewayでEast側のReviews-v3 podにルーティングされることが分かります。


## 最後に

Hatoba上でMultiCluster Service Meshを構築してみました。また、Bookinfoを使用して簡単な負荷分散も試してみました。今回は簡単な動作確認のみでDeployment Modelに記載されているようなMultiClusterの利点の検証まではできなかったので、別途時間を作って試してみようかなと思います。また、通信経路の確認を`istioctl`コマンドで行いましたが、[Kiali](https://kiali.io/)や[Jaeger](https://www.jaegertracing.io/)を使って視覚的に見ても面白そうですね。

明日は[Authns](https://qiita.com/Authns)さんで、「TauriでSVGのグラフ生成・描画・保存をしてくれるデスクトップアプリ作成」です。よろしくお願いいたします :bow: