Commit 558c34a2 authored by Miek Gieben's avatar Miek Gieben Committed by GitHub

middleware/proxy: healthchecks fixes (#183)

* middleware/proxy: add spray keyword

When spray is used, the proxy will, when all backend are down, spray to
each target. When not used, default to the old defaults: max 1 failure
and no spray. These defaults are also used when forwarding queries to
another CoreDNS instance.

Update the README with the new keyword.

* typos

* Make MaxFail = 1 again

* more reversals
parent 181ad851
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/etcd/msg" "github.com/miekg/coredns/middleware/etcd/msg"
"github.com/miekg/coredns/middleware/test" "github.com/miekg/coredns/middleware/test"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
`proxy` facilitates both a basic reverse proxy and a robust load balancer. The proxy has support for `proxy` facilitates both a basic reverse proxy and a robust load balancer. The proxy has support for
multiple backends and adding custom headers. The load balancing features include multiple policies, multiple backends and adding custom headers. The load balancing features include multiple policies,
health checks, and failovers. health checks, and failovers. If all hosts fails their health check the proxy middleware will fail
back to randomly selecting a target and sending packets to it.
## Syntax ## Syntax
...@@ -24,7 +25,7 @@ proxy from to... { ...@@ -24,7 +25,7 @@ proxy from to... {
max_fails integer max_fails integer
health_check path:port [duration] health_check path:port [duration]
except ignored_names... except ignored_names...
ecs [v4 address/mask] [v6 address/mask] (TODO) spray
} }
~~~ ~~~
...@@ -35,8 +36,7 @@ proxy from to... { ...@@ -35,8 +36,7 @@ proxy from to... {
* `max_fails` is the number of failures within fail_timeout that are needed before considering a backend to be down. If 0, the backend will never be marked as down. Default is 1. * `max_fails` is the number of failures within fail_timeout that are needed before considering a backend to be down. If 0, the backend will never be marked as down. Default is 1.
* `health_check` will check path (on port) on each backend. If a backend returns a status code of 200-399, then that backend is healthy. If it doesn't, the backend is marked as unhealthy for duration and no requests are routed to it. If this option is not provided then health checks are disabled. The default duration is 10 seconds ("10s"). * `health_check` will check path (on port) on each backend. If a backend returns a status code of 200-399, then that backend is healthy. If it doesn't, the backend is marked as unhealthy for duration and no requests are routed to it. If this option is not provided then health checks are disabled. The default duration is 10 seconds ("10s").
* `ignored_names...` is a space-separated list of paths to exclude from proxying. Requests that match any of these paths will be passed thru. * `ignored_names...` is a space-separated list of paths to exclude from proxying. Requests that match any of these paths will be passed thru.
* `ecs` add EDNS0 client submit metadata to the outgoing query. This can be optionally be followed * `spray` when all backends are unhealth randomly pick one to send the traffic to (this is a failsafe).
by an IPv4 and/or IPv6 address. If none is specified the server's addresses are used.
## Policies ## Policies
......
package proxy package proxy
// function OTHER middleware might want to use to do lookup in the same // functions OTHER middleware might want to use to do lookup in the same
// style as the proxy. // style as the proxy.
import ( import (
...@@ -77,8 +77,8 @@ func (p Proxy) lookup(state middleware.State, r *dns.Msg) (*dns.Msg, error) { ...@@ -77,8 +77,8 @@ func (p Proxy) lookup(state middleware.State, r *dns.Msg) (*dns.Msg, error) {
// hosts until timeout (or until we get a nil host). // hosts until timeout (or until we get a nil host).
for time.Now().Sub(start) < tryDuration { for time.Now().Sub(start) < tryDuration {
host := upstream.Select() host := upstream.Select()
if host == nil { if host == nil {
// TODO(miek): if all HC fail, spray the targets.
return nil, errUnreachable return nil, errUnreachable
} }
......
package proxy package proxy
import ( import (
"log"
"math/rand" "math/rand"
"sync/atomic" "sync/atomic"
) )
...@@ -44,9 +45,6 @@ func (r *Random) Select(pool HostPool) *UpstreamHost { ...@@ -44,9 +45,6 @@ func (r *Random) Select(pool HostPool) *UpstreamHost {
} }
} }
} }
if randHost == nil {
return new(Spray).Select(pool)
}
return randHost return randHost
} }
...@@ -58,6 +56,7 @@ type Spray struct{} ...@@ -58,6 +56,7 @@ type Spray struct{}
func (r *Spray) Select(pool HostPool) *UpstreamHost { func (r *Spray) Select(pool HostPool) *UpstreamHost {
rnd := rand.Int() % len(pool) rnd := rand.Int() % len(pool)
randHost := pool[rnd] randHost := pool[rnd]
log.Printf("[WARNING] All hosts reported as down, spraying to target: %s", randHost.Name)
return randHost return randHost
} }
...@@ -93,9 +92,6 @@ func (r *LeastConn) Select(pool HostPool) *UpstreamHost { ...@@ -93,9 +92,6 @@ func (r *LeastConn) Select(pool HostPool) *UpstreamHost {
} }
} }
} }
if bestHost == nil {
return new(Spray).Select(pool)
}
return bestHost return bestHost
} }
...@@ -113,8 +109,5 @@ func (r *RoundRobin) Select(pool HostPool) *UpstreamHost { ...@@ -113,8 +109,5 @@ func (r *RoundRobin) Select(pool HostPool) *UpstreamHost {
for i := uint32(1); host.Down() && i < poolLen; i++ { for i := uint32(1); host.Down() && i < poolLen; i++ {
host = pool[(selection+i)%poolLen] host = pool[(selection+i)%poolLen]
} }
if host.Down() {
return new(Spray).Select(pool)
}
return host return host
} }
...@@ -23,6 +23,7 @@ type staticUpstream struct { ...@@ -23,6 +23,7 @@ type staticUpstream struct {
from string from string
Hosts HostPool Hosts HostPool
Policy Policy Policy Policy
Spray Policy
FailTimeout time.Duration FailTimeout time.Duration
MaxFails int32 MaxFails int32
...@@ -48,6 +49,7 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) { ...@@ -48,6 +49,7 @@ func NewStaticUpstreams(c parse.Dispenser) ([]Upstream, error) {
from: "", from: "",
Hosts: nil, Hosts: nil,
Policy: &Random{}, Policy: &Random{},
Spray: nil,
FailTimeout: 10 * time.Second, FailTimeout: 10 * time.Second,
MaxFails: 1, MaxFails: 1,
} }
...@@ -181,11 +183,8 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error { ...@@ -181,11 +183,8 @@ func parseBlock(c *parse.Dispenser, u *staticUpstream) error {
ignoredDomains[i] = strings.ToLower(dns.Fqdn(ignoredDomains[i])) ignoredDomains[i] = strings.ToLower(dns.Fqdn(ignoredDomains[i]))
} }
u.IgnoredSubDomains = ignoredDomains u.IgnoredSubDomains = ignoredDomains
case "ecs": case "spray":
ips := c.RemainingArgs() u.Spray = &Spray{}
if len(ips) > 0 {
}
default: default:
return c.Errf("unknown property '%s'", c.Val()) return c.Errf("unknown property '%s'", c.Val())
...@@ -228,7 +227,7 @@ func (u *staticUpstream) HealthCheckWorker(stop chan struct{}) { ...@@ -228,7 +227,7 @@ func (u *staticUpstream) HealthCheckWorker(stop chan struct{}) {
func (u *staticUpstream) Select() *UpstreamHost { func (u *staticUpstream) Select() *UpstreamHost {
pool := u.Hosts pool := u.Hosts
if len(pool) == 1 { if len(pool) == 1 {
if pool[0].Down() { if pool[0].Down() && u.Spray == nil {
return nil return nil
} }
return pool[0] return pool[0]
...@@ -241,13 +240,29 @@ func (u *staticUpstream) Select() *UpstreamHost { ...@@ -241,13 +240,29 @@ func (u *staticUpstream) Select() *UpstreamHost {
} }
} }
if allDown { if allDown {
return nil if u.Spray == nil {
return nil
}
return u.Spray.Select(pool)
} }
if u.Policy == nil { if u.Policy == nil {
return (&Random{}).Select(pool) h := (&Random{}).Select(pool)
if h == nil && u.Spray == nil {
return nil
}
return u.Spray.Select(pool)
}
h := u.Policy.Select(pool)
if h != nil {
return h
}
if u.Spray == nil {
return nil
} }
return u.Policy.Select(pool) return u.Spray.Select(pool)
} }
func (u *staticUpstream) IsAllowedPath(name string) bool { func (u *staticUpstream) IsAllowedPath(name string) bool {
......
...@@ -10,6 +10,7 @@ func TestHealthCheck(t *testing.T) { ...@@ -10,6 +10,7 @@ func TestHealthCheck(t *testing.T) {
from: "", from: "",
Hosts: testPool(), Hosts: testPool(),
Policy: &Random{}, Policy: &Random{},
Spray: nil,
FailTimeout: 10 * time.Second, FailTimeout: 10 * time.Second,
MaxFails: 1, MaxFails: 1,
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment