From d7f21a16947285e740b3be59f5cc79f5210f657c Mon Sep 17 00:00:00 2001 From: Georg Pfuetzenreuter Date: Sun, 30 Mar 2025 18:21:16 +0200 Subject: [PATCH] Unix socket listener support This introduces support for binding to a UNIX instead of a TCP/IP socket, which is useful in applications where binding to a TCP/IP socket is not desired due to security and/or performance considerations or constraints of the surrounding system. Signed-off-by: Georg Pfuetzenreuter --- cmd/prometheus/main.go | 2 +- cmd/prometheus/main_test.go | 44 +++++++++++++++++++++++++++++++++ docs/command-line/prometheus.md | 2 +- web/web.go | 11 ++++++++- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 4c3ede362e..a9b3fd8ed9 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -352,7 +352,7 @@ func main() { a.Flag("config.auto-reload-interval", "Specifies the interval for checking and automatically reloading the Prometheus configuration file upon detecting changes."). Default("30s").SetValue(&cfg.autoReloadInterval) - a.Flag("web.listen-address", "Address to listen on for UI, API, and telemetry. Can be repeated."). + a.Flag("web.listen-address", "Address or UNIX domain socket path (prefixed with `unix://`) to listen on for UI, API, and telemetry. Can be repeated."). Default("0.0.0.0:9090").StringsVar(&cfg.web.ListenAddresses) a.Flag("auto-gomaxprocs", "Automatically set GOMAXPROCS to match Linux container CPU quota"). diff --git a/cmd/prometheus/main_test.go b/cmd/prometheus/main_test.go index 0d0ab56eb4..f70b19c3d0 100644 --- a/cmd/prometheus/main_test.go +++ b/cmd/prometheus/main_test.go @@ -431,6 +431,50 @@ func TestAgentSuccessfulStartup(t *testing.T) { require.Equal(t, 0, actualExitStatus) } +func TestSuccessfulStartupWithUnixSocket(t *testing.T) { + socket := "prometheus-test.sock" + + prom := exec.Command(promPath, "-test.main", "--web.listen-address=unix://"+socket, "--config.file="+promConfig) + require.NoError(t, prom.Start()) + + actualExitStatus := 0 + done := make(chan error, 1) + + go func() { done <- prom.Wait() }() + select { + case err := <-done: + out, outerr := prom.Output() + t.Logf("prometheus should be still running: %v %v %v", err, out, outerr) + actualExitStatus = prom.ProcessState.ExitCode() + case <-time.After(startupTime): + prom.Process.Kill() + } + os.Remove(socket) + require.Equal(t, 0, actualExitStatus) +} + +func TestSuccessfulStartupWithUnixSocketAndExternalUrl(t *testing.T) { + socket := "prometheus-test.sock" + + prom := exec.Command(promPath, "-test.main", "--web.external-url=https://example.com", "--web.listen-address=unix://"+socket, "--config.file="+promConfig) + require.NoError(t, prom.Start()) + + actualExitStatus := 0 + done := make(chan error, 1) + + go func() { done <- prom.Wait() }() + select { + case err := <-done: + out, outerr := prom.Output() + t.Logf("prometheus should be still running: %v %v %v", err, out, outerr) + actualExitStatus = prom.ProcessState.ExitCode() + case <-time.After(startupTime): + prom.Process.Kill() + } + os.Remove(socket) + require.Equal(t, 0, actualExitStatus) +} + func TestAgentFailedStartupWithServerFlag(t *testing.T) { t.Parallel() diff --git a/docs/command-line/prometheus.md b/docs/command-line/prometheus.md index ebd6007f7b..8a69fc94af 100644 --- a/docs/command-line/prometheus.md +++ b/docs/command-line/prometheus.md @@ -16,7 +16,7 @@ The Prometheus monitoring server | --version | Show application version. | | | --config.file | Prometheus configuration file path. | `prometheus.yml` | | --config.auto-reload-interval | Specifies the interval for checking and automatically reloading the Prometheus configuration file upon detecting changes. | `30s` | -| --web.listen-address ... | Address to listen on for UI, API, and telemetry. Can be repeated. | `0.0.0.0:9090` | +| --web.listen-address ... | Address or UNIX domain socket path (prefixed with `unix://`) to listen on for UI, API, and telemetry. Can be repeated. | `0.0.0.0:9090` | | --auto-gomaxprocs | Automatically set GOMAXPROCS to match Linux container CPU quota | `true` | | --auto-gomemlimit | Automatically set GOMEMLIMIT to match Linux container or system memory limit | `true` | | --auto-gomemlimit.ratio | The ratio of reserved GOMEMLIMIT memory to the detected maximum container or system memory | `0.9` | diff --git a/web/web.go b/web/web.go index 84c4a2a529..55c3b54764 100644 --- a/web/web.go +++ b/web/web.go @@ -651,11 +651,20 @@ func (h *Handler) Listeners() ([]net.Listener, error) { return listeners, nil } +// tcpOrUnixDomainSocket returns either TCP/IP or UNIX domain binding information. +func tcpOrUnixDomainSocket(address string) (string, string) { + if strings.HasPrefix(address, "unix://") { + return "unix", strings.Replace(address, "unix://", "", 1) + } + + return "tcp", address +} + // Listener creates the TCP listener for web requests. func (h *Handler) Listener(address string, sem chan struct{}) (net.Listener, error) { h.logger.Info("Start listening for connections", "address", address) - listener, err := net.Listen("tcp", address) + listener, err := net.Listen(tcpOrUnixDomainSocket(address)) if err != nil { return listener, err }