Introduction
Traefik is a cloud native reverse proxy, meaning it’s both a bodyguard and a guide to your backend. It intercepts incoming requests and routes them to the intended services according to rules set by you, potentially even modifying the requests. There are a multitude of benefits from using a reverse proxy:
- Load Balancing: Traefik can distribute incoming traffic to maximize speed and utilization of your services
- Request Acceleration: Traefik can compress incoming and outgoing data; in the future, Traefik may also support caching
- Security: Traefik protects your backend service’s identity and acts as another layer of defence by acting as the gateway
Core definitions
Traefik revolves around three key concepts:
- Entrypoints are essentially synonyms for a port and protocol tuple (i.e.
5000/udp
), with[host]:port[/tcp|/udp]
being its exact definition - Services forward the incoming request to your actual services
- Routers serve as an intermediary between Entrypoints and Services. Routers have user defined rules that decide which Traefik service the incoming request should go to
Let’s return to our bodyguard and guide metaphor. Entrypoints are the heavily forfeited doors to our backend. Once a request has gone through the door it is handled by our bodyguards (Routers) which ensure the request should be here and find out where it wishes to go. The bodyguards then escort the request to our guides (Services) which handle delivery to our backend service.
Here’s a high level architecture diagram of Traefik:
An incoming request talks to an Entrypoint. Routers corresponding to that Entrypoint examine the request (Host, Path, Headers, Method) to determine which Service this request should be handed off to. The Service that receives the request is responsible for forwarding the request to its end destination.
Having these key components divided and independent of one another allows Traefik to be customized to every use case.
Simple Example
Let’s take a look at a simple example to better understand Traefik’s key concepts. Let’s imagine we have a simple service called whoami
that we are looking to expose to the outer world. whoami
simply returns information about the machine it is deployed on.
For this tutorial we will be using Docker and Docker-compose. We’ll create a docker-compose.yml
file defining our Traefik and whoami
service.
version: "3"
services:
reverse-proxy:
# The official v2 Traefik docker image
image: traefik:v2.3
ports:
# The HTTP port
- "80:80"
# The Web UI (enabled by api.insecure=true)
- "8080:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
# So that we can configure Traefik
- "./config.toml:/etc/traefik/traefik.toml"
whoami:
# A container that exposes an API to show its host IP address
image: containous/whoami
Next, create a file in the same location as the docker-compose.yml
called config.toml
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints] # Creating an entrypoint listening on port 80 with the default protocol of TCP
[entryPoints.server]
address = ":80"
[http.routers] # Creating a router which routes all requests matching Host == whoami.docker.localhost to the whoami-service
[http.routers.my-router]
rule = "Host(`whoami.docker.localhost`)"
service = "whoami-service"
[http.services] # Defining a service called whoami-service with its accompanying url
[http.services.whoami-service.loadBalancer]
[[http.services.whoami-service.loadBalancer.servers]]
url = "http://whoami:80"
[api]
insecure = true # Enables the web UI
[providers] # Providers will be looked at in the next section
[providers.file]
filename = "/etc/traefik/traefik.toml"
Start the containers using docker-compose up
. Then in another terminal let’s curl our whoami
service.
curl -H Host:whoami.docker.localhost http://127.0.0.1
returning
Hostname: 10a2a065a5a7
IP: 127.0.0.1
...
So what we’ve done is defined a TCP entrypoint on port 80. We’ve also created a http router that will examine the incoming request coming in on port 80 (specifically the Host header) and any requests with a Host header of whoami.docker.localhost
will be routed to the whoami-service
service. Next, we define our http service whoami-service
which simply defines the URL of our docker container.
All relatively simple and straightforward, albeit not particularly useful.
Providers: A deeper look into Traefik
Traefik’s configuration has two separate forms: static and dynamic. The static portion can be configured as part of a file, CLI arguments, or environment variables. The dynamic section can be fed to Traefik in a multitude of formats. The most time consuming is in the file alongside the static portion, but it’s also possible to use your favorite orchestrator (Docker, Kubernetes, Consul Catalog, …) or key value store (Redis, Zookeeper, etcd) to feed Traefik its dynamic configuration. Entrypoints and Providers are defined in the static portion, while Routers and Services are defined in the dynamic portion. Providers are existing infrastructure components (Docker, Kubernetes, Redis, Zookeeper, …) which can be queried by Traefik to automatically discover new Routers and Services.
Providers give Traefik lots of flexibility as Routers and Services can be defined after Traefik has already started which can be useful as your services go offline or get put on new machines.
We’ll be using the Docker provider for the rest of the tutorial.
Real Use Case
Now, let’s imagine a more realistic use case for Traefik where we have several microservices our clients wish to fetch data from. Our system represents a simplistic shopping website with services called order
(with a URI of /order
), account
(with a URI of /api/v1/account
) and inventory
(with a URI of /api/v1/inventory
). All services performing actions related to their names; We’ll also have an auth
service.
# handles all requests related to orders. Requires authentication
order:
image: containous/whoami
# handles all requests related to inventory
inventory:
image: containous/whoami
# handles all requests related to accounts. Requires authentication
account:
image: containous/whoami
# our auth service which all calls to account/order must go through
auth:
image: lthummus/auththingie
Since any user is free to browse our inventory
, those routes can be accessed without authorization. But, for our all order
and account
requests the user must be authenticated. We can accomplish this using Traefik’s middleware pattern.
Middlewares attach to a router and enable us to modify the request using Traefik’s pre-created middlewares.
The ForwardAuth middleware delegates user authentication to an external service (for us we will delegate to our auth
service).
reverse-proxy:
image: traefik:v2.3
...
labels:
- "traefik.enable=true"
# defining a ForwardAuth middleware called test-auth, which forwards all authentication to the auth service
- "traefik.http.middlewares.test-auth.forwardauth.address=http://reverse-proxy/auth"
Defining all middlewares under reverse-proxy
is not necessary, and similar to variable definition the middlewares definition should correspond to its usage.
Now, let’s have the order
service utilize our test-auth
middleware
# handles all requests related to orders. Requires authentication
order:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.order.entrypoints=server"
- "traefik.http.routers.order.middlewares=test-auth" # specifying what middlewares to use
- "traefik.http.routers.order.rule=Path(`/order`)"
Putting that all together we have a docker-compose.yml
file:
version: "3"
services:
reverse-proxy:
image: traefik:v2.3
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- "./config.toml:/etc/traefik/traefik.toml"
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.test-auth.forwardauth.address=http://reverse-proxy/auth"
# handles all requests related to orders. Requires authentication
order:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.order.entrypoints=server"
- "traefik.http.routers.order.middlewares=test-auth"
- "traefik.http.routers.order.rule=Path(`/order`)" # the order service handles all requests with a path of /order
# handles all requests related to inventory
inventory:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.inventory.entrypoints=server"
- "traefik.http.routers.inventory.rule=Path(`/api/v1/inventory`)" # the inventory service handles all requests with a path of /api/v1/inventory
# handles all requests related to accounts. Requires authentication
account:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.account.entrypoints=server"
- "traefik.http.routers.account.middlewares=test-auth"
- "traefik.http.routers.account.rule=Path(`/api/v1/account`)" # the account service handles all requests with a path of /api/v1/account
# our auth service which all calls to account/order must go through
auth:
image: lthummus/auththingie
ports:
- "9000:9000"
volumes:
- ./authconfig.conf:/authconfig.conf
environment:
- AUTHTHINGIE_CONFIG_FILE_PATH=/authconfig.conf
labels:
- "traefik.enable=true"
- "traefik.http.routers.auth.entrypoints=server"
- "traefik.http.routers.auth.rule=Path(`/auth`)"
And a new updated config.toml
file:
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.server]
address = ":80"
[entryPoints.server.forwardedHeaders]
insecure = true # necessary for the auth service we are using. Not to be used in production
[api]
insecure = true # Enables the web UI
[providers.docker]
exposedByDefault = false
As well as a authconfig.conf
for configuring our auth
service.
auththingie {
domain: reverse-proxy
secretKey: "test"
rules: [
{
"name": "/*",
"pathPattern": "/*",
"hostPattern": "reverse-proxy",
"public": true,
"permittedRoles": []
}
]
users: [
{
"htpasswdLine": "test:$2y$05$WgJrjE0ao3gbysOcE1F6.utXniEudCDEQ5EABjkBOVHze5iM/rFu2",
"admin": true,
"roles": [],
}
]
authSiteUrl = "http://127.0.0.1:9000"
}
Now, let’s restart our solution with Ctrl + C
and docker-compose up
.
Next, let’s get some orders with curl http://127.0.0.1/order
which will return an empty respond. Why? Because we are not authenticated and Traefik did not forward our request to the backend. Let’s now authenticate by passing an Authorization
header (corresponding to the htpasswdLine
we set in the authconfig.conf
but that’s not too important).
curl http://127.0.0.1/order -H "Authorization: Basic dGVzdDp0ZXN0"
Hostname: a9b412305755
IP: 127.0.0.1
...
Our auth service verifies the Authorization
token we sent it (you can verify the authentication process by sending a different Authorization
token and seeing the request fail) then proceeds to forward the request to the actual order
service.
We can test out our account
service in a similar fashion
~/workspace# curl http://127.0.0.1/api/v1/account -H "Authorization: Basic dGVzdDp0ZXN0"
Hostname: 8c8415d0c974
IP: 127.0.0.1
...
~/workspace# curl http://127.0.0.1/api/v1/account -H "Authorization: Basic dGVzdDp0ZXM=" -v
...
>
< HTTP/1.1 401 Unauthorized
But we can access our inventory without any need for authentication.
~/workspace# curl http://127.0.0.1/api/v1/inventory
Hostname: 570d161310c2
IP: 127.0.0.1
...
Advanced Middleware Usage
You may have noticed in the previous example the order
service responded to /order
requests. While account
and inventory
had a prefix of /api/v1/
in their paths. Let’s pretend that the team reworked the order
service to have two APIs: the old one is available at /api/v1/order
, and a new one is available at /api/v2/order
. We would like to remedy this situation without having to discontinue any clients that continue to use the /order
endpoint.
We’ll utilize the Chain and AddPrefix middlewares to accomplish this.
# handles all requests related to orders. Requires authentication
order:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.order_deprecated.entrypoints=server"
# our order service has gone through a complete rehaul, resulting in an /api/v1/order and /api/v2/order. However originally all clients simply used /order so we can add /api/v1 path prefix to any request that requests /order.
- "traefik.http.routers.order_deprecated.middlewares=deprecated-clients"
- "traefik.http.routers.order_deprecated.rule=Path(`/order`)"
- "traefik.http.routers.order.entrypoints=server"
- "traefik.http.routers.order.middlewares=test-auth"
- "traefik.http.routers.order.rule=Path(`/api/v2/order`) || Path(`/api/v1/order`)"
- "traefik.http.middlewares.deprecated-clients.chain.middlewares=add-version,test-auth"
- "traefik.http.middlewares.add-version.addprefix.prefix=/api/v1"
We’ll utilize the same authconfig.conf
and config.toml
as before.
authconfig.conf
auththingie {
domain: reverse-proxy
secretKey: "test"
rules: [
{
"name": "/*",
"pathPattern": "/*",
"hostPattern": "reverse-proxy",
"public": true,
"permittedRoles": []
}
]
users: [
{
"htpasswdLine": "test:$2y$05\$WgJrjE0ao3gbysOcE1F6.utXniEudCDEQ5EABjkBOVHze5iM/rFu2",
"admin": true,
"roles": [],
}
]
authSiteUrl = "http://127.0.0.1:9000"
}
config.toml
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.server]
address = ":80"
[entryPoints.server.forwardedHeaders]
insecure = true # necessary for the auth service we are using. Not to be used in production
[api]
insecure = true # Enables the web UI
[providers.docker]
exposedByDefault = false
With an updated docker-compose.yml
.
docker-compose.yml
version: "3"
services:
reverse-proxy:
image: traefik:v2.3
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- "./config.toml:/etc/traefik/traefik.toml"
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.test-auth.forwardauth.address=http://reverse-proxy/auth"
# handles all requests related to orders. Requires authentication
order:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.order_deprecated.entrypoints=server"
# our order service has gone through a complete rehaul, resulting in an /api/v1/order and /api/v2/order. However originally all clients simply used /order so we can add /api/v1 path prefix to any request that requests /order.
- "traefik.http.routers.order_deprecated.middlewares=deprecated-clients"
- "traefik.http.routers.order_deprecated.rule=Path(`/order`)"
- "traefik.http.routers.order.entrypoints=server"
- "traefik.http.routers.order.middlewares=test-auth"
- "traefik.http.routers.order.rule=Path(`/api/v2/order`) || Path(`/api/v1/order`)"
- "traefik.http.middlewares.deprecated-clients.chain.middlewares=add-version,test-auth"
- "traefik.http.middlewares.add-version.addprefix.prefix=/api/v1"
# handles all requests related to inventory
inventory:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.inventory.entrypoints=server"
- "traefik.http.routers.inventory.rule=Path(`/api/v1/inventory`)" # the inventory service handles all requests with a path of /api/v1/inventory
# handles all requests related to accounts. Requires authentication
account:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.account.entrypoints=server"
- "traefik.http.routers.account.middlewares=test-auth"
- "traefik.http.routers.account.rule=Path(`/api/v1/account`)" # the account service handles all requests with a path of /api/v1/account
# our auth service which all calls to account/order must go through
auth:
image: lthummus/auththingie
ports:
- "9000:9000"
volumes:
- ./authconfig.conf:/authconfig.conf
environment:
- AUTHTHINGIE_CONFIG_FILE_PATH=/authconfig.conf
labels:
- "traefik.enable=true"
- "traefik.http.routers.auth.entrypoints=server"
- "traefik.http.routers.auth.rule=Path(`/auth`)"
After restaritng our solution with Ctrl + C
and docker-compose up
. Clients can now request /api/v1/order
, /api/v2/order
and /order
.
~/workspace# curl http://127.0.0.1/order -H "Authorization: Basic dGVzdDp0ZXN0"
Hostname: 570d161310c2
IP: 127.0.0.1
...
~/workspace# curl http://127.0.0.1/api/v1/order -H "Authorization: Basic dGVzdDp0ZXN0"
Hostname: 570d161310c2
IP: 127.0.0.1
...
~/workspace# curl http://127.0.0.1/api/v2/order -H "Authorization: Basic dGVzdDp0ZXN0"
Hostname: 570d161310c2
IP: 127.0.0.1
...
UDP
Let’s also expand our services to include a track
service which sends minute by minute GPS data of ones purchase. Since this is minutely data it’s okay if a packet gets lost here and there, so we’ll use UDP for this service.
# handles sending minutely data about a purchase
track:
image: containous/whoamiudp
labels:
- "traefik.enable=true"
- "traefik.udp.routers.track.entrypoints=server-udp"
We’ll also have to create a new entrypoint that listens for UDP.
[entryPoints]
...
[entryPoints.server-udp]
address = ":81/udp"
And expose port 81 on Traefik’s container
reverse-proxy:
image: traefik
ports:
- "80:80"
- "81:81/udp"
- "8080:8080"
...
docker-compose.yml
version: "3"
services:
reverse-proxy:
image: traefik:v2.3
ports:
- "80:80"
- "81:81/udp"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- "./config.toml:/etc/traefik/traefik.toml"
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.test-auth.forwardauth.address=http://reverse-proxy/auth"
# handles all requests related to orders. Requires authentication
order:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.order_deprecated.entrypoints=server"
# our order service has gone through a complete rehaul, resulting in an /api/v1/order and /api/v2/order. However originally all clients simply used /order so we can add /api/v1 path prefix to any request that requests /order.
- "traefik.http.routers.order_deprecated.middlewares=deprecated-clients"
- "traefik.http.routers.order_deprecated.rule=Path(`/order`)"
- "traefik.http.routers.order.entrypoints=server"
- "traefik.http.routers.order.middlewares=test-auth"
- "traefik.http.routers.order.rule=Path(`/api/v2/order`) || Path(`/api/v1/order`)"
- "traefik.http.middlewares.deprecated-clients.chain.middlewares=add-version,test-auth"
- "traefik.http.middlewares.add-version.addprefix.prefix=/api/v1"
# handles all requests related to inventory
inventory:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.inventory.entrypoints=server"
- "traefik.http.routers.inventory.rule=Path(`/api/v1/inventory`)" # the inventory service handles all requests with a path of /api/v1/inventory
# handles all requests related to accounts. Requires authentication
account:
image: containous/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.account.entrypoints=server"
- "traefik.http.routers.account.middlewares=test-auth"
- "traefik.http.routers.account.rule=Path(`/api/v1/account`)" # the account service handles all requests with a path of /api/v1/account
# our auth service which all calls to account/order must go through
auth:
image: lthummus/auththingie
ports:
- "9000:9000"
volumes:
- ./authconfig.conf:/authconfig.conf
environment:
- AUTHTHINGIE_CONFIG_FILE_PATH=/authconfig.conf
labels:
- "traefik.enable=true"
- "traefik.http.routers.auth.entrypoints=server"
- "traefik.http.routers.auth.rule=Path(`/auth`)"
# handles sending minutely data about a purchase
track:
image: containous/whoamiudp
labels:
- "traefik.enable=true"
- "traefik.udp.routers.track.entrypoints=server-udp"
config.toml
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints]
[entryPoints.server]
address = ":80"
[entryPoints.server.forwardedHeaders]
insecure = true # necessary for the auth service we are using. Not to be used in production
[entryPoints.server-udp]
address = ":81/udp"
[api]
insecure = true # Enables the web UI
[providers.docker]
exposedByDefault = false
authconfig.conf
auththingie {
domain: reverse-proxy
secretKey: "test"
rules: [
{
"name": "/*",
"pathPattern": "/*",
"hostPattern": "reverse-proxy",
"public": true,
"permittedRoles": []
}
]
users: [
{
"htpasswdLine": "test:$2y$05$WgJrjE0ao3gbysOcE1F6.utXniEudCDEQ5EABjkBOVHze5iM/rFu2",
"admin": true,
"roles": [],
}
]
authSiteUrl = "http://127.0.0.1:9000"
}
First let’s restart our solution with Ctrl + C
and docker-compose up
. Next, using nc
(Netcat) we can confirm the ability to connect to our new track
service.
~/workspace# nc 127.0.0.1 81 -v -u
WHO
Connection to 127.0.0.1 81 port [udp/*] succeeded!
Received: XReceived: XReceived: XReceived: XReceived: XHostname: 784d3e419126
IP: 127.0.0.1
IP: 172.29.0.3
With this you’ve covered the majority there is to know on Traefik. The key is to fully understand Providers, Entrypoints, Routers and Services then the rest is just fluff for specific use cases.