Skip to content

Commit 17e7232

Browse files
hypnoglowkevindelgado
authored andcommitted
Expose metrics http server for extra endpoints
This allows users to register extra http endpoints on the http server that serves metrics.
1 parent 9580cc3 commit 17e7232

File tree

3 files changed

+84
-4
lines changed

3 files changed

+84
-4
lines changed

pkg/manager/internal.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const (
5050

5151
defaultReadinessEndpoint = "/readyz"
5252
defaultLivenessEndpoint = "/healthz"
53+
defaultMetricsEndpoint = "/metrics"
5354
)
5455

5556
var log = logf.RuntimeLog.WithName("manager")
@@ -95,6 +96,9 @@ type controllerManager struct {
9596
// metricsListener is used to serve prometheus metrics
9697
metricsListener net.Listener
9798

99+
// metricsExtraHandlers contains extra handlers to register on http server that serves metrics.
100+
metricsExtraHandlers map[string]http.Handler
101+
98102
// healthProbeListener is used to serve liveness probe
99103
healthProbeListener net.Listener
100104

@@ -260,6 +264,25 @@ func (cm *controllerManager) SetFields(i interface{}) error {
260264
return nil
261265
}
262266

267+
// AddMetricsExtraHandler adds extra handler served on path to the http server that serves metrics.
268+
func (cm *controllerManager) AddMetricsExtraHandler(path string, handler http.Handler) error {
269+
if path == defaultMetricsEndpoint {
270+
return fmt.Errorf("overriding builtin %s endpoint is not allowed", defaultMetricsEndpoint)
271+
}
272+
273+
cm.mu.Lock()
274+
defer cm.mu.Unlock()
275+
276+
_, found := cm.metricsExtraHandlers[path]
277+
if found {
278+
return fmt.Errorf("can't register extra handler by duplicate path %q on metrics http server", path)
279+
}
280+
281+
cm.metricsExtraHandlers[path] = handler
282+
log.V(2).Info("Registering metrics http server extra handler", "path", path)
283+
return nil
284+
}
285+
263286
// AddHealthzCheck allows you to add Healthz checker
264287
func (cm *controllerManager) AddHealthzCheck(name string, check healthz.Checker) error {
265288
cm.mu.Lock()
@@ -341,19 +364,28 @@ func (cm *controllerManager) GetWebhookServer() *webhook.Server {
341364
}
342365

343366
func (cm *controllerManager) serveMetrics(stop <-chan struct{}) {
344-
var metricsPath = "/metrics"
345367
handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{
346368
ErrorHandling: promhttp.HTTPErrorOnError,
347369
})
348370
// TODO(JoelSpeed): Use existing Kubernetes machinery for serving metrics
349371
mux := http.NewServeMux()
350-
mux.Handle(metricsPath, handler)
372+
mux.Handle(defaultMetricsEndpoint, handler)
373+
374+
func() {
375+
cm.mu.Lock()
376+
defer cm.mu.Unlock()
377+
378+
for path, extraHandler := range cm.metricsExtraHandlers {
379+
mux.Handle(path, extraHandler)
380+
}
381+
}()
382+
351383
server := http.Server{
352384
Handler: mux,
353385
}
354386
// Run the server
355387
go func() {
356-
log.Info("starting metrics server", "path", metricsPath)
388+
log.Info("starting metrics server", "path", defaultMetricsEndpoint)
357389
if err := server.Serve(cm.metricsListener); err != nil && err != http.ErrServerClosed {
358390
cm.errSignal.SignalError(err)
359391
}
@@ -367,6 +399,8 @@ func (cm *controllerManager) serveMetrics(stop <-chan struct{}) {
367399
}
368400

369401
func (cm *controllerManager) serveHealthProbes(stop <-chan struct{}) {
402+
// TODO(hypnoglow): refactor locking to use anonymous func in the similar way
403+
// it's done in serveMetrics.
370404
cm.mu.Lock()
371405
mux := http.NewServeMux()
372406

pkg/manager/manager.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package manager
1919
import (
2020
"fmt"
2121
"net"
22+
"net/http"
2223
"time"
2324

2425
"github.com/go-logr/logr"
@@ -54,6 +55,13 @@ type Manager interface {
5455
// interface - e.g. inject.Client.
5556
SetFields(interface{}) error
5657

58+
// AddMetricsExtraHandler adds an extra handler served on path to the http server that serves metrics.
59+
// Might be useful to register some diagnostic endpoints e.g. pprof. Note that these endpoints meant to be
60+
// sensitive and shouldn't be exposed publicly.
61+
// If the simple path -> handler mapping offered here is not enough, a new http server/listener should be added as
62+
// Runnable to the manager via Add method.
63+
AddMetricsExtraHandler(path string, handler http.Handler) error
64+
5765
// AddHealthzCheck allows you to add Healthz checker
5866
AddHealthzCheck(name string, check healthz.Checker) error
5967

@@ -291,6 +299,9 @@ func New(config *rest.Config, options Options) (Manager, error) {
291299
return nil, err
292300
}
293301

302+
// By default we have no extra endpoints to expose on metrics http server.
303+
metricsExtraHandlers := make(map[string]http.Handler)
304+
294305
// Create health probes listener. This will throw an error if the bind
295306
// address is invalid or already in use.
296307
healthProbeListener, err := options.newHealthProbeListener(options.HealthProbeBindAddress)
@@ -311,6 +322,7 @@ func New(config *rest.Config, options Options) (Manager, error) {
311322
resourceLock: resourceLock,
312323
mapper: mapper,
313324
metricsListener: metricsListener,
325+
metricsExtraHandlers: metricsExtraHandlers,
314326
internalStop: stop,
315327
internalStopper: stop,
316328
port: options.Port,

pkg/manager/manager_test.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ var _ = Describe("manger.Manager", func() {
412412
Expect(resp.StatusCode).To(Equal(200))
413413
})
414414

415-
It("should not serve anything other than metrics endpoint", func(done Done) {
415+
It("should not serve anything other than metrics endpoint by default", func(done Done) {
416416
opts.MetricsBindAddress = ":0"
417417
m, err := New(cfg, opts)
418418
Expect(err).NotTo(HaveOccurred())
@@ -469,6 +469,40 @@ var _ = Describe("manger.Manager", func() {
469469
ok := metrics.Registry.Unregister(one)
470470
Expect(ok).To(BeTrue())
471471
})
472+
473+
It("should serve extra endpoints", func(done Done) {
474+
opts.MetricsBindAddress = ":0"
475+
m, err := New(cfg, opts)
476+
Expect(err).NotTo(HaveOccurred())
477+
478+
err = m.AddMetricsExtraHandler("/debug", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
479+
_, _ = w.Write([]byte("Some debug info"))
480+
}))
481+
Expect(err).NotTo(HaveOccurred())
482+
483+
// Should error when we add another extra endpoint on the already registered path.
484+
err = m.AddMetricsExtraHandler("/debug", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
485+
_, _ = w.Write([]byte("Another debug info"))
486+
}))
487+
Expect(err).To(HaveOccurred())
488+
489+
s := make(chan struct{})
490+
defer close(s)
491+
go func() {
492+
defer GinkgoRecover()
493+
Expect(m.Start(s)).NotTo(HaveOccurred())
494+
close(done)
495+
}()
496+
497+
endpoint := fmt.Sprintf("http://%s/debug", listener.Addr().String())
498+
resp, err := http.Get(endpoint)
499+
Expect(err).NotTo(HaveOccurred())
500+
Expect(resp.StatusCode).To(Equal(http.StatusOK))
501+
502+
body, err := ioutil.ReadAll(resp.Body)
503+
Expect(err).NotTo(HaveOccurred())
504+
Expect(string(body)).To(Equal("Some debug info"))
505+
})
472506
})
473507
})
474508

0 commit comments

Comments
 (0)