Prometheus 0.14.0での高度なサービスディスカバリ

2015年6月1日筆者: Fabian Reinartz, Julius Volz

今週、私たちはPrometheus v0.14.0をリリースしました。これは、待望の多くの追加機能と改善を含むバージョンです。

ユーザー側では、Prometheusは新しいサービスディスカバリメカニズムをサポートするようになりました。DNS-SRVレコードに加えて、Consulをすぐにサポートし、ファイルベースのインターフェースにより独自のディスカバリメカニズムを接続できます。今後、Prometheusに他の一般的なサービスディスカバリメカニズムを追加していく予定です。

多くの小さな修正と改善に加えて、SIGHUPをPrometheusプロセスに送信することで、実行中に設定を再読み込みできるようになりました。変更点の完全なリストは、このリリースの変更ログで確認できます。

このブログ記事では、組み込みのサービスディスカバリメカニズムを詳しく見て、いくつかの実用的な例を提供します。追加リソースとして、Prometheusの設定ドキュメントを参照してください。

Prometheusとターゲット

このブログ記事を正しく理解するためには、まずPrometheusがターゲットにラベルを付ける方法を見ておく必要があります。

設定ファイルには、ターゲットラベルを設定できる場所がいくつかあります。これらは以下の順序で適用され、後の段階で前の段階で設定されたラベルが上書きされます。

  1. Prometheusインスタンスによってスクレイピングされるすべてのターゲットに割り当てられるグローバルラベル。
  2. 各スクレイプ設定のデフォルト値として設定されるjobラベル。
  3. スクレイプ設定内でターゲットグループごとに設定されるラベル。
  4. リラベルによる高度なラベル操作。

各ステージは、前のステージからの競合するラベルを上書きします。最終的に、単一のターゲットを記述するフラットなラベルセットが得られます。これらのラベルは、そのターゲットからスクレイピングされるすべての時系列に付与されます。

注:内部的には、ターゲットのアドレスでさえ、特別な__address__ラベルに保存されます。これは、後で説明する高度なラベル操作(リラベル)中に役立つことがあります。__で始まるラベルは、最終的な時系列には表示されません。

スクレイプ設定とリラベル

ASCIIプロトコルバッファ形式からYAMLへの移行に加えて、Prometheusの設定の根本的な変更点は、ジョブごとの設定からより汎用的なスクレイプ設定への変更です。単純な設定では両者はほぼ同等ですが、スクレイプ設定はより高度なユースケースでより高い柔軟性を提供します。

各スクレイプ設定は、jobラベルのデフォルト値として機能するジョブ名を定義します。jobラベルは、ターゲットグループ全体または個々のターゲットに対して再定義できます。たとえば、2つのターゲットグループを定義し、それぞれが1つのジョブのターゲットを定義できます。同じパラメーターでそれらをスクレイピングするには、次のように設定できます。

scrape_configs:
- job_name: 'overwritten-default'

  scrape_interval: 10s
  scrape_timeout:  5s

  target_groups:
  - targets: ['10.1.200.130:5051', '10.1.200.134:5051']
    labels:
      job: 'job1'

  - targets: ['10.1.200.130:6220', '10.1.200.134:6221']
    labels:
      job: 'job2'

リラベルと呼ばれるメカニズムにより、任意のラベルをターゲットごとに削除、作成、または変更できます。これにより、サービスディスカバリから得られるメタデータも考慮に入れたきめ細かいラベリングが可能になります。リラベルはラベル割り当ての最終段階であり、以前に設定されたすべてのラベルを上書きします。

リラベルは次のように機能します。

  • ソースラベルのリストが定義されます。
  • 各ターゲットについて、これらのラベルの値が区切り文字で連結されます。
  • 結果の文字列に対して正規表現が照合されます。
  • これらのマッチに基づいて新しい値が別のラベルに割り当てられます。

各スクレイプ設定に対して複数のリラベルルールを定義できます。2つのラベルを1つにまとめる単純な例は次のとおりです。

relabel_configs:
- source_labels: ['label_a', 'label_b']
  separator:     ';'
  regex:         '(.*);(.*)'
  replacement:   '${1}-${2}'
  target_label:  'label_c'

このルールは、ラベルセットを持つターゲットを変換します。

{
  "job": "job1",
  "label_a": "foo",
  "label_b": "bar"
}

...ラベルセットを持つターゲットに。

{
  "job": "job1",
  "label_a": "foo",
  "label_b": "bar",
  "label_c": "foo-bar"
}

その後、追加のリラベルステップでソースラベルを削除することもできます。

リラベルと、それを使用してターゲットをフィルタリングする方法の詳細については、設定ドキュメントを参照してください。

次のセクションでは、サービスディスカバリを使用する際にリラベルをどのように活用できるかを見ていきます。

DNS-SRVレコードによるディスカバリ

Prometheusは当初からDNS-SRVレコードを介したターゲットディスカバリをサポートしていました。それぞれの設定は次のようになります。

job {
  name: "api-server"
  sd_name: "telemetry.eu-west.api.srv.example.org"
  metrics_path: "/metrics"
}

Prometheus 0.14.0では、単一のスクレイプ設定で複数のSRVレコードを照会できるようになり、リラベルフェーズで役立つサービスディスカバリ固有のメタ情報も提供されます。

DNS-SRVレコードを照会すると、__meta_dns_nameというラベルが各ターゲットに付与されます。その値は、それが返されたSRVレコード名に設定されます。telemetry.<zone>.<job>.srv.example.orgのような構造化されたSRVレコード名がある場合、そこから関連するラベルを抽出できます。

scrape_configs:
- job_name: 'myjob'

  dns_sd_configs:
  - names:
    - 'telemetry.eu-west.api.srv.example.org'
    - 'telemetry.us-west.api.srv.example.org'
    - 'telemetry.eu-west.auth.srv.example.org'
    - 'telemetry.us-east.auth.srv.example.org'

  relabel_configs:
  - source_labels: ['__meta_dns_name']
    regex:         'telemetry\.(.+?)\..+?\.srv\.example\.org'
    target_label:  'zone'
    replacement:   '$1'
  - source_labels: ['__meta_dns_name']
    regex:         'telemetry\..+?\.(.+?)\.srv\.example\.org'
    target_label:  'job'
    replacement:   '$1'

これにより、各ターゲットに、そのターゲットが由来するSRVレコードに基づいてzoneおよびjobラベルが付与されます。

Consulによるディスカバリ

Consulを介したサービスディスカバリがネイティブにサポートされるようになりました。これは、Consulエージェントのアクセスパラメーターと、ターゲットを照会したいConsulサービスのリストを定義することで設定できます。

各Consulノードのタグは、設定可能な区切り文字で連結され、__meta_consul_tagsラベルを通じて公開されます。その他、様々なConsul固有のメタラベルも提供されます。

指定されたサービスのすべてのインスタンスをスクレイピングするには、シンプルなconsul_sd_configとリラベルルールで実現できます。

scrape_configs:
- job_name: 'overwritten-default'

  consul_sd_configs:
  - server:   '127.0.0.1:5361'
    services: ['auth', 'api', 'load-balancer', 'postgres']

  relabel_configs:
  - source_labels: ['__meta_consul_service']
    regex:         '(.*)'
    target_label:  'job'
    replacement:   '$1'
  - source_labels: ['__meta_consul_node']
    regex:         '(.*)'
    target_label:  'instance'
    replacement:   '$1'
  - source_labels: ['__meta_consul_tags']
    regex:         ',(production|canary),'
    target_label:  'group'
    replacement:   '$1'

これにより、ローカルのConsulエージェントから指定されたサービスが検出されます。結果として、4つのジョブ(authapiload-balancerpostgres)のメトリックが得られます。ノードがproductionまたはcanaryのConsulタグを持っている場合、それぞれのgroupラベルがターゲットに割り当てられます。各ターゲットのinstanceラベルは、Consulによって提供されるノード名に設定されます。

Consulを介したサービスディスカバリのすべての設定パラメーターの完全なドキュメントは、Prometheusウェブサイトで確認できます。

カスタムサービスディスカバリ

最後に、カスタムサービスディスカバリや、まだすぐにサポートされていない他の一般的なメカニズムを統合するためのファイルベースのインターフェースを追加しました。

このメカニズムにより、Prometheusはターゲットグループを定義する一連のディレクトリまたはファイルを監視します。これらのファイルのいずれかが変更されると、ファイルからターゲットグループのリストが読み取られ、スクレイプターゲットが抽出されます。任意のサービスディスカバリメカニズムからの変更を取得し、ターゲット情報を監視対象ファイルにターゲットグループのリストとして書き込む小さなブリッジプログラムを、Prometheusのサイドキックとして実行するのが私たちの仕事です。

これらのファイルはYAML形式でも

- targets: ['10.11.150.1:7870', '10.11.150.4:7870']
  labels:
    job: 'mysql'

- targets: ['10.11.122.11:6001', '10.11.122.15:6002']
  labels:
    job: 'postgres'

...JSON形式でも構いません。

[
  {
    "targets": ["10.11.150.1:7870", "10.11.150.4:7870"],
    "labels": {
      "job": "mysql"
    }
  },
  {
    "targets": ["10.11.122.11:6001", "10.11.122.15:6002"],
    "labels": {
      "job": "postgres"
    }
  }
]

Prometheusに作業ディレクトリ内のtgroups/ディレクトリにあるすべての.jsonファイルを監視するように設定します。

scrape_configs:
- job_name: 'overwritten-default'

  file_sd_configs:
  - names: ['tgroups/*.json']

このディレクトリにファイルを書き込むプログラムが不足しています。この例のために、異なるジョブのすべてのインスタンスが単一の非正規化されたMySQLテーブルにあると仮定しましょう。(ヒント:おそらく、このような方法でサービスディスカバリを行うべきではありません。)

30秒ごとに、MySQLテーブルからすべてのインスタンスを読み取り、結果のターゲットグループをJSONファイルに書き込みます。ターゲットやそのラベルが変更されたかどうかを状態として保持する必要がないことに注意してください。Prometheusは自動的に変更を検出し、スクレイプサイクルを中断することなくターゲットに適用します。

import os, time, json

from itertools import groupby
from MySQLdb import connect


def refresh(cur):
    # Fetch all rows.
    cur.execute("SELECT address, job, zone FROM instances")

    tgs = []
    # Group all instances by their job and zone values.
    for key, vals in groupby(cur.fetchall(), key=lambda r: (r[1], r[2])):
        tgs.append({
            'labels': dict(zip(['job', 'zone'], key)),
            'targets': [t[0] for t in vals],
        })

    # Persist the target groups to disk as JSON file.
    with open('tgroups/target_groups.json.new', 'w') as f:
        json.dump(tgs, f)
        f.flush()
        os.fsync(f.fileno())

    os.rename('tgroups/target_groups.json.new', 'tgroups/target_groups.json')


if __name__ == '__main__':
    while True:
        with connect('localhost', 'root', '', 'test') as cur:
            refresh(cur)
        time.sleep(30)

Prometheusは不正な形式の変更をファイルに適用しませんが、例で示しているように、ファイル名を変更してファイルをアトミックに更新することがベストプラクティスとされています。また、大量のターゲットグループは論理的なグループ分けに基づいて複数のファイルに分割することをお勧めします。

結論

DNS-SRVレコードとConsulにより、主要なサービスディスカバリメソッドがPrometheusでネイティブにサポートされるようになりました。リラベルが、サービスディスカバリメカニズムによって提供されるメタデータを活用するための強力なアプローチであることを確認しました。

新しいリリースにPrometheusセットアップをアップグレードし、基本的なHTTP認証やリラベルによるターゲットフィルタリングなど、他の設定オプションについて確認するには、新しい設定ドキュメントを必ず参照してください。

既存の設定ファイルを新しいYAML形式にアップグレードする移行ツールを提供しています。小規模な設定の場合は、新しい形式に慣れるため、またコメントを保持するためにも手動でのアップグレードをお勧めします。