カスタムサービスディスカバリーの実装

カスタムサービスディスカバリーの実装

Prometheus には、Consul、Kubernetes、Azureなどのパブリッククラウドプロバイダーなど、多くのサービスディスカバリー(SD)システムとの統合が組み込まれています。ただし、すべてのサービスディスカバリーオプションに対応した統合実装を提供することはできません。Prometheus チームは、現在の SD 統合のセットをサポートするだけでも手一杯であり、考えられるすべての SD オプションに対応した統合を維持することは現実的ではありません。多くの場合、現在の SD 実装はチーム外の人々によって提供され、その後、十分にメンテナンスやテストが行われていません。私たちは、メンテナンスが可能で、意図したとおりに動作すると分かっているサービスディスカバリーメカニズムとの直接統合のみを提供することに尽力したいと考えています。このため、現在、新しい SD 統合の導入は一時停止されています。

ただし、Docker Swarmなどの他の SD メカニズムと統合したいという要望が依然としてあることは承知しています。最近、Prometheus リポジトリ内のディレクトリに、小さなコード変更と例がドキュメントに追加され、メインの Prometheus バイナリにマージすることなく、カスタムサービスディスカバリー統合を実装できるようになりました。このコード変更により、内部の Discovery Manager コードを利用して、新しい SD メカニズムと対話し、Prometheus の file_sd と互換性のあるファイルを出力する別の実行可能ファイルを作成できるようになります。Prometheus と新しい実行可能ファイルを同じ場所に配置することで、実行可能ファイルの file_sd 互換出力を読み取るように Prometheus を構成し、そのサービスディスカバリーメカニズムからターゲットをスクレイピングすることができます。将来的には、これにより、SD 統合をメインの Prometheus バイナリから移動し、アダプターを利用する安定した SD 統合を Prometheus のdiscoveryパッケージに移動できるようになります。

アダプターコードで実装されているものなど、file_sd を使用した統合は、こちらにリストされています。

例のコードを見てみましょう。

アダプター

まず、ファイルadapter.goがあります。このファイルは、カスタム SD 実装用にコピーするだけで構いませんが、ここで何が起こっているかを理解することは有益です。

// Adapter runs an unknown service discovery implementation and converts its target groups
// to JSON and writes to a file for file_sd.
type Adapter struct {
    ctx     context.Context
    disc    discovery.Discoverer
    groups  map[string]*customSD
    manager *discovery.Manager
    output  string
    name    string
    logger  log.Logger
}

// Run starts a Discovery Manager and the custom service discovery implementation.
func (a *Adapter) Run() {
    go a.manager.Run()
    a.manager.StartCustomProvider(a.ctx, a.name, a.disc)
    go a.runCustomSD(a.ctx)
}

アダプターは、discovery.Managerを利用して、カスタム SD プロバイダーの Run 関数を goroutine で実際に開始します。 Manager には、カスタム SD が更新を送信するチャネルがあります。これらの更新には、SD ターゲットが含まれます。groups フィールドには、カスタム SD 実行可能ファイルが SD メカニズムから認識しているすべてのターゲットとラベルが含まれています。

type customSD struct {
    Targets []string          `json:"targets"`
    Labels  map[string]string `json:"labels"`
}

この customSD 構造体は、主に内部の Prometheus targetgroup.Group 構造体を file_sd 形式の JSON に変換するために存在します。

実行中、アダプターはカスタム SD 実装からの更新をチャネルでリッスンします。更新を受信すると、targetgroup.Groups を別の map[string]*customSD に解析し、アダプターの groups フィールドに格納されているものと比較します。2つが異なる場合、新しいグループをアダプター構造体に割り当て、JSON として出力ファイルに書き込みます。この実装では、チャネルを介して SD 実装によって送信される各更新に、SD が認識しているすべてのターゲットグループの完全なリストが含まれていることを前提としていることに注意してください。

カスタム SD 実装

次に、アダプターを使用して独自のカスタム SD を実装します。完全な動作例は、同じ examples ディレクトリのこちらにあります。

ここでは、アダプターコード "github.com/prometheus/prometheus/documentation/examples/custom-sd/adapter" と、他のいくつかの Prometheus ライブラリをインポートしていることがわかります。カスタム SD を作成するには、Discoverer インターフェースの実装が必要です。

// Discoverer provides information about target groups. It maintains a set
// of sources from which TargetGroups can originate. Whenever a discovery provider
// detects a potential change, it sends the TargetGroup through its channel.
//
// Discoverer does not know if an actual change happened.
// It does guarantee that it sends the new TargetGroup whenever a change happens.
//
// Discoverers should initially send a full set of all discoverable TargetGroups.
type Discoverer interface {
    // Run hands a channel to the discovery provider(consul,dns etc) through which it can send
    // updated target groups.
    // Must returns if the context gets canceled. It should not close the update
    // channel on returning.
    Run(ctx context.Context, up chan<- []*targetgroup.Group)
}

実際には、1つの関数 Run(ctx context.Context, up chan<- []*targetgroup.Group) を実装する必要があります。これは、アダプターコード内のマネージャーが goroutine 内で呼び出す関数です。Run 関数は、終了時期を知るためにコンテキストを利用し、ターゲットグループの更新を送信するためのチャネルを渡されます。

提供された例のRun関数を見ると、別の SD の実装で実行する必要があるいくつかの重要な処理が行われていることがわかります。この例では、定期的に Consul(この例では、組み込みの Consul SD 実装は存在しないと想定します)を呼び出し、応答を targetgroup.Group 構造体のセットに変換します。Consul の動作方法により、まずすべての既知のサービスを取得するための呼び出しを行い、次にすべてのバックエンドインスタンスに関する情報を取得するためにサービスごとに別の呼び出しを行う必要があります。

各サービスについて Consul を呼び出しているループの上のコメントに注目してください。

// Note that we treat errors when querying specific consul services as fatal for for this
// iteration of the time.Tick loop. It's better to have some stale targets than an incomplete
// list of targets simply because there may have been a timeout. If the service is actually
// gone as far as consul is concerned, that will be picked up during the next iteration of
// the outer loop.

ここで、すべてのターゲットの情報を取得できない場合は、不完全な更新を送信するよりも、更新を一切送信しない方が良いと言っています。ネットワークの問題、プロセスの再起動、HTTP タイムアウトなどの一時的な問題による誤検知を防ぐために、古いターゲットのリストを短期間保持する方が良いでしょう。Consul からすべてのターゲットに関する応答が得られた場合は、それらのすべてのターゲットをチャネルで送信します。また、個々のサービスに対する Consul の応答を受け取り、ラベル付きのバックエンドノードからターゲットグループを作成するヘルパー関数 parseServiceNodes もあります。

現在の例の使用

独自のカスタム SD 実装を書き始める前に、コードを確認した後で現在の例を実行することをお勧めします。簡単にするために、私は通常、例のコードを使用する際に、Docker コンテナとして Consul と Prometheus の両方を docker-compose 経由で実行します。

docker-compose.yml

version: '2'
services:
consul:
    image: consul:latest
    container_name: consul
    ports:
    - 8300:8300
    - 8500:8500
    volumes:
    - ${PWD}/consul.json:/consul/config/consul.json
prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
    - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
    - 9090:9090

consul.json

{
"service": {
    "name": "prometheus",
    "port": 9090,
    "checks": [
    {
        "id": "metrics",
        "name": "Prometheus Server Metrics",
        "http": "http://prometheus:9090/metrics",
        "interval": "10s"
    }
    ]

}
}

docker-compose 経由で両方のコンテナを起動し、例の main.go を実行すると、localhost:8500 で Consul HTTP API をクエリし、file_sd 互換ファイルが custom_sd.json として書き込まれます。file_sd 設定を介してこのファイルをピックアップするように Prometheus を構成できます。

scrape_configs:
  - job_name: "custom-sd"
    scrape_interval: "15s"
    file_sd_configs:
    - files:
      - /path/to/custom_sd.json