Traefik as a dynamic reverse proxy for Docker Swarm

In this post we will setup Traefik as a Dynamic reverse proxy for a Docker Swarm. We will be able to run the same on a Raspberry Pi or on x86.

If you want to see how to do the same with HAProxy, check this post!

Why a Dynamic reverse proxy and load balancer?

Classic loadblancers and proxies (nginx, haproxy, apache) were built with a static backend in mind and are usually enhanced with other projects such as Victor Farcic' HA docker-flow-proxy or jwilder/nginx. Traefik is built for a dynamic world from the start.

Why Traefik?

Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. It supports several backends (Docker, Swarm, Kubernetes, Marathon, Mesos, Consul, Etcd, Zookeeper, BoltDB, Eureka, Rest API, file...) to manage its configuration automatically and dynamically.

To be honest, I wanted to try it as I really like the logo :)
(of course in order to learn a new reverse proxy too)

Ok. Let's go now.

Deploy Traefik on the Swarm

Create an overlay network
docker network create --driver=overlay traefik-net
Deploy Traefik
docker service create \
--name traefik \
--constraint 'node.role==manager' \
--publish 80:80 \
--publish 8080:8080 \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--network traefik-net \
traefik:camembert \
--docker \
--docker.swarmmode \
--docker.domain=jmkhael.io \
--docker.watch \
--logLevel=DEBUG \
--web

We will next deploy some services to mimic the below screenshot. We will configure several frontends serving different hosts while themselves are being served by different backends.

Deploy api service
docker service create \
--name api \
--label 'traefik.port=5000' \
--network traefik-net \
jmkhael/myservice:0.0.1
Deploy backoffice service
docker service create \
--name backoffice \
--label 'traefik.port=5000' \
--network traefik-net \
jmkhael/myservice:0.0.2

Scale the backoffice service to 3 instances to match the screenshot:

docker service scale backoffice=3

Deploy web service

docker service create \
--name web \
--label 'traefik.port=5000' \
--label traefik.frontend.rule="Host:jmkhael.io; Path: /web/" \
--network traefik-net \
jmkhael/myservice:0.0.2

Test this

Issue the commands:

curl -H Host:api.jmkhael.io http://0.0.0.0
curl -H Host:backoffice.jmkhael.io http://0.0.0.0
curl -H Host:jmkhael.io http://0.0.0.0/web/

and we should get valid output, like the below:

I'm 24ab5c2a9402
I'm eec66086b687
I'm aaf5402a2387

Checking the Visualizer we can see all the services running:

Checking http://stratus-clay:8080/ we can reach the Web dashboard of Traefik and see our frontends and backends well defined:

All this happening like...

Running the same thing on the Raspberry Pi

Deploy Traefik on Rpi

docker service create \
--name traefik \
--constraint 'node.role==manager' \
--publish 81:80 \
--publish 8081:8080 \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--network traefik-net \
hypriot/rpi-traefik \
--docker \
--docker.swarmmode \
--docker.domain=jmkhael.io \
--docker.watch \
--logLevel=DEBUG \
--web

Deploy two API Endpoints Service

One to respond on api.jmkhael.io

docker service create \
--name api \
--label 'traefik.port=8000' \
--network traefik-net \
hypriot/rpi-whoami

Another to respond on api.jmkhael.io/products/

docker service create \
--name products \
--label 'traefik.port=8000' \
--label traefik.frontend.rule="Host:api.jmkhael.io; Path: /products/" \
--network traefik-net \
hypriot/rpi-whoami

Deploy Blog service on blog.jmkhael.io

Constraint it to node blog as it has the data there... until we handle the statefulness better (nfs?)

docker service create \
--name blog \
--label 'traefik.port=2368' \
--label traefik.frontend.rule="Host:api.jmkhael.io, blog.jmkhael.io;" 
--constraint 'node.hostname==blog' \
--network traefik-net \
alexellis2/ghost-on-docker:armv6

Notice that we've written frontend rules for api and blog, as Ghost needs both to be redirected to it...
(that means that we cannot host api endpoints, at least not without thinking of a potential clash, as our /products/ above still works)

Test api

curl -H Host:api.jmkhael.io http://0.0.0.0:81
curl -H Host:api.jmkhael.io http://0.0.0.0:81/products/

If you check http://blog.jmkhael.io:81, you'll see the blog running :)
Sweet!

Let's also make sure that our products api endpoint is working too:

curl http://api.jmkhael.io:81/products/
I'm eec66086b687

Scale app

docker service scale api=3

curl -H Host:api.jmkhael.io http://0.0.0.0
curl -H Host:api.jmkhael.io http://0.0.0.0
curl -H Host:api.jmkhael.io http://0.0.0.0

This might seem to be "not working" (as expected), because the sessions are kept alive for a while - this issue will "correct" it.