mirror of
https://github.com/zix99/traefik-lazyload.git
synced 2025-12-21 13:23:04 +01:00
Some more code cleanup and timeouts
This commit is contained in:
@@ -112,14 +112,10 @@ labelprefix: lazyloader
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
* `lazyloader.needs=a,b,c` -- List of dependencies a container needs (will be started before starting the container)
|
* `lazyloader.needs=a,b,c` -- List of dependencies a container needs (will be started before starting the container). Can only be specified on a `lazyloader=true` container
|
||||||
* `lazyloader.provides=a` -- What dependency name a container provides (Not necessarily a `lazyloader` container)
|
* `lazyloader.provides=a` -- What dependency name a container provides (Not necessarily a `lazyloader` container)
|
||||||
* `lazyloader.provides.delay=5s` -- Delay starting other containers for this duration
|
* `lazyloader.provides.delay=5s` -- Delay starting other containers for this duration
|
||||||
|
|
||||||
# Features
|
|
||||||
|
|
||||||
- [ ] Dependencies & groups (eg. shut down DB if all dependent apps are down)
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
||||||
GPLv3
|
GPLv3
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ splash: splash.html
|
|||||||
stopdelay: 5m # How long to wait before stopping container
|
stopdelay: 5m # How long to wait before stopping container
|
||||||
pollfreq: 10s # How often to check
|
pollfreq: 10s # How often to check
|
||||||
|
|
||||||
|
# Default operation timeout (eg. starting and stopping a container)
|
||||||
|
timeout: 30s
|
||||||
|
|
||||||
# This will be the label-prefix to look at settings on a container
|
# This will be the label-prefix to look at settings on a container
|
||||||
# usually won't need to change (only if running multiple instances)
|
# usually won't need to change (only if running multiple instances)
|
||||||
labelprefix: lazyloader
|
labelprefix: lazyloader
|
||||||
|
|||||||
31
main.go
31
main.go
@@ -18,8 +18,10 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var core *service.Core
|
type controller struct {
|
||||||
var discovery *containers.Discovery
|
core *service.Core
|
||||||
|
discovery *containers.Discovery
|
||||||
|
}
|
||||||
|
|
||||||
func mustCreateDockerClient() *client.Client {
|
func mustCreateDockerClient() *client.Client {
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
@@ -37,10 +39,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dockerClient := mustCreateDockerClient()
|
dockerClient := mustCreateDockerClient()
|
||||||
discovery = containers.NewDiscovery(dockerClient)
|
discovery := containers.NewDiscovery(dockerClient)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
core, err = service.New(dockerClient, discovery, config.Model.PollFreq)
|
core, err := service.New(dockerClient, discovery, config.Model.PollFreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -50,11 +52,16 @@ func main() {
|
|||||||
core.StopAll()
|
core.StopAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controller := controller{
|
||||||
|
core,
|
||||||
|
discovery,
|
||||||
|
}
|
||||||
|
|
||||||
// Set up http server
|
// Set up http server
|
||||||
subFs, _ := fs.Sub(httpAssets, "assets")
|
subFs, _ := fs.Sub(httpAssets, "assets")
|
||||||
router := http.NewServeMux()
|
router := http.NewServeMux()
|
||||||
router.Handle(httpAssetPrefix, http.StripPrefix(httpAssetPrefix, http.FileServer(http.FS(subFs))))
|
router.Handle(httpAssetPrefix, http.StripPrefix(httpAssetPrefix, http.FileServer(http.FS(subFs))))
|
||||||
router.HandleFunc("/", ContainerHandler)
|
router.HandleFunc("/", controller.ContainerHandler)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: config.Model.Listen,
|
Addr: config.Model.Listen,
|
||||||
@@ -79,7 +86,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *controller) ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
host := r.Host
|
host := r.Host
|
||||||
if host == "" {
|
if host == "" {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
@@ -87,11 +94,11 @@ func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if host == config.Model.StatusHost && config.Model.StatusHost != "" {
|
if host == config.Model.StatusHost && config.Model.StatusHost != "" {
|
||||||
StatusHandler(w, r)
|
s.StatusHandler(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sOpts, err := core.StartHost(host); err != nil {
|
if sOpts, err := s.core.StartHost(host); err != nil {
|
||||||
if errors.Is(err, containers.ErrNotFound) {
|
if errors.Is(err, containers.ErrNotFound) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
io.WriteString(w, "not found")
|
io.WriteString(w, "not found")
|
||||||
@@ -111,17 +118,17 @@ func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StatusHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *controller) StatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/":
|
case "/":
|
||||||
var stats runtime.MemStats
|
var stats runtime.MemStats
|
||||||
runtime.ReadMemStats(&stats)
|
runtime.ReadMemStats(&stats)
|
||||||
|
|
||||||
qualifying, _ := discovery.QualifyingContainers(r.Context())
|
qualifying, _ := s.discovery.QualifyingContainers(r.Context())
|
||||||
providers, _ := discovery.ProviderContainers(r.Context())
|
providers, _ := s.discovery.ProviderContainers(r.Context())
|
||||||
|
|
||||||
statusPageTemplate.Execute(w, StatusPageModel{
|
statusPageTemplate.Execute(w, StatusPageModel{
|
||||||
Active: core.ActiveContainers(),
|
Active: s.core.ActiveContainers(),
|
||||||
Qualifying: qualifying,
|
Qualifying: qualifying,
|
||||||
Providers: providers,
|
Providers: providers,
|
||||||
RuntimeMetrics: fmt.Sprintf("Heap=%d, InUse=%d, Total=%d, Sys=%d, NumGC=%d", stats.HeapAlloc, stats.HeapInuse, stats.TotalAlloc, stats.Sys, stats.NumGC),
|
RuntimeMetrics: fmt.Sprintf("Heap=%d, InUse=%d, Total=%d, Sys=%d, NumGC=%d", stats.HeapAlloc, stats.HeapInuse, stats.TotalAlloc, stats.Sys, stats.NumGC),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type ConfigModel struct {
|
|||||||
|
|
||||||
StopDelay time.Duration // Amount of time to wait before stopping a container
|
StopDelay time.Duration // Amount of time to wait before stopping a container
|
||||||
PollFreq time.Duration // How often to check for changes
|
PollFreq time.Duration // How often to check for changes
|
||||||
|
Timeout time.Duration // Default operation timeout (eg. starting/stopping a container)
|
||||||
|
|
||||||
Verbose bool // Debug-level logging
|
Verbose bool // Debug-level logging
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"traefik-lazyload/pkg/config"
|
||||||
"traefik-lazyload/pkg/containers"
|
"traefik-lazyload/pkg/containers"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@@ -59,16 +60,18 @@ func (s *Core) StartHost(hostname string) (*ContainerState, error) {
|
|||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx, cancel := context.WithTimeout(context.Background(), config.Model.Timeout)
|
||||||
|
|
||||||
ct, err := s.discovery.FindContainerByHostname(ctx, hostname)
|
ct, err := s.discovery.FindContainerByHostname(ctx, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("Unable to find container for host %s: %s", hostname, err)
|
logrus.Warnf("Unable to find container for host %s: %s", hostname, err)
|
||||||
|
cancel()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ets, exists := s.active[ct.ID]; exists {
|
if ets, exists := s.active[ct.ID]; exists {
|
||||||
logrus.Debugf("Asked to start host, but we already think it's started: %s", ets.name)
|
logrus.Debugf("Asked to start host, but we already think it's started: %s", ets.name)
|
||||||
|
cancel()
|
||||||
return ets, nil
|
return ets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +83,7 @@ func (s *Core) StartHost(hostname string) (*ContainerState, error) {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
cancel()
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
ets.pinned = false
|
ets.pinned = false
|
||||||
ets.lastActivity = time.Now()
|
ets.lastActivity = time.Now()
|
||||||
@@ -102,10 +106,13 @@ func (s *Core) StopAll() {
|
|||||||
logrus.Info("Stopping all containers...")
|
logrus.Info("Stopping all containers...")
|
||||||
for cid, ct := range s.active {
|
for cid, ct := range s.active {
|
||||||
logrus.Infof("Stopping %s...", ct.name)
|
logrus.Infof("Stopping %s...", ct.name)
|
||||||
s.client.ContainerStop(ctx, cid, container.StopOptions{})
|
if err := s.client.ContainerStop(ctx, cid, container.StopOptions{}); err != nil {
|
||||||
|
logrus.Warnf("Error stopping %s: %v", ct.name, err)
|
||||||
|
} else {
|
||||||
delete(s.active, cid)
|
delete(s.active, cid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns all actively managed containers
|
// Returns all actively managed containers
|
||||||
func (s *Core) ActiveContainers() []*ContainerState {
|
func (s *Core) ActiveContainers() []*ContainerState {
|
||||||
@@ -136,20 +143,24 @@ func (s *Core) startContainerSync(ctx context.Context, ct *containers.Wrapper) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Core) startDependencyFor(ctx context.Context, needs []string, forContainer string) {
|
func (s *Core) startDependencyFor(ctx context.Context, needs []string, forContainer string) error {
|
||||||
for _, dep := range needs {
|
for _, dep := range needs {
|
||||||
providers, err := s.discovery.FindDepProvider(ctx, dep)
|
providers, err := s.discovery.FindDepProvider(ctx, dep)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Error finding dependency provider for %s: %v", dep, err)
|
logrus.Errorf("Error finding dependency provider for %s: %v", dep, err)
|
||||||
|
return err
|
||||||
} else if len(providers) == 0 {
|
} else if len(providers) == 0 {
|
||||||
logrus.Warnf("Unable to find any container that provides %s for %s", dep, forContainer)
|
logrus.Warnf("Unable to find any container that provides %s for %s", dep, forContainer)
|
||||||
|
return ErrProviderNotFound
|
||||||
} else {
|
} else {
|
||||||
for _, provider := range providers {
|
for _, provider := range providers {
|
||||||
if !provider.IsRunning() {
|
if !provider.IsRunning() {
|
||||||
logrus.Infof("Starting dependency for %s: %s", forContainer, provider.NameID())
|
logrus.Infof("Starting dependency for %s: %s", forContainer, provider.NameID())
|
||||||
|
|
||||||
s.startContainerSync(ctx, &provider)
|
if err := s.startContainerSync(ctx, &provider); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
delay, _ := provider.ConfigDuration("provides.delay", 2*time.Second)
|
delay, _ := provider.ConfigDuration("provides.delay", 2*time.Second)
|
||||||
logrus.Debugf("Delaying %s to start %s", delay.String(), dep)
|
logrus.Debugf("Delaying %s to start %s", delay.String(), dep)
|
||||||
@@ -158,10 +169,13 @@ func (s *Core) startDependencyFor(ctx context.Context, needs []string, forContai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Core) stopDependenciesFor(ctx context.Context, cid string, cts *ContainerState) {
|
func (s *Core) stopDependenciesFor(ctx context.Context, cid string, cts *ContainerState) []error {
|
||||||
// Look at our needs, and see if anything else needs them; if not, shut down
|
// Look at our needs, and see if anything else needs them; if not, shut down
|
||||||
|
var errs []error
|
||||||
|
|
||||||
deps := make(map[string]bool) // dep -> needed
|
deps := make(map[string]bool) // dep -> needed
|
||||||
for _, dep := range cts.needs {
|
for _, dep := range cts.needs {
|
||||||
@@ -182,6 +196,7 @@ func (s *Core) stopDependenciesFor(ctx context.Context, cid string, cts *Contain
|
|||||||
containers, err := s.discovery.FindDepProvider(ctx, dep)
|
containers, err := s.discovery.FindDepProvider(ctx, dep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Unable to find dependency provider containers for %s: %v", dep, err)
|
logrus.Errorf("Unable to find dependency provider containers for %s: %v", dep, err)
|
||||||
|
errs = append(errs, err)
|
||||||
} else if len(containers) == 0 {
|
} else if len(containers) == 0 {
|
||||||
logrus.Warnf("Unable to find any containers for dependency %s", dep)
|
logrus.Warnf("Unable to find any containers for dependency %s", dep)
|
||||||
} else {
|
} else {
|
||||||
@@ -195,6 +210,7 @@ func (s *Core) stopDependenciesFor(ctx context.Context, cid string, cts *Contain
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ticker loop that will check internal state against docker state (Call Poll)
|
// Ticker loop that will check internal state against docker state (Call Poll)
|
||||||
@@ -216,7 +232,7 @@ func (s *Core) pollThread(rate time.Duration) {
|
|||||||
// stopping idle containers
|
// stopping idle containers
|
||||||
// Will normally happen in the background with the pollThread
|
// Will normally happen in the background with the pollThread
|
||||||
func (s *Core) Poll() {
|
func (s *Core) Poll() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), config.Model.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
s.checkForNewContainersSync(ctx)
|
s.checkForNewContainersSync(ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user