Stefan Zörner
31.05.2018
Micro Moves, Bauteil 4. Wir räumen mit einem Reverse Proxy und Docker ein wenig auf.
Mit den bisherigen Beiträgen der Micro Moves Blog-Serie ist ein Konglomerat von Bauteilen für unsere Online-Schach-Plattform FLEXess entstanden. Services laufen in separaten Prozessen und lauschen auf bestimmten Netzwerkadressen per HTTP. Besonders spürt das die Single Page Application (SPA) play aus Teil 3, die auf die REST-API aus Teil 1 und den Diagramm-Service auf Teil 2 zugreift. Dazu benötigt sie aktuell deren Hostnamen und Ports.
In dieser Folge kommt keine neue Funktionalität hinzu. Stattdessen räumen wir ein wenig auf, vereinheitlichen die Art, wie wir Services betreiben und auffindbar machen, und lassen FLEXess für einen Browser-Client wie eine Anwendung aussehen. Mit ein wenig Makro-Architektur schaffen wir Voraussetzungen sowohl für weitere Services in den nächsten Folgen, als auch für querschnittliche Themen wie beispielsweise Security.
Die Hauptrolle in dieser Folge spielt nginx. Ein Webserver, den wir als Reverse Proxy konfigurieren, und hinter dem wir alle Services (bestehende und zukünftige) verbergen. Die rote (4) markiert dessen Standpunkt im Gesamtbild („Sie befinden sich hier.“).
Weiterhin nutzen wir diese Folge, um die bestehenden Services in Docker-Images zu verwandeln. Anschließend können wir sie trotz der unterschiedlichen Laufzeitumgebungen (aktuell: Java/Tomcat, Python/gunicorn, JavaScript/node.js) im Deployment einheitlich behandeln. Auch unser neues Bauteil, den Reverse Proxy, betreiben wir später im Container.
Zunächst lassen wir das Ganze einfach mit Docker Compose auf einem einzelnen Host laufen. Interessantere Verteilungsoptionen (z.B. in der Cloud) heben wir uns für später auf.
Bisher laufen die Module games, chess-diagrams und play in separaten Prozessen und stellen ihre Funktionalität per HTTP auf unterschiedlichen Ports zur Verfügung. Im einfachsten Fall alle lokal auf localhost, standardmäßig (d.h. falls nicht von Euch verändert) auf den Ports 8080 (games via Apache Tomcat), 8000 (chess-diagrams via gunicorn) und 7000 (play via Node.js). Die von play ausgelieferte SPA greift übers Netzwerk nicht nur auf ihre eigenen Ressourcen (JavaScript-Quelltexte) zu, sondern auch auf die anderen beiden Services (um mit den Partien zu interagieren und das Schachbrett zu visualisieren).
Für diese Folge habe ich die Module zur Vereinheitlichung jeweils mit einem Dockerfile ausgestattet, um Images dafür zu bauen. Docker führt eine Virtualisierung auf Betriebssystemebene durch, die auch als Containerisierung (engl. containerization) bezeichnet wird. Dieser Spickzettel gibt einen guten Überblick über die wichtigsten Docker-Befehle – Alternativen findet Ihr etwa bei ZeroTurnaround (Docker Commands Cheat Sheet) und DZone (Refcard #221). Weitere Details zu Docker auf deren Homepage.
Die Dockerfiles liegen jeweils direkt im entsprechenden Modulverzeichnis, also zum Beispiel micro-moves/modules/chess-diagrams/Dockerfile für chess-diagrams (Quelltexte hier). Ein Docker-Image bauen und einen entsprechenden Container starten könnt Ihr zum Beispiel für das Modul _chess-diagram_s per Kommandozeile so:
$ cd micro-moves/modules/chess-diagrams
$ docker build . -t chess-diagrams
$ docker run -p 8000:8000 -t chess-diagrams &
Die folgende Tabelle beinhaltet die verwendeten Basis-Images und Run-Commands innerhalb der Dockerfiles von FLEXess.
Modul | Basis-Image ("FROM name:tag") | Kommando ("CMD") |
games | openjdk:8 | java -jar games.jar |
chess-diagrams | python:3.6 | gunicorn chess_diagrams:app |
play | node:8 | npm start |
Beim games-Modul müsst Ihr vorher die Java-Anwendung mit Gradle bauen (siehe Folge 1). Das Dockerfile kopiert nämlich lediglich das resultierende jar-File (games.jar) mit allen Abhängigkeiten (“Uberjar”, frei nach Nietzsche) in das Image. Auf den Build in einem Container habe ich verzichtet.
Viel gewonnen haben wir noch nicht. Die Prozesse laufen jetzt in Docker-Containern, aber die play-SPA beispielsweise muss immer noch die Adressen/Ports von games und chess-diagrams kennen.
Um die interne Struktur von FLEXess (inkl. weiterer Services in der Zukunft) zu verbergen setzen wir einen sogenannten Reverse Proxy ein.
Bei einem “klassischen” (Web-)Proxy nehmen Clients beispielsweise innerhalb eines Unternehmensnetzwerkes über diesen Kontakt mit der Außenwelt (dem Internet) auf. Die Motivation in Unternehmen ist oftmals das Filtern von Inhalten (kein Facebook bei der Arbeit). Bei einem Reverse Proxy hingegen nehmen Clients von außen über diesen Kontakt mit in unserem Netz liegenden Servern auf. Der direkte Aufruf von außen wird unterbunden, alles Anfragen gehen über den Reverse Proxy.
Auf diese Weise kann ein Client alle Module von FLEXess über eine Adresse (Hostname und Port) ansprechen. Das Routing erfolgt mit Hilfe der URL. Als Software nutzen wir hierzu nginx. Hierbei handelt es sich um einen sehr rasanten Open Source Webserver, der sich seit einigen Jahren als Alternative zum Apache HTTP-Server etabliert. Er kann als Reverse Proxy betrieben werden, die Dokumentation dazu findet Ihr hier: “NGINX Reverse Proxy”.
Die Konfigurationsdatei nginx.conf für unsere Anwendung liegt in einem eigenen Modulverzeichnis reverse-proxy gemeinsam mit dem passenden Dockerfile (Struktur siehe Abbildung). nginx übernimmt nun auch die Aufgabe. die “Homepage” von FLEXess auszuliefern. Zuvor hatte games eine provisorische Homepage. Die statischen Inhalte dazu liegen im reverse-proxy-Modul in einem Unterverzeichnis static. Die passende Konfiguration steht ebenfalls in nginx.conf. Die folgende Tabelle zeigt die Weiterleitung an die bisherigen Bauteile der Serie (wie in nginx.conf definiert).
Anfragemuster (location) | Weiterleitung (proxy_pass) | Beschreibung |
/games-api/ | http://games:8080/api/ | REST API, Folge 1 |
/chess-diagrams/ | http://chess-diagrams:8000/ | Service für Diagramme, Folge 2 |
/games/ | http://games:8080/ | Spring Web MVC Oberfläche, Folge 3 |
/games-websocket/ | http://games:8080/games-websocket/ | WebSocket-Kommunikation, Folge 3 |
/play/ | http://play:7000/ | Auslieferung der SPA, Folge 3 |
Die Konfiguration der Weiterleitung für WebSocket ist dabei etwas spezieller, der Beitrag “NGINX as a WebSocket Proxy” beschreibt, wie Ihr das in nginx anstellt. Dass die Weiterleitung mit den Hostnamen (wie z.B. play in http://play:7000/) funktioniert liegt am Docker-internen Netzwerk. Dazu jetzt …
Um die mittlerweile immerhin vier Container unserer FLEXess-Applikation komfortabel bauen, starten und stoppen zu können bedienen wir uns Docker Compose. Hierbei handelt es sich um ein Werkzeug, um Anwendungen einfach zu betreiben, die aus mehreren Containern bestehen.
Einen guten Einstieg und Überblick über das Werkzeug gibt dessen Dokumentation (“Overview of Docker Compose”). Gegenüber “ausgewachsenen” Orchestrierungswerkzeugen wie Kubernetes hat es Einschränkungen, in unserer Blog-Serie hier zählt aber erst einmal “einfach”.
Die Konfigurationsdatei im YAML-Format, die alle Bestandteile der Applikation aufzählt, liegt als docker-compose.yml auf einer Ebene mit den Modulen. Docker Compose nennt die Anwendung standardmäßig wie das Verzeichnis, also “modules”. Dies lässt sich per Kommandozeilenparameter -p (für project name) überschreiben. Um diesen nicht jedesmal angeben zu müssen legen wir eine weitere Konfigurationsdatei .env mit dem Projektnamen “flexess” im Verzeichnis ab, die docker-compose bei jedem Aufruf ausließt.
Ein Start der kompletten FLEXess-Anwendung (Stand bis jetzt) vom Auschecken bis zum Nutzen läuft per Kommandozeile wie folgt ab.
$ git clone https://github.com/embarced/micro-moves.git
$ cd micro-moves/modules/games/
$ ./gradlew assemble
$ cd ..
$ docker-compose build
$ docker-compose up &
Installiert sein muss dafür ein JDK (zum Bauen von games) und Docker. Unter Ubuntu Linux etwa die folgenden Pakete mit apt-get: openjdk-8-jdk-headless, docker, docker-compose. Der verwendete Benutzer muss Mitglied der Unix-Gruppe docker sein, siehe “Post-installation steps for Linux”.
Das reverse-proxy-Modul ist in der Konfiguration das einzige, das einen Port veröffentlicht (bei uns 9000 für den HTTP-Transport von nginx, konfiguriert in docker-compose.yml). Die Anwendung ist daher im Anschluss über http://hostname:9000 im Browser aufrufbar. Die übrigen Services stehen nur innerhalb des Anwendungseigenen Netzes zur Verfügung (zu Netzwerken in Docker Compose siehe “Networking in Compose”). nginx wohnt selber in dem Netz und findet sie über ihre Service-Namen (z.B. chess-diagrams).
Die play-SPA kann die anderen Services (chess-diagrams, games) nun ansprechen, ohne deren Hostnamen und Port zu kennen. Sie verwendet einfach ihre eigenen Werte, die sich leicht per JavaScript im Browser ermitteln lassen. Es müssen dann nur noch die URLs erweitert werden, damit nginx das mit dem Routing hinbekommt (siehe Tabelle oben),
Docker Compose ermöglicht auch eine horizontale Skalierung “out-of-the-box”. Beispielsweise bringt der Befehl
$ docker-compose up --scale chess-diagrams=3 &
drei Exemplare des entsprechenden Services ins Spiel, die unser Reverse Proxy auch automatisch alle nutzt (Load Balancing). Mit dem games-Service funktioniert das leider nicht so gut, da er (noch) zustandsbehaftet ist.
Später werden Services auch innerhalb der FLEXess-Applikation andere Services direkt nutzen (nicht nur die play-SPA andere). Das Auffinden von Services innerhalb des Docker-eigenen Netzwerkes erspart uns hier den Einsatz einer separaten Service Registry wie beispielsweise Netflix Eureka oder HasiCorp Consul. Chris Richardson beschreibt dieses Lösungsmuster in seinem Das Microservices Patterns Buch genauer, siehe auch seine Musterbeschreibung online.
Neben den Kommandozeilentools gibt es auch einige graphische Werkzeuge zum Beobachten von und Interagieren mit Docker-Containern. Ganz hübsch finde ich Kitematic, das Teil der Docker Toolbox ist. Die folgende Abbildung zeigt die Oberfläche mit FLEXess in Betrieb.
Wir haben mit diesem Beitrag und Bauteil einen gehörigen Satz gemacht. Weitere Services unserer FLEXess-Applikation lassen sich nun leicht integrieren und im Reverse Proxy bekannt machen.
Das ist auch nötig, denn es gibt funktional und qualitativ noch Luft nach oben, z.B.:
Weitere Folgen dieser Blog-Serie adressieren (u.a.) die oben genannten Mängel. Das führt zu weiteren Services (etwa für die Spielerverwaltung und die Schachregeln) aber auch querschnittlichen Aspekten (allen voran Security).
Bleibt also (so hoffe ich) spannend.
Ach ja: Fragen und Anregungen sind natürlich jederzeit gerne Willkommen. Per Kommentar hier im Blog oder gerne auch per Mail direkt an mich …