From fcf0ad913da318fa30af435f0ad8cabba0922314 Mon Sep 17 00:00:00 2001 From: Jason Piper Date: Fri, 4 Feb 2022 16:35:40 +0000 Subject: [PATCH] prometheus metrics: add option to specify listen address In the situation that you have multiple interfaces/IP addresses, we want to be able to specify which one we want to expose the prometheus metrics on. --- docs/user-guide.md | 1 + pkg/cmd/kube-router.go | 10 ++++++++++ pkg/metrics/metrics_controller.go | 4 +++- pkg/options/options.go | 2 ++ pkg/utils/utils.go | 13 +++++++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/user-guide.md b/docs/user-guide.md index 4abe45003c..f66144fe32 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -102,6 +102,7 @@ Usage of kube-router: --loadbalancer-sync-period duration The delay between checking for missed services (e.g. '5s', '1m'). Must be greater than 0. (default 1m0s) --masquerade-all SNAT all traffic to cluster IP/node port. --master string The address of the Kubernetes API server (overrides any value in kubeconfig). + --metrics-addr string Prometheus metrics address to listen on, (Default: all interfaces) --metrics-path string Prometheus metrics path (default "/metrics") --metrics-port uint16 Prometheus metrics port, (Default 0, Disabled) --nodeport-bindon-all-ip For service of NodePort type create IPVS service that listens on all IP's of the node. diff --git a/pkg/cmd/kube-router.go b/pkg/cmd/kube-router.go index f3f79b2693..577e781c51 100644 --- a/pkg/cmd/kube-router.go +++ b/pkg/cmd/kube-router.go @@ -16,6 +16,7 @@ import ( "github.com/cloudnativelabs/kube-router/v2/pkg/healthcheck" "github.com/cloudnativelabs/kube-router/v2/pkg/metrics" "github.com/cloudnativelabs/kube-router/v2/pkg/options" + "github.com/cloudnativelabs/kube-router/v2/pkg/utils" "github.com/cloudnativelabs/kube-router/v2/pkg/version" "k8s.io/klog/v2" @@ -115,6 +116,15 @@ func (kr *KubeRouter) Run() error { go hc.RunCheck(healthChan, stopCh, &wg) if kr.Config.MetricsPort > 0 && kr.Config.MetricsPort < 65535 { + + // Verify the metrics address/port combo provided is listenable + if err := utils.TCPAddressBindable(kr.Config.MetricsAddr, kr.Config.MetricsPort); err != nil { + return fmt.Errorf("failed to listen on %s:%d for metrics: %w", + kr.Config.MetricsAddr, + int(kr.Config.MetricsPort), + err) + } + kr.Config.MetricsEnabled = true mc, err := metrics.NewMetricsController(kr.Config) if err != nil { diff --git a/pkg/metrics/metrics_controller.go b/pkg/metrics/metrics_controller.go index cf53d9a3f9..1710658858 100644 --- a/pkg/metrics/metrics_controller.go +++ b/pkg/metrics/metrics_controller.go @@ -214,6 +214,7 @@ var ( // Controller Holds settings for the metrics controller type Controller struct { MetricsPath string + MetricsAddr string MetricsPort uint16 } @@ -236,7 +237,7 @@ func (mc *Controller) Run(healthChan chan<- *healthcheck.ControllerHeartbeat, st DefaultRegisterer.MustRegister(ControllerIpvsMetricsExportTime) srv := &http.Server{ - Addr: ":" + strconv.Itoa(int(mc.MetricsPort)), + Addr: mc.MetricsAddr + ":" + strconv.Itoa(int(mc.MetricsPort)), Handler: http.DefaultServeMux, ReadHeaderTimeout: 5 * time.Second} @@ -267,6 +268,7 @@ func (mc *Controller) Run(healthChan chan<- *healthcheck.ControllerHeartbeat, st // NewMetricsController returns new MetricController object func NewMetricsController(config *options.KubeRouterConfig) (*Controller, error) { mc := Controller{} + mc.MetricsAddr = config.MetricsAddr mc.MetricsPath = config.MetricsPath mc.MetricsPort = config.MetricsPort return &mc, nil diff --git a/pkg/options/options.go b/pkg/options/options.go index a13a3ce6b3..2a872d8ff5 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -60,6 +60,7 @@ type KubeRouterConfig struct { MetricsEnabled bool MetricsPath string MetricsPort uint16 + MetricsAddr string NodePortBindOnAllIP bool NodePortRange string OverlayType string @@ -190,6 +191,7 @@ func (s *KubeRouterConfig) AddFlags(fs *pflag.FlagSet) { "The address of the Kubernetes API server (overrides any value in kubeconfig).") fs.StringVar(&s.MetricsPath, "metrics-path", "/metrics", "Prometheus metrics path") fs.Uint16Var(&s.MetricsPort, "metrics-port", 0, "Prometheus metrics port, (Default 0, Disabled)") + fs.StringVar(&s.MetricsAddr, "metrics-addr", "", "Prometheus metrics address to listen on, (Default: all interfaces)") fs.BoolVar(&s.NodePortBindOnAllIP, "nodeport-bindon-all-ip", false, "For service of NodePort type create IPVS service that listens on all IP's of the node.") fs.BoolVar(&s.FullMeshMode, "nodes-full-mesh", true, diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index afb3126417..490b2fc295 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,8 +1,10 @@ package utils import ( + "fmt" "io" "net" + "strconv" "sync" ) @@ -86,3 +88,14 @@ func SliceContainsString(needle string, haystack []string) bool { } return false } + +// TCPAddressBindable checks to see if an IP/port is bindable by attempting to open a listener then closing it +// returns nil if successful +func TCPAddressBindable(addr string, port uint16) error { + endpoint := addr + ":" + strconv.Itoa(int(port)) + ln, err := net.Listen("tcp", endpoint) + if err != nil { + return fmt.Errorf("unable to open %s: %w", endpoint, err) + } + return ln.Close() +}