この記事はMackerel Advent Calendar 2022とKAYAC Advent Calendar 2022 の7日目です。
こんにちは、SREチーム所属の@mashiikeです。
前年の Mackerel Advent Calendar 2021 ではSREにおいて大事なSLO/エラーバジェットをMackerelのメトリックとして投稿するツール shimesaba について話しました。 1年経って、Mackerelを用いたSLI/SLO/エラーバジェットの運用が安定化してくると、次に気になってくるのはトイルだと思います。
「トイルとは、手作業、繰り返される、自動化が可能、戦術的、長期的な価値がない、サービスの成長に比例して増加する、といった特徴を持つ作業です。」 トイルの例としては次のようなものがあります。
・割り当てリクエストの処理
・データベース スキーマ変更の適用
・重要性の低いモニタリング アラートの確認
・プレイブックからのコマンドのコピーと貼り付けここに挙げたすべての例に共通する特徴は、エンジニアが頭で判断する必要がないということです。
さて、上記の例の中で、Mackerelでのシステム運用に関係するトイルとは、『重要性の低いモニタリング アラートの確認』です。 今回は、アラート対応におけるトイルを削減するためのツール prepalert について話したいと思います。
アラート対応におけるトイル
冒頭で挙げられたトイル『重要性の低いモニタリング アラートの確認』とは一体どういうことでしょう?
ここで言う『重要度の低い』という言葉はいろいろな解釈があるとは思います。
一つの考え方として『エラーバジェットが削りきれていないような状況でのアラート』や『ユーザーに重大なエラーが出ていないようなアラート』のことを指すと考えられます。
エラーバジェットが削れていたり、すでにユーザーに見える形でエラーが起きている場合、できるだけ早く対応する必要があるため人間が様々な調査を行うことでしょう。
しかしながら、そうでない場合・・・例えば『単発の5xxエラーに関するWarnアラート』のような、起きていることを把握はしたいが一瞬発生しただけでは慌てる必要のないというアラートは運用中フェーズではよくあることでしょう。
そういった、重要度の低いアラートは確認項目が定型化することはよくあると思います。
shimesabaを導入したあるプロダクトでは、SLO:latency として『ALBのレスポンスタイムのp90が700ms以内である』というものを設定しています。
週次のSRE定例で、SLO:latencyのエラーバジェットがどれぐらい削られたのか?いつ削られたのか? というのをMackerelのアラートベースで確認するようにしています。
これらのアラートはSRE的にはエラーバジェットが残っている状況では、それほど焦る必要はなく重要度の低いアラートとなります。
しかし、週次の定例では、ALBやNginxのアクセスログにクエリして、毎回手作業で以下のような確認が行われていました。
- どのエンドポイントが起因となって発生したのか?
- 具体的なリクエストの件数は?
- リファラーはどこなのか?
- 対象となったリクエストIDは?
- p90以外のmedianやaverage,max,minなどのレスポンスタイムは?
これらの確認は、トイルでありエンジニアのモチベーションを下げる要因になっていました。
そこで、このトイルを解消するために生み出されたのが prepalert になります。
※ 名前の由来は prepare + alert
トイル削減ツール 『prepalert』
prepalertは何をするツールか?
一言で言えば『アラート対応ために必要な情報を、アラートのメモやグラフアノテーションとして書き出すツール』です。
アーキテクチャーは以下のようになってます。
alertをWebhookとして受け取って、SQSへメッセージを送ります。 送られたSQSメッセージをトリガーにして、workerが起動して、RedshiftへのクエリやS3 Select、Cloudwatch Logs Insightsなどを駆使して関連情報を取得して、アラートのメモやグラフアノテーションとしてMackerelに投稿します。
Terraform等での構築の例として github.com/mashiike/prepalert/lambda がありますので、こちらをご覧いただけるとより詳細がわかると思います。
実際の動作例としては、以下のようになっています。
この例では、各種のログに問い合わせて、その結果をアラートのメモに投稿しています。
その時間帯で、エンドポイントごとのp90が閾値を超えたものを列挙している例です。
prepalertが入る以前は、redash等でクエリした結果を共有していましたが、prepalertが入ることによって自動で情報がアラートに関連付けられるようになりました。
prepalertによって、現在では重要度の低いのアラートの確認がスムーズに行くようになりました。
prepalertの使い方
prepalertの簡単な使い方を紹介します。
Mac,Linuxをお使いの方はbrewコマンドでインストール可能です。
$ brew install mashiike/tap/prepalert
prepalertをインストールしたら、初期のconfigを生成します。
$ prepalert init Which Mackerel service do you use to post graph annotations?: dev Which SQS Queue do you use?: dev-prepalert Try running the following two commands in a separate terminal: $ prepalert --config . --mackerel-apikey <your Mackerel api key> run --mode webhook $ prepalert --config . --mackerel-apikey <your Mackerel api key> run --mode worker When performing a local operating environment, request the following $ cat event.json | curl -d @- -H "Content-Type: application/json" http://localhost:8080 Have fun prepalert.
Mackerelのgraph annotationを投稿するサービスやSQSのQueue名を聞かれるので入力します。
コンソール上では、ローカルで動かす場合の方法が書かれています。
実際に使う場合はAWS Cloud上に構築することになります。
生成されたconfigを見てみましょう。コメントアウトされてる部分を取り除くとこのような感じになっています。
config.hcl
// Composition of the entire prepalert prepalert { required_version = ">=v0.8.0" sqs_queue_name = "dev-prepalert" service = "dev" } rule "simple" { alert { any = true on_opened = true on_closed = false } information = "How do you respond to alerts?" update_alert_memo = true max_alert_memo_size = 10000 //If the size of the memo exceeds 10KB, a part of the memo will be omitted. This setting can be changed from 100Bytes ~ 80KB. post_graph_annotation = false }
ある特定のアラートについて、失敗等のリカバリやクエリチェック向けで用意してる exec
コマンドを使ってみましょう。
$ export MACKEREL_APIKEY=<your Mackerel API key (writable)> $ prepalert --config . exec <alert_id> 2022/12/01 17:39:07 [info] try get sqs queue url: dev-prepalert 2022/12/01 17:39:07 [info][-] match rule `simple` 2022/12/01 17:39:07 [info][-] update alert memo: alert_id=<alert_id> 2022/12/01 17:39:08 [info][-] 1 rules match
コレを実行すると、対象のアラートのメモが以下のようになります。
この設定の状態で、AWS Cloud上に構築して、Mackerelのwebhookを設定すると、すべてのアラートのメモに How do you respond to alerts?
という固定の文字列が設定されるようになります。
このままでも、アラート対応の手順書等をメモに入れる使い方ができますが、より実用的な例を示します。
監視のIDが4xxxxxxxxxx
であるアラートが来た際に、アラートの発生30分前から現在までの間の時刻について、ALBのログをS3 Select経由でクエリし、5xxのリクエストの情報をアラートのメモに残す設定です。
config.hcl
prepalert { required_version = ">=v0.8.0" sqs_queue_name = "dev-prepalert" service = "dev" } query_runner "s3_select" "default" { region = "ap-northeast-1" } query "alb_5xx_logs" { runner = query_runner.s3_select.default bucket_name = "logs-example-com" object_key_prefix = "external-alb/AWSLogs/123456789012/elasticloadbalancing/ap-northeast-1/${strftime("%Y/%m/%d", runtime.event.alert.opened_at)}/" compression_type = "GZIP" csv { field_delimiter = " " record_delimiter = "\n" } expression = templatefile("get_alb_5xx_log.sql", { runtime = runtime }) } rule "alb_5xx" { alert { monitor_id = "4xxxxxxxxxx" on_opened = true on_closed = false } queries = [ query.alb_5xx_logs, ] information = <<EOT 下記のALBのログを確認しましょう。 ALBログの読み方は、https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-access-logs.html を参照してください。 ※ "actions_executed"が"waf-failed"の場合は、error_reasonで詳細を確認しWAFConnectionTimeout の場合は "AWS WAF への接続がタイムアウトした"ことを意味します。AWS WAFのSLAは99.95%でよく出る上に深刻ではない可能性が高いので単発の場合は一旦放念しましょう。 それ以外で、気になる場合は詳細調査を行いましょう。 ${runtime.query_result.alb_5xx_logs.json_lines} EOT update_alert_memo = true max_alert_memo_size = 80000 post_graph_annotation = true }
get_alb_5xx_log.sql
SELECT s._1 as "type" ,s._2 as "time" ,s._3 as "elb" ,s._4 as "client_port" ,s._5 as "target_port" ,s._6 as "request_processing_time" ,s._7 as "target_processing_time" ,s._8 as "response_processing_time" ,s._9 as "elb_status_code" ,s._10 as "target_status_code" ,s._11 as "received_bytes" ,s._12 as "sent_bytes" ,s._13 as "request" ,s._14 as "user_agent" ,s._15 as "ssl_cipher" ,s._16 as "ssl_protocol" ,s._17 as "target_group_arn" ,s._18 as "trace_id" ,s._19 as "domain_name" ,s._20 as "chosen_cert_arn" ,s._21 as "matched_rule_priority" ,s._22 as "request_creation_time" ,s._23 as "actions_executed" ,s._24 as "redirect_url" ,s._25 as "error_reason" ,s._26 as "target_port_list" ,s._27 as "target_status_code_list" ,s._28 as "classification" ,s._29 as "classification_reason" FROM s3object s WHERE TO_TIMESTAMP(s._2) >= DATE_ADD(minute,-30,TO_TIMESTAMP('${strftime_in_zone("%Y-%m-%dT%H:%M:%SZ","UTC",runtime.event.alert.opened_at)}')) AND TO_TIMESTAMP(s._2) <= TO_TIMESTAMP('${strftime_in_zone("%Y-%m-%dT%H:%M:%SZ","UTC",runtime.event.alert.closed_at)}') AND cast(s._9 as INT) >= 500
このように、query_runnerとqueryという設定を追加すると、prepalertは設定された内容に従い情報を取得します。
そして、 ${runtime.query_result.<query_name>.json_lines}
というような形で、参照してメモにその結果を埋め込むことが可能になります。
prepalertにはまだ紹介できていない機能もありますが、大まかにはこのような動きをするツールとなっております。
おわりに
prepalert
はアラート対応で必要となる情報をMackerelに集約するために生まれたトイル削減ツールです。
アラート確認のために、RedshiftへのクエリやS3 Select、Cloudwatch Logs Insightsに定型のクエリを実行している場合は、是非使用を検討してみてください。
いつか皆様のSREの活動の役に立てれば幸いです。