feat: support multiple metrics addresses via repeated flag

This commit is contained in:
Ingmar Stein 2026-02-17 13:45:19 +01:00
parent a0bcbf6a44
commit 81caea30f0
No known key found for this signature in database
4 changed files with 83 additions and 39 deletions

View File

@ -445,13 +445,15 @@ func StartServer(
return err
}
metricsListener, err := metrics.CreateMetricsListener(&listeners, c.String("metrics"))
metricsListeners, err := metrics.CreateMetricsListener(&listeners, c.StringSlice("metrics"))
if err != nil {
log.Err(err).Msg("Error opening metrics server listener")
return errors.Wrap(err, "Error opening metrics server listener")
}
defer metricsListener.Close()
for _, l := range metricsListeners {
defer l.Close()
}
wg.Add(1)
go func() {
@ -484,7 +486,7 @@ func StartServer(
QuickTunnelHostname: quickTunnelURL,
Orchestrator: orchestrator,
}
errC <- metrics.ServeMetrics(metricsListener, ctx, metricsConfig, log)
errC <- metrics.ServeMetrics(metricsListeners, ctx, metricsConfig, log)
}()
reconnectCh := make(chan supervisor.ReconnectSignal, c.Int(cfdflags.HaConnections))
@ -877,9 +879,9 @@ func configureCloudflaredFlags(shouldHide bool) []cli.Flag {
Value: false,
Hidden: shouldHide,
}),
altsrc.NewStringFlag(&cli.StringFlag{
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
Name: cfdflags.Metrics,
Value: metrics.GetMetricsDefaultAddress(metrics.Runtime),
Value: cli.NewStringSlice(metrics.GetMetricsDefaultAddress(metrics.Runtime)),
Usage: fmt.Sprintf(
`Listen address for metrics reporting. If no address is passed cloudflared will try to bind to %v.
If all are unavailable, a random port will be used. Note that when running cloudflared from an virtual

View File

@ -478,10 +478,11 @@ func buildReadyCommand() *cli.Command {
}
func readyCommand(c *cli.Context) error {
metricsOpts := c.String(flags.Metrics)
if !c.IsSet(flags.Metrics) {
metricsAddrs := c.StringSlice(flags.Metrics)
if len(metricsAddrs) == 0 {
return errors.New("--metrics has to be provided")
}
metricsOpts := metricsAddrs[0]
requestURL := fmt.Sprintf("http://%s/ready", metricsOpts)
req, err := http.NewRequest(http.MethodGet, requestURL, nil)

View File

@ -99,44 +99,53 @@ func newMetricsHandler(
return router
}
// CreateMetricsListener will create a new [net.Listener] by using an
// CreateMetricsListener will create a new set of [net.Listener] by using an
// known set of ports when the default address is passed with the fallback
// of choosing a random port when none is available.
//
// In case the provided address is not the default one then it will be used
// as is.
func CreateMetricsListener(listeners *gracenet.Net, laddr string) (net.Listener, error) {
if laddr == GetMetricsDefaultAddress(Runtime) {
func CreateMetricsListener(listeners *gracenet.Net, laddrs []string) ([]net.Listener, error) {
// If the user didn't provide any address, use the default one
if len(laddrs) == 1 && laddrs[0] == GetMetricsDefaultAddress(Runtime) {
// On the presence of the default address select
// a port from the known set of addresses iteratively.
addresses := GetMetricsKnownAddresses(Runtime)
for _, address := range addresses {
listener, err := listeners.Listen("tcp", address)
if err == nil {
return listener, nil
return []net.Listener{listener}, nil
}
}
// When no port is available then bind to a random one
listener, err := listeners.Listen("tcp", laddr)
listener, err := listeners.Listen("tcp", laddrs[0])
if err != nil {
return nil, fmt.Errorf("failed to listen to default metrics address: %w", err)
}
return listener, nil
return []net.Listener{listener}, nil
}
// Explicitly got a local address then bind to it
listener, err := listeners.Listen("tcp", laddr)
if err != nil {
return nil, fmt.Errorf("failed to bind to address (%s): %w", laddr, err)
// Explicitly got local addresses then bind to them
var ls []net.Listener
for _, laddr := range laddrs {
listener, err := listeners.Listen("tcp", laddr)
if err != nil {
// Close already opened listeners
for _, l := range ls {
l.Close()
}
return nil, fmt.Errorf("failed to bind to address (%s): %w", laddr, err)
}
ls = append(ls, listener)
}
return listener, nil
return ls, nil
}
func ServeMetrics(
l net.Listener,
listeners []net.Listener,
ctx context.Context,
config Config,
log *zerolog.Logger,
@ -153,12 +162,17 @@ func ServeMetrics(
Handler: h,
}
wg.Add(1)
go func() {
defer wg.Done()
err = server.Serve(l)
}()
log.Info().Msgf("Starting metrics server on %s", fmt.Sprintf("%v/metrics", l.Addr()))
for _, l := range listeners {
wg.Add(1)
go func(l net.Listener) {
defer wg.Done()
if serveErr := server.Serve(l); serveErr != nil && serveErr != http.ErrServerClosed {
log.Err(serveErr).Msgf("Metrics server failed on %s", l.Addr())
}
}(l)
log.Info().Msgf("Starting metrics server on %s", fmt.Sprintf("%v/metrics", l.Addr()))
}
// server.Serve will hang if server.Shutdown is called before the server is
// fully started up. So add artificial delay.
time.Sleep(startupTime)
@ -174,12 +188,8 @@ func ServeMetrics(
cancel()
wg.Wait()
if err == http.ErrServerClosed {
log.Info().Msg("Metrics server stopped")
return nil
}
log.Err(err).Msg("Metrics server failed")
return err
log.Info().Msg("Metrics server stopped")
return nil
}
func RegisterBuildInfo(buildType, buildTime, version string) {

View File

@ -13,28 +13,49 @@ import (
func TestMetricsListenerCreation(t *testing.T) {
t.Parallel()
listeners := gracenet.Net{}
listener1, err := metrics.CreateMetricsListener(&listeners, metrics.GetMetricsDefaultAddress("host"))
ls1, err := metrics.CreateMetricsListener(&listeners, []string{metrics.GetMetricsDefaultAddress("host")})
require.NoError(t, err)
require.Len(t, ls1, 1)
listener1 := ls1[0]
assert.Equal(t, "127.0.0.1:20241", listener1.Addr().String())
ls2, err := metrics.CreateMetricsListener(&listeners, []string{metrics.GetMetricsDefaultAddress("host")})
require.NoError(t, err)
listener2, err := metrics.CreateMetricsListener(&listeners, metrics.GetMetricsDefaultAddress("host"))
require.Len(t, ls2, 1)
listener2 := ls2[0]
assert.Equal(t, "127.0.0.1:20242", listener2.Addr().String())
ls3, err := metrics.CreateMetricsListener(&listeners, []string{metrics.GetMetricsDefaultAddress("host")})
require.NoError(t, err)
listener3, err := metrics.CreateMetricsListener(&listeners, metrics.GetMetricsDefaultAddress("host"))
require.Len(t, ls3, 1)
listener3 := ls3[0]
assert.Equal(t, "127.0.0.1:20243", listener3.Addr().String())
ls4, err := metrics.CreateMetricsListener(&listeners, []string{metrics.GetMetricsDefaultAddress("host")})
require.NoError(t, err)
listener4, err := metrics.CreateMetricsListener(&listeners, metrics.GetMetricsDefaultAddress("host"))
require.Len(t, ls4, 1)
listener4 := ls4[0]
assert.Equal(t, "127.0.0.1:20244", listener4.Addr().String())
ls5, err := metrics.CreateMetricsListener(&listeners, []string{metrics.GetMetricsDefaultAddress("host")})
require.NoError(t, err)
listener5, err := metrics.CreateMetricsListener(&listeners, metrics.GetMetricsDefaultAddress("host"))
require.Len(t, ls5, 1)
listener5 := ls5[0]
assert.Equal(t, "127.0.0.1:20245", listener5.Addr().String())
ls6, err := metrics.CreateMetricsListener(&listeners, []string{metrics.GetMetricsDefaultAddress("host")})
require.NoError(t, err)
listener6, err := metrics.CreateMetricsListener(&listeners, metrics.GetMetricsDefaultAddress("host"))
require.Len(t, ls6, 1)
listener6 := ls6[0]
addresses := [5]string{"127.0.0.1:20241", "127.0.0.1:20242", "127.0.0.1:20243", "127.0.0.1:20244", "127.0.0.1:20245"}
assert.NotContains(t, addresses, listener6.Addr().String())
ls7, err := metrics.CreateMetricsListener(&listeners, []string{"localhost:12345"})
require.NoError(t, err)
listener7, err := metrics.CreateMetricsListener(&listeners, "localhost:12345")
require.Len(t, ls7, 1)
listener7 := ls7[0]
assert.Equal(t, "127.0.0.1:12345", listener7.Addr().String())
require.NoError(t, err)
err = listener1.Close()
require.NoError(t, err)
err = listener2.Close()
@ -49,4 +70,14 @@ func TestMetricsListenerCreation(t *testing.T) {
require.NoError(t, err)
err = listener7.Close()
require.NoError(t, err)
ls8, err := metrics.CreateMetricsListener(&listeners, []string{"127.0.0.1:12346", "127.0.0.1:12347"})
require.NoError(t, err)
require.Len(t, ls8, 2)
assert.Equal(t, "127.0.0.1:12346", ls8[0].Addr().String())
assert.Equal(t, "127.0.0.1:12347", ls8[1].Addr().String())
err = ls8[0].Close()
require.NoError(t, err)
err = ls8[1].Close()
require.NoError(t, err)
}