Blog

Är Apache Groovy eller YAML bättre för pipelines?

JUN 4, 2020

De som skriver pipelines för Continuous Delivery delas vanligtvis in i två läger: Apache Groovy-lägret och YAML-lägret. Vilket är bäst?

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 dominerade området under en tid, men på senare tid har YAML-lösningar fått medvind. Den här jämförelsen bygger på mina erfarenheter av att arbeta med både Groovy och YAML, samt på diskussioner med andra.

Läsbarhet

YAML-pipelines ser ofta bra ut. Titta till exempel på exempelpipelinen från GitLab CI:

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 rspec

Det här exemplet är utan tvekan tydligt och lätt att förstå. Eftersom de flesta exempel är det, är den första reaktionen på att definiera pipelines i YAML ofta mycket positiv: ”Självklart ska vi definiera dem i YAML. Titta så snyggt slutresultatet blir!”

Groovy ser inte lika bra ut och är inte lika lätt att läsa, eftersom syntaxen är mer komplicerad:

node('docker'){
    checkout scm 
    stage('Build'){
       docker.image('node:6.3').inside{ 
          sh 'npm --version'
       }           
    }
} 

Groovy kan kännas lite skrämmande. Särskilt personer som inte programmerar i Java eller Scala har ofta starkt negativa känslor inför Groovy. Java-programmerare brukar däremot älska Groovy vid första ögonkastet. Men eftersom Java- och Scala-programmerare inte är i majoritet inom mjukvarubranschen (åtminstone inte på GitHub), är det lätt att förstå skiftet mot YAML-baserade pipelines. Det finns till och med Jenkins-pluginer och blogginlägg om att skriva Jenkins-pipelines i YAML!

Användbarhet

YAML stod ursprungligen för ”Yet Another Markup Language”, men beskrivs enligt Wikipedia som ”ett dataserialiseringsspråk som kan läsas av människor”. YAML är i alla fall en övermängd av JSON.

Det avsedda användningsområdet för både YAML och JSON är att serialisera data. YAML försöker göra resultatet mer lättläst för människor genom att ersätta hakparenteser med indrag och konventioner för radbrytningar. Poängen här är dock att YAML inte är ett skriptspråk. Konsekvensen för dem som arbetar med pipelines är att YAML inte kan uttrycka logik. Det finns inga if-satser, loopar eller variabler i YAML.

Många YAML-baserade CI-motorer erbjuder ett eget ramverk eller egna konventioner för att uttrycka logik. Ta till exempel de här GitLab-reglerna:

workflow:
  rules:
    - if: $CI_COMMIT_REF_NAME =~ /-wip$/
      when: never
    - if: $CI_COMMIT_TAG 
      when: never
    - when: always

När komplex logik läggs till i pipelines slutar de flesta att uppleva dem som enkla och lättlästa. Tvärtom börjar många känna att de måste kontakta sin favorit-”DevOps-kille” om något går fel i deras pipelines. Det beror på två faktorer:

Tröskeln för att fullt ut förstå pipelinen (som någon annan har skrivit) höjs snabbt. Människor söker ofta trygghet i okunskap.

Allt detta är helt förståeligt. Om du råkar vara den där ”DevOps-killen”, försök att förstå de ”vanliga” användarnas frustration och ge dem uppmuntran och empati. Och snacks. Särskilt när de gör ett bra jobb på egen hand. Snacks hjälper alltid.

En annan konsekvens av att YAML inte är ett skriptspråk är att de flesta YAML-pipelines antingen refererar till skript eller innehåller inbäddade skriptblock. Vanligtvis skrivs dessa block i Bash eller Python. Ta en snabb titt på det här exemplet:

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;;   
      done

Block som detta tar snabbt bort alla positiva känslor som ”vanliga” användare fortfarande har för ”enkla YAML-pipelines”.

Groovy är ett riktigt programmeringsspråk. Därför erbjuder det alla funktioner som krävs för att uttrycka logik. Fördelen är att Groovy-pipelines nästan aldrig innehåller något annat än Groovy (och shell-kommandon på en rad).

Nackdelen är att användare som inte känner till syntaxen måste lära sig ett ganska komplicerat DSL innan de kan känna sig bekväma med pipelines. Titta till exempel på den här ”enkla” Groovy-funktionen som innehåller en closure:

def withImageStagingSelector(Closure selector) {
      this.imageselector = selector ?: { img -> img }
      this
}

De flesta håller nog med om att detta inte är enkelt att förklara. Det är ganska vanligt att Groovy-pipelines innehåller komplex logik direkt i pipelinefilerna. Det gör det svårt att förstå vad som händer.

Å andra sidan är det enkelt att importera ett bibliotek i Groovy och anropa en funktion med parametrar. Till exempel:

mycorplib.deploy(‘myapp’, ‘production’)

YAML har däremot inget stöd för funktioner alls. Möjligheten att skicka parametrar till funktionsanrop är en betydande fördel för Groovy jämfört med YAML.

Slutsats

Jag vill återvända till grunderna en stund och påminna dig om att pipelines normalt består av operationer för build, test och driftsättning. Alla dessa operationer består i grunden av logik. Ett skript för driftsättning är trots allt bara en skriptad definition av exakt hur driftsättningen ska fungera. Med andra ord är det ett uttryck för logik.

Utifrån detta kan vi säkert dra slutsatsen att YAML inte fungerar för pipelines på egen hand. Det måste alltid kompletteras med ett riktigt skriptspråk eller ett ramverk som tolkar YAML-data som logik.

Det vore dock vårdslöst att dra slutsatsen att Groovy alltid är rätt val. Jag anser faktiskt att beprövad praxis för alla pipelines är att hålla dem enkla och flytta merparten av logiken till återanvändbara funktioner eller mallar. Dessutom är det lätt att se när logiken i en YAML-pipeline överskrider en acceptabel komplexitetsnivå, eftersom YAML helt enkelt inte kan implementera komplex logik.

Min slutliga uppfattning är att YAML i de flesta fall är bättre för att skriva pipelines, eftersom det skapar ett naturligt incitament att hålla komplex logik utanför pipelinefilerna. Dessutom är YAML-pipelines mer lättlästa eftersom den komplexa logiken abstraheras bort.

Jag försöker dock inte utplåna all användning av Groovy från universum. Groovy fungerar bättre med importer och är ett utmärkt val när det primära programmeringsspråket i din organisation är Java eller Scala.

Oavsett vilket du väljer är det viktigt att förstå att språket egentligen är ett sekundärt val som beror på sammanhanget. Det finns ingen universell beprövad praxis för detta. Det finns vissa universella beprövade metoder för pipelines, men ingen av dem handlar om att välja Groovy eller YAML.

  • DevOps
  • CI/CD

Subscribe to our newsletter