Kontext-Switching mit asdf

22.06.2022 Raffael Schneider
DevOps version-manager

Das Leben eines Entwicklers ist nicht immer einfach. Nebst dem, dass ein Entwickler Resultate liefern muss, hat er auch noch die Aufgabe, dabei eine verständliche, wartbare Codebase zu hinterlassen. Zu der grossen Anzahl von Tools, über die man als Entwickler den Überblick behalten muss, kommen auch noch verschiedene Versionen eben dieser Tools, zwischen denen man je nach Anwendung hin und her "switchen" muss. Diese Form von Kontext-Switching lässt sich mit einem Version-Manager wie asdf stark vereinfachen. Da Kontext-Switching in der IT an unterschiedlichen Punkten verwendung findet, möchte ich dir zunächst einen Überblick über die Grundlagen und die Entwicklung des Kontext-Switching geben. Viel Spass! 😄

Möglichkeiten des Kontext-Switching ↔️

Bevor man von einem Kontext in einen anderen Kontext wechseln kann, müssen zwei Kontexte parallel zueinander und unabhängig voneinander existieren können. Das setzt voraus, dass Kontexte isoliert werden können. Genau diese Kontextisolation ist ein Treiber vieler Stossrichtungen in der IT-Welt. Es ist ja gerade der Wunsch danach, gewisse Aspekte eines gegebenen Systems so zu isolieren, dass man etwas ganz einfach wieder replizieren kann, um dann ein gewünschten Endzustand zu erzielen, ohne dass dieser von äusseren Einflüssen beeinflusst wird.

Zuerst hat man ganze Betriebssysteme isoliert und nannte das Hardware-Virtualisierung. Natürlich sage ich das hier sehr vereinfachend und lasse viele Teilkonzepte aussen vor, um nicht zu weit ausholen zu müssen. Als nächster Schritt kam die Virtualisierung, als auf Betriebssystem-Ebene eine Isolation der Umgebung möglich wurde. Dies nennt man umgangssprachlich auch Containerisierung. Innerhalb einer virtualisierten Umgebung steht nichtsdestotrotz im Vordergrund, welche Bedingungen darin herrschen, sprich, welche Softwarepakete verbaut sind. Für die Verwaltung dieser Pakete braucht es einen Mechanismus. Package-Manager können diese Funktion übernehmen, sofern die Verwaltung nicht über manuelle Installationsschritte übernommen wird.

Mit einer Umgebungsisolierung über eine Hardware- oder OS-Level-Virtualisierung ist aber dennoch viel betrieblicher Overhead notwendig. Gerade in solchen Situationen verschaffen alternative Methoden Abhilfe. Worauf ich hier hinaus will ist, dass man mithilfe von einem sogenannten Version-Manager wie asdf genau ein solches Software-Tool hat, mit dem man diesen betrieblichen Overhead umgehen kann.

Mit einem Version-Manager löst man eines der mühsamsten Probleme des täglichen Geschäfts eines jeden Entwicklers, nämlich die Harmonisierung der Entwicklerumgebung. Es gibt auch noch andere Alternativen wie der Nix Package Manager, aber heute werden wir uns asdf als kleines aber feines Tool, das sich besonders für das Versionsmanagement von lokalen Entwicklungsumgebungen eignet, anschauen.

asdf – Der (einfache) Versionsmanager 😇

asdf ist vielleicht ein etwas unscheinbarer oder gar uninspirierter Name, doch steckt dahinter eine optimale Anspielung darauf, wie transparent und zugänglich es als Hilfsmittel im Entwickleralltag sein kann. asdf ist in erster Linie ein Versionsmanager und übernimmt die automatische Versionsverwaltung. Dabei erkennt asdf, wo man sich in einem Verzeichnis oder Projekt befindet und entscheidet, welche Version von einem gegeben Toolset hierfür angebracht ist.

Sofern man asdf richtig konfiguriert hat und weiss, welche Hebel man betätigen muss, um asdf einsetzen zu können, braucht man sich nicht mehr um Versionen von Entwicklungs- und Softwareumgebungen zu kümmern. Das wirkliche grandiose an asdf ist einfach der Umstand, wie einfach die Versionsverwaltung gehandhabt wird und wie leicht man deren Setup nach seinen Bedürfnissen und Ansprüchen erweitern kann.

asdf in Kürze

Das Prinzip hinter der Funktionsweise von asdf ist ganz einfach. Es gibt im Home-Verzeichnis des Users ein verstecktes .asdf-Verzeichnis. Darin ist der Einstiegspunkt von asdf als ~/.asdf/asdf.sh-Shell-Skript enthalten. Sobald der Pfad dieses Shell-Skripts der Shell hinzugefügt wurde, kann man asdf aufrufen. Neben dem Einstiegspunkt gibt es noch weitere Ordner, wie zum Beispiel das ~/.asdf/plugins-Verzeichnis, in dem alle verfügbaren Runtimes enthalten sind.

asdf kennt globale und lokale Versionen für Runtimes. Lokale Versionen sind deklariert, sobald für ein gegebenes Verzeichnis, sowie dessen Subverzeichnisse, eine .tool-versions-Textdatei vorhanden ist. Darin wird einfach die Runtime-Bezeichnung, eine eindeutige Bezeichnung, welche dem entsprechenden asdf-Plugin gleichkommt, sowie dessen Version angegeben.

#<target-dir>/.tool-versions
elixir 1.13.2-otp-24
erlang 24.3

Globale Versionen sind Versionen, die man für alle anderen Verzeichnisse über die asdf-CLI deklariert hat.

How-To und Best-Practices 🧐

asdf ist nicht nur "straightforward" in seiner Benutzung, sondern lässt sich auch sehr einfach installieren. Hier werde ich eine Standard-Installation von asdf vornehmen, sowie eine Elixir-Runtime mithilfe des Plugins installieren. Danach deklariere ich eine globale Version für die Elixir-Runtime von 1.13.2-otp-24 und eine lokale Version von 1.12-otp-23.

Installation

Installation auf MacOS.

$ brew install asdf

Sonst GNU/Linux oder Unixartige, einfach über die GitHub-Repo.

$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.9.0

Genau wie Brew setzt das obere Kommando die Repo in ein Dot-Verzeichnis im Home-Verzeichnis, sprich ~/.asdf.

Das muss dann noch in die .bashrc oder in die .zshrc.

$ . $HOME/.asdf/asdf.sh

Hier vergewissern wir uns dass auch das asdf.bash-File unter completions/ liegt.

$ . $HOME/.asdf/completions/asdf.bash

Erste Schritte

Sobald asdf installiert ist, können wir es eigentlich schon nutzen.

Plugin installieren

Beispiel an Elixir, da wir sowieso ein Phoenix-Projekt aufsetzen müssen.

$ asdf plugin add elixir

Version installieren

So, jetzt versteht asdf Elixir. Jetzt können wir beliebige Versionen, sprich, Language-Runtimes dafür provisionieren.

$ asdf install elixir main-otp-24
==> Checking whether specified Elixir release exists...
==> Downloading main-otp-24 to /Users/rschneider/.asdf/downloads/elixir/main-otp-24/elixir-precompiled-main-otp-24.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 6434k  100 6434k    0     0  34.3M      0 --:--:-- --:--:-- --:--:-- 35.7M
==> Copying release into place

Lass uns für dieses Beispiel noch eine ältere Elixir-Version installieren. Um das ganze ein wenig realistischer zu machen, nehmen wir eine ältere OTP-Version, nämlich die 23er-Version.

$ asdf install elixir main-otp-23
==> Checking whether specified Elixir release exists...
==> Downloading main-otp-23 to /Users/rschneider/.asdf/downloads/elixir/main-otp-23/elixir-precompiled-main-otp-23.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 6267k  100 6267k    0     0  22.3M      0 --:--:-- --:--:-- --:--:-- 22.9M
==> Copying release into place

Kontexte definieren

Jetzt setzen wir eine globale und mehrere lokale Versionen.

$ asdf global elixir main-otp-24

Falls ich jetzt in einem zufälligen Verzeichnis bin, kann ich die Elixir-Version prüfen.

$ elixir --version
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1]

Elixir 1.14.0-dev (94bab44) (compiled with Erlang/OTP 24)

Es gibt ein 3rd-Party-Projekt, welches die 23-OTP-Version von Elixir nutzt, das ich in einem GitHub-Repo gefunden habe und jetzt beispielhaft nutzen werde.

$ asdf local elixir main-otp-23

Dieser Befehl platziert ein .tool-versions-Datei in das Zielverzeichnis.

elixir main-otp-23

Jetzt sollte die Elixir-OTP-Version in diesem Verzeichnis auch angepasst sein.

$ elixir --version
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1]

Elixir 1.14.0-dev (94bab44) (compiled with Erlang/OTP 23)

✅ Hinweis für Elixir

Bei Elixir ist wichtig anzumerken, dass neben Elixir stets eine Erlang-Toolchain installiert ist. Da die Erlang-Toolchain auch die OTP-Runtime zur Verfügung stellt, müsste die Elixir-Version immer für die gleiche Ziel-Runtime, sprich, die gleiche OTP-Plattform kompiliert sein. In diesem Fall müssten wir in der lokalen .tool-versions noch die Erlang-Version auf eine 23er-Version downgraden.

Jetzt kommt die Crux: Erfahrungsgemäss werden alle Versionen von Erlang kompiliert. Jetzt installieren wir die Erlang-Version 23.3. Davon wird das entsprechende Repository gezogen und danach kompiliert. Das kann eine Weile dauern.

❯ asdf install erlang 23.3
asdf_23.3 is not a kerl-managed Erlang/OTP installation
No build named asdf_23.3
Downloading 23.3 to /Users/rschneider/.asdf/plugins/erlang/kerl-home/archives...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   652  100   652    0     0  10086      0 --:--:-- --:--:-- --:--:-- 11438
100 94.7M  100 94.7M    0     0  28.4M      0  0:00:03  0:00:03 --:--:-- 27.6M
Extracting source code
Building Erlang/OTP 23.3 (asdf_23.3), please wait...
...

Sobald man diese erfolgreich gebaut hat (auf meinem MacBook Pro mit dem neuen M1-Prozessor hat das gut 5 Minuten gedauert), kann man Erlang als Eintrag in die .tool-versions wie folgt aufnehmen:

elixir main-otp-23
erlang 23.3

Jetzt sollte in der ersten Zeile von elixir --version "Erlang/OTP 23" stehen. Somit kann man eine OTP-23-fähige Elixir-Runtime nutzen.

❯ elixir --version
Erlang/OTP 23 [erts-11.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1]

Elixir 1.14.0-dev (94bab44) (compiled with Erlang/OTP 23)

In der Prompt wird die Version auch angezeigt:

Best-Practices 👌

In der Standardausführung von asdf werden gewisse Dateien im .asdf-Verzeichnis angelegt. Was aber nicht standardmässig erstellt wird, ist eine Runtime-Configuration. Diese kommt aber nicht ins .asdf-Verzeichnis sondern ins Home-Verzeichnis. Darin können gewisse Einstellungen angepasst werden. Es ist sicherlich ein Best-Practice, diese Einstellungen zu kennen und zu wissen, wie man das Verhalten von asdf für die eigenen Bedürfnisse anpassen kann.

Erstelle am besten gleich die Runtime-Konfiguration als ~/.asdfrc-Datei im Home-Verzeichnis. In der offiziellen Dokumentation ist das hier beschrieben.

In dieser .asdfrc-Datei kann man folgende vier Parameter anpassen:

legacy_version_file = no
use_release_candidates = no
always_keep_download = no
plugin_repository_last_check_duration = 60

Falls man die Möglichkeit offen lassen möchte, ob ein Fallback für eine Legacy-Version bestehen soll, so ist müsste man die erste Property auf "wahr" wie folgt setzen: legacy_version_file = true.

Erweiterbarkeit

Es gibt auch ein relativ gut dokumentiertes asdf-Plugin-Template, mit dem man ein eigenes Plugin schreiben kann. Das darf auch in einem privaten Git-Repository liegen, da man beim anziehen eines Plugins als letzten Parameter das Git-Repository angeben kann. Das Schema wäre wie folgt:

$ asdf plugin add <name> [<git-url>]
$ asdf plugin add mytemplate https://github.com/b-nova-openhub/my-template.git

asdf bei b-nova

Da die Community auch eigene asdf-Plugins schreiben kann, gibt es mittlerweile eine Vielzahl von asdf-Plugins. Die Liste wird im offiziellen GitHub-Repo unter asdf-vm/asdf-plugins gepflegt.

Wir nutzen asdf für folgende Runtimes:

  • asdf-graalvm
  • asdf-hashicorp (terraform)
  • asdf-java

Fazit 🙌

asdf ist ein nettes Open-Source-Projekt, welches einen zentralen Teilbreich des Entwickleralltags zu einem quasi "No-Brainer" vereinfacht. Gerade in der heutigen DevOps-Landschaft, in der man nicht immer zwingend alle Projekte für die lokale Entwicklung containerisiert hat, ist asdf ein genialer Lückenfüller und erfüllt seinen Zweck ungemein gut.

Vor- und Nachteile

  • 👍 Einfache Installation und Setup
  • 👍 Viele, viele Plugins für so ziemlich alle Runtimes, Software-Pakete und Programmiersprachen
  • 👍 Viele Versionen
  • 👎 Manche ältere oder sehr spezifische Versionen müssen kompiliert werden, was zeitaufwändig sein kann

Weiterführende Links und Ressourcen 🤓

asdf | Offzielle Webseite

asdf-vm/asdf | GitHub Repository

The future-proof solution to manage your Flutter versions: global, FVM, or asdf-vm? | iainsmith.me

asdf and Docker for Managing Local Development Dependencies | pawelurbanek.com

Raffael Schneider – Crafter, Disruptor, Freethinker. Als leidenschaftlicher Software-Crafter schreibt Raffael gerne über Programmiersprachen und Resilienz in modernen verteilten Systemen. Ob DevOps, SRE oder Systemarchitektur, Raffael weiss stets wie man diese Dinge auf eine neue Weise betrachten kann.