OpenShift Service Meshを使ったマイクロサービスのトラブルシューティング入門

# はじめに
OpenShiftでは、開発や運用を支援する様々なツールの導入・管理機能をOperatorとして提供しています。今回はこのOperatorとIstioのサンプルアプリである[Bookinfo](https://istio.io/latest/docs/examples/bookinfo/)を使って、Service Mesh環境におけるObservabilityの確保とトラブルシューティングについてまとめていきます。


# 前提条件
今回利用する環境とバージョンは以下の通りです。各ツールのインストール方法は後述の手順内にて説明します。
* OpenShift Container Platform : 4.10.24
* Red Hat OpenShift Service Mesh Operator : 2.2.1-0
* OpenShift Elasticsearch Operator :  5.4.4
* Red Hat OpenShift distributed tracing platform Operator : 1.34.1-5
* Kiali Operator : 1.48.2
* Prometheus Operator : 0.56.3
* Red Hat OpenShift Logging Operator : 5.4.4

その他の前提条件は以下の通りです。
* AWS環境
    * AdministratorAccessロールを持つIAMユーザー
* 踏み台兼Dokcerビルド用EC2インスタンス（以下、bastion）
    * AMI: Amazon Linux 2
    * Type: m6i.xlarge


# 手順
## OpenShiftインストール
bastionで以下の手順を実施します。
```
aws configure
 → AdministratorAccessのIAMユーザーのCredentialsを入力
curl -O https://mirror.openshift.com/pub/openshift-v4/clients/ocp/4.10.24/openshift-install-linux-4.10.24.tar.gz
curl -O https://mirror.openshift.com/pub/openshift-v4/clients/ocp/4.10.24/openshift-client-linux-4.10.24.tar.gz
tar xvzf openshift-install-linux-4.10.24.tar.gz
tar xvzf openshift-client-linux-4.10.24.tar.gz
sudo mv oc kubectl /usr/local/bin
```
次にmasterノードにSSHする際に使うキーを生成します。ここではパスワードなしで作成しています。
```
ssh-keygen -t ed25519 -f ~/.ssh/openshift-master -N "" <<<y
```

OpenShiftのインストール時に使うpull-secretを取得します。後ほどコマンド実行時に中身をペーストするので、ここではCopy pull secretを選択しましょう。

Red Hat OpenShift Cluster Management
https://console.redhat.com/openshift/install/gcp/installer-provisioned

`install-config.yaml`を作成します。
```
./openshift-install create install-config
```

デフォルトで作成されるinstall-configではWorkerノードのタイプが`m6i.large`になります。これでは少しスペックが足りないので、以下のように追記してWorkerノードのタイプを`m6i.xlarger`にします。
```install-config.yaml
apiVersion: v1
baseDomain: xxx.com
compute:
- architecture: amd64
  hyperthreading: Enabled
  name: worker
  platform:
    aws:                   #追記
      type: m6i.xlarge     #追記
  replicas: 3
...省略...
```

クラスターを作成します。
```
./openshift-install create cluster
```

しばらくして作成が完了すると、コンソールログにapiのURLとkubeadminのパスワードが表示されるので、それを使ってCLIでログインしておきます。
```
oc login -u kubeadmin -p <パスワード> https://api.aws-cluster.xxx.com:6443
```

最後にデフォルトのRouteに対して証明書を設定します。後ほどローカルでビルドしたイメージをOpenShiftの内部レジストリにPushするにあたり必要になります。
今回は`acme.sh`を使って無償のSSL証明書を発行し、OpenShiftのdefault-routerにセットします。

まずacme.shをインストールします。アドレスは任意の値を設定ください。
```
curl https://get.acme.sh | sh -s email=sample@example.com
```

次に以下のシェルスクリプトを作成して実行します。
```openshift-router-cert.sh
#!/bin/bash

export CERTDIR=$HOME/certificates
export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id)
export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key)
export LE_API=$(oc whoami --show-server | cut -f 2 -d ':' | cut -f 3 -d '/' | sed 's/-api././')
export LE_WILDCARD=$(oc get ingresscontroller default -n openshift-ingress-operator -o jsonpath='{.status.domain}')

.acme.sh/acme.sh --issue --dns dns_aws -d ${LE_API} -d *.${LE_WILDCARD}
mkdir -p ${CERTDIR}
.acme.sh/acme.sh --install-cert -d ${LE_API} -d *.${LE_WILDCARD} --cert-file ${CERTDIR}/cert.pem --key-file ${CERTDIR}/key.pem --fullchain-file ${CERTDIR}/fullchain.pem --ca-file ${CERTDIR}/ca.cer

oc delete secret router-certs -n openshift-ingress
oc create secret tls router-certs --cert=${CERTDIR}/fullchain.pem --key=${CERTDIR}/key.pem -n openshift-ingress

oc patch ingresscontroller default -n openshift-ingress-operator --type=merge --patch='{"spec": {"defaultCertificate": { "name": "router-certs" }}}'
```
```
chmod +x openshift-router-cert.sh
./openshift-router-cert.sh
```

しばらくしてrouterが再起動し、Webコンソールアクセスして適切に証明書が適用されているか確認します。



## サービスメッシュ環境の構築
次にサービスメッシュを構成する各種Operatorをインストールしていきます。
* OpenShift Elasticsearch Operator
* Kiali Operator
* Red Hat OpenShift distributed tracing platform
* Red Hat OpenShift Service Mesh

以下のファイルを作成してapplyします。
事前に`Elasticsearch Operator`をデプロイするための`openshift-operators-redhat`プロジェクトも作成しておきます。
```servicemesh-operators.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: openshift-operators-redhat
  annotations:
    openshift.io/node-selector: ""
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  labels:
  name: elasticsearch-operator
  namespace: openshift-operators-redhat
spec:
  channel: stable
  installPlanApproval: Automatic
  name: elasticsearch-operator
  source: redhat-operators
  sourceNamespace: openshift-marketplace
  startingCSV: elasticsearch-operator.5.4.4
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: jaeger-product
  namespace: openshift-operators
spec:
  channel: stable
  installPlanApproval: Automatic
  name: jaeger-product
  source: redhat-operators
  sourceNamespace: openshift-marketplace
  startingCSV: jaeger-operator.v1.34.1-5
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: kiali-ossm
  namespace: openshift-operators
spec:
  channel: stable
  installPlanApproval: Automatic
  name: kiali-ossm
  source: redhat-operators
  sourceNamespace: openshift-marketplace
  startingCSV: kiali-operator.v1.48.2
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: servicemeshoperator
  namespace: openshift-operators
spec:
  channel: stable
  installPlanApproval: Automatic
  name: servicemeshoperator
  source: redhat-operators
  sourceNamespace: openshift-marketplace
  startingCSV: servicemeshoperator.v2.2.1
```
```
oc apply -f servicemesh-operators.yaml
```

Operatorがデプロイされました。
```
oc get operator --all-namespaces

NAME                                                AGE
elasticsearch-operator.openshift-operators-redhat   8m34s
jaeger-product.openshift-operators                  8m7s
kiali-ossm.openshift-operators                      7m58s
servicemeshoperator.openshift-operators             7m47s
```

ではサービスメッシュを構成するコンポーネントをデプロイしていきます。
まずはNamespaceの作成です。今回はIstioのコントロールプレーンを配置する`Istio-system`とBookinfoアプリをデプロイする`bookinfo`を準備します。
```
oc new-project istio-system
oc new-project bookinfo
```

次にIstioのコントロールプレーンを作成します。OpenShiftのService Mesh OperatorはOSSのIstioとは異なり、同じクラスターで複数のコントロールプレーンが持てたり、Observabilityに必要なコンポーネントをプリセットでデプロイしてくれます。詳細はOpenShiftの[公式ドキュメント](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.10/html/service_mesh/ossm-vs-community#doc-wrapper)を参照ください。
ではCRDのyamlファイルを作成してapplyします。
```servicemeshcontrolplane.yaml
kind: ServiceMeshControlPlane
apiVersion: maistra.io/v2
metadata:
  name: basic
  namespace: istio-system
spec:
  version: v2.1
  tracing:
    type: Jaeger
    sampling: 10000
  policy:
    type: Istiod
  telemetry:
    type: Istiod
  addons:
    jaeger:
      install:
        storage:
          type: Memory
    prometheus:
      enabled: true
    kiali:
      enabled: true
    grafana:
      enabled: true
```
```
oc apply -f servicemeshcontrolplane.yaml
```
Jaegerのストレージには商用の永続ストレージとして`Elasticsearch`が選択可能ですが、今回はデモのため一時的なストレージで対応しています。Elasticsearchを使う場合は[こちらのページ](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.10/html/service_mesh/distr-tracing-config-storage_jaeger-config-reference#distributed-tracing-config-auto-provisioning-es_jaeger-config-reference)を参照ください。

しばらく待つと、`istio-system`プロジェクトにIstioをはじめ、Prometheus、Jaeger、Elasticsearch、GrafanaのPodが生成されます。
```
oc get pods -n istio-system

NAME                                    READY   STATUS    RESTARTS   AGE
grafana-869946b4fc-6sm9r                2/2     Running   0          109s
istio-egressgateway-684b45584f-zk5mh    1/1     Running   0          109s
istio-ingressgateway-5964cc866b-6ng74   1/1     Running   0          109s
istiod-basic-7cff65cb5b-wfbtm           1/1     Running   0          119s
jaeger-d67c849c8-9hh9x                  2/2     Running   0          106s
kiali-67bdff5b46-kwz8b                  1/1     Running   0          80s
prometheus-668d558d98-mlnb7      	2/2     Running   0          114s
wasm-cacher-basic-d9dbcc97b-6zmz8	1/1     Running   0          101s
```

次にCRDの`ServicemeshMemberRoll`を作成します。こちらは`spec.members`にNamespace名を入力することでそのNamespaceをサービスメッシュに参加させることができます。
```servicemeshmemberroll.yaml
kind: ServiceMeshMemberRoll
apiVersion: maistra.io/v1
metadata:
  name: default
  namespace: istio-system
spec:
  members:
    - bookinfo
```
```
oc apply -f servicemeshmemberroll.yaml
```

これでアプリケーションをデプロイするための下準備が完了しました。

## Bookinfoアプリケーションのビルド・デプロイ
Bookinfoのデプロイにあたり、デフォルトのソースコードに対して少しだけ修正を加えます。コンテナが出力するログにリクエストIDを表示させます。
```
git clone https://github.com/istio/istio.git && cd istio/samples/bookinfo
vi src/reviews/reviews-application/src/main/java/application/rest/LibertyRestEndpoint.java
```
```LibertyRestEndpoint.java
…(略)...
167         } else {
168           System.out.println("[" + requestHeaders.getHeaderString("x-request-id") + "] Error    : unable to contact " + ratings_service + " got status of " + statusCode);   //エラーログの先頭に”x-request-id”ヘッダーを追記
169           return null;
170         }
…(略)...
```
```
vi src/ratings/ratings.js
```
```ratings.js
…(略)...
199           if (unavailable) {
200               getLocalReviewsServiceUnavailable(res)
201               console.log("error: 'Service unavailable'" + ' ' + JSON.stringify(req.headers))  //行追記
202           } else {

…(略)...
```
コード修正が完了したらイメージのビルド＆プッシュを行います。
今回はbastionでイメージをビルドしOpenshiftの内部レジストリにプッシュします。しかしOpenShiftの内部レジストリはデフォルトでは外部からのアクセスができないため、先にレジストリへのアクセスパスを作成します。

```
oc patch configs.imageregistry.operator.openshift.io/cluster --patch '{"spec":{"defaultRoute":true}}' --type=merge
```
しばらくするとdefault-routerが作成されます。
```
oc get route -n openshift-image-registry

NAME            HOST/PORT                                                                           PATH   SERVICES         PORT    TERMINATION   WILDCARD
default-route   default-route-openshift-image-registry.apps.aws-cluster.xxx.com          image-registry   <all>   reencrypt     None
```
dockerをインストールして内部レジストリにログインします。
```
sudo yum install -y docker 
sudo gpasswd -a $USER docker
sudo systemctl restart docker
HOST=$(oc get route default-route -n openshift-image-registry --template='{{ .spec.host }}')
docker login -u kubeadmin -p $(oc whoami -t) $HOST
```

ログインできたらイメージをプッシュします。
```
./build_push_update_images.sh v0.1 --prefix=${HOST}/bookinfo
```
しばらくして、イメージがプッシュされていることを確認します。
```
oc get is -n bookinfo

NAME                                       IMAGE REPOSITORY                                                                                                                      TAGS          UPDATED
examples-bookinfo-details-v1               default-route-openshift-image-registry.apps.aws-cluster.xxx.com/bookinfo/examples-bookinfo-details-v1               latest,v0.1   3 minutes ago
examples-bookinfo-details-v2               default-route-openshift-image-registry.apps.aws-cluster.xxx.com/bookinfo/examples-bookinfo-details-v2               latest,v0.1   3 minutes ago
...(略)...
```



デプロイの前に、Bookinfoは特定のユーザーIDで動作しますが、OpenShiftではそのIDが許可されていないため、事前に許可設定を入れておきます。
```
oc project bookinfo
oc adm policy add-scc-to-user anyuid -z bookinfo-details
oc adm policy add-scc-to-user anyuid -z bookinfo-productpage
oc adm policy add-scc-to-user anyuid -z bookinfo-ratings
oc adm policy add-scc-to-user anyuid -z bookinfo-reviews
```

ではデプロイしていきます。
```
oc apply -f platform/kube/bookinfo.yaml -n bookinfo -f networking/bookinfo-gateway.yaml
```

実はこのままデプロイしただけではサイドカーコンテナが起動しません。OSSのIstioとは違い、OpenShiftのIstioではdeploymentに明示的にannotationsを付与しないとサイドカーコンテナが立ち上がらない仕様になっています。
そのため、annotationsを付与してPodを再起動します。
```
oc patch deploy details-v1 productpage-v1 ratings-v1 reviews-v1 reviews-v2 reviews-v3 -n bookinfo --type=merge --patch='{"spec": {"template": { "metadata": { "annotations": { "sidecar.istio.io/inject": "true" }}}}}'
oc rollout restart -n bookinfo deploy details-v1 productpage-v1 ratings-v1 reviews-v1 reviews-v2 reviews-v3
```
Podを確認すると、全てのPodでサイドカーコンテナが立ち上がっていることを確認できます。
```
oc get pods -n bookinfo

NAME                              READY   STATUS        RESTARTS   AGE
details-v1-76b9bff7cd-5b6x2	  2/2     Running	0          40s
productpage-v1-79cd499988-frj9m   2/2     Running	0          40s
ratings-v1-5dcfcc5b6-tp28z        2/2     Running	0          39s
reviews-v1-5d9b67ddcd-5d8vp	  2/2     Running	0          39s
reviews-v2-6888fb4c96-kfw7t	  2/2     Running	0          39s
reviews-v3-7447bbd598-j25wd	  2/2     Running	0          39s
```

reviewsにおいてはv1,v2,v3の3つがデプロイされますが、デフォルトだとランダムに振り分けられます。ここでは`ratings`にアクセスする必要があるため、全てのリクエストを`reviews-v2`に向けるように`VirtualService`をデプロイします。
```
cat networking/virtual-service-reviews-test-v2.yaml | sed -e s/"subset: v1"/"subset: v2"/g | oc apply -f - -n bookinfo
```

最後に`DestinationRule`をデプロイします。
```
oc apply -f networking/destination-rule-all.yaml
```

では以下のコマンドでURLを取得してブラウザでアクセスしてみましょう。
```
export GATEWAY_URL=$(oc -n istio-system get route istio-ingressgateway -o jsonpath='{.spec.host}')
echo "http://$GATEWAY_URL/productpage"
```
![スクリーンショット 2022-08-12 3.18.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/55f59199-227d-fd1b-9f0f-4e4e90ca380a.png)

## アラートの設定
サービスメッシュ環境とサンプルアプリがデプロイできたので、これからトラブルシューティングに向けた設定を行います。
まずはアラートの設定です。OpenShift Service MeshではプリセットでPrometheusやGrafanaがデプロイされますが、アラート設定用のコンポーネントは提供されておりません。そのため、自分で設定変更を行い環境準備が必要です。

今回はプリセットのPrometheusでアラートルールを設定し、別途デプロイする`Alertmanager`でSlackに通知を飛ばします。

まず以下のyamlファイルを作成します。ファイル名はこのまま設定してください。istio全体のリクエストの内、500番台以外のステータスコードの割合が95%を下回るとアラートを発出するルールです。
```alerting_rules.yml
groups:
- name: alert sample
  rules:
  - alert: IstioHigh5xxErrorRate
    expr: sum(rate(istio_requests_total{reporter="source", response_code!~"5.*"}[5m])) / sum(rate(istio_requests_total{reporter="source"}[5m])) * 100 < 95
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: Istio high 5xx error rate
      description: "Global Success Rate (non-5xx responses) < 95% .\n  VALUE = {{ $value }} %"
```

このファイルを使って`ConfigMap`を作成します。
```
oc create configmap alert-rule --from-file=./alerting_rules.yml -n istio-system
```

次にPrometheusにこのアラートルールを設定するよう、PrometheusのDeploymentを編集します。
先ほどのConfigMapの設定ファイルを`/etc/config`配下にマウントします。
```
oc edit deploy/prometheus -n istio-system
```
```
...(略)...
180         volumeMounts:
181         - mountPath: /etc/prometheus
182           name: config-volume
183         - mountPath: /etc/config    #追記
184           name: alert-volume        #追記
...(略)...
218       - configMap:
219           defaultMode: 420
220           name: prometheus
221         name: config-volume
222       - configMap:                  #追記
223           defaultMode: 420          #追記
224           name: alert-rule          #追記
225         name: alert-volume          #追記
...(略)...
```
Podが再起動したら、以下のコマンドでPrometheusのURLを取得してブラウザでアクセスします。
```
oc get route prometheus -n istio-system --template='{{ .spec.host }}'
```

`Alert`タブを選択すると、`alerting_rules.yml`の設定内容が反映されていることを確認できます。
![スクリーンショット 2022-08-12 3.40.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/f0a662d2-b5f7-0b71-609a-ce077dfb040e.png)

次にAlertmanagerをデプロイしてSlackへの通知設定を行います。
まずPrometheus Operatorをインストールします。
以下のファイルを作成して、applyします。
```prometheus-operator.yaml
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: istio-system-operatorgroup
  namespace: istio-system
spec:
  targetNamespaces:
  - istio-system
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: prometheus
  namespace: istio-system
spec:
  channel: beta
  installPlanApproval: Automatic
  name: prometheus
  source: community-operators
  sourceNamespace: openshift-marketplace
  startingCSV: prometheusoperator.0.56.3
```
```
oc apply -f prometheus-operator.yaml -n istio-system
```
次にAlertmanagerの通知先の設定ファイルを作成します。
slackの通知先情報(URLやチャンネル名)を記載します。

```alertmanager.yaml
global:
  resolve_timeout: 1m
  slack_api_url: >-
    https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX
receivers:
  - name: test-slack
    slack_configs:
      - channel: istio-alert
        title: "{{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}"
        text: "{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}"
route:
  receiver: test-slack
```
設定ファイルを`Secret`としてデプロイします。
```
oc create secret generic alertmanager --from-file=./alertmanager.yaml -n istio-system
```

次にAlertmanagerをデプロイするために`Alertmanager`CRDをデプロイします。
```alertmanager.yaml
apiVersion: monitoring.coreos.com/v1
kind: Alertmanager
metadata:
  name: alertmanager-main
  namespace: istio-system
spec:
  alertmanagerConfigSelector: {}
  replicas: 1
  configSecret: alertmanager
```
```
oc apply -f alertmanager.yaml
```

しばらくするとAlertmanagerの`StatefulSets`が立ち上がります。
このコンテナに対して、Prometheusからアラートを飛ばします。
Prometheusの設定ファイル（`ConfigMap`）を編集して、Alertmanager向けにアラート情報を連携するように設定します。
```
oc edit cm prometheus -n istio-system
```
```
...(略)...
  7   prometheus.yml: |-
  8     global:
  9       evaluation_interval: 1m
 10       scrape_interval: 15s
 11       scrape_timeout: 10s
 12     alerting:                                                            # 追記 
 13       alertmanagers:                                                     # 追記
 14       - static_configs:                                                  # 追記
 15         - targets:                                                       # 追記
 16           - alertmanager-operated.istio-system.svc.cluster.local:9093    # 追記
 17     rule_files:
 18     - /etc/config/recording_rules.yml
 19     - /etc/config/alerting_rules.yml
 20     - /etc/config/rules
 21     - /etc/config/alerts
...(略)...
```
変更内容を反映させます。
```
oc rollout restart deploy/prometheus -n istio-system
```

これでサービスの通信の不具合が発生した際、アラートがSlackに飛ぶよう設定できました。

## OpenShift Loggingのインストール
最後に、コンテナのログを収集する環境を導入します。
まず`openshift-logging`プロジェクトを作成します。
```
oc new-project openshift-logging
```
次に以下のyamlファイルでOpenshift Logging Operatorをインストールします。
```logging-operator.yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: openshift-logging
  annotations:
    openshift.io/node-selector: ""
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: openshift-logging
  namespace: openshift-logging
spec:
  targetNamespaces:
  - openshift-logging
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: cluster-logging
  namespace: openshift-logging
spec:
  channel: stable
  installPlanApproval: Automatic
  name: cluster-logging
  source: redhat-operators
  sourceNamespace: openshift-marketplace
  startingCSV: cluster-logging.5.4.4
```
```
oc apply -f logging-operator.yaml
```
次に`ClusterLogging`CRDでロギングスタックをデプロイします。
```cluster-logging.yaml
kind: ClusterLogging
apiVersion: logging.openshift.io/v1
metadata:
  name: instance
  namespace: openshift-logging
spec:
  managementState: Managed
  logStore:
    type: elasticsearch
    elasticsearch:
      nodeCount: 3
      redundancyPolicy: SingleRedundancy
      storage:
        storageClassName: gp2
        size: 200G
      resources:
        requests:
          memory: "8Gi"
    retentionPolicy:
      application:
        maxAge: 7d
  visualization:
    type: kibana
    kibana:
      replicas: 1
  collection:
    logs:
      type: fluentd
      fluentd: {}
```
```
oc apply -f cluster-logging.yaml
```
これでロギング環境が作成できました。


## トラブルシューティング
ようやく環境の準備ができたので、この状態でratingsを別のイメージに切り替えてみます。
```
oc project bookinfo
oc set image deployment/ratings-v1 ratings=image-registry.openshift-image-registry.svc:5000/bookinfo/examples-bookinfo-ratings-v-unavailable:v0.1
```

新しいPodがデプロイできたら、watchコマンドでBookinfoにアクセスします。
```
watch -n 1 http://$GATEWAY_URL/productpage
```
しばらくすると、Slackにアラートが通知されます。
![スクリーンショット 2022-08-12 8.52.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/4ea6dbc9-6601-7687-4250-bb7baab40f93.png)

状況を把握するために、Grafanaダッシュボードで異常な値が出ていないか確認します。以下のコマンドでGrafanaのURLを取得してブラウザでアクセスします。
```
oc get route grafana -n istio-system --template='{{ .spec.host }}'
```
アクセス後、Iatio Service Dashboardでratingsのサービスを確認するとClient Success Rateが著しく下がっていることが分かります。
![grafana.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/bb9fa327-fc29-ad53-20dd-0a435723d686.png)


次にKialiを使って、このratingsが使われているサービスがどのような構成になっているのかを確認します。
以下のコマンドでKialiのURLを取得しブラウザでアクセスします。
```
oc get route kiali -n istio-system --template='{{ .spec.host }}'
```

![KialiのGraph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/4427537d-e952-423b-ce9e-0f7ce5605e77.png)

Kialiを見るとたしかにreviewsとratings間の通信でエラーが発生していることが分かります。
では次はトラフィックフローをリクエスト単位の視点で確認していきます。以下のコマンドでJaegerのURLを取得してブラウザでアクセスします。
```
oc get route jaeger -n istio-system --template='{{ .spec.host }}'
```

![エラーリクエストのトレース情報(Jaeger).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/b8e2766b-e153-4c4f-7877-eb287ab4616a.png)

ここまで問題箇所が特定できれば、あとは実際にコンテナのログを確認しにいきます。Jaegerの画面でエラーとなったリクエストのID（x-request-id）が確認できましたので、Kibanaでコンテナのログを検索しエラーの詳細を解析します。
以下のコマンドでKibanaのURLを取得してブラウザでアクセスします。
```
oc get route kibana -n openshift-logging --template='{{ .spec.host }}'
```
アクセス後、インデックスを作成します。パターンに`app-*`と入力して、`Next Step`を選択します。
![スクリーンショット 2022-08-12 9.13.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/9f7318b2-c45d-b194-bf0e-2a2819ec49c6.png)
Time Filter field nameで`@timestamp`を選択して、インデックスを作成します。
![スクリーンショット 2022-08-12 9.13.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/39ffa93a-5f76-7a90-a673-0d1775818181.png)

この状態でDiscover画面でmessage: “\*<リクエストID>\*”を検索すると、エラーとなったリクエスト関連のコンテナログを取得できます。検索したログを見ると、reviewsでratingsに対するアクセスが503エラーとなっていることや、ratingsで`error: ‘Service unavailable’`というログが出力されていることが分かります。このように、ログの出力にリクエストIDを含めることで、ひとつのリクエストに起因したログの検索が可能になります。
![図X リクエストIDに紐づくエラーログの表示(Kibana).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/796526b9-b844-a4e7-a0d7-de7137086822.png)

このログをもとにratingsのソースコードを確認すると、先ほど挿入したエラーログが出力されていることが分かります。
```
view istio/samples/bookinfo/src/ratings/ratings.js

...(略)...
198       else if (process.env.SERVICE_VERSION === 'v-unavailable' || process.env.SERVICE_VERSION === 'v-unhealthy') {
199           if (unavailable) {
200               getLocalReviewsServiceUnavailable(res)
201               console.log("error: 'Service unavailable'" + ' ' + JSON.stringify(req.headers))
202           } else {
203               getLocalReviewsSuccessful(res, productId)
204           }
...(略)...
```
この部分は環境変数SERVICE_VERSIONが”v-unavailable”か”v-unhealthy”の場合に呼ばれるため、このコンテナにこれらの環境変数が含まれていることを確認してみます。
```
oc exec -it ratings-v1-76c754d45c-rz2rr -c ratings -- env | grep SERVICE_VERSION

SERVICE_VERSION=v-unavailable
```
たしかにコンテナに環境変数が設定されていました。今回のエラーは、この環境変数によって1分ごとに503エラーを返すプログラムが動作しているために発生しました。
```ratings.js
…(略)... 
25 if (process.env.SERVICE_VERSION === 'v-unavailable') {
 26     // make the service unavailable once in 60 seconds
 27     setInterval(function () {
 28         unavailable = !unavailable
 29     }, 60000);
 30 }
…(略)...
```

# おわりに
簡単ですが、OpenShiftのインストールからサービスメッシュ環境を構築し、サンプルアプリでのトラブルシューティングの内容をまとめてみました。今回の設定はデモ用途のため本番では適さない設定もありましたが、サービスメッシュ環境において各監視ツールを使い、どんなプロセスで解析するかのイメージはつけられるかと思います。