mirror of
https://github.com/coredns/coredns.git
synced 2026-04-08 04:55:40 -04:00
Add optional TLS support to /metrics endpoint (#7255)
* Use exporter-toolkit to enable optional TLS encryption on /metrics endpoint Signed-off-by: peppi-lotta <peppi-lotta.saari@est.tech> * Implement startup listener to signal server readiness Signed-off-by: peppi-lotta <peppi-lotta.saari@est.tech> --------- Signed-off-by: peppi-lotta <peppi-lotta.saari@est.tech>
This commit is contained in:
@@ -3,18 +3,22 @@ package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coredns/caddy"
|
||||
"github.com/coredns/coredns/plugin"
|
||||
"github.com/coredns/coredns/plugin/pkg/log"
|
||||
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/prometheus/exporter-toolkit/web"
|
||||
)
|
||||
|
||||
// Metrics holds the prometheus configuration. The metrics' path is fixed to be /metrics .
|
||||
@@ -34,6 +38,8 @@ type Metrics struct {
|
||||
zoneMu sync.RWMutex
|
||||
|
||||
plugins map[string]struct{} // all available plugins, used to determine which plugin made the client write
|
||||
|
||||
tlsConfigPath string
|
||||
}
|
||||
|
||||
// New returns a new instance of Metrics with the given address.
|
||||
@@ -83,6 +89,32 @@ func (m *Metrics) ZoneNames() []string {
|
||||
return s
|
||||
}
|
||||
|
||||
// startupListener wraps a net.Listener to detect when Accept() is first called
|
||||
type startupListener struct {
|
||||
net.Listener
|
||||
readyOnce sync.Once
|
||||
ready chan struct{}
|
||||
}
|
||||
|
||||
func newStartupListener(l net.Listener) *startupListener {
|
||||
return &startupListener{
|
||||
Listener: l,
|
||||
ready: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *startupListener) Accept() (net.Conn, error) {
|
||||
// Signal ready on first Accept() call (server is running)
|
||||
sl.readyOnce.Do(func() {
|
||||
close(sl.ready)
|
||||
})
|
||||
return sl.Listener.Accept()
|
||||
}
|
||||
|
||||
func (sl *startupListener) Ready() <-chan struct{} {
|
||||
return sl.ready
|
||||
}
|
||||
|
||||
// OnStartup sets up the metrics on startup.
|
||||
func (m *Metrics) OnStartup() error {
|
||||
ln, err := reuseport.Listen("tcp", m.Addr)
|
||||
@@ -91,7 +123,9 @@ func (m *Metrics) OnStartup() error {
|
||||
return err
|
||||
}
|
||||
|
||||
m.ln = ln
|
||||
startupListener := newStartupListener(ln)
|
||||
|
||||
m.ln = startupListener
|
||||
m.lnSetup = true
|
||||
|
||||
m.mux = http.NewServeMux()
|
||||
@@ -99,6 +133,7 @@ func (m *Metrics) OnStartup() error {
|
||||
|
||||
// creating some helper variables to avoid data races on m.srv and m.ln
|
||||
server := &http.Server{
|
||||
Addr: m.Addr,
|
||||
Handler: m.mux,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
@@ -106,10 +141,53 @@ func (m *Metrics) OnStartup() error {
|
||||
}
|
||||
m.srv = server
|
||||
|
||||
if m.tlsConfigPath == "" {
|
||||
go func() {
|
||||
if err := server.Serve(ln); err != nil && err != http.ErrServerClosed {
|
||||
log.Errorf("Failed to start HTTP metrics server: %s", err)
|
||||
}
|
||||
}()
|
||||
ListenAddr = ln.Addr().String() // For tests.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check TLS config file existence
|
||||
if _, err := os.Stat(m.tlsConfigPath); os.IsNotExist(err) {
|
||||
log.Errorf("TLS config file does not exist: %s", m.tlsConfigPath)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create web config for ListenAndServe
|
||||
webConfig := &web.FlagConfig{
|
||||
WebListenAddresses: &[]string{m.Addr},
|
||||
WebSystemdSocket: new(bool), // false by default
|
||||
WebConfigFile: &m.tlsConfigPath,
|
||||
}
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
// Create channels for synchronization
|
||||
startUpErr := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
server.Serve(ln)
|
||||
// Try to start the server and report result if there an error.
|
||||
// web.Serve() never returns nil, it always returns a non-nil error and
|
||||
// it doesn't retun anything if server starts successfully.
|
||||
// startupListener handles capturing succesful startup.
|
||||
err := web.Serve(m.ln, server, webConfig, logger)
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
log.Errorf("Failed to start HTTPS metrics server: %v", err)
|
||||
startUpErr <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for startup errors
|
||||
select {
|
||||
case err := <-startUpErr:
|
||||
return err
|
||||
case <-startupListener.Ready():
|
||||
log.Infof("Server is ready and accepting connections")
|
||||
}
|
||||
|
||||
ListenAddr = ln.Addr().String() // For tests.
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user