One year ago I experimented with Traefik and wrote about the experience. We ended up using it as a reverse proxy in my project at the time. It was remarkably unremarkable, in that it didn’t give us much trouble. That is a good thing! I kind of forgot about it, other than the occasional update.
Anyways, in my current project, we need a reverse proxy with automatic discovery again. Thus, Traefik is back on the menu. Version 2 has been released since I last touched it. It brings quite a few changes. I decided to adapt my sample setup and compare both.
I took the old repository and made a new one using version 2. Let’s see how things have changed.
TOML vs YAML
Right of the bat, TOML is no longer the only supported configuration mechanism. YAML support has been added. I don’t have a strong opinion on the matter, though YAML seems to be more of a standard these days.
Core notions have changed significantly. Before, we had entrypoint, frontend and backend. Now we’ve got entrypoint, router, and service. Modifications of the request happen through middleware.
In terms of functionality, not a lot has changed. The format is not compatible, so you’ll have to migrate quite a few things.
The configuration for entrypoints and providers goes in a static configuration file. The rest is dynamic, which can be extra YAML/TOML files, or directly as labels for the containers.
Service Discovery and Load Balancing
Mostly works the same. Support for ECS is gone, pending to be re-added.
As for load balancing, I had an example with two separate services in docker-compose running the same container in version 1. It was solved by using the same backend. Now you’ll want to define the same service for both, which couldn’t be done directly, as far as I could tell. I went around this by defining a healthcheck:
labels: - "traefik.http.services.web.loadBalancer.healthCheck.path=/"
Feels a bit hacky, but production services will have a healthcheck anyways, so it’s not too bad.
Routing goes from frontend to routers. The syntax is slightly different. Other than that it remains unchanged.
Setting TLS was a lot harder than expected. In the past, you would associate your certificates with the entrypoint. That would work automatically for a frontend using the entrypoint.
Now the certificates are defined as part of a store, which can look like this:
tls: stores: default: defaultCertificate: certFile: /certs/cert.crt keyFile: /certs/cert.key
Then, we activate
tls for a route with labels (full compose file here):
labels: - "traefik.http.routers.web-secure.entrypoints=https" - "traefik.http.routers.web-secure.tls=true" - "traefik.http.routers.web-secure.rule=Host(`echo.testing.com`) && Path(`/standard`)"
In theory, you can use multiple stores, but I only managed to get my certificate delivered by making it the default one.
I found the whole thing quite confusing. I was getting errors about self-signed certificates in the logs all the time. I was thinking that self-signed certificates were not supported. But then they did. Confusing logs have been a theme during this whole ordeal.
This was a hard requirement I had when I first tried Traefik. To get mTLS, you have to add a configuration block that is applied to the TLS connection:
tls: options: enforceClientCert: clientAuth: caFiles: - /certs/client.crt clientAuthType: RequireAndVerifyClientCert
I think this has to be a dynamic configuration, although I’m not sure. Then it’s a matter of activating that configuration for your router (full compose file here):
labels: - "traefik.http.routers.web-secure-mtls.entrypoints=mTLS" - "traefik.http.routers.web-secure-mtls.tls.options=enforceClientCert@file"
Somehow my https router from the previous example stopped working without client certificates. I only managed to separate them by using two entrypoints running two different ports, much like in old Traefik. My understanding from the documentation is that this shouldn’t be the case, but I couldn’t make it work otherwise.
The biggest change I’ve seen so far is that Traefik supports manipulating requests before sending them to a service and before returning them to the caller. It is called middleware.
Operations on the path of the request, like stripping parts or adding a prefix, are now expressed as middleware. Authentication has become middleware as well. I thought for a second this was new functionality. It turns out you could do most of it before, although it is a lot more consistent and organized now.
I like the idea. It is potentially pretty powerful, especially if custom middleware becomes a thing.
The dashboard has gotten quite a facelift. This is how it looks like now:
Fancy! I found the dashboard really useful in the beginning, although I didn’t check it as much once Traefik was up and running. Setting basic auth for it was surprisingly tricky.
It is a mixed bag for me, to be honest. I’m not sure if I just got used to the old stuff, but I’ve struggled to wrap my head around how things work now. Where does the config go? In a dynamic file? In labels? Who knows. The debug output could be a lot more helpful with that.
Maybe doing a direct translation of an existing setup is not the way to go? From my short experience, you have to dig deep into the documentation and assume that what you knew before no longer applies.
My favorite new part is middleware. For the rest, it feels a bit of a lateral move.