diff --git a/cluster/leadership.go b/cluster/leadership.go index af01b5290..c6ff904b8 100644 --- a/cluster/leadership.go +++ b/cluster/leadership.go @@ -2,15 +2,22 @@ package cluster import ( "context" + "net/http" "time" "github.com/cenk/backoff" + "github.com/containous/mux" "github.com/containous/traefik/log" "github.com/containous/traefik/safe" "github.com/containous/traefik/types" "github.com/docker/leadership" + "github.com/unrolled/render" ) +var templatesRenderer = render.New(render.Options{ + Directory: "nowhere", +}) + // Leadership allows leadership election using a KV store type Leadership struct { *safe.Pool @@ -98,7 +105,32 @@ func (l *Leadership) onElection(elected bool) { } } +type leaderResponse struct { + Leader bool `json:"leader"` +} + +func (l *Leadership) getLeaderHandler(response http.ResponseWriter, request *http.Request) { + leader := &leaderResponse{Leader: l.IsLeader()} + + status := http.StatusOK + if !leader.Leader { + // Set status to be `429`, as this will typically cause load balancers to stop sending requests to the instance without removing them from rotation. + status = http.StatusTooManyRequests + } + + err := templatesRenderer.JSON(response, status, leader) + if err != nil { + log.Error(err) + } +} + // IsLeader returns true if current node is leader func (l *Leadership) IsLeader() bool { return l.leader.Get().(bool) } + +// AddRoutes add dashboard routes on a router +func (l *Leadership) AddRoutes(router *mux.Router) { + // Expose cluster leader + router.Methods(http.MethodGet).Path("/api/cluster/leader").HandlerFunc(l.getLeaderHandler) +} diff --git a/docs/configuration/api.md b/docs/configuration/api.md index cdcddf939..b2db8517a 100644 --- a/docs/configuration/api.md +++ b/docs/configuration/api.md @@ -43,6 +43,7 @@ For more customization, see [entry points](/configuration/entrypoints/) document | Path | Method | Description | |-----------------------------------------------------------------|------------------|-------------------------------------------| | `/` | `GET` | Provides a simple HTML frontend of Træfik | +| `/cluster/leader` | `GET` | JSON leader true/false response | | `/health` | `GET` | JSON health metrics | | `/api` | `GET` | Configuration for all providers | | `/api/providers` | `GET` | Providers | @@ -222,6 +223,25 @@ curl -s "http://localhost:8080/api" | jq . } ``` +### Cluster Leadership + +```shell +curl -s "http://localhost:8080/cluster/leader" | jq . +``` +```shell +< HTTP/1.1 200 OK +< Content-Type: application/json; charset=UTF-8 +< Date: xxx +< Content-Length: 15 +``` +If the given node is not a cluster leader, an HTTP status of `429-Too-Many-Requests` will be returned. +```json +{ + // current leadership status of the queried node + "leader": true +} +``` + ### Health ```shell diff --git a/server/server.go b/server/server.go index f02af806e..78ca7a83f 100644 --- a/server/server.go +++ b/server/server.go @@ -739,6 +739,9 @@ func (s *Server) addInternalRoutes(entryPointName string, router *mux.Router) { if s.globalConfiguration.API != nil && s.globalConfiguration.API.EntryPoint == entryPointName { s.globalConfiguration.API.AddRoutes(router) + if s.leadership != nil { + s.leadership.AddRoutes(router) + } } }