From 574e90f73f58dcd82562529fd135facea0485546 Mon Sep 17 00:00:00 2001 From: Christopher LaPointe Date: Wed, 24 May 2023 21:46:55 -0400 Subject: [PATCH] Some docs and cleanup --- README.md | 96 +++++++++++++++++++++++++++++++++++++++++- config.yaml | 23 +++++++--- main.go | 2 +- pkg/service/service.go | 7 +-- pkg/service/util.go | 14 ++++-- 5 files changed, 127 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 29e62b0..fdc05ba 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,104 @@ # traefik-lazyloader -Takes advantage of traefik's router priority to proxy to this small application that will manage -starting/stopping containers to save resources. +This small app will automatically start and stop docker containers based on access, for instance, +via traefik. + +## How it Works + +It works by acting as the fallback-route for the containers. For instance, if you have +`example.com` as a container you want to lazy-load, you would add the container, as well +as this lazyloader that would act as a lower-priority router for the same domain. If the +host is accessed, the lazyloader will work to boot up the container and redirect the user +as soon as it's up. + +It then monitors the container's network interface. If the network is idle for X minutes, it +will stop the container. ## Quick-Start +### docker-compose +```yaml +version: '3.5' + +services: + reverse-proxy: + image: traefik:v2.4 + command: + - --api.insecure + - --providers.docker + - --providers.docker.defaultRule=Host(`{{.Name}}.example.com`) + - --entryPoints.web.address=:80 + - --entryPoints.web.forwardedHeaders.insecure + - --providers.docker.exposedByDefault=false + restart: always + ports: + - "80:80" # The HTTP port + - "8080:8080" # The Web UI (enabled by --api) + volumes: + - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events + + lazyloader: + build: . + labels: + - traefik.enable=true + - "traefik.http.routers.lazyload.priority=-100" # Lower router priority. Would only be hit if the app isn't running + - "traefik.http.routers.lazyload.rule=Host(`whoami.example.com`, `lazyloader.example.com`)" + environment: + TLL_STOPATBOOT: true # Stop all lazyloaded containers at boot (great for an example) + TLL_STATUSHOST: lazyloader.example.com # This hostname will display a status page. Disabled by default + networks: + - traefik-bridge + volumes: + - /var/run/docker.sock:/var/run/docker.sock # Must access docker + + whoami: + image: containous/whoami + networks: + - traefik-bridge + labels: + - traefik.enable=true + - "traefik.http.routers.lazywhoami.rule=Host(`whoami.example.com`)" + - lazyloader=true + - lazyloader.stopdelay=30s # Overrides the default + +networks: + traefik-bridge: + external: true + name: traefik-bridge +``` + +You can run `docker-compose up` on the above for a quick-start. You will need to alter the domains as needed. + ## Config +Configuration uses [viper]() and can be specified by either overwriting the `config.yaml` file or +via environment variables with the `TLL_` prefix (Traefik lazy loader) + +```yaml +# What port to listen on +listen: :8080 + +# If set, when access via this hostname, will display status page +statushost: "" + +# Enable debug logging +verbose: false + +# if true, will stop all running tagged containers when the lazyloader starts +stopatboot: false + +# which splash-page asset to use +splash: splash.html + +# Container defaults +stopdelay: 5m # How long to wait before stopping container +pollfreq: 10s # How often to check + +# This will be the label-prefix to look at settings on a container +# usually won't need to change (only if running multiple instances) +labelprefix: lazyloader +``` + ## Labels * `lazyloader=true` -- (Required) Add to containers that should be managed diff --git a/config.yaml b/config.yaml index 0bf4fc1..f510ed3 100644 --- a/config.yaml +++ b/config.yaml @@ -1,11 +1,22 @@ +# What port to listen on listen: :8080 -stopatboot: false -splash: splash.html - -stopdelay: 5m -pollfreq: 10s +# If set, when access via this hostname, will display status page statushost: "" + +# Enable debug logging verbose: false -labelprefix: lazyloader \ No newline at end of file +# if true, will stop all running tagged containers when the lazyloader starts +stopatboot: false + +# which splash-page asset to use +splash: splash.html + +# Container defaults +stopdelay: 5m # How long to wait before stopping container +pollfreq: 10s # How often to check + +# This will be the label-prefix to look at settings on a container +# usually won't need to change (only if running multiple instances) +labelprefix: lazyloader diff --git a/main.go b/main.go index 3678c0b..e9c97b4 100644 --- a/main.go +++ b/main.go @@ -94,7 +94,7 @@ func StatusHandler(w http.ResponseWriter, r *http.Request) { runtime.ReadMemStats(&stats) statusPageTemplate.Execute(w, StatusPageModel{ Active: core.ActiveContainers(), - Qualifying: core.QualifyingContainers(), + Qualifying: core.QualifyingContainers(r.Context()), RuntimeMetrics: fmt.Sprintf("Heap=%d, InUse=%d, Total=%d, Sys=%d, NumGC=%d", stats.HeapAlloc, stats.HeapInuse, stats.TotalAlloc, stats.Sys, stats.NumGC), }) default: diff --git a/pkg/service/service.go b/pkg/service/service.go index 79d260b..bab61ce 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -74,7 +74,6 @@ func (s *Core) StartHost(hostname string) (*StartResult, error) { } if ets, exists := s.active[ct.ID]; exists { - // TODO: Handle case we think it's active, but not? (eg. crash? slow boot?) logrus.Debugf("Asked to start host, but we already think it's started: %s", ets.name) return &StartResult{ WaitForCode: ets.waitForCode, @@ -265,6 +264,7 @@ func (s *Core) findAllLazyloadContainers(ctx context.Context, includeStopped boo }) } +// Returns all actively managed containers func (s *Core) ActiveContainers() []*ContainerState { s.mux.Lock() defer s.mux.Unlock() @@ -279,8 +279,9 @@ func (s *Core) ActiveContainers() []*ContainerState { return ret } -func (s *Core) QualifyingContainers() []string { - ct, err := s.findAllLazyloadContainers(context.Background(), true) +// Return all containers that qualify to be load-managed (eg. have the tag) +func (s *Core) QualifyingContainers(ctx context.Context) []string { + ct, err := s.findAllLazyloadContainers(ctx, true) if err != nil { return nil } diff --git a/pkg/service/util.go b/pkg/service/util.go index 654362a..3025d11 100644 --- a/pkg/service/util.go +++ b/pkg/service/util.go @@ -2,6 +2,7 @@ package service import ( "fmt" + "strings" "traefik-lazyload/pkg/config" "github.com/docker/docker/api/types" @@ -22,7 +23,7 @@ func labelOrDefault(ct *types.Container, sublabel, dflt string) (string, bool) { return dflt, false } -func short(id string) string { +func shortId(id string) string { const SLEN = 8 if len(id) <= SLEN { return id @@ -33,11 +34,18 @@ func short(id string) string { func containerShort(c *types.Container) string { var name string if len(c.Names) > 0 { - name = c.Names[0] + name = trimRootPath(c.Names[0]) } else { name = c.Image } - return fmt.Sprintf("%s(%s)", name, short(c.ID)) + return fmt.Sprintf("%s(%s)", name, shortId(c.ID)) +} + +func trimRootPath(s string) string { + if strings.HasPrefix(s, "/") { + return s[1:] + } + return s } func isRunning(c *types.Container) bool {