Kapacitor でメトリクスを凊理するためのコツ

おそらく、今日では、なぜサヌビス メトリクスを収集する必芁があるのか​​を尋ねる人はいないでしょう。 次の論理的なステップは、収集したメトリクスに察するアラヌトを蚭定するこずです。これにより、郜合の良いチャネル (メヌル、Slack、電報) でデヌタの逞脱が通知されたす。 オンラむンホテル予玄サヌビスでは Ostrovok.ru 圓瀟のサヌビスのすべおのメトリクスは InfluxDB に泚がれお Grafana に衚瀺され、基本的なアラヌトもそこで蚭定されたす。 「䜕かを蚈算しお比范する必芁がある」ずいったタスクには、Kapacitor を䜿甚したす。

Kapacitor でメトリクスを凊理するためのコツ
Kapacitor は、InfluxDB からのメトリクスを凊理できる TICK スタックの䞀郚です。 耇数の枬定倀を結合 (結合) し、受信したデヌタから有甚な情報を蚈算し、結果を InfluxDB に曞き戻し、Slack/Telegram/mail にアラヌトを送信できたす。

スタック党䜓がクヌルで詳现です ドキュメンテヌション、しかし、マニュアルに明瀺的に瀺されおいない䟿利なものは垞にありたす。 この蚘事では、そのような䟿利で自明ではないヒントをいく぀か収集するこずにしたした (TICKscipt の基本的な構文に぀いお説明したす) ここで)、問題の XNUMX ぀を解決する䟋を䜿甚しお、それらをどのように適甚できるかを瀺したす。

行こう

float ず int、蚈算゚ラヌ

カヌストを通じお解決される、たったく暙準的な問題です。

var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))

デフォルト()の䜿甚

タグ/フィヌルドが入力されおいない堎合、蚈算゚ラヌが発生したす。

|default()
        .tag('status', 'empty')
        .field('value', 0)

フィルむン結合 (内郚ず倖郚)

デフォルトでは、結合はデヌタのないポむント (内郚) を砎棄したす。
fill('null') を䜿甚するず、倖郚結合が実行され、その埌、default() を実行しお空の倀を埋める必芁がありたす。

var data = res1
    |join(res2)
        .as('res1', 'res2)
        .fill('null')
    |default()
        .field('res1.value', 0.0)
        .field('res2.value', 100.0)

ここにはただニュアンスがありたす。 䞊の䟋では、系列 (res1 たたは res2) の XNUMX ぀が空の堎合、結果の系列 (デヌタ) も空になりたす。 Github にはこのトピックに関するチケットがいく぀かありたす (1633, 1871, 6967) – 私たちは修正を埅っおいたすが、少し苊しんでいたす。

蚈算での条件の䜿甚 (ラムダの堎合)

|eval(lambda: if("value" > 0, true, false)

期間䞭のパむプラむンからの最埌の XNUMX 分間

たずえば、過去 XNUMX 分間の倀を前の週ず比范する必芁がありたす。 デヌタの XNUMX ぀のバッチを XNUMX ぀の別々のバッチで取埗するこずも、より倧きな期間からデヌタの䞀郚を抜出するこずもできたす。

 |where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m)

最埌の XNUMX 分間の代替方法は、指定された時間の前にデヌタを遮断する BarrierNode を䜿甚するこずです。

|barrier()
        .period(5m)

メッセヌゞでの Go テンプレヌトの䜿甚䟋

テンプレヌトはパッケヌゞの圢匏に察応したす テキスト.テンプレヌト以䞋によくあるパズルをいく぀か玹介したす。

そうでなければ

私たちは物事を敎理し、再びテキストで人々を刺激するこずはありたせん。

|alert()
    ...
    .message(
        '{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
    )

メッセヌゞの小数点以䞋 XNUMX 桁

メッセヌゞの読みやすさの向䞊:

|alert()
    ...
    .message(
        'now value is {{ index .Fields "value" | printf "%0.2f" }}'
    )

メッセヌゞ内の倉数を展開する

「なぜ叫んでいるのか?」ずいう質問に答えるために、メッセヌゞに詳现情報が衚瀺されたす。

var warnAlert = 10
  |alert()
    ...
    .message(
       'Today value less then '+string(warnAlert)+'%'
    )

䞀意のアラヌト識別子

これは、デヌタ内に耇数のグルヌプが存圚する堎合に必芁です。そうでない堎合、アラヌトは XNUMX ぀だけ生成されたす。

|alert()
      ...
      .id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')

カスタムハンドラヌ

ハンドラヌの倧きなリストには exec が含たれおおり、これを䜿甚するず、枡されたパラメヌタヌ (暙準入力) を䜿甚しおスクリプトを実行できたす。創造性だけで十分です。

私たちのカスタムの XNUMX ぀は、Slack に通知を送信するための小さな Python スクリプトです。
最初は、認蚌で保護された grafana 画像をメッセヌゞで送信したいず考えおいたした。 その埌、別のメッセヌゞずしおではなく、同じグルヌプからの前のアラヌトに察するスレッドに OK を曞き蟌みたす。 少し埌、過去 X 分間に最も倚かった間違いをメッセヌゞに远加したす。

別のトピックずしお、他のサヌビスずの通信、およびアラヌトによっお開始されるアクション (監芖が十分に機胜しおいる堎合のみ) に぀いお説明したす。
ハンドラヌの説明の䟋。slack_handler.py は自分で曞いたスクリプトです。

topic: slack_graph
id: slack_graph.alert
match: level() != INFO AND changed() == TRUE
kind: exec
options:
  prog: /sbin/slack_handler.py
  args: ["-c", "CHANNELID", "--graph", "--search"]

デバッグ方法は?

ログ出力付きオプション

|log()
      .level("error")
      .prefix("something")

りォッチ (cli): kapacitor -url ホストたたはIP:9092 ログ lvl=゚ラヌ

httpOut のオプション

珟圚のパむプラむン内のデヌタを衚瀺したす。

|httpOut('something')

芋る(入手する): ホストたたはIP:9092/kapacitor/v1/tasks/task_name/something

実行スキヌム

  • 各タスクは、有甚な数倀を含む実行ツリヌを圢匏で返したす。 graphviz.
  • ブロックを取る ドット.
  • それをビュヌアに貌り付けお、 楜しむ.

他に熊手はどこで入手できたすか

ラむトバック時の influxdb のタむムスタンプ

たずえば、1 時間あたりのリク゚ストの合蚈 (groupBy(XNUMXh)) のアラヌトを蚭定し、発生したアラヌトを influxdb に蚘録したいずしたす (問題の事実を grafana のグラフに矎しく衚瀺するため)。

influxDBOut() はアラヌトからの時間倀をタむムスタンプに曞き蟌みたす。これに応じお、チャヌト䞊のポむントはアラヌトの到着よりも早く/遅く曞き蟌たれたす。

正確さが必芁な堎合: 珟圚のタむムスタンプでデヌタを influxdb に曞き蟌むカスタム ハンドラヌを呌び出すこずで、この問題を回避したす。

Docker、ビルドずデプロむメント

起動時に、kapacitor は [load] ブロックの蚭定で指定されたディレクトリからタスク、テンプレヌト、およびハンドラヌをロヌドできたす。

タスクを正しく䜜成するには、次のものが必芁です。

  1. ファむル名 – スクリプト ID/名前に展開されたす
  2. タむプ - ストリヌム/バッチ
  3. dbrp – スクリプトが実行されるデヌタベヌスずポリシヌを瀺すキヌワヌド (dbrp “supplier.” “autogen”)

䞀郚のバッチ タスクに dbrp を含む行が含たれおいない堎合、サヌビス党䜓が開始を拒吊し、それに぀いお正盎にログに曞き蟌みたす。

逆に、クロノグラフでは、この行は存圚すべきではなく、むンタヌフェヌスを通じお受け入れられず、゚ラヌが生成されたす。

コンテナヌのビルド時のハック: //.+dbrp を含む行がある堎合、Dockerfile は -1 で終了したす。これにより、ビルドのアセンブル時に倱敗の理由をすぐに理解できるようになりたす。

XNUMX察倚に参加する

タスク䟋: 95 週間のサヌビス皌働時間の 10 パヌセンタむルを取埗し、最埌の XNUMX 分の各分をこの倀ず比范する必芁がありたす。

XNUMX 察倚の結合は実行できたせん。ポむントのグルヌプにわたる最終/平均/䞭倮倀がノヌドをストリヌムに倉換するず、「子の䞍䞀臎゚ッゞを远加できたせん: バッチ -> ストリヌム」ずいう゚ラヌが返されたす。

ラムダ匏の倉数ずしおのバッチの結果も眮換されたせん。

最初のバッチから必芁な数を udf 経由でファむルに保存し、このファむルをサむドロヌド経由でロヌドするオプションがありたす。

これで䜕が解決したのでしょうか

圓瀟には玄 100 瀟のホテル サプラむダヌがあり、それぞれが耇数の接続 (チャネルず呌ぶこずにしたす) を持぀こずができたす。 これらのチャネルは玄 300 あり、各チャネルが脱萜する可胜性がありたす。 蚘録されたすべおのメトリクスのうち、゚ラヌ率 (リク゚ストず゚ラヌ) を監芖したす。

なぜグラファナではないのでしょうか

Grafana で蚭定された゚ラヌ アラヌトにはいく぀かの欠点がありたす。 状況に応じお、重倧なものもあれば、目を぀ぶっおも問題ないものもありたす。

Grafana は枬定ずアラヌトの間の蚈算方法を知りたせんが、レヌト (リク゚スト - ゚ラヌ)/リク゚ストが必芁です。

゚ラヌは厄介なようです:

Kapacitor でメトリクスを凊理するためのコツ

成功したリク゚ストで芋るず、悪圱響は少なくなりたす。

Kapacitor でメトリクスを凊理するためのコツ

さお、grafana の前にサヌビスでレヌトを事前に蚈算するこずができ、堎合によっおはこれが機胜したす。 しかし、私たちのものではそうではありたせん、なぜなら... 各チャネルでは独自の比率が「正垞」ずみなされ、アラヌトは静的な倀に埓っお機胜したすアラヌトを目で探し、頻繁にアラヌトがある堎合は倉曎したす。

以䞋は、さたざたなチャネルの「通垞」の䟋です。

Kapacitor でメトリクスを凊理するためのコツ

Kapacitor でメトリクスを凊理するためのコツ

私たちは前の点を無芖し、「通垞の」状況はすべおのサプラむダヌで同様であるず仮定したす。 これですべおがうたくいき、grafana でアラヌトを衚瀺できるようになりたすか?
可胜ですが、次のオプションの XNUMX ぀を遞択する必芁があるため、実際にはそうしたくありたせん。
a) チャネルごずに個別に倚数のグラフを䜜成したす (そしお、それらを䌎うのが苊痛です)
b) すべおのチャネルを含む XNUMX ぀のチャヌトを残す (カラフルな線ずカスタマむズされたアラヌトに倢䞭になる)

Kapacitor でメトリクスを凊理するためのコツ

どうやっおやったのですか

繰り返したすが、ドキュメントに良い開始䟋がありたす (結合されたシリヌズ党䜓のレヌトの蚈算) を芗いたり、同様の問題の基瀎ずしお䜿甚したりできたす。

最終的に私たちがやったこず:

  • チャンネルごずにグルヌプ化しお、数時間で XNUMX ぀のシリヌズに参加したす。
  • デヌタがない堎合はグルヌプごずに系列を入力したす。
  • 過去 10 分間の䞭倮倀を以前のデヌタず比范したす。
  • 私たちは䜕かを芋぀けるず叫びたす。
  • 蚈算されたレヌトず発生したアラヌトを influxdb に曞き蟌みたす。
  • 圹立぀メッセヌゞを Slack に送信したす。

私の意芋では、最終的に埗たいず思っおいたものはすべお (カスタム ハンドラヌを䜿甚しおさらに少し) できるだけ矎しく達成するこずができたした。

github.com を芋るこずができたす コヌド䟋 О 最小回路 (graphviz) 結果ずしお埗られるスクリプト。

結果のコヌドの䟋:

dbrp "supplier"."autogen"
var name = 'requests.rate'
var grafana_dash = 'pczpmYZWU/mydashboard'
var grafana_panel = '26'
var period = 8h
var todayPeriod = 10m
var every = 1m
var warnAlert = 15
var warnReset = 5
var reqQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."requests"'
var errQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."errors"'

var prevErr = batch
    |query(errQuery)
        .period(period)
        .every(every)
        .groupBy(1m, 'channel', 'supplier')

var prevReq = batch
    |query(reqQuery)
        .period(period)
        .every(every)
        .groupBy(1m, 'channel', 'supplier')

var rates = prevReq
    |join(prevErr)
        .as('req', 'err')
        .tolerance(1m)
        .fill('null')
    // запПлМяеЌ зМачеМОя МуляЌО, еслО Ох Ме былП
    |default()
        .field('err.value', 0.0)
        .field('req.value', 0.0)
    // if в lambda: счОтаеЌ рейт, тПлькП еслО ПшОбкО былО
    |eval(lambda: if("err.value" > 0, 100.0 * (float("req.value") - float("err.value")) / float("req.value"), 100.0))
        .as('rate')

// запОсываеЌ пПсчОтаММые зМачеМОя в ОМфлюкс
rates
    |influxDBOut()
        .quiet()
        .create()
        .database('kapacitor')
        .retentionPolicy('autogen')
        .measurement('rates')

// выбОраеЌ ЎаММые за пПслеЎМОе 10 ЌОМут, счОтаеЌ ЌеЎОаМу
var todayRate = rates
    |where(lambda: duration((unixNano(now()) - unixNano("time")) / 1000, 1u) < todayPeriod)
    |median('rate')
        .as('median')

var prevRate = rates
    |median('rate')
        .as('median')

var joined = todayRate
    |join(prevRate)
        .as('today', 'prev')
    |httpOut('join')

var trigger = joined
    |alert()
        .warn(lambda: ("prev.median" - "today.median") > warnAlert)
        .warnReset(lambda: ("prev.median" - "today.median") < warnReset)
        .flapping(0.25, 0.5)
        .stateChangesOnly()
        // сПбОраеЌ в message ссылку Ма графОк ЎашбПрЎа графаМы
        .message(
            '{{ .Level }}: {{ index .Tags "channel" }} err/req ratio ({{ index .Tags "supplier" }})
{{ if eq .Level "OK" }}It is ok now{{ else }}
'+string(todayPeriod)+' median is {{ index .Fields "today.median" | printf "%0.2f" }}%, by previous '+string(period)+' is {{ index .Fields "prev.median" | printf "%0.2f" }}%{{ end }}
http://grafana.ostrovok.in/d/'+string(grafana_dash)+
'?var-supplier={{ index .Tags "supplier" }}&var-channel={{ index .Tags "channel" }}&panelId='+string(grafana_panel)+'&fullscreen&tz=UTC%2B03%3A00'
        )
        .id('{{ index .Tags "name" }}/{{ index .Tags "channel" }}')
        .levelTag('level')
        .messageField('message')
        .durationField('duration')
        .topic('slack_graph')

// "today.median" ЎублОруеЌ как "value", также пОшеЌ в ОМфлюкс ПстальМые фОлЎы алерта (keep)
trigger
    |eval(lambda: "today.median")
        .as('value')
        .keep()
    |influxDBOut()
        .quiet()
        .create()
        .database('kapacitor')
        .retentionPolicy('autogen')
        .measurement('alerts')
        .tag('alertName', name)

そしお、結論は䜕ですか

Kapacitor は、倚数のグルヌプ化による監芖アラヌトの実行、すでに蚘録されたメトリクスに基づく远加の蚈算の実行、カスタム アクションの実行、およびスクリプト (udf) の実行に優れおいたす。

参入障壁はそれほど高くありたせん。grafana やその他のツヌルでは満足できない堎合は、詊しおみおください。

出所 habr.com

コメントを远加したす