PagerDuty互換APIを作ってAlertmanagerから通知させる〜其の壱〜

Alertmanagerからの通知先に自作システムを利用したいが、webhookだとなんかダサいかなと思っているので、タイトルの通りに最近巷でアツいPagerDutyの互換APIを自作システムに持たせてアラートを送りたくなったのでやってみました。

## 其の壱のゴール

Alertmanagerの[pagerduty_config](https://prometheus.io/docs/alerting/configuration/#pagerduty_config)で互換APIを指定し、アラート通知が届くこと

## Alertmanagerとは

メールやSlack、webhookなどと言った先に通知を送るシステムです。Prometheusと併せてよく使われますが、別のプロダクトで単体でも使えます。

また、以下のような特徴を持ちます。

- グルーピング：類似のアラートを、単一の通知に分類
- インヒビション：他のアラートが発生している時に、特定のアラート通知を抑制する
- サイレンス：一定時間、アラートをミュートにする

## PagerDutyとは

> システム管理者およびサポートチーム向けのアラーム集約およびディスパッチサービスです。監視ツールからアラートを収集し、すべての監視アラームを全体的に表示し、問題がある場合は担当者に警告します。

参照：[PagerDuty - Digital Stacks](https://pagerduty.digitalstacks.net/faq/officia-deserunt-mollit-anim-id-est-laborum-2/)

## 本編

### TL;DR

できた！ソースはこちら（[Transnano / pagerduty-api · GitLab](https://gitlab.com/transnano/pagerduty-api)）

### 互換API

最近はGoにハマっているのでGo/Ginで書いてみました。（サブタイトルを其の壱と付けたようにまだまだここは適当です。とりあえずエンドポイントとエラーコードを揃えたくらいです。）
内容としてはAlertmanagerからの通知を受け取ってそのまま出力します。

``` go
...
	r.POST("/v2/enqueue", func(c *gin.Context) {
		buf := make([]byte, 2048)
		n, _ := c.Request.Body.Read(buf)
		b := string(buf[0:n])
		fmt.Println(b)
		num := len(b)
		switch {
		case num == 0:
			c.JSON(http.StatusBadRequest, gin.H{
				"status":  "invalid event",
				"message": "Event object is invalid",
				"errors":  "Length of 'routing_key' is incorrect (should be 32 characters)",
			})
		case num > 500:
			c.AbortWithStatus(http.StatusTooManyRequests)
		default:
			c.JSON(http.StatusAccepted, gin.H{
				"status":    "success",
				"message":   "Event processed",
				"dedup_key": "samplekeyhere",
			})
		}
	})
	r.Run()
...
```

### Alertmanagerの設定

通知に関する設定は以下のようなymlファイルになります。

``` yml
---
receivers:
- name: 'team-X-pager'
  pagerduty_configs:
  - routing_key: dummykey_length_is_32_characters
    url: http://pagerduty-api:8080/v2/enqueue
---
```

ポイントはPagerDutyのV2のAPIを呼び出すため、`routing_key`を使っていることと自作したシステムのエンドポイントを`url`で指定していることです。

### サンプル

こちらのソース[sample · master · Transnano / pagerduty-api · GitLab](https://gitlab.com/transnano/pagerduty-api/tree/master/sample)にも用意してまが、以下の手順でAlertmangerにアラート発生をリクエストすると自作システムが出力したログを確認できます。

``` shell
# Alertmangerにアラート発生をリクエストする
$ curl -d '[{"labels": {"Alertname": "PagerDuty Test"}}]' http://localhost:9093/api/v1/alerts

# 自作システムのログ

[GIN-debug] POST   /v2/enqueue               --> main.main.func3 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default

...（障害発生）

{"routing_key":"dummykey_length_is_32_characters","dedup_key":"<dedup_key>","event_action":"trigger","payload":{"summary":"[FIRING:1] PagerDuty Test ","source":"AlertManager","severity":"error","custom_details":{"firing":"Labels:\n - alertname = PagerDuty Test\nAnnotations:\nSource: \n","num_firing":"1","num_resolved":"0","resolved":""}},"client":"AlertManager","client_url":"http://alertmanager:9093/#/alerts?receiver=team-X-pager"}

[GIN] 2019/03/22 - 11:00:49 | 202 |      1.3033ms |      172.25.0.4 | POST     /v2/enqueue

...（障害復旧）

{"routing_key":"dummykey_length_is_32_characters","dedup_key":"<dedup_key>","event_action":"resolve","payload":{"summary":"[RESOLVED] PagerDuty Test ","source":"AlertManager","severity":"error","custom_details":{"firing":"","num_firing":"0","num_resolved":"1","resolved":"Labels:\n - alertname = PagerDuty Test\nAnnotations:\nSource: \n"}},"client":"AlertManager","client_url":"http://alertmanager:9093/#/alerts?receiver=team-X-pager"}

[GIN] 2019/03/22 - 11:15:31 | 202 |       207.8µs |      172.25.0.4 | POST     /v2/enqueue

```