diff --git a/pkg/config/config.go b/pkg/config/config.go index 08e0801..60f52b0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -14,10 +14,12 @@ import ( ) type Config struct { - BindAddress string `json:"bind-address"` - LogFormat string `json:"log-format"` - LogLevel string `json:"log-level"` - MetricsBindAddress string `json:"metrics-bind-address"` + BindAddress string `json:"bind-address"` + LogFormat string `json:"log-format"` + LogLevel string `json:"log-level"` + MetricsBindAddress string `json:"metrics-bind-address"` + ShutdownGracefulPeriod time.Duration `json:"shutdown-graceful-period"` + ShutdownWaitBeforePeriod time.Duration `json:"shutdown-wait-before-period"` AutoLogin bool `json:"auto-login"` AutoLoginIgnorePaths []string `json:"auto-login-ignore-paths"` @@ -63,10 +65,12 @@ const ( ) const ( - BindAddress = "bind-address" - LogFormat = "log-format" - LogLevel = "log-level" - MetricsBindAddress = "metrics-bind-address" + BindAddress = "bind-address" + LogFormat = "log-format" + LogLevel = "log-level" + MetricsBindAddress = "metrics-bind-address" + ShutdownGracefulPeriod = "shutdown-graceful-period" + ShutdownWaitBeforePeriod = "shutdown-wait-before-period" AutoLogin = "auto-login" AutoLoginIgnorePaths = "auto-login-ignore-paths" @@ -97,6 +101,8 @@ func Initialize() (*Config, error) { flag.String(LogFormat, "json", "Log format, either 'json' or 'text'.") flag.String(LogLevel, "info", "Logging verbosity level.") flag.String(MetricsBindAddress, "127.0.0.1:3001", "Listen address for metrics only.") + flag.Duration(ShutdownGracefulPeriod, 30*time.Second, "Graceful shutdown period when receiving a shutdown signal after which the server is forcibly exited.") + flag.Duration(ShutdownWaitBeforePeriod, 0*time.Second, "Wait period when receiving a shutdown signal before actually starting a graceful shutdown. Useful for allowing propagation of Endpoint updates in Kubernetes.") flag.Bool(AutoLogin, false, "Automatically redirect all HTTP GET requests to login if the user does not have a valid session for all matching upstream paths.") flag.StringSlice(AutoLoginIgnorePaths, []string{}, "Comma separated list of absolute paths to ignore when 'auto-login' is enabled. Supports basic wildcard matching with glob-style asterisks. Invalid patterns are ignored.") @@ -222,6 +228,10 @@ func (c *Config) Validate() error { return fmt.Errorf("%q must be set when %q is set (was %q)", UpstreamPort, UpstreamIP, c.UpstreamIP) } + if c.ShutdownGracefulPeriod <= c.ShutdownWaitBeforePeriod { + return fmt.Errorf("%q must be greater than %q", ShutdownGracefulPeriod, ShutdownWaitBeforePeriod) + } + return nil } diff --git a/pkg/server/server.go b/pkg/server/server.go index 6e826a1..390e626 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -44,17 +44,22 @@ func Start(cfg *config.Config, r chi.Router) error { sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) go func() { - <-sig + s := <-sig + log.Infof("server: received %q; waiting for %s before starting graceful shutdown...", s, cfg.ShutdownWaitBeforePeriod) + time.Sleep(cfg.ShutdownWaitBeforePeriod) - shutdownCtx, shutdownStopCtx := context.WithTimeout(serverCtx, 20*time.Second) + // the total terminationGracePeriodSeconds in Kubernetes starts immediately when SIGTERM is sent, so we need to subtract the wait-before period to exit before SIGKILL + shutdownTimeout := cfg.ShutdownGracefulPeriod - cfg.ShutdownWaitBeforePeriod + shutdownCtx, shutdownStopCtx := context.WithTimeout(serverCtx, shutdownTimeout) go func() { <-shutdownCtx.Done() if shutdownCtx.Err() == context.DeadlineExceeded { - log.Fatal("graceful shutdown timed out.. forcing exit.") + log.Fatalf("server: graceful shutdown timed out after %s; forcing exit.", shutdownTimeout) } }() + log.Infof("server: starting graceful shutdown (will timeout after %s)...", shutdownTimeout) err := server.Shutdown(shutdownCtx) if err != nil { log.Fatal(err) @@ -69,5 +74,6 @@ func Start(cfg *config.Config, r chi.Router) error { } <-serverCtx.Done() + log.Infof("server: shutdown completed") return nil }