Server Deployment leicht gemacht

  • Hallo Leute,

    in diesem Tutorial möchte ich euch vorstellen wie die Release-Pipeline, die ich zusammen mit dem eXo-Team für ihren Server entworfen habe, funktioniert.
    Dieses Tutorial ist eindeutig an fortgeschrittene Nutzer gerichtet und ist keine Schritt-für-Schritt Anleitung, sondern soll vielmehr einen Anreiz geben, die eigene Release-Pipeline ähnlich aufzubauen (alles andere würde außerdem den Rahmen eines einzelnen Tutorials deutlich sprengen).
    Davon abgesehen lässt sich das vorgestellte Konzept nicht sinnvoll einsetzen und den eigenen Bedürfnissen anpassen, wenn man die einzelnen Schritte nicht versteht. Es ist also in jedem Fall notwendig sich mit den verwendeten Tools zu beschäftigen.

    Damit ihr das Tutorial versteht, solltet ihr zumindest folgende Vorkenntnisse mitbringen:

    • Git-Grundkenntnisse
    • Linux-Grundkenntnisse
    • zumindest schonmal was von Docker gehört haben

    Tools und Codebeispiele, die in diesem Zusammenhang entstanden sind, habe ich z.T. hier hochgeladen: https://github.com/Jusonex/mtasa-deployment-tools

    Motivation
    Dieses Tutorial richtet sich auch an die, die bisher ihr Script per FTP auf ihren Server laden und das Script per Dropbox oder Skype zwischen den Teammitgliedern synchronisieren.
    Dieser "Ablauf" bringt mehrere Probleme mit sich:

    • Das Synchronisieren per Dropbox/Skype führt sehr leicht zu Konflikten und nicht selten zu einer komplett kaputten Codebasis, die nur nach viel Anstrengung wiederhergestellt werden kann. Die Lösung dafür ist ein Versionskontrollsystem - in unserem Fall Git. Um Versionskontrolle soll es in diesem Tutorial jedoch nicht gehen (es sei hier auf jede Menge gute Git Tutorials im Internet verwiesen - z.B. Git Cheatsheet).
    • Wenn das Script zusätzlich kompiliert werden soll oder weitere Schritte bis zum Release notwendig sind, steigt die Komplexität ungemein. Dementsprechend wäre der Ablauf üblicherweise: Lokal mit einem Tool kompilieren (sei es direkt per luac oder über grafische/batched Tools) und anschließend per FTP o.ä. auf den Server hochladen.

    Schnell stellt man fest, dass dieser Ablauf überhaupt nicht skaliert. Wenn einmal im Monat ein Update releast wird, mag das mit mehrminütigen Downtimes vielleicht (oder auch nicht ;) ) funktionieren.
    Wenn nicht, kann es schonmal eine Weile dauern, bis die Ursache des Problems gefunden und ausgemerzt wurde.
    Wenn man aber nun nicht 1x pro Woche, sondern im Extremfall 1x pro Stunde updaten möchte (z.B. für einen Testserver, der ebenfalls alle "Release-Schritte" durchlaufen muss, endet man mit sehr viel Aufwand
    und gleichzeitig einer hohen Anfälligkeit für Fehler).

    Daraus lässt sich eigentlich nur eines folgern: Wir müssen diese Schritte automatisieren.

    Anforderungen
    Darüber hinaus gab es ein paar weitere Anforderungen, die bei eXo eingehalten werden sollten:

    • Integration mit GitLab
    • verschiedene Release-Stufen:

      • Produktivserver (Hauptserver): Wird langfristig alle 1-2 Wochen geupdatet
      • Testserver: Server für jeweils kommendes Update, das vom Team auf Fehler getestet werden muss (dieser Stand wird dann 1:1 zum Produktivserver)
      • Devserver: Server, auf dem die aktuelle Version aus dem Git-Repositories (Entwicklungsbranch) läuft
    • vollautomatisch und zentral gesteuert (niemand will lokal extra ein Script ausführen, um die "Release-Schritte" zu starten)
    • portable Server: Die Server sollen nicht an eine bestimmte Betriebssystemkonfiguration gebunden sein, sodass Server innerhalb weniger Minuten auf andere dedizierte Server umgezogen werden könnten
    • Upload der Assets (Bilder, Soundeffekte, Musik, Modelle etc.) via HTTP auf einen externen Webserver (zur Lastverteilung der Downloads und damit Entlastung des MTA-Servers bei vielen gleichzeitigen Connects)
    • Steuerung des MTA-Servers über ein Webinterface (mit Funktionen Start/Stopp/Restart/Logs live einsehen/Befehle ausführen)
    • MTA-Server überwachen (z.B. bei Crash automatisch neustarten)

    Implementierung

    In diesem Abschnitt möchte ich nun für jeden der oben genannten Punkte aufzeigen wie wir sie schließlich umgesetzt haben:

    1) Integration mit GitLab
    Hier lag es auf der Hand GitLab CI zu benutzen. GitLab CI ist das in GitLab integrierte CI (Continous Integration) System (für die, die mit CI nichts anfangen können:
    Grob umschrieben bedeutet das, dass nach jedem Commit das Projekt kompiliert und Tests ausgeführt werden, um Fehler frühzeitig zu erkennen und zu beheben).
    Wenn GitHub genutzt wird, bietet sich z.B. TravisCI an (Tipp: https://education.github.com/pack) - ansonsten geht aber auch fast jede andere CI-Lösung.

    GitLab CI hat insbesondere den Vorteil, dass es sich nahtlos in GitLab integriert.
    Auf dem Bild sieht man die grünen Häkchen, die durch Gitlab CI in die Commitliste kommen. Grünes Häkchen bedeutet dabei: Der Buildprozess (Kompilieren des Gamemodes + Packen der Assets + Hochladen)
    war erfolgreich. Rotes Häkchen bedeutet, dass bei einem dieser Schritte ein Fehler vorliegt (z.B. Syntaxfehler im Script, der dazu führte, dass das Script nicht kompiliert werden konnte).

    Pro Tipp am Rande: GitLab kann super mit Messengern wie z.B. Slack integriert werden, sodass alle Entwickler eine Benachrichtigung per Chat bekommen, dass ein Problem vorliegt.

    2) Verschiedene Release-Stufen
    Um obige Aufteilung sinnvoll hinzubekommen, muss die Lösung dieses Problems mit der Versionskontrolle (in unserem Fall also Git) gefunden werden.
    Dazu wird eine etwas reduzierte Variante von Git Flow eingesetzt.

    Das bedeutet: Es gibt für jede Release-Stufe einen Git-Branch:

    • release/production für den Releaseserver: In diesen Branch wird von release/testing gemerged, wenn ein neues Update releaset werden soll
    • release/testing für den Testserver: Hier werden die annährend stabilen Änderungen aus dem Entwicklungsbranch gemerged, wenn das kommende Update getestet werden soll
    • develop bzw. master: Hier finden alle Änderungen statt

    Ganz am Rande: Featurebranches kommen für neue Features ebenfalls zum Einsatz, sind jedoch für die Release-Pipeline nicht relevant.

    3) Vollautomatisch, zentral und portabel
    Ein Teil von 'vollautomatisch' und 'zentral' haben wir durch das Kompilieren durch GitLab CI schon abgedeckt. Fehlt also nur noch der Upload auf den tatsächlichen Server bei gleichzeitiger Portabilität.

    Beim Punkt "Portabilität" haben wir uns entschieden diese mit Docker umzusetzen. Docker ist grob eine Möglichkeit Anwendungen mit einem mehr oder weniger komplett fertig installierten Betriebssystem als Abbild auszuliefern, welches dann in einer Art virtuellen Maschine läuft. Das hat den wesentlichen Vorteil, dass man sich nicht mehr manuell um die Einrichtung des Systems (z.B. Installation der Abhängigkeiten wie z.B. fehlende Bibliotheken wie libmysqlclient.so.16 oder der Einrichtung der acl.xml) kümmern muss.
    Dabei ist zu beachten, dass es sich bei "Docker-VMs" nicht wirklich um vollvirtualisierte Maschinen handelt (die verhältnismäßig langsam sind), sondern um sogenannte Containervirtualisierung, welche im
    Unterschied zu einer Vollvirtualisierung Features des Linux-Kernels nutzt, um Container auf demselben Linux-Kernel laufen zu lassen, aber in einer isolierten Umgebung.

    Docker Images werden mit sog. Dockerfiles beschrieben und müssen daher ebenfalls "kompiliert" werden (was im Wesentlichen alle Installationsschritte automatisiert ablaufen lässt und dann in eine Binärdatei schreibt, die irgendwo (z.B. DockerHub) hochgeladen wird).

    Um das Ganze noch portabler zu machen, legen wir nicht nur den MTA-Server an sich in das Docker-Image, sondern das kompilierte Script mitsamt aller Configfiles (mtaserver.conf, acl.xml etc) gleich mit.
    Das bedeutet aber auch, dass wir das Docker-Image bei jeder Änderung am Script (also bei jedem Commit) ebenfalls mit erstellen müssen.
    Kein Problem - da kann uns Gitlab CI weiterhelfen.

    Bleibt also für diesen Aspekt nur noch der Upload auf den Releaseserver. Dieser Schritt ist etwas tricky, denn die einzige sinnvolle Möglichkeit von GitLab aus irgendwelche externen Ereignisse
    auszulösen ist über den Aufruf eines Webhooks, welches eigentlich nur ein HTTP-Request an einen bestimmten Server ist. Dieser Schritt kann relativ leicht über einen Aufruf des
    Linux-Kommandozeilentools curl realisiert werden.
    Problem ist aber jetzt folgendes: Der Docker-Dienst auf dem Server, über den die Container auf Basis des gerade erstellten Images gestartet werden, kann nur mit root-Rechten gesteuert werden.
    Einen HTTP-Server mit root-Rechten laufen zu lassen wäre jedoch äußerst fahrlässig, da ein Angreifer, der den HTTP-Server übernimmt, dann volle Kontrolle über das System hätte.
    Ein PHP-Script mit Zugangsdaten für den root-Nutzer ist besser, wirkt aber auch eher wie ein Hack.

    Das führt uns zu einem Tool, das ich für diesen Zweck entwickelt habe (https://github.com/Jusonex/deployron). Die Idee dahinter ist recht simpel: Wir haben 2 Dienste:
    Der eine Dienst ist ein HTTP-Server, der auf Anfragen von GitLab wartet und mit normalen Benutzerrechten läuft und ein Dienst, der mit root-Rechten läuft. Diese beiden Dienste sind dann über einen
    Unix-Socket verbunden sind (Möglichkeit zum Datenaustausch zwischen Prozessen), der aber nur auf den Befehl "starte Script für Deployment X" reagiert. Alle weiteren Informationen werden dann
    aus einer Configdatei (siehe GitHub-Repository) ausgelesen.
    In dieser Konfigurationsdatei können wir den Benutzer, der das Script ausführen soll (in unserem Falle also 'root') angegeben. Das Script dazu holt dann per docker pull unserer gerade
    erstelltes Image herunter und führt dann den Befehl docker run ... aus und erstellt so unseren Container.

    Damit sind wir nun bei folgender Pipeline angekommen:

    "Setup Env" führt dabei einige Initialisierungsschritte aus (z.B. prüft es, ob alle Abhängigkeiten aktuell sind) und "Clean Env" bereinigt erstellte Dateien (löscht z.B. das Docker-Image direkt wieder,
    da es im Deploy-Schritt schon hochgeladen wurde)

    4) Upload der Assets
    Dieser Schritt ist relativ analog zum vorherigen realisiert. Hier benutzen wir wieder curl, laden aber dieses Mal direkt das Archiv hoch.

    5) Steuerung des MTA-Servers über ein Webinterface und MTA-Server überwachen
    In diesen Schritt mussten wir ebenso etwas mehr Aufwand investieren. Denn bisher haben wir zwar den MTA-Server erfolgreich starten können, haben aber keine Möglichkeit diesen im Nachhinein zu steuern oder zu überwachen. Die Docker API ist zwar durchaus umfangreich, schränkt uns aber an ein paar Stellen - insbesondere den MTA-spezifischen - zu sehr ein.
    Die Lösung war dann ein kleines Programm, das nochmal vor den MTA-Server geschaltet wird und diesen dann kontrolliert.
    Zur Steuerung stellt es dann eine REST-API bereit, die folgende Funktionen besitzt:

    • Start/Stop/Restart
    • Logs einsehen (um Größe zu reduzieren, werden hier nur die letzten 10000(?) Zeilen zurückgegeben)
    • Befehle ausführen
    • Status abfragen
    • Ressourcen ersetzen (für schnelles Hochladen zum erleichterten Debuggen)

    Der Vorteil dieser REST-API ist vor allem, dass sie durch jedes beliebige Programm, das einen HTTP-Client hat, benutzt werden kann.
    So ist auf dem Weg zu einem umfangreicheren Monitoring-CP als Zwischenziel folgendes CP durch das eXo-Team entstanden:

    Dieses konnte mit relativ wenigen Zeilen PHP-Code realisiert werden.

    Damit haben wir schließlich alle Anforderungen abgedeckt :)

    Literaturverweise
    Hier schließlich noch eine kurze Auflistung zur Dokumentation der Tools, die verwendet werden:

    Ich hoffe ich konnte euch dazu motivieren die eigene Release-Pipeline ähnlich oder sogar noch besser aufzubauen. Für Verbesserungsvorschläge oder konkrete Fragen habe ich stets ein offenes Ohr - also einfach hier im Thread fragen (oder bei sehr speziellen Fragen eine PN schicken).

    Viele Grüße

  • Dieses Thema enthält 4 weitere Beiträge, die nur für registrierte Benutzer sichtbar sind, bitte registrieren Sie sich oder melden Sie sich an um diese lesen zu können.

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!