The Story of the Configuration Structure

Hi everyone! Today is a very special day: we’re here to talk about changes.

Yes, the changes we’ve been playing around with, the changes we talked about in the latest release note, the changes we’re looking forward to bringing to the next version of Traefik.

But like in every spoiler undertaking, we won’t disclose everything (where would be the fun in that?).

Today we’re going to discuss the configuration structure — the infamous entrypoints , frontends , and backends and how they interact together.

Note: In the article, we’ll use the file provider to talk about the changes. This provider best exposes the structure of the configuration and is the foundation for the other label / annotation-driven providers. Of course, everything that applies to the file provider should apply to the label/annotation based providers.

Note 2: In this article, we’ll assume that you’re a knowledgeable Traefik enthusiast.

Note 3: Nope, no note 3, I’m good, let’s move on.

It All Started with…

To this day, Traefik’s configuration is quite simple and accounts for a great part of its success.

Entrypoints accept the incoming requests (for the most part: ports & certificate management), frontends analyze the requests to determine what should handle them, and backends are responsible for forwarding the requests to your system (your both beloved and hated microservices).

Since everyone loves diagrams (at least I do), below is the magic happening.

This architecture works like a charm because you can configure everything you need, from authentication to redirects, to circuit breaker mechanisms, to retry mechanisms, to custom errors, to HTTPS, to load-balancing, to whatever you need. And better, it works with many providers (k8s, docker, AWS, mesos, …).

But … (always a but) … sometimes people were confused about where to configure these options. Other times, people were confused because some options were magically applied in a predetermined order they could not easily change (and no, hacking our way into the bowels of Traefik is not literally easy).

Because so many options were added since Traefik’s initial launch, we’re in a situation where the magic comes along with a cost: things are less obvious than before, and it becomes a problem when you need to fine-tune the routing for specific requests.

Also, since we’re complaining about the configuration, I have to say that I hate to repeat myself — which was sometimes a problem when (for example) I wanted to put the same auth mechanism on several frontends (but not every one of them).

… And It Will Become

So we took a step back, gave it some thought, and came up with our first proposal … What if we wrote…

Entrypoints accept the incoming requests, routers analyze the requests to determine what should handle them, and services are responsible for handling the requests and ultimately forwarding them to your system (your both beloved and hated microservices).

Déjà vu?

See? SO different! It’s even more obvious in the following diagram.

Are You Kidding?

Well, we’re not.

At first sight, apart from the vocabulary update, we agree that nothing has obviously changed: same sequence of operations.

What really changed is what’s happening inside.

Middleware — The (Almost) Newcomer

If you look closely to the new diagram, you’ll notice that something is missing, and then you’ll realize that this something is quite important.

You can pause here, go back to the diagram and keep reading once you’ve figured it out, but for the impatient amongst us, here it is: In the diagram, nothing updates the request, nothing checks the credentials, nothing tweaks the path or the domain. In the diagram, a whole block of features is missing.

But how come this crucial element isn’t shouting its absence from the diagram? It’s simply because the configuration for these updates/tweaks/behaviors used to be spread across the existing components ( entrypoints , frontends , and backends ).

Soon, they will be configured in dedicated pieces of middleware and referred to from entrypoints , routers , and services . What’s more important — You will decide how, when, and in which order.

Yes, you will have full control.

Before / After (The TL;DR you’re looking for)

We Want Examples!

That’s a legitimate request — let’s start with a super basic example with a frontend, a backend, and no additional fluff.

A frontend (router), a backend (service), and no additional fluff

before

[frontends] [frontends.my-frontend] entrypoint = ["http"] backend = my-backend [frontend.my-frontend.routes.route1] rule = "Host:myhost.com" [backends] [backends.my-backend] [backends.my-backend1.servers] [backends.backend1.servers.server-1] url = "http://xx.xx.xx.xx"

after

[routers] [routers.my-router] entrypoint = ["http"] rule = "Host:myhost.com" service = my-service [services] [services.my-service.load-balancer] [[services.my-services.load-balancer.servers]] url = "http://xx.xx.xx.xx"

Apart from the vocabulary difference, this one brings nothing new to the table. (Yes, I’ll behave as if there was no additional .load-balancer type for the service because this will be for another episode.)

If we wanted to have another server, of course, we would write:

[routers] [routers.my-router] entrypoint = ["http"] rule = "Host:myhost.com" service = my-service [services] [services.my-service.load-balancer] [[services.my-services.load-balancer.servers]] url = "http://xx.xx.xx.xx" [[services.my-services.load-balancer.servers]] url = "http://xx.xx.xx.xx"

Now, let’s add basic authentication!

before

[frontends] [frontends.my-frontend] entrypoint = ["http"] backend = my-backend [frontend.my-frontend.routes.route1] rule = "Host:myhost.com" [frontend.my-frontend.auth] [frontend.my-frontend.auth.basic] users = ["admin:xxx","super-admin:xxx",] [backends] [backends.my-backend] [backends.my-backend1.servers] [backends.backend1.servers.server-1] url = "http://xx.xx.xx.xx"

after

[routers] [routers.my-router] entrypoint = ["http"] rule = "Host:myhost.com" middlewares = ["admins-only"] service = my-service [middlewares] [middlewares.admins-only.basicauth] users = ["admin:xxx","super-admin:xxx",] [services] [services.my-service.load-balancer] [[services.my-services.load-balancer.servers]] url = "http://xx.xx.xx.xx"

The basic authentication middleware is no longer configured inside the router but in a dedicated middleware section. The good news, besides readability, is that you can now reuse the same middleware in a different router, along with other middlewares.

[routers] [routers.my-router] entrypoint = ["http"] rule = "Host:myhost.com" middlewares = ["admins-only"] service = my-service [routers.my-other-router] entrypoint = ["http"] rule = "Host:myhost2.com" middlewares = ["admins-only", "retry-std"] service = my-service [middlewares] [middlewares.admins-only.basicauth] users = ["admin:xxx","super-admin:xxx",] [middlewares.retry-std.retry] attempts = 2 [services] [services.my-service.load-balancer] [[services.my-services.load-balancer.servers]] url = "http://xx.xx.xx.xx"

Also, another benefit you probably have noticed — you can now have many separate configurations for some features (like retry) and choose the one that fits for your routers.

Behind the scenes, many other things have already changed (not yet obvious), but we’d like to tease with more episodes!

Besides, we’ve decided to show you one “work in progress” feature at a time — this gives time to listen to your feedback!

Traefik is a project driven by the community! We are more grateful than ever for your feedback on the project. We’ve reached 18k+ stars on Github, and the product has been downloaded more than 350 million times!

Join us on GitHub, Twitter, on the Community Forum, or in the comments below so we can hear your voice!