Adding Header On Load Balanced Single Route

I have two different containers that are providing the same service. I have setup Traefik to load balance between them like below:

  container1:
    image: some_container
    labels:
      traefik.http.routers.backend.rule: HeadersRegexp(`Accept`, `application/json`)
      traefik.http.services.backend.loadbalancer.server.port: 80

  container2:
    image: another_container
    labels:
      traefik.http.routers.backend.rule: HeadersRegexp(`Accept`, `application/json`)
      traefik.http.services.backend.loadbalancer.server.port: 80

My understanding is that this sets up a single service named backend that is load balanced between the two docker compose services container1 and container2. There is also a route named backend that is activated if a JSON request is made which sends the traffic to this service. This works great!

But I want the caller to know which container fulfilled the request. Ideally I want a header added named X-Backend with the value of container1 or container2 depending on which docker compose service handled the request.

I investigated using middleware for this due to it having the functionality to add headers. But you attach middleware to routes not to the individual docker compose service so this wouldn't work. Is there a way to do this?

Your setup seems not standard. Usually you create a Docker service which you can scale, with Docker Swarm across nodes.

Usually you would have a clear router rule setup, so no overlap, where Traefik would need to decide which one to take.

I think your case is rather non-deterministic, first router loaded is taken, I would expect no load balancing there.

I agree, it's a bit odd and the more normal thing to do is have a one-to-one mapping between Docker Compose service and Traefik service. That being said it does work. Every other request goes to a different container and they share the same route and Traefik service. I just was wondering if there was a way to know what container serviced the request (other than have the container itself announce it).

Although my need is more specialized I could see a more real world need for this if I wanted to slowly transition traffic to a new version. I.E. container1 could be the old version, container2 could be the new version and I want to do a weighted round robin slowly increasing the traffic to the new version.

You could give the container a name, you could also add a response header with the name included.

:eyes:

I'm pretty new to Traefik. Could you provide a bit more info on how I might do that?

See Traefik doc (link). Create the header and assign it to the router.

Sorry, if I'm being dense. I've been through those docs quite a bit as well as tried various things with no luck.

The middleware is attached to a route and both containers share the same route. So if I attach add middleware to that route for each container I end up with an error. For example if the following labels are added to the first container:

traefik.http.middlewares.backend1.headers.customresponseheaders.X-Backend: container1
traefik.http.routers.backend.middlewares: backend1

and then on the second container I do:

traefik.http.middlewares.backend2.headers.customresponseheaders.X-Backend: container2
traefik.http.routers.backend.middlewares: backend2

I end up wtih the error:

Router defined multiple times with different configurations

It seems like you might be suggesting some sort of parameterization maybe but I'm not sure I follow enough.

Sorry, I didn't really recognize that you use the same router name on different containers. I would say with that you are out of specification and in experimental mode.

There have been some requests for dynamic middlewares to add dynamic values, you can check the Traefik plugins (link) if something is available to dynamically add something like local hostname or local env var.

Yea, the fact that both containers are using the same route and service is what enabled Traefik to load balance between then but that also means I cannot have differentiating middleware.

I did look at the plugins (both existing and how to make one) but I don't think the plugins offers the ability to do it either. There are basically two types of plugins:

  • provider plugins - This is basically about automatic configuration. Obviously I'm using the built-in docker provider plugin but even if I made my own plugin to provide the configuration I don't think via configuration this sort of header addition can be accomplished.
  • middleware plugins - This allows the request to be manipulated but it really only has the request and response as context for doing that manipulation. There is no additional context about the route, service, servers, containers that would help me in making decisions regarding that response manipulation.

Sounds like the answer is that it's just not possible and I will need to make each container self-assert the header. Thanks for at least working through it with me so I know it's not possible vs me just not knowing how to do it.

Something like this does not work?

os.Getenv("ENV_VAR_NAME")

Sorry, I still seem to be dense.

If the middleware is pulling from an ENV variable wouldn't that still be the same for all Docker services in my configuration since they still share the same route and Traefik service?

Or are you suggesting the docker service itself reads an ENV variable that docker compose provides it and that service itself creates the header based on the env variable. If so, that is basically my workaround but I'm trying to avoid the app needing to self-attest which container it is. Ideally the app doesn't create any headers and that's all added within the Traefik config.

You are probably right, even when declared in labels on the target service, a Traefik plugin will be executed on Traefik and read the env there.

Also it will probably not know which service is going to be called, as the flow is router -> middleware -> service.