Java verwenden in der Azure Data Factory

Microsofts Azure Data Factory (ADF) ist ein Datenintegrationsdienst, in dem man komplexe Arbeitsabläufe in Form von Pipelines erstellen und ausführen kann. Seit Ende Juni 2018 gibt es ADF V2, in der man Pipelines bequem in einer grafischen Oberfläche im Browser entwickeln kann – ähnlich wie mit SQL Server Integration Services. Dabei hat man für die einzelnen Activities (den Bausteinen der Pipeline) viele Möglichkeiten, andere Dienste des Azure-Ökosystems einzubinden, und so Daten zu verarbeiten und zu bewegen.

Was fehlt, ist eine einfache Möglichkeit ein einzelnes Java-Programm auszuführen – wie saracus consultings Data-Warehouse-Automatisierungssoftware DWautomatic. Zwar ist Java eine zuverlässige, weit verbreitete und mit vielen Systemen kompatible Technologie (man denke an das auf vielen Smartphones und Tablets verwendete Android), doch sie ist nicht direkt in Data Factory integriert.

Dennoch gibt es Möglichkeiten, Java in einer ADF Pipeline zu verwenden.

Java ausführen via Azure Databricks

Azure Databricks basiert auf Apache Spark, einer Analytics Engine für Big Data. Als solche ist Spark vor allem darauf ausgelegt, verteilte Anwendungen in großen Clustern auszuführen.

In Azure Databricks lassen sich Arbeitsabläufe in Form von Notebooks anlegen. Außer diesen Notebooks können wir von ADF aus auch direkt ein geeignetes Java JAR oder Python Code ausführen lassen.

Dazu ist etwas Vorarbeit nötig.

Einen Cluster erstellen

Die notwendige Rechenleistung kann in Azure Databricks über den Menüpunkt „Clusters“ bereitgestellt werden. Je nach Bedarf lässt sich hier sowohl die Ausstattung der einzelnen Cluster Nodes als auch deren Anzahl festlegen. Wird dieser Schritt nicht in Azure Databricks selbst durchgeführt, so kann später stattdessen aus ADF aus ein Job-spezifischer Cluster konfiguriert werden.

Azure Databricks und Data Factory verbinden

Damit der Azure-Databricks-Cluster aus einer Data Factory-Pipeline aus verwendet werden kann, müssen die Dienste verbunden werden. Hierzu dient der Punkt „Connections“ im Author-Bereich von ADF. Unter Compute befindet sich unter anderem Azure Databricks. Für die Verbindung wird ein Access Token oder Azure Key Vault benötigt; der Token muss im Azure Databricks Interface erstellt werden. Weiterhin kann hier ausgewählt werden, ob ein neuer oder ein vorher erstellter Cluster verwendet werden soll.

Ein JAR mit Azure Databricks ausführen

In der ADF Pipeline können Sie nun die Databricks – Jar Activity hinzufügen und die Verbindung zu Azure Databricks und die Main Klasse konfigurieren. Der DBFS-Pfad zur Jar-Datei kann unter „Append Libraries“ angegeben werden – dazu muss sie vorher in Azure Databricks hochgeladen worden sein. Ein erster Klick auf „Debug“ führt die Pipeline aus, auch wenn sie noch nicht published wurde …

… und dann heißt es warten, denn bis unsere Anwendung läuft kann es aufgrund des notwendigen Provisionings der Spark-Umgebung einige Minuten dauern.

Spark, und damit auch Azure Databricks, eignet sich für Anwendungen, die stark parallelisierbar sind. Für ein HelloWorld oder auch unser relativ kleines, effizientes Jar, das nicht viel Leistung benötigt, ist es ein zu großer Overhead.

Glücklicherweise gibt es noch eine weitere Alternative.

 

Java ausführen in Azure Batch

Wenn man Tasks ausführen möchte, die kein Spark benötigen, ist der Dienst „Batch“ eine Alternative. In Azure Batch konfiguriert man Pools von Servern, die einzelne Tasks ausführen können. Im Gegensatz zu Azure Databricks läuft hierbei jeweils jeder Task auf einem einzelnen Node und wird nicht über den Pool verteilt. Der Overhead von Spark fällt weg. Die Anzahl der im Pool zur Verfügung stehenden Server lässt sich frei konfigurieren und bei Bedarf ändern: Fest oder dynamisch mit einem Skalierungsscript.

Azure Batch vorbereiten

Unter „Features“ – „Pools“ lassen sich neue Pools anlegen und vorhandene Pools verwalten. Die Pool-ID identifiziert den Pool und wird bei der Konfiguration der Verbindung zwischen ADF und Batch benötigt; die Eigenschaften der Nodes und deren Anzahl lassen sich je nach Bedarf und Budget konfigurieren. Interessant ist hierbei auch die Möglichkeit, Low-Priority-Nodes aus Überkapazitäten in Azure zu verwenden – diese sind günstiger als ihre dezidierten Pendants.

Azure Batch und Data Factory verbinden

Ähnlich wie bei der Verwendung von Azure Data Bricks lässt sich eine Verbindung im Author-Bereich unter „Connections“ anlegen. Die dazu notwendigen Daten finden sich im Batch-Bereich des Azure Portals; der Pool Name muss dem vorher angegebenen entsprechen.

In ADF gibt es unter „Batch Service“ nur einen Baustein, nämlich „Custom“. Wie der Name vermuten lässt ist dieser allerdings sehr flexibel, denn er ermöglicht es einen beliebigen Befehl an den Batch-Dienst zur Ausführung zu schicken. Wenn Sie z.B. einen Pool mit Linux-Computern in Batch haben, können Sie mit dem einzelnen Befehl

/bin/bash -c 'java -version'

die aktuell installierte Java-Version anzeigen lassen. Die Verwendung von /bin/bash -c ist nützlich, um bei Bedarf Zugriff auf Umgebungsvariablen zu haben.

Ausgaben überprüfen

Sowohl stdout als auch stderr werden in eine Textdatei auf dem ausführenden Node geschrieben. Diese Dateien finden Sie im Batch Portal über die Taskliste unter dem Punkt „Files on node“. In unserem Beispiel stellen wir so fest, dass die installierte Java-Version…

… nicht vorhanden ist. In den Standardimages für Batch-Computer ist Java nicht installiert.

Ein eigenes Image für die Pool-Computer verwenden

Neben der Verwendung von Standardimages besteht die Möglichkeit, selbst erzeugte Images für die Pool-Computer zu verwenden. In einem solchen Image lässt sich Java sowie ggf. benötigte weitere Software und Dateien vorinstallieren. Microsoft stellt hierzu eine Anleitung bereit. Dieser Ansatz ist sehr flexibel, erfordert aber auch größere Vorarbeit und Wartung. Sie sind selber verantwortlich dafür, dieses Image aktuell und sicher zu halten, so dass mögliche Angreifer keine Chance haben, Ihre Abläufe zu stören oder Daten zu stehlen.

Container verwenden

Azure Batch lässt auch die Ausführung von Containern zu, wie sie z.B. von der beliebten Software Docker verwendet werden. Dazu müssen Sie beim Anlegen des Pools ein Betriebssystemimage wählen, das Container unterstützt; derzeit gibt es neben einer WindowsServer-Variante noch Linux-Images, die unter dem Publisher „microsoft-azure-batch“ zur Verfügung gestellt werden. Achten Sie darauf, dass unter Offer oder SKU explizit „Container“ erwähnt werden! Es lohnt sich, in der Poolkonfiguration die verwendeten Container-Images direkt mit anzugeben, so dass diese direkt beim Starten der Pool-Computer geladen werden, und nicht erst wenn ein Task sie bereits benötigt. Für viele Einsatzszenarien gibt es passende Container, die man u.a. im Azure-Portal selbst suchen kann, so auch welche mit Java-Unterstützung.

Jeder Task, der in einem containerisierten Pool ausgeführt wird, muss selbst eine Containerkonfiguration enthalten, in der angegeben wird, innerhalb welchen Containers der Task laufen soll – schließlich können mehrere Container-Images auf den Nodes vorhanden sein. Legt man einen Task innerhalb der Batch-Oberfläche an, befindet sich dieser Punkt unter den Advanced Settings. Leider ist es derzeit nicht möglich, dies beim Aufruf von Batch aus ADF zu konfigurieren. Der Container-Ansatz scheidet daher (noch) für die Ausführung von Java aus einer ADF Pipeline aus!

Java in einem Standardimage installieren

Es ist durchaus möglich, Softwareinstallationen durch einen Batch Task anzustoßen. Dies ist sinnvoll, wenn man Standardimages verwenden möchte. Laufen die Maschinen im Pool mit CentOS, so installiert der Befehl

sudo yum -y install java-1.8.0-openjdk

eine Java-Umgebung. Hierbei eskaliert sudo die eigenen Rechte innerhalb der Maschine, was in der Regel für Softwareinstallationen notwendig ist; yum ist die Standard-Paketverwaltung unter CentOS und anderen Linux-Distributionen. Unter Debian-basierten Linuxsystemen könnte man statt „yum“ „apt-get“ verwenden. Damit mit sudo die notwendigen Rechte erlangt werden können, muss der Task innerhalb von Batch mit einem Administratoruser ausgeführt werden.

Dies funktioniert z.B. einmalig durch einen manuell im Batch-Portal hinzugefügten Task. Wenn mehrere Computer im Pool existieren, haben Sie jedoch keine Kontrolle darüber, auf welchem Computer der Befehl ausgeführt wird; außerdem sollte die Installation sinnvollerweise auf jedem verfügbaren Server stattfinden. Ein geeignetes Mittel, dies sicherzustellen, ist die Einrichtung als Start Task. Der Start Task gehört zur Pool-Konfiguration, und wird auf jedem neu gestarteten Node ausgeführt. Wichtige Konfigurationseinstellungen in diesem Zusammenhang sind die User Identity für den Task (Admin-Rechte sind für Softwareinstallationen sinnvoll), sowie „Wait for success“. Ein Setzen dieser Einstellung auf true bewirkt, dass dem Node erst nach erfolgreicher Ausführung des Start Tasks andere Tasks zugewiesen werden. Ansonsten könnte versucht werden, einen Task auszuführen, ohne dass die Installation geglückt ist – in unserem Fall würde dies nur zu Fehlermeldungen führen.

Eine solche Installation, egal ob durch den Start Task oder manuell, überlebt auch den Neustart eines Servers. Es ist also nicht notwendig den Task danach erneut auszuführen. Ist die Installation als Startup-Task eingerichtet worden, so wird sie bei jedem Neustart erneut angestoßen; dies stellt jedoch kein größeres Problem dar, da yum erkennt, dass die Installation bereits durchgeführt wurde.

Hinzufügen eigener JARs in Batch

Ihre eigenen Java-Programme müssen den Weg auf die Nodes im Batch-Pool finden. Eine Möglichkeit dazu besteht durch das Feature „Applications“ im Batch-Bereich des Azure-Portals. Hier können Sie Ihre eigenen Programme und zugehörige Dateien im ZIP-Format hochladen. Hochgeladene Programme werden bei der Poolkonfiguration angegeben und werden dann bei jedem Start eines neuen Nodes automatisch aus dem verbundenen Storage geladen und entpackt. Der Ort der entpackten Programme wird in einer Umgebungsvariablen gespeichert. Um eine Übersicht der vorhandenen Variablen zu bekommen, kann z.B. der Befehl printenv ausgeführt werden.
Ein Programmaufruf für die Jar-Datei von MyApp Version 0.1 sieht dann z.B. so aus:

/bin/bash -c "java -cp \"${AZ_BATCH_APP_PACKAGE_myapp_0_1}/MyApp.jar\" MyStartClass"

Zusätzliche Libraries können wie gewohnt dem angegeben Classpath hinzugefügt werden, ebenfalls lassen sich der main-Methode der Startklasse weitere Parameter übergeben, die einfach hinter dem Klassennamen aufgelistet werden. Sollen mehrere Befehle ausgeführt werden, oder ist der Aufruf schlichtweg lang und schwer lesbar, so bietet es sich an, diese in ein Script zu packen und nur dieses direkt aus ADF aufzurufen.

Zugriff auf Azure SQL Datenbanken

Die Ausführung von Pipelines in Azure Data Factory ist vor allem attraktiv, wenn auch die Daten in der Azure Cloud liegen. SQL Datenbanken in Azure verhalten sich dabei ähnlich (aber nicht gleich) wie lokal installierte Instanzen des Microsoft SQLServer. Um eine Java-Anwendung mit einer Azure SQL Datenbank kommunizieren zu lassen, bietet sich daher der JDBC-Treiber von Microsoft an, mit dem auch On-Premise SQLServer verwendet werden können. In einigen Fällen muss jedoch die Anwendung selbst für Azure SQL angepasst werden. Dies betrifft z.B. das Wechseln der Datenbank mit dem USE-Befehl – stattdessen ist es notwendig, sich direkt mit der gewünschten Datenbank zu verbinden.

Fazit

Java als Teil von Azure Data Factory Pipelines auszuführen ist möglich, wenn die richtigen Dienste miteinander kombiniert werden. Einige Kombinationen funktionieren derzeit noch nicht wie gewünscht. Da die Azure Cloud stetig weiterentwickelt wird, ist anzunehmen, dass in Zukunft weitere Möglichkeiten hinzukommen werden.