Menschen, die Continuous-Delivery-Pipelines erstellen, lassen sich im Allgemeinen in zwei Lager einteilen: das Apache-Groovy- und das YAML-Lager. Welches ist besser?
Markus Suonto
Cloud Architect
Markus is a Cloud Architect based in Helsinki. He likes to apply the latest open-source technologies to enterprises and occasionally talks about it.
Groovy-Pipelines dominierten eine Zeit lang, doch in letzter Zeit haben YAML-Lösungen Rückenwind. Dieser Vergleich basiert auf meinen Erfahrungen mit Groovy und YAML sowie auf Gesprächen mit anderen.
Lesbarkeit
YAML-Pipelines sehen in der Regel gut aus. Schauen wir uns zum Beispiel diese Beispiel-Pipeline aus GitLab CI an:
image: "ruby:2.5"
before_script:
- apt-get update -qq && apt-get-install -y -qq sqlite3 libsqlite3-dev nodejs
- ruby -v
- which ruby
- gem install bundler --no-document
- bundle install --jobs $(nproc) "${FLAGS[@]}"
rspec:
script:
- bundle exec rspec
rubocop:
script:
- bundle exec rspecDieses Beispiel ist zweifellos klar und verständlich. Da die meisten Beispiele so aussehen, fällt die erste Reaktion auf die Definition von Pipelines in YAML oft sehr positiv aus: „Natürlich definieren wir sie in YAML. Schaut nur, wie schön das Ergebnis aussieht!“
Groovy sieht nicht so gut oder leicht lesbar aus, da die Syntax komplexer ist:
node('docker'){
checkout scm
stage('Build'){
docker.image('node:6.3').inside{
sh 'npm --version'
}
}
} Groovy kann etwas abschreckend wirken. Besonders Menschen, die nicht mit Java oder Scala programmieren, haben oft starke Vorbehalte gegenüber Groovy. Java-Programmierer lieben Groovy dagegen meist auf den ersten Blick. Da Java-/Scala-Programmierer in der Softwarebranche jedoch nicht die Mehrheit stellen – zumindest nicht auf GitHub –, ist die Entwicklung hin zu YAML-basierten Pipelines leicht nachvollziehbar. Es gibt sogar Jenkins-Plugins und Blogbeiträge darüber, wie sich Jenkins-Pipelines in YAML schreiben lassen!
Benutzerfreundlichkeit
YAML stand ursprünglich für „Yet Another Markup Language“, laut Wikipedia handelt es sich jedoch um „eine für Menschen lesbare Sprache zur Datenserialisierung“. In jedem Fall ist YAML eine Obermenge von JSON.
Sowohl YAML als auch JSON dienen dazu, Daten zu serialisieren. YAML versucht, das Ergebnis für Menschen besser lesbar zu machen, indem es Klammern durch Einrückungen und Konventionen für Zeilenumbrüche ersetzt. Entscheidend ist jedoch: YAML ist keine Skriptsprache. Für Pipelines bedeutet das, dass YAML keine Logik ausdrücken kann. YAML kennt keine if-Klauseln, Schleifen oder Variablen.
Viele YAML-basierte CI-Engines bieten eigene Frameworks oder Konventionen, um Logik auszudrücken. Nehmen wir zum Beispiel diese GitLab-Regeln:
workflow:
rules:
- if: $CI_COMMIT_REF_NAME =~ /-wip$/
when: never
- if: $CI_COMMIT_TAG
when: never
- when: alwaysSobald Pipelines komplexe Logik enthalten, empfinden die meisten Menschen sie nicht mehr als einfach und gut lesbar. Im Gegenteil: Viele haben dann das Gefühl, ihren bevorzugten „DevOps-Experten“ kontaktieren zu müssen, wenn etwas mit ihren Pipelines schiefläuft. Das hat zwei Gründe:
Die Hürde, eine Pipeline vollständig zu verstehen, die jemand anderes geschrieben hat, steigt schnell.
Menschen ziehen es oft vor, es nicht genauer wissen zu müssen.
All das ist völlig verständlich. Wenn ihr zufällig dieser „DevOps-Experte“ seid, versucht bitte, die Frustration der „normalen“ Nutzer zu verstehen, und begegnet ihnen mit Ermutigung und Empathie. Und Snacks. Besonders dann, wenn sie eigenständig gute Arbeit leisten. Snacks helfen immer.
Eine weitere Folge davon, dass YAML keine Skriptsprache ist: Die meisten YAML-Pipelines referenzieren entweder Skripte oder enthalten eingebettete Skriptblöcke. Typischerweise werden diese Blöcke in Bash oder Python geschrieben. Schaut euch kurz dieses Beispiel an:
upload:
image: alpine
stage: upload
script:
- for f in $(find . -name '*.yml' -o -name '*.yaml'); do
bname=${f#./};
if [[ "$bnam" == ".gitlab-ci-yml" ]]; then
continue;
fi;
if [[ "$CI_COMMI_REF_SLUG" == "edge" ]]; then
sed -i.bak 's/s-latest/latest/g' $bname;
fi
url="$NEXUS_PIPELINES/$CI_COMMIT_REF_SLUG/$bname";
curl --verbose --show-error --fail ----upload-file $bname url;;
doneSolche Blöcke nehmen „normalen“ Nutzern schnell auch die letzten positiven Gefühle gegenüber „einfachen YAML-Pipelines“.
Groovy ist eine echte Programmiersprache und bietet daher alle Funktionen, die nötig sind, um Logik auszudrücken. Der Vorteil: Groovy-Pipelines enthalten fast nie etwas anderes als Groovy – und Shell-Einzeiler.
Der Nachteil ist, dass Nutzer, die mit der Syntax nicht vertraut sind, erst eine recht komplexe DSL lernen müssen, bevor sie sich mit den Pipelines wohlfühlen. Schaut euch zum Beispiel diese „einfache“ Groovy-Funktion mit einem Closure an:
def withImageStagingSelector(Closure selector) {
this.imageselector = selector ?: { img -> img }
this
}Die meisten werden wohl zustimmen, dass sich das nicht einfach erklären lässt. Groovy-Pipelines enthalten häufig komplexe Logik direkt in den Pipeline-Dateien. Dadurch wird es ziemlich schwierig nachzuvollziehen, was passiert.
Andererseits lassen sich in Groovy problemlos eine Bibliothek importieren und eine Funktion mit Parametern aufrufen. Zum Beispiel:
mycorplib.deploy(‘myapp’, ‘production’)Im Vergleich dazu unterstützt YAML überhaupt keine Funktionen. Die Möglichkeit, Funktionsaufrufen Parameter zu übergeben, ist ein wesentlicher Vorteil von Groovy gegenüber YAML.
Fazit
Kehren wir für einen Moment zu den Grundlagen zurück: Pipelines bestehen normalerweise aus Build-, Test- und Deployment-Operationen. All diese Operationen beruhen im Kern auf Logik. Schließlich ist ein Deployment-Skript nichts anderes als eine skriptbasierte Definition dafür, wie ein Deployment genau ablaufen soll. Mit anderen Worten: Es drückt Logik aus.
Auf Grundlage dieser Informationen können wir sicher schlussfolgern, dass YAML allein nicht für Pipelines ausreicht. Es muss immer durch eine echte Skriptsprache oder ein Framework ergänzt werden, das YAML-Daten als Logik interpretiert.
Es wäre jedoch vorschnell, daraus zu schließen, dass Groovy immer die richtige Wahl ist. Tatsächlich besteht die Best Practice für alle Pipelines darin, sie einfach zu halten und den Großteil der Logik in wiederverwendbare Funktionen oder Templates auszulagern. Außerdem lässt sich leicht erkennen, wenn die Logik einer YAML-Pipeline den akzeptablen Komplexitätsgrad überschreitet: Sie ist schlicht nicht in der Lage, komplexe Logik umzusetzen.
Meine abschließende Einschätzung: In den meisten Fällen eignet sich YAML besser zum Schreiben von Pipelines, weil es auf natürliche Weise dazu anregt, komplexe Logik außerhalb der Pipeline-Dateien zu halten. Zudem sind YAML-Pipelines besser lesbar, da komplexe Logik abstrahiert wird.
Ich möchte damit jedoch nicht jegliche Nutzung von Groovy aus dem Universum verbannen. Groovy funktioniert besser mit Imports und ist eine ausgezeichnete Wahl, wenn Java oder Scala die wichtigste Programmiersprache in eurem Unternehmen ist.
Unabhängig davon, wofür ihr euch entscheidet: Wichtig ist zu verstehen, dass die Sprache letztlich eine nachrangige, kontextabhängige Wahl ist. Dafür gibt es keine allgemeingültige Best Practice. Für Pipelines gibt es einige allgemeingültige Best Practices, doch keine davon betrifft die Wahl zwischen Groovy und YAML.
- DevOps
- CI/CD
Subscribe to our newsletter
Related blogs