diff --git a/docs/configuration/ping.md b/docs/configuration/ping.md index 98d50a71f..e6b99bfe1 100644 --- a/docs/configuration/ping.md +++ b/docs/configuration/ping.md @@ -85,3 +85,7 @@ Note the dedicated port `:8082` for `/ping`. In the above example, it is _very_ important to create a named dedicated entry point, and do **not** include it in `defaultEntryPoints`. Otherwise, you are likely to expose _all_ services via this entry point. + +### Using ping for external Load-balancer rotation health check + +If you are running traefik behind a external Load-balancer, and want to configure rotation health check on the Load-balancer to take a traefik instance out of rotation gracefully, you can configure [lifecycle.requestAcceptGraceTimeout](/configuration/commons.md#life-cycle) and the ping endpoint will return `503` response on traefik server termination, so that the Load-balancer can take the terminating traefik instance out of rotation, before it stops responding. diff --git a/integration/basic_test.go b/integration/basic_test.go index a468c0eec..357fc44a1 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -128,6 +128,10 @@ func (s *SimpleSuite) TestRequestAcceptGraceTimeout(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/service", 3*time.Second, try.StatusCodeIs(http.StatusOK)) c.Assert(err, checker.IsNil) + // Check that /ping endpoint is responding with 200. + err = try.GetRequest("http://127.0.0.1:8001/ping", 3*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + // Send SIGTERM to Traefik. proc, err := os.FindProcess(cmd.Process.Pid) c.Assert(err, checker.IsNil) @@ -143,6 +147,12 @@ func (s *SimpleSuite) TestRequestAcceptGraceTimeout(c *check.C) { defer resp.Body.Close() c.Assert(resp.StatusCode, checker.Equals, http.StatusOK) + // ping endpoint should now return a Service Unavailable. + resp, err = http.Get("http://127.0.0.1:8001/ping") + c.Assert(err, checker.IsNil) + defer resp.Body.Close() + c.Assert(resp.StatusCode, checker.Equals, http.StatusServiceUnavailable) + // Expect Traefik to shut down gracefully once the request accepting grace // period has elapsed. waitErr := make(chan error) diff --git a/integration/fixtures/reqacceptgrace.toml b/integration/fixtures/reqacceptgrace.toml index f7937a3fc..81333c38f 100644 --- a/integration/fixtures/reqacceptgrace.toml +++ b/integration/fixtures/reqacceptgrace.toml @@ -6,6 +6,9 @@ logLevel = "DEBUG" [entryPoints.http] address = ":8000" + [entryPoints.traefik] + address = ":8001" + [lifeCycle] requestAcceptGraceTimeout = "10s" @@ -20,3 +23,5 @@ logLevel = "DEBUG" backend = "backend" [frontends.frontend.routes.service] rule = "Path:/service" + +[ping] diff --git a/ping/ping.go b/ping/ping.go index 6e37052c1..7f5c51e8b 100644 --- a/ping/ping.go +++ b/ping/ping.go @@ -3,19 +3,38 @@ package ping import ( "fmt" "net/http" + "sync" "github.com/containous/mux" ) -//Handler expose ping routes +// Handler expose ping routes type Handler struct { - EntryPoint string `description:"Ping entryPoint" export:"true"` + EntryPoint string `description:"Ping entryPoint" export:"true"` + terminating bool + lock sync.RWMutex +} + +// SetTerminating causes the ping endpoint to serve non 200 responses. +func (g *Handler) SetTerminating() { + g.lock.Lock() + defer g.lock.Unlock() + + g.terminating = true } // AddRoutes add ping routes on a router -func (g Handler) AddRoutes(router *mux.Router) { +func (g *Handler) AddRoutes(router *mux.Router) { router.Methods(http.MethodGet, http.MethodHead).Path("/ping"). HandlerFunc(func(response http.ResponseWriter, request *http.Request) { - fmt.Fprint(response, "OK") + g.lock.RLock() + defer g.lock.RUnlock() + + statusCode := http.StatusOK + if g.terminating { + statusCode = http.StatusServiceUnavailable + } + response.WriteHeader(statusCode) + fmt.Fprint(response, http.StatusText(statusCode)) }) } diff --git a/server/server.go b/server/server.go index 78ca7a83f..92cff0a25 100644 --- a/server/server.go +++ b/server/server.go @@ -212,6 +212,9 @@ func (s *Server) StartWithContext(ctx context.Context) { <-ctx.Done() log.Info("I have to go...") reqAcceptGraceTimeOut := time.Duration(s.globalConfiguration.LifeCycle.RequestAcceptGraceTimeout) + if s.globalConfiguration.Ping != nil && reqAcceptGraceTimeOut > 0 { + s.globalConfiguration.Ping.SetTerminating() + } if reqAcceptGraceTimeOut > 0 { log.Infof("Waiting %s for incoming requests to cease", reqAcceptGraceTimeOut) time.Sleep(reqAcceptGraceTimeOut)