🎄

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インスタンスの状態に応じて動的に監視対象に追加/削除をしてくれます。

/etc/prometheus/prometheus.yml
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分停止していたらアラートが発報するようにします。

/etc/prometheus/conf.d/instances.yml
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に通知をするだけです。

/etc/alertmanager/alertmanager.yml
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の設定を変更します。

/etc/prometheus/prometheus.yml
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から利用することができず、アラートルールの箇所等で値を参照したい場合はラベル付けをしてあげないといけません。

ServiceType -> server_typeへ変換
  # 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 の場合に監視対象から除外する、という挙動が可能となります。

extraかつstoppedとなった場合
  # 2. "server_type" が "extra" かつ "stopped" の場合のみ、監視対象から除外(drop)する
  - source_labels: [server_type, __meta_ec2_instance_state]
    separator: ;
    regex: extra;stopped
    action: drop

最後に、server_typeregular の場合は継続して監視対象とするように、明示的に keep としておきます。

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 }}) を追加してみているだけです。

/etc/prometheus/conf.d/instances.yml
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の設定だけで実施する場合

/etc/prometheus/prometheus.yml
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 Tech Blog

Discussion