Skip to content

Graceful shutdown for Nginx #1257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions controllers/nginx/pkg/cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,15 @@ import (
"time"

"github.com/golang/glog"
"k8s.io/ingress/core/pkg/ingress/controller"
)

func main() {
// start a new nginx controller
ngx := newNGINXController()
// create a custom Ingress controller using NGINX as backend
ic := controller.NewIngressController(ngx)
go handleSigterm(ic)

go handleSigterm(ngx)
// start the controller
ic.Start()
ngx.Start()
// wait
glog.Infof("shutting down Ingress controller...")
for {
Expand All @@ -42,14 +40,14 @@ func main() {
}
}

func handleSigterm(ic *controller.GenericController) {
func handleSigterm(ngx *NGINXController) {
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGTERM)
<-signalChan
glog.Infof("Received SIGTERM, shutting down")

exitCode := 0
if err := ic.Stop(); err != nil {
if err := ngx.Stop(); err != nil {
glog.Infof("Error during shutdown %v", err)
exitCode = 1
}
Expand Down
81 changes: 74 additions & 7 deletions controllers/nginx/pkg/cmd/controller/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"time"

"github.com/golang/glog"
"github.com/mitchellh/go-ps"
"github.com/spf13/pflag"

proxyproto "github.com/armon/go-proxyproto"
Expand All @@ -43,6 +44,7 @@ import (
ngx_template "k8s.io/ingress/controllers/nginx/pkg/template"
"k8s.io/ingress/controllers/nginx/pkg/version"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/controller"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/net/dns"
"k8s.io/ingress/core/pkg/net/ssl"
Expand Down Expand Up @@ -71,7 +73,7 @@ var (
// newNGINXController creates a new NGINX Ingress controller.
// If the environment variable NGINX_BINARY exists it will be used
// as source for nginx commands
func newNGINXController() ingress.Controller {
func newNGINXController() *NGINXController {
ngx := os.Getenv("NGINX_BINARY")
if ngx == "" {
ngx = binary
Expand Down Expand Up @@ -132,14 +134,13 @@ Error loading new template : %v

n.t = ngxTpl

go n.Start()

return ingress.Controller(n)
return n
}

// NGINXController ...
type NGINXController struct {
t *ngx_template.Template
controller *controller.GenericController
t *ngx_template.Template

configmap *api_v1.ConfigMap

Expand All @@ -161,6 +162,8 @@ type NGINXController struct {

isSSLPassthroughEnabled bool

isShuttingDown bool

proxy *proxy

ports *config.ListenPorts
Expand All @@ -170,10 +173,22 @@ type NGINXController struct {

// Start start a new NGINX master process running in foreground.
func (n *NGINXController) Start() {
glog.Info("starting NGINX process...")
n.isShuttingDown = false

n.controller = controller.NewIngressController(n)
go n.controller.Start()

done := make(chan error, 1)
cmd := exec.Command(n.binary, "-c", cfgPath)

// put nginx in another process group to prevent it
// to receive signals meant for the controller
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pgid: 0,
}

glog.Info("starting NGINX process...")
n.start(cmd, done)

// if the nginx master process dies the workers continue to process requests,
Expand All @@ -183,6 +198,11 @@ func (n *NGINXController) Start() {
// To avoid this issue we restart nginx in case of errors.
for {
err := <-done

if n.isShuttingDown {
break
}

if exitError, ok := err.(*exec.ExitError); ok {
waitStatus := exitError.Sys().(syscall.WaitStatus)
glog.Warningf(`
Expand All @@ -202,11 +222,34 @@ NGINX master process died (%v): %v
conn.Close()
time.Sleep(1 * time.Second)
}
// start a new nginx master process
// restart a new nginx master process if the controller
// is not being stopped
n.start(cmd, done)
}
}

// Stop gracefully stops the NGINX master process.
func (n *NGINXController) Stop() error {
n.isShuttingDown = true
n.controller.Stop()

// Send stop signal to Nginx
glog.Info("stopping NGINX process...")
cmd := exec.Command(n.binary, "-c", cfgPath, "-s", "quit")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return err
}

// Wait for the Nginx process disappear
waitForNginxShutdown()
glog.Info("NGINX process has stopped")

return nil
}

func (n *NGINXController) start(cmd *exec.Cmd, done chan error) {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Expand Down Expand Up @@ -716,3 +759,27 @@ func isIPv6Enabled() bool {
cmd := exec.Command("test", "-f", "/proc/net/if_inet6")
return cmd.Run() == nil
}

// isNginxRunning returns true if a process with the name 'nginx' is found
func isNginxProcessPresent() bool {
processes, _ := ps.Processes()
for _, p := range processes {
if p.Executable() == "nginx" {
return true
}
}
return false
}

func waitForNginxShutdown() {
timer := time.NewTicker(time.Second * 1)
defer timer.Stop()
for {
select {
case <-timer.C:
if !isNginxProcessPresent() {
return
}
}
}
}