본문 바로가기

Log,Monitorings

[Prometheus] AlertManager로 Slack 알람 전송하는 법 알아보자

반응형

1. 개요

해외문서도 그렇고 국내문서도 그렇고 마땅히 잘 정리된 글을 찾기 힘들었다.

그나마 AliceK 님 글이 잘 정리되어 있어서 참고가 되었다.

그래서 전체적으로 이해하는데 약간 애를 먹었지만 삽질을 계속 하다보니 얼추 전체적인 흐름이 정리되어서 간략하게 정리해본다.
 
 
사실 Grafana Managed Alert를 사용하면 더욱 쉽고 간편하게 알람을 보낼 수 있지만 너무 많은 규칙들이 Grafana에서 처리될 경우 부하가 발생할 수 있기 때문에 이를 분산하기 위해 가급적이면 AlertManager로 알람을 보내려고 한다.
 
 
필자는 Grafana Mimir를 통해 시계열 메트릭을 저장하기에 Mimir Ruler를 통해 규칙을 평가하고, AlertManager에게 이를 전달하여 Slack으로 알람을 보내도록 할 것이다.
 
 

2. 아키텍쳐

전체적인 흐름은 다음과 같다.
 
1. PromQL식을 통해 주기적으로 규칙을 평가한다.
- PromQL을 통해 도출된 Vector 값을 기반으로 기 정의한 조건에 도달할 경우 Pending에 걸린다.
(ex. 평균 에러률이 1%가 넘을 경우, 남은 메모리 공간이 10% 미만일 경우, ...)
 
 
2. for(특정 주기) 동안 Pending이 지속될 경우 Firing / Resolved가 처리된다.
 
 
3. AlertManager는 Json 데이터를 Ruler에게 전달받는다.
- Ruler는 필요한 정보들을 Annotation 혹은 Label 등으로 전달해준다.
 
 
4. Ruler가 전달해준 정보를 가지고, 기 정의한 Slack Template + Slack Config를 조합하여 AlertManager가 Slack으로 메시지를 보내게 된다.
- Ruler가 전달해준 Label을 기반으로 AlertManager는 어떤 Slack Channel로 메시지를 보낼 것인지 Routing 규칙을 정할 수 있다.
 
 
 

3. 방법

이해하기 쉽게 Web UI로 보여주겠다.
 
설정에 대한 자세한 내용은 다른 블로그 혹은 문서를 참고하자.
 
 
1. Prometheus Query로 조건식을 작성해준다.

- istio_requests_total은 카운터 값이기 때문에 이를 5분 당 초당 증가율로 계산하기 위해 rate 함수를 사용하며 range vector는 5분으로 하였다.
 
- 이를 destination_app, cluster, namespace로 평균값을 계산한다.
 
- 예시로는 벡터값이 > 0 이상인 대상은 Alert로 처리되도록 하였다.
 
이렇게 하면 $labels.레이블명 을 변수로 사용하여 값을 얻어낼 수 있다. 나중에 요긴하게 쓰인다.
 
 
 
2. 평가 행동 , Annotation을 작성해준다.
 
- 예시이기에 For은 10s동안 Pending이 지속될 경우 Firing이 될 수 있도록 평가 규칙을 설정하였다.
 
- resolvedMessage와 triggerMessage란 Annotation 2개를 만들었다.
나눈 이유는 firing일 경우에는 triggerMessage를 Slack에 전달하기 위함이고 resolved일 경우에는 resolvedMessage를 전달하기 위함이다.
 
위에서 말한대로 PromQL 메트릭 레이블 값은 $labels.xxxx 로 접근할 수 있다.
 

 
 
3. 어떤 내용들이 AlertManager로 전달될까?

 
Template Data를 이해하면 Slack Template을 작성하는데 큰 도움이 된다.
 
주로 쓰이는 데이터들은 Status , Alerts , Alerts.Firing / Resolved , CommonAnnotations , CommonLabels 등이 있다.
 
여기서 잘 보면 Alerts.Firing 과 Alerts.Resolved는 Type이 []Alert, 즉 리스트다.
 
Iterate하게 반복문을 돌아야 한다는 뜻이다.

 
[1] CommonLabels , CommonAnnotations : 모든 얼럿에 동일한 값으로 들어가는 Annotation 혹은 Labels이 Aggregate되어 Common 데이터로 들어간다.
 
[2] labels, annotations : 각 얼럿마다 다른 값들이 있다면 Alerts[n].annotations 혹은 Alerts[n].labels 로 전달된다.
 
 
다시 아까 작성했던 Annotation을 확인해보자.
 
resolvedMessage와 triggerMessage는 각 Alert마다 다른 값이 들어갈 것이므로 CommonAnnotation으로 들어가지 않지만 beCommon이란 어노테이션은 공통 텍스트이기 때문에 CommonAnnotation으로부터 이 값을 사용할 수 있을 것이다.

 
그리고 Notifications에 channel = xxx-channel 이란 Label을 넣어주었다.
이는 나중에 Notification policies에 의해 어떤 Label일 경우 어떤 Slack Channel로 알람을 보낼 것인지 정의할 수 있다.

 
 
4. Slack Config를 만들어보자. (Contact Point)
 
Webhook 같은건 설명 안하고 중요한 부분만 설명한다.
 
Fallback : 보통 대부분 슬랙 메시지를 휴대폰 미리보기로 먼저 볼텐데 여기에 정의해줘야 어떤 알람이 온 것인지 한 눈에 확인할 수 있다. 다만 개별 얼럿 내용은 클릭해서 직접 확인해야 한다.
 
Titie : 메시지 맨 위에서 보이는 내용이다. 보통 Common 값을 기반으로 작성하는게 좋다.
 
Message Body : 개별 Alert에 대한 내용을 보여주기 위한 부분이다.
 

 
참고로 Slack Config는 아래와 같다.

# Whether to notify about resolved alerts.
[ send_resolved: <boolean> | default = false ]

# The Slack webhook URL. Either api_url or api_url_file should be set.
# Defaults to global settings if none are set here.
[ api_url: <secret> | default = global.slack_api_url ]
[ api_url_file: <filepath> | default = global.slack_api_url_file ]

# The channel or user to send notifications to.
channel: <tmpl_string>

# API request data as defined by the Slack webhook API.
[ icon_emoji: <tmpl_string> ]
[ icon_url: <tmpl_string> ]
[ link_names: <boolean> | default = false ]
[ username: <tmpl_string> | default = '{{ template "slack.default.username" . }}' ]
# The following parameters define the attachment.
actions:
  [ <action_config> ... ]
[ callback_id: <tmpl_string> | default = '{{ template "slack.default.callbackid" . }}' ]
[ color: <tmpl_string> | default = '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}' ]
[ fallback: <tmpl_string> | default = '{{ template "slack.default.fallback" . }}' ]
fields:
  [ <field_config> ... ]
[ footer: <tmpl_string> | default = '{{ template "slack.default.footer" . }}' ]
[ mrkdwn_in: '[' <string>, ... ']' | default = ["fallback", "pretext", "text"] ]
[ pretext: <tmpl_string> | default = '{{ template "slack.default.pretext" . }}' ]
[ short_fields: <boolean> | default = false ]
[ text: <tmpl_string> | default = '{{ template "slack.default.text" . }}' ]
[ title: <tmpl_string> | default = '{{ template "slack.default.title" . }}' ]
[ title_link: <tmpl_string> | default = '{{ template "slack.default.titlelink" . }}' ]
[ image_url: <tmpl_string> ]
[ thumb_url: <tmpl_string> ]

# The HTTP client's configuration.
[ http_config: <http_config> | default = global.http_config ]



<action_config>

text: <tmpl_string>
type: <tmpl_string>
# Either url or name and value are mandatory.
[ url: <tmpl_string> ]
[ name: <tmpl_string> ]
[ value: <tmpl_string> ]

[ confirm: <action_confirm_field_config> ]
[ style: <tmpl_string> | default = '' ]


<action_confirm_field_config>
text: <tmpl_string>
[ dismiss_text: <tmpl_string> | default '' ]
[ ok_text: <tmpl_string> | default '' ]
[ title: <tmpl_string> | default '' ]


<field_config>
title: <tmpl_string>
value: <tmpl_string>
[ short: <boolean> | default = slack_config.short_fields ]

 
 
 
 
5. Slack Template을 작성한다.
 
slack.custom.title , slack.custom.message 2개를 만들었다.
 
- Status가 firing일 경우와 resolved일 경우를 각각 다른 title로 만들어주었다.
- Alerts.Firing , Alerts.Resolved를 나누어 반복문을 돌면서 기 Ruler에서 정의한 Annotations.(trigger|resolved)Message를 보여주기 위해 아래와 같이 설정하였다.
 

{{ define "slack.custom.title" }}
	{{- if (eq .Status "firing") -}}
		[Triggered : {{ .Alerts.Firing | len }}회] {{ .CommonLabels.alertname }}
	{{- else if (eq .Status "resolved") -}}
		[Resolved : {{ .Alerts.Resolved | len }}회] {{ .CommonLabels.alertname }}
	{{- else -}}
		[Unknown Status] {{ .CommonLabels.alertname }}
	{{- end -}}
{{ end }}

{{ define "slack.custom.message" }}
	{{- if gt (len .Alerts.Firing) 0 -}}
		{{- range .Alerts.Firing -}}
			{{- printf "%s\n-------------------------------------\n" .Annotations.triggerMessage -}}
		{{- end -}}
	{{- else if gt (len .Alerts.Resolved) 0 -}}
		{{- range .Alerts.Resolved -}}
			{{- printf "%s\n-------------------------------------\n" .Annotations.resolvedMessage -}}
    {{- end -}}
	{{- end -}}
{{ end }}

 
 
물론 위 내용은 데모이기 때문에 간단하게 템플릿을 정의했지만 뭔가 버튼 UI를 통해 Silence(침묵) 하던가 Runbook URL을 보여주게 할 수도 있다.
 
 
아래는 슬랙으로 알람이 오는 예시이다.
 
맨 위에는 Titie : [Resolved : ${Alert 객체 갯수}] $CommonLabels.alertname
 
아래부터는 Alert를 range로 반복문 돌아서 각각 얼럿 내용들을 보여준다.
Cluster : `$lables.cluster`
..

 
지금까지 간단하게 AlertManager를 통해 슬랙으로 알람을 보내는 방법에 대해 알아보았다.
 
중요한 내용들을 한번 정리해보자면,
 
1. PromQL 기반으로 원하는 벡터값을 뽑아낼 수 있도록 한다.
- Counter , Histogram , Guage, Summary 4가지 타입에 따라 각각 어떻게 쿼리를 짜야 단일 / 다중 벡터값으로 나오는지에 대해서 알고 있어야 한다.
 
2. Common(Annotation|Labels)랑 개별 Alert에 대한 Annotation|Labels에 대해 이해하고 있어야 한다.
 
3. Mimir / Prometheus Ruler가 AlertManager에 보내는 Alert는 n개일 수 있기에 이에 대해서 어떤 부분에서 Common을 사용할 지 이해하고 있어야 한다.
 
4. Slack Template을 원하는대로 만들기 위해 간단한 Go Template 작성법은 이해하고 있어야 한다.
 
 
참고 
 
https://blog.naver.com/alice_k106/221910045964

반응형