サービスメッシュと Istio の基本を理解する

この記事では、マイクロサービスやクラウドコンピューティング、DevOps の文脈で登場する サービスメッシュ について解説します。登場した背景や概念を見た後、サービスメッシュを実現するための OSS である Istio についても解説し、実際にサンプルアプリケーションを動かしてみます。

サービスメッシュとは

マイクロサービスの課題

  • マイクロサービスは、アプリケーションのアーキテクチャの一つです。
  • 従来のモノリシックなアーキテクチャと違うのは、アプリケーションのコア機能ごとに細分化する点です。
    • 各機能は他のサービスに影響を与えることなく開発・デプロイ・実行ができます。
    • コンポーネント間の通信は API を通じて行われます。

f:id:linkode-okazaki:20200616154911p:plain

  • サービスが増えて、通信が複雑になってくると、マイクロサービスにおいては以下のようなことが課題となってきます。

Service Discovery(サービスの発見)

  • 依存関係にあるサービスの接続情報の管理が煩雑になってくる
  • 従来では DNS やロードバランサによって実現していたが、デプロイやリリースの度にそれらの設定変更を行うことは、ソフトウェアの迅速な提供を妨げる

Fault Isolation(障害の分離)

  • あるサービスで障害が起こった時に、他のサービスに連鎖して大規模な障害につながることを防ぐ
  • 障害があったサービスへのアクセスを遮断する サーキットブレーカー(CircuitBreaker)パターン を実現するためには各サービスの外部呼び出し側に実装せねばならないため、それぞれのサービスに実装するのは大きなコストになる

Observability(分散トレーシング)

  • マイクロサービスでは、1 つのリクエストが複数のサービスにまたがるため、全体の処理の流れを把握するのが困難になる
  • あるサービスでレスポンスの悪化やエラーが発生した場合、どのサービスで問題が発生したかを確認することが難しい

Security(認証・認可)

  • アクセス元のサービスの権限に応じて、提供するコンテンツを制御する仕組みを各サービスで個別実装することは大きなコストとなる

サービスメッシュの概念

  • 上記のような問題点を解決すべく登場したのが サービスメッシュ です。
  • すべてのサービスに対して サイドカープロキシ を用意し、サービス間の通信をすべてサイドカープロキシ経由とします。
  • 下図のように、全てのサービスがメッシュ状の接続されることから「サービスメッシュ」と呼ばれます。

f:id:linkode-okazaki:20200616154919p:plain

Istio : サービスメッシュを実現するソフトウェア

1. トラフィック管理

  • サービス間のトラフィックAPI コールのフローを制御
  • サーキットブレーカー、タイムアウト、リトライなどのプロパティ構成が容易になる
    • それにより、A/B テスト、カナリア展開、1%単位でのトラフィック分割による段階的なアプリケーション展開などを簡単に設定できる
    • また、トラフィックの可視性が向上し、問題が発生する前に検出可能になる

2. セキュリティ

  • サービス間の通信の認証・認可、暗号化を提供
  • 様々なプロトコルやランタイムに渡って一貫したポリシーを適用できる
  • K8s のネットワークポリシーとともに使用することで、ネットワーク層やアプリケーション層での pod 間の通信、service 間の通信を安全に行える

3. 可観測性

  • トレース機能、監視機能、ログ機能を提供
    • カスタムダッシュボードを利用することで、すべてのサービスのパフォーマンスを可視化し、そのパフォーマンスが他のプロセスにどのような影響を与えているかを確認できる。

Istio のアーキテクチャ

f:id:linkode-okazaki:20200616155414p:plain

  • Istio のサービスメッシュは大きく以下の 2 つに分割されます。
    • データプレーン :マイクロサービス間のすべてのネットワーク通信を仲介・制御する
      • すべてのポッドに Envoy (後述)をサイドカープロキシとして配備する
      • すべてのサービス間通信の観測結果を収集し、レポートする
    • コントロールプレーン :サービス間通信をルーティングするプロキシの管理・設定を行う

Envoy

  • Lyft 社が開発したサービスメッシュを実現するためのソフトウェアです。
  • Istio においては拡張されたものがサイドカープロキシとして配備され、以下の機能を提供します
    • サービスディスカバリ
    • 負荷分散
    • TLS終端
    • HTTP/2 と gRPC のプロキシ
    • サーキットブレーカー
    • ヘルスチェック
    • トラフィックの分割(A/Bテスト)
    • Fault injection(フォールト・インジェクション)
    • メトリクスの取得

Istiod

  • サービス検出・構成・証明書管理を行います
  • 以下の 3 つの構成要素を含みます:
    • Mixer : Envoy を通して各サービスのデータを収集し、その情報を元にアクセスコントロールを行う
      • プラグインモデルを採っており、柔軟なカスタマイズが可能
    • Pilot : サービスディスカバリやトラフィック管理などを行う
      • 例えば、A/Bテスト・カナリーリリースの実現や、タイムアウト・リトライ・サーキットブレーカーなどの手法を用いて Fault Isolation(障害の分離)の問題を解決することができる
    • Citadal : サービス間認証とエンドユーザ認証を実現する
      • この認証を利用することで、ポリシーベースでサービスメッシュを管理することができる。

実際に Istio を利用してみる

  • 実際に Istio を利用したアプリケーションをデプロイしてみます。

アプリケーションの構成

f:id:linkode-okazaki:20200616155410p:plain

  • 以下の 4 つのマイクロサービスから構成されています
    • productpage : ページに入力するために detailsreviews を呼び出す(Python で実装)
    • details : 本の情報を含む。(Ruby で実装)
    • reviews : 本のレビュー情報を含む。 rating を呼び出す。(Java で実装)
    • ratings : 本のレビュー情報に依るランキング情報を含む。(Node.js で実装)
  • reviews は以下の 3 つのバージョンがあります
    • v1: ratings を呼ばない
    • v2: ratings を呼び、黒い星での 5 段階評価を表示する
    • v3: ratings を呼び、赤い星での 5 段階評価を表示する

Istio の動作環境を整える

  • 今回は、ローカル環境の Minikube (バージョン 0.28.1 以上)を利用します。
  • 通常のアプリケーションと一緒に Istio を動かす場合、メモリ 8192 MB、CPU 4 つを割り当てることを推奨されているため、 Minikube 起動時に下記のようなオプションを指定して起動します。
$ minikube start --memory=8192 --cpus=4 --vm-driver=hyperkit --kubernetes-version=v1.18.3
  • Istio をインストールするのに必要なファイル群をダウンロードします
$ curl -L https://istio.io/downloadIstio | sh -
$ cd istio-1.6.1
$ export PATH=$PWD/bin:$PATH
  • Istio をインストールします。
$ istioctl install --set profile=demo
Detected that your cluster does not support third party JWT authentication. Falling back to less secure first party JWT. See https://istio.io/docs/ops/best-practices/security/#configure-third-party-service-account-tokens for details.
✔ Istio core installed
✔ Istiod installed
✔ Egress gateways installed
✔ Ingress gateways installed
✔ Addons installed
✔ Installation complete
  • アプリケーションをデプロイする前に、 default 名前空間istio-injection=enabled というラベルをつけておきます。
  • これにより、default 名前空間 にデプロイされるサービスに対し、Istio が自動的に各サービスのコンテナへサイドカーをデプロイしてくれるようになります。
$ kubectl label namespace default istio-injection=enabled
namespace/default labeled
  • 以上で、 Istio を起動する準備は整いました。

サンプルアプリケーションを動かす

  • 次のコマンドで必要なマニフェストを適用し、アプリケーションが起動するのを待ちます。無事適用されると 3 つのマイクロサービスがデプロイされます。
$ kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
service/details created
serviceaccount/bookinfo-details created
deployment.apps/details-v1 created
service/ratings created
serviceaccount/bookinfo-ratings created
deployment.apps/ratings-v1 created
service/reviews created
serviceaccount/bookinfo-reviews created
deployment.apps/reviews-v1 created
deployment.apps/reviews-v2 created
deployment.apps/reviews-v3 created
service/productpage created
serviceaccount/bookinfo-productpage created
deployment.apps/productpage-v1 created

$ kubectl get services
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
details       ClusterIP   10.99.129.73    <none>        9080/TCP   18m
kubernetes    ClusterIP   10.96.0.1       <none>        443/TCP    55d
productpage   ClusterIP   10.101.23.113   <none>        9080/TCP   18m
ratings       ClusterIP   10.107.80.196   <none>        9080/TCP   18m
reviews       ClusterIP   10.107.79.61    <none>        9080/TCP   18m

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
details-v1-6c9f8bcbcb-qfzjj       2/2     Running   0          19m
productpage-v1-7df7cb7f86-7fscw   2/2     Running   0          19m
ratings-v1-65cff55fb8-h2dk7       2/2     Running   0          19m
reviews-v1-7bccdbbf96-zbspt       2/2     Running   0          19m
reviews-v2-7c9685df46-bq774       2/2     Running   0          19m
reviews-v3-58fc46b64-f2rtt        2/2     Running   0          19m

アプリケーションのマニフェストファイルの内容(クリックして展開)

##################################################################################################
# Details サービスの内容
##################################################################################################
apiVersion: v1
kind: Service
metadata:
  name: details
  labels:
    app: details
    service: details
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: details
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-details
  labels:
    account: details
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: details-v1
  labels:
    app: details
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: details
      version: v1
  template:
    metadata:
      labels:
    app: details
    version: v1
    spec:
      serviceAccountName: bookinfo-details
      containers:
      - name: details
    image: docker.io/istio/examples-bookinfo-details-v1:1.15.1
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 9080
---
##################################################################################################
# Ratings サービスの内容
##################################################################################################
apiVersion: v1
kind: Service
metadata:
  name: ratings
  labels:
    app: ratings
    service: ratings
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: ratings
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-ratings
  labels:
    account: ratings
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ratings-v1
  labels:
    app: ratings
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ratings
      version: v1
  template:
    metadata:
      labels:
    app: ratings
    version: v1
    spec:
      serviceAccountName: bookinfo-ratings
      containers:
      - name: ratings
    image: docker.io/istio/examples-bookinfo-ratings-v1:1.15.1
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 9080
---
##################################################################################################
# Reviews サービスの内容
##################################################################################################
apiVersion: v1
kind: Service
metadata:
  name: reviews
  labels:
    app: reviews
    service: reviews
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: reviews
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-reviews
  labels:
    account: reviews
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: reviews-v1
  labels:
    app: reviews
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
      version: v1
  template:
    metadata:
      labels:
    app: reviews
    version: v1
    spec:
      serviceAccountName: bookinfo-reviews
      containers:
      - name: reviews
    image: docker.io/istio/examples-bookinfo-reviews-v1:1.15.1
    imagePullPolicy: IfNotPresent
    env:
    - name: LOG_DIR
      value: "/tmp/logs"
    ports:
    - containerPort: 9080
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: wlp-output
      mountPath: /opt/ibm/wlp/output
      volumes:
      - name: wlp-output
    emptyDir: {}
      - name: tmp
    emptyDir: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: reviews-v2
  labels:
    app: reviews
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
      version: v2
  template:
    metadata:
      labels:
    app: reviews
    version: v2
    spec:
      serviceAccountName: bookinfo-reviews
      containers:
      - name: reviews
    image: docker.io/istio/examples-bookinfo-reviews-v2:1.15.1
    imagePullPolicy: IfNotPresent
    env:
    - name: LOG_DIR
      value: "/tmp/logs"
    ports:
    - containerPort: 9080
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: wlp-output
      mountPath: /opt/ibm/wlp/output
      volumes:
      - name: wlp-output
    emptyDir: {}
      - name: tmp
    emptyDir: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: reviews-v3
  labels:
    app: reviews
    version: v3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
      version: v3
  template:
    metadata:
      labels:
    app: reviews
    version: v3
    spec:
      serviceAccountName: bookinfo-reviews
      containers:
      - name: reviews
    image: docker.io/istio/examples-bookinfo-reviews-v3:1.15.1
    imagePullPolicy: IfNotPresent
    env:
    - name: LOG_DIR
      value: "/tmp/logs"
    ports:
    - containerPort: 9080
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: wlp-output
      mountPath: /opt/ibm/wlp/output
      volumes:
      - name: wlp-output
    emptyDir: {}
      - name: tmp
    emptyDir: {}
---
##################################################################################################
# Productpage サービスの内容
##################################################################################################
apiVersion: v1
kind: Service
metadata:
  name: productpage
  labels:
    app: productpage
    service: productpage
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: productpage
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-productpage
  labels:
    account: productpage
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: productpage-v1
  labels:
    app: productpage
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: productpage
      version: v1
  template:
    metadata:
      labels:
    app: productpage
    version: v1
    spec:
      serviceAccountName: bookinfo-productpage
      containers:
      - name: productpage
    image: docker.io/istio/examples-bookinfo-productpage-v1:1.15.1
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 9080
    volumeMounts:
    - name: tmp
      mountPath: /tmp
      volumes:
      - name: tmp
    emptyDir: {}
---

  • 稼働したら、確認のため、下記のコマンドを実行して、ページタイトルが表示されることを確認します
$ kubectl exec -it $(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}') -c ratings -- curl productpage:9080/productpage | grep -o "<title>.*</title>"
<title>Simple Bookstore App</title>
  • ここまでの状態では、外部からアクセスができないので、 Gateway と呼ばれるリソースを作成します。
$ kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created
  • Gateway は、サービスメッシュのゲートウェイ(一番外側に位置するロードバランサとなる部分)を設定するためのリソースです。以下のようなことを設定できます。
  • Gateway とともに、 VirtualService リソースも作成しています。

    • VirtualService は、URL のパスごとのリクエストルーティング等を可能にする
    • Gateway だけでは、リクエストの設定がなされていないため、 VirtualService も併せて利用する
  • 適用したマニフェストの中身はこちら:

GatewayVirtualServiceマニフェストファイルの内容(クリックして展開)

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  selector:
    istio: ingressgateway # ingressgateway のラベルを持つ Pod を設定対象のロードバランサーとして指定
  servers:
  - port: # 外部からの通信を受け付けるための設定:ポート番号とプロトコル
      number: 80
      name: http
      protocol: HTTP
    hosts: # この Gateway リソースが受け付ける対象のホスト名を指定
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
  - "*"
  gateways:
  - bookinfo-gateway
  http:
  - match:
    - uri:
    exact: /productpage
    - uri:
    prefix: /static
    - uri:
    exact: /login
    - uri:
    exact: /logout
    - uri:
    prefix: /api/v1/products
    route:
    - destination:
    host: productpage
    port:
      number: 9080

  • 構成に問題がないことを確認します。
$ istioctl analyze
✔ No validation issues found when analyzing namespace: default.
  • 新しいターミナルウィンドウで次のコマンドを実行して、Istio 入力ゲートウェイトラフィックを送信する Minikube トンネルを開始します。
$ minikube tunnel
  • アクセスのためのホスト名とポート番号を取得します。Minikube を利用している場合は、下記のコマンドで確認します。
$ kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}'
31345
$ minikube ip
192.168.64.3 # INGRESS_HOST
  • 以上の結果から、アクセス先のURLは http://192.168.64.3:31345 となります。

  • ブラウザで http://192.168.64.3:31345/productpage アクセスし、以下のような画面が表示されることを確認します。

f:id:linkode-okazaki:20200616155001p:plain

ダッシュボードを見てみる

  • Istio のデモインストールに含まれる Kiali のダッシュボードを見てみます。
  • このダッシュボードは、トポロジを表示してメッシュの状態を示すことで、サービスメッシュの構造を理解するのに役立ります。
  • 以下のコマンドで、Kiali のダッシュボードにアクセスします。
$ istioctl dashboard kiali
  • ログイン画面が表示されたら、ユーザー名とパスワードを入力します。
    • デフォルトのユーザー名は admin 、パスワードは admin

f:id:linkode-okazaki:20200616155015p:plain f:id:linkode-okazaki:20200625174528p:plain f:id:linkode-okazaki:20200616155005p:plain

  • 上記のように、デプロイしたアプリケーションのメッシュが可視化されます。

まとめ

  • 分散システムアーキテクチャパターンである「サービスメッシュ」の基本的な概念を確認しました。
  • サービスメッシュを実現する方法として、代表的なソフトウェアである Istio を試用してみました。
    • 今回は、ローカル環境の Minikube 上で Istio を使ったアプリケーションを稼働させてみましたが、次の機会に、 Amazon EKS 上で稼働させてみたいと思います。

参考資料