Juju-62q's blog

参加記録やメモ書き、思考のまとめをしています

マイクロサービスを形式的に見てみる

f:id:Juju_62q:20200108235120p:plain

マイクロサービスについて考えていたら疲弊したので、少し技術者らしく形式的に見てダメのものを思考から削ぎ落としたいと思った。 グラフ理論などコンピュータサイエンスの基礎を交えて話をするが、基本的には当たり前のことしか言わないと思うのでここに書くことを意識せずとも暗黙的に実践している人も多いだろう。

なお、個人の意見でしかないのであっているか間違っているかはわからないし、筆者にこの記述に反した実装を否定する意図はない。 今回は適当に書き散らかすのでかなりテイストが違うが他のブログと同一人物が書いている。乗っ取り等ではないです。

TL;DR

  • マイクロサービスはDAGとすると考えやすいしデプロイしやすい
  • 閉路があるなら設計を見直した方がいい
  • DAGかどうかはサブシステムレベルでそれぞれ考えると簡単
  • デプロイに関係するリポジトリでは閉路がないことを意識させる設計にするといい

マイクロサービスと疲弊

マイクロサービスはしんどい

マイクロサービスアーキテクチャが一般的なものになってきた最近では、こんなことを聞く機会が非常に多い。 実体験としてもマイクロサービスは非常に難しいものだと思う。 これが何故なのか考える。

マイクロサービスの文脈になるとコンテナオーケストレーションツール、サービスメッシュ、分散トレーシング、メッセージング、Protocol Buffersのようなツールがセットで現れ、それら技術の管理に疲弊するというようなことが頻繁に語られる。 これらのツールは非常に便利で強力なツールである一方、文化づくりやチームへの布教、運用が簡単でないのは事実だろう。 実際に自分も技術の難しさなどについてはひしひしと感じている。

一方で自分が一番マイクロサービスで難しいなと感じる部分はツールに依存する部分ではない。 ビジネスドメインを適切に分割して、システムを設計するのが一番難しい問題であると思う。 ドメイン駆動設計などの文脈ではドメインエキスパートと対話を繰り返すことでビジネスを表現する最適なモデルを作り上げていく。 マイクロサービスの設計も基本的には同様である。 ビジネスをシステムとして実装する際に、ドメインに合わせたサブシステム(入れ子になっている場合もある)に分解し、いい感じにマイクロサービスへ分解する。 言葉にしてしまえばやるべきことはこれだけである。

しかしながらDDDの文脈でも頻繁に語られるように最適なドメインモデルは神のみぞ知るものである。 つまり、最適なマイクロサービス単位も神のみぞ知るものである。 したがって答えのないものに対して詳しい人と話して、思いを馳せながら折り合いをつけることになる。 答えのないものに思いを馳せるという行為は結構疲れる。疲れるというとネガティブに聞こえるかもしれないが、少なくともエネルギーが必要となる行為であることは事実だろう。 個人的には"マイクロサービスのしんどさ"の最たる部分はここにあるのだと思う。 最適なマイクロサービスに分割した経験は今のところないが、おそらくその分割単位が最適であるのなら管理もそこまで難しくないのだろう。

マイクロサービスを形式的にみる

答えのないものについて、無数にある選択肢の中から何かを選ぶのは非常にエネルギーを使う作業である。 よって今回は基本的に考える必要がなさそうな分割単位を機械的に弾いていくことを考える。 機械的に弾くため思いを馳せる必要もなく、省エネである。

マイクロサービスアーキテクチャを形式的に捉えてみよう。 システムは全体として複数のサービスが協調することで成り立っている。 サービスをノード、連携をエッジとしてみるとマイクロサービスアーキテクチャはグラフとして捉えられる。 さらにいえばHTTPアクセスはクライアントサーバモデルを体現したものであるため有向である。 マイクロサービスアーキテクチャを形式的に捉えると、1つのサービスをノード、依存関係をエッジとして捉えることができるため有向グラフであることがわかる。 有向グラフとはこんな感じに点(ノード)と道(エッジ)があり、エッジに向きがあるモデルである。

f:id:Juju_62q:20200109004852p:plain

出典: 道 (グラフ理論) - Wikipedia

ここで依存関係とは、依存先が存在しないと最大限機能が提供されていない状態を指す。 マイクロサービスアーキテクチャではフェールソフトなシステム設計をされることがしばしばある。 フェールソフトな設計をされている時にシステムのアクセス先を"依存"と呼ぶべきかどうかは議論の余地があるが、少なくとも機会損失や体験の損失が発生していることには違いないので、ここでは依存関係ということにする。また、PubSubのようなメッセージングで非同期に連携しているものも依存と呼ぶ。

マイクロサービス設計とDAG

マイクロサービスアーキテクチャを採用する理由はなんだろう? 多くの場合それは変更を独立させることによるビジネスの成長速度を落とさないためである。

ビジネスの成長速度を落とさないということはシステムにガシガシ変更を加えてバシバシデプロイする必要がある。 仮に疎結合かつフェールソフトな設計になっているとして、バシバシデプロイするためにはマイクロサービスの依存関係は明瞭である方がいいだろう。

そこでDAGである。

DAGについて知らない人もいると思うが、数学屋さんでもなんでもない自分が解説してもいいことはなさそうなので自身で調べるか、Wikipediaを見ておくと良い。

ja.wikipedia.org

DAGには閉路がない。 よって依存関係は明瞭に理解することができ、その順序も明快に解決される。 本当に何も考えたくない場合トポロジカルソートでもすれば順序の確認及び、自分がデプロイしたいアプリケーションの依存を知ることができる。

一方でマイクロサービスアーキテクチャは基本的には有向グラフでしかない。 DAGよりも表現力の高いマイクロサービスアーキテクチャの世界で、深く考えずに機能追加を繰り返していくとアプリケーションの依存関係にはしばしば閉路ができる。 そもそも、閉路があることは問題なのだろうか?責任範囲を明確に分割しているのであれば問題となり得ないと思うかもしれない。 しかし自分は閉路は明らかに問題であると思う。

マイクロサービスアーキテクチャと閉路

マイクロサービスアーキテクチャ設計の文脈では以下のようなことが語られる

  • 1つのサービスは高凝集であること
  • それぞれのサービスが疎結合であること

グラフ理論の文脈で閉路は強連結成分である。 強く連結した成分が複数のアプリケーションに分かれている時点で、凝集できていないし、結合強度は疎ではない。 また、いくらフェールソフトになっているからと言って、積極的に機会損失をしたい人は多くない。 閉路があるとデプロイ時に開発者がマイクロサービスの中に立ち入ってチェックを始める羽目になるので結果的にデプロイ速度が落ちてしまう。

このような要素からマイクロサービスアーキテクチャシステム開発する際には徹底的に閉路を排除した方がいいと自分は思う。

考え方

しかしながら100もあるマイクロサービスの依存関係を全て理解するというのはあまり現実的でない。 よってシステムからサブシステムを分解していき、それぞれの依存関係を解決しながら常にDAGになっている状態を目指す。 サブシステムは入れ子になっていても問題ないし、最終的に1つのマイクロサービスもサブシステムである。

まず一番大きなサブシステムでシステムを表現し、それの閉路をなくす。 次にサブシステムの中のサブシステム間の閉路をなくす。これを最小単位まで繰り返す。 そうしてできたシステムは任意のマイクロサービス間の依存に閉路がなくなる。

全体について考えるのは難しいので細かく分けて考えていく。 粒度としては10程度であれば十分考えられるし、管理も可能であると思う。

デプロイ手段と開発者が考える範囲

ここで、デプロイ手段について考えてみる。 K8sやTerraform、CloudFormationと言ったマイクロサービスをデプロイするために使われていそうなツールは "1つのデプロイ単位において" 閉路を許さない1。 一方でアプリケーションレベルでの通信などには立ち入ってこない。 つまり、おかしな設計をしていなければ1つのマイクロサービスアプリケーションの依存関係はDAGとすることができる。 TerraformやK8sのCRDを利用すればPubSubなどのメッセージングのような必要要素もこの依存の一部として管理することが可能である。

したがってマイクロサービスアプリケーションというレベルの依存関係については基本的にはDAGとして管理されている or 管理するのが容易にできるため、深く踏み入らなくて良さそうである。 つまり、開発者はマイクロサービスアプリケーション間の連携について考えていく必要がある。

依存関係の自動的な制御

現在、HTTP通信やPubSubによる通知の経路を一貫して管理する著名なテクノロジは存在しないと思っている(あったら教えてください)。 例えば通信経路の制御が可能なサービスメッシュはPubSubのようなメッセージングを基本的に対象としてない。 理由としてはPubSubには多くの場合リトライ機構が十分に備わっており、publisherとsubscriberの経路制御が簡単であることが考えられる。

一方で分散トレーシングは物によってはある程度PubSubのようなメッセージングをサポートしているが2、これは結果でしかない。 結果からアラートを出すという方法も考えられるが、ある程度PubSubに対応している分散トレーシングツールであってもパブリッククラウドのマネージドサービスの場合、現状経路がどこかで途切れてしまう3

つまり、現状自動的に閉路のある依存関係を完全に弾くのは容易でない4。 したがって、開発者はシステムがDAGであることを意識し、管理することになる。

デプロイリポジトリでDAGを可能な限り表現する

さて、マイクロサービス間の連携を開発者が管理しなければならないとして、どのように管理するかという話になる。 自分は少なくともデプロイ関係はモノレポで管理したい派なので、これで表現あるいは制御するのが一番手っ取り早そうである。

しかしここで問題が発生する。 我々が頻繁に利用するファイルシステムはグラフではなく木構造である。 グラフから木構造を作ることは可能だが、木構造でグラフを表現しきることは不可能である。 グラフの中でもDAGのみに絞ったとして、木構造よりも表現力は高い。

親子関係のものは木構造に落とし込めばいいとして、依存先が並列な物に対して複数あるものをどう表現したらいいのか答えが出ていない。 これについては基本的にplantumlか何かで README.md に関係を記述していくことになるのかなと思っている。 サブシステムを10個以下程度に制限すれば十分依存関係の整理は可能だろう。 次に考えるべきことについては整理できたのでとりあえずやってみることにする。

まとめ

  • マイクロサービスアーキテクチャの依存関係の閉路はビジネスの速度を落とす要因になる
  • システムをサブシステムに分解してそれぞれについてDAGであることを確認すれば無理なくシステム全体がDAGであることを確認できる
  • マイクロサービスの依存関係を自動的に完全に表現するためのツールはおそらくない
  • ファイルシステムでどこまでDAGを表現できるか分からないが README.md に書くみたいな泥臭い運用も含めてやってみる。

  1. K8sで言えば、Ingress, Service, Deployment, ReplicaSet, Pod, ConfigMap, Secretなどの依存に閉路はない。

  2. 例えばAWS X-RayはSQSに対応している。ただし、SNSからSQSにSubscribeされるとそこで途切れる。

  3. オンプレミスについてはあまり詳しくないが通信経路を全て自分で把握することが理論的にはできるため実現可能かもしれない。

  4. おそらく、サービスメッシュやポリシーエージェントを利用すれば部分的に可能である。