Friedrich Ellmer

Frachtwerk goes Europe Teil 3: Wie wir unsere Domain-Konfiguration bei Frachtwerk automatisiert haben

Als wir mit Frachtwerk 2017 gestartet sind, war die Welt noch unkompliziert und einfach… Ein paar Server in die Welt gestellt, apache2 oder nginx drauf installiert und ein paar händische Konfigurationen geschrieben (und Projekte aus gitlab via Webhooks deployed, aber das Kapitel verschweigen wir besser!). Doch mit der Zeit wurde die Welt spannender – und komplexer. Mittlerweile betreiben wir bei Frachtwerk 840 Docker-Container mit 308 Domains – und wollen die mit “Frachtwerk goes Europe” auch noch auf mehrere unabhängige Standorte verteilen.

Die Frage ist also, wie wir die richtige Konfiguration auf die richtigen Server bekommen und wie wir den Traffic korrekt durch unser Netz geroutet bekommen. Dieser Artikel soll einen kleinen Einblick darin geben, wie wir unsere Deployment-Prozesse automatisiert haben. Dafür schauen wir zuerst auf unsere Infrastruktur und die Datenbasis, die wir haben. Danach darauf, wie wir das Deployment umgesetzt haben und zuletzt, was wir mit unserem Monitoring gemacht haben – denn ohne gutes Monitoring kein guter Betrieb!

Infrastruktur und Datenbasis

Die Basis für alles ist immer erstmal, eine brauchbare Datenbasis zu haben. Und was ist dafür besser geeignet als LDAP? Viele Leser:innen werden an dieser Stelle bestimmt schon die Stirn runzeln, warum wir nicht den bequemen Weg gehen und unsere Nutzer:innen nicht einfach bei Microsoft 365 ins Entra ID rühren – die Antwort ist ganz einfach: Weil wir schon von Anfang an viel zu viel Spaß am eigenen Betrieb hatten! Mal ein eigenes LDAP aufsetzen zu können, ist ja auch eine Erfahrung für sich. Wir hatten also schon ziemlich bald nach Frachtwerk-Gründung einen eigenen LDAP-Server und was soll man sagen – eine inetorgperson kommt selten allein. Mit einem Ort, an dem alle Nutzer- und Gruppendaten liegen, kam die Idee auf, das Schema sukzessive zu erweitern. Dadurch ergaben sich diverse Erweiterungen:

Zum einen die Erweiterung der Benutzerobjekte um Personalstammdaten wie Geburtsdatum, Bankdaten, Start- und Enddatum bei Frachtwerk, Notfallkontakt und ein paar mehr. Über Node-Red haben wir eine Synchronisation mit DATEV gebaut, um die Daten nicht an zwei Stellen pflegen zu müssen.

Zum anderen unterschiedliche Gruppen, die wir initial primär als Mailinglisten genutzt haben. Mit der Zeit haben wir dann aber gemerkt, dass wir Gruppen auch prima für Serverzugänge nutzen können: Da unsere interne PKI (“Public Key Infrastructure”, die Verwaltung von digitalen Schlüsseln und Zertifikaten) die öffentlichen Schlüssel der Nutzer:innen direkt im LDAP speichert, könnte man ja Admin-Gruppen anlegen, die für die Administration bestimmter Server zuständig sind und die öffentlichen Schlüssel der Nutzer:innen direkt als SSH-Pubkeys auf den entsprechenden Servern ausrollen.

Das hat dann dazu geführt, dass es nur folgerichtig war, auch unsere Server direkt im LDAP zu verwalten – denn dann ließe sich ja direkt im gleichen Tool eine Zuordnung von Admingruppen zu Servern vornehmen. Gesagt, getan, also entstand ein FWHost-Objekt:

olcObjectClasses: {0}( 1.3.6.1.4.1.53146.1.1.2.1
    NAME 'FWHost'
    DESC 'host with in infrastructure'
    SUP top
    STRUCTURAL
    MUST ( cn $ ip )
    MAY ( usergroup $ admingroup $ modules $ port $ ansibleConfig) )

(Kleiner Einschub an dieser Stelle: Die Nummer 1.3.6.1.4.1.53146 ist natürlich nicht zufällig gewählt, sondern unser eigenes OID bei der IANA: https://oid-base.com/get/1.3.6.1.4.1.53146 – auf die wir natürlich auch ein kleines bisschen stolz sind ;-))

Das Attribut admingroup ist dabei die Verlinkung zur entsprechenden GroupOfNames in LDAP. Auf das Attribut ansibleConfig kommen wir später noch zu sprechen!

Und zuletzt war es dann nur noch ein letzter logischer Schritt, auch unsere Domains in LDAP zu verwalten. Denn dannn können wir sie da ja direkt mit dem Server verheiraten, auf dem das entsprechende Tool läuft! Also entstand zusätzlich noch Folgendes:

olcObjectClasses: {1}( 1.3.6.1.4.1.53146.1.1.2.2
    NAME 'FWDomain'
    DESC 'domain within infrastructure'
    SUP top
    ABSTRACT
    MUST ()
    MAY (targetHost $ cn) )
    
olcObjectClasses: {2}( 1.3.6.1.4.1.53146.1.1.2.3
    NAME 'FWProxyConfig'
    DESC 'domain within infrastructure'
    SUP FWDomain
    STRUCTURAL
    MAY ( port $ whitelistedIPs $ proxyConfig $ proxyLocationConfig )
    
olcObjectClasses: {1}( 1.3.6.1.4.1.53146.1.2.2.1 NAME 'EAMHost'
    DESC 'object for enterprise asset information'
    SUP top AUXILIARY
    MUST ( technicalAdmin $ provider $ serverName )
    MAY ( hostDescription $ parentHost $ levelAvailability $ levelConfidentiality )
    )
 
olcObjectClasses: {2}( 1.3.6.1.4.1.53146.1.2.2.2 NAME 'EAMApplication'
    DESC 'object for enterprise asset information'
    SUP FWDomain
    AUXILIARY
    MUST ( applicationName $ technicalAdmin $ functionalAdmin $ levelAvailability $ levelConfidentiality )
    MAY ( requiredApplications $ linkedApplications $ applicationDescription $ state $ dockerStackName $ projectID $ customer $ activeSince $ activeUntil $ iconURL $ type $ product $ loginURL $ authenticationProvider)
    )

In Summe ergibt sich in LDAP also folgendes Bild:

Ausführung

Soweit also die Theorie. In der Praxis haben wir uns schon recht früh für OpenLDAP entschieden – und haben daran eigentlich nichts zu bemängeln. Einziges Tuning war, dass wir ihn eines Tages selbst verdockert haben, um unsere Schemata in gitlab pflegen zu können und damit – sofern keine breaking changes stattfinden – automatisch auf alle LDAP-Hosts ausrollen zu können. Teil dessen ist auch eine Parent-Child-Replikation geworden, um an allen Standorten eine unabhängige read-only-Kopie zu haben.

Die Automatisierung unserer Infrastruktur übernimmt Ansible AWX – ein ganz großes Benefit hierbei ist, dass das gesamte Inventory (also insb. die Hosts) inklusive Variablen direkt und ohne Zwischensystem aus LDAP bezogen werden können! D.h. sobald ein Host im LDAP angelegt wird, findet er auch automatisch seinen Weg ins AWX und damit in die automatische Versorgung mit Konfigurationen etc. – und die Verwaltung der zugehörigen Parameter kann auch ausschließlich im LDAP erfolgen (dafür das Attribut ansibleConfig).

Mit dem Wissen im LDAP, welche Anwendungen nun auf welchem Zielhost läuft, lässt sich nun jeweils ein großer Proxy je Standort mit Konfiguration versorgen – dieser Proxy ist damit dann gleichzeitig auch für die Terminierung der SSL-Strecke zuständig und beantragt automatisiert die dafür nötigen Let’s-Encrypt-Zertifikate. Auf den Docker-Hosts wiederum läuft mit traefik nochmal ein Proxy, der die eingehenden Anfragen vor Ort auf die konkreten Stacks verteilt. Die Konfiguration des traefiks erfolgt automatisch über Labels an den jeweiligen Stacks. Wir haben uns hier für traefik entschieden, um nicht für jeden Stack einen anderen Port nach außen freigeben zu müssen.

Zusätzlich haben wir dann pro Standort noch einen PowerDNS als DNS-Server mit automatischer LDAP-Anbindung eingerichtet, der uns auf Basis der Anwendungen und Hosts im LDAP ein internes DNS zur Verfügung stellt, damit wir nicht alle Hosts und Anwendungen intern statisch pflegen müssen, das wird nämlich sonst irgendwann recht viel… 😉

Zuletzt haben wir eine Automatisierung gebaut, um alle Hosts und Anwendungen auch für alle Frachtwerk-Menschen als “Enterprise Asset Management” im Wiki zu dokumentieren. Die Informationen (wer ist zuständig, wie hoch ist die Verfügbarkeitsanforderung, wie hoch ist die Vertraulichkeitsanforderung, wer ist Kunde, seit wann existiert diese Software, in welchem Status befindet sie sich, …) werden jede Nacht automatisch aus dem LDAP aktualisiert:

Monitoring

Eine gute Infrastruktur ist nur so gut wie ihr Monitoring! Um einen Überblick über alle ~300 Anwendungen zu behalten, haben wir neben der Proxy-Automatisierung noch unseren Zabbix-Server automatisiert: Für alle Domains im LDAP wird für den jeweiligen Host in Zabbix ein zugehöriges “Web Scenario” definiert, das dann automatisch von Zabbix überwacht wird. Per Default wird dabei erstmal nur geprüft, ob eine Seite erreichbar ist (also einen Status 200 zurückliefert), zusätzlich wird die Downloadgeschwindigkeit und die Latenz der Antwort mitüberwacht. In Zabbix selbst könnten Web-Szenarien dann noch beliebig erweitert werden, sodass z. B. bestimmte Inhalte erwartet werden oder mehrere Schritte nacheinander abgefragt werden. Über automatisch angelegte “Trigger” werden automatisch Nachrichten verschickt, sobald ein Web-Szenario nicht mehr erreichbar ist.

Ausblick

Grundsätzlich sind wir mit unserem Ansatz sehr zufrieden, da er uns ermöglicht, eine große Anzahl an Tools mit einem hohen Servicelevel betreiben zu können. Aber man kann natürlich immer irgendetwas verbessern und eine große Infrastruktur ist nie fertig… Konkret steht vmtl. grob Folgendes auf dem Zettel:

  • nach erfolgreicher Einbindung der anderen europäischen Standorte einen guten Weg zu finden, die parallel wachsende Kubernetes-Welt sinnvoll mit zu integrieren,
  • neben internem DNS zukünftig auch externe DNS-Server zu betreiben, um unabhängiger von Cloudflare zu werden und auch da mehr automatisieren zu können,
  • für größere Anwendungen die Möglichkeit zu schaffen, ein DNS-basiertes Loadbalancing über mehrere Standorte hinweg zu schaffen.

Wir haben also noch viel vor – falls du Lust hast, uns dabei zu unterstützen oder eine Anwendung hast, die einen stabilen 24/7-Betrieb benötigt, melde dich gerne bei uns! 🙂

Über Frachtwerk

Die Frachtwerk GmbH wurde im Jahr 2017 in Berlin als Kombination aus Unternehmensberatung und Softwareentwicklung mit Fokus auf Logistik und Mobilität gegründet. Zu den Projektschwerpunkten gehören Software-Entwicklung, Software-Betrieb, Ablösung von Altsystemen, Data Analytics und Agile Organisationsentwicklung. An zwei Standorten, in Berlin und Karlsruhe, arbeiten insgesamt 40 Mitarbeiter:innen daran, das Beste für ihre Kund:innen zu ermöglichen.

Noch Fragen?

Bereit, das neue Projekt anzugehen? Dann lass uns loslegen! Kontaktiere uns jetzt und entdecke, wie wir dich unterstützen können. Lass uns gemeinsam die Welt von morgen gestalten. digital. nachhaltig.

Mehr Futter für Leseratten