11 KiB
Docker & Traefik
In this use case, we want to use Træfik as a layer-7 load balancer with SSL termination for a set of micro-services used to run a web application.
We also want to automatically discover any services on the Docker host and let Træfik reconfigure itself automatically when containers get created (or shut down) so HTTP traffic can be routed accordingly.
In addition, we want to use Let's Encrypt to automatically generate and renew SSL certificates per hostname.
Setting Up
In order for this to work, you'll need a server with a public IP address, with Docker installed on it.
In this example, we're using the fictitious domain my-awesome-app.org.
In real-life, you'll want to use your own domain and have the DNS configured accordingly so the hostname records you'll want to use point to the aforementioned public IP address.
Networking
Docker containers can only communicate with each other over TCP when they share at least one network. This makes sense from a topological point of view in the context of networking, since Docker under the hood creates IPTable rules so containers can't reach other containers unless you'd want to.
In this example, we're going to use a single network called web
where all containers that are handling HTTP traffic (including Træfik) will reside in.
On the Docker host, run the following command:
docker network create web
Now, let's create a directory on the server where we will configure the rest of Træfik:
mkdir -p /opt/traefik
Within this directory, we're going to create 3 empty files:
touch /opt/traefik/docker-compose.yml
touch /opt/traefik/acme.json && chmod 600 /opt/traefik/acme.json
touch /opt/traefik/traefik.toml
The docker-compose.yml
file will provide us with a simple, consistent and more importantly, a deterministic way to create Træfik.
The contents of the file is as follows:
version: '2'
services:
traefik:
image: traefik:1.3.5
restart: always
ports:
- 80:80
- 443:443
networks:
- web
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/traefik/traefik.toml:/traefik.toml
- /opt/traefik/acme.json:/acme.json
container_name: traefik
networks:
web:
external: true
As you can see, we're mounting the traefik.toml
file as well as the (empty) acme.json
file in the container.
Also, we're mounting the /var/run/docker.sock
Docker socket in the container as well, so Træfik can listen to Docker events and reconfigure it's own internal configuration when containers are created (or shut down).
Also, we're making sure the container is automatically restarted by the Docker engine in case of problems (or: if the server is rebooted).
We're publishing the default HTTP ports 80
and 443
on the host, and making sure the container is placed within the web
network we've created earlier on.
Finally, we're giving this container a static name called traefik
.
Let's take a look at a simple traefik.toml
configuration as well before we'll create the Træfik container:
debug = false
logLevel = "ERROR"
defaultEntryPoints = ["https","http"]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[retry]
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "my-awesome-app.org"
watch = true
exposedbydefault = false
[acme]
email = "your-email-here@my-awesome-app.org"
storage = "acme.json"
entryPoint = "https"
OnHostRule = true
[acme.httpChallenge]
entryPoint = "http"
This is the minimum configuration required to do the following:
- Log
ERROR
-level messages (or more severe) to the console, but silenceDEBUG
-level messages - Check for new versions of Træfik periodically
- Create two entry points, namely an
HTTP
endpoint on port80
, and anHTTPS
endpoint on port443
where all incoming traffic on port80
will immediately get redirected toHTTPS
. - Enable the Docker configuration backend and listen for container events on the Docker unix socket we've mounted earlier. However, new containers will not be exposed by Træfik by default, we'll get into this in a bit!
- Enable automatic request and configuration of SSL certificates using Let's Encrypt.
These certificates will be stored in the
acme.json
file, which you can back-up yourself and store off-premises.
Alright, let's boot the container. From the /opt/traefik
directory, run docker-compose up -d
which will create and start the Træfik container.
Exposing Web Services to the Outside World
Now that we've fully configured and started Træfik, it's time to get our applications running!
Let's take a simple example of a micro-service project consisting of various services, where some will be exposed to the outside world and some will not.
The docker-compose.yml
of our project looks like this:
version: "2.1"
services:
app:
image: my-docker-registry.com/my-awesome-app/app:latest
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: always
networks:
- web
- default
expose:
- "9000"
labels:
- "traefik.backend=my-awesome-app-app"
- "traefik.docker.network=web"
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.enable=true"
- "traefik.port=9000"
- "traefik.default.protocol=http"
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
- "traefik.admin.protocol=https"
- "traefik.admin.port=9443"
db:
image: my-docker-registry.com/back-end/5.7
restart: always
redis:
image: my-docker-registry.com/back-end/redis:4-alpine
restart: always
events:
image: my-docker-registry.com/my-awesome-app/events:latest
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: always
networks:
- web
- default
expose:
- "3000"
labels:
- "traefik.backend=my-awesome-app-events"
- "traefik.docker.network=web"
- "traefik.frontend.rule=Host:events.my-awesome-app.org"
- "traefik.enable=true"
- "traefik.port=3000"
networks:
web:
external: true
Here, we can see a set of services with two applications that we're actually exposing to the outside world.
Notice how there isn't a single container that has any published ports to the host -- everything is routed through Docker networks.
Also, only the containers that we want traffic to get routed to are attached to the web
network we created at the start of this document.
Since the traefik
container we've created and started earlier is also attached to this network, HTTP requests can now get routed to these containers.
Labels
As mentioned earlier, we don't want containers exposed automatically by Træfik.
The reason behind this is simple: we want to have control over this process ourselves. Thanks to Docker labels, we can tell Træfik how to create it's internal routing configuration.
Let's take a look at the labels themselves for the app
service, which is a HTTP webservice listing on port 9000:
- "traefik.backend=my-awesome-app-app"
- "traefik.docker.network=web"
- "traefik.frontend.rule=Host:app.my-awesome-app.org"
- "traefik.enable=true"
- "traefik.port=9000"
- "traefik.default.protocol=http"
- "traefik.admin.frontend.rule=Host:admin-app.my-awesome-app.org"
- "traefik.admin.protocol=https"
- "traefik.admin.port=9443"
We use both container labels
and service labels
.
Container labels
First, we specify the backend
name which corresponds to the actual service we're routing to.
We also tell Træfik to use the web
network to route HTTP traffic to this container.
With the traefik.enable
label, we tell Træfik to include this container in it's internal configuration.
With the frontend.rule
label, we tell Træfik that we want to route to this container if the incoming HTTP request contains the Host
app.my-awesome-app.org
.
Essentially, this is the actual rule used for Layer-7 load balancing.
Finally but not unimportantly, we tell Træfik to route to port 9000
, since that is the actual TCP/IP port the container actually listens on.
Service labels
Service labels
allow managing many routes for the same container.
When both container labels
and service labels
are defined, container labels
are just used as default values for missing service labels
but no frontend/backend are going to be defined only with these labels.
Obviously, labels traefik.frontend.rule
and traefik.port
described above, will only be used to complete information set in service labels
during the container frontends/bakends creation.
In the example, two service names are defined : default
and admin
.
They allow creating two frontends and two backends.
default
has only oneservice label
:traefik.default.protocol
. Træfik will use values set intraefik.frontend.rule
andtraefik.port
to create thedefault
frontend and backend. The frontend listens to incoming HTTP requests which contain theHost
app.my-awesome-app.org
and redirect them inHTTP
to the port9000
of the backend.admin
has all theservices labels
needed to create theadmin
frontend and backend (traefik.admin.frontend.rule
,traefik.admin.protocol
,traefik.admin.port
). Træfik will create a frontend to listen to incoming HTTP requests which contain theHost
admin-app.my-awesome-app.org
and redirect them inHTTPS
to the port9443
of the backend.
Gotchas and tips
- Always specify the correct port where the container expects HTTP traffic using
traefik.port
label.
If a container exposes multiple ports, Træfik may forward traffic to the wrong port. Even if a container only exposes one port, you should always write configuration defensively and explicitly. - Should you choose to enable the
exposedbydefault
flag in thetraefik.toml
configuration, be aware that all containers that are placed in the same network as Træfik will automatically be reachable from the outside world, for everyone and everyone to see. Usually, this is a bad idea. - With the
traefik.frontend.auth.basic
label, it's possible for Træfik to provide a HTTP basic-auth challenge for the endpoints you provide the label for. - Træfik has built-in support to automatically export Prometheus metrics
- Træfik supports websockets out of the box. In the example above, the
events
-service could be a NodeJS-based application which allows clients to connect using websocket protocol. Thanks to the fact that HTTPS in our example is enforced, these websockets are automatically secure as well (WSS)
Final thoughts
Using Træfik as a Layer-7 load balancer in combination with both Docker and Let's Encrypt provides you with an extremely flexible, powerful and self-configuring solution for your projects.
With Let's Encrypt, your endpoints are automatically secured with production-ready SSL certificates that are renewed automatically as well.