mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00
docker: clear routes on docker disconnect, reload routes on connection restore
This commit is contained in:
parent
2c21387ad9
commit
3e1a7a0dc5
4 changed files with 73 additions and 23 deletions
|
@ -213,7 +213,7 @@ func (w *Watcher) expires() time.Time {
|
||||||
return w.lastReset.Add(w.IdleTimeout)
|
return w.lastReset.Add(w.IdleTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Watcher) getEventCh(dockerWatcher watcher.DockerWatcher) (eventCh <-chan events.Event, errCh <-chan gperr.Error) {
|
func (w *Watcher) getEventCh(dockerWatcher *watcher.DockerWatcher) (eventCh <-chan events.Event, errCh <-chan gperr.Error) {
|
||||||
eventCh, errCh = dockerWatcher.EventsWithOptions(w.Task().Context(), watcher.DockerListOptions{
|
eventCh, errCh = dockerWatcher.EventsWithOptions(w.Task().Context(), watcher.DockerListOptions{
|
||||||
Filters: watcher.NewDockerFilter(
|
Filters: watcher.NewDockerFilter(
|
||||||
watcher.DockerFilterContainer,
|
watcher.DockerFilterContainer,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/route/provider/types"
|
"github.com/yusing/go-proxy/internal/route/provider/types"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
"github.com/yusing/go-proxy/internal/watcher"
|
"github.com/yusing/go-proxy/internal/watcher"
|
||||||
|
eventsPkg "github.com/yusing/go-proxy/internal/watcher/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EventHandler struct {
|
type EventHandler struct {
|
||||||
|
@ -29,10 +30,19 @@ func (p *Provider) newEventHandler() *EventHandler {
|
||||||
|
|
||||||
func (handler *EventHandler) Handle(parent task.Parent, events []watcher.Event) {
|
func (handler *EventHandler) Handle(parent task.Parent, events []watcher.Event) {
|
||||||
oldRoutes := handler.provider.routes
|
oldRoutes := handler.provider.routes
|
||||||
|
|
||||||
|
isForceReload := false
|
||||||
|
for _, event := range events {
|
||||||
|
if event.Action == eventsPkg.ActionForceReload {
|
||||||
|
isForceReload = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newRoutes, err := handler.provider.loadRoutes()
|
newRoutes, err := handler.provider.loadRoutes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.errs.Add(err)
|
handler.errs.Add(err)
|
||||||
if len(newRoutes) == 0 {
|
if len(newRoutes) == 0 && !isForceReload {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,22 @@ package watcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
docker_events "github.com/docker/docker/api/types/events"
|
docker_events "github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
"github.com/yusing/go-proxy/internal/gperr"
|
"github.com/yusing/go-proxy/internal/gperr"
|
||||||
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
DockerWatcher struct {
|
DockerWatcher struct {
|
||||||
host string
|
host string
|
||||||
client *D.SharedClient
|
client *docker.SharedClient
|
||||||
clientOwned bool
|
clientOwned bool
|
||||||
}
|
}
|
||||||
DockerListOptions = docker_events.ListOptions
|
DockerListOptions = docker_events.ListOptions
|
||||||
|
@ -42,38 +45,66 @@ var (
|
||||||
)}
|
)}
|
||||||
|
|
||||||
dockerWatcherRetryInterval = 3 * time.Second
|
dockerWatcherRetryInterval = 3 * time.Second
|
||||||
|
|
||||||
|
reloadTrigger = Event{
|
||||||
|
Type: events.EventTypeDocker,
|
||||||
|
Action: events.ActionForceReload,
|
||||||
|
ActorAttributes: map[string]string{},
|
||||||
|
ActorName: "",
|
||||||
|
ActorID: "",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func DockerFilterContainerNameID(nameOrID string) filters.KeyValuePair {
|
func DockerFilterContainerNameID(nameOrID string) filters.KeyValuePair {
|
||||||
return filters.Arg("container", nameOrID)
|
return filters.Arg("container", nameOrID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerWatcher(host string) DockerWatcher {
|
func NewDockerWatcher(host string) *DockerWatcher {
|
||||||
return DockerWatcher{
|
return &DockerWatcher{
|
||||||
host: host,
|
host: host,
|
||||||
clientOwned: true,
|
clientOwned: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDockerWatcherWithClient(client *D.SharedClient) DockerWatcher {
|
func NewDockerWatcherWithClient(client *docker.SharedClient) *DockerWatcher {
|
||||||
return DockerWatcher{
|
return &DockerWatcher{
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan gperr.Error) {
|
func (w *DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan gperr.Error) {
|
||||||
return w.EventsWithOptions(ctx, optionsDefault)
|
return w.EventsWithOptions(ctx, optionsDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w DockerWatcher) Close() {
|
func (w *DockerWatcher) Close() {
|
||||||
if w.clientOwned && w.client.Connected() {
|
if w.clientOwned && w.client.Connected() {
|
||||||
w.client.Close()
|
w.client.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerListOptions) (<-chan Event, <-chan gperr.Error) {
|
func (w *DockerWatcher) parseError(err error) gperr.Error {
|
||||||
eventCh := make(chan Event, 100)
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
errCh := make(chan gperr.Error, 10)
|
return gperr.New("docker client connection timeout")
|
||||||
|
}
|
||||||
|
if client.IsErrConnectionFailed(err) {
|
||||||
|
return gperr.New("docker client connection failure")
|
||||||
|
}
|
||||||
|
return gperr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *DockerWatcher) checkConnection(ctx context.Context) bool {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, dockerWatcherRetryInterval)
|
||||||
|
defer cancel()
|
||||||
|
_, err := w.client.Ping(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *DockerWatcher) EventsWithOptions(ctx context.Context, options DockerListOptions) (<-chan Event, <-chan gperr.Error) {
|
||||||
|
eventCh := make(chan Event)
|
||||||
|
errCh := make(chan gperr.Error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -84,7 +115,7 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||||
|
|
||||||
if !w.client.Connected() {
|
if !w.client.Connected() {
|
||||||
var err error
|
var err error
|
||||||
w.client, err = D.ConnectClient(w.host)
|
w.client, err = docker.ConnectClient(w.host)
|
||||||
attempts := 0
|
attempts := 0
|
||||||
retryTicker := time.NewTicker(dockerWatcherRetryInterval)
|
retryTicker := time.NewTicker(dockerWatcherRetryInterval)
|
||||||
for err != nil {
|
for err != nil {
|
||||||
|
@ -95,7 +126,7 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||||
retryTicker.Stop()
|
retryTicker.Stop()
|
||||||
return
|
return
|
||||||
case <-retryTicker.C:
|
case <-retryTicker.C:
|
||||||
w.client, err = D.ConnectClient(w.host)
|
w.client, err = docker.ConnectClient(w.host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
retryTicker.Stop()
|
retryTicker.Stop()
|
||||||
|
@ -104,7 +135,7 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
|
||||||
cEventCh, cErrCh := w.client.Events(ctx, options)
|
cEventCh, cErrCh := w.client.Events(ctx, options)
|
||||||
|
defer logging.Debug().Str("host", w.host).Msg("docker watcher closed")
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -126,14 +157,21 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errCh <- gperr.Wrap(err)
|
errCh <- w.parseError(err)
|
||||||
select {
|
// trigger reload (clear routes)
|
||||||
case <-ctx.Done():
|
eventCh <- reloadTrigger
|
||||||
return
|
for !w.checkConnection(ctx) {
|
||||||
default:
|
select {
|
||||||
time.Sleep(dockerWatcherRetryInterval)
|
case <-ctx.Done():
|
||||||
cEventCh, cErrCh = w.client.Events(ctx, options)
|
return
|
||||||
|
case <-time.After(dockerWatcherRetryInterval):
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// connection successful, trigger reload (reload routes)
|
||||||
|
eventCh <- reloadTrigger
|
||||||
|
// reopen event channel
|
||||||
|
cEventCh, cErrCh = w.client.Events(ctx, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -34,6 +34,8 @@ const (
|
||||||
ActionContainerDie
|
ActionContainerDie
|
||||||
ActionContainerDestroy
|
ActionContainerDestroy
|
||||||
|
|
||||||
|
ActionForceReload
|
||||||
|
|
||||||
actionContainerWakeMask = ActionContainerCreate | ActionContainerStart | ActionContainerUnpause
|
actionContainerWakeMask = ActionContainerCreate | ActionContainerStart | ActionContainerUnpause
|
||||||
actionContainerSleepMask = ActionContainerKill | ActionContainerStop | ActionContainerPause | ActionContainerDie
|
actionContainerSleepMask = ActionContainerKill | ActionContainerStop | ActionContainerPause | ActionContainerDie
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue