OpenShift
Wir bei VSHN bauen Deployment Pipelines für und zusammen mit unseren Kunden. Ganz gemäss dem Agile Manifesto sind Menschen wichtiger als Tools. Darum kommen je nach Arbeitsprozess und Technologie des Kunden entsprechend angepasste Tools zum Einsatz.
Manche Kunden deployen die neueste Version ihrer Applikation manuell vom Arbeitsplatz des Entwicklers oder Projektleiters. Sie nutzen dafür Ansible, Capistrano, capifony, Drush, einem manuellen “git pull” auf dem Server – meistens wenn es nur einen Server gibt – oder einem “Git push” an ein spezielles Git remote (git push heroku, git push dokku, etc). Die SSH-Keys der Entwickler und die Zugriffsrechte auf die Projekte werden meistens via Puppet verwaltet um so flexibel und zügig vom Kunden selbst angepasst werden zu können.
Anderen Kunden ist es wichtiger, dass der Build-, Test- und Deploy-Prozess für alle Projekte und Mitarbeiter zentral gesteuert, verwaltet und überwacht werden kann. Dabei kommen Tools wie Jenkins, Gitlab CI, Travis CI, etc. zum Einsatz, welche diese Aufgaben für jede Änderung der Applikation automatisch ausführen und die Ergebnisse der Tests und Deployments präsentiert. Das Deployment selbst geschieht mit einem der oben genannten Methoden, nur dass das Tool zuerst überprüft ob alle Tests erfolgreich durchgelaufen sind und das Deployment erst dann erfolgt.
In beiden Fällen wird die Applikation auf dedizierte virtuelle Server deployed, wobei VSHN die Umgebung bzw. Plattform, bestehend aus Applikationssever, Webserver, Datenbank etc und den Zugriff darauf für den Kunden weiterentwickelt und betreibt. Die virtuellen Server können in einer Public Cloud (Infrastruktur as a Service, IaaS) bezogen werden oder auf der privaten Infrastruktur des Kunden betreut werden. Wir kümmern uns zusammen mit dem Kunden darum dass die requirements und dependencies der Applikation zur Verfügung stehen und der Kunde weiss wo und wie er sie verwalten kann (composer.json, Gemfile, packages.yaml, requirements.txt, etc).
Eine andere Art des Verpackens der Applikation inkl. der Dependencies ist der Docker-Container: wir stellen eine Reihe von Basis-Containern zur Verfügung (und stellen sicher dass sie aktuell gehalten wird), der Entwickler schaut selbst dass die Applikation und alle von ihr benötigten Pakete/Module in den Container gepackt werden und wir kümmern uns dann um den Betrieb des/der Container. Der Vorteil dabei ist, dass der Entwickler selbst auch die Qualitätssicherung des Containers macht und ganz nach dem Prinzip “what you see is what you get” derselbe Container nachher auch in der Produktion verwendet wird.
Wie beim traditionellen Deployment oben gibt es auch bei der Erstellung der Container die Bestrebung dies zu zentralisieren, insbesondere damit sichergestellt werden kann, dass alle Tests erfolgreich verlaufen sind, aber auch damit das Deployment automatisiert und nicht von Einzelpersonen abhängig ist. Um eine solche Lösung handelt dieser Blogbeitrag: OpenShift
Was ist OpenShift?
OpenShift ist ein Open Source Projekt von Red Hat, welches die (ebenfalls Open Source) Komponenten Docker und Kubernetes um eine Web-Oberfläche zur Administration und den “source to image” (Quellcode zu Container-Image) Prozess erweitert.
Docker
Docker ist ein Tool, welches die Erstellung und Verwaltung von Linux-Containern vereinfacht. Linux-Container sind eine Art der Virtualisierung von Applikationen, also das Abschotten von verschiedenen auf dem gleichen Server laufenden Prozessen damit sie sich gegenseitig weder mit Rechenkapazität, Arbeitsspeicher oder Speicherplatz konkurrenzieren können.
Anders als virtuelle Server, die ein ganzes Betriebssystem (z.B. Linux) plus die Applikationen beinhalten, enthalten die Container nur eine Applikation und die dafür benötigten Pakete und Module, sind also viel kleiner und dadurch schneller erstellt, übertragen und gestartet als ganze Server.
Die Schritte die benötigt werden um einen Container ausgehend von einem bestimmten Basis-Image (möglichst automatisch) zu erstellen werden in einem sogenannten Dockerfile beschrieben. Dabei gibt es bereits tausende von verschiedenen Softwareprojekten und Dienstleistern bereitgestellte und gepflegte Images auf die man aufbauen kann damit man sich nur noch auf die Schritte die die eigene Applikation benötigt konzentrieren kann.
Das Dockerfile beschreibt zusätzlich die Schnittstellen zur Aussenwelt, also über welchen TCP-Port die Anfragen hineinkommen sollen und welche Verzeichnisse des Containers Benutzerdaten enthalten und gesichert werden sollen, sogenannte Volumes. Alle anderen Verzeichnisse innerhalb des Containers können schreibgeschützt sein und werden beim Update der Applikation durch ersetzen des alten durch den neuen Container auch nicht übernommen. Dies macht den Betrieb der Applikation auch sicherer da der Programmcode des laufenden Containers nicht verändert werden kann.
Diese “Einschränkungen” helfen dabei die Applikation nach dem Leitfaden der 12-Factor-App zu strukturieren und dadurch die Betreibbarkeit und Skalierbarkeit der Applikation zu erhöhen.
Konfigurationsparameter werden bei Docker durch Umgebungsvariablen in den Container gesetzt, man muss die Konfiguration als auch nicht “fix” in den Container backen sondern nur dokumentieren welche Variablen gesetzt werden sollen. So werden zB die Adresse und die Zugangsdaten (Benutzername/Passwort) für den Zugriff auf eine Datenbank als Konfigurations-Variablen beim Start des Applikations-Containers mitgegeben, damit ist es einfach möglich mehrere Instanzen der Applikation voneinander getrennt auf separate Datenbanken zu verweisen oder aber bewusst mehrere Instanzen auf dieselbe Datenbank zu legen und somit die Applikation zu skalieren.
Kubernetes
Im Docker Ökosystem gibt es bereits die Möglichkeit einen Verbund aus mehreren Containern zu bilden (Docker Compose). Dabei wird zB dem Applikationscontainer die Adresse und der Port des im gleichen Verbund gestarteten Datenbankcontainers übergeben. Dies benutzen wir um dem Entwickler eine einfache und schnelle Entwicklungsumgebung mit allen benötigten Backends (Datenbanken, Queues, Caches, etc) zB auf seinem Laptop zu geben.
Um einen Cluster von verschiedenen miteinander verlinkten Containern in der Produktion einsetzen zu können braucht es aber zusätzlich:
- Service Discovery und Load Balancing zwischen den verschiedenen Services in den Containern
- Isolation zwischen verschiedenen Instanzen von Clustern (testing/produktion, verschiedene Kunden, verschiedene Teams, verschiedene Entwickler)
- Verteilen der Container auf verschiedene Hosts damit bei einem Ausfall nicht alles weg ist
- Überwachung der Container, starten von zusätzlichen Containern bei Ausfall
- Messen von Last und/oder Reaktionszeit der Services, automatisches skalieren der Services (Autoscaling)
- Orchestrierung von Deployments, rollende unterbruchsfreie Upgrades, neue Version des Containers hochfahren, ggf. Migration anstossen, den Healthcheck überprüfen, in den Loadbalancer aufnehmen und erst danach den alten Container herunterfahren.
Genau das ist die Funktion des Open Source Projektes Kubernetes, welches aus der massiven Infrastruktur von Google hervorgegangen ist. Kubernetes verwendet die folgenden Begriffe:
- Container: kleinste Einheit, typischerweise ein Docker-Container, könnte aber auch ein anderes Format sein
- Pod: Gruppe von Containern die immer zusammen gestartet, gestoppt oder skaliert werden. Besteht im Normalfall aus einem Container, für komplexe Dienste könnten auch mehrere Container miteinander platziert werden.
- Volume: Verzeichnis von Benutzerdaten, wie bei Docker, welche innerhalb eines Pod auch von mehreren Containern verwendet werden können.
- Labels: Pods sollten normalerweise zumindest mit einem eindeutigen Servicenamen in einem Label (zB. app=meineapplikation) beschriftet sein damit man alle Instanzen von Pods anhand des Labels wieder findet
- Der Replication Controller ist dafür verantwortlich jederzeit eine bestimmte Anzahl eines Pods (identifiziert über ein Label, zB. app=meineapplikation) am laufen zu halten. Wenn ein Container bzw. Pod verschwindet (wegen Absturz, Ausfall des Servers, Netzwerkunterbruch, etc) startet der Replication Controller einen neuen Pod nach dem anderen auf anderen Maschinen bis die gesamte Anzahl wieder erreicht ist.
- Ein Service ist eine Beschreibung eines Load Balancers, der einen Bestimmten TCP-Port (oder UDP etc.) auf alle Pods mit einem bestimmten Label (zB. app=meineapplikation) verteilt. Innerhalb eines Pods können alle Container direkt miteinander kommunizieren, um zwischen Pods zu kommunizieren muss immer explizit ein Service definiert werden, sonst wird die Kommunikation sowohl aus Sicherheitsgründen als auch damit die Applikation sauber strukturiert bleibt blockiert. Ein Service kann entweder nur intern oder auch von extern erreichbar sein, externe Services werden über eine öffentliche IP-Adresse und Port auch der Öffentlichkeit verfügbar gemacht, interne Services sind nur von den anderen Pods aus erreichbar.
- Ein Autoscaler kann regelmässig gemäss CPU/RAM-Auslastung, Applikationsgeschwindigkeit oder eigenen/anderen Metriken die Anzahl Replicas in einem Replication Controller anpassen, damit kann jeder Service innerhalb seiner konfigurierten Limiten dynamisch skalieren.
- Der Namespace macht Kubernetes mandantenfähig und trennt verschiedene Projekte und Kunden voneinander. Interne Services sind nur innerhalb eines Namespace erreichbar und Benutzer mit Zugriff auf nur einen Namespace können die anderen Namespaces nicht sehen.
OpenShift
Damit eine Applikation nun von Kubernetes betrieben und skaliert werden kann muss sie zusammen mit ihrem Applikationsserver in einen Container verpackt werden, genau dies ist die Aufgabe der Builder Komponente von OpenShift.
Neben einer Programmierschnittstelle für die Konfiguration selbst bietet Openshift natürlich auch eine Schnittstelle für automatische Deployments. Es handelt sich dabei um einen sogenannten “Webhook”, also einen Webserver, der HTTP-Post mit einer JSON-Nachricht entgegen nimmt und die JSON-Nachricht interpretiert. Alle üblichen GIT-management Applikationen (Github, Gitlab, Bitbucket, etc) unterstützen diese Art der Benachrichtigung bei Änderungen am Applikationscode. Für jeden Release (je nach Konfiguration definiert durch Commit, Branch und/oder Tag im Git) kombiniert der “source to image” Prozess nun den Applikationscode mit dem leeren Base Image und erstellt daraus das Container-Image für den Release.
Alternativ kann dieselbe Schnittstelle auch von einem Continuous Integration Tool (Jenkins, Gitlab CI, etc) angestossen werden, insbesondere wenn dieses bereits vorhanden ist und wenn eine Programmiersprache verwendet wird, die zuerst kompiliert werden muss (zB Java, Golang, C/C++, etc). Dabei kann der “source to image” Prozess auch fertig kompilierte Artefakte (jar/war-Archive, Binaries oder ganze Docker-Container) verwenden.
Sobald das Image fertig ist stosst es den Deployment-Prozess im Kubernetes an:
- Neuen Replication Controller für das neue Release erstellen mit replicas=1
- der Replication Controller startet den ersten Pod bzw. Container mit dem neuen Image
- sicher stellen dass er auf seinem Service-Port erreichbar ist, wenn nicht Deployment abbrechen
- anstossen von Migrationen falls nötig
- aufnehmen in den Service-Loadbalancer da die Applikation erreichbar ist und der neue Pod dasselbe Label hat
- Nach und nach (Geschwindigkeit einstellbar) die Anzahl replicas im alten controller verringern und die Anzahl replicas im neuen controller erhöhen bis der alte Controller replicas=0 hat und gelöscht werden kann
Warum OpenShift?
Viele Plattform-as-a-Service (PaaS) Angebote bieten diesen Prozess als Lösung an, für uns hat OpenShift die folgenden Vorteile:
OpenShift selbst und die Komponenten Kubernetes und Docker sind alle 100% Open Source. Wir glauben an die Synergieeffekte durch das Verbinden der verschiedenen Personen und Firmen (in diesem Fall VSHN, Red Hat, Google und Docker) in einer Community. Wir können für uns und unsere Kunden wichtige Erweiterungen selbst vornehmen oder von einer beliebigen Drittpartei entwickeln lassen.
Viele PaaS-Angebote koppeln die Technologie, den Deploymentprozess und den Betrieb fix miteinander. Wir mögen an OpenShift dass der Entscheid für die Technologie dem Kunden immer die Freiheit lässt dies durch den kompetentesten Partner betreiben zu lassen. Bei anderen Anbietern ist der Kunde an den Anbieter gebunden weil ein Wechsel durch den grossen Aufwand des Technologiewechsels unwirtschaftlich ist (Lock-in-Effekt). Insbesondere kann die Plattform sowohl auf einer beliebigen Cloud, beim Kunden intern oder auf jedem Laptop der Entwickler installiert werden falls nötig. Als Entwickler von Software funktioniert der Build/Deployment-Prozess immer gleich egal wo sich der Ort des Betreibens physikalisch, geografisch oder administrativ befindet. Einen häufigen use-case sehen wir wenn Applikationen sowohl als Cloud/SaaS-Dienst als auch bei Enterprise-Kunden on-premises betrieben werden soll, OpenShift kann beides. Der Technologie-Entscheid ist also entkoppelt vom Lieferanten-Entscheid.
Wie funktioniert OpenShift?
Wir haben ein Tutorial für ein minimal kleines “Hello World” Beispiel als PDF vorbereitet, gerne schicken wir es Ihnen per Email:
OpenShift @ VSHN
Wir betreiben unter appuio.ch zusammen mit der Puzzle ITC einen öffentlichen OpenShift-as-a-Service Dienst, der von uns betrieben, überwacht, gesichert und supportet wird. Für Kunden mit grösseren Ressourcen- oder Sicherheitsanforderungen betreiben wir auch dedizierte OpenShift-Cluster, wie bei VSHN üblich kann der Kunde dabei aussuchen wo (geografisch, Cloud-Anbieter, Sicherheitsstandard (ISO27001/FINMA), beim Kunden intern) dies sein soll.
Selbstverständlich unterstützen wir unsere Kunden bei der Einrichtung und dem Betrieb des Continuous Deployment und Delivery Prozesses.