@@ -32,6 +32,7 @@ import (
32
32
"contrib.go.opencensus.io/exporter/prometheus"
33
33
"contrib.go.opencensus.io/exporter/stackdriver"
34
34
"github.com/GoogleCloudPlatform/alloydb-auth-proxy/alloydb"
35
+ "github.com/GoogleCloudPlatform/alloydb-auth-proxy/internal/healthcheck"
35
36
"github.com/GoogleCloudPlatform/alloydb-auth-proxy/internal/log"
36
37
"github.com/GoogleCloudPlatform/alloydb-auth-proxy/internal/proxy"
37
38
"github.com/spf13/cobra"
@@ -88,6 +89,7 @@ type Command struct {
88
89
telemetryProject string
89
90
telemetryPrefix string
90
91
prometheusNamespace string
92
+ healthCheck bool
91
93
httpPort string
92
94
}
93
95
@@ -186,6 +188,10 @@ the maximum time has passed. Defaults to 0s.`)
186
188
"Enable Prometheus for metric collection using the provided namespace" )
187
189
cmd .PersistentFlags ().StringVar (& c .httpPort , "http-port" , "9090" ,
188
190
"Port for the Prometheus server to use" )
191
+ cmd .PersistentFlags ().BoolVar (& c .healthCheck , "health-check" , false ,
192
+ `Enables HTTP endpoints /startup, /liveness, and /readiness
193
+ that report on the proxy's health. Endpoints are available on localhost
194
+ only. Uses the port specified by the http-port flag.` )
189
195
190
196
// Global and per instance flags
191
197
cmd .PersistentFlags ().StringVarP (& c .conf .Addr , "address" , "a" , "127.0.0.1" ,
@@ -241,18 +247,18 @@ func parseConfig(cmd *Command, conf *proxy.Config, args []string) error {
241
247
cmd .logger .Infof ("Using API Endpoint %v" , conf .APIEndpointURL )
242
248
}
243
249
244
- if userHasSet ("http-port" ) && ! userHasSet ("prometheus-namespace" ) {
245
- return newBadCommandError ( "cannot specify --http-port without --prometheus-namespace" )
250
+ if userHasSet ("http-port" ) && ! userHasSet ("prometheus-namespace" ) && ! userHasSet ( "health-check" ) {
251
+ cmd . logger . Infof ( "Ignoring --http-port because --prometheus-namespace or --health-check was not set " )
246
252
}
247
253
248
254
if ! userHasSet ("telemetry-project" ) && userHasSet ("telemetry-prefix" ) {
249
- cmd .logger .Infof ("Ignoring telementry-prefix as telemetry-project was not set" )
255
+ cmd .logger .Infof ("Ignoring -- telementry-prefix as -- telemetry-project was not set" )
250
256
}
251
257
if ! userHasSet ("telemetry-project" ) && userHasSet ("disable-metrics" ) {
252
- cmd .logger .Infof ("Ignoring disable-metrics as telemetry-project was not set" )
258
+ cmd .logger .Infof ("Ignoring -- disable-metrics as -- telemetry-project was not set" )
253
259
}
254
260
if ! userHasSet ("telemetry-project" ) && userHasSet ("disable-traces" ) {
255
- cmd .logger .Infof ("Ignoring disable-traces as telemetry-project was not set" )
261
+ cmd .logger .Infof ("Ignoring -- disable-traces as -- telemetry-project was not set" )
256
262
}
257
263
258
264
var ics []proxy.InstanceConnConfig
@@ -328,9 +334,8 @@ func runSignalWrapper(cmd *Command) error {
328
334
ctx , cancel := context .WithCancel (cmd .Context ())
329
335
defer cancel ()
330
336
331
- // Configure Cloud Trace and/or Cloud Monitoring based on command
332
- // invocation. If a project has not been enabled, no traces or metrics are
333
- // enabled.
337
+ // Configure collectors before the proxy has started to ensure we are
338
+ // collecting metrics before *ANY* AlloyDB Admin API calls are made.
334
339
enableMetrics := ! cmd .disableMetrics
335
340
enableTraces := ! cmd .disableTraces
336
341
if cmd .telemetryProject != "" && (enableMetrics || enableTraces ) {
@@ -358,40 +363,22 @@ func runSignalWrapper(cmd *Command) error {
358
363
}()
359
364
}
360
365
361
- shutdownCh := make (chan error )
362
-
366
+ var (
367
+ needsHTTPServer bool
368
+ mux = http .NewServeMux ()
369
+ )
363
370
if cmd .prometheusNamespace != "" {
371
+ needsHTTPServer = true
364
372
e , err := prometheus .NewExporter (prometheus.Options {
365
373
Namespace : cmd .prometheusNamespace ,
366
374
})
367
375
if err != nil {
368
376
return err
369
377
}
370
- mux := http .NewServeMux ()
371
378
mux .Handle ("/metrics" , e )
372
- addr := fmt .Sprintf ("localhost:%s" , cmd .httpPort )
373
- server := & http.Server {Addr : addr , Handler : mux }
374
- go func () {
375
- select {
376
- case <- ctx .Done ():
377
- // Give the HTTP server a second to shutdown cleanly.
378
- ctx2 , _ := context .WithTimeout (context .Background (), time .Second )
379
- if err := server .Shutdown (ctx2 ); err != nil {
380
- cmd .logger .Errorf ("failed to shutdown Prometheus HTTP server: %v\n " , err )
381
- }
382
- }
383
- }()
384
- go func () {
385
- err := server .ListenAndServe ()
386
- if err == http .ErrServerClosed {
387
- return
388
- }
389
- if err != nil {
390
- shutdownCh <- fmt .Errorf ("failed to start prometheus HTTP server: %v" , err )
391
- }
392
- }()
393
379
}
394
380
381
+ shutdownCh := make (chan error )
395
382
// watch for sigterm / sigint signals
396
383
signals := make (chan os.Signal , 1 )
397
384
signal .Notify (signals , syscall .SIGTERM , syscall .SIGINT )
@@ -429,18 +416,55 @@ func runSignalWrapper(cmd *Command) error {
429
416
cmd .logger .Errorf ("The proxy has encountered a terminal error: %v" , err )
430
417
return err
431
418
case p = <- startCh :
419
+ cmd .logger .Infof ("The proxy has started successfully and is ready for new connections!" )
432
420
}
433
- cmd .logger .Infof ("The proxy has started successfully and is ready for new connections!" )
434
- defer p .Close ()
435
421
defer func () {
436
422
if cErr := p .Close (); cErr != nil {
437
423
cmd .logger .Errorf ("error during shutdown: %v" , cErr )
438
424
}
439
425
}()
440
426
441
- go func () {
442
- shutdownCh <- p .Serve (ctx )
443
- }()
427
+ notify := func () {}
428
+ if cmd .healthCheck {
429
+ needsHTTPServer = true
430
+ hc := healthcheck .NewCheck (p , cmd .logger )
431
+ mux .HandleFunc ("/startup" , hc .HandleStartup )
432
+ mux .HandleFunc ("/readiness" , hc .HandleReadiness )
433
+ mux .HandleFunc ("/liveness" , hc .HandleLiveness )
434
+ notify = hc .NotifyStarted
435
+ }
436
+
437
+ // Start the HTTP server if anything requiring HTTP is specified.
438
+ if needsHTTPServer {
439
+ server := & http.Server {
440
+ Addr : fmt .Sprintf ("localhost:%s" , cmd .httpPort ),
441
+ Handler : mux ,
442
+ }
443
+ // Start the HTTP server.
444
+ go func () {
445
+ err := server .ListenAndServe ()
446
+ if err == http .ErrServerClosed {
447
+ return
448
+ }
449
+ if err != nil {
450
+ shutdownCh <- fmt .Errorf ("failed to start HTTP server: %v" , err )
451
+ }
452
+ }()
453
+ // Handle shutdown of the HTTP server gracefully.
454
+ go func () {
455
+ select {
456
+ case <- ctx .Done ():
457
+ // Give the HTTP server a second to shutdown cleanly.
458
+ ctx2 , cancel := context .WithTimeout (context .Background (), time .Second )
459
+ defer cancel ()
460
+ if err := server .Shutdown (ctx2 ); err != nil {
461
+ cmd .logger .Errorf ("failed to shutdown Prometheus HTTP server: %v\n " , err )
462
+ }
463
+ }
464
+ }()
465
+ }
466
+
467
+ go func () { shutdownCh <- p .Serve (ctx , notify ) }()
444
468
445
469
err := <- shutdownCh
446
470
switch {
0 commit comments