Canary Deployment mit traefik
In vielen Kundenprojekten ist der Wunsch zu beobachten, von klassisch betriebenen Anwendungen sofort in die Cloud oder zumindest nach Kubernetes zu migrieren. Vielleicht schwingt dabei der Wunsch mit, Zeit zu sparen, indem Zwischenschritte ausgelassen werden. Oder man ist sich sehr wohl bewußt, dass man in Gewissen Bereichen versäumt hat, Know-How aufzubauen und in Modernisierung zu investieren.
Wir empfehlen regelmäßig zumindest kleine Zwischenschritte einzuplanen, um Erfahrungen mit der Erstellung aber auch dem Design von Anwendungen für Container- und Cloudumgebungen zu sammeln. Das gilt um so mehr, wenn das Unternehmen sich nicht ganze Teams, die sich nur um Infrastruktur und Support kümmern können, leisten möchte.
Eine gute Möglichkeit zum Start stellt der Einsatz von Docker Containern ohne automatischen Orchestrator wie Kubernetes, Mesos oder Docker-Swarm dar. Dabei wählt man typischerweise eine Anwendung aus, die nicht absolut essentiell ist, und optimalerweise bereits von einem Team mit modernen Technologien und vor allem Mindset entwickelt und betreut wird.
Mit verhältnismäßig wenig Infrastruktur können dann auch bereits Patterns aus der Cloud-Welt verprobt werden und entsprechende Erfahrungen mit den notwendigen Umsystemen und Prozessen gewonnen werden. Wichtig ist dabei, dass man den Schwenk auf fertige Lösungen vornimmt, und nicht mit eigenen Mitteln Dinge nachbaut und wartet, die ein Orchestrator mitliefert.
Hat man sich für traefik als Reverseproxy und Loadbalancer entschieden, um Container verfügbar zu machen, kann man bereits von vielen Vorzügen profitieren.
Ein Kunde wünschte sich Canary-Deployments auszuprobieren, und das allein mit traefik. Wie so ein Canary oder A/B Deployment mit traefik umgesetzt werden kann, zeigt der folgende Beitrag.
Vorweg der Hinweise: Das Verfahren bezieht sich stets auf einen einzelnen Server. Natürlich kann der Server jedoch in einem Verbund betrieben werden.
traefik Proxy
Das besondere an traefik ist, dass er durch eine Verbindung zum Docker Daemon über Events benachrichtigt wird, wenn sich an den laufenden Containern etwas ändert. Darüber ist eine sehr dynamische Konfiguration möglich, die keine manuellen Eingriffe erforderlich macht. Auch über eine Konfigurationsdatei im TOML oder YAML Format kann traefik - ohne Neustart - umkonfiguriert werden.
Zunächst muss traefik selbst als container bereitgestellt werden.
Dazu bietet sich docker-compose
an, der folgende Ausschnitt zeigt ein traefik Deployment incl. dem traefik Dashboard auf http://traefik.localhost
.
version: "3.9"
networks:
traefik:
driver: bridge
name: traefik
services:
traefik:
image: "traefik:v2.4"
command:
#- "--log.level=DEBUG"
- "--api.insecure=true"
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--providers.file.filename=/opt/traefik/config.yaml"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./config:/opt/traefik"
labels:
- traefik.enable=true
# Dashboard
- traefik.http.routers.traefik.rule=Host(`traefik.localhost`)
- traefik.http.routers.traefik.service=api@internal
- traefik.http.services.traefik.loadbalancer.server.port=8080
networks:
- traefik
Weiterhin ist das Verzeichnis config
als Volume nach /opt/traefik
eingebunden und traefik wird angewiesen zusätzlich die config.yaml
als Konfigurationsquelle zu verwenden.
Das Netzwerk traefik
wird unter dem Namen bereitgestellt, damit weitere Container später nachgestartet werden können, die sich in exakt dies Netzwerk integrieren.
Die typische Isolation von docker-compose
für Netzwerke wird daher gezielt aufgehoben.
Anwendungscontainer
Als Beispielanwendung soll hier ein nginx
Webserver dienen, der lediglich eine Datei ausliefert, in der der Buchstabe A
steht.
Durch Label am Container erkennt traefik, dass der Container bereitgestellt weden soll, und wie er angesprochen werden soll.
In diesem Fall durch den Hostnamen example.localhost
.
app:
image: nginx:alpine
labels:
- traefik.enable=true
- traefik.http.routers.app_prod.rule=Host(`example.localhost`)
- traefik.docker.network=traefik
volumes:
- ./app/a.html:/usr/share/nginx/html/index.html
networks:
- traefik
Canary Deployment
Unter einem Canary Deployment versteht man, dass ein bestimmter Anteil von Anfragen auf eine neue Version einer Anwendung geht.
Gibt es dabei Probleme, so betrifft das nur einen geringen Anteil der Nutzer, und die Änderung kann zurückgenommen werden.
Die Canary Instanz kann z.B. durch bestimmte Regeln explizit angesprochen werden, dann verwendet man einen speziellen Hostnamen, zusätzliche Header oder einen Cookie.
In traefik würde man dazu die rule
anpassen.
Möchte man eine bestimmte Menge von Anfragen automatisch an die Canary Anwendung delegieren, kann traefik dies über ein Gewicht beim Loadbalancing vornehmen. Da traefik jedoch nur für Server bzw. Services ein Gewicht erlaubt, macht man sich für das Canary Szenario einen Trick zu Nutze: Man definiert einen separaten Service, der dann wiederum an die Container weiterleitet.
Diese Konfiguration ist über eine Konfigurationsdatei möglich, wie im folgenden zu sehen. Dabei wird eine Verteilung von 40:10 auf die produktive und Canary Instanz definiert.
config.yaml
http:
routers:
router-canary:
service: app_weighted
rule: "Host(`example.localhost`)"
services:
app_weighted:
weighted:
services:
- name: app_prod@docker
weight: 40
- name: app_canary@docker
weight: 10
Die einzelnen Docker Container app_prod
und app_canary
müssen dann über entsprechende Label für traefik definiert werden.
Wichtig ist hier, dass kein Router definiert wird, sondern lediglich die Services, auf die traefik dann die Requests verteilen soll.
app:
image: nginx:alpine
labels:
- traefik.enable=true
- traefik.http.services.app_prod.loadbalancer.server.port=80
- traefik.docker.network=traefik
volumes:
- ./app/a.html:/usr/share/nginx/html/index.html
networks:
- traefik
canary:
image: nginx:alpine
labels:
- traefik.enable=true
- traefik.http.services.app_canary.loadbalancer.server.port=80
- traefik.docker.network=traefik
volumes:
- ./app/b.html:/usr/share/nginx/html/index.html
networks:
- traefik
Die Datei kann jederzeit angepasst werden, und traefik konfiguriert sich automatisch um. Damit kann dann auch einfach zwischen einem reinen produktiven Deployment und einem Canary Deployment umgestellt werden.
Im traefik Dashboard sieht die Konfiguration anschließend so aus:
Canary Test
Das Canary Setup mit traefik soll natürlich auch getestet werden.
Mit einem einfachen Shellscript können Requests generiert werden und anschließend die Anzahl der Antworten mit A
und B
gezählt werden.
#!/bin/bash
for i in {1..100}; do curl -s example.localhost:80 ; done | sort | uniq -c
Das Ergebnis ist in der folgenden Ausgabe zu sehen:
$ ./check.sh
80 A
20 B
traefik Canary Deployment Fazit
Das gezeigte Setup ist gut zur Veranschaulichung geeignet und kann auch für Anwendungsfälle wie A/B-Tests erweitert werden. Für einfache Szenarien handelt es sich um eine funktionale Lösung mit wenig Komplexität und geringem Ressourcenbedarf.
Jedoch ist das ganze keinesfalls mit der Robustheit einer mit Kubernetes umgesetzten Lösung vergleichbar.
Fällt hier ein Container vollständig aus, findet traefik den Service nicht mehr, und alle Requests resultieren in einem Fehler.
Dazu kommt, dass manuelle Schritte nötig sind, um die Konfigurationsdatei von traefik anzupassen.
Fängt man an der Stelle an, über Automatisierung nachzudenken, ist auch der Moment erreicht, in dem man über eine umfassendere Lösung, wie Kubernetes, nachdenken sollte.
Zu den Themen Kubernetes, Docker und Cloud Architektur bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:
Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.