mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-31 06:03:06 +02:00
Improved healthcheck, idlewatcher support for loadbalanced routes, bug fixes
This commit is contained in:
@@ -16,32 +16,36 @@ import (
|
||||
|
||||
type (
|
||||
ReverseProxyEntry struct { // real model after validation
|
||||
Alias T.Alias `json:"alias"`
|
||||
Scheme T.Scheme `json:"scheme"`
|
||||
URL net.URL `json:"url"`
|
||||
Raw *types.RawEntry `json:"raw"`
|
||||
|
||||
Alias T.Alias `json:"alias,omitempty"`
|
||||
Scheme T.Scheme `json:"scheme,omitempty"`
|
||||
URL net.URL `json:"url,omitempty"`
|
||||
NoTLSVerify bool `json:"no_tls_verify,omitempty"`
|
||||
PathPatterns T.PathPatterns `json:"path_patterns"`
|
||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck"`
|
||||
PathPatterns T.PathPatterns `json:"path_patterns,omitempty"`
|
||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
||||
LoadBalance *loadbalancer.Config `json:"load_balance,omitempty"`
|
||||
Middlewares D.NestedLabelMap `json:"middlewares,omitempty"`
|
||||
|
||||
/* Docker only */
|
||||
IdleTimeout time.Duration `json:"idle_timeout"`
|
||||
WakeTimeout time.Duration `json:"wake_timeout"`
|
||||
StopMethod T.StopMethod `json:"stop_method"`
|
||||
StopTimeout int `json:"stop_timeout"`
|
||||
IdleTimeout time.Duration `json:"idle_timeout,omitempty"`
|
||||
WakeTimeout time.Duration `json:"wake_timeout,omitempty"`
|
||||
StopMethod T.StopMethod `json:"stop_method,omitempty"`
|
||||
StopTimeout int `json:"stop_timeout,omitempty"`
|
||||
StopSignal T.Signal `json:"stop_signal,omitempty"`
|
||||
DockerHost string `json:"docker_host"`
|
||||
ContainerName string `json:"container_name"`
|
||||
ContainerID string `json:"container_id"`
|
||||
ContainerRunning bool `json:"container_running"`
|
||||
DockerHost string `json:"docker_host,omitempty"`
|
||||
ContainerName string `json:"container_name,omitempty"`
|
||||
ContainerID string `json:"container_id,omitempty"`
|
||||
ContainerRunning bool `json:"container_running,omitempty"`
|
||||
}
|
||||
StreamEntry struct {
|
||||
Alias T.Alias `json:"alias"`
|
||||
Scheme T.StreamScheme `json:"scheme"`
|
||||
Host T.Host `json:"host"`
|
||||
Port T.StreamPort `json:"port"`
|
||||
Healthcheck *health.HealthCheckConfig `json:"healthcheck"`
|
||||
Raw *types.RawEntry `json:"raw"`
|
||||
|
||||
Alias T.Alias `json:"alias,omitempty"`
|
||||
Scheme T.StreamScheme `json:"scheme,omitempty"`
|
||||
Host T.Host `json:"host,omitempty"`
|
||||
Port T.StreamPort `json:"port,omitempty"`
|
||||
Healthcheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -88,6 +92,10 @@ func ValidateEntry(m *types.RawEntry) (any, E.NestedError) {
|
||||
|
||||
func validateRPEntry(m *types.RawEntry, s T.Scheme, b E.Builder) *ReverseProxyEntry {
|
||||
var stopTimeOut time.Duration
|
||||
cont := m.Container
|
||||
if cont == nil {
|
||||
cont = D.DummyContainer
|
||||
}
|
||||
|
||||
host, err := T.ValidateHost(m.Host)
|
||||
b.Add(err)
|
||||
@@ -101,21 +109,21 @@ func validateRPEntry(m *types.RawEntry, s T.Scheme, b E.Builder) *ReverseProxyEn
|
||||
url, err := E.Check(url.Parse(fmt.Sprintf("%s://%s:%d", s, host, port)))
|
||||
b.Add(err)
|
||||
|
||||
idleTimeout, err := T.ValidateDurationPostitive(m.IdleTimeout)
|
||||
idleTimeout, err := T.ValidateDurationPostitive(cont.IdleTimeout)
|
||||
b.Add(err)
|
||||
|
||||
wakeTimeout, err := T.ValidateDurationPostitive(m.WakeTimeout)
|
||||
wakeTimeout, err := T.ValidateDurationPostitive(cont.WakeTimeout)
|
||||
b.Add(err)
|
||||
|
||||
stopMethod, err := T.ValidateStopMethod(m.StopMethod)
|
||||
stopMethod, err := T.ValidateStopMethod(cont.StopMethod)
|
||||
b.Add(err)
|
||||
|
||||
if stopMethod == T.StopMethodStop {
|
||||
stopTimeOut, err = T.ValidateDurationPostitive(m.StopTimeout)
|
||||
stopTimeOut, err = T.ValidateDurationPostitive(cont.StopTimeout)
|
||||
b.Add(err)
|
||||
}
|
||||
|
||||
stopSignal, err := T.ValidateSignal(m.StopSignal)
|
||||
stopSignal, err := T.ValidateSignal(cont.StopSignal)
|
||||
b.Add(err)
|
||||
|
||||
if err != nil {
|
||||
@@ -123,6 +131,7 @@ func validateRPEntry(m *types.RawEntry, s T.Scheme, b E.Builder) *ReverseProxyEn
|
||||
}
|
||||
|
||||
return &ReverseProxyEntry{
|
||||
Raw: m,
|
||||
Alias: T.NewAlias(m.Alias),
|
||||
Scheme: s,
|
||||
URL: net.NewURL(url),
|
||||
@@ -136,10 +145,10 @@ func validateRPEntry(m *types.RawEntry, s T.Scheme, b E.Builder) *ReverseProxyEn
|
||||
StopMethod: stopMethod,
|
||||
StopTimeout: int(stopTimeOut.Seconds()), // docker api takes integer seconds for timeout argument
|
||||
StopSignal: stopSignal,
|
||||
DockerHost: m.DockerHost,
|
||||
ContainerName: m.ContainerName,
|
||||
ContainerID: m.ContainerID,
|
||||
ContainerRunning: m.Running,
|
||||
DockerHost: cont.DockerHost,
|
||||
ContainerName: cont.ContainerName,
|
||||
ContainerID: cont.ContainerID,
|
||||
ContainerRunning: cont.Running,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +167,7 @@ func validateStreamEntry(m *types.RawEntry, b E.Builder) *StreamEntry {
|
||||
}
|
||||
|
||||
return &StreamEntry{
|
||||
Raw: m,
|
||||
Alias: T.NewAlias(m.Alias),
|
||||
Scheme: *scheme,
|
||||
Host: host,
|
||||
|
||||
@@ -32,7 +32,7 @@ func ValidateStreamScheme(s string) (ss *StreamScheme, err E.NestedError) {
|
||||
}
|
||||
|
||||
func (s StreamScheme) String() string {
|
||||
return fmt.Sprintf("%s:%s", s.ListeningScheme, s.ProxyScheme)
|
||||
return fmt.Sprintf("%s -> %s", s.ListeningScheme, s.ProxyScheme)
|
||||
}
|
||||
|
||||
// IsCoherent checks if the ListeningScheme and ProxyScheme of the StreamScheme are equal.
|
||||
|
||||
@@ -72,7 +72,7 @@ func (p *DockerProvider) LoadRoutesImpl() (routes R.Routes, err E.NestedError) {
|
||||
}
|
||||
|
||||
entries.RangeAll(func(_ string, e *types.RawEntry) {
|
||||
e.DockerHost = p.dockerHost
|
||||
e.Container.DockerHost = p.dockerHost
|
||||
})
|
||||
|
||||
routes, err = R.FromEntries(entries)
|
||||
@@ -88,7 +88,7 @@ func (p *DockerProvider) shouldIgnore(container *D.Container) bool {
|
||||
strings.HasSuffix(container.ContainerName, "-old")
|
||||
}
|
||||
|
||||
func (p *DockerProvider) OnEvent(event W.Event, routes R.Routes) (res EventResult) {
|
||||
func (p *DockerProvider) OnEvent(event W.Event, oldRoutes R.Routes) (res EventResult) {
|
||||
switch event.Action {
|
||||
case events.ActionContainerStart, events.ActionContainerStop:
|
||||
break
|
||||
@@ -98,75 +98,66 @@ func (p *DockerProvider) OnEvent(event W.Event, routes R.Routes) (res EventResul
|
||||
b := E.NewBuilder("event %s error", event)
|
||||
defer b.To(&res.err)
|
||||
|
||||
routes.RangeAll(func(k string, v *R.Route) {
|
||||
if v.Entry.ContainerID == event.ActorID ||
|
||||
v.Entry.ContainerName == event.ActorName {
|
||||
matches := R.NewRoutes()
|
||||
oldRoutes.RangeAllParallel(func(k string, v *R.Route) {
|
||||
if v.Entry.Container.ContainerID == event.ActorID ||
|
||||
v.Entry.Container.ContainerName == event.ActorName {
|
||||
matches.Store(k, v)
|
||||
}
|
||||
})
|
||||
|
||||
var newRoutes R.Routes
|
||||
var err E.NestedError
|
||||
|
||||
if matches.Size() == 0 { // id & container name changed
|
||||
matches = oldRoutes
|
||||
newRoutes, err = p.LoadRoutesImpl()
|
||||
b.Add(err)
|
||||
} else {
|
||||
cont, err := D.Inspect(p.dockerHost, event.ActorID)
|
||||
if err != nil {
|
||||
b.Add(E.FailWith("inspect container", err))
|
||||
return
|
||||
}
|
||||
|
||||
if p.shouldIgnore(cont) {
|
||||
// stop all old routes
|
||||
matches.RangeAllParallel(func(_ string, v *R.Route) {
|
||||
b.Add(v.Stop())
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := p.entriesFromContainerLabels(cont)
|
||||
b.Add(err)
|
||||
newRoutes, err = R.FromEntries(entries)
|
||||
b.Add(err)
|
||||
}
|
||||
|
||||
matches.RangeAll(func(k string, v *R.Route) {
|
||||
if !newRoutes.Has(k) && !oldRoutes.Has(k) {
|
||||
b.Add(v.Stop())
|
||||
routes.Delete(k)
|
||||
matches.Delete(k)
|
||||
res.nRemoved++
|
||||
}
|
||||
})
|
||||
|
||||
if res.nRemoved == 0 { // id & container name changed
|
||||
// load all routes (rescan)
|
||||
routesNew, err := p.LoadRoutesImpl()
|
||||
routesOld := routes
|
||||
if routesNew.Size() == 0 {
|
||||
b.Add(E.FailWith("rescan routes", err))
|
||||
return
|
||||
}
|
||||
routesNew.Range(func(k string, v *R.Route) bool {
|
||||
if !routesOld.Has(k) {
|
||||
routesOld.Store(k, v)
|
||||
b.Add(v.Start())
|
||||
res.nAdded++
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
routesOld.Range(func(k string, v *R.Route) bool {
|
||||
if !routesNew.Has(k) {
|
||||
b.Add(v.Stop())
|
||||
routesOld.Delete(k)
|
||||
res.nRemoved++
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
client, err := D.ConnectClient(p.dockerHost)
|
||||
if err != nil {
|
||||
b.Add(E.FailWith("connect to docker", err))
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
cont, err := client.Inspect(event.ActorID)
|
||||
if err != nil {
|
||||
b.Add(E.FailWith("inspect container", err))
|
||||
return
|
||||
}
|
||||
|
||||
if p.shouldIgnore(cont) {
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := p.entriesFromContainerLabels(cont)
|
||||
b.Add(err)
|
||||
|
||||
entries.RangeAll(func(alias string, entry *types.RawEntry) {
|
||||
if routes.Has(alias) {
|
||||
b.Add(E.Duplicated("alias", alias))
|
||||
} else {
|
||||
if route, err := R.NewRoute(entry); err != nil {
|
||||
newRoutes.RangeAll(func(alias string, newRoute *R.Route) {
|
||||
oldRoute, exists := oldRoutes.Load(alias)
|
||||
if exists {
|
||||
if err := oldRoute.Stop(); err != nil {
|
||||
b.Add(err)
|
||||
} else {
|
||||
routes.Store(alias, route)
|
||||
b.Add(route.Start())
|
||||
res.nAdded++
|
||||
}
|
||||
}
|
||||
oldRoutes.Store(alias, newRoute)
|
||||
if err := newRoute.Start(); err != nil {
|
||||
b.Add(err)
|
||||
}
|
||||
if exists {
|
||||
res.nReloaded++
|
||||
} else {
|
||||
res.nAdded++
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
@@ -88,20 +88,20 @@ func TestApplyLabelWildcard(t *testing.T) {
|
||||
ExpectDeepEqual(t, a.Middlewares, middlewaresExpect)
|
||||
ExpectEqual(t, len(b.Middlewares), 0)
|
||||
|
||||
ExpectEqual(t, a.IdleTimeout, common.IdleTimeoutDefault)
|
||||
ExpectEqual(t, b.IdleTimeout, common.IdleTimeoutDefault)
|
||||
ExpectEqual(t, a.Container.IdleTimeout, common.IdleTimeoutDefault)
|
||||
ExpectEqual(t, b.Container.IdleTimeout, common.IdleTimeoutDefault)
|
||||
|
||||
ExpectEqual(t, a.StopTimeout, common.StopTimeoutDefault)
|
||||
ExpectEqual(t, b.StopTimeout, common.StopTimeoutDefault)
|
||||
ExpectEqual(t, a.Container.StopTimeout, common.StopTimeoutDefault)
|
||||
ExpectEqual(t, b.Container.StopTimeout, common.StopTimeoutDefault)
|
||||
|
||||
ExpectEqual(t, a.StopMethod, common.StopMethodDefault)
|
||||
ExpectEqual(t, b.StopMethod, common.StopMethodDefault)
|
||||
ExpectEqual(t, a.Container.StopMethod, common.StopMethodDefault)
|
||||
ExpectEqual(t, b.Container.StopMethod, common.StopMethodDefault)
|
||||
|
||||
ExpectEqual(t, a.WakeTimeout, common.WakeTimeoutDefault)
|
||||
ExpectEqual(t, b.WakeTimeout, common.WakeTimeoutDefault)
|
||||
ExpectEqual(t, a.Container.WakeTimeout, common.WakeTimeoutDefault)
|
||||
ExpectEqual(t, b.Container.WakeTimeout, common.WakeTimeoutDefault)
|
||||
|
||||
ExpectEqual(t, a.StopSignal, "SIGTERM")
|
||||
ExpectEqual(t, b.StopSignal, "SIGTERM")
|
||||
ExpectEqual(t, a.Container.StopSignal, "SIGTERM")
|
||||
ExpectEqual(t, b.Container.StopSignal, "SIGTERM")
|
||||
}
|
||||
|
||||
func TestApplyLabelWithAlias(t *testing.T) {
|
||||
@@ -186,16 +186,16 @@ func TestPublicIPLocalhost(t *testing.T) {
|
||||
c := D.FromDocker(&types.Container{Names: dummyNames}, client.DefaultDockerHost)
|
||||
raw, ok := Must(p.entriesFromContainerLabels(c)).Load("a")
|
||||
ExpectTrue(t, ok)
|
||||
ExpectEqual(t, raw.PublicIP, "127.0.0.1")
|
||||
ExpectEqual(t, raw.Host, raw.PublicIP)
|
||||
ExpectEqual(t, raw.Container.PublicIP, "127.0.0.1")
|
||||
ExpectEqual(t, raw.Host, raw.Container.PublicIP)
|
||||
}
|
||||
|
||||
func TestPublicIPRemote(t *testing.T) {
|
||||
c := D.FromDocker(&types.Container{Names: dummyNames}, "tcp://1.2.3.4:2375")
|
||||
raw, ok := Must(p.entriesFromContainerLabels(c)).Load("a")
|
||||
ExpectTrue(t, ok)
|
||||
ExpectEqual(t, raw.PublicIP, "1.2.3.4")
|
||||
ExpectEqual(t, raw.Host, raw.PublicIP)
|
||||
ExpectEqual(t, raw.Container.PublicIP, "1.2.3.4")
|
||||
ExpectEqual(t, raw.Host, raw.Container.PublicIP)
|
||||
}
|
||||
|
||||
func TestPrivateIPLocalhost(t *testing.T) {
|
||||
@@ -211,8 +211,8 @@ func TestPrivateIPLocalhost(t *testing.T) {
|
||||
}, client.DefaultDockerHost)
|
||||
raw, ok := Must(p.entriesFromContainerLabels(c)).Load("a")
|
||||
ExpectTrue(t, ok)
|
||||
ExpectEqual(t, raw.PrivateIP, "172.17.0.123")
|
||||
ExpectEqual(t, raw.Host, raw.PrivateIP)
|
||||
ExpectEqual(t, raw.Container.PrivateIP, "172.17.0.123")
|
||||
ExpectEqual(t, raw.Host, raw.Container.PrivateIP)
|
||||
}
|
||||
|
||||
func TestPrivateIPRemote(t *testing.T) {
|
||||
@@ -228,9 +228,9 @@ func TestPrivateIPRemote(t *testing.T) {
|
||||
}, "tcp://1.2.3.4:2375")
|
||||
raw, ok := Must(p.entriesFromContainerLabels(c)).Load("a")
|
||||
ExpectTrue(t, ok)
|
||||
ExpectEqual(t, raw.PrivateIP, "")
|
||||
ExpectEqual(t, raw.PublicIP, "1.2.3.4")
|
||||
ExpectEqual(t, raw.Host, raw.PublicIP)
|
||||
ExpectEqual(t, raw.Container.PrivateIP, "")
|
||||
ExpectEqual(t, raw.Container.PublicIP, "1.2.3.4")
|
||||
ExpectEqual(t, raw.Host, raw.Container.PublicIP)
|
||||
}
|
||||
|
||||
func TestStreamDefaultValues(t *testing.T) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
R "github.com/yusing/go-proxy/internal/route"
|
||||
W "github.com/yusing/go-proxy/internal/watcher"
|
||||
@@ -19,7 +20,7 @@ type (
|
||||
routes R.Routes
|
||||
|
||||
watcher W.Watcher
|
||||
watcherCtx context.Context
|
||||
watcherTask common.Task
|
||||
watcherCancel context.CancelFunc
|
||||
|
||||
l *logrus.Entry
|
||||
@@ -38,9 +39,10 @@ type (
|
||||
Type ProviderType `json:"type"`
|
||||
}
|
||||
EventResult struct {
|
||||
nRemoved int
|
||||
nAdded int
|
||||
err E.NestedError
|
||||
nAdded int
|
||||
nRemoved int
|
||||
nReloaded int
|
||||
err E.NestedError
|
||||
}
|
||||
)
|
||||
|
||||
@@ -129,6 +131,7 @@ func (p *Provider) StopAllRoutes() (res E.NestedError) {
|
||||
p.routes.RangeAllParallel(func(alias string, r *R.Route) {
|
||||
errors.Add(r.Stop().Subject(r))
|
||||
})
|
||||
p.routes.Clear()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -175,27 +178,21 @@ func (p *Provider) Statistics() ProviderStats {
|
||||
}
|
||||
|
||||
func (p *Provider) watchEvents() {
|
||||
p.watcherCtx, p.watcherCancel = context.WithCancel(context.Background())
|
||||
events, errs := p.watcher.Events(p.watcherCtx)
|
||||
p.watcherTask, p.watcherCancel = common.NewTaskWithCancel("Watcher for provider %s", p.name)
|
||||
defer p.watcherTask.Finished()
|
||||
|
||||
events, errs := p.watcher.Events(p.watcherTask.Context())
|
||||
l := p.l.WithField("module", "watcher")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-p.watcherCtx.Done():
|
||||
case <-p.watcherTask.Context().Done():
|
||||
return
|
||||
case event := <-events:
|
||||
res := p.OnEvent(event, p.routes)
|
||||
l.Infof("%s event %q", event.Type, event)
|
||||
if res.nAdded > 0 || res.nRemoved > 0 {
|
||||
n := res.nAdded - res.nRemoved
|
||||
switch {
|
||||
case n == 0:
|
||||
l.Infof("%d route(s) reloaded", res.nAdded)
|
||||
case n > 0:
|
||||
l.Infof("%d route(s) added", n)
|
||||
default:
|
||||
l.Infof("%d route(s) removed", -n)
|
||||
}
|
||||
if res.nAdded+res.nRemoved+res.nReloaded > 0 {
|
||||
l.Infof("%s event %q", event.Type, event)
|
||||
l.Infof("| %d NEW | %d REMOVED | %d RELOADED |", res.nAdded, res.nRemoved, res.nReloaded)
|
||||
}
|
||||
if res.err != nil {
|
||||
l.Error(res.err)
|
||||
|
||||
Reference in New Issue
Block a user