Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use web configuration from exporter-toolkit #461

Merged
merged 1 commit into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions examples/basic_auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# NGINX Prometheus Exporter with Web Configuration for Basic Authentication

This example shows how to run NGINX Prometheus Exporter with web configuration. In this folder you will find an example
configuration `web-config.yml` that enables basic authentication. It is configured to have a single user `alice` with
password `password`.

The full documentation for the web configuration can be found
[here](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md).

## Prerequisites

- NGINX Prometheus Exporter binary. See the [main README](../../README.md) for installation instructions.
- NGINX or NGINX Plus running on the same machine.

## Running NGINX Prometheus Exporter with Web Configuration in Basic Authentication mode

You can run NGINX Prometheus Exporter with web configuration in Basic Authentication mode using the following command:

```console
nginx-prometheus-exporter --web.config.file=web-config.yml --nginx.scrape-uri="http://127.0.0.1:8080/stub_status"
```

Depending on your environment, you may need to specify the full path to the binary or change the path to the web
configuration file.

## Verification

Run `curl -u alice:password http://localhost:9113/metrics` to see the metrics exposed by the exporter. Without the `-u`
flag, the request will fail with `401 Unauthorized`.
2 changes: 2 additions & 0 deletions examples/basic_auth/web-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
basic_auth_users:
alice: $2y$10$6xfhlaIhUDCUl60zPxkqLudN3QjL3Lfjg5gPAWiqElTLErpxAxJbC
35 changes: 35 additions & 0 deletions examples/systemd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# NGINX Prometheus Exporter with systemd-activated socket

This example shows how to run NGINX Prometheus Exporter with systemd-activated socket.

## Prerequisites

- Linux machine with [systemd](https://www.freedesktop.org/wiki/Software/systemd/).
- NGINX Prometheus Exporter binary in `/usr/local/bin/nginx-prometheus-exporter` or a location of your choice. See the
[main README](../../README.md) for installation instructions.
- NGINX or NGINX Plus running on the same machine.

## Customization

Modify `nginx_exporter.service` and `nginx_exporter.socket` to match your environment.

The default configuration assumes that NGINX Prometheus Exporter binary is located in
`/usr/local/bin/nginx-prometheus-exporter`.

The `ExecStart` directive has the flag `--web.systemd-socket` which tells the exporter to listen on the socket specified
in the `nginx_exporter.socket` file.

The `ListenStream` directive in `nginx_exporter.socket` specifies the socket to listen on. The default configuration
uses `9113` port, but the address can be written in various formats, for example `/run/nginx_exporter.sock`. To see the
full list of supported formats, run `man systemd.socket`.

## Installation

1. Copy `nginx_exporter.service` and `nginx_exporter.socket` to `/etc/systemd/system/`
2. Run `systemctl daemon-reload`
3. Run `systemctl start nginx_exporter`
4. Run `systemctl status nginx_exporter` to check the status of the service

## Verification

1. Run `curl http://localhost:9113/metrics` to see the metrics exposed by the exporter.
10 changes: 10 additions & 0 deletions examples/systemd/nginx_exporter.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Unit]
Description=NGINX Prometheus Exporter
Requires=nginx_exporter.socket

[Service]
User=nginx_exporter
ExecStart=/usr/local/bin/nginx-prometheus-exporter --nginx.scrape-uri="http://127.0.0.1:8080/stub_status" --web.systemd-socket

[Install]
WantedBy=multi-user.target
8 changes: 8 additions & 0 deletions examples/systemd/nginx_exporter.socket
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[Unit]
Description=NGINX Prometheus Exporter

[Socket]
ListenStream=9113

[Install]
WantedBy=sockets.target
37 changes: 37 additions & 0 deletions examples/tls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# NGINX Prometheus Exporter with Web Configuration for TLS

This example shows how to run NGINX Prometheus Exporter with web configuration. In this folder you will find an example
configuration `web-config.yml` that enables TLS and specifies the path to the TLS certificate and key files.
Additionally, there are two example TLS files `server.crt` and `server.key` that are used in the configuration.

The full documentation for the web configuration can be found
[here](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md).

## Prerequisites

- NGINX Prometheus Exporter binary. See the [main README](../../README.md) for installation instructions.
- NGINX or NGINX Plus running on the same machine.

## Running NGINX Prometheus Exporter with Web Configuration in TLS mode

You can run NGINX Prometheus Exporter with web configuration in TLS mode using the following command:

```console
nginx-prometheus-exporter --web.config.file=web-config.yml --nginx.scrape-uri="http://127.0.0.1:8080/stub_status"
```

you should see an output similar to this:

```console
...
ts=2023-07-20T02:00:26.932Z caller=tls_config.go:274 level=info msg="Listening on" address=[::]:9113
ts=2023-07-20T02:00:26.936Z caller=tls_config.go:310 level=info msg="TLS is enabled." http2=true address=[::]:9113
```

Depending on your environment, you may need to specify the full path to the binary or change the path to the web
configuration file.

## Verification

Run `curl -k https://localhost:9113/metrics` to see the metrics exposed by the exporter. The `-k` flag is needed because
the certificate is self-signed.
3 changes: 3 additions & 0 deletions examples/tls/web-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tls_server_config:
cert_file: server.crt
key_file: server.key
129 changes: 48 additions & 81 deletions exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
Expand All @@ -25,6 +26,9 @@ import (
"github.com/prometheus/common/promlog"
"github.com/prometheus/common/promlog/flag"
"github.com/prometheus/common/version"

"github.com/prometheus/exporter-toolkit/web"
"github.com/prometheus/exporter-toolkit/web/kingpinflag"
)

// positiveDuration is a wrapper of time.Duration to ensure only positive values are accepted
Expand Down Expand Up @@ -90,42 +94,19 @@ func parseUnixSocketAddress(address string) (string, string, error) {
return unixSocketPath, requestPath, nil
}

func getListener(listenAddress string) (net.Listener, error) {
var listener net.Listener
var err error

if strings.HasPrefix(listenAddress, "unix:") {
path, _, pathError := parseUnixSocketAddress(listenAddress)
if pathError != nil {
return listener, fmt.Errorf("parsing unix domain socket listen address %s failed: %w", listenAddress, pathError)
}
listener, err = net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
} else {
listener, err = net.Listen("tcp", listenAddress)
}

if err != nil {
return listener, err
}
return listener, nil
}

var (
constLabels = map[string]string{}

// Command-line flags
listenAddr = kingpin.Flag("web.listen-address", "An address or unix domain socket path to listen on for web interface and telemetry.").Default(":9113").Envar("LISTEN_ADDRESS").String()
securedMetrics = kingpin.Flag("web.secured-metrics", "Expose metrics using https.").Default("false").Envar("SECURED_METRICS").Bool()
sslServerCert = kingpin.Flag("web.ssl-server-cert", "Path to the PEM encoded certificate for the nginx-exporter metrics server(when web.secured-metrics=true).").Default("").Envar("SSL_SERVER_CERT").String()
sslServerKey = kingpin.Flag("web.ssl-server-key", "Path to the PEM encoded key for the nginx-exporter metrics server(when web.secured-metrics=true).").Default("").Envar("SSL_SERVER_KEY").String()
metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("TELEMETRY_PATH").String()
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()
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()
sslVerify = kingpin.Flag("nginx.ssl-verify", "Perform SSL certificate verification.").Default("false").Envar("SSL_VERIFY").Bool()
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()
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()
sslClientKey = kingpin.Flag("nginx.ssl-client-key", "Path to the PEM encoded client certificate key file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_KEY").String()
nginxRetries = kingpin.Flag("nginx.retries", "A number of retries the exporter will make on start to connect to the NGINX stub_status page/NGINX Plus API before exiting with an error.").Default("0").Envar("NGINX_RETRIES").Uint()
webConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9113")
metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").Envar("TELEMETRY_PATH").String()
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()
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()
sslVerify = kingpin.Flag("nginx.ssl-verify", "Perform SSL certificate verification.").Default("false").Envar("SSL_VERIFY").Bool()
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()
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()
sslClientKey = kingpin.Flag("nginx.ssl-client-key", "Path to the PEM encoded client certificate key file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_KEY").String()
nginxRetries = kingpin.Flag("nginx.retries", "A number of retries the exporter will make on start to connect to the NGINX stub_status page/NGINX Plus API before exiting with an error.").Default("0").Envar("NGINX_RETRIES").Uint()

// Custom command-line flags
timeout = createPositiveDurationFlag(kingpin.Flag("nginx.timeout", "A timeout for scraping metrics from NGINX or NGINX Plus.").Default("5s").Envar("TIMEOUT"))
Expand Down Expand Up @@ -213,22 +194,6 @@ func main() {
Transport: userAgentRT,
}

srv := http.Server{
ReadHeaderTimeout: 5 * time.Second,
}

signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
go func() {
level.Info(logger).Log("msg", "Signal received, exiting...", "signal", <-signalChan)
err := srv.Close()
if err != nil {
level.Error(logger).Log("msg", "Error occurred while closing the server", "error", err.Error())
os.Exit(1)
}
os.Exit(0)
}()

if *nginxPlus {
plusClient, err := createClientWithRetries(func() (interface{}, error) {
return plusclient.NewNginxClient(httpClient, *scrapeURI)
Expand All @@ -252,48 +217,50 @@ func main() {

http.Handle(*metricsPath, promhttp.Handler())

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, `<!DOCTYPE html>
<title>NGINX Exporter</title>
<h1>NGINX Exporter</h1>
<p><a href=%q>Metrics</a></p>`,
*metricsPath)
if *metricsPath != "/" && *metricsPath != "" {
landingConfig := web.LandingConfig{
Name: "NGINX Prometheus Exporter",
Description: "Prometheus Exporter for NGINX and NGINX Plus",
HeaderColor: "#039900",
Version: version.Info(),
Links: []web.LandingLinks{
{
Address: *metricsPath,
Text: "Metrics",
},
},
}
landingPage, err := web.NewLandingPage(landingConfig)
if err != nil {
level.Error(logger).Log("msg", "Error while sending a response for the '/' path", "error", err.Error())
level.Error(logger).Log("err", err)
os.Exit(1)
}
})
http.Handle("/", landingPage)
}

listener, err := getListener(*listenAddr)
if err != nil {
level.Error(logger).Log("msg", "Could not create listener", "error", err.Error())
os.Exit(1)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM)
defer cancel()

srv := &http.Server{
ReadHeaderTimeout: 5 * time.Second,
}
level.Info(logger).Log("msg", "Listening on address", "address", *listenAddr)

if *securedMetrics {
_, err = os.Stat(*sslServerCert)
if err != nil {
level.Error(logger).Log("msg", "Cert file is not set, not readable or non-existent. Make sure you set -web.ssl-server-cert when starting your exporter with -web.secured-metrics=true", "error", err.Error())
os.Exit(1)
}
_, err = os.Stat(*sslServerKey)
if err != nil {
level.Error(logger).Log("msg", "Key file is not set, not readable or non-existent. Make sure you set -web.ssl-server-key when starting your exporter with -web.secured-metrics=true", "error", err.Error())
os.Exit(1)
}
level.Info(logger).Log("msg", "NGINX Prometheus Exporter has successfully started using https")
if err := srv.ServeTLS(listener, *sslServerCert, *sslServerKey); err != nil {
level.Error(logger).Log("msg", "Error while serving", "error", err.Error())
go func() {
if err := web.ListenAndServe(srv, webConfig, logger); err != nil {
if errors.Is(err, http.ErrServerClosed) {
level.Info(logger).Log("msg", "HTTP server closed")
os.Exit(0)
}
level.Error(logger).Log("err", err)
os.Exit(1)
}
}
}()

level.Info(logger).Log("msg", "NGINX Prometheus Exporter has successfully started")
if err := srv.Serve(listener); err != nil {
level.Error(logger).Log("msg", "Error while serving", "error", err.Error())
os.Exit(1)
}
<-ctx.Done()
level.Info(logger).Log("msg", "Shutting down")
srvCtx, srvCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer srvCancel()
_ = srv.Shutdown(srvCtx)
}

type userAgentRoundTripper struct {
Expand Down
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,31 @@ require (
github.com/nginxinc/nginx-plus-go-client v0.10.0
github.com/prometheus/client_golang v1.16.0
github.com/prometheus/common v0.44.0
github.com/prometheus/exporter-toolkit v0.10.0

)

require (
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Loading