From f243c8877978cf3d328c536b36cdf0ef5ca08a37 Mon Sep 17 00:00:00 2001 From: Steffen Butzer Date: Fri, 25 Jan 2019 00:34:42 +0100 Subject: [PATCH] windows/service: implement graceful shutdown when run as windows service - Fixes https://github.com/kubernetes/kubernetes/issues/72900 The issue here originally is that os.Exit() is called which exits the process too early (before svc.Execute updates the status to stopped). This is picked up as service error and leads to restarting, if restart-on-fail is configured for the windows service. svc.Execute already guarantees that the application is exited after, so that os.Exit call would be unnecessary. This rework also adds graceful shutdown, which also resolves the underlying root cause. The graceful shutdown is not guaranteed to succeed, since the service controller can decide to kill the service any time after exceeding a shutdown timeout. --- pkg/windows/service/service.go | 19 ++++++++++++++----- .../src/k8s.io/apiserver/pkg/server/signal.go | 16 ++++++++++++---- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pkg/windows/service/service.go b/pkg/windows/service/service.go index a5bffa1822e..28b531ae77c 100644 --- a/pkg/windows/service/service.go +++ b/pkg/windows/service/service.go @@ -19,8 +19,7 @@ limitations under the License. package service import ( - "os" - + "k8s.io/apiserver/pkg/server" "k8s.io/klog" "golang.org/x/sys/windows" @@ -80,9 +79,19 @@ Loop: case svc.Interrogate: s <- c.CurrentStatus case svc.Stop, svc.Shutdown: - s <- svc.Status{State: svc.Stopped} - // TODO: Stop the kubelet gracefully instead of killing the process - os.Exit(0) + klog.Infof("Service stopping") + // We need to translate this request into a signal that can be handled by the the signal handler + // handling shutdowns normally (currently apiserver/pkg/server/signal.go). + // If we do not do this, our main threads won't be notified of the upcoming shutdown. + // Since Windows services do not use any console, we cannot simply generate a CTRL_BREAK_EVENT + // but need a dedicated notification mechanism. + server.RequestShutdown() + + // Free up the control handler and let us terminate as gracefully as possible. + // If that takes too long, the service controller will kill the remaining threads. + // As per https://docs.microsoft.com/en-us/windows/desktop/services/service-control-handler-function + s <- svc.Status{State: svc.StopPending} + break Loop } } } diff --git a/staging/src/k8s.io/apiserver/pkg/server/signal.go b/staging/src/k8s.io/apiserver/pkg/server/signal.go index 6f0cff4baed..6f3edbba470 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/signal.go +++ b/staging/src/k8s.io/apiserver/pkg/server/signal.go @@ -22,6 +22,7 @@ import ( ) var onlyOneSignalHandler = make(chan struct{}) +var shutdownHandler = make(chan os.Signal, 2) // SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned // which is closed on one of these signals. If a second signal is caught, the program @@ -30,14 +31,21 @@ func SetupSignalHandler() <-chan struct{} { close(onlyOneSignalHandler) // panics when called twice stop := make(chan struct{}) - c := make(chan os.Signal, 2) - signal.Notify(c, shutdownSignals...) + signal.Notify(shutdownHandler, shutdownSignals...) go func() { - <-c + <-shutdownHandler close(stop) - <-c + <-shutdownHandler os.Exit(1) // second signal. Exit directly. }() return stop } + +// RequestShutdown emulates a received event that is considered as shutdown signal (SIGTERM/SIGINT) +func RequestShutdown() { + select { + case shutdownHandler <- shutdownSignals[0]: + default: + } +}