Service meshとは何か

by Taichi Nakashima,

Microservicesの世界においてService meshは大きなキーワードになった.KubeCon 2017やKubeCon 2018 EUにおいても多くのセッションをService mesh(もしくはその代表格であるIstio)が占めており注目の高さも伺える.もちろんMicroservicesを進めるMercariにおいても導入を検討しており今後重要なコンポーネントの1つになると考えている.本記事ではそもそもなぜService meshという考え方が登場したのか,なぜ重要なのか? その実装としてのIstioとは何で何ができるのか? について簡単にまとめてみる.

参考文献

Service meshを一番理想的な形でサービスに使い始めその考え方を広めたのはLyftだ(と思う).LyftはIstioのコアのコンポーネントであるEnvoyを開発しそれを用いてService meshを構築し自社のMicroservices化の課題を解決してきた.Service meshの初期衝動や真価を知るにはLyftの事例を見るのが良い.Envoyの作者であるMatt KleinによるKubeCon2017での発表“The mechanics of deploying Envoy at Lyft”や彼が寄稿しているSeeking SREの13章"The Service Mesh: Wrangler of Your Microservices?“などがとても参考になる.

またService meshを広めるきっかけとなったオープンソースのプロジェクトはIstioである.Istioはまだ登場したばかりであるが既に書籍がある.RedHatの開発者によるIntroducing Istio Service Mesh for Microservices(無料!)を読むとIstioの大まかな概要を掴めると思う.

さらに(これはまだ自分が読み途中だが)Zero Trust NetworksもService meshを知る上で重要な考え方の1つだと思う.

Microservicesの現状と課題

最初にMicroservicesの世界の現状と課題について簡単にまとめる

言語

MicroservicesおいてPolyglotは普通だ.そもそも適切なサービスで適切な技術を採用できるようにすることはMicroservicesの大きな目的の1つであり,その選択にはプログラミング言語も含まれる.特に現状では普通のAPIに関してはGoが選択されることが多いが,Machine learningのモデルのServingにはPythonをフロントエンド系にはNodeをという選択は普通に有り得る.正直そのサービスオーナーが運用や採用を含めて責任を持てるならHaskell使おうとRustを使おうと問題はない(はず).

また言語によってはフレームワークも多彩であり,例えばPythonであればFlaskを使うこともあればDjangoを使うこともあるだろう.

Protocol

Microservicesにおいてはサービス間はネットワーク越しにコミュニケーションを行う.そのためそのコミュニケーションに利用するProtocolも多彩になる.MercariのようにgRPCを共通のProtocolとして採用することもあれば,HTTP/1.1もしくはHTTP/2でRESTを使うこともある.MessagingとしてKafkaやCloud PubSubを使ったり,CacheとしてRedisやMemcacheを使ったり,DatabaseとしてMySQLやMongoを使うこともありそれぞれProtocolは異なる.

分散システム

ネットワーク越しのリクエストが前提となるMicroservicesは分散システムである.Fallacies of distributed computing(分散コンピューティングの落とし穴)にあるように「ネットワークは信頼できる」と思ってはいけない.この分野では,リクエストが失敗したときにback-offつきでRetryを行うこと,Timeoutを設定すること,適切なRate-limitをつけ異常なリクエストをブロックすること,対象のサービスが何らかの障害で死んでしまってもCircuit breakingでそれを回避することなど多くのBest practiceが養われてきた.MicroservicesはこれらのPracticeを使う必要がある.

Observability

上で紹介したSeeking SREにおいてMatt Kleinが述べているようにMicroservicesでは可視化が全てだ.ログやメトリクス,分散Tracingを駆使してあるリクエストがどこで何が起こったのかを理解できるようにしなければならない.これをちゃんとやるにはログやメトリクスなどは一貫性のあるフォーマットに揃っている必要もある.

(KubeConとかで話しているとこれができてないところは多いらしい…)

認証認可

Microservicesでは各チームは自分のサービスに対して責任を持ち他のサービスについて気にしないことが理想だ.このためにはZero Trust(もしくはDon’t trust each other)を前提とし,サービス間はデフォルトでmTLSもしくはRole-Based Access Control (RBAC)などによるAuthN/AuthZを行う必要がある.

Service meshの登場

上述した機能や課題を全てアプリケーションレイヤーで実装するのは現実的ではない.アプリケーション開発者はビジネスロジックを書き最高のサービスを書くことに集中しそれ以外のMicroservices固有の問題からは開放されないければならない.古くからMicroservicesを実践してきた大企業,例えばNetflixやGoogle,Twitterなどは各言語向けのSDKを実装することでこれらを解決してきた.NetflixがOSSとして公開しているHystrixなどは有名だ.しかしこの方法には以下のような課題がある.

  • 利用できる言語が限定されてしまう(例えばNetflixはJVMが前提)
  • 各言語毎にSDKを準備したとしても言語間で一貫性を保つのが難しい
  • SDKのアップデートを集中管理するのが難しい

これらの課題を踏まえ登場したのがService meshである.Service meshは以下の図のように全てのサービスと一緒にSidecarとしてProxyをデプロイし,サービス間のコミュニケーションをすべてこのProxy経由で実行するようにすることでmeshを構成する.そしてこのProxyレイヤーでTraffic controllやLB,ネットワークのResiliency、Observability,Securityなどを担保する.

http://philcalcado.com/img/service-mesh/6-a.png

http://philcalcado.com/img/service-mesh/mesh1.png

Istioとは何か

このService meshの代表的な実装として有名なのがIstioである(他にもLinkerdconduitがある).

以下の図はIstioのアーキテクチャである.ここではIstioの各コンポーネントについて簡単にまとめる.Istioのコンポーネントは大きく分けてData planeControl planeに分けることができる.

Data plane

Data planeは全てのサービスのIngressとEgressリクエストをinterceptする.アプリケーションから見ると単純にリモートのサーバーに対してHTTPリクエスト,もしくはgRPC callなど発行してるだけでありその下で何が起こってるかは意識する必要がない.Data planeが多くの処理をアプリケーションの代わりに担う.

Data PlaneはService proxyとSidecar containerという2つの概念から構成される.

Service proxy

アプリケーションはService proxyを経由して外部のサービスとのやりとりを行う.このProxyが,リクエストのRetryやTimeout,Circuit breaking,Service discovery,Securityなどを担う.IstioではデフォルトでこのProxyにEnvoyを利用している.

EnvoyはC++で書かれたL7 ProxyでありLyftで開発された.Lyftの大規模なリクエストを捌いているという実績があり,HTTP/1.1やHTTP/2,gRPCのロードバランシング,リクエストレベルのメトリクスやTracing span,active/passiveのhealth checkなどの多くの機能を持つ.Istioの多くの機能はEnvoyがあってこそ実現されている.

ではIstioはどのようにEnvoyをService Proxyとしてデプロイしているか? Sidecar container patternを使う.

Sidecar container

Kubernetesの世界では複数のContainerをまとめてPodという単位でデプロイを行う(Kubernetes: Understanding Pods vs. Containers by Tim Hockin).同じPodに属するContainerは同じNodeにデプロイされ協調して動くことで1つのサービスや機能を提供する.Sidecar containerはメインとなるアプリケーションContainerと一緒にPodとしてデプロイされ補助的な役割を担うContainerである.

Istioにおいては,このSidecar containerはIstio-proxyと呼ばれアプリケーションcontainerと一緒にデプロイされそこからのIngressとEgressのリクエストを受けるようになる.Sidecar containerのデプロイは手動でInjectすることもできるしHookとして自動でInjectすることもできる(完全なmeshを構築する場合は後者の手法を用いるのが良い).

Control plane

Control planeはData planeのBrain的な役割を担う.Control planeはPilotとMixerとAuthの3つのコンポーネントから構成される.

Pilot

PilotはMicroservicesが最新のNetwork topologyやRouting tableを持っていることを保証する.Service discoveryに加えてA/B testsやCanary deploymentのようなTraffic controllやリクエストのTimeouts,Retries,Circuit breakeringのようなネットワークのResiliencyの機能を提供する.Pilotはこれらの機能のための専用の設定をEnvory specificな設定に変換し配下のService proxyにそれを伝搬させる.具体的に専用の設定にはRouteRuleDestinationPolicyがある.

Mixer

Mixerはアクセスコントールの施行やService proxyからのTelemetry dataの収集を担う.MixerによりACL(Precondition Checking)やリクエストのRate-limit(Quota Management)の設定をしたり共通のメトリクスの収集(Telemetry Reporting)を行うことができる.MixerはPluggableであり,Adaptersを実装することでloggingやmonitoringやACLの機能をインフラに合わせて拡張していくことができる.MixerはruleやAdapterごとの設定ファイルをもつ.

ちなみにMixerがSPOFになるのでは?と思ったらMixer and the SPOF Mythを読むと良い(まとめるとStatelessでありむしろスケールしやすいアーキテクチャ).

Auth

Authはx509証明書のIssue,RevokeやRotationを行う.Authは全てのMicroservicesに証明書を発行しサービス間のmTLSによるAuthNを実現する.アプリケーションからUnencriptedなリクエストを投げてもService Proxy間,Mesh内ではEncryptedな通信へと透過的にUpgradeする.

IstioのConfiguration

IstioのConfigurationとして主に我々がコントールするのはTraffic ManagementやSecurityになる.ここでは具体的な設定例を見てみる.

Traffic Management

Traffic Mamagementを主に担うのはPilotだ.Pilotの設定には大きくRouteRuleDestinationPolicyがある.

RouteRuleはService mesh内でリクエストをどのようにRoutingするかを決定する.RoutingのルールにはHTTP headerを使ったり複数バージョンごとに重みを設定することもできる.例えばv1とv2という複数のバージョンを同時にデプロイし以下のような設定ファイルを適用するとv2に25%,v1に75%の割合でリクエストをRoutingすることができる.またリクエストのRetryやTimeoutの設定もRouteRuleで実現できる.

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: reviews-v2-rollout
spec:
  destination:
    name: reviews
  route:
  - labels:
      version: v2
    weight: 25
  - labels:
      version: v1
    weight: 75

ちなみにRouteRuleを使うことでリクエストにFaultをInjectすることもできる.例えば以下のルールを適用すると10%の割合でリクエストに5sのDelayを付加することができる.他にも特定の割合で任意のHTTP Statusコードを返すなどもできる.これにより例えば開発環境で依存するサービスに問題があっても自身のサービスを問題なく動かし続けることができるかをテストすることができる(Chaos testingにはGremlinのような専用のSaaSを使うという手もある).

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: ratings-delay
spec:
  destination:
    name: reviews
  route:
  - labels:
      version: v1
  httpFault:
    delay:
      percent: 10
      fixedDelay: 5s

DestinationPolicyにより特定のBackendやサービスに対するLoad-balancingのアルゴリズムやCircuit BreakingやHealth checkの設定を行うことができる.例えば以下の設定ファイルを適用するとreviewサービスのv2からratingサービスのv1に対してROUND_ROBINによるLoad-balancingが行われるようになる.

apiVersion: config.istio.io/v1alpha2
kind: DestinationPolicy
metadata:
  name: ratings-lb-policy
spec:
  source:
    name: reviews
    labels:
      version: v2
  destination:
    name: ratings
    labels:
      version: v1
  loadBalancing:
    name: ROUND_ROBIN

RouteRuleDestinationPolicyの違いはリクエストベースの設定か特定のBackendベースの設定かの違いである.どちらかを使えば良いわけではなく例えばネットワークのResiliencyの設定はRouteRuleのTimeoutとRetryとDestinationPolicyのCircuit Breakingを組み合わせることで実現する.

Security

Securityで主に関わるコンポーネントはIstio-AuthとMixerだ.Istio-AuthでmTLSをMixerでACLを実現することができる.

例えば以下はMixerのPrecondition Checkingの機構を使いBlacklistを実現する例である.reviewsサービスのv3からのリクエストを全て禁止している.

apiVersion: "config.istio.io/v1alpha2"
kind: denier
metadata:
  name: denyreviewsv3handler
spec:
  status:
    code: 7
    message: Not allowed
---
apiVersion: "config.istio.io/v1alpha2"
kind: checknothing
metadata:
  name: denyreviewsv3request
spec:
---
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: denyreviewsv3
spec:
  match: destination.labels["app"] == "ratings" && source.labels["app"]=="reviews" && source.labels["version"] == "v3"
  actions:
  - handler: denyreviewsv3handler.denier
    instances: [ denyreviewsv3request.checknothing ]

まとめ

Service meshとは何でなぜ必要なのか?からその実装例としてのIstioの概観をまとめた.

現在実際にサービスに導入することを考えているが,(Istio自体の安定性やPerformanceは改善されるとして)大きく2つの悩みがある.まずKubernetes YAMLの壁で書いたのと同様のYAML問題.KubernetesでさえYAML問題を抱えているのに更にそれを増やすか…Kubernetesのそれに対する解答として登場したHelmやKsonnetのIstio版,つまりより抽象化された設定などが必要であると感じている.またFault InjectionやAB testingなどはGUIでちゃちゃっとやっても良いのではないかと思っている.

次に誰がYAMLを管理するか問題.基本的には開発者がIstioの設定などに悩むことなくデフォルトで安全なネットワークを提供するのがベストだと考えている.その上で要求に合わせて設定を変えていくという方式,集中管理から適宜移譲していくのが良いのかなあ? ただABテストなどを集中管理してもなあ.Precedenceなどをうまく使うか… この辺りの解はそのうち会社のBlogで.