Skip to content

Commit 69bd82f

Browse files
authored
Add support for multiple scrape addresses (#539)
Make the `nginx.scrape-uri` accept a slice of addresses, and register prometheus collectors for each nginx instance. When multiple scrape addresses are registered, include the scrape address as a label to differentiate the metrics for each instance.
1 parent d337c84 commit 69bd82f

File tree

2 files changed

+57
-34
lines changed

2 files changed

+57
-34
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ Usage of ./nginx-prometheus-exporter:
113113
-nginx.scrape-uri string
114114
A URI or unix domain socket path for scraping NGINX or NGINX Plus metrics.
115115
For NGINX, the stub_status page must be available through the URI. For NGINX Plus -- the API. The default value can be overwritten by SCRAPE_URI environment variable. (default "http://127.0.0.1:8080/stub_status")
116+
Configure this option with the URI for every nginx instance to scrape.
116117
-nginx.ssl-ca-cert string
117118
Path to the PEM encoded CA certificate file used to validate the servers SSL certificate. The default value can be overwritten by SSL_CA_CERT environment variable.
118119
-nginx.ssl-client-cert string

exporter.go

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"crypto/x509"
77
"errors"
88
"fmt"
9+
"maps"
910
"net"
1011
"net/http"
1112
"os"
@@ -19,6 +20,7 @@ import (
1920
"github.com/nginxinc/nginx-prometheus-exporter/collector"
2021

2122
"github.com/alecthomas/kingpin/v2"
23+
"github.com/go-kit/log"
2224
"github.com/go-kit/log/level"
2325
"github.com/prometheus/client_golang/prometheus"
2426
"github.com/prometheus/client_golang/prometheus/promhttp"
@@ -83,7 +85,7 @@ var (
8385
webConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9113")
8486
metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("TELEMETRY_PATH").String()
8587
nginxPlus = kingpin.Flag("nginx.plus", "Start the exporter for NGINX Plus. By default, the exporter is started for NGINX.").Default("false").Envar("NGINX_PLUS").Bool()
86-
scrapeURI = kingpin.Flag("nginx.scrape-uri", "A URI or unix domain socket path for scraping NGINX or NGINX Plus metrics. For NGINX, the stub_status page must be available through the URI. For NGINX Plus -- the API.").Default("http://127.0.0.1:8080/stub_status").String()
88+
scrapeURIs = kingpin.Flag("nginx.scrape-uri", "A URI or unix domain socket path for scraping NGINX or NGINX Plus metrics. For NGINX, the stub_status page must be available through the URI. For NGINX Plus -- the API.").Default("http://127.0.0.1:8080/stub_status").Strings()
8789
sslVerify = kingpin.Flag("nginx.ssl-verify", "Perform SSL certificate verification.").Default("false").Envar("SSL_VERIFY").Bool()
8890
sslCaCert = kingpin.Flag("nginx.ssl-ca-cert", "Path to the PEM encoded CA certificate file used to validate the servers SSL certificate.").Default("").Envar("SSL_CA_CERT").String()
8991
sslClientCert = kingpin.Flag("nginx.ssl-client-cert", "Path to the PEM encoded client certificate file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_CERT").String()
@@ -120,6 +122,11 @@ func main() {
120122

121123
prometheus.MustRegister(version.NewCollector(exporterName))
122124

125+
if len(*scrapeURIs) == 0 {
126+
level.Error(logger).Log("msg", "No scrape addresses provided")
127+
os.Exit(1)
128+
}
129+
123130
// #nosec G402
124131
sslConfig := &tls.Config{InsecureSkipVerify: !*sslVerify}
125132
if *sslCaCert != "" {
@@ -149,42 +156,17 @@ func main() {
149156
transport := &http.Transport{
150157
TLSClientConfig: sslConfig,
151158
}
152-
if strings.HasPrefix(*scrapeURI, "unix:") {
153-
socketPath, requestPath, err := parseUnixSocketAddress(*scrapeURI)
154-
if err != nil {
155-
level.Error(logger).Log("msg", "Parsing unix domain socket scrape address failed", "uri", *scrapeURI, "error", err.Error())
156-
os.Exit(1)
157-
}
158-
159-
transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
160-
return net.Dial("unix", socketPath)
161-
}
162-
newScrapeURI := "http://unix" + requestPath
163-
scrapeURI = &newScrapeURI
164-
}
165159

166-
userAgent := fmt.Sprintf("NGINX-Prometheus-Exporter/v%v", version.Version)
167-
userAgentRT := &userAgentRoundTripper{
168-
agent: userAgent,
169-
rt: transport,
170-
}
171-
172-
httpClient := &http.Client{
173-
Timeout: *timeout,
174-
Transport: userAgentRT,
175-
}
160+
if len(*scrapeURIs) == 1 {
161+
registerCollector(logger, transport, (*scrapeURIs)[0], constLabels)
162+
} else {
163+
for _, addr := range *scrapeURIs {
164+
// add scrape URI to const labels
165+
labels := maps.Clone(constLabels)
166+
labels["addr"] = addr
176167

177-
if *nginxPlus {
178-
plusClient, err := plusclient.NewNginxClient(*scrapeURI, plusclient.WithHTTPClient(httpClient))
179-
if err != nil {
180-
level.Error(logger).Log("msg", "Could not create Nginx Plus Client", "error", err.Error())
181-
os.Exit(1)
168+
registerCollector(logger, transport, addr, labels)
182169
}
183-
variableLabelNames := collector.NewVariableLabelNames(nil, nil, nil, nil, nil, nil, nil, nil)
184-
prometheus.MustRegister(collector.NewNginxPlusCollector(plusClient, "nginxplus", variableLabelNames, constLabels, logger))
185-
} else {
186-
ossClient := client.NewNginxClient(httpClient, *scrapeURI)
187-
prometheus.MustRegister(collector.NewNginxCollector(ossClient, "nginx", constLabels, logger))
188170
}
189171

190172
http.Handle(*metricsPath, promhttp.Handler())
@@ -235,6 +217,46 @@ func main() {
235217
_ = srv.Shutdown(srvCtx)
236218
}
237219

220+
func registerCollector(logger log.Logger, transport *http.Transport,
221+
addr string, labels map[string]string,
222+
) {
223+
if strings.HasPrefix(addr, "unix:") {
224+
socketPath, requestPath, err := parseUnixSocketAddress(addr)
225+
if err != nil {
226+
level.Error(logger).Log("msg", "Parsing unix domain socket scrape address failed", "uri", addr, "error", err.Error())
227+
os.Exit(1)
228+
}
229+
230+
transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
231+
return net.Dial("unix", socketPath)
232+
}
233+
addr = "http://unix" + requestPath
234+
}
235+
236+
userAgent := fmt.Sprintf("NGINX-Prometheus-Exporter/v%v", version.Version)
237+
238+
httpClient := &http.Client{
239+
Timeout: *timeout,
240+
Transport: &userAgentRoundTripper{
241+
agent: userAgent,
242+
rt: transport,
243+
},
244+
}
245+
246+
if *nginxPlus {
247+
plusClient, err := plusclient.NewNginxClient(addr, plusclient.WithHTTPClient(httpClient))
248+
if err != nil {
249+
level.Error(logger).Log("msg", "Could not create Nginx Plus Client", "error", err.Error())
250+
os.Exit(1)
251+
}
252+
variableLabelNames := collector.NewVariableLabelNames(nil, nil, nil, nil, nil, nil, nil, nil)
253+
prometheus.MustRegister(collector.NewNginxPlusCollector(plusClient, "nginxplus", variableLabelNames, labels, logger))
254+
} else {
255+
ossClient := client.NewNginxClient(httpClient, addr)
256+
prometheus.MustRegister(collector.NewNginxCollector(ossClient, "nginx", labels, logger))
257+
}
258+
}
259+
238260
type userAgentRoundTripper struct {
239261
agent string
240262
rt http.RoundTripper

0 commit comments

Comments
 (0)