In vielen Organisationen entwickeln Softwareteams ihre eigenen CI/CD-Pipelines, um wiederkehrende Aufgaben wie Code-Auschecken, Testing, Scanning, Build und Deployment zu bewältigen. Diese individuelle Vorgehensweise führt oft zu einem unnötigen Mehraufwand bei Konfiguration und Wartung, was Zeit kostet, die besser in die Entwicklung neuer Features investiert wäre. Obwohl die Aufgaben und Workflows zwischen den Teams ähnlich sind, erfindet jedes Team das Rad neu, anstatt von bereits existierenden Lösungen zu profitieren. Diese Praxis kann die Release-Zyklen verlängern und die Effizienz der Teams beeinträchtigen. Anzeige Kolumne Cloud Native: Dennis Sobczak Als Senior Software Ingenieur bei adesso SE hat Dennis neben seinen Kernthemen Softwarearchitektur und -Entwicklung auch langjährige Erfahrung mit DevSecOps. Skalierbare und robuste Anwendungen zu erstellen, stehen für Dennis im Vordergrund. Um diesen Herausforderungen zu begegnen, wäre es sinnvoll, unternehmensweite Standards für CI/CD-Pipelines zu etablieren. Dies könnte durch die Bereitstellung wiederverwendbarer Standard-Bausteine wie Job-Templates und GitLab Components geschehen, die zentral katalogisiert, dokumentiert und teamübergreifend bekannt gemacht werden. Solche Module sollten generisch und flexibel sein, um von verschiedenen Teams leicht eingebaut, ausgetauscht oder erweitert werden zu können, ohne komplexe Abhängigkeiten oder eine Vielzahl von Parametern. Ein unternehmensweiter Standard würde die Effizienz steigern und sicherstellen, dass alle Teams auf bewährte und stabile Lösungen zurückgreifen können, was letztlich die Entwicklung beschleunigt und die Qualität der Software erhöht. Der erste Ansatz und Voraussetzungen Im ersten Schritt gilt es, die eingesetzten Technologien – Programmiersprache, resultierende Artefakte, Konfigurationsdateien – zu erfassen und notwendige Steps für die CI/CD-Pipelines, Kontexte sowie die Kontext-Grenzen der Steps vollständig zu definieren. Den nachfolgenden Szenarien liegt beispielhaft eine Java-Anwendung zugrunde, die in Form eines Maven-Projekts in einem GitLab-Repository vorliegt. Der Technologie-Stack umfasst daher: Java, Maven, Docker und GitLab als CI/CD-System. Der Java-Sourcecode soll zunächst ausgecheckt, getestet (Unit-Tests) und im Anschluss kompiliert werden, also die Stages "test" und "build" durchlaufen. Die Verzeichnisstruktur der Anwendung: Anzeige app/ .mvn/wrapper src # Java Sourcecode der Anwendung .gitignore mvnw # Maven Wrapper pom.xml Der "naive" Ansatz Bevor die beiden GitLab-Konzepte Job Templates und GitLab Components zum Einsatz kommen, soll zunächst eine "naive" Test- und Build-Pipeline die Vorgehensweise veranschaulichen. Zum Ausführen der Unit-Tests kommt das Maven CLI Tool mit dem Kommando mvn test zum Einsatz. Der Befehl mvn package löst den Build der Anwendung aus. Beide Steps setzen ein Docker Container Image voraus, in dem das Maven CLI Tool enthalten und verwendbar ist – sofern sich die Wrapper-Datei mvnw nicht im Repository der Anwendung befindet. Ansonsten wäre statt mvn das Skript ./mvnw mit gleichnamigen Parametern auszuführen. Das Dockerfile ist wie folgt definiert: # syntax=docker/dockerfile:1 MAINTAINER FROM docker.io/library/debian:bookworm-slim ENV TZ=Europe/Berlin # tzdata vorausgesetzt RUN apt-get install -y mvn RUN useradd -u 10001 noadmin USER noadmin Bei dem Dockerfile sind folgende Aspekte zu beachten: Es ist eine konkrete Version der Spezifikation des Dockerfiles anzugeben. Das Docker Container Image (ist hier aus dem Docker Hub bezogen) muss leichtgewichtig sein. Es soll ausschließlich Programme und Bibliotheken enthalten, die zum erfolgreichen Ausführen der eigentlichen Aufgabe notwendig sind, damit der Download aus der Registry zügig erfolgt und etwaige Angriffsflächen für Hacker klein bleiben. Zudem ist auf die gefundenen Vulnerabilities im Docker Hub zu achten. Das Maven-CLI ist installiert und verwendbar. Die Zeitzone muss gesetzt werden, damit auch die Timestamps korrekt dargestellt werden. Das Docker Container Image ist "rootless". Es ist also ein dedizierter Nutzer zu erstellen, der Kommandos ausführt und Prozesse startet. Dieser Nutzer erhält eine UID, die unter anderem im Security Context in den Kubernetes-Deployment-Ressourcen (wie den GitLab-Runnern) zum Erhöhen der Sicherheit zur Laufzeit verwendet ist. Es empfiehlt sich, das Container Image vorab zu bauen, damit es sich aus einem Container Image Repository beziehen lässt. Nach Abschluss dieser Vorarbeiten lässt sich die initiale GitLab-Pipeline – in .gitlab-ci.yml im Projekt-Root der Anwendung – wie folgt definieren: # Stages und Abfolge definieren stages: - test - build # Code liegt bereits vor run_maven_test: stage: test image: name: # based on docker.io/library/openjdk:21 entrypoint: [""] script: - echo "Run Unit Tests with Maven" - mvn test run_maven_compile: stage: build image: name: # based on docker.io/library/openjdk:21 entrypoint: [""] script: - echo "Run Build with Maven" - mvn compile Nach dem Einchecken der Pipeline-Datei in das Repository der Anwendung rückt der Job in die Queue. Sobald ein GitLab-Runner verfügbar ist, laufen die definierten Steps sequenziell ab. Der beschriebene Ansatz hat einen unmittelbar erkennbaren Nachteil: Der Workflow lässt sich nicht direkt in andere Projekte über eine Referenz – etwa einen include – sauber importieren. Die einfachste Abhilfe in diesem Fall ist Copy und Paste. Dabei verstreut man denselben Code jedoch und muss ihn am Ende an allen Stellen und somit mehrfach pflegen. Solche zusätzlichen Aktivitäten werden umso aufwendiger, je mehr Stages und damit Steps eine GitLab-Pipeline enthält. Beispielsweise sollen die Stages "secrets-scanning" (zum Detektieren unwillentlich eingecheckter Secrets), "cve-scanning" (Abhängigkeiten nach bekannten CVEs scannen), "build-container-image" (zum Bauen eines Docker-Container-Images) und "dependency-update" (zum Aktualisieren der Abhängigkeiten) zu der bestehenden Pipeline hinzukommen, wobei die Tools Gitleaks, Aquasec Trivy, Kaniko Executor und Renovate zum Einsatz kommen.