PrometheusのService Discoveryで、AWSのスケールアウト用EC2インスタンスを監視対象に動的追加/削除をする
この記事は SKIYAKI Tech Blog Advent Calendar 2025 の5日目の記事になります。
表題の通りで、AWSのEC2インスタンスに対してPrometheusのService Discoveryの機能でやってみたよという内容の備忘録です。
【やりたいこと】 Service Discoveryで監視対象の動的追加/削除を実現する
PrometheusではService Discoveryの設定ができます。
文字通りサービスを検出する機能ですね。
これを使って動的に監視対象を検知することができます。
今回はAWSのEC2インスタンスに対し、このService Discoveryを使って、動的に監視対象に追加/削除をしてみます。
例えば webapp-server という名前のEC2インスタンスがあるとします。
このインスタンスは、末尾に 01 , 02... のように連番でナンバリングしていて、スケールアウトしたりスケールインしたりできるものと想定してみます。
webapp-server01
webapp-server02
webapp-server03
.
.
.
webapp-server99
このインスタンス内で node_exporter が動いていれば、以下のような設定をPrometheusに実施すれば、EC2インスタンスの状態に応じて動的に監視対象に追加/削除をしてくれます。
scrape_configs:
- job_name: 'node_exporter'
ec2_sd_configs:
- port: 9100
relabel_configs:
- source_labels: [__meta_ec2_instance_state]
regex: "stopped"
action: drop
- source_labels: [__meta_ec2_tag_Name]
target_label: name
これだけで実現できます。なんて簡単なんでしょう…!
ポイントは以下の箇所になります。
- source_labels: [__meta_ec2_instance_state]
regex: "stopped"
action: drop
EC2インスタンスの状態が stopped となったら action: drop が効いて監視対象から除外される…という設定です。
あとはSlackにアラートを投げるための設定をPrometheusとAlertManagerにしてあげるだけですね。
Prometheusの rule_files で指定する設定ファイルはこんな感じで、 node_exporter が3分停止していたらアラートが発報するようにします。
groups:
- name: node_exporter
rules:
- alert: InstanceDown
expr: up{job="node_exporter"} == 0
for: 180s
labels:
severity: critical
annotations:
description: '{{ $labels.name }} of job {{ $labels.job }} has been down for more than 3 minutes.'
summary: 'Instance {{ $labels.name }} down'
AlertManagerは以下のような形にしてみます。
単純にSlackに通知をするだけです。
route:
routes:
- receiver: 'slack_prometheus'
continue: false
group_by:
- alertname
matchers:
- severity="critical"
- job="node_exporter"
receivers:
- name: 'slack_prometheus'
slack_configs:
- api_url: 'https://hooks.slack.com/services/XXXXXXXX'
channel: '#server_alert'
pretext: <!here> :x: *Critical*
color: '{{ if eq .Status "firing" }}danger{{ else }}warning{{ end }}'
title: "{{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}"
text: "{{ range .Alerts }}{{ .Annotations.description }}\n{{ end }}"
これで完了!めでたしめでたし。
…といきたいところなのですが、この設定には考慮できていない点があります。
【未考慮の点】 常時稼働するインスタンスが存在する場合
例えば以下のように常時稼働させているインスタンスがある場合を考えてみましょう。
常時稼働させるインスタンス:
- webapp-server01
- webapp-server02
スケールアウト時に稼働するインスタンス
- webapp-server03 - 99 (連番)
この例では webapp-server01, 02 が常時稼働しているインスタンスだとします。
この場合、Prometheusに上記設定をしておけば、03以降のインスタンスが起動すれば当然監視対象には追加されますし、停止すれば監視対象から除外されることになりますね。
では webapp-server01, 02 が停止した場合は?
現状の設定では、 常時稼働を求められるインスタンスも監視対象から除外されてしまいます 。
現状では、インスタンスの状態が停止( stopped )したことを検知したら、Prometheusの監視対象から除外( action: drop )してしまいます。
- source_labels: [__meta_ec2_instance_state]
regex: "stopped"
action: drop
インスタンスの種別に関係なくそういった動きをする設定となっています。
常時稼働を求められるインスタンスなのに、停止したら監視対象から外れてしまう…。
そうなるとどうなるかというと /etc/prometheus/conf.d/instances.yml に定義している InstanceDown のアラートが飛ばないのですね。
常時稼働インスタンスが停止してしまったのに、アラートが来なかった場合…想像するだけでも恐いですね。
というわけで、「稼働しているインスタンスの若い番号の幾つかは常時稼働させないといけない」といった運用をしている場合、常時稼働しているインスタンスはこの動的追加/削除の対象から除外して、監視対象として固定してあげる必要があります。
じゃあどうしましょう?どうすればその要件を実現できるだろう…という話になるのですが、これはEC2のタグ設定とPrometheusの設定の書き方次第で実現可能なのです。
【解決策】 EC2のタグを基に監視対象と挙動を変化させる
解決策としてはいたってシンプルで、EC2のタグを使って挙動を変えるというものになります。
ここでは ServerType というタグをEC2に設定してある場合を想定してみます。
このタグに設定される値と意味合いは以下とします。
| 値 | 意味 |
|---|---|
regular |
常時稼働インスタンス |
extra |
スケールアウト用インスタンス |
以下のようにPrometheusの設定を変更します。
scrape_configs:
- job_name: 'node_exporter'
ec2_sd_configs:
- port: 9100
relabel_configs:
# 1. EC2の "ServerType" タグを "server_type" ラベルに変換する
- source_labels: [__meta_ec2_tag_ServerType]
target_label: server_type
# 2. "server_type" が "extra" かつ "stopped" の場合のみ、監視対象から除外(drop)する
- source_labels: [__meta_ec2_tag_ServerType, __meta_ec2_instance_state]
separator: ;
regex: extra;stopped
action: drop
# 3. "regular" のインスタンスは明示的に keep とする
- source_labels: [__meta_ec2_tag_ServerType]
regex: regular
action: keep
ちょっとばかり設定内容が増えました。
やっていることは、 ServerType タグをラベルとして付与し直して、そのラベルの値を条件としてインスタンス停止時に監視を継続するかしないかを判定する…というものです。
最初にEC2の ServerType タグをPrometheusで利用できる server_type ラベルに変換します。
これで server_type ラベルがPrometheusのPromQLでも利用できるようになります。
なお __meta_ から始まるラベルはPromQLから利用することができず、アラートルールの箇所等で値を参照したい場合はラベル付けをしてあげないといけません。
# 1. EC2の "ServerType" タグを "server_type" ラベルに変換する
- source_labels: [__meta_ec2_tag_ServerType]
target_label: server_type
次に、 server_type ラベルの値が extra だった時の条件を書いていきます。
インスタンスを停止すると __meta_ec2_instance_state のラベルが stopped になります。
これを組み合わせることで、 server_type ラベルの値が extra かつ stopped の場合に監視対象から除外する、という挙動が可能となります。
# 2. "server_type" が "extra" かつ "stopped" の場合のみ、監視対象から除外(drop)する
- source_labels: [server_type, __meta_ec2_instance_state]
separator: ;
regex: extra;stopped
action: drop
最後に、server_type が regular の場合は継続して監視対象とするように、明示的に keep としておきます。
# 3. "regular" のインスタンスは明示的に keep とする
- source_labels: [__meta_ec2_tag_Name]
regex: regular
action: keep
これで extra の場合は稼働すれば監視対象となり、停止すれば対象外となるようになります。
そして regular は常に監視対象となるため、インスタンスが停止すればアラートが発報するようになります。
これで常時稼働インスタンスは監視対象に固定され、スケールアウト用インスタンスのみ監視対象から動的追加/削除できるようになりました。
アラートルールの箇所も変更してみる
折角 server_type ラベルが使えるようになったことですし、 rules 内の alert でも server_type を取得できるようにしてみます。
といってもやっていることは description に ({{ $labels.server_type }}) を追加してみているだけです。
groups:
- name: node_exporter
rules:
- alert: InstanceDown
expr: up{job="node_exporter"} == 0
for: 180s
labels:
severity: critical
annotations:
description: '{{ $labels.name }}({{ $labels.server_type }}) of job {{ $labels.job }} has been down for more than 3 minutes.'
summary: 'Instance {{ $labels.name }} down'
これでSlack通知の際、 server_type もメッセージに出るようになりました。
めでたしめでたし。
【別解】 Prometheusの設定だけで実施する場合
scrape_configs:
- job_name: 'node_exporter'
ec2_sd_configs:
- port: 9100
relabel_configs:
# 1. まず名前を見て、常時稼働サーバー(01, 02)なら "server_type" に "regular" というラベルを付ける
- source_labels: [__meta_ec2_tag_Name]
target_label: server_type
regex: webapp-server(0[1-2])
# webapp-server01-02 -> label[regular]
replacement: regular
# 2. それ以外(03〜99)なら "server_type" に "extra" というラベルを付ける
- source_labels: [__meta_ec2_tag_Name]
target_label: server_type
regex: webapp-server(0[^1-2]|0[3-9]|[1-9][0-9]) # 正規表現で03-99を指定
# webapp-server03-99 -> label[extra]
replacement: extra
# 3. "server_type" が "extra" かつ "stopped" の場合のみ、監視対象から除外(drop)する
- source_labels: [server_type, __meta_ec2_instance_state]
separator: ;
regex: extra;stopped
action: drop
# 4. "regular" のインスタンスは明示的に keep とする
- source_labels: [__meta_ec2_tag_Name]
regex: regular
action: keep
別の解決策です。
ここではPrometheusの設定だけで頑張ってラベル付けをしてみています。
01-02であれば server_type ラベルに regular という値を付与し、それ以外であれば extra という値を付与します。
後の流れは一緒ですね。
Prometheusだけで完結させたい場合はこういった力技での解決方法もあります。
とはいえベストプラクティスはEC2インスタンスのタグと然るべき値を設定してあげることかなと思います。
よりシンプルな設定にも収まりますしね。
ただ、タグの追加だけでなく、こういった形でインスタンス名から判定するといった、場合によって柔軟な設定ができるというのはPrometheusならではの強みかもしれませんね。
株式会社SKIYAKIのテックブログです。ファンクラブプラットフォームBitfanの開発・運用にまつわる知見や調べたことなどを発信します。主な技術スタックは Ruby on Rails / React / AWS / Swift / Kotlin などです。 recruit.skiyaki.com/
Discussion