From ae25bb43f39a6110bc737e09dcf1dac6991e8eae Mon Sep 17 00:00:00 2001 From: Botond Szirtes <158265557+bszirtes@users.noreply.github.com> Date: Mon, 19 Aug 2024 11:30:36 +0200 Subject: [PATCH] Add an option to expose Prometheus metrics via http/s server (#1653) Signed-off-by: Botond Szirtes --- go.mod | 2 +- pkg/tools/prometheus/prometheus.go | 35 +++++++++ pkg/tools/prometheus/server.go | 109 +++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 pkg/tools/prometheus/prometheus.go create mode 100644 pkg/tools/prometheus/server.go diff --git a/go.mod b/go.mod index 966773319..c31f44d92 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/networkservicemesh/api v1.13.4-0.20240815101554-fdbfcd84fd0e github.com/open-policy-agent/opa v0.44.0 github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.17.0 github.com/r3labs/diff v1.1.0 github.com/sirupsen/logrus v1.9.0 github.com/spiffe/go-spiffe/v2 v2.1.7 @@ -85,7 +86,6 @@ require ( github.com/nats-io/nuid v1.0.1 // indirect github.com/openzipkin/zipkin-go v0.4.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect diff --git a/pkg/tools/prometheus/prometheus.go b/pkg/tools/prometheus/prometheus.go new file mode 100644 index 000000000..926ca166a --- /dev/null +++ b/pkg/tools/prometheus/prometheus.go @@ -0,0 +1,35 @@ +// Copyright (c) 2024 Nordix Foundation. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package prometheus provides a set of utilities for assisting with Prometheus data +package prometheus + +import ( + "os" + "strconv" +) + +const ( + prometheusEnv = "PROMETHEUS" +) + +// IsEnabled returns true if prometheus is enabled +func IsEnabled() bool { + if v, err := strconv.ParseBool(os.Getenv(prometheusEnv)); err == nil { + return v + } + return false +} diff --git a/pkg/tools/prometheus/server.go b/pkg/tools/prometheus/server.go new file mode 100644 index 000000000..fea266076 --- /dev/null +++ b/pkg/tools/prometheus/server.go @@ -0,0 +1,109 @@ +// Copyright (c) 2024 Nordix Foundation. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package prometheus provides a set of utilities for assisting with Prometheus data +package prometheus + +import ( + "context" + "crypto/tls" + "net/http" + "time" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" + "github.com/spiffe/go-spiffe/v2/workloadapi" + + "github.com/networkservicemesh/sdk/pkg/tools/log" +) + +// ListenAndServe gathers the certificate and initiates the server to begin handling incoming requests +func ListenAndServe(ctx context.Context, listenOn string, headerTimeout time.Duration, cancel context.CancelFunc) { + metricsServer := server{ + ListenOn: listenOn, + HeaderTimeout: headerTimeout, + } + + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + } + source, err := workloadapi.NewX509Source(ctx) + if err != nil { + log.FromContext(ctx).Fatalf("error getting x509 source: %v", err.Error()) + } + tlsConfig.GetCertificate = tlsconfig.GetCertificate(source) + metricsServer.TLSConfig = tlsConfig + + select { + case <-ctx.Done(): + err = source.Close() + log.FromContext(ctx).Errorf("unable to close x509 source: %v", err.Error()) + default: + } + + go func() { + err := metricsServer.start(ctx) + if err != nil { + log.FromContext(ctx).Error(err.Error()) + cancel() + } + }() +} + +type server struct { + TLSConfig *tls.Config + ListenOn string + HeaderTimeout time.Duration +} + +func (s *server) start(ctx context.Context) error { + log.FromContext(ctx).Info("Start metrics server on ", s.ListenOn) + + server := &http.Server{ + Addr: s.ListenOn, + TLSConfig: s.TLSConfig, + ReadHeaderTimeout: s.HeaderTimeout, + } + + http.Handle("/metrics", promhttp.Handler()) + + serverCtx, cancel := context.WithCancel(ctx) + var ListenAndServeErr error + + go func() { + ListenAndServeErr = server.ListenAndServeTLS("", "") + if ListenAndServeErr != nil { + cancel() + } + }() + + <-serverCtx.Done() + + if ListenAndServeErr != nil { + return errors.Errorf("failed to ListenAndServe on metrics server: %s", ListenAndServeErr) + } + + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 1*time.Second) + defer shutdownCancel() + + err := server.Shutdown(shutdownCtx) + if err != nil { + return errors.Errorf("failed to shutdown metrics server: %s", err) + } + + return nil +}