Traefik: Reverse Proxy with Configuration Discovery and Let's Encrypt
You might already know the “cloud native” proxy Traefik from Kubernetes. Or not, as Kubernetes hides most of the configuration from you. But Traefik can be really useful on its own and has a lot of functionality. I find two of its features especially useful for smaller servers ot home servers: the configuration/service discovery and Traefiks ability to resolve and renew Let’s Encrypt certificates.
Traefik Configuration Discovery
Traefik configuration discovery decouples the service configuration from the proxy configuration. Instead of editing a proxy config file every time you want to add a new webpage, you place the configuration at a known source and let Traefik pick it up. This allows for modular deployments where the services that Traefik serves do not have to be known beforehand with minimal downtime of the proxy.
Traefik can watch multiple sources for new configurations of services that it shall act as a proxy for. The service configurations can be read from a file, etcd, Redis and others. One particularly useful variant is the Docker configuration discovery where Traefik reads the configuration from the labels of a running container.
Automatic Lets’t Encrypt certificates with Traefik
Traefik is able to obtain Let’s Encrypt certificates for the domains of your services. When discovering a new configuration, for instance when a docker container starts up, Traefik will check wether SSL is required and how the certificate shall be resolved. If the ACME resolver is selected, Traefik will obtain a SSL certificate for the domain the service will run on and also keep it up to date.
The domain in question has to point to the server that runs Traefik, of course.
The following example shows how to set up Traefik in a Docker container and discover services on the same Docker network, serving them with SSL certificates. I will reuse the jumphost VM created in my guide for Azure VM Deployment With Ansible.
Making the VM a Docker Host
The VM we created earlier is not yet capable of running Docker containers. We will add this functionality now including a Docker network to which we will attach all our containers.
Additionally we add our user to the docker group so we can manage the docker containers without having to sudo every time.
In an earlier post I already highlighted how to name your Docker network and why.
Traefik Proxy Installation
Now we can already install the Traefik. We start it as a Docker container and expose ports 80 and 443. You will find these ports later in the playbook. This makes the script more versatile.
Apart from the container we just check that the needed directories are present and the config file is available.
The config file is relatively short. We only tell Traefik where to discover the configurations, paste the entrypoints and configure the resolver for the Let’s Encrypt certificates.
An entrypoint is an IP/port combination on which Traefik will listen. We can later choose the names of these entrypoints in our service configuration and therefore specify where a service is available. The names have to be consistent in Traefik and the Docker container labels where they are referenced.
We will use two entrypoints: web for port 80 and websecure for port 443.
In our variables file we just configure the location of the mount point for the traefik config.
Ok, done. Now we need a service to show off.
Grafana with Traefik Autodiscovery
Grafana will do nicely for our demonstration because it doesn’t need other services on our VM. Just like before we mainly ensure the necessary directories and copy over the config file.
The labels of the grafana container are what configures Traefik. There, we specify the URL that Grafana shall be on, the port of the grafana container to proxy, that we want to use the letsEncryptResolver from the Traefik configuration to resolve a SSL certificate and that we want to use compression on responses.
For Grafana it’s not enough to specify the URL in the container label. Grafana wants to know about it separately. Grafana is very picky when it comes to this URL. When it’s not spot on, Grafana won’t work properly.
Also we set an admin password in our configuration. This service will be on the open web after all.
Now we specify the password variable alongside the config directories in the vars file. But it points to a special vars file that we call vault.yml. There we place the actual password.
The vault file looks like this:
The reason for storing the password in an extra file is, that we will later encrypt the vault file so nobody with access to the code can get the Grafana password. Still, we want to be able to locate the grafana_admin_password variable. We cannot search in the encrypted vault file, though. Therefore we create the indirection that points to the vault file and makes it easy for us to find our variables.
You can encrypt the vault file like this:
ansible-vault encrypt roles/grafana/vars/vault.yml
Combining all Ansible Roles
Before we execute the playbook we have to tell Ansible where it can find the VM. Be sure to reference the ssh key that you used in creating the jumphost VM in the Azure VM Deployment With Ansible post.
The playbook then targets the jumphost and applies all the roles we created above. Note that here we also pass the variables we referenced earlier: the docker network subnet, the Traefik published ports and entrypoints.
Now we can execute the playbook. If you have already encrypted your vault file, use the –ask-vault-pass parameter.
ansible-playbook --ask-vault-pass jumphost.yml
Grafana should now be accessible under azuredemojumphost.northcentralus.cloudapp.azure.com (you should pick your own URL).