Service Meshをスモールスタートするという選択
Click here for English version
今記事は、CyberAgent Developers Advent Calendar 2018 7日目の記事です。
こんにちは、20卒内定者で現在AWAでアルバイトをしている中尾涼(@nakabonne)です。
現在マイクロサービス界隈でService Meshというアーキテクチャが注目されています。
実現のために様々な製品がありますが、私はsxdsという小さなcontrol-planeを作りました。
今記事ではService Meshを小さく始める手段について、sxdsを使用しながら紹介していきます。
対象読者
- Service Meshの導入を検討している人
- Service Meshへの理解を深めたい人
- マイクロサービスに興味がある人
Service Meshとは
乱暴に説明すると、マイクロサービス化することで起きる様々な問題を解決するためのアーキテクチャの一つです。
Service Meshの定義については、様々な記事で紹介されてるので、今記事では割愛します。
- What's a service mesh?
- Istio / What is Istio?
- What Is a Service Mesh? - NGINX
- Lyft's Envoy: From Monolith to Service Mesh - Matt Klein | Microservices.com
- What's a service mesh? And why do I need one? | Buoyant
- Service meshとは何か | SOTA
Service Meshの実現手段
control-plane, data-planeそれぞれを実現するツールは、以下のようなものがあります。
ここでは執筆時点(2018/12/7)で代表格とされているもののみ説明します。
control-plane
- Istio
- Synapse
- Nelson
- AWS App Mesh(執筆時点ではpublic previewとしてのみ利用可能)
執筆時点でのcontrol-planeとしてはIstioが有名です。
Istioはdata-planeを内包しているため、ほぼこれ一つでService Meshを実現することが出来ます。
非常に優れたツールではありますが、もちろん銀の弾丸ではありません。
Istioはコンテナオーケストレーションツールからクラスタ情報等を収集することで効果を発揮します。
つまり、コンテナオーケストレーションツールを使っていない環境では効果を発揮することが出来ません。
また、Istioは多くの機能が提供されていますが、ごく僅かな機能しか使わない場合は同じく効果を十分に発揮することが出来ません。
data-plane
Istioのデフォルトdata-planeにも採用されていて、先日発表されたApp Meshでも使われているEnvoyが、代表格といえるでしょうか。
EnvoyはL7プロキシです。特徴は、インバウンドとアウトバウンドトラフィックの仲介を前提に作られているため、分散トレーシングや動的なサービスディスカバリをプロキシレベルで実現出来る点です。
また、優れたスレッドモデルによってパフォーマンスが非常に高いという点も挙げられます。Envoyでは、全てのハードウェアスレッドにワーカーを生成してそれをメインのスレッドが制御しています。
C++を選んだのも、そのモデルを実現できる唯一のプロダクショングレードな言語だからとのことです。スレッドモデルに関してはブログに詳しく記載されています。
control-plane自作
紹介したとおり素晴らしいツールはたくさんあります。しかし私が当初欲しかった機能は、サービスディスカバリのためにdata-planeのルートテーブルを動的に更新するというものだけでした。
また、ほとんどのサービスがAWS の ECS というコンテナ管理サービスの上で動いていたため、k8s等のオーケストレーションツールを使っていませんでした。
そこで私はdata-planeにEnvoyを採用し、それを制御するsxdsというcontrol-planeを作りました。
次の節から作り方を詳しく説明していきます。
インターフェース
Envoyと通信するためのインターフェースはdata-plane-apiとして公開されており、ルートテーブルを動的に更新するためにはこれを満たすAPIを作る必要があります。
執筆時点でこのAPI定義に準拠しているプロキシはEnvoyのみですが、作者のMatt Kleinはdata-plane-apiを汎用的にしていきたいとブログにて発言しています。
そのため、将来このAPI定義が汎用的になった場合、自作したcontrol-planeがプラガブルになります。
また、data-plane-apiを満たしたAPIをxdsと呼びます。
Goによる実装
Envoyは独自のプロトコルであるxds protocolを定義しています。
これに従ってgRPCのbidiストリームかロングポーリングのRESTでxdsを実装します。
xds protocolを忠実に守って実装しないといけないわけですが、GoとJavaのみこの実装が含まれる公式ライブラリが公開されています。
sxdsではGoを選択し、go-control-planeをインポートしてxdsサーバーを実装しました。
はい、これだけでxdsの実装は終わりです。ただ、go-control-planeはdata-planeの設定をメモリにキャッシュする必要があるため、以下のようにキャッシュするためのRESTサーバーも立てています。
- sxds/server.go at master · nakabonne/sxds · GitHub
- sxds/cacher.go at master · nakabonne/sxds · GitHub
全体図はこんな感じです。
静的リソースの管理
Envoyへのレスポンスに必要な静的なリソースを管理する必要があります。
sxdsはLDS/CDS/RDS/EDSという4つのサービスを提供しているため、これらに必要な以下のリソースを定義しておく必要があります。
また、リソースのバージョンも明示する必要があります。
無駄なパケットのやりとりを防ぐため、バージョンに変更が無い時はレスポンスを返さないという決まりがxds protocolに定義されています。
sxdsではこれらをjsonで定義し、前述したキャッシュするためのサーバーにPUTします。
Envoyの設定
sxdsのクラスタ情報は静的なので、static_resourcesとして定義します。
そしてそのクラスタをdynamic_resourcesとして指定します。
実行
Envoyの実行
sxdsはメモリ消費を抑えるため、ノードタイプ毎にリソースをキャッシュします。
そのためEnvoyのノードタイプを設定する必要があり、ノードIDの命名規則を{node_type}-{node_id}
とすることでこれを設定します。
また、sxdsへのdiscovery requestのために自身のクラスタ情報が必要なので、クラスタ名も指定します。
このノードIDとクラスタ名は、設定ファイルに定義することも可能です。
$ envoy --service-node sidecar-app1 --service-cluster app1
sxdsの実行
以下のように2つのサーバーが起動します。
$ sxds 2018-12-01T10:00:00.518+0900 INFO xds/server.go:56 xDS server is listening {"conf": {"Port":8081}} 2018-12-01T10:00:00.518+0900 INFO cacher/server.go:58 cacher server is listening {"conf": {"Port":8082}}
リソースの設定
先程作成したjsonファイルをcacherサーバーに送信します。
そうしたらEnvoyに設定が反映されることが確認出来ると思います。
$ curl -XPUT http://{IP_ADDRESS}:8082/resources/sidecar -d @sidecar.json
まとめ
sxdsの紹介をしていきましたが、もちろんこれも完璧ではありません。
control-planeを冗長化した際は扱いづらくなるし、リソースjsonを永続化する仕組みも必要です。
伝えたいことは、envoyproxyの公式ライブラリを使えば低コストでcontrol-planeを作ることが出来て、必要な機能だけ自作するという手段も十分考えられるということです。
既存のサービスをマイクロサービス化する場合、必ずしもモダンな環境であるとは限りません。
そういった中で、環境に合わせて簡単に自作出来るという手段を持っていることは大きな武器となります。
ここまで読んで下さったあなたの頭に、Service Meshをスモールスタートするという選択肢が芽生えたとしたら幸いです。