Service Meshの範囲をインクリメンタルに広げる

本記事では、Istioを用いてKubernetesの1つのNamespace内でService Meshを実現することを目的としています。(もちろん、Service MeshはKubernetesのNamespaceやクラスタに閉じるものではありません。)
Service Meshが必要だと感じるプロダクトは既にアーキテクチャが複雑になっているはずなので、Istioのチュートリアルで出てくるBookinfo ApplicationほどスムーズにIstioを導入することは難しいと思います。いきなり広い範囲にService Meshを導入するより、Service Meshの範囲をインクリメンタルに広げていく方が、Service Mesh導入の難易度は下がります。影響範囲が局所化されるので、問題が発生しても原因の特定が容易になるためです。また、サービスとして動く状態を維持しつつ、少しずつリリースしていくことも可能です。

Service Meshの範囲をインクリメンタルに広げていくためには十分な量の自動テスト(Podに対するテストと複数のマイクロサービスを結合したE2Eテスト)が必要です。また、それらのテストを高速に実行できることが望ましいです。

Service Meshの範囲をインクリメンタルに広げる手順

Service Meshの範囲をインクリメンタルに広げる流れを大まかに説明すると以下のようになります。(KubernetesやIstioのinstallは省略します。)
※ この手順はバージョンが1.1.8のIstioで試しています。

  1. 1つPodにのみ対してistio-proxy(Envoy)をinjectする

1~4の流れをを別のPodに対して同様に行っていき、最終的にNamespace内でService Meshを実現します。Blue-green Deploymentsや認証・認可、Circuit Breakingなどで必要なものがある場合も、一旦サービスが正しく動くことを確認してから設定すれば良いと思います。
作業の単位が細かく、かつE2Eテストを流す頻度が高いと感じるかもしれません。これはテスト駆動開発の「歩幅」と同じで微調整し続けるものだと思います。Istioの導入に不安を感じているのであれば、細かなステップで始めるのがよいでしょう。

1つPodにのみ対してistio-proxy(Envoy)をinjectする

まず、istio-proxyをinjectするPodを1つ決めます。マイクロサービスの依存関係の末端など、通信の経路が少ないPodからinjectしていく方がサービス全体への影響は少ないでしょう。
istioctlで以下のようにistio-proxyを対象のPodに手動でinjectすることができます。

$ istioctl kube-inject -f <your-app-spec>.yaml | kubectl apply -f 

また、1つずつinjectせず、自動的にistio-proxyがinjectされるようなlabelをNamespaceに付けることができます。

$ kubectl label namespace <namespace> istio-injection=enabled

最終的には1つのNamespace内でService Meshを実現することを目的としているので、 istio-injection=enabled のラベルを付けておくとよいでしょう。ただし、今はインクリメンタルにService Meshの範囲を広げていきたいので、最初にistio-proxyをinjectする対象以外はistio-proxyがinjectされないようDeploymentに sidecar.istio.io/inject: "false"というアノテーションを一旦付けておきましょう。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: foo
spec:
template:
metadata:
labels:
app: foo
version: blue
annotations:
sidecar.istio.io/inject: "false"
spec:
containers:
- name: foo
image: foo

Namespace内Podをすべてデプロイし直してみてください。対象のPodにはistio-proxyがSidecarとして起動し、sidecar.istio.io/inject: "false"というアノテーションが付いているPod(istio-proxyをinjectしたいPod以外すべて)は以前のままになっています。istio-proxyがSidecarとして起動しているPodはインバウンドとアウトバウンドのトラフィックがすべてistio-proxyを経由することになります。これはPod起動時に istio-init というコンテナがiptablesのルールを書き換えているためです。

現段階では、1つのPodにのみistio-proxyがinjectされただけです。気をつけるとすれば、リトライです。デフォルトだと、5xx系のエラーが発生した場合は最大2回リトライするので、リトライしても問題がない実装になっていることを確認してください。リトライする回数や条件はIstioのドキュメントで確認できなかったので、コードで確認しました。

RetryOn のところに書かれているリトライの条件はEnvoyのドキュメントで詳細を確認できます。

自動テストを流す

次はPodに対するテストとE2Eテストを流しましょう。Podに対するトラフィックがistio-proxyを経由しても問題が起こっていないことを確認しています。テストが通っていることだけでなく、テスト実行時間が伸びていないことも確認するとよいでしょう。

Virtual ServiceとDestination Ruleを書く

次はistio-proxをinjectしたPodに対してTraffic Managementのための最低限必要なVirtual ServiceとDestination Ruleを書きます。Virtual ServiceとDestination Ruleの例は以下のとおりです。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: foo-vs
namespace: bar
spec:
gateways:
- default/default-gateway
hosts:
- foo.bar.svc.cluster.local
http:
- route:
- destination:
host: foo.bar.svc.cluster.local
subset: blue
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: foo-dr
namespace: bar
spec:
host: foo.bar.svc.cluster.local
subsets:
- name: blue
labels:
version: blue

上記のVirtual ServiceとDestination RuleをapplyするとIstioのGateway経由でPodにアクセスできるようになります。(Gatewayで許可しているHostのルールに foo.bar.svc.cluster.localが該当していない場合はVirtual Serviceの hosts を追加・修正する必要があります。)また、最終的にはBlue-green Deploymentsしたいですが、一旦は決め打ちで1つのバージョン(この例ではblue)に固定しています。

IstioのGatewayを経由させて自動テストを流す

最後にIstioのGatewayを経由させてPodに対するテストとE2Eテストを流し、Virtual ServiceとDestination Ruleに書いたルーティングに問題がないかを確認します。また、Kialiを使用すれば、設定ファイルが間違っている場合、YAMLのどこが間違っているかも表示されます。

問題がなかった場合は1~4の手順を別のPodに対しても行ってください。次の対象となるPodにistio-proxyをinjectする際、一旦付けていたsidecar.istio.io/inject: "false"というアノテーションを外し、再度デプロイするだけで、対象のPodにistio-proxyがSidecarとして起動します。残りの手順は2~4と同様です。また、性能の劣化に懸念がある場合、Fortioで気軽に負荷検証を行うことができます。

このようにTraffic Managementのための最低限必要な設定を完了してから、Blue-green DeploymentsやRetry、Circuit Breakingなど、プロダクトに必要なものを設定していく方がスムーズに作業を進められると考えています。また、Blue-green DeploymentsやRetry、Circuit Breakingなどの設定も細かなステップで適用していくことが可能です。

まとめ

Service Meshは小さく始めることができます。Service Meshに対する知見が少ない場合でも、作業の単位を小さく保つことで、問題が発生した際の原因特定が容易になり、Service Mesh導入の難易度は下がります。また、こまめにE2Eを流すことで、不具合が発生していないことを確かめながら、自信を持って前に進むことができます。

Software engineer