mirror of
https://github.com/yusing/godoxy.git
synced 2025-06-07 12:02:34 +02:00
feat(healthcheck): allow health checking for excluded routes
This commit is contained in:
parent
4705989f4b
commit
9087c4f195
9 changed files with 138 additions and 57 deletions
23
internal/route/common.go
Normal file
23
internal/route/common.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkExists(r routes.Route) gperr.Error {
|
||||||
|
var (
|
||||||
|
existing routes.Route
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
switch r := r.(type) {
|
||||||
|
case routes.HTTPRoute:
|
||||||
|
existing, ok = routes.HTTP.Get(r.Key())
|
||||||
|
case routes.StreamRoute:
|
||||||
|
existing, ok = routes.Stream.Get(r.Key())
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -96,8 +96,16 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.ShouldExclude() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkExists(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
routes.HTTP.Add(s)
|
routes.HTTP.Add(s)
|
||||||
s.task.OnCancel("entrypoint_remove_route", func() {
|
s.task.OnFinished("remove_route_from_http", func() {
|
||||||
routes.HTTP.Del(s)
|
routes.HTTP.Del(s)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -71,9 +71,6 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||||
|
|
||||||
for _, c := range containers {
|
for _, c := range containers {
|
||||||
container := docker.FromDocker(&c, p.dockerHost)
|
container := docker.FromDocker(&c, p.dockerHost)
|
||||||
if container.IsExcluded {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.IsHostNetworkMode {
|
if container.IsHostNetworkMode {
|
||||||
err := container.UpdatePorts()
|
err := container.UpdatePorts()
|
||||||
|
@ -89,10 +86,15 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||||
}
|
}
|
||||||
for k, v := range newEntries {
|
for k, v := range newEntries {
|
||||||
if conflict, ok := routes[k]; ok {
|
if conflict, ok := routes[k]; ok {
|
||||||
errs.Add(gperr.Multiline().
|
err := gperr.Multiline().
|
||||||
Addf("route with alias %s already exists", k).
|
Addf("route with alias %s already exists", k).
|
||||||
Addf("container %s", container.ContainerName).
|
Addf("container %s", container.ContainerName).
|
||||||
Addf("conflicting container %s", conflict.Container.ContainerName))
|
Addf("conflicting container %s", conflict.Container.ContainerName)
|
||||||
|
if conflict.ShouldExclude() || v.ShouldExclude() {
|
||||||
|
gperr.LogWarn("skipping conflicting route", err)
|
||||||
|
} else {
|
||||||
|
errs.Add(err)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
routes[k] = v
|
routes[k] = v
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/gperr"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
"github.com/yusing/go-proxy/internal/route"
|
"github.com/yusing/go-proxy/internal/route"
|
||||||
"github.com/yusing/go-proxy/internal/route/provider/types"
|
"github.com/yusing/go-proxy/internal/route/provider/types"
|
||||||
|
"github.com/yusing/go-proxy/internal/route/routes"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
W "github.com/yusing/go-proxy/internal/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||||
|
@ -90,9 +92,17 @@ func (p *Provider) startRoute(parent task.Parent, r *route.Route) gperr.Error {
|
||||||
err := r.Start(parent)
|
err := r.Start(parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
delete(p.routes, r.Alias)
|
delete(p.routes, r.Alias)
|
||||||
|
routes.All.Del(r)
|
||||||
return err.Subject(r.Alias)
|
return err.Subject(r.Alias)
|
||||||
}
|
}
|
||||||
p.routes[r.Alias] = r
|
if conflict, added := routes.All.AddIfNotExists(r); !added {
|
||||||
|
delete(p.routes, r.Alias)
|
||||||
|
return gperr.Errorf("route %s already exists: from %s and %s", r.Alias, r.ProviderName(), conflict.ProviderName())
|
||||||
|
} else {
|
||||||
|
r.Task().OnCancel("remove_routes_from_all", func() {
|
||||||
|
routes.All.Del(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,10 +165,6 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
||||||
delete(routes, alias)
|
delete(routes, alias)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r.ShouldExclude() {
|
|
||||||
delete(routes, alias)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.FinalizeHomepageConfig()
|
r.FinalizeHomepageConfig()
|
||||||
}
|
}
|
||||||
return routes, errs.Error()
|
return routes, errs.Error()
|
||||||
|
|
|
@ -50,7 +50,7 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||||
} else {
|
} else {
|
||||||
trans = gphttp.NewTransport()
|
trans = gphttp.NewTransport()
|
||||||
if httpConfig.NoTLSVerify {
|
if httpConfig.NoTLSVerify {
|
||||||
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
|
||||||
}
|
}
|
||||||
if httpConfig.ResponseHeaderTimeout > 0 {
|
if httpConfig.ResponseHeaderTimeout > 0 {
|
||||||
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
|
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
|
||||||
|
@ -98,9 +98,6 @@ func (r *ReveseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy {
|
||||||
|
|
||||||
// Start implements task.TaskStarter.
|
// Start implements task.TaskStarter.
|
||||||
func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||||
if existing, ok := routes.HTTP.Get(r.Key()); ok && !r.UseLoadBalance() {
|
|
||||||
return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
|
||||||
}
|
|
||||||
r.task = parent.Subtask("http."+r.Name(), false)
|
r.task = parent.Subtask("http."+r.Name(), false)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
@ -139,11 +136,19 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.ShouldExclude() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkExists(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if r.UseLoadBalance() {
|
if r.UseLoadBalance() {
|
||||||
r.addToLoadBalancer(parent)
|
r.addToLoadBalancer(parent)
|
||||||
} else {
|
} else {
|
||||||
routes.HTTP.Add(r)
|
routes.HTTP.Add(r)
|
||||||
r.task.OnFinished("entrypoint_remove_route", func() {
|
r.task.OnCancel("remove_route_from_http", func() {
|
||||||
routes.HTTP.Del(r)
|
routes.HTTP.Del(r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -215,6 +216,13 @@ func (r *Route) Validate() gperr.Error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Route) Task() *task.Task {
|
||||||
|
if r.impl == nil { // should not happen
|
||||||
|
panic(errors.New("route not initialized"))
|
||||||
|
}
|
||||||
|
return r.impl.Task()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Route) Start(parent task.Parent) (err gperr.Error) {
|
func (r *Route) Start(parent task.Parent) (err gperr.Error) {
|
||||||
if r.impl == nil { // should not happen
|
if r.impl == nil { // should not happen
|
||||||
return gperr.New("route not initialized")
|
return gperr.New("route not initialized")
|
||||||
|
@ -354,10 +362,20 @@ func (r *Route) UseLoadBalance() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Route) UseIdleWatcher() bool {
|
func (r *Route) UseIdleWatcher() bool {
|
||||||
return r.Idlewatcher != nil && r.Idlewatcher.IdleTimeout > 0
|
return r.Idlewatcher != nil && r.Idlewatcher.IdleTimeout != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Route) UseHealthCheck() bool {
|
func (r *Route) UseHealthCheck() bool {
|
||||||
|
if r.Container != nil {
|
||||||
|
switch {
|
||||||
|
case r.Container.Image.Name == "godoxy-agent":
|
||||||
|
return false
|
||||||
|
case !r.Container.Running && !r.UseIdleWatcher():
|
||||||
|
return false
|
||||||
|
case strings.HasPrefix(r.Container.ContainerName, "buildx_"):
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return !r.HealthCheck.Disable
|
return !r.HealthCheck.Disable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,6 +500,12 @@ func (r *Route) FinalizeHomepageConfig() {
|
||||||
}
|
}
|
||||||
r.Homepage = r.Homepage.GetOverride(r.Alias)
|
r.Homepage = r.Homepage.GetOverride(r.Alias)
|
||||||
|
|
||||||
|
if r.ShouldExclude() && isDocker {
|
||||||
|
r.Homepage.Show = false
|
||||||
|
r.Homepage.Name = r.Container.ContainerName // still show container name in metrics page
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
hp := r.Homepage
|
hp := r.Homepage
|
||||||
refs := r.References()
|
refs := r.References()
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
|
|
|
@ -7,15 +7,16 @@ import (
|
||||||
var (
|
var (
|
||||||
HTTP = pool.New[HTTPRoute]("http_routes")
|
HTTP = pool.New[HTTPRoute]("http_routes")
|
||||||
Stream = pool.New[StreamRoute]("stream_routes")
|
Stream = pool.New[StreamRoute]("stream_routes")
|
||||||
|
// All is a pool of all routes, including HTTP, Stream routes and also excluded routes.
|
||||||
|
All = pool.New[Route]("all_routes")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
All.DisableLog()
|
||||||
|
}
|
||||||
|
|
||||||
func Iter(yield func(r Route) bool) {
|
func Iter(yield func(r Route) bool) {
|
||||||
for _, r := range HTTP.Iter {
|
for _, r := range All.Iter {
|
||||||
if !yield(r) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, r := range Stream.Iter {
|
|
||||||
if !yield(r) {
|
if !yield(r) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -23,12 +24,7 @@ func Iter(yield func(r Route) bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IterKV(yield func(alias string, r Route) bool) {
|
func IterKV(yield func(alias string, r Route) bool) {
|
||||||
for k, r := range HTTP.Iter {
|
for k, r := range All.Iter {
|
||||||
if !yield(k, r) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, r := range Stream.Iter {
|
|
||||||
if !yield(k, r) {
|
if !yield(k, r) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -36,12 +32,13 @@ func IterKV(yield func(alias string, r Route) bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NumRoutes() int {
|
func NumRoutes() int {
|
||||||
return HTTP.Size() + Stream.Size()
|
return All.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Clear() {
|
func Clear() {
|
||||||
HTTP.Clear()
|
HTTP.Clear()
|
||||||
Stream.Clear()
|
Stream.Clear()
|
||||||
|
All.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
||||||
|
@ -54,9 +51,5 @@ func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(alias string) (Route, bool) {
|
func Get(alias string) (Route, bool) {
|
||||||
r, ok := HTTP.Get(alias)
|
return All.Get(alias)
|
||||||
if ok {
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
return Stream.Get(alias)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,6 @@ func NewStreamRoute(base *Route) (routes.Route, gperr.Error) {
|
||||||
|
|
||||||
// Start implements task.TaskStarter.
|
// Start implements task.TaskStarter.
|
||||||
func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||||
if existing, ok := routes.Stream.Get(r.Key()); ok {
|
|
||||||
return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
|
||||||
}
|
|
||||||
r.task = parent.Subtask("stream."+r.Name(), true)
|
r.task = parent.Subtask("stream."+r.Name(), true)
|
||||||
r.Stream = NewStream(r)
|
r.Stream = NewStream(r)
|
||||||
|
|
||||||
|
@ -60,12 +57,13 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||||
r.HealthMon = monitor.NewMonitor(r)
|
r.HealthMon = monitor.NewMonitor(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !r.ShouldExclude() {
|
||||||
if err := r.Setup(); err != nil {
|
if err := r.Setup(); err != nil {
|
||||||
r.task.Finish(err)
|
r.task.Finish(err)
|
||||||
return gperr.Wrap(err)
|
return gperr.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.l.Info().Int("port", r.Port.Listening).Msg("listening")
|
r.l.Info().Int("port", r.Port.Listening).Msg("listening")
|
||||||
|
}
|
||||||
|
|
||||||
if r.HealthMon != nil {
|
if r.HealthMon != nil {
|
||||||
if err := r.HealthMon.Start(r.task); err != nil {
|
if err := r.HealthMon.Start(r.task); err != nil {
|
||||||
|
@ -73,10 +71,18 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.ShouldExclude() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkExists(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
go r.acceptConnections()
|
go r.acceptConnections()
|
||||||
|
|
||||||
routes.Stream.Add(r)
|
routes.Stream.Add(r)
|
||||||
r.task.OnFinished("entrypoint_remove_route", func() {
|
r.task.OnCancel("remove_route_from_stream", func() {
|
||||||
routes.Stream.Del(r)
|
routes.Stream.Del(r)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -11,6 +11,7 @@ type (
|
||||||
Pool[T Object] struct {
|
Pool[T Object] struct {
|
||||||
m *xsync.Map[string, T]
|
m *xsync.Map[string, T]
|
||||||
name string
|
name string
|
||||||
|
disableLog bool
|
||||||
}
|
}
|
||||||
Object interface {
|
Object interface {
|
||||||
Key() string
|
Key() string
|
||||||
|
@ -19,41 +20,54 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
func New[T Object](name string) Pool[T] {
|
func New[T Object](name string) Pool[T] {
|
||||||
return Pool[T]{xsync.NewMap[string, T](), name}
|
return Pool[T]{xsync.NewMap[string, T](), name, false}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Name() string {
|
func (p *Pool[T]) DisableLog() {
|
||||||
|
p.disableLog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) Name() string {
|
||||||
return p.name
|
return p.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Add(obj T) {
|
func (p *Pool[T]) Add(obj T) {
|
||||||
p.checkExists(obj.Key())
|
p.checkExists(obj.Key())
|
||||||
p.m.Store(obj.Key(), obj)
|
p.m.Store(obj.Key(), obj)
|
||||||
|
if !p.disableLog {
|
||||||
log.Info().Msgf("%s: added %s", p.name, obj.Name())
|
log.Info().Msgf("%s: added %s", p.name, obj.Name())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Del(obj T) {
|
func (p *Pool[T]) AddIfNotExists(obj T) (actual T, added bool) {
|
||||||
|
actual, loaded := p.m.LoadOrStore(obj.Key(), obj)
|
||||||
|
return actual, !loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pool[T]) Del(obj T) {
|
||||||
p.m.Delete(obj.Key())
|
p.m.Delete(obj.Key())
|
||||||
|
if !p.disableLog {
|
||||||
log.Info().Msgf("%s: removed %s", p.name, obj.Name())
|
log.Info().Msgf("%s: removed %s", p.name, obj.Name())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Get(key string) (T, bool) {
|
func (p *Pool[T]) Get(key string) (T, bool) {
|
||||||
return p.m.Load(key)
|
return p.m.Load(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Size() int {
|
func (p *Pool[T]) Size() int {
|
||||||
return p.m.Size()
|
return p.m.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Clear() {
|
func (p *Pool[T]) Clear() {
|
||||||
p.m.Clear()
|
p.m.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Iter(fn func(k string, v T) bool) {
|
func (p *Pool[T]) Iter(fn func(k string, v T) bool) {
|
||||||
p.m.Range(fn)
|
p.m.Range(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pool[T]) Slice() []T {
|
func (p *Pool[T]) Slice() []T {
|
||||||
slice := make([]T, 0, p.m.Size())
|
slice := make([]T, 0, p.m.Size())
|
||||||
for _, v := range p.m.Range {
|
for _, v := range p.m.Range {
|
||||||
slice = append(slice, v)
|
slice = append(slice, v)
|
||||||
|
|
Loading…
Add table
Reference in a new issue