mirror of
https://github.com/yusing/godoxy.git
synced 2025-06-05 19:22: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)
|
||||
s.task.OnCancel("entrypoint_remove_route", func() {
|
||||
s.task.OnFinished("remove_route_from_http", func() {
|
||||
routes.HTTP.Del(s)
|
||||
})
|
||||
return nil
|
||||
|
|
|
@ -71,9 +71,6 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
|||
|
||||
for _, c := range containers {
|
||||
container := docker.FromDocker(&c, p.dockerHost)
|
||||
if container.IsExcluded {
|
||||
continue
|
||||
}
|
||||
|
||||
if container.IsHostNetworkMode {
|
||||
err := container.UpdatePorts()
|
||||
|
@ -89,10 +86,15 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
|||
}
|
||||
for k, v := range newEntries {
|
||||
if conflict, ok := routes[k]; ok {
|
||||
errs.Add(gperr.Multiline().
|
||||
err := gperr.Multiline().
|
||||
Addf("route with alias %s already exists", k).
|
||||
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 {
|
||||
routes[k] = v
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
"github.com/yusing/go-proxy/internal/gperr"
|
||||
"github.com/yusing/go-proxy/internal/route"
|
||||
"github.com/yusing/go-proxy/internal/route/provider/types"
|
||||
"github.com/yusing/go-proxy/internal/route/routes"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
W "github.com/yusing/go-proxy/internal/watcher"
|
||||
"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)
|
||||
if err != nil {
|
||||
delete(p.routes, r.Alias)
|
||||
routes.All.Del(r)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -155,10 +165,6 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
|||
delete(routes, alias)
|
||||
continue
|
||||
}
|
||||
if r.ShouldExclude() {
|
||||
delete(routes, alias)
|
||||
continue
|
||||
}
|
||||
r.FinalizeHomepageConfig()
|
||||
}
|
||||
return routes, errs.Error()
|
||||
|
|
|
@ -50,7 +50,7 @@ func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
|||
} else {
|
||||
trans = gphttp.NewTransport()
|
||||
if httpConfig.NoTLSVerify {
|
||||
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
trans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
|
||||
}
|
||||
if httpConfig.ResponseHeaderTimeout > 0 {
|
||||
trans.ResponseHeaderTimeout = httpConfig.ResponseHeaderTimeout
|
||||
|
@ -98,9 +98,6 @@ func (r *ReveseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy {
|
|||
|
||||
// Start implements task.TaskStarter.
|
||||
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)
|
||||
|
||||
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() {
|
||||
r.addToLoadBalancer(parent)
|
||||
} else {
|
||||
routes.HTTP.Add(r)
|
||||
r.task.OnFinished("entrypoint_remove_route", func() {
|
||||
r.task.OnCancel("remove_route_from_http", func() {
|
||||
routes.HTTP.Del(r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package route
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -215,6 +216,13 @@ func (r *Route) Validate() gperr.Error {
|
|||
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) {
|
||||
if r.impl == nil { // should not happen
|
||||
return gperr.New("route not initialized")
|
||||
|
@ -354,10 +362,20 @@ func (r *Route) UseLoadBalance() 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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -482,6 +500,12 @@ func (r *Route) FinalizeHomepageConfig() {
|
|||
}
|
||||
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
|
||||
refs := r.References()
|
||||
for _, ref := range refs {
|
||||
|
|
|
@ -7,15 +7,16 @@ import (
|
|||
var (
|
||||
HTTP = pool.New[HTTPRoute]("http_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) {
|
||||
for _, r := range HTTP.Iter {
|
||||
if !yield(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, r := range Stream.Iter {
|
||||
for _, r := range All.Iter {
|
||||
if !yield(r) {
|
||||
break
|
||||
}
|
||||
|
@ -23,12 +24,7 @@ func Iter(yield func(r Route) bool) {
|
|||
}
|
||||
|
||||
func IterKV(yield func(alias string, r Route) bool) {
|
||||
for k, r := range HTTP.Iter {
|
||||
if !yield(k, r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for k, r := range Stream.Iter {
|
||||
for k, r := range All.Iter {
|
||||
if !yield(k, r) {
|
||||
break
|
||||
}
|
||||
|
@ -36,12 +32,13 @@ func IterKV(yield func(alias string, r Route) bool) {
|
|||
}
|
||||
|
||||
func NumRoutes() int {
|
||||
return HTTP.Size() + Stream.Size()
|
||||
return All.Size()
|
||||
}
|
||||
|
||||
func Clear() {
|
||||
HTTP.Clear()
|
||||
Stream.Clear()
|
||||
All.Clear()
|
||||
}
|
||||
|
||||
func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
||||
|
@ -54,9 +51,5 @@ func GetHTTPRouteOrExact(alias, host string) (HTTPRoute, bool) {
|
|||
}
|
||||
|
||||
func Get(alias string) (Route, bool) {
|
||||
r, ok := HTTP.Get(alias)
|
||||
if ok {
|
||||
return r, true
|
||||
}
|
||||
return Stream.Get(alias)
|
||||
return All.Get(alias)
|
||||
}
|
||||
|
|
|
@ -41,9 +41,6 @@ func NewStreamRoute(base *Route) (routes.Route, gperr.Error) {
|
|||
|
||||
// Start implements task.TaskStarter.
|
||||
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.Stream = NewStream(r)
|
||||
|
||||
|
@ -60,23 +57,32 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
|||
r.HealthMon = monitor.NewMonitor(r)
|
||||
}
|
||||
|
||||
if err := r.Setup(); err != nil {
|
||||
r.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
if !r.ShouldExclude() {
|
||||
if err := r.Setup(); err != nil {
|
||||
r.task.Finish(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 err := r.HealthMon.Start(r.task); err != nil {
|
||||
gperr.LogWarn("health monitor error", err, &r.l)
|
||||
}
|
||||
}
|
||||
|
||||
if r.ShouldExclude() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := checkExists(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go r.acceptConnections()
|
||||
|
||||
routes.Stream.Add(r)
|
||||
r.task.OnFinished("entrypoint_remove_route", func() {
|
||||
r.task.OnCancel("remove_route_from_stream", func() {
|
||||
routes.Stream.Del(r)
|
||||
})
|
||||
return nil
|
||||
|
|
|
@ -9,8 +9,9 @@ import (
|
|||
|
||||
type (
|
||||
Pool[T Object] struct {
|
||||
m *xsync.Map[string, T]
|
||||
name string
|
||||
m *xsync.Map[string, T]
|
||||
name string
|
||||
disableLog bool
|
||||
}
|
||||
Object interface {
|
||||
Key() string
|
||||
|
@ -19,41 +20,54 @@ type (
|
|||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (p Pool[T]) Add(obj T) {
|
||||
func (p *Pool[T]) Add(obj T) {
|
||||
p.checkExists(obj.Key())
|
||||
p.m.Store(obj.Key(), obj)
|
||||
log.Info().Msgf("%s: added %s", p.name, obj.Name())
|
||||
if !p.disableLog {
|
||||
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())
|
||||
log.Info().Msgf("%s: removed %s", p.name, obj.Name())
|
||||
if !p.disableLog {
|
||||
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)
|
||||
}
|
||||
|
||||
func (p Pool[T]) Size() int {
|
||||
func (p *Pool[T]) Size() int {
|
||||
return p.m.Size()
|
||||
}
|
||||
|
||||
func (p Pool[T]) Clear() {
|
||||
func (p *Pool[T]) 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)
|
||||
}
|
||||
|
||||
func (p Pool[T]) Slice() []T {
|
||||
func (p *Pool[T]) Slice() []T {
|
||||
slice := make([]T, 0, p.m.Size())
|
||||
for _, v := range p.m.Range {
|
||||
slice = append(slice, v)
|
||||
|
|
Loading…
Add table
Reference in a new issue