sablierapp / sablier

Start your containers on demand, shut them down automatically when there's no activity. Docker, Docker Swarm Mode and Kubernetes compatible.
https://sablierapp.dev/
GNU Affero General Public License v3.0
1.36k stars 46 forks source link

Reusable Traefik middleware and `names` config field #150

Closed tommiv closed 1 year ago

tommiv commented 1 year ago

Intro So first of all let me thank you for Sablier because this is the only working solution for my problem I found so far.

Describe the bug It's not really a bug but more like a clarification or maybe a feature request. I want to use Sablier to on-demand start/stop "staging" instances of the same application stack (just a website, so api, frontend app, DB and some more containers, doesn't really matter). For this, I have multiple versions of the same application running each in its own container with a unique container name (prefixed with a git branch name). For brevity, let's say that I have these two:

So, what puzzles me – if I create just one instance of Sablier middleware, it only works if I enumerate all possible names in the names field of the middleware config, which is pretty hard to automate. A couple of examples to demostrate:

wrong approach

# dynamic/defaults.yaml
http:
  middlewares:
    sablier:
      plugin:
        sablier:
          sablierUrl: http://sablier:10000
          # <====== notice there's no `names` field here
          sessionDuration: 1m
          dynamic:
            showDetails: true
            theme: ghost
            refreshFrequency: 5s
# dynamic/git_branch_name1.yaml
http:
  services:
    git_branch_name1_front:
      loadBalancer:
        servers:
          - url: http://git_branch_name1_front:80

  routers:
    git_branch_name1_front:
      rule: Host(`git_branch_name1.mycompany.domain`)
      middlewares:
        - gzip@docker
        - sablier@file
      service: git_branch_name1_front

This approach doesn't work, Sablier tries to poll the container with an empty name, which is illegal.

working approach

# dynamic/defaults.yaml
# ... no reusable middleware declared here 
# dynamic/git_branch_name1.yaml
http:
  services:
    git_branch_name1_front:
      loadBalancer:
        servers:
          - url: http://git_branch_name1_front:80

  middlewares:
    git_branch_name1_sablier:
      plugin:
        sablier:
          dynamic:
            displayName: 'git_branch_name1'
          names: 'git_branch_name1_front'
          sablierUrl: 'http://sablier:10000'
          sessionDuration: '1m'

  routers:
    git_branch_name1_front:
      rule: Host(`git_branch_name1.mycompany.domain`)
      middlewares:
        - gzip@docker
        - git_branch_name1_sablier@file
      service: git_branch_name1_front

This approach does work but in this case I will create a new middleware instance per git branch, which can be kinda clumsy. I mean it's ok if there's no other choice, it will work, I'll just lose an ability to centralize Sablier config in one simple semi-static file easily deployable via Ansible. Not sure if it impacts performance of Traefik if I multiply the number of middlewares used. Logically it should not.

Context

Expected behavior It would be great if Sablier could somehow calculate the names array automatically per-request level (getting it from router for example), but I'm not sure if Traefik allows this at all. Another possible approach is to override one centralized middleware config with something on the dynamic configuration file level, but I can't find anything like this in Traefik docs.

acouvreur commented 1 year ago

Hey!

So currently, there is no solution that is reverse proxy agnostic.

And because of the nature of reverse proxies in general, a route is not necessarily tied to a specific container instance.

For this reason I'm not developping any integration for a specific reverse proxy.

On the other hand, you might be interested in the group feature. This is a new feature in beta that you can see here: https://github.com/acouvreur/sablier/pull/134

It will explain how to tag your container instead of knowing their names.


But in the end, a single middleware will always be tied to a specific group of containers by using names or group. Calling the same middleware cannot have a different behavior depending on the host path for the moment.

For feature like this, you should always think "provider agnostic".


However, one way to have a reusable middleware would be to be able to transmit specific data to the middleware on the runitme.

This can be achieved wiht: headers

So we could definitely create a combination of groups and header to have a reusable middleware.

Let say you add a middleware that adds the following header: X-Sablier-Group or X-Sablier-Names prior to the sablier middleware being called, the middleware could use these headers to override the ones that you have to configure today on your middleware.


To sum up:

tommiv commented 1 year ago

@acouvreur thanks for a quick response.

You're making a great point regarding reverse proxy agnosticism so I assume this answers my question.

I've checked the group feature. It's nice to have but in my particular case it changes nothing because it's still one middleware instance per stack instance.

The idea with dynamic groups via headers is a smart one though, but I think that for now I'm happy with what I have already, I'm going to close the issue.