この記事では、Mackerelを使ったSLI/SLOによるサービス運用を紹介します。
SLI/SLOとは何か
SLI(Service Level Indicator)とはユーザーに提供したいサービスのレベル(水準)を計測する指標であり、SLO(Service Level Objective)とはSLIの目標値です。
サービスレベルを適切に定め、その指標(SLI)を実装・計測し、設定した目標値(SLO)を満たしているかどうかを定期的に確認することで、サービスレベルを適切に改善するための指針が得られます。
SLIの実装
運用者は、サービスがユーザーにとってどのようなレベルで提供されているかを定量的に計測できるようにSLIを実装します。たとえば可用性(Availability)や待ち時間(Latency)などがSLIとして挙げられます。
SLIは 有効なイベントのうち、良かったイベントの割合
で表現します。
表現を割合(0%〜100%)として統一することで直感的に理解でき、監視の閾値として設定したり他のツールと連携したりすることが容易になります。
SLOの設定
一方、SLOは達成目標となるSLIの値(99.5%や99.9%など)を設定します。
前述のとおりSLIは 有効なイベントのうち、良かったイベントの割合
で表現するため、SLOとして100%を設定したくなります。
しかしサービスは多くのファシリティやハードウェア、外部サービスを利用して動いており、それらが利用不可能な状態になるとSLIに影響を与えます。 たとえば、1台のEC2インスタンスだけで動くサービスの可用性をSLIとした場合、そのSLOはEC2の可用性より高く設定することはできません。
可用性の高いサービスを利用する、インスタンスを冗長化するなどのアプローチで可用性を高めることができますが、コストがかかってしまいます。
また、サービスの機能開発において、ソフトウェアの潜在的なバグやリリースにともなう障害もSLIに影響を与えます。
これに対し、設計・コードレビュー・テストなどを改善することで影響をある程度抑制できますが、それも限界があります。 最も効果が高いのは機能開発をしないことですが、それでは当然ながらサービスの成長が滞ってしまいます。
これらのことから、あまりにも高いSLOを設定するのは難しく、ましてや100%に設定するのは現実的ではないことが理解できます。
エラーバジェットとは
エラーバジェットは100%からSLOの値を引いて算出される値で、サービスが満足に利用できない時間の最大許容量のことです。
たとえば可用性をSLIとして定義し、そのSLOを99.9%と設定した場合、残りの0.1%がエラーバジェットとなります。
100% - SLO(99.9%) = 0.1%
ウィンドウサイズ(計測期間)を30日とした場合、その0.1%である約43分のダウンタイムが許容されることになります。
30日(43,200分) * 0.1 = 43.2分
エラーバジェットの消費状況から、機能開発を続行するのか、それとも機能開発を止めてSLIの改善に努めるのか……すなわち、新しい価値の提供と信頼性の維持のどちらを優先させるべきかを判断します。
さらに詳しく知るには
SLI/SLOに関してより詳しく知りたい場合は、下記の資料をご参照ください。
- O'Reilly Japan - SRE サイトリライアビリティエンジニアリング
- O'Reilly Japan - サイトリライアビリティワークブック
- [PUBLIC] The Art of SLOs – Participant Handbook - Google ドキュメント
ここからは、一般的なウェブサービスにおいて、Mackerelを利用してSLI/SLO監視を行う方法を解説します。
MackerelでのSLI/SLO運用
MackerelでSLI/SLOを運用する方法の1つとして、取得したメトリックを式監視や式グラフを利用してSLIの値に換算し、モニタリングすることが挙げられます。 Mackerelの式とは、関数を使って式を定義し、グラフとしてダッシュボードに並べたり、式によって計算された値を監視する監視ルールとして使えたりする機能です。この柔軟さを利用してSLI/SLOを運用してみましょう。
ここでは、AWSにてコンテナでアプリケーションを実行し、ロードバランサーを介してアクセスできるウェブサービスに対してSLI/SLOを運用する例を提示します。
また、ロードバランサーにMackerelのサービスロールとして example:alb
を設定します。
SLI/SLOを策定する
SLIは、ユーザーがそのサービスを利用したときのレイテンシやエラーの割合など、ユーザーの視点から体験を観測する必要があります。今回はそのユーザー体験を計測するメトリックとして、ALBのメトリックを利用します。
SLOの設定にあたっては、典型的なユーザーが満足する値をSLOとするのがよいとされています。SLI/SLO運用をこれから始めるのであれば、まずは現在のパフォーマンスを参考にSLOを設定し、運用を始めてから見直していくのがよいでしょう。
SLI | SLO | |
---|---|---|
可用性 | 有効なリクエストのうち成功したリクエストの割合。HTTPステータスコード500〜599以外を成功とする。 | 99.8%(ウィンドウサイズは1週間) |
レイテンシ | 有効なリクエストのうち十分に高速なリクエストの割合。400ミリ秒未満を十分に高速とする。 | 99.8% (ウィンドウサイズは1週間) |
このSLI/SLOをMackerelでモニタリングしてみましょう。
可用性の計測
ALBのHTTPステータスコードカウントを利用して可用性を計測してみましょう。 先程、可用性のSLIとして、有効なリクエストのうちHTTPステータスコード500〜599以外を成功したリクエストとしました。これは、システムのエラーのみを成功しなかったリクエストと定義したためです。 システムやサービスによって何を成功の状態とするのかは異なるため、まずはどの値を成功した数と全体の数とするかを定めてから、式に置き換えるとよいでしょう。
可用性は 成功したレスポンスの数/有効なリクエストの数
、つまり HTTPステータス 200〜499のレスポンス/有効なリクエストの数
で計算します。
AWSインテグレーションを利用してAWS ALBをMackerelに連携し、可用性のSLIを計算する式の例を以下に示します。
分単位ではなく中長期の傾向を把握するために、2週間の移動平均を利用しています。
scale( divide( movingAverage( sum( group( role(example:alb,custom.alb.httpcode_count.target_2xx), role(example:alb,custom.alb.httpcode_count.target_3xx), role(example:alb,custom.alb.httpcode_count.target_4xx) ) ), '2w' ), movingAverage( sum( group( role(example:alb,custom.alb.httpcode_count.*) ) ), '2w' ) ), 100 )
レイテンシの計測
ALBのレスポンスタイムが閾値を超えていない割合をレイテンシのSLIとしました。 しかし、ALBのメトリックとしてパーセンタイルはありますが、レスポンスタイムが指定の値を超えたリクエストの数のメトリックはありません。そのため、ALBのログを利用して計算することとします。ALBのログはAmazon Athenaでクエリを利用して集計することができます。
レイテンシは 十分に高速なリクエストの割合/有効なリクエスト
で計算します。
ログがAthenaで検索できるまでの遅延を考慮して、サブクエリで検索対象のタイムスタンプを生成しています。
レイテンシのSLIはユーザーから見た値として request_processing_time
、target_processing_time
、response_processing_time
(それぞれミリ秒精度の秒単位)を加算します。また、400ミリ秒未満を十分に高速としているため、0.4を超えたリクエスト数を数えています。
なお下記クエリは、日付単位のパーティション date
が Athena のテーブルに作成されている環境にて動作します。ALB でのパーティションの作成については、パーティション射影を使用した Athena での ALB ログ用のテーブルの作成 などを参考に作成してください。
また、date
パーティションを利用した絞り込み部分では、11分前~10分前のログが集計対象になることから、00:00 ~ 00:10 までに実行した際に前日分のパーティション配下のログも必要となるため、start_time
ならびに end_time
の年月日を用いて絞り込みを行っています。
WITH time_window AS ( SELECT end_time - interval '1' minute as start_time, end_time FROM ( SELECT date_trunc('minute', now() - interval '10' minute) as end_time ) ) SELECT count(*) as count FROM <ALBのログ用のテーブル名>, time_window where ( date = date_format(start_time, '%Y/%m/%d') or date = date_format(end_time, '%Y/%m/%d') ) AND from_iso8601_timestamp(time) BETWEEN CAST(time_window.start_time AS timestamp) AND CAST(time_window.end_time AS timestamp) AND request_processing_time + target_processing_time + response_processing_time > 0.4
なお、Mackerel開発チームでは、Athenaで実行したクエリの結果をMackerelへ投稿するツールとしてmackerelio-labs/mackerel-sql-metric-collectorを利用しています。 また、ユーザーによるSLI/SLO向けツールとしてmashiike/shimesabaなどもあります。
ダッシュボードで定期的に状況を確認する
SLI/SLOは定めたら終わりではありません。サービスの状況は成長に合わせて変化するものなので、定期的に確認し、必要であれば内容を見直して目標値を変更したり追加したりする必要があります。たとえばユーザーが増えるとキャパシティの見直しが必要になるかもしれません。
Mackerelでは、カスタムダッシュボードを利用して確認しておきたいメトリックをまとめることができます。チームでダッシュボードを見て状況の認識を一致させることも期待できます。
たとえばMackerel開発チームでは、数ヶ月おきにSLIの定義とSLOが適切かを見直し、隔週でシステムメトリックと合わせてサービス全体のメトリックとSLOのダッシュボードを見ています。ここでSLOに違反しそうな兆候を見つけた場合は、パフォーマンス改善やキャパシティの見直しを行っています。
式による監視でモニタリングする
定期的にダッシュボードで様子を確認するのも重要ですが、SLOを下回りそうな場合にアラートが上がるようにしておくと、よりリアルタイムに状況を把握できます。 実験的機能にはなりますが、先程用意した式を利用して、式による監視を設定してみましょう。
先程の可用性を「式による監視」で監視する場合の例がこちらです。 タイムウィンドウより間隔を狭めた移動平均を監視することで、このままの状態でSLOを下回らないかを監視できます。
movingAverage( scale( divide( role(example:alb,custom.alb.httpcode_count.target_2xx), sum(role(example:alb,custom.alb.request.*)) ), 100.0), 1w)
SLI/SLOを運用と開発に活用するためには
前述のとおり、エラーバジェット内であれば積極的に機能開発を行い、エラーバジェットを消費しきった場合はSLIの改善に努める必要があります。 今回例示したSLOでエラーバジェットを消費しきった場合には、たとえば以下のようなアクションが考えられます。
- 可用性のSLOに違反した場合
- ステータスコードで失敗を返しているエンドポイントの特定と、エラーの原因の調査・対応
- キャパシティが不足していたら追加する
- レイテンシのSLOに違反した場合
- パフォーマンスを悪化させているクエリがあればチューニングを行う
- キャパシティが不足していたら追加する
SLI/SLOの運用では、エラーバジェットを消費することが重要です。 たとえばエラーバジェットを消費していない状態だと、過剰なキャパシティを利用していることや機能開発が行われていないことを意味することもあります。SLIが高すぎると想定以上の信頼性を提供していることになるため、エラーバジェットを消費してSLIがSLOに近づくような運用がよいでしょう。
まとめ
この記事では、Mackerelを利用したSLI/SLO運用の一例を紹介しました。 紹介した以外にも手法や考慮事項はありますが、まずは取り組んでみて改善してみることが重要です。 Mackerelには、この記事で触れなかった機能もたくさんありますので、ぜひご活用ください。