Mackerelでのアラート対応のためのトイルを削減するツール prepalert

この記事はMackerel Advent Calendar 2022KAYAC Advent Calendar 2022 の7日目です。
こんにちは、SREチーム所属の@mashiikeです。

前年の Mackerel Advent Calendar 2021 ではSREにおいて大事なSLO/エラーバジェットをMackerelのメトリックとして投稿するツール shimesaba について話しました。 1年経って、Mackerelを用いたSLI/SLO/エラーバジェットの運用が安定化してくると、次に気になってくるのはトイルだと思います。

cloud.google.com

「トイルとは、手作業、繰り返される、自動化が可能、戦術的、長期的な価値がない、サービスの成長に比例して増加する、といった特徴を持つ作業です。」 トイルの例としては次のようなものがあります。

・割り当てリクエストの処理
・データベース スキーマ変更の適用
・重要性の低いモニタリング アラートの確認
・プレイブックからのコマンドのコピーと貼り付け

ここに挙げたすべての例に共通する特徴は、エンジニアが頭で判断する必要がないということです。

さて、上記の例の中で、Mackerelでのシステム運用に関係するトイルとは、『重要性の低いモニタリング アラートの確認』です。 今回は、アラート対応におけるトイルを削減するためのツール prepalert について話したいと思います。

github.com

アラート対応におけるトイル

冒頭で挙げられたトイル『重要性の低いモニタリング アラートの確認』とは一体どういうことでしょう?
ここで言う『重要度の低い』という言葉はいろいろな解釈があるとは思います。
一つの考え方として『エラーバジェットが削りきれていないような状況でのアラート』や『ユーザーに重大なエラーが出ていないようなアラート』のことを指すと考えられます。
エラーバジェットが削れていたり、すでにユーザーに見える形でエラーが起きている場合、できるだけ早く対応する必要があるため人間が様々な調査を行うことでしょう。
しかしながら、そうでない場合・・・例えば『単発の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の活動の役に立てれば幸いです。

カヤックではサービスの信頼性を追求するエンジニアも募集しています

hubspot.kayac.com