From 1c68646c1d8d877eb8171daa6ddb00ce0fe9d211 Mon Sep 17 00:00:00 2001 From: n-marton Date: Fri, 17 Nov 2023 11:00:31 +0100 Subject: [PATCH 1/2] add flag for running agent only mode, to collect metrics about services registered only on the local agent Signed-off-by: n-marton --- cmd/consul_exporter/consul_exporter.go | 1 + pkg/exporter/consul_exporter.go | 76 ++++++++++++++++++++------ 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/cmd/consul_exporter/consul_exporter.go b/cmd/consul_exporter/consul_exporter.go index 8a439c5..0f891cd 100644 --- a/cmd/consul_exporter/consul_exporter.go +++ b/cmd/consul_exporter/consul_exporter.go @@ -65,6 +65,7 @@ func main() { kingpin.Flag("consul.timeout", "Timeout on HTTP requests to the Consul API.").Default("500ms").DurationVar(&opts.Timeout) kingpin.Flag("consul.insecure", "Disable TLS host verification.").Default("false").BoolVar(&opts.Insecure) kingpin.Flag("consul.request-limit", "Limit the maximum number of concurrent requests to consul, 0 means no limit.").Default("0").IntVar(&opts.RequestLimit) + kingpin.Flag("consul.agent-only", "Only export metrics about services registered on local agent").Default("false").BoolVar(&opts.AgentOnly) // Query options. kingpin.Flag("consul.allow_stale", "Allows any Consul server (non-leader) to service a read.").Default("true").BoolVar(&queryOptions.AllowStale) diff --git a/pkg/exporter/consul_exporter.go b/pkg/exporter/consul_exporter.go index 1be53e7..58d7dce 100644 --- a/pkg/exporter/consul_exporter.go +++ b/pkg/exporter/consul_exporter.go @@ -111,6 +111,7 @@ type Exporter struct { kvPrefix string kvFilter *regexp.Regexp healthSummary bool + agentOnly bool logger log.Logger requestLimitChan chan struct{} } @@ -125,6 +126,7 @@ type ConsulOpts struct { Timeout time.Duration Insecure bool RequestLimit int + AgentOnly bool } // New returns an initialized Exporter. @@ -182,6 +184,7 @@ func New(opts ConsulOpts, queryOptions consul_api.QueryOptions, kvPrefix, kvFilt healthSummary: healthSummary, logger: logger, requestLimitChan: requestLimitChan, + agentOnly: opts.AgentOnly, }, nil } @@ -206,14 +209,19 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { // Collect fetches the stats from configured Consul location and delivers them // as Prometheus metrics. It implements prometheus.Collector. func (e *Exporter) Collect(ch chan<- prometheus.Metric) { - ok := e.collectPeersMetric(ch) - ok = e.collectLeaderMetric(ch) && ok - ok = e.collectNodesMetric(ch) && ok - ok = e.collectMembersMetric(ch) && ok - ok = e.collectMembersWanMetric(ch) && ok - ok = e.collectServicesMetric(ch) && ok - ok = e.collectHealthStateMetric(ch) && ok - ok = e.collectKeyValues(ch) && ok + ok := false + if e.agentOnly { + ok = e.collectServicesMetric(ch) + } else { + ok = e.collectPeersMetric(ch) + ok = e.collectLeaderMetric(ch) && ok + ok = e.collectNodesMetric(ch) && ok + ok = e.collectMembersMetric(ch) && ok + ok = e.collectMembersWanMetric(ch) && ok + ok = e.collectServicesMetric(ch) && ok + ok = e.collectHealthStateMetric(ch) && ok + ok = e.collectKeyValues(ch) && ok + } if ok { ch <- prometheus.MustNewConstMetric( @@ -297,11 +305,25 @@ func (e *Exporter) collectMembersWanMetric(ch chan<- prometheus.Metric) bool { } func (e *Exporter) collectServicesMetric(ch chan<- prometheus.Metric) bool { - serviceNames, _, err := e.client.Catalog().Services(&e.queryOptions) - if err != nil { - level.Error(e.logger).Log("msg", "Failed to query for services", "err", err) - return false + serviceNames := make(map[string][]string) + if e.agentOnly { + services, err := e.client.Agent().Services() + if err != nil { + level.Error(e.logger).Log("msg", "Failed to query for agent services", "err", err) + return false + } + for name, srv := range services { + serviceNames[name] = srv.Tags + } + } else { + services, _, err := e.client.Catalog().Services(&e.queryOptions) + if err != nil { + level.Error(e.logger).Log("msg", "Failed to query for services", "err", err) + return false + } + serviceNames = services } + ch <- prometheus.MustNewConstMetric( serviceCount, prometheus.GaugeValue, float64(len(serviceNames)), ) @@ -401,13 +423,33 @@ func (e *Exporter) collectOneHealthSummary(ch chan<- prometheus.Metric, serviceN } level.Debug(e.logger).Log("msg", "Fetching health summary", "serviceName", serviceName) - service, _, err := e.client.Health().Service(serviceName, "", false, &e.queryOptions) - if err != nil { - level.Error(e.logger).Log("msg", "Failed to query service health", "err", err) - return false + var serviceEntries []*consul_api.ServiceEntry + + if e.agentOnly { + nodeName, err := e.client.Agent().NodeName() + if err != nil { + level.Error(e.logger).Log("msg", "Failed to query agent node name", "err", err) + return false + } + + _, agentServices, err := e.client.Agent().AgentHealthServiceByName(serviceName) + if err != nil { + level.Error(e.logger).Log("msg", "Failed to query agent service health", "err", err) + return false + } + for _, agentService := range agentServices { + serviceEntries = append(serviceEntries, &consul_api.ServiceEntry{Checks: agentService.Checks, Service: agentService.Service, Node: &consul_api.Node{Node: nodeName}}) + } + } else { + service, _, err := e.client.Health().Service(serviceName, "", false, &e.queryOptions) + if err != nil { + level.Error(e.logger).Log("msg", "Failed to query service health", "err", err) + return false + } + serviceEntries = service } - for _, entry := range service { + for _, entry := range serviceEntries { // We have a Node, a Service, and one or more Checks. Our // service-node combo is passing if all checks have a `status` // of "passing." From 8ea90b47b122ca98c309181724bb6c6d0dfbce1c Mon Sep 17 00:00:00 2001 From: n-marton Date: Wed, 13 Dec 2023 13:57:35 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Ben Kochie Signed-off-by: n-marton --- pkg/exporter/consul_exporter.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/exporter/consul_exporter.go b/pkg/exporter/consul_exporter.go index 58d7dce..c2f3864 100644 --- a/pkg/exporter/consul_exporter.go +++ b/pkg/exporter/consul_exporter.go @@ -209,16 +209,13 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { // Collect fetches the stats from configured Consul location and delivers them // as Prometheus metrics. It implements prometheus.Collector. func (e *Exporter) Collect(ch chan<- prometheus.Metric) { - ok := false - if e.agentOnly { - ok = e.collectServicesMetric(ch) - } else { - ok = e.collectPeersMetric(ch) + ok := e.collectServicesMetric(ch) + if !e.agentOnly { + ok = e.collectPeersMetric(ch) && ok ok = e.collectLeaderMetric(ch) && ok ok = e.collectNodesMetric(ch) && ok ok = e.collectMembersMetric(ch) && ok ok = e.collectMembersWanMetric(ch) && ok - ok = e.collectServicesMetric(ch) && ok ok = e.collectHealthStateMetric(ch) && ok ok = e.collectKeyValues(ch) && ok }