"Replikation bezeichnet in der Wissenschaftstheorie die Wiederholung einer wissenschaftlichen Studie. Das Ziel ist dabei die Kontrolle und Überprüfung der berichteten Forschungsergebnisse. Im Endeffekt wird durch Replikation zweierlei erreicht: Zum einen steigt die Akzeptanz der erfolgreich replizierten Studie und zum anderen diszipliniert sie Forscher, in ihrer Studie sorgfältig zu arbeiten und Betrug und Fälschung in der Wissenschaft zu unterlassen."[1]
So steht es in der Wikipedia, wenn den einleitenden Text zum Thema Replikation (Wissenschaft) nachschlägt.
Wie garantiert man aber Replikation in den Computerwissenschaften? Ein "Trend" der sich abzeichnet, ist die Publikation von Daten. Somit soll die Replikation und Weiterentwicklung gewährleistet sein. [2] Das ist aber nicht ausreichend, denn der Code, welcher Algorithmen beschreibt, muss ebenfalls publiziert werden.
Wer jetzt schon mal eine Reproduktion eines Papers versucht hat, wird schnell merken, dass es nicht nur den Code braucht. Der Code baut auf eine Softwareversion auf, die in der Regel dem Lesenden unbekannt ist. Nur durch aufwendige Spurensuche und mit viel Glück ist eine Reproduktion möglich.
Wie lösen wir das Problem? Wir kapseln die Laufzeitumgebung des Codes und publizieren es mit. Docker wird dem geneigten Leser einfallen, doch Obacht: Docker ist nicht für die Anforderungen der Wissenschaft gemacht:
- Mit einem Docker Container während der Laufzeit zu interagieren ist aufwendig
- Export und Import ist nur sinnvoll auf Registry Ebene möglich
- Aktives entwickeln und ausprobieren ist innerhalb von Docker umständlich.
Die Lösung heisst Apptainer [3].
Apptainer ist eine Plattform, die versucht ein klassisches Problem der HPC (High Performance Computing) zu lösen. Software ist komplex und abhängig ihrer Laufzeitumgebung. Diese auf verschiedenen Systemen gleich zu halten (Laptop, Superrechner oder Firmencomputer) ist eine Sisyphusarbeit.
Apptainer packt die Laufzeitumgebung in einen Container, welcher als Datei (.sif) verteilt und gestartet werden kann. Zur Sicherung der Container können diese signiert oder verschlüsselt werden. Letzteres ist sinnvoll, wenn Daten enthalten sind, die eben nicht für die Öffentlichkeit (jetzt) zugänglich sein sollen.
Das Motto lautet BYOE: Bring Your Own Environment!
Ich nutze den Apptainer in meiner Arbeit ständig und bin sehr zufrieden damit.
Zum Erstellen eines Apptainer benötigst du ein Definition File. Am einfachsten lässt es sich am Beispiel aus der Dokumentation zeigen [4]:
Bootstrap: docker
From: ubuntu:{{ VERSION }}
Stage: build
%arguments
VERSION=22.04
%setup
touch /file1
touch ${APPTAINER_ROOTFS}/file2
%files
/file1
/file1 /opt
%environment
export LISTEN_PORT=54321
export LC_ALL=C
%post
apt-get update && apt-get install -y netcat
NOW=`date`
echo "export NOW=\"${NOW}\"" >> $APPTAINER_ENVIRONMENT
%runscript
echo "Container was created $NOW"
echo "Arguments received: $*"
exec echo "$@"
%startscript
nc -lp $LISTEN_PORT
%test
grep -q NAME=\"Ubuntu\" /etc/os-release
if [ $? -eq 0 ]; then
echo "Container base is Ubuntu as expected."
else
echo "Container base is not Ubuntu."
exit 1
fi
%labels
Author myuser@example.com
Version v0.0.1
%help
This is a demo container used to illustrate a def file that uses all
supported sections.
Header
Wird über Schlüsselwort Bootstrap
der Boostrap Agent definiert, welcher eine Vielzahl an Quellen für die Basis des Apptainer ermöglicht. Hier wird ein Ubuntu aus dem Docker Hub geladen, es sind jedoch ebenso andere Apptainer, ein Busybox basiertes System oder andere OCI konforme Container möglich.
%arguments
In dieser Sektion sind Variablen für die .def Datei definiert, die, wie im Header zu sehen, mit zwei geschwungenen Klammern und Leerzeichen {{ VERSION }}
ausgelesen werden.
%setup
Die darin enthaltenen Befehle werden auf dem Host System ausgeführt, der Pfad zum Container ist in der Variable APPTAINER_ROOTFS
gespeichert.
%files
Kopiert nur Dateien vom Host (absoluter Pfad innerhalb des Hosts) zum Container (absoluter Pfad innerhalb des Containers). Mit der Angabe %files from stage_name
können Dateien auch von Zwischenstufen, wie auch hier im Header definiert, kopiert werden. Das ist bei nachfolgenden Buildprozessen sinnvoll, wenn Daten aus verschiedenen Stages extrahiert und ein finales Image zusammengeführt werden sollen.
%environment
Definiert Environment Variablen, die während der Laufzeit geladen werden.
%post
Lade Daten aus dem Internet, füge Software dem Container hinzu oder baue eigenen Quellcode: Es ist alles möglich innerhalb des Containers. Agiere so als wärst du die root Nutzerin. Wichtig: Variablen aus der Hostumgebung sind nicht innerhalb dieser Laufzeitumgebung sichtbar.
Die Shell der Umgebung kann durch %post -c /bin/SHELL
angepasst werden, ansonsten wird die sh
oder bash
verwendet.
%runscript
Der Code hier wird später ausgeführt, wenn der Apptainer mit apptainer run CONTAINER.sif
aufgerufen wird. Argumente können klassisch mit $*
als einzelner String abgerufen werden.
%startscript
Vergleichbar mit %environment
, die darin enthaltenen Kommandos werden bei jedem Start des Apptainer ausgeführt.
%test
Wird am Ende des Buildprozesses ausgeführt und muss mit einer 0
zurückgeben, damit der Bauvorgang erfolgreich abgeschlossen wird.
%labels
Speichert Metadaten zu Apptainer ab. Sie werden bei einem Aufruf mittels apptainer inspect CONTAINER.sif
angezeigt.
%help
Speichert den Hilfetext zum Apptainer, welcher später mit apptainer run-help CONTAINER.sif
aufgerufen wird.
Nutzungszenario
Ich selbst verwende bei der Entwicklung von Software gerne apptainer build --sandbox ...
, es wird kein Image gebaut, sondern die Ergebnisse in eine Sandbox abgelegt. Damit ist auch nachträglich leicht anzupassen, entweder innerhalb der Apptainer Shell mit Root Rechten apptainer shell --writeable --fakeroot CONTAINER/
oder von aussen über den Zugriff des Ordners durch das Hostsystem.
Quellen:
[1] https://de.wikipedia.org/wiki/Replikation_(Wissenschaft)
[2] https://de.wikipedia.org/wiki/Open_Data
[3] https://apptainer.org/
[4] https://apptainer.org/docs/user/latest/definition_files.html#sections