マイクロサービス間のポーリングをなくすために奮闘した話 in サーバーエージェントインターン
またインターン
「何社目だよ!!」とツッコミ入れていただいた方ありがとうございます。8社目ですね。 このインターン自体は昨年の7月から決まっていたので、決まってから随分たったなという感じですね。
どこでインターンしたの?
Freshでサーバーサイドとして、インターンをさせていただきました! 期間は2/1~2/28です
何をしたのか
マイクロサービス間のポーリングを無くす、ミドルウェアを作成することが、僕の一ヶ月のタスクでした。 (このタスクも個人的にはすごい抽象的な振られ方をして、内心『まじか!終わるか?』と思いつつ、やりました)
どんなものを作ったか
neoplasma というものを作りました。 もともとFreshにはイベント配信基盤のplasmaというものがありまして、それを応用活用して作りました。
plasmaの説明は以下をご覧ください、plasmaについて知っている前提で以下では話す部分もあるので読んでください
(大学の先輩でもありますが、あまり関わったことがないのに失礼します) upamune.hatenablog.com
ここで疑問が生まれます。plasamaでマイクロサービス間のポーリングも消えんじゃないの? と、 しかし、plasmaはイベントすうに対してスケールすることができない仕組みをしているため、高頻度でポーリングを行うイベント通信(動画のキャッシュ部分など)では使いづらいという問題点がありました。
スケールするplasmaを作りたい!!!
まず要件を考える中二つの問題点がありまして、
- clientが複数接続を張る必要がある。
- 全plasmaに接続を張れている状態の担保、全イベントが届く状態の担保が必要
以上二つを解決するには以下の動きをすると解決できる!! という今回の構成と動きを紹介します
今回は、Freshでは最近kubernetes(以下k8s)を使っているので、k8sの用語で説明していきますm( )m、インターフェースを作ってあるので、実装すれば他のプラットフォームでも動くと思いますが、今回はごめんなさい、、、
Deployments(neoplasma群)内(最小構成)の動き
最小構成で必要な部分の図のみが貼られています
⑴ neoplasmaがイベント通知を行える状態にない場合
イベントを受け取れる状態にないときは、ロードバランサからイベントが飛んできてしまった場合ロードバランサに送り返します。
⑵ 新規neoplasma-client立ち上がるとき
- k8sマスターにneoplasamのdeploymentsの接続先リストをもらう
- もらって来た接続先に接続をはる(grpc)
実装的には、①はk8sに依存しているのでインターフェースとしては②のみを作っています。
⑶ スケールアウトしたneoplasmaに接続する
- 新規neoplasmaの所属するdeploymentsに対して自分の状態を送る
- 別のneoplasmaに送られる
- 送られて来たイベントをneoplasma-clientに対して送る
- 状態を送った先にコネクションをはります
neoplasmaは常に数秒おきに自分のDeploymentsに対して、自分の接続情報を送っています。無限ループの可能性は0ではないですが、確率的に低いので許容範囲としています。
状態イベントには接続先情報があるのでそこにneoplasma-clientは接続していきます。
⑷ スケールアウトしたneoplasmaがイベント処理が行えるようになるとき
- 新規neoplasmaの所属するdeploymentsに対して自分の状態を送る
- 別のneoplasmaに送られる。
- 受け取ったneoplasmaは送って来た先にneoplasmaに対して、自身の接続状態を送ります。
- 送られて来たものと同等の状態の場合、正常のイベント処理を始めます。
④の時、送られてきた接続状態と自分の接続状態を比較して、大丈夫そうならイベント処理を行いますが、ダメなら、①のように送り返します。
⑸palsma-clientとの接続が一つ切れた場合
- 新規neoplasmaの所属するdeploymentsに対して自分の状態を送る
- 別のneoplasmaに送られる
- 送られて来たイベントをneoplasma-clientに対して送る
- 足りない接続を検知し、そこに繋ぎに行きます。
これで、行けた!!!と思いますが、そうではありません、これはまだ、クラスタ内の話なので、次に外部との接続の話です。
クラスタから外部クライアント
podでスケールアウトできるようになったけどまだパブリックに入っていないのでそうしたいのですがい問題があります。
k8s外のパブリックな環境からはポート固定
なぜ欲しいのかというと、 - k8s内は仮装環境なので、ポートはなんとかなるが、外部との接続はポート限られているので、pod一個ずつ開けることはできまっせん、そこでk8sのserviceを使って、NodePortを開けたとしても、grpcの接続はNodeportに繋がっているどれかのpod一つにしか繋がりません。
ここで、 neoplasmaのproxyを設置してできそうな予感がしたので、 考えたのが先ほどから出て来ているneoplasma-clientです こいつは何かと言いますと、proxyの役割を持っています。
- neoplasam-client群(deployments)でポートを持てば、外部クライアントは接続先を特にきにする必要はない
- plasma-client群で全てのneoplasmaへの接続を持つ
- neoplasma-client自体もスケールアウトする
- 振り分け判定がないので、負荷は低い
- node数依存になっても管理しきれる想定
なのでこうなります。
少し簡略さした図でイベントの流れを以下のようになります
- ロードバランサがイベントを振り分ける
- 振り分けられた先のdeploymentsの中のどれかのneoplasmaにへ振り分けられる
- 別のdeploymentsにもデータを流す
- neoplasma-cliet群へ投げる
- Nodeportを通って外部へ
- クラアントへ飛ばします。
これによって、neoplasmaのpodのスケールをして、一つのdeploymentでは接続数が耐えられなくなったら、接続を分散するために、deploymentsを増やすことと、neoplasma-clientを増やして対応します。
これで、ポーリングをまたさらに減らして平和な世界へと近づくことができました!(多分)
まとめると
だらだらと動きを説明しましたが、二段階のスケールし、イベント処理をするneoplasma群と外部と繋げてよしなにやってくれるneoplasma-clientを作ったということです!!!
感想
k8sは、ほぼほぼ初見で、社内の資料や、公式ドキュメントもめちゃくちゃあさりましたし、Deploymentsの概念の理解と、service,ingressの理解などが割と手間取りました。あとは設計もあーでもないこーでもないと、一人ホワイトボードと向き合いながら考える日々で、ほとんどコードは書いていないのですが、初めてこんなに時間をかけて真剣に設計をして、設計の重要性と大変であることが学べました。
最後に
関わってくれた、Freshの皆さん、ランチや飲みに連れてってくれたサイバーの方々ありがとうございました!またおはないしできることを楽しみにしています!!!
3/1からは、DMMのネクストカレンシーシーでお世話になるので、会社での業務以外のことなどは、4月にまとめて書きたいと思います。