サービス全体への障害の影響を最小限にする3つの方法

廣部圭一氏(以下、廣部):自己紹介します。私はKyashに2019年の5月に入社しました。ふだんはFundsチームという外部のベンダーとのやり取りを主に行うチームを担当していて、クレジットカードのチャージ処理や、資金移動ライセンスを下に銀行との接続やATMとの連携を担当しています。個人的に興味があることは、データベースの実装です。

さっそくCircuit Breakerについて話します。まずは、「そもそもCircuit Breakerとは何なのか」というところを話したいと思います。資料の右側に映っているのは『マイクロサービスアーキテクチャ』という本で、11.5章にマイクロサービスにおけるサービス全体への障害の影響を最小限にする方法が紹介されていて、方法が大きく3つ紹介されています。

1つ目はタイムアウトで、一番基本的なものになるんですが、タイムアウトを設定することによって、プロセスが滞留する可能性を減らすことができます。呼び出し先のサービスが負荷などで一時的にサービスが止まっているというときに、クライアント側がプロセスが滞留する可能性を減らすことができます。

基本的には7秒から9秒でタイムアウトを指定していることが多いんですけど、Fundsチームの場合は、特に外部ベンダーとやり取りをすることが多いので、それらのAPIを叩くときには必ず指定をするようにしています。ただ、外部ベンダーのAPIの仕様書で「タイムアウトは必ず1分以上でないといけない」ということもあって、その場合はタイムアウトを設定することの効果がほぼなくなってしまいます。

次に隔壁というものです。これは概念的なもので、被害を最小限にするためのサービス間に壁を設けるような仕組みや設定などを指しています。右側にある図は本に記載されている図で、あくまで隔壁の1つの例ですが、呼び出し先によって接続のプールを分けることで、2つのサービスがあって片方のサービスが重くなった場合に、片方の接続プールが影響を受けないための仕組みが載っています。

KyashはGoを使っているんですけど、Goの場合はnet/httpのMaxIdleConnsPerHostの設定で同じようなことができると思います。

最後が今回のテーマのCircuit Breakerです。これはまさに自宅のブレーカーと同じ仕組みです。右側の図にあるように呼び出しがタイムアウトしたり遅くなってきたりするのを判定して、ある閾(しきい)値を超えるとリクエストをさせないようにするという仕組みで、ブレーカーが落ちる流れと同じです。

システムが自動で復旧していくのが理想的な流れですが、健全性をチェックして、サービスを呼ぶことができると判断をしたうえで再開という流れです。この3と4のプロセスは常にできるとは限りませんが、したほうがよいものではあります。

監視ツールの移行に伴い汎用的な仕組みづくりがスタート

KyashにはもともとCircuit Breakerの仕組み自体はあって、cronで数分ごとにjobを起動して、New Relicという監視ツールのレスポンスタイムの90パーセンタイルといった特定のメトリクスを参照して、その閾値を見て判断するというものがありました。

具体的には、健全性のチェックの結果をRedisに保存し、リクエストすべきかどうかを判断するという流れです。

監視ツールをDatadog Monitorに移行するときに、もともとこういう構成にしようという話になっていて、jobでポーリングするんじゃなくて、SNSに通知してSNSからLambdaを。それで、Lambda内で先ほどのRedisに書き込む処理をするという流れを想定していました。

この移行自体がすでに進んでいたタイミングで私が対応することになって、メンテナンスをしていくのが辛そうだなと感じました。今後もFundsチームでは、外部のベンダーと接続していくし、Circuit Breakerも継続的に必要だからそれを考えるとちょっと辛そうだなと。

例えば新規のCircuit Breakerを作りたいときは、Datadog MonitorとSNSとLambdaの3つのセットが必要になります。これを都度作っていくのは大変そうだなと思います。かと言って、1つのLambda関数にどんどん処理を追加していくとLambdaに割り当てるAWSの権限等が肥大化し、各マイクロサービスのドメイン知識がLambdaに入り乱れる状況になったりします。

そこで私は、汎用的な仕組みを作りました。具体的には、Datadog MonitorからSNS、Lambdaという流れはそのまま踏襲して以下を満たしたいと考えました。

各マイクロサービスに具体的な処理をさせて、Lambdaは各マイクロサービスのREST APIのエンドポイントを叩くだけにしたいので、この場合、Lambdaはどこをどうやって叩けばいいのか判断をする必要があります。

他にもイベントに付随する情報をどうやって取得するべきかという問題があります。例えば銀行のチャージの場合は、どの銀行のレスポンスが遅いのか、障害に近いのかという情報が必要なので、銀行コードというものが必要です。

ただ、その処理をLambdaに書いてしまうと、汎用的にしたいという目的が達成されなくなってしまいます。また、右にスクリーンショットを貼っているとおり、Datadog Monitorにはいろいろなmonitor typeがあるので、汎用的に作るのであればそのすべてに対応できるようにする、というのが満たしたいことでした。

どうやって課題を達成していったのか

まずどこを叩けばいいのかをどうやって知るかというと、Datadogの設定のところにこういったかたちで書きます。通知先はここにあるように1つはSlackのチャンネルで、もう1つはSNSですね。SNSとSlackに通知します。if文でアラートを書けるので、サービスで叩いてほしいエンドポイントをここで指定することで達成できます。

次に、イベントに付随する情報をどうやって取得するべきか。またあらゆるmonitor typeにどうやって対応するべきかですが、どうやって達成できるかを考えるのにかなり時間がかかりました。調べてみたら、DatadogはREST APIを提供していることがわかりました。CURLだとこんな感じです。5言語ぐらいのライブラリがすでに用意されていて、Goもあるのでこんな感じでかなり簡単に書けます。

まず何をもとにREST APIを叩くかというと、SNSのメッセージにデフォルトで含まれているevent_idとmonitor_idというものがあります。ここにLambdaが受け取ったSNSのメッセージのログを載せているんですけど、event_idはこの部分に書いています。monitorもどういうmonitorかという情報が公開されています。これをもとにDatadogのREST APIを叩きます。

次に何を取得できるかというと、対象の銀行コードや、イベントが起きた時刻や今起きたイベントは何か、warnなのかalertなのかという情報が取得できます。WebUIにはかなりの情報があるんですが、基本的にすべてREST APIから取得が可能です。

Lambdaは作ってしまえば改修は不要

結果的にこうなりました。SNSとLambdaは1つずつ存在する。Datadog Monitorは監視したい項目ごとに作って通知先として登録します。Lambdaが指定されたコールバックを叩いて、各サービス(ECSやFargate)がevent_idやmonitor_idをもとに詳細を取得して、各自でRedisに書いたりデータベースに書いたりという処理をします。

Lambdaは何をするのかをもう少し詳しく話すと、event_idとmonitor_idを判別します。そして、circuit-breaker-callbackが指定されていれば叩きます。レスポンスステータスコードだけを見て200だと成功、200以外であればエラーとして、エラーが起きてもLambdaの関数内でエラーを投げるようにしています。Slack通知などは別の仕組みでしていて、こちらはあとで説明します。

Lambdaは再送の処理がデフォルトで組み込まれているので、再送を勝手にしてくれます。またデッドレターキューも設定できるので、再送上限で失敗した場合にデッドレターキューに勝手に入るという仕組みにもできます。Slack通知もデッドレターキューをDatadog Monitorで監視するという仕組みで達成できます。

Kyashではこれ以外でも、基本的にデッドレターキューはDatadog Monitorで監視してすぐに気づくようにしています。

結局何がうれしいのかというと、Lambdaの改修は1度作ってしまえば基本不要ということです。やることは単純でパースして叩く。レスポンスステータスコードを判断するというだけなので、改修は一切不要です。インフラ的に必要な要件がLambdaから各サービスへネットワークの到達性なので、基本的にSREの裁量はまったく発生しません。すでにあるコード資源、ドメイン知識などが利用できます。

最後にまとめです。作ったのはいいんですが、アラートやリカバリーの閾値の決めというのがすごく難しくて、最初はSlack通知をもとにどの辺りがいいのかを話すことになると思います。これはよくよく考えると、Datadog Monitorのイベントを各マイクロサービスにハンドリングする仕組みなので、Circuit Breaker以外の用途にも使えそうだなと思いました。

話しきれない部分も多かったので、興味がある方はアドベントカレンダーを参照していただければと思います。以上です。ご清聴ありがとうございました。