What you will learn today 🦦:
- what is Traefik and why I decisively recommend to use it with Docker Swarm
- how to show custom error page if Traefik returns 404 (service not found)
How I met Traefik 😁
Traefik is a top modern reverse proxy and load balancer. I started to use it from major version 1. At that moment, I already had Docker Swarm cluster, which I began to prepare for production use, and only one thing in the coherent picture has been left: “External Load Balancer”. Official docker doc had an example with HaProxy so it was obvious to try it out first. As I remember it took a significant amount of time to get HaProxy works smoothly with Swarm. But I wasn’t satisfied with the result:
- static config which is needed to re-deploy in case of changes
- *.cfg not supports variables substitution (out of the box)
- no native support of Lets’s Encrypt
The production deadline was getting close, but it was out of my religion to leave such an ugly solution. I was searching the Internet and bumped into Traefik. Integration was surprisingly fast and easy. I thought to myself: “That is how modern DevOps should look like”. It is been 3 years since I deal with Traefik, and today I wanna tell about one of the most much wished-for feature.
Step by step setup 🐟 🐒 🦧 ⤴️ 👷🏼
Traefik v2.x comes to us with middlewares and ErrorPage
one of them.
Lett’s assume:
- traefik dasboard domain: traefik-board.com
- your app: my-app.com
0001. Add labels to traefik. Important here traefik.http.routers.traefik.middlewares=maintenance-page@docker
. If you try to look for https://service-does-not-exists.com you will get friendly error page.
traefik:
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.services.traefik.loadbalancer.server.port=8080"
- "traefik.http.routers.traefik.rule=Host(`${traefik-board.com}`)"
- "traefik.http.routers.traefik.entryPoints=websecure"
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.routers.traefik.middlewares=maintenance-page@docker"
0010. Create dedicated service for error page. Make sure service would be as highly available as Traefik! I recommend to set deploy mode: global
and placement: constraints: - node.role == manager
. If you don’t, hello boring black page again.
maintenance-page:
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.services.maintenance-page.loadbalancer.server.port=80"
- "traefik.http.routers.maintenance-page.rule=HostRegexp(`{host:.+}`)"
- "traefik.http.routers.maintenance-page.priority=1"
- "traefik.http.routers.maintenance-page.middlewares=maintenance-page@docker"
- "traefik.http.routers.maintenance-page.entrypoints=websecure"
- "traefik.http.routers.maintenance-page.tls=true"
- "traefik.http.middlewares.maintenance-page.errors.status=400-599"
- "traefik.http.middlewares.maintenance-page.errors.service=maintenance-page"
- "traefik.http.middlewares.maintenance-page.errors.query=/"
These lines are essential. You tell the server to catch all requests and redirect them to maintenance-page service. priority=1
means “all-catcher” is not alone in the system. There is someone higher who wants to handle requests firstly, for example, “all-catcher” for http->https redirect.
traefik.http.routers.maintenance-page.priority=1
traefik.http.routers.maintenance-page.rule=HostRegexp(`{host:.+}`)
0011. Add priority=100
to app service.
app:
deploy:
labels:
- "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`${my-app.com}`)"
- "traefik.http.services.frontend.loadbalancer.server.port=80"
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls=true"
- "traefik.http.routers.frontend.priority=100"
Testing 🤹🏽♀️
- case 1: app service goes down or updating
replicas: 0:1
-> see maitenance page - case 2: user searched for https://service-does-not-exists.com -> see maitenance page
Further reading 👩🏽💻
- hot thread about this topic on the Containous Community Forum
So, that’s all for now 😊. 👩🚀 was happy to share my experience, and I’m sure this solution will save a bunch of time for someone. Happy hacking=) 🦾