Skip to content

Commit 4960dce

Browse files
csweichelroboquat
authored andcommitted
[baseserver] Enforce common standards services
specifically debug, health and readiness
1 parent 7685ee0 commit 4960dce

File tree

13 files changed

+135
-145
lines changed

13 files changed

+135
-145
lines changed

components/common-go/baseserver/config.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ type Configuration struct {
99
}
1010

1111
type ServicesConfiguration struct {
12-
Debug *ServerConfiguration `json:"debug,omitempty" yaml:"debug,omitempty"`
13-
GRPC *ServerConfiguration `json:"grpc,omitempty" yaml:"grpc,omitempty"`
14-
HTTP *ServerConfiguration `json:"http,omitempty" yaml:"http,omitempty"`
12+
GRPC *ServerConfiguration `json:"grpc,omitempty" yaml:"grpc,omitempty"`
13+
HTTP *ServerConfiguration `json:"http,omitempty" yaml:"http,omitempty"`
1514
}
1615

1716
type ServerConfiguration struct {

components/common-go/baseserver/options.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type options struct {
2424
// closeTimeout is the amount we allow for the server to shut down cleanly
2525
closeTimeout time.Duration
2626

27+
noBuiltinServices bool
28+
underTest bool
29+
2730
// metricsRegistry configures the metrics registry to use for exporting metrics. When not set, the default prometheus registry is used.
2831
metricsRegistry *prometheus.Registry
2932

@@ -39,9 +42,6 @@ func defaultOptions() *options {
3942
Services: ServicesConfiguration{
4043
GRPC: nil, // disabled by default
4144
HTTP: nil, // disabled by default
42-
Debug: &ServerConfiguration{
43-
Address: "localhost:9500",
44-
},
4545
},
4646
},
4747

@@ -62,6 +62,20 @@ func WithConfig(config *Configuration) Option {
6262
}
6363
}
6464

65+
func WithoutBuiltinServices() Option {
66+
return func(opts *options) error {
67+
opts.noBuiltinServices = true
68+
return nil
69+
}
70+
}
71+
72+
func WithUnderTest() Option {
73+
return func(opts *options) error {
74+
opts.underTest = true
75+
return nil
76+
}
77+
}
78+
6579
// WithHTTP configures and enables the HTTP server.
6680
func WithHTTP(addr string, tls *TLSConfiguration) Option {
6781
return func(opts *options) error {
@@ -84,17 +98,6 @@ func WithGRPC(addr string, tls *TLSConfiguration) Option {
8498
}
8599
}
86100

87-
// WithDebug configures and enables the debug server.
88-
func WithDebug(addr string, tls *TLSConfiguration) Option {
89-
return func(opts *options) error {
90-
opts.config.Services.Debug = &ServerConfiguration{
91-
Address: addr,
92-
TLS: tls,
93-
}
94-
return nil
95-
}
96-
}
97-
98101
func WithLogger(logger *logrus.Entry) Option {
99102
return func(opts *options) error {
100103
if logger == nil {

components/common-go/baseserver/options_test.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@ func TestOptions(t *testing.T) {
2121
registry := prometheus.NewRegistry()
2222
health := healthcheck.NewHandler()
2323
grpcHealthService := &grpc_health_v1.UnimplementedHealthServer{}
24-
debugCfg := ServerConfiguration{Address: "localhost:9500"}
2524
httpCfg := ServerConfiguration{Address: "localhost:8080"}
2625
grpcCfg := ServerConfiguration{Address: "localhost:8081"}
2726

2827
var opts = []Option{
29-
WithDebug(debugCfg.Address, debugCfg.TLS),
3028
WithHTTP(httpCfg.Address, httpCfg.TLS),
3129
WithGRPC(grpcCfg.Address, grpcCfg.TLS),
3230
WithLogger(logger),
@@ -42,9 +40,8 @@ func TestOptions(t *testing.T) {
4240
logger: logger,
4341
config: &Configuration{
4442
Services: ServicesConfiguration{
45-
Debug: &debugCfg,
46-
GRPC: &grpcCfg,
47-
HTTP: &httpCfg,
43+
GRPC: &grpcCfg,
44+
HTTP: &httpCfg,
4845
},
4946
},
5047
closeTimeout: timeout,

components/common-go/baseserver/server.go

Lines changed: 98 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/prometheus/client_golang/prometheus"
2323
"github.com/prometheus/client_golang/prometheus/promhttp"
2424
"github.com/sirupsen/logrus"
25+
"golang.org/x/sync/errgroup"
2526
"google.golang.org/grpc"
2627
"google.golang.org/grpc/credentials"
2728
"google.golang.org/grpc/health/grpc_health_v1"
@@ -37,11 +38,7 @@ func New(name string, opts ...Option) (*Server, error) {
3738
Name: name,
3839
options: options,
3940
}
40-
41-
err = server.initializeDebug()
42-
if err != nil {
43-
return nil, fmt.Errorf("failed to initialize debug server: %w", err)
44-
}
41+
server.builtinServices = newBuiltinServices(server)
4542

4643
server.httpMux = http.NewServeMux()
4744
server.http = &http.Server{Handler: server.httpMux}
@@ -76,9 +73,7 @@ type Server struct {
7673

7774
options *options
7875

79-
// debug is an HTTP server for debug endpoints - metrics, pprof, readiness & liveness.
80-
debug *http.Server
81-
debugListener net.Listener
76+
builtinServices *builtinServices
8277

8378
// http is an http Server, only used when port is specified in cfg
8479
http *http.Server
@@ -114,21 +109,13 @@ func (s *Server) ListenAndServe() error {
114109
}
115110
}()
116111

117-
if srv := s.options.config.Services.Debug; srv != nil {
118-
s.debugListener, err = net.Listen("tcp", srv.Address)
112+
go func() {
113+
err := s.builtinServices.ListenAndServe()
119114
if err != nil {
120-
return fmt.Errorf("failed to start debug server: %w", err)
115+
s.Logger().WithError(err).Errorf("builtin services encountered an error - closing remaining servers.")
116+
s.Close()
121117
}
122-
s.debug.Addr = srv.Address
123-
124-
go func() {
125-
err := serveHTTP(srv, s.debug, s.debugListener)
126-
if err != nil {
127-
s.Logger().WithError(err).Errorf("debug server encountered an error - closing remaining servers.")
128-
s.Close()
129-
}
130-
}()
131-
}
118+
}()
132119

133120
if srv := s.options.config.Services.HTTP; srv != nil {
134121
s.httpListener, err = net.Listen("tcp", srv.Address)
@@ -229,16 +216,12 @@ func (s *Server) close(ctx context.Context) error {
229216
s.Logger().Info("HTTP server terminated.")
230217
}
231218

232-
// Always terminate debug server last, we want to keep it running for as long as possible
233-
if s.debug != nil {
234-
err := s.debug.Shutdown(ctx)
235-
if err != nil {
236-
return fmt.Errorf("failed to close debug server: %w", err)
237-
}
238-
// s.http.Shutdown() also closes the underlying net.Listener, we just release the reference.
239-
s.debugListener = nil
240-
s.Logger().Info("Debug server terminated.")
219+
// Always terminate builtin server last, we want to keep it running for as long as possible
220+
err := s.builtinServices.Close()
221+
if err != nil {
222+
return fmt.Errorf("failed to close debug server: %w", err)
241223
}
224+
s.Logger().Info("Debug server terminated.")
242225

243226
return nil
244227
}
@@ -253,30 +236,19 @@ func (s *Server) isClosing() bool {
253236
}
254237
}
255238

256-
func (s *Server) initializeDebug() error {
257-
logger := s.Logger().WithField("protocol", "debug")
258-
239+
func (s *Server) healthEndpoint() http.Handler {
259240
mux := http.NewServeMux()
260-
261241
mux.HandleFunc("/ready", s.options.healthHandler.ReadyEndpoint)
262-
logger.Debug("Serving readiness handler on /ready")
263-
264242
mux.HandleFunc("/live", s.options.healthHandler.LiveEndpoint)
265-
logger.Debug("Serving liveliness handler on /live")
243+
return mux
244+
}
266245

246+
func (s *Server) metricsEndpoint() http.Handler {
247+
mux := http.NewServeMux()
267248
mux.Handle("/metrics", promhttp.InstrumentMetricHandler(
268249
s.options.metricsRegistry, promhttp.HandlerFor(s.options.metricsRegistry, promhttp.HandlerOpts{}),
269250
))
270-
s.Logger().WithField("protocol", "http").Debug("Serving metrics on /metrics")
271-
272-
mux.Handle(pprof.Path, pprof.Handler())
273-
logger.Debug("Serving profiler on /debug/pprof")
274-
275-
s.debug = &http.Server{
276-
Handler: mux,
277-
}
278-
279-
return nil
251+
return mux
280252
}
281253

282254
func (s *Server) initializeGRPC() error {
@@ -331,12 +303,87 @@ func httpAddress(cfg *ServerConfiguration, l net.Listener) string {
331303
}
332304

333305
func (s *Server) DebugAddress() string {
334-
return httpAddress(s.options.config.Services.Debug, s.debugListener)
306+
if s.builtinServices == nil {
307+
return ""
308+
}
309+
return "http://" + s.builtinServices.Debug.Addr
310+
}
311+
func (s *Server) HealthAddr() string {
312+
if s.builtinServices == nil {
313+
return ""
314+
}
315+
return "http://" + s.builtinServices.Health.Addr
335316
}
336317
func (s *Server) HTTPAddress() string {
337318
return httpAddress(s.options.config.Services.HTTP, s.httpListener)
338319
}
339-
func (s *Server) ReadinessAddress() string {
340-
return s.DebugAddress()
341-
}
342320
func (s *Server) GRPCAddress() string { return s.options.config.Services.GRPC.GetAddress() }
321+
322+
const (
323+
BuiltinDebugPort = 6060
324+
BuiltinMetricsPort = 9500
325+
BuiltinHealthPort = 9501
326+
)
327+
328+
type builtinServices struct {
329+
underTest bool
330+
331+
Debug *http.Server
332+
Health *http.Server
333+
Metrics *http.Server
334+
}
335+
336+
func newBuiltinServices(server *Server) *builtinServices {
337+
healthAddr := fmt.Sprintf(":%d", BuiltinHealthPort)
338+
if server.options.underTest {
339+
healthAddr = "localhost:0"
340+
}
341+
342+
return &builtinServices{
343+
underTest: server.options.underTest,
344+
Debug: &http.Server{
345+
Addr: fmt.Sprintf("localhost:%d", BuiltinDebugPort),
346+
Handler: pprof.Handler(),
347+
},
348+
Health: &http.Server{
349+
Addr: healthAddr,
350+
Handler: server.healthEndpoint(),
351+
},
352+
Metrics: &http.Server{
353+
Addr: fmt.Sprintf("localhost:%d", BuiltinMetricsPort),
354+
Handler: server.metricsEndpoint(),
355+
},
356+
}
357+
}
358+
359+
func (s *builtinServices) ListenAndServe() error {
360+
if s == nil {
361+
return nil
362+
}
363+
364+
var eg errgroup.Group
365+
if !s.underTest {
366+
eg.Go(func() error { return s.Debug.ListenAndServe() })
367+
eg.Go(func() error { return s.Metrics.ListenAndServe() })
368+
}
369+
eg.Go(func() error {
370+
// health is the only service which has a variable address,
371+
// because we need the health service to figure out if the
372+
// server started at all
373+
l, err := net.Listen("tcp", s.Health.Addr)
374+
if err != nil {
375+
return err
376+
}
377+
s.Health.Addr = l.Addr().String()
378+
return s.Health.Serve(l)
379+
})
380+
return eg.Wait()
381+
}
382+
383+
func (s *builtinServices) Close() error {
384+
var eg errgroup.Group
385+
eg.Go(func() error { return s.Debug.Close() })
386+
eg.Go(func() error { return s.Metrics.Close() })
387+
eg.Go(func() error { return s.Health.Close() })
388+
return eg.Wait()
389+
}

0 commit comments

Comments
 (0)