├── .gitignore ├── .gitmodules ├── .traefik.yml ├── README.md ├── config_dev.yml ├── docker-compose-dev.yml ├── docker-compose.yml ├── go.mod ├── go.sum ├── manager.go ├── traefik.yml └── traefik_dev.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Go 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "traefik-container-manager-service"] 2 | path = traefik-container-manager-service 3 | url = https://git.adyanth.site/adyanth/traefik-container-manager-service 4 | -------------------------------------------------------------------------------- /.traefik.yml: -------------------------------------------------------------------------------- 1 | displayName: Container Manager for Traefik 2 | type: middleware 3 | import: github.com/adyanth/traefik-container-manager 4 | summary: 'Traefik plugin to start/stop containers as needed' 5 | testData: 6 | serviceUrl: http://manager:10000 7 | name: whoami 8 | timeout: 60 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # traefik-container-manager 2 | 3 | Traefik plugin to start/stop containers as needed. 4 | 5 | Needs `traefik-container-manager-service` and should be accessible by traefik container to work. Defaults to `http://manager:10000/api`, so if you have a compose file with the service named manager accessible by traefik over the default network, you are good to go. 6 | 7 | Just add this middleware to any router, configuring name which should match `traefik-container-manager.name`. and timeout. with the needed labels for `traefik-container-manager-service`. 8 | 9 | A sample shown below can be used for reference: 10 | 11 | ```yaml 12 | whoami: 13 | image: containous/whoami 14 | labels: 15 | - traefik.enable=true 16 | - traefik.http.routers.whoami.entrypoints=entryhttp 17 | - traefik.http.routers.whoami.rule=Host(`whoami.adyanth.lan`) || PathPrefix(`/whoami`) 18 | - traefik.http.routers.whoami.middlewares=whoami-timeout 19 | - traefik.http.services.whoami.loadbalancer.server.port=80 20 | - traefik.http.middlewares.whoami-timeout.plugin.traefik-container-manager.timeout=5 21 | - traefik.http.middlewares.whoami-timeout.plugin.traefik-container-manager.name=whoami 22 | - traefik.http.middlewares.whoami-timeout.plugin.traefik-container-manager.serviceUrl=http://manager:10000/api # Optional 23 | - traefik-container-manager.name=whoami 24 | - traefik-container-manager.path=/whoami # Prefix matched to the incoming path. To be used if using PathPrefix routing 25 | - traefik-container-manager.host=whoami # Prefix matched to the incoming hostname. Can provide the subdomain or the complete fqdn. To be used if using Host routing 26 | ``` 27 | -------------------------------------------------------------------------------- /config_dev.yml: -------------------------------------------------------------------------------- 1 | # http: 2 | # middlewares: 3 | # custom-404: 4 | # errors: 5 | # status: 6 | # - "404" 7 | # service: manager@docker 8 | # query: "http://10.10.10.3:10000/manager" 9 | # manager: 10 | # plugin: 11 | # dev: 12 | # serviceUrl: http://manager:10000 13 | # name: whoami 14 | # timeout: 10 15 | # services: 16 | # whoami: 17 | # loadBalancer: 18 | # servers: 19 | # - url: "http://whoami:80" 20 | # routers: 21 | # whoami: 22 | # rule: PathPrefix(`/whoami`) 23 | # entryPoints: 24 | # - "http" 25 | # middlewares: 26 | # - manager 27 | # service: "whoami" 28 | -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | traefik: 4 | image: traefik 5 | ports: 6 | - "8000:80" 7 | - "8080:8080" 8 | volumes: 9 | - './traefik_dev.yml:/etc/traefik/traefik.yml' 10 | - '/var/run/docker.sock:/var/run/docker.sock' 11 | - '.:/plugins-local/src/github.com/adyanth/traefik-container-manager' 12 | - './config_dev.yml:/etc/traefik/config.yml' 13 | environment: 14 | - TRAEFIK_PILOT_TOKEN=fc2fd759-11c9-4461-9bb5-978a790f4a45 15 | labels: 16 | - traefik.enable=true 17 | - traefik.http.services.traefik.loadbalancer.server.port=8080 18 | 19 | manager: 20 | build: traefik-container-manager-service 21 | image: adyanth/traefik-container-manager-service 22 | expose: 23 | - 10000 24 | volumes: 25 | - '/var/run/docker.sock:/var/run/docker.sock' 26 | labels: 27 | - traefik.enable=true 28 | - traefik.http.routers.container-manager.entrypoints=entryhttp 29 | - traefik.http.routers.container-manager.rule=HostRegexp(`{host:.+}`) 30 | - traefik.http.routers.container-manager.priority=1 31 | - traefik.http.middlewares.container-manager.errors.status=404 32 | - traefik.http.middlewares.container-manager.errors.service=container-manager 33 | - traefik.http.middlewares.container-manager.errors.query=/ 34 | - traefik.http.routers.container-manager.middlewares=manager-timeout 35 | - traefik.http.services.container-manager.loadbalancer.server.port=80 36 | - traefik.http.middlewares.manager-timeout.plugin.manager.name=generic-container-manager 37 | 38 | # whoami: 39 | # image: containous/whoami 40 | # labels: 41 | # - traefik-manager-name=whoami 42 | 43 | whoami: 44 | image: containous/whoami 45 | labels: 46 | - traefik.enable=true 47 | - traefik.http.routers.whoami.entrypoints=entryhttp 48 | - traefik.http.routers.whoami.rule=Host(`whoami.adyanth.lan`) || PathPrefix(`/whoami`) 49 | - traefik.http.routers.whoami.middlewares=whoami-timeout 50 | - traefik.http.middlewares.whoami-timeout.plugin.manager.timeout=5 51 | - traefik.http.middlewares.whoami-timeout.plugin.manager.name=whoami 52 | - traefik.http.services.whoami.loadbalancer.server.port=80 53 | - traefik-container-manager.name=whoami 54 | - traefik-container-manager.path=/whoami # Prefix matched to the incoming path. 55 | - traefik-container-manager.host=whoami # Prefix matched to the incoming hostname. Can provide the subdomain or the complete fqdn. 56 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | traefik: 4 | image: traefik:latest 5 | restart: always 6 | ports: 7 | - "80:80" 8 | environment: 9 | - TZ=Asia/Kolkata 10 | volumes: 11 | - /var/run/docker.sock:/var/run/docker.sock 12 | - ./traefik.yml:/etc/traefik/traefik.yml 13 | - ./config:/etc/traefik/dynamic/ # If needed 14 | labels: 15 | - traefik.enable=true 16 | 17 | manager: 18 | image: adyanth/traefik-container-manager-service 19 | expose: 20 | - 10000 21 | volumes: 22 | - '/var/run/docker.sock:/var/run/docker.sock' 23 | labels: 24 | - traefik.enable=true 25 | - traefik.http.routers.manager.entrypoints=web 26 | - traefik.http.routers.manager.rule=HostRegexp(`{host:.+}`) 27 | - traefik.http.routers.manager.priority=1 28 | - traefik.http.middlewares.manager.errors.status=404 29 | - traefik.http.middlewares.manager.errors.service=manager 30 | - traefik.http.middlewares.manager.errors.query=/ 31 | - traefik.http.routers.manager.middlewares=manager-timeout 32 | - traefik.http.services.manager.loadbalancer.server.port=80 33 | - traefik.http.middlewares.manager-timeout.plugin.traefik-container-manager.Name=generic-container-manager 34 | - traefik.http.middlewares.manager-timeout.plugin.traefik-container-manager.ServiceUrl=http://manager:10000/api 35 | 36 | whoami: 37 | image: containous/whoami 38 | labels: 39 | - traefik.enable=true 40 | - traefik.http.routers.whoami.entrypoints=web 41 | - traefik.http.routers.whoami.rule=Host(`whoami.adyanth.lan`) || PathPrefix(`/whoami`) 42 | - traefik.http.routers.whoami.middlewares=whoami-timeout 43 | - traefik.http.middlewares.whoami-timeout.plugin.traefik-container-manager.timeout=5 44 | - traefik.http.middlewares.whoami-timeout.plugin.traefik-container-manager.name=whoami 45 | - traefik.http.middlewares.whoami-timeout.plugin.traefik-container-manager.ServiceUrl=http://manager:10000/api 46 | - traefik.http.services.whoami.loadbalancer.server.port=80 47 | - traefik-container-manager.name=whoami 48 | - traefik-container-manager.path=/whoami # Prefix matched to the incoming path. 49 | - traefik-container-manager.host=whoami # Prefix matched to the incoming hostname. Can provide the subdomain or the complete fqdn. 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/adyanth/traefik-container-manager 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adyanth/traefik-container-manager/e7ae8d34260f9c518cdd65b68b81c0233b38894b/go.sum -------------------------------------------------------------------------------- /manager.go: -------------------------------------------------------------------------------- 1 | package traefik_container_manager 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const defaultTimeoutSeconds = 60 * 5 // 5 minutes 14 | const defaultServiceUrl = "http://manager:10000/api" // Default URL is a container called manager serving on port 10000 15 | 16 | var netClient = &http.Client{ 17 | Timeout: time.Second * 2, 18 | } 19 | 20 | // Config the plugin configuration 21 | type Config struct { 22 | Name string 23 | ServiceUrl string 24 | Timeout uint64 25 | } 26 | 27 | // CreateConfig creates a config with its default values 28 | func CreateConfig() *Config { 29 | return &Config{ 30 | ServiceUrl: defaultServiceUrl, 31 | Timeout: defaultTimeoutSeconds, 32 | } 33 | } 34 | 35 | // Manager holds the request for the container 36 | type Manager struct { 37 | name string 38 | next http.Handler 39 | request string 40 | serviceUrl string 41 | timeout uint64 42 | } 43 | 44 | func buildRequest(baseUrl string, name string, timeout uint64, host, path string) (string, error) { 45 | // TODO: Check url validity 46 | request := fmt.Sprintf("%s?name=%s&timeout=%d&host=%s&path=%s", baseUrl, name, timeout, url.QueryEscape(host), url.QueryEscape(path)) 47 | fmt.Println(timeout) 48 | return request, nil 49 | } 50 | 51 | // New function creates the configuration 52 | func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { 53 | 54 | if len(config.Name) == 0 { 55 | return nil, fmt.Errorf("name cannot be null") 56 | } 57 | 58 | request, err := buildRequest(config.ServiceUrl, config.Name, config.Timeout, "", "") 59 | 60 | if err != nil { 61 | return nil, fmt.Errorf("error while building request") 62 | } 63 | 64 | return &Manager{ 65 | next: next, 66 | name: config.Name, 67 | request: request, 68 | serviceUrl: config.ServiceUrl, 69 | timeout: config.Timeout, 70 | }, nil 71 | } 72 | 73 | // ServeHTTP retrieve the service status 74 | func (e *Manager) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 75 | if e.name == "generic-container-manager" { 76 | fmt.Printf("Request object: %+v", req) 77 | host := req.Host 78 | if host == "" { 79 | host = req.URL.Host 80 | } 81 | path := req.URL.Path 82 | if strings.Contains(path, ".") { 83 | if s := strings.Split(path, "/"); len(s) > 2 { 84 | path = strings.Join(s[:len(s)-1], "") 85 | } else { 86 | e.next.ServeHTTP(rw, req) 87 | return 88 | } 89 | } 90 | e.request, _ = buildRequest(e.serviceUrl, e.name, e.timeout, host, path) 91 | fmt.Println("Request set to ", e.request) 92 | } 93 | 94 | starting := false 95 | var status string 96 | var err error 97 | for status, err = getServiceStatus(e.request); err == nil && status == "starting"; status, err = getServiceStatus(e.request) { 98 | starting = true 99 | } 100 | 101 | if starting { 102 | time.Sleep(1 * time.Second) 103 | http.Redirect(rw, req, req.URL.Path, http.StatusTemporaryRedirect) 104 | } 105 | 106 | if err != nil { 107 | rw.WriteHeader(http.StatusInternalServerError) 108 | rw.Write([]byte(err.Error())) 109 | } 110 | 111 | if status == "started" { 112 | // Service started forward request 113 | e.next.ServeHTTP(rw, req) 114 | 115 | } else { 116 | // Error 117 | rw.WriteHeader(http.StatusInternalServerError) 118 | rw.Write([]byte("Unexpected status answer from Manager service")) 119 | } 120 | } 121 | 122 | func getServiceStatus(request string) (string, error) { 123 | 124 | // This request starts up the service if it is stopped 125 | resp, err := netClient.Get(request) 126 | if err != nil { 127 | return "error", err 128 | } 129 | 130 | defer resp.Body.Close() 131 | body, err := ioutil.ReadAll(resp.Body) 132 | if err != nil { 133 | return "parsing error", err 134 | } 135 | 136 | return strings.TrimSuffix(string(body), "\n"), nil 137 | } 138 | -------------------------------------------------------------------------------- /traefik.yml: -------------------------------------------------------------------------------- 1 | api: 2 | dashboard: true 3 | 4 | entrypoints: 5 | web: 6 | address: :80 7 | 8 | providers: 9 | docker: 10 | watch: true 11 | exposedbydefault: false 12 | file: 13 | watch: true 14 | directory: /etc/traefik/dynamic # If needed 15 | 16 | pilot: 17 | token: "XXXX" 18 | 19 | experimental: 20 | plugins: 21 | traefik-container-manager: 22 | moduleName: "github.com/adyanth/traefik-container-manager" 23 | version: "v0.1.2" 24 | -------------------------------------------------------------------------------- /traefik_dev.yml: -------------------------------------------------------------------------------- 1 | pilot: 2 | token: "fc2fd759-11c9-4461-9bb5-978a790f4a45" 3 | api: 4 | dashboard: true 5 | insecure: true 6 | experimental: 7 | localPlugins: 8 | manager: 9 | moduleName: github.com/adyanth/traefik-container-manager 10 | entryPoints: 11 | entryhttp: 12 | address: ":80" 13 | forwardedHeaders: 14 | insecure: true 15 | providers: 16 | docker: 17 | exposedByDefault: false 18 | file: 19 | filename: "/etc/traefik/config.yml" 20 | watch: true 21 | --------------------------------------------------------------------------------