Prometheus 0.14.0における高度なサービスディスカバリ

今週、待望の追加と改善が多数含まれたPrometheus v0.14.0をリリースしました。

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

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

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

Prometheusとターゲット

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

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

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

各段階は、前の段階からの衝突するラベルを上書きします。最終的に、単一のターゲットを記述するフラットなラベルセットが得られます。これらのラベルは、このターゲットからスクレイプされたすべての時系列に添付されます。

注:内部的には、ターゲットのアドレスでさえ特別な__address__ラベルに格納されます。後で説明するように、これは高度なラベル操作(再ラベル付け)中に役立ちます。__で始まるラベルは、最終的な時系列には表示されません。

スクレイプ設定と再ラベル付け

ASCIIプロトコルバッファ形式からYAMLへの移行に加えて、Prometheusの設定の基本的な変更点は、ジョブごとの設定からより一般化されたスクレイプ設定への変更です。単純な設定では2つはほぼ同等ですが、スクレイプ設定では、より高度なユースケースでより高い柔軟性が得られます。

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

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'

これにより、zoneラベルとjobラベルが、由来となったSRVレコードに基づいて各ターゲットに添付されます。

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-balancer、およびpostgres)のメトリックが得られます。ノードにproductionまたはcanary Consulタグが付いている場合、それぞれのgroupラベルがターゲットに割り当てられます。各ターゲットのinstanceラベルは、Consulによって提供されるノード名に設定されます。

Consulを介したサービスディスカバリのすべてのパラメータ設定に関するドキュメントは、PrometheusのWebサイトにあります。

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

最後に、カスタムサービスディスカバリまたはすぐに使用できない他の一般的なメカニズムを統合するためのファイルベースのインターフェースを追加しました。

このメカニズムにより、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"
    }
  }
]

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

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により、2つの主要なサービスディスカバリメソッドがPrometheusによってネイティブでサポートされるようになりました。再ラベル付けは、サービスディスカバリメカニズムによって提供されるメタデータを利用するための強力なアプローチであることがわかりました。

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

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