Slide 1

Slide 1 text

自 動 生 成を活 用 した 運 用 保守コストを抑える Error/Alert/Runbook の 一 元集約管理 株式会社サイバーエージェント AI事業本部 岩 見 彰太 GitHub:@BIwashi X: @B_Sardine DevOpsDays Tokyo 2024

Slide 2

Slide 2 text

自己 紹介 岩 見 彰太 / Iwamin 株式会社サイバーエージェント ೥౓৽ଔೖࣾ "*ࣄۀຊ෦ڠۀϦςʔϧϝσΟΞ%JW খചاۀͷΞϓϦ։ൃ @BIwashi @B_Sardine

Slide 3

Slide 3 text

1 .はじめに 2 .エラー検知の重要性 3 .アラートの重要性と注意点 4 .「アラートが来た!」で終わらせず 行 動に繋げるには 5 . 自 動 生 成による情報の集約 6 .エラー検知で終わらせずに進捗を追う 7 .まとめ

Slide 4

Slide 4 text

はじめに

Slide 5

Slide 5 text

ターゲット:以下の問いにドキッとした 方 へ • エラーに対して適切にアラートを設定していますか? ただひたすらよく分からないエラーが垂れ流されている Slack チャンネルはありませんか? •そのアラートの内容、みんながある程度把握できていますか? 実装者しか把握できない内容になっていませんか? •そのアラートが来た時、どのような 行 動をすればいいかすぐわかりますか? •そのアラート対応、誰がやっていますか?もしくは誰も対応されず放置され ていませんか?その情報を追えていますか?

Slide 6

Slide 6 text

エラー検知の重要性

Slide 7

Slide 7 text

エラー検知の重要性 • 開発を進めていく中で適切なエラー検知は不可 欠 適切なハンドリングを 行 うことで、エラー発 生 から修正までの時間 を最 小 化できる • より正確で詳細な情報を事前に仕込むことが重要 • Sentry や Datadog Error Tracking など、エラーの種類 を適切に振り分けらられるような仕組みを利 用 Tag Stack Trace Custom Logger Log Level Error Message Status Code

Slide 8

Slide 8 text

アラートの重要性と注意点

Slide 9

Slide 9 text

アラートの重要性と注意点 • エラーを適切に検知してアラートを設定する仕組みは重要 エラーを垂れ流しているだけでは気づけない • アラート流せばいいというものではない アラートを闇雲の流しすぎると狼少年化してしまう 本当に重要なアラートを逃してしまう • アラートの内容がわかる 人 が属 人 化されないようにする必要がある アラートが発 生 される際は基本的に異常時で何かしら対処が必要なはず 対処 方 法、 行 動を把握できる 人 は増えるに越したことはない

Slide 10

Slide 10 text

アラートの属 人 化 エラーメッセージ スタックトレース ログリンク etc. エラー内容 分かる 人 エラー内容 分からない 人 エンジニア じゃない 人 あーこれは対処しなくていいやつだ。 一 旦放置! よく通知されるけどどこのエラーなんだ…?何が起き てるんだ…?対処する必要あるのか?ちゃんと調査し たほうがいいのかな… なんかヤバそうな雰囲気はあるけど何も分からん。〇〇さ ん(エラー内容分からない 人 )に聞いてもよく分からない と 言 われた。アラートチャンネルはもう 見 るのやめよう😇 #hoge_alert

Slide 11

Slide 11 text

アラートの属 人 化 エラーメッセージ スタックトレース ログリンク etc. エラー内容 分かる 人 エラー内容 分からない 人 エンジニア じゃない 人 あーこれは対処しなくていいやつだ。 一 旦放置! よく通知されるけどどこのエラーなんだ…?何が起き てるんだ…?対処する必要あるのか?ちゃんと調査し たほうがいいのかな… なんかヤバそうな雰囲気はあるけど何も分からん。〇〇さ ん(エラー内容分からない 人 )に聞いてもよく分からない と 言 われた。アラートチャンネルはもう 見 るのやめよう😇 そもそもこれは アラートすべきではない #hoge_alert

Slide 12

Slide 12 text

「アラートが来た!」 で終わらせずに 行 動に繋げるには

Slide 13

Slide 13 text

どう 行 動に移すか • ひたすらエラー内容が流れてくるだけで誰もみていないチャンネル… それはアラートを流している意味がない • アラートするからには… 何か伝えたいことがある それに応じて 行 動すべきことがあるはず • アラート内容をもとに次の 行 動に繋げていく必要がある 具体的な 行 動 手 順があるとすぐに 行 動できる アラートは エラー →対処 を動かすためのトリガー

Slide 14

Slide 14 text

どう 行 動に移すか 手 順書のない世界 何すればいいかわからん! どこから 手 をつければええんや! えっと… 手 順書によるとこれをすればいいのか…! まずはここの値を調査して、XXXしてみるか。 手 順書のある世界 手 順書 Runbook Playbook 手 順書があるとアラートに対してより詳細な情報を付与できる →すぐに 行 動に移せる/ 行 動できる 人 を増やせる

Slide 15

Slide 15 text

手 順書(Runbook/Playbook) • アラートへの対処法や調査すべき項 目 などをあらかじめ記載しておく 想定外のエラーが出ても調査にとっかかりやすくなる 想定できうるエラーであれば 手 順書を明記しておくことで、認知コストを下げられる • 手 順書はアラートとの紐付けとメンテナンスが肝 メンテナンスされておらず古い情報… 記載場所が決まっておらず同じような 手 順書が複数 見 つかる… 手 順書のどこを 見 ればいいかわからない… खॱॻʢrunbookʣ͸Ξϥʔτ͕དྷͨ࣌ʹ͢͹΍ࣗ͘෼ͷਐΉ΂͖ํ޲Λࣔ͢ૉ੖Β͍͠ํ๏Ͱ ͢ɻ ؀ڥ͕ෳࡶʹͳͬͯ͘ΔͱɺνʔϜͷ୭΋͕֤γεςϜͷ͜ͱΛ஌͍ͬͯΔΘ͚Ͱ͸ͳ͘ͳ Γɺखॱॻ͕஌ࣝΛ޿ΊΔΑ͍ํ๏ʹͳΓ·͢ɻ by ೖ໳؂ࢹ

Slide 16

Slide 16 text

情報の分散(Custom Code で紐づける例) Custom Code を埋め込む実装と Runbook が分離してしまう 参照するのもメンテナンスするのもコストがかかる エラーメッセージ スタックトレース ログリンク etc. + Custom Code Runbook with Custom Code エラーにCustom Codeを埋め込む Notion Con fl uence Github Wiki etc … Custom Code で紐づける エラー内にある Custom Code から Runbook を検索して内容を確認して 行 動に移す

Slide 17

Slide 17 text

自 動 生 成による情報の集約

Slide 18

Slide 18 text

情報集約のモチベーション • Custom Code と Runbook によるアプローチはいいが保守コストが 高 い できるだけ集約してそこだけをみれるようにしたい • アラート通知の内容をできるだけ誰でもわかるようにしたい エラー内容によってアラート 文 をリッチに変えるようにしたい • リッチな Runbook をログに埋め込むとログ量が増えてしまうため避けたい Custom Code で連携するというアプローチではいきたい そもそもアラートが Runbook にもなっていればいいのでは…? アラートメッセージを Custom Code に応じて出し分ければいいのでは…?

Slide 19

Slide 19 text

Go Generate!! The Go gopher was designed by Renée French. Created using gopherize.me

Slide 20

Slide 20 text

全体図

Slide 21

Slide 21 text

全体図

Slide 22

Slide 22 text

Protoc Plugin • Protocol Bu ff ers に記述された情報を使 用 して 自 動 生 成などを 行 う • Extend を使 用 して message や fi eld に情報を 追加したり拡張することができる • 今回は protoc plugin をサクッと書ける protoc-gen-star(PG*)を使 用 Go Conference mini 20 23 Winter in KYOTO

Slide 23

Slide 23 text

enum ReasonCode { RC00000 = 0 [(ext.reason_code) = { message: [ "hoge͕ൃੜ͠·ͨ͠", "`http://example.com` Λࢀরͯ͠ϦιʔεΛ", ”֬ೝ͍ͯͩ͘͠͞", "fooͩͬͨ৔߹͸foo͍ͯͩ͘͠͞" ] }]; RC00001 = 1 [(ext.reason_code) = { message: [ "piyo͕μ΢ϯ͍ͯ͠·͢", "PMͱ࿈ܞͯ͠piyoʹ࿈ܞ͍ͯͩ͘͠͞" ] }]; } Reason code proto fi le

Slide 24

Slide 24 text

enum ReasonCode { RC00000 = 0 [(ext.reason_code) = { message: [ "hoge͕ൃੜ͠·ͨ͠", "`http://example.com` Λࢀরͯ͠ϦιʔεΛ", "֬ೝ͍ͯͩ͘͠͞", "fooͩͬͨ৔߹͸foo͍ͯͩ͘͠͞" ] }]; RC00001 = 1 [(ext.reason_code) = { message: [ "piyo͕μ΢ϯ͍ͯ͠·͢", "PMͱ࿈ܞͯ͠piyoʹ࿈ܞ͍ͯͩ͘͠͞" ] }]; } Reason code proto fi le • Reason Code • 実装側とRunbookを紐づける独 自 の code • 後に Datadog のモニタリングを設定する際 に重要

Slide 25

Slide 25 text

enum ReasonCode { RC00000 = 0 [(ext.reason_code) = { message: [ "hoge͕ൃੜ͠·ͨ͠", "`http://example.com` Λࢀরͯ͠ϦιʔεΛ", "֬ೝ͍ͯͩ͘͠͞", "fooͩͬͨ৔߹͸foo͍ͯͩ͘͠͞" ] }]; RC00001 = 1 [(ext.reason_code) = { message: [ "piyo͕μ΢ϯ͍ͯ͠·͢", "PMͱ࿈ܞͯ͠piyoʹ࿈ܞ͍ͯͩ͘͠͞" ] }]; } Reason code proto fi le • Message(Runbook) • その Reason Code に紐づく Runbook • 実際に管理したい Runbook の内容を 入 れて おく

Slide 26

Slide 26 text

全体図

Slide 27

Slide 27 text

// Code generated by protoc-gen-go-reason-code. type ReasonCode string const ( RC00000 ReasonCode = "RC00000" // Message: hoge͕... RC00001 ReasonCode = "RC00001" // Message: piyo͕... ) // Custom Error type Error struct { error ... reasonCodes []ReasonCode ... } func WithReasonCode(rc ReasonCode) Option { return func(e *Error) { e.reasonCodes = append(e.reasonCodes, rc) } } Application Code • Custom Error に Reason Codes を 入 れられるようにする • 定数の Reason Code を 自 動 生 成する • 自 動 生 成されたものをエラーハンド リングしている箇所で 入 れる

Slide 28

Slide 28 text

{ "error": { "reason_codes": [ "RC00000" ], ... "message": "hoge error" } } Error JSON • 構造化ログの error に reason codes を出しておく • 後に Datadog を使 用 する際に重要

Slide 29

Slide 29 text

全体図

Slide 30

Slide 30 text

Datadog Monitors • エラーが発 生 した際にアラートを 飛 ばすモニタリングアラートを設定 Error Tracking Base で設定 • Datadog の Message (アラート内容)では独 自 の条件付き変数が利 用 できる これを利 用 して reason code によって message (Runbook)を分岐する

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

is_match によるMessage分岐 • Datadog のMessageでは log.attributes で構造化されたログのフィールド の値を取得できる • error に埋め込んだ reason_codes を is_match で検知して、 message(runbook)を表 示 する

Slide 33

Slide 33 text

is_match によるMessage分岐 • Datadog のMessageでは log.attributes で構造化されたログのフィールド の値を取得できる • error に埋め込んだ reason_codes を is_match で検知して、 message(runbook)を表 示 する 配列フィールドの場合、以下のよう に検知されるため ”” で囲っている “RC00000”,“RC00001”, …

Slide 34

Slide 34 text

全体図

Slide 35

Slide 35 text

{{!-- Monitor generated by protoc-gen-go-reason-code and terraform. DO NOT EDIT. --}} {{ log.message }} {{#is_match "log.attributes.error.reason_codes" "\"RC00000\"" }} reason_code: `RC00000` ``` hoge͕ൃੜ͠·ͨ͠ `http://example.com` Λࢀরͯ͠ϦιʔεΛ ֬ೝ͍ͯͩ͘͠͞ fooͩͬͨ৔߹͸foo͍ͯͩ͘͠͞ ``` {{/is_match}} {{#is_match "log.attributes.error.reason_codes" "\"RC00001\"" }} reason_code: `RC00001` ``` piyo͕μ΢ϯ͍ͯ͠·͢ PMͱ࿈ܞͯ͠piyoʹ࿈ܞ͍ͯͩ͘͠͞ ``` {{/is_match}} > `{{{ log.attributes.error.file }}}` > **{{{ log.attributes.error.message }}}** - [Log Link]({{log.link}}) - Issue ID: [{{[issue.id].name}}](https://app.datadoghq.com/logs/error-tracking/issue/ {{[issue.id].name}}) - Version: {{log.attributes.version}} {{#is_alert}} @${notification_alert_channel} {{/is_alert}} Terraform による管理 • Datadog の Monitors は Terraform 管理可能 • proto に記述した内容を元に reason code による分岐と message(Runbook)を 生 成する application_error_alert.pb.md

Slide 36

Slide 36 text

resource "datadog_monitor" "application_error_alert" { name = "[${var.env}] αʔόʔΤϥʔ͕ൃੜ͠·ͨ͠" type = "error-tracking alert" tags = ["env:${var.env}", "managed_by:terraform"] priority = 2 on_missing_data = "resolve" include_tags = true group_retention_duration = "3d" monitor_thresholds { critical = 0 } query = <<-EOT error-tracking-logs( "(issue.age:<=300000 OR issue.regression.age:<=300000) env:${var.env}" ).index("*") .rollup("count") .by("issue.id") .last("5m") > 0 EOT message = templatefile( "${path.module}/messages/application_error_alert.pb.md", { notification_alert_channel = var.notification_alert_channel } ) } Terraform による管理 • protoc plugin で 生 成した Markdown を template fi le 関数で 読み込み設定する monitor.tf

Slide 37

Slide 37 text

resource "datadog_monitor" "application_error_alert" { name = "[${var.env}] αʔόʔΤϥʔ͕ൃੜ͠·ͨ͠" type = "error-tracking alert" tags = ["env:${var.env}", "managed_by:terraform"] priority = 2 on_missing_data = "resolve" include_tags = true group_retention_duration = "3d" monitor_thresholds { critical = 0 } query = <<-EOT error-tracking-logs( "(issue.age:<=300000 OR issue.regression.age:<=300000) env:${var.env}" ).index("*") .rollup("count") .by("issue.id") .last("5m") > 0 EOT message = templatefile( "${path.module}/messages/application_error_alert.pb.md", { notification_alert_channel = var.notification_alert_channel } ) } Terraform による管理 • protoc plugin で 生 成した Markdown を template fi le 関数で 読み込み設定する monitor.tf 複雑かつ 大 量の分岐だが 自 動 生 成してるため コスト0で実現可能

Slide 38

Slide 38 text

アラート • アラートの中にすでに Runbook の内容 が含まれている Runbook を探すという作業が必要ない • エラーに関する実装上の情報などだけで はなくより豊富な情報をアラートで提供 非 実装者や 非 エンジニアでも温度感が分かる 対処すべき 行 動がすぐわかる アラートに対する認知者の増加 注 目 が集まることによる 見 逃し軽減

Slide 39

Slide 39 text

エラー検知で終わらせずに 進捗を追う

Slide 40

Slide 40 text

エラー検知で終わらせずに進捗を追う • アラートされたエラーに対して対処する必要がある場合それをしっかり チケット化して進捗を追っていくことが重要 • チケットとアラートが紐づいていないと進捗管理が困難 • そのエラーの現在のステータスがわからない どれだ…? すでに対応済み(反映待ち) 修正したが再発(デグレ) 誰かが修正している 誰も対応しておらず修正が必要

Slide 41

Slide 41 text

全体図

Slide 42

Slide 42 text

Error Tracking×Casa Management×Jira • Error Tracking の Issue から Case Management を作成 事前に Jira と連携しておくと Case を作成すると 自 動でチケットが切られる 担当者やステータスも双 方 向で同期できる %BUBEPH$BTB.BOBHFNFOU

Slide 43

Slide 43 text

Error Tracking×Casa Management×Jira • Error Tracking の Issue から Case Management を作成 事前に Jira と連携しておくと Case を作成すると 自 動でチケットが切られる 担当者やステータスも双 方 向で同期できる %BUBEPH$BTB.BOBHFNFOU +JSB

Slide 44

Slide 44 text

まとめ • Alert の適切な管理が重要 • Runbook を活 用 した Error に対する認知負荷の軽減 • protoc plugin + Datadog + Terraform Reason Code と Runbook を 自 動 生 成で紐づけて管理コストを軽減 • タスク管理ツールと Error を紐付ける Error とタスクチケットを紐づけて対応の進捗までリンクしておく ステータスを確認するコスト軽減 情報の集約、連携でエラー取り逃がさず追い続ける