TerraformでPagerDuty設定応用 - Alertmanagerのconfigを自動生成する
はじめに
PagerDuty Advent Calendar 2024の21日目の記事です。
この記事は、PagerDuty on Tour TOKYO 2024のLT枠で登壇したときに紹介した内容について、より詳しく説明するものです。
前提: TerraformでPagerDutyの設定を実施する
TerraformでPagerDuty設定入門 その1などで述べられているように、TerraformでPagerDutyの各種リソースを管理することができます。
この記事の想定読者は、既にServiceやService Integrationの設定をTerraformで実施している方々です。
具体的には、
pagerduty_service
pagerduty_service_integration
の2種類のリソースをTerraformで管理していて、pagerduty_service_integration
のtypeをeventapi_v2_inbound_integration
に設定している、もしくはそのように設定することができることを想定しています。
Alertmanager x PagerDuty
Alertmanagerは、Prometheusなどからのアラートを受け取り、それらを任意の宛先に通知するためのアプリケーションです。宛先にはSlackやEmail, PagerDutyなどを指定することができます。
Alertmanagerでは宛先のことをreceiver
、どのアラートをどのreceiver
に通知するかのルールをroute
という設定で定義します。
Receiverの設定
一旦receiver
の話に絞って、PagerDutyへの通知設定をどのように記述するのかの例を見てみましょう。
receivers:
- name: 'pd_service_hoge'
pagerduty_configs:
- routing_key: '<service intergrationで発行されたkey>'
description: '<インシデントのタイトル>'
details: <map[string]string>
receivers
の下に任意の名前のreceiver
を定義し、pagerduty_configs
の下にPagerDutyへの通知設定を記述します。ここで重要なのはrouting_key
で、これは基本的にはservice integrationの設定時にしか確認できない情報です。
そのため、宛先のPagerDuty Serviceを追加する度にService Integrationを作成->routing_key
を確認->alertmanager.yml
を編集->Alertmanagerを再起動という手順を踏む必要があります。
Routeの設定
無事任意のPagerDuty Serviceへのreceiver
を設定したら、どのアラートをそのreceiver
に送るかをroute
として設定する必要があります。
以下は、service
というラベルがalertに定義されていて、それがalertのgroupingに使われているときに、service
ラベルの値がhoge-
で始まるアラートを先程の例で定義したreceiver
に送る設定の例です。
routes:
- receiver: 'pd_service_hoge'
match_re:
service: 'hoge-.+'
これらを手動で管理するのは大変です。特に、多くのPagerDuty Serviceに対して様々な種類のアラートをルーティングしなくてはいけない場合は、手動管理では記述ミスのリスクが大きくなります。routingされないアラートには気付きづらいため、そもそも記述ミスが発覚しないまま重大インシデントを迎えてしまうことも考えられます。
Terraform stateとAnsibleを使ったAlertmanager config fileの自動生成
そこで、Terraform stateとAnsibleを使ってalertmanager config fileを自動生成する方法を紹介します。
基本的な生成手順は以下です。
- Terraform stateのdump
- Ansibleの
from_json
filterでのデータ取り込み - Ansibleの
template
moduleでのconfig fileの生成
また、適切にreceiver
とroute
をマッピングするために、TerraformでのService, Service Integration resourceの定義にも仕込みをします。まずはその仕込みから説明し、その後に1~3について例を示します。
TerraformのService, Service Integrationのリソース定義
「仕込み」の目的は、Alertmanagerのreceiver
とroute
のマッピングを適切に行うことです。
receiver
に必要な情報はpagerduty_service_integration
のintegration_key
から取れるのですが、pagerduty_service_integration
の定義内でroute
に必要な情報を記述できるArgumentは存在しないので、頭を悩ませることになります。
そこで、pagerduty_service_integration
の前提リソースであるpagerduty_service
に定義されているdescription
argumentを利用します。
具体的な仕込みの内容は以下です。
pagerduty_service
のdescription
に「route
に必要な情報」をjsonで記述する
- 必要な情報は
route
のmatch_re
に指定するkey-valueと、receiver
の名前です。-
receiver
にはpagerduty_service_integration
のname
を指定します。
-
- 後でパースできるようにjsonで書き込みます。
-
description
が改行をサポートしてる可能性があるので、YAMLの方が可読性が高くていいかもしれないです(未検証)
-
以下はhoge
という名前のServiceを作成する例です。
resource "pagerduty_service" "hoge" {
name = "Hoge"
description = jsonencode({
"receiver": "pd_hoge",
"match_re": {
"service": "hoge-.*",
"severity": "critical|error"
}
})
escalation_policy = pagerduty_escalation_policy.hoge.id
alert_creation = "create_alerts_and_incidents"
incident_urgency_rule {
type = "constant"
urgency = "severity_based"
}
}
resource "pagerduty_service_integration" "hoge" {
name = "pd_hoge"
type = "events_api_v2_inbound_integration"
service = pagerduty_service.hoge.id
}
1. Terraform stateのdump
Terraformのstateは、terraform state pull
コマンドでjson形式で取得することができます。
jsonの構造を説明すると長くなってしまうので、いくつかデータ参照のための例を示します。
pagerduty_service
のdescription
を抜き出すjqコマンド
先ほどの仕込みをしたterraform state pull | jq -r '[.resources[] | select(.type == "pagerduty_service") | .instances[] | .attributes.description | fromjson]'
[
{
"match_re": {
"service": "hoge-.*"
},
"receiver": "pd_hoge"
},
{
"match_re": {
"service": "fuga-.*"
},
"receiver": "pd_fuga"
}
]
pagerduty_service_integration
のname
とintegration_key
を抜き出すjqコマンド
先ほどの仕込みをしたterraform state pull | jq -r '[.resources[] | select(.type == "pagerduty_service_integration") | .instances[] | {"name": .attributes.name, "integration_key": .attributes.integration_key}]'
[
{
"name": "pd_hoge",
"integration_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
{
"name": "pd_fuga",
"integration_key": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
}
]
from_json
filterを使ってのデータの取り込み
2. Ansibleの先ほど示したjqコマンドをスクリプト化するなどして、Ansibleのlookup pluginのpipe
を通して実行 → from_json
filterでデータを取り込むことができます。
例えば、alertmanager roleのdefaults/main.yml
などに以下のように定義しておくと、role内で取り込んだデータを使うことができます。
alertmanager_routes: "{{ lookup('pipe', 'scripts/get_alertmanager_routes.sh') | from_json}}"
alertmanager_receivers: "{{ lookup('pipe', 'scripts/get_alertmanager_receivers.sh') | from_json}}"
※ 実際にはスクリプトを参照できるように絶対パスを変数などで補正してあげる必要があります。
template
moduleでのconfig fileの生成
3. Ansibleの後はtemplate
moduleを使ってconfigを生成するだけです。
必要な部分だけ例を示します。
route:
group_by: ['alertname', 'env']
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receiver: default
routes:
{% for route in alertmanager_routes %}
- match_re:
{% for key, value in route.match_re.items() %}
{{ key }}: {{ value }}
{% endfor %}
receiver: {{ route.receiver }}
{% endfor %}
receivers:
{% for receiver in alertmanager_receivers %}
- name: {{ receiver.name }}
pagerduty_configs:
- routing_key: {{ receiver.integration_key }}
description: '[{{ .GroupLabels.env }}] {{ .GroupLabels.alertname }}'
details: {...}
{% endfor %}
Key points
- terraform stateはjsonで取得できるので、Ansibleの
from_json
filterで取り込んで使うことができる- jqで前処理しておくと楽
-
pagerduty_service
のdescription
にjsonを埋め込むことで、pagerduty_service_integration
との関連を表現することができる- 1000文字くらいは入るので、複雑な条件でも問題無し
- そもそもこの2つのリソースは同じ
.tf
ファイルで管理した方が見通しが良いこともあり、格納場所として都合がよい
さいごに
通知周りはミスると大変なので、PagerDutyへの通知ルートをAlertmanagerなどで自前実装するのであれば、設定は極力自動化しましょう!
Discussion