Microservice-Deployment ganz einfach ohne Docker mit der Linux-Paketverwaltung

Keine Kommentare

In den letzten Artikeln der Serie “Microservice-Deployment ganz einfach” haben wir uns vorwiegend mit Werkzeugen beschäftigt, die auf dem Deployment-Werkzeug Docker aufbauen. In dem Artikel “Microservice-Deployment ganz einfach mit Giant Swarm” wurden die ersten Erfahrungen mit der PaaS-Lösung Giant Swarm beschrieben. In dem Artikel “Microservice-Deployment ganz einfach mit Kubernetes” wurde vorgestellt, wie man mit Kubernetes einen Cluster zum Betrieb von Docker-Containern aufbauen kann. Dass auch der Betrieb einer Microservice-Anwendung auf einem einzelnen Rechner kein Problem ist, habe ich in dem Artikel “Microservice-Deployment ganz einfach mit Docker Compose” gezeigt.

Auch wenn Docker den Betrieb von Microservice-Anwendungen stark vereinfacht, ist es nicht zwingend nötig, Docker für das Deployment von Microservice-Anwendungen zu verwenden. Die meisten Linux-Distributionen verfügen über eine komfortable Software-Paketverwaltung, die auch zum Deployment von Microservice-Anwendungen benutzt werden kann. Wie in einem Docker-Container sollten in einem Linux-Paket alle notwendigen Dateien enthalten sein, um eine Anwendung auf einem Rechner ausführen zu können.

Konkrete Beispiele für Programme, um Software-Pakete zu installieren, sind:

  • RPM, das Pakete vom Typ *.rpm installieren und löschen kann und
  • Dpkg, das Pakete vom Typ *.deb installieren und löschen kann.

Beide Programme können aber keine Abhängigkeiten zwischen Paketen auflösen, da sie keine Funktionen haben, um Software nachzuladen. Dies können nur höhere Schichten der Paketverwaltung wie YUM oder APT tun. Im Folgenden betrachten wir Debian-Pakete als Beispiel näher. Die Konzepte für RPM-Pakete sind allerdings ähnlich.

Es gibt zwei Arten von Debian-Paketen:

  1. Zum Einen gibt es Binärpakete, die die ausführbaren Dateien, Konfigurationsdateien und Dokumentation beinhalten.
  2. Zum Anderen gibt es so genannte Quellpakete. Sie bestehen aus einer .dsc-Datei, die das Quellpaket beschreibt, einer .orig.tar.gz-Datei, welche die Quellen in einem gzip-komprimierten tar-Format enthält, und einer .diff.gz-Datei, die Debian-spezifische Änderungen an den Original-Quellen enthält.

Betrachten wir das Binärpaket, das wohl die meisten für das Deployment ihrer Anwendungen verwenden würden, da man sich das Kompilieren der Anwendung auf dem Zielrechner spart. Jedes Binärpaket besteht aus mindestens drei Dateien, die mit den Kommandos ar oder dpkg-deb entpackt werden können. Diese drei Dateien sind:

  • debian-binary: eine Textdatei mit der Versionsnummer des verwendeten Paketformats.
  • control.tar.gz: ein Archiv, das Dateien enthält, die zur Installation dienen oder Abhängigkeiten auflisten. Hier werden nur ein paar Dateien aufgeführt, die in dem Archiv enthalten sind. Eine weiterführende Beschreibung findet man in der offiziellen Debian FAQ zu .deb-Paketen.
    • Die control-Datei enthält Metainformationen zum Paket, wie etwa eine Kurzbeschreibung sowie weitere Informationen wie dessen Abhängigkeiten. Ein Beispiel für eine control-Datei ist im folgenden Listing zu sehen.
Source: shop-cart-service
Section: web
Priority: optional
Version: 4.2.42
Maintainer: Bernd Zuther <bernd.zuther@codecentric.de>
Homepage: http://www.bernd-zuther.de/
Vcs-Git: https://github.com/zutherb/AppStash.git
Vcs-Browser: https://github.com/zutherb/AppStash
Package: shop-cart-service
Architecture: amd64
Depends: redis-server (>= 2.8.13)
Description: Cart Service
    • preinst, postinst, prerm, postrm sind optionale Skripte, die vor oder nach dem Installieren bzw. Entfernen des Pakets ausgeführt werden. Diese Skripte werden mit root-Rechten ausgeführt.
  • data.* ist ein komprimiertes Archiv und enthält die eigentlichen Programmdaten mit relativen, mit dem Wurzelverzeichnis beginnenden Pfaden.

Im folgenden Listing ist zu sehen, wie die beschriebenen Bestandteile innerhalb eines Debian-Pakets aussehen:

bz@cc:~/$ ar t cart_0.6.20.deb 
debian-binary
control.tar.gz
data.tar.gz
bz@cc:~/$ ar x cart_0.6.20.deb 
bz@cc:~/$ ls
cart_0.6.20.deb  control.tar.gz  data.tar.gz  debian-binary
bz@cc:~/$ tar tzf data.tar.gz | head -n 14
./etc/
./etc/default/
./etc/init.d/
./etc/default/cart
./etc/init.d/cart
./usr/
./usr/share/
./usr/share/shop/
./usr/share/shop/cart/
./usr/share/shop/cart/bin/
./usr/share/shop/cart/lib/
./usr/share/shop/cart/bin/cart
./usr/share/shop/cart/bin/cart.bat
./usr/share/shop/cart/lib/cart-microservice-0.6.20.jar
bz@cc:~/$ tar tzf control.tar.gz
./postinst
./control
./md5sums
bz@cc:~/$ cat debian-binary
2.0

Betrachten wir nun, wie man die Debian-Paketverwaltung in einem Microservice-Projekt verwenden kann. In der folgenden Darstellung ist ein Verzeichnisbaum zu sehen. Darin ist zu erkennen, welche Dateien man anlegen muss, damit man diese zusammen mit den ausführbaren Dateien eines Warenkorb-Microservice in ein Deb-Paket verwandeln kann. Im debian-Verzeichnis befindet sich das control-Verzeichnis, aus dem das Archiv control.tar.gz generiert wird. Darin ist die oben beschriebene control-Datei und ein postinst-Script enthalten.

Nach dem Twelve-Factor-App-Konzept sollten Anwendungen immer als einzelne Prozesse ausgeführt werden. Auch Martin Fowler schlägt vor, dass man einen Microservice als einzelnen Prozess auf einen Rechner laufen lassen soll. Damit ein Prozess auf einem Linux-Rechner beim Betriebssystemstart automatisch gestartet wird, kann man ein so genanntes init.d-Script verwenden. In der Verzeichnis-Darstellung liegt im data-Verzeichnis unter /etc/init.d/ so ein init.d-Script. Alle Dateien, die unter dem data-Verzeichnis liegen, kommen so in das Debian-Paket, dass sie unter derselben Position unter dem Wurzelverzeichnis auf dem Zielrechner installiert werden. Unter dem Verzeichnis /data/etc/default liegt ein zusätzliches Script, das Umgebungsvariablen enthält, die vom dem Warenkorb-Microservice zum Starten benötigt werden. Dieses Script wird vom init.d-Script ausgeführt.

Verzeichnisstruktur Unix-Package

Verzeichnisstruktur eines Unix-Pakets

Im src-Verzeichnis liegt der Quellcode des Warenkorb-Microservice. Wie man an der build.gradle erkennen kann, wird das Projekt mit dem Buildwerkzeug Gradle gebaut. Wie bekommt man nun alle Teile zusammen, um ein Debian-Paket zu erhalten? Es ist sinnvoll das Bauen eines Debian-Pakets in den Anwendungsbuild zu integrieren. Mit JDeb gibt es eine Java-Bibliothek, um Debian-Pakete zu erstellen. Diese Bibliothek ist plattformunabhängig und baut Debian-Pakete ohne die Installation von irgendwelchen zusätzlichen Tools. Es gibt auch ein Gradle-Plugin, das auf JDeb basiert. Das folgende Listing zeigt die Verwendung dieses Plugins zusammen mit dem Spring-Boot-Plugin, dass das Gradle-Application-Plugin erweitert und den Teil der build.gradle, der für das Bauen eines Debian-Pakets verantwortlich ist:

apply plugin: 'spring-boot'
apply plugin: 'pkg-debian'
 
....
 
mainClassName = "io.github.zutherb.appstash.shop.service.cart.Boot"
def debName = "cart"
 
task prepareDeb {
   dependsOn installApp
 
   copy {
       from "${buildDir}/install/"
       into "${buildDir}/debian-data/usr/share/shop/"
   }
}
 
debian {
   packagename = "cart-service"
   controlDirectory = "${projectDir}/debian/control"
   changelogFile = "${projectDir}/debian/changelog"
   outputFile = "${buildDir}/debian/${debName}_${version}.deb"
 
   data {
       dir {
           name = "${projectDir}/debian/data"
           exclusions = ["**/.DS_Store", "changelog"]
       }
       dir {
           name = "${buildDir}/debian-data/"
           exclusions = ["**/.DS_Store"]
       }
   }
}

Das Gradle-Application-Plugin generiert zur Auslieferung einer beliebigen Anwendung die folgenden Verzeichnisse:

Verzeichnis Beschreibung
libEnthält alle abhängigen Jar-Dateien und die Jar-Datei des Programms selbst.
binEnthält generierte Start-Scripte, um das von der mainClassName referenzierte Programm zu starten.

Diese Verzeichnisse und die Start-Scripte kann man auch im einem Debian-Paket benutzen. Der Task prepareDeb führt dazu vorher den installApp Task aus, der die Verzeichnisstruktur generiert und die Start-Scripte erstellt, in der alle Jar-Artefakte der Anwendung enthalten sind, und kopiert diese innerhalb des build-Verzeichnisses in das Verzeichnis debian-data. Um das Gradle-Debian-Plugin zu konfigurieren, gibt es die debian { … }-Sektion. Darin definiert man die Variablen, die von JDeb benötigt werden, um ein Debian-Paket zu erstellen. In der debian { data }-Sektion ist das debian-data-Verzeichnis zusammen mit dem data-Verzeichnis definert. Die in den Verzeichnissen enthaltenen Dateien gelangen zusammen in das data-Archiv, wenn der Task buildDeb ausgeführt wird, der vom Gradle-Debian-Plugin bereitgestellt wird, um das Debian-Paket zu bauen.

Neben der Nutzung der offiziellen Paketquellen des Betriebssystem-Herstellers gibt es auch die Möglichkeit, eine eigene Paketquelle anzulegen. Dies kann zum Beispiel dann sinnvoll sein, wenn man ein eigenes Artefakt-Repository aufbauen möchte, in dem man alle Microservices seiner Microservice-Anwendung ablegt und das zur Installation benutzt wird. Mit dem Konsolen-Programm reprepro können sehr einfach eigene Paketquellen erstellt und verwaltet werden. Das folgende Listing zeigt, wie man reprepro von einem Buildserver aus benutzen kann, um sich eine eigene Paketquelle zu bauen. Stellt man das Verzeichnis /var/packages/debian über einen Webserver bereit, kann man die Quelle auf einen beliebigen Rechner hinzufügen und zur Installation einer Microservice-Anwendung nutzen.

echo *****************************************
echo * PUBLISH ARTIFACTS                     *
echo *****************************************
reprepro -Vb /var/packages/debian includedeb shop /tmp/*.deb
echo *****************************************
echo * REMOVE TMP ARTIFACTS                  *
echo *****************************************
rm -f /tmp/*.deb
echo *****************************************
echo * EXPOSE REPO ARTIFACTS                 *
echo *****************************************
reprepro -b /var/packages/debian/ export
reprepro -b /var/packages/debian/ list shop

Mit reprepro kann man sich sehr einfach eine Multi-Deployment-Pipeline aufbauen. Am Ende jedes einzelnen Builds pusht man das entstandene Debian-Paket mit dem Komando includedeb in die Paketquelle. Auf dem Zielrechner muss man nun die Paketquelle bekannt geben und kann den Warenkorb-Microservice ganz einfach mit dem Kommando apt-get install shop-cart-service installieren. Wie eine solche Multi-Deployment-Pipeline auf dem Jenkins Buildserver aussieht, zeigt die folgende Darstellung.

Microservice Multi-Deployment-Pipeline

Microservice Multi-Deployment-Pipeline

Das komplette Beispiel für eine solche Multi-Deployment-Pipeline kann man sich in meinem GitHub-Repository ansehen und ausführen. Im Repository ist eine Vagrant-Datei enthalten, mit der man sich ein Cluster aufbauen kann, das aus mehren virtuellen Maschinen besteht. In diesem Cluster befinden sich unter anderem zwei virtuelle Maschinen, auf denen ein Microservice-basierter Online-Shop läuft. Eine weitere virtuelle Maschine enthält den Buildserver zum Bauen der Microservice-Anwendung, der für deren Deployment eine auf Linux-Paketverwaltung aufbauende Multi-Deployment-Pipeline nutzt. Eine komplette fachliche Beschreibung der Anwendung befindet sich in meinem Artikel “Microservices und die Jagd nach mehr Konversion”.

Wie man an dem Beispiel sieht, muss man nicht immer Docker verwenden, um eine Microservice-Anwendung vollautomatisiert ausliefern zu können. Auch mit der Linux-Paketverwaltung kann man sich eine Registry aufbauen, wie wir sie von Docker kennen. Diese Registry nennt sich in diesem Zusammenhang aber “Paketquelle”. Eine solche Paketquelle kann man auf einem beliebigen Rechner benutzen, um die Microservice-Anwendung zu installieren. Anders als bei Docker, wo man über verschiedene Umgebungsvariablen in den laufenden Container beispielsweise die Adresse einer Datenbank mitgeteilt bekommt, muss man sich allerdings bei diesem Ansatz selbst um das Thema Service-Discovery kümmern. Allerdings hat man dann von Operationsseite auch die meisten Möglichkeiten, in den Betrieb der Microservice-Anwendungen einzugreifen.

Bernd Zuther

Kampferprobter Projektveteran, der immer fokussiert auf die Lieferung von funktionierender Software in der vorgegebenen Zeit ist. Zu seinen stärksten Waffen gehören ein sicherer Umgang mit agilen Methoden wie Scrum und sein breit gestreutes IT-Wissen, das er gezielt dazu einsetzt, auch die schwierigsten Problemstellungen pragmatisch und einfach zu lösen.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.