[{"data":1,"prerenderedAt":817},["ShallowReactive",2],{"/de-de/blog/basics-of-gitlab-ci-updated":3,"navigation-de-de":40,"banner-de-de":454,"footer-de-de":464,"blog-post-authors-de-de-Itzik Gan Baruch":700,"blog-related-posts-de-de-basics-of-gitlab-ci-updated":714,"blog-promotions-de-de":754,"next-steps-de-de":807},{"id":4,"title":5,"authorSlugs":6,"authors":8,"body":10,"category":11,"categorySlug":11,"config":12,"content":16,"date":20,"description":17,"extension":25,"externalUrl":26,"featured":14,"heroImage":19,"isFeatured":14,"meta":27,"navigation":28,"path":29,"publishedDate":20,"rawbody":30,"seo":31,"slug":13,"stem":35,"tagSlugs":36,"tags":38,"template":15,"updatedDate":24,"__hash__":39},"blogPosts/de-de/blog/basics-of-gitlab-ci-updated.yml","Grundlagen der GitLab-CI-Pipeline: Aufgaben sequenziell parallel oder ohne Reihenfolge ausführen",[7],"itzik-gan-baruch",[9],"Itzik Gan Baruch","Nehmen wir an, dass du nichts über [kontinuierliche Integration (CI)](/topics/ci-cd/) and [why it's needed](/blog/how-to-keep-up-with-ci-cd-best-practices/) weißt und darüber, warum sie im Lebenszyklus der Softwareentwicklung benötigt wird.\n## Inhaltsverzeichnis\n- [Der erste Test in CI](#der-erste-test-in-ci)\n- [Ergebnisse von Builds zum Herunterladen bereitstellen](#ergebnisse-von-builds-zum-herunterladen-bereitstellen)\n- [Aufträge der Reihe nach ausführen](#aufträge-der-reihe-nach-ausführen)\n- [Welches Docker Image muss verwendet werden?](#welches-docker-image-muss-verwendet-werden%3F)\n- [Umgang mit komplexen Szenarien](#umgang-mit-komplexen-szenarien)\n- [Umgang mit fehlender Software/Paketen](#umgang-mit-fehlender-softwarepaketen)\n- [Directed Acyclic Graphs: Schnellere und flexiblere Pipelines](#directed-acyclic-graphs-schnellere-und-flexiblere-pipelines)\n- [Wie wertest du deine Pipeline auf?](#wie-wertest-du-deine-pipeline-auf%3F)\n  - [Automatisierte Tests in CI-Pipelines einbinden](#automatisierte-tests-in-ci-pipelines-einbinden)\n  - [Matrix-Builds](#matrix-builds)\n- [Unit-Tests](#unit-tests)\n  - [Was sind Unit-Tests?](#was-sind-unit-tests%3F)\n  - [Best Practices für Unit-Tests](#best-practices-für-unit-tests)\n    - [JUnit Test-Report](#junit-test-report)\n- [Strategien für Integrations- und End-to-End-Tests](#strategien-für-integrations--und-end-to-end-tests)\n- [Testumgebung](#testumgebung)\n- [Implementierung von Sicherheitsscans in CI-Pipelines](#implementierung-von-sicherheitsscans-in-ci-pipelines)\n  - [SAST und DAST-Integration](#sast-und-dast-integration)\n- [Zusammenfassung](#zusammenfassung)\n- [Beschreibungen der Keywords](#beschreibungen-der-keywords)\nStell dir vor, du arbeitest an einem Projekt, bei dem der gesamte Code aus zwei Textdateien besteht. Dabei ist es sehr wichtig, dass die Verkettung dieser beiden Dateien die Phrase „Hello world\" enthält.\nWenn das nicht der Fall ist, wird das gesamte Development-Team in diesem Monat nicht bezahlt. Ja, so ernst ist es!\nDer oder die verantwortliche Softwareentwickler(in) hat ein kleines Skript geschrieben, das jedes Mal ausgeführt wird, wenn wir unseren Code an die Kunden senden wollen.\nDer Code ist ziemlich komplex:\n```bash\ncat file1.txt file2.txt | grep -q \"Hello world\"\n```\nDas Problem ist, dass das Team aus 10 Entwickler(inne)n besteht. Da bleiben menschliche Fehler nicht aus.\nVor einer Woche vergaß einer der Mitarbeiter(innen), das Skript auszuführen, und drei Kund(inn)en erhielten fehlerhafte Builds. Also hast du beschlossen, dieses Problem endgültig zu lösen. Glücklicherweise befindet sich der Code bereits auf GitLab, und du erinnerst dich, dass es eine [integrierte CI](/de-de/solutions/continuous-integration/) gibt. Zudem hast du auf einer Konferenz gehört, dass viele Entwickler(innen) eine CI verwenden, um Tests durchzuführen...\n> **12x kürzere Bereitstellungszeit: Dank GitLabs vollständiger Integration lebt Hilti Effizienz.** GitLab bringt vollständige Transparenz, eine umfassende Codeverwaltung und umfangreiche Sicherheitsscans mit, um Hilti neue Softwarefähigkeiten zu ermöglichen. Erfahre, wie Hilti seine Softwareentwicklung revolutioniert hat. **[Erfolgsstory lesen](https://about.gitlab.com/de-de/customers/hilti/)**\n## Der erste Test in CI\nNach ein paar Minuten Suche und Lesen der Dokumentation scheint es, dass wir nur diese zwei Codezeilen benötigen, die wir in einer Datei namens .gitlab-ci.yml finden:\n```yaml\ntest:\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n```\nWir übertragen die Zeilen, und siehe da– unser Build ist erfolgreich:\n![build succeeded](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_succeeded.png)\nNun ändern wir in der zweiten Datei \"World\" zu \"Africa\" und prüfen, was passiert:\n![build failed](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_failed.png)\nDer Build schlägt wie erwartet fehl!\nNun haben wir hier automatisierte Tests! GitLab CI führt unser Testskript jedes Mal aus, wenn wir neuen Code in das Quellcode-Repository in der DevOps-Umgebung übertragen.\n**Hinweis:** Im obigen Beispiel gehen wir davon aus, dass file1.txt und file2.txt auf dem Runner-Host vorhanden sind.\nUm dieses Beispiel in GitLab auszuführen, verwende den folgenden Code, der zunächst die Dateien erstellt und dann das Skript ausführt.\n```yaml\ntest:\nbefore_script:\n      - echo \"Hello \" > | tr -d \"\\n\" | > file1.txt\n      - echo \"world\" > file2.txt\nscript: cat file1.txt file2.txt | grep -q 'Hello world'\n```\nAus Gründen der Übersichtlichkeit gehen wir davon aus, dass diese Dateien auf dem Host vorhanden sind und werden sie in den folgenden Beispielen nicht erstellen.\n## Ergebnisse von Builds zum Herunterladen bereitstellen\nDie nächste Anforderung besteht darin, den Code zu paketieren, bevor wir ihn an unsere Kunden senden. Lass uns auch diesen Teil des Softwareentwicklungsprozesses automatisieren!\nAlles, was wir machen müssen, ist, einen weiteren Job für CI zu definieren. Nennen wir den Auftrag mal „Package\":\n```yaml\ntest:\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n\npackage:\n  script: cat file1.txt file2.txt | gzip > package.gz\n```\nNun haben wir zwei Tabs:\n![Two tabs - generated from two jobs](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/two_tabs.png)\nWir haben jedoch vergessen anzugeben, dass die neue Datei ein Build-Artefakt ist, damit sie heruntergeladen werden kann. Wir können dies beheben, indem wir einen Abschnitt für Artifacts hinzufügen:\n```yaml\ntest:\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n\npackage:\n  script: cat file1.txt file2.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n```\nChecking... it is there:\n![Checking the download button](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/artifacts.png)\nSo klappt's. Wir haben jedoch noch ein Problem zu lösen: Die Aufträge laufen parallel, aber wir wollen unsere Anwendung nicht paketieren, wenn unsere Tests fehlschlagen.\n## Aufträge der Reihe nach ausführen\nDer Auftrag „Paket\" soll nur ausgeführt werden, wenn die Tests erfolgreich sind. Definieren wir die Reihenfolge, indem wir stages angeben:\n```yaml\nstages:\n  - test\n  - package\n\ntest:\n  stage: test\n  script: cat file1.txt file2.txt | grep -q 'Hello world'\n\npackage:\n  stage: package\n  script: cat file1.txt file2.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n```\nDas sollte funktionieren!\nAußerdem haben wir vergessen zu erwähnen, dass die Zusammenstellung (die in unserem Fall durch Verkettung dargestellt wird) eine Weile dauert, sodass wir sie nicht zweimal ausführen wollen. Definieren wir also einen separaten Schritt dafür:\n```yaml\nstages:\n  - compile\n  - test\n  - package\n\ncompile:\n  stage: compile\n  script: cat file1.txt file2.txt > compiled.txt\n  artifacts:\n    paths:\n    - compiled.txt\n\ntest:\n  stage: test\n  script: cat compiled.txt | grep -q 'Hello world'\n\npackage:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n```\nLass uns jetzt auf unsere Artifacts an:\n![Unnecessary artifact](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/clean-artifacts.png)\nWir brauchen diese „Kompilierungsdatei\" nicht zum Herunterladen. Deshalb lassen wir unsere temporären Artefakte ablaufen, indem wir expire_in auf „20 Minuten\" setzen:\n```yaml\ncompile:\n  stage: compile\n  script: cat file1.txt file2.txt > compiled.txt\n  artifacts:\n    paths:\n    - compiled.txt\n    expire_in: 20 minutes\n```\nJetzt sieht unsere Konfiguration ziemlich beeindruckend aus:\n- Wir haben drei aufeinanderfolgende Phasen zum Kompilieren, Testen und Paketieren unserer Anwendung.\n- Wir übergeben die kompilierte Anwendung an die nächsten Stufen, damit die Kompilierung nicht zweimal ausgeführt werden muss (und somit schneller läuft).\n- Wir speichern eine paketierte Version unserer Anwendung in Build-Artefakten für die weitere Verwendung.\n## Welches Docker Image muss verwendet werden?\nEs scheint, dass unsere Builds immer noch langsam sind. Werfen wir einen Blick auf die Protokolle.\n![ruby3.1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/ruby-31.png)\nWas ist Ruby 3.1?\nGitLab.com verwendet Docker-Images, um unsere [Builds](/blog/shared-runners/) auszuführen, und [standardmäßig](https://docs.gitlab.com/user/gitlab_com/#shared-runners) wird das [`ruby:3.1`](https://hub.docker.com/_/ruby/)-Image verwendet. Dieses Image enthält natürlich viele Pakete, die wir nicht brauchen. Nach einer Minute des Googlens finden wir heraus, dass es ein Image namens [`alpine`](https://hub.docker.com/_/alpine/) gibt, das ein fast leeres Linux-Image ist.\nWir geben also explizit an, dass wir dieses Image verwenden wollen, indem wir image: alpine` to `.gitlab-ci.yml`.\nWir haben so drei Minuten gespart:\n![Build speed improved](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/speed.png)\nEs sieht so aus, als gäbe es viele öffentliche Images:\n- [mysql](https://hub.docker.com/_/mysql/)\n- [Python](https://hub.docker.com/_/python/)\n- [Java](https://hub.docker.com/_/java/)\n- [php](https://hub.docker.com/_/php/)\nWir können also einfach eines für unseren Technologie-Stack nehmen. Es ist sinnvoll, ein Image anzugeben, das keine zusätzliche Software enthält, da dies die Downloadzeit verringert.\n## Umgang mit komplexen Szenarien\nNehmen wir nun aber an, wir haben neue Kund(inn)en, die möchten, dass wir unsere Anwendung in ein .iso-Image statt in ein .gz-Image packen. ISO-Images können mit dem Befehl [mkisofs](http://www.w3big.com/linux/linux-comm-mkisofs.html) erstellt werden. Da CI die ganze Arbeit erledigt, können wir einfach einen weiteren Job hinzufügen. Darauf basierend sollte unsere Konfiguration so aussehen:\n```yaml\nimage: alpine\n\nstages:\n  - compile\n  - test\n  - package\n\n# ... \"compile\" and \"test\" jobs are skipped here for the sake of compactness\n\npack-gz:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  artifacts:\n    paths:\n    - packaged.gz\n\npack-iso:\n  stage: package\n  script:\n  - mkisofs -o ./packaged.iso ./compiled.txt\n  artifacts:\n    paths:\n    - packaged.iso\n```\nBeachte, dass die Auftragsnamen nicht unbedingt gleich sein sollten. Wären sie identisch, wäre es nicht möglich, die Aufträge innerhalb derselben Phase des Softwareentwicklungsprozesses parallel laufen zu lassen. Sollte es daher doch mal vorkommen, kannst du das getrost als Zufall betrachten.\nWie dem auch sei, zurück zu unserem Job, da läuft es nicht recht rund – der Build schlägt fehl:\n![Failed build because of missing mkisofs](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/mkisofs.png)\n`mkisofs` ist nicht im `alpine` Image mit dabei, also müssenw ir es erstmal installieren.\n## Umgang mit fehlender Software/Paketen\nLaut der [Alpine Linux website](https://pkgs.alpinelinux.org/contents?file=mkisofs&path=&name=&branch=edge&repo=&arch=) ist mkisofs Teil der Pakete xorriso und cdrkit. Dies sind die Befehle, die wir ausführen müssen, um ein Paket zu installieren:\n```bash\necho \"ipv6\" >> /etc/modules  # enable networking\napk update                   # update packages list\napk add xorriso              # install package\n```\nFür CI sind dies die gleichen Befehle wie für alle anderen. Die vollständige Liste der Befehle, die wir dem Skriptabschnitt übergeben müssen, sollte wie folgt aussehen:\n```yml\nscript:\n- echo \"ipv6\" >> /etc/modules\n- apk update\n- apk add xorriso\n- mkisofs -o ./packaged.iso ./compiled.txt\n```\nUm es jedoch semantisch korrekt zu machen, sollten wir die Befehle, die sich auf die Paketinstallation beziehen, in before_script unterbringen. Beachte, dass, wenn du before_script auf der obersten Ebene einer Konfiguration verwendest, die Befehle vor allen Aufträgen ausgeführt werden. In unserem Fall wollen wir nur, dass sie vor einem bestimmten Auftrag ausgeführt werden.\n## Directed Acyclic Graphs: Schnellere und flexiblere Pipelines\nWir haben die Stufen so definiert, dass die Paketaufgaben nur ausgeführt werden, wenn die Tests bestanden wurden. Was aber, wenn wir die Phasenabfolge ein wenig aufbrechen und einige Aufträge früher ausführen wollen, auch wenn sie in einer späteren Phase definiert sind? In einigen Fällen kann die herkömmliche Phasenabfolge die Gesamtausführungszeit der Pipeline verlangsamen.\nStell dir vor, dass unsere Testphase einige umfangreichere Tests enthält, deren Ausführung viel Zeit in Anspruch nimmt und diese Tests nicht unbedingt mit den Paketaufgaben zusammenhängen. In diesem Fall wäre es effizienter, wenn die Paketaufgaben nicht auf den Abschluss dieser Tests warten müssten, bevor sie beginnen können. An dieser Stelle kommen Directed Acyclic Graphs (DAG) ins Spiel: Um die Phasenreihenfolge für bestimmte Aufträge zu unterbrechen, kannst du Abhängigkeiten von Aufgaben definieren, die die reguläre Phasenreihenfolge übergehen.\nGitLab verfügt über ein spezielles Keyword „needs\", das Abhängigkeiten zwischen Aufträgen schafft und es ermöglicht, Aufträge früher auszuführen, sobald ihre abhängigen Aufträge abgeschlossen sind.\nIm folgenden Beispiel werden die Paketaufgaben ausgeführt, sobald der Testjob abgeschlossen ist. Wenn also in Zukunft jemand weitere Tests in der Testphase hinzufügt, beginnen die Paketjobs zu laufen, bevor die neuen Testjobs abgeschlossen sind\n```yaml\npack-gz:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.gz\n\npack-iso:\n  stage: package\n  before_script:\n  - echo \"ipv6\" >> /etc/modules\n  - apk update\n  - apk add xorriso\n  script:\n  - mkisofs -o ./packaged.iso ./compiled.txt\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.iso\n```\nUnsere finale Version von: `.gitlab-ci.yml`:\n```yaml\nimage: alpine\n\nstages:\n  - compile\n  - test\n  - package\n\ncompile:\n  stage: compile\n  before_script:\n      - echo \"Hello  \" | tr -d \"\\n\" > file1.txt\n      - echo \"world\" > file2.txt\n  script: cat file1.txt file2.txt > compiled.txt\n  artifacts:\n    paths:\n    - compiled.txt\n    expire_in: 20 minutes\n\ntest:\n  stage: test\n  script: cat compiled.txt | grep -q 'Hello world'\n\npack-gz:\n  stage: package\n  script: cat compiled.txt | gzip > packaged.gz\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.gz\n\npack-iso:\n  stage: package\n  before_script:\n  - echo \"ipv6\" >> /etc/modules\n  - apk update\n  - apk add xorriso\n  script:\n  - mkisofs -o ./packaged.iso ./compiled.txt\n  needs: [\"test\"]\n  artifacts:\n    paths:\n    - packaged.iso\n```\nWir haben gerade eine Pipeline erstellt! Wir haben drei sequenzielle Stufen, die Aufträge `pack-gz` und `pack-iso` innerhalb der package-Stufe laufen parallel:\n![Pipelines illustration](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/pipeline.png)\n## Wie wertest du deine Pipeline auf?\nSo kannst du deine Pipeline aufwerten.\n### Automatisierte Tests in CI-Pipelines einbinden\nEine wichtige Regel der DevOps-Strategie für die Softwareentwicklung besteht darin, wirklich großartige Anwendungen mit erstaunlicher Benutzererfahrung zu entwickeln. Fügen wir also einige Tests in unsere CI-Pipeline ein, um Fehler frühzeitig im gesamten Prozess zu erkennen. Auf diese Weise können wir Probleme beheben, bevor sie zu groß werden und bevor wir an einem neuen Projekt weiterarbeiten.\nGitLab macht uns das Leben leichter, indem es fertige Vorlagen für verschiedene [Tests](https://docs.gitlab.com/ci/testing/) anbietet. Alles, was wir tun müssen, ist, diese Vorlagen in unsere CI-Konfiguration aufzunehmen.\nIn diesem Beispiel schließen wir auch [Accessibility-Tests](https://docs.gitlab.com/ci/testing/accessibility_testing/) ein:\n```yaml\nstages:\n  - accessibility\n\nvariables:\n  a11y_urls: \"https://about.gitlab.com https://www.example.com\"\n\ninclude:\n  - template: \"Verify/Accessibility.gitlab-ci.yml\"\n```\nPasse die Variable a11y_urls an, um die URLs der Webseiten aufzulisten, die mit [Pa11y](https://pa11y.org/) und der [Codequalität](https://docs.gitlab.com/ci/testing/code_quality/) getestet werden sollen.\n```yaml\n   include:\n   - template: Jobs/Code-Quality.gitlab-ci.yml\n```\nMit GitLab kannst du den Testbericht direkt im Widget-Bereich der Zusammenführungsanforderung sehen. Wenn du die Codeüberprüfung, den Pipelinestatus und die Testergebnisse an einem Ort hast, wird alles reibungsloser und effizienter.\n![Accessibility report](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_10.56.41.png)\n\u003Ccenter>\u003Ci>Widget für die Zusammenführung von Accessibility-Anfragen\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n![Code quality widget in MR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.00.25.png)\n\u003Ccenter>\u003Ci>Widget für Zusammenführungsanfragen in Codequalität\u003C/i>\u003C/center>\n### Matrix-Builds\nIn einigen Fällen müssen wir unsere Anwendung in verschiedenen Konfigurationen, Betriebssystemversionen, Programmiersprachenversionen usw. testen. In diesen Fällen verwenden wir den [parallel:matrix](https://docs.gitlab.com/ci/yaml/#parallelmatrix)-Build, um unsere Anwendung in verschiedenen Kombinationen parallel mit einer Job-Konfiguration zu testen. In diesem Artikel werden wir unseren Code mit verschiedenen Python-Versionen unter Verwendung des Schlüsselworts matrix testen.\n```yaml\npython-req:\n  image: python:$VERSION\n  stage: lint\n  script:\n    - pip install -r requirements_dev.txt\n    - chmod +x ./build_cpp.sh\n    - ./build_cpp.sh\n  parallel:\n    matrix:\n      - VERSION: ['3.8', '3.9', '3.10', '3.11']   # https://hub.docker.com/_/python\n```\nWährend der Pipeline-Ausführung wird dieser Auftrag viermal parallel ausgeführt, wobei jedes Mal ein anderes Python-Image verwendet wird (siehe unten):\n![Matrix job running](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.12.48.png)\n### Unit-Tests\n#### Was sind Unit-Tests?\nUnit-Tests sind kleine, gezielte Tests, die einzelne Komponenten oder Funktionen von Software prüfen, um sicherzustellen, dass sie wie erwartet funktionieren. Sie sind wichtig, um Fehler in einem frühen Stadium des Softwareentwicklungsprozesses aufzuspüren und zu überprüfen, ob jeder Teil des Codes für sich genommen korrekt funktioniert.\nBeispiel: Stell dir vor, du entwickelst eine Taschenrechner-App. Ein Unit-Test für die Additionsfunktion würde prüfen, ob 2 + 2 gleich 4 ist. Wenn dieser Test erfolgreich ist, bestätigt er, dass die Additionsfunktion korrekt funktioniert.\n#### Best Practices für Unit-Tests\nWenn die Tests fehlschlagen, schlägt die Pipeline fehl und die Benutzer werden benachrichtigt. Entwickler(innen) müssen die Auftragsprotokolle, die in der Regel Tausende von Zeilen enthalten, überprüfen und feststellen, wo die Tests fehlgeschlagen sind, um sie zu korrigieren. Diese Prüfung ist zeitaufwendig und ineffizient.\nDu kannst deinen Auftrag so konfigurieren, dass er [Unit-Test-Berichte](https://docs.gitlab.com/ci/testing/unit_test_reports/) verwendet. GitLab zeigt die Berichte in der Zusammenführungsanforderung und auf der Detailseite der Pipeline an, sodass du den Fehler einfacher und schneller identifizieren kannst, ohne das gesamte Protokoll überprüfen zu müssen.\n##### JUnit Test-Report\nDies ist ein beispielhafter JUnit Test-Report:\n![pipelines JUnit test report v13 10](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674097/Blog/Content%20Images/pipelines_junit_test_report_v13_10.png){: .shadow.center}\n### Strategien für Integrations- und End-to-End-Tests\nZusätzlich zu unserer regulären Entwicklungsroutine ist es sehr wichtig, eine spezielle Pipeline nur für Integrations- und End-to-End-Tests einzurichten. Damit wird überprüft, ob alle verschiedenen Teile unseres Codes reibungslos zusammenarbeiten, einschließlich der [Microservices](https://about.gitlab.com/topics/microservices/), der UI-Tests und aller anderen Komponenten.\nWir führen diese Tests jede [Nacht](https://docs.gitlab.com/ci/pipelines/schedules/) durch. Wir können es so einrichten, dass die Ergebnisse automatisch an einen speziellen [Slack](https://docs.gitlab.com/user/project/integrations/gitlab_slack_application/#notification-events)-Kanal gesendet werden. Auf diese Weise können die Entwickler(innen), wenn sie am nächsten Tag kommen, schnell alle Probleme erkennen. Es geht darum, Probleme frühzeitig zu erkennen und zu beheben!\n### Testumgebung\nFür einige der Tests benötigen wir möglicherweise eine Testumgebung, um unsere Anwendungen ordnungsgemäß zu testen. Mit GitLab CI/CD können wir die Bereitstellung von Testumgebungen automatisieren und so eine Menge Zeit sparen. Da es in diesem Blog hauptsächlich um CI geht, werde ich nicht näher darauf eingehen, aber du kannst diesen Abschnitt in der [GitLab-Dokumentation](https://docs.gitlab.com/topics/release_your_application/) nachlesen.\n## Implementierung von Sicherheitsscans in CI-Pipelines\nFolgend siehst du die Möglichkeiten zur Implementierung von Sicherheitsscans in CI-Pipelines.\n### SAST und DAST-Integration\nWir legen großen Wert darauf, dass unser Code sicher ist. Wenn unsere letzten Änderungen Schwachstellen aufweisen, wollen wir das so schnell wie möglich wissen. Sicherheitsscans sind hier eine sinnvolle Lösung und wir empfehlen dir, sie auch in deine Pipeline aufzunehmen. Sie überprüfen den Code bei jeder Übertragung und warnen dich vor möglichen Risiken. Wir haben eine Produktübersicht zusammengestellt, die dich durch das Hinzufügen von Scans, einschließlich statischer Anwendungssicherheitstests ([SAST](https://docs.gitlab.com/user/application_security/sast/)) und dynamischer Anwendungssicherheitstests ([DAST](https://docs.gitlab.com/user/application_security/dast/)), zu deiner CI-Pipeline führt.\n__Klicke__ auf das Bild unten, um zur Übersicht zu gelangen.\n[![Scans product tour](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-14_at_13.44.42.png)](https://gitlab.navattic.com/gitlab-scans)\nAußerdem können wir mithilfe von KI noch tiefer in Schwachstellen eindringen und Vorschläge zu ihrer Behebung erhalten.\nWeitere Informationen findest du in dieser Demo.\n[![product tour explain vulnerability ](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-14_at_13.50.24.png)](https://tech-marketing.gitlab.io/static-demos/pt-explain-vulnerability.html)\n## Zusammenfassung\nEs gibt noch viel mehr zu erläutern, aber lass uns hier erst einmal aufhören.\nAlle Beispiele sind bewusst einfach gehalten, um das Konzept GitLab CI vorzustellen, ohne die Dinge zu verkomplizieren. Fassen wir zusammen, was wir gelernt haben:\n1. Um Arbeit an GitLab CI zu delegieren, solltest du einen oder mehrere [Jobs](https://docs.gitlab.com/ci/jobs/) in.gitlab-ci.yml. definieren.\n2. Jobs sollten Namen haben – also denk dir was Gutes aus! Jeder Auftrag enthält eine Reihe von Regeln und Anweisungen für GitLab CI, die durch spezielle Schlüsselwörter definiert sind.\n3. Aufträge können nacheinander, parallel oder ungeordnet über [DAG](https://docs.gitlab.com/ci/directed_acyclic_graph/) ausgeführt werden.\n4. Du kannst Dateien zwischen Aufträgen weitergeben und sie in Build-Artefakten speichern, sodass sie über die Schnittstelle heruntergeladen werden können.\n5. Du kannst Dateien zwischen Aufträgen weitergeben und sie in Build-Artefakten speichern, sodass sie über die Schnittstelle heruntergeladen werden können.\nNachstehend findest du eine genauere Beschreibung der von uns verwendeten Begriffe und Schlüsselwörter sowie Links zu den entsprechenden Dokumenten.\n### Beschreibungen der Keywords\n{: #keywords}\n| Keyword/term       | Beschreibung | |---------------|--------------------|\n| [.gitlab-ci.yml](https://docs.gitlab.com/ci/yaml/) | Datei mit allen Definitionen dazu, wie dein Projekt aufgebaut sein sollte |\n| [script](https://docs.gitlab.com/ci/yaml/#script)        | Definiert ein Shell-Script, das ausgeführt werden soll |\n| [before_script](https://docs.gitlab.com/ci/yaml/#before_script) | Wird verwendet, um den Befehl zu definieren, der vor (allen) Aufträgen ausgeführt werden soll |\n| [image](https://docs.gitlab.com/ci/docker/using_docker_images/#what-is-image) | Definiert das zu verwendende Docker-Image |\n| [stages](https://docs.gitlab.com/ci/yaml/#stages)         | Legt eine Pipelinestufe fest (Standard: test) |\n| [artifacts](https://docs.gitlab.com/ci/yaml/#artifacts)     | Definiert eine Liste von Build-Artifacts |\n| [artifacts:expire_in](https://docs.gitlab.com/ci/yaml/#artifactsexpire_in) | Wird verwendet, um hochgeladene Artifacts nach der angegebenen Zeit zu löschen |\n| [needs](https://docs.gitlab.com/ci/yaml/#needs) | Dient zur Definition von Abhängigkeiten zwischen Aufträgen und ermöglicht die Ausführung von Aufträgen außerhalb der Reihenfolge |\n| [pipelines](https://about.gitlab.com/topics/ci-cd/cicd-pipeline/) | Eine Pipeline ist eine Gruppe von Builds, die stufenweise (Batches) ausgeführt werden |\n","engineering",{"slug":13,"featured":14,"template":15},"basics-of-gitlab-ci-updated",false,"BlogPost",{"title":5,"description":17,"authors":18,"heroImage":19,"date":20,"body":10,"category":11,"tags":21,"updatedDate":24},"Neu in der Continuous Integration? Erfahre, wie du deine erste CI-Pipeline mit GitLab erstellst.",[9],"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662061/Blog/Hero%20Images/cicdcover.png","2020-12-10",[22,23],"CI","tutorial","2025-05-16","yml",null,{},true,"/de-de/blog/basics-of-gitlab-ci-updated","seo:\n  title: >-\n    Grundlagen der GitLab-CI-Pipeline: Aufgaben sequenziell parallel oder ohne\n    Reihenfolge ausführen\n  description: >-\n    Neu in der Continuous Integration? Erfahre, wie du deine erste CI-Pipeline\n    mit GitLab erstellst.\n  ogTitle: >-\n    Grundlagen der GitLab-CI-Pipeline: Aufgaben sequenziell parallel oder ohne\n    Reihenfolge ausführen\n  ogDescription: >-\n    Neu in der Continuous Integration? Erfahre, wie du deine erste CI-Pipeline\n    mit GitLab erstellst.\n  noIndex: false\n  ogImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662061/Blog/Hero%20Images/cicdcover.png\n  ogUrl: https://about.gitlab.com/blog/basics-of-gitlab-ci-updated\n  ogSiteName: https://about.gitlab.com\n  ogType: article\n  canonicalUrls: https://about.gitlab.com/blog/basics-of-gitlab-ci-updated\ncontent:\n  title: >-\n    Grundlagen der GitLab-CI-Pipeline: Aufgaben sequenziell parallel oder ohne\n    Reihenfolge ausführen\n  description: >-\n    Neu in der Continuous Integration? Erfahre, wie du deine erste CI-Pipeline\n    mit GitLab erstellst.\n  authors:\n    - Itzik Gan Baruch\n  heroImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662061/Blog/Hero%20Images/cicdcover.png\n  date: '2020-12-10'\n  body: >\n    Nehmen wir an, dass du nichts über [kontinuierliche Integration\n    (CI)](/topics/ci-cd/) and [why it's\n    needed](/blog/how-to-keep-up-with-ci-cd-best-practices/) weißt und darüber,\n    warum sie im Lebenszyklus der Softwareentwicklung benötigt wird.\n\n    ## Inhaltsverzeichnis\n\n    - [Der erste Test in CI](#der-erste-test-in-ci)\n\n    - [Ergebnisse von Builds zum Herunterladen\n    bereitstellen](#ergebnisse-von-builds-zum-herunterladen-bereitstellen)\n\n    - [Aufträge der Reihe nach ausführen](#aufträge-der-reihe-nach-ausführen)\n\n    - [Welches Docker Image muss verwendet\n    werden?](#welches-docker-image-muss-verwendet-werden%3F)\n\n    - [Umgang mit komplexen Szenarien](#umgang-mit-komplexen-szenarien)\n\n    - [Umgang mit fehlender\n    Software/Paketen](#umgang-mit-fehlender-softwarepaketen)\n\n    - [Directed Acyclic Graphs: Schnellere und flexiblere\n    Pipelines](#directed-acyclic-graphs-schnellere-und-flexiblere-pipelines)\n\n    - [Wie wertest du deine Pipeline\n    auf?](#wie-wertest-du-deine-pipeline-auf%3F)\n      - [Automatisierte Tests in CI-Pipelines einbinden](#automatisierte-tests-in-ci-pipelines-einbinden)\n      - [Matrix-Builds](#matrix-builds)\n    - [Unit-Tests](#unit-tests)\n      - [Was sind Unit-Tests?](#was-sind-unit-tests%3F)\n      - [Best Practices für Unit-Tests](#best-practices-für-unit-tests)\n        - [JUnit Test-Report](#junit-test-report)\n    - [Strategien für Integrations- und\n    End-to-End-Tests](#strategien-für-integrations--und-end-to-end-tests)\n\n    - [Testumgebung](#testumgebung)\n\n    - [Implementierung von Sicherheitsscans in\n    CI-Pipelines](#implementierung-von-sicherheitsscans-in-ci-pipelines)\n      - [SAST und DAST-Integration](#sast-und-dast-integration)\n    - [Zusammenfassung](#zusammenfassung)\n\n    - [Beschreibungen der Keywords](#beschreibungen-der-keywords)\n\n    Stell dir vor, du arbeitest an einem Projekt, bei dem der gesamte Code aus\n    zwei Textdateien besteht. Dabei ist es sehr wichtig, dass die Verkettung\n    dieser beiden Dateien die Phrase „Hello world\" enthält.\n\n    Wenn das nicht der Fall ist, wird das gesamte Development-Team in diesem\n    Monat nicht bezahlt. Ja, so ernst ist es!\n\n    Der oder die verantwortliche Softwareentwickler(in) hat ein kleines Skript\n    geschrieben, das jedes Mal ausgeführt wird, wenn wir unseren Code an die\n    Kunden senden wollen.\n\n    Der Code ist ziemlich komplex:\n\n    ```bash\n\n    cat file1.txt file2.txt | grep -q \"Hello world\"\n\n    ```\n\n    Das Problem ist, dass das Team aus 10 Entwickler(inne)n besteht. Da bleiben\n    menschliche Fehler nicht aus.\n\n    Vor einer Woche vergaß einer der Mitarbeiter(innen), das Skript auszuführen,\n    und drei Kund(inn)en erhielten fehlerhafte Builds. Also hast du beschlossen,\n    dieses Problem endgültig zu lösen. Glücklicherweise befindet sich der Code\n    bereits auf GitLab, und du erinnerst dich, dass es eine [integrierte\n    CI](/de-de/solutions/continuous-integration/) gibt. Zudem hast du auf einer\n    Konferenz gehört, dass viele Entwickler(innen) eine CI verwenden, um Tests\n    durchzuführen...\n\n    > **12x kürzere Bereitstellungszeit: Dank GitLabs vollständiger Integration\n    lebt Hilti Effizienz.** GitLab bringt vollständige Transparenz, eine\n    umfassende Codeverwaltung und umfangreiche Sicherheitsscans mit, um Hilti\n    neue Softwarefähigkeiten zu ermöglichen. Erfahre, wie Hilti seine\n    Softwareentwicklung revolutioniert hat. **[Erfolgsstory\n    lesen](https://about.gitlab.com/de-de/customers/hilti/)**\n\n    ## Der erste Test in CI\n\n    Nach ein paar Minuten Suche und Lesen der Dokumentation scheint es, dass wir\n    nur diese zwei Codezeilen benötigen, die wir in einer Datei namens\n    .gitlab-ci.yml finden:\n\n    ```yaml\n\n    test:\n      script: cat file1.txt file2.txt | grep -q 'Hello world'\n    ```\n\n    Wir übertragen die Zeilen, und siehe da– unser Build ist erfolgreich:\n\n    ![build\n    succeeded](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_succeeded.png)\n\n    Nun ändern wir in der zweiten Datei \"World\" zu \"Africa\" und prüfen, was\n    passiert:\n\n    ![build\n    failed](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/build_failed.png)\n\n    Der Build schlägt wie erwartet fehl!\n\n    Nun haben wir hier automatisierte Tests! GitLab CI führt unser Testskript\n    jedes Mal aus, wenn wir neuen Code in das Quellcode-Repository in der\n    DevOps-Umgebung übertragen.\n\n    **Hinweis:** Im obigen Beispiel gehen wir davon aus, dass file1.txt und\n    file2.txt auf dem Runner-Host vorhanden sind.\n\n    Um dieses Beispiel in GitLab auszuführen, verwende den folgenden Code, der\n    zunächst die Dateien erstellt und dann das Skript ausführt.\n\n    ```yaml\n\n    test:\n\n    before_script:\n          - echo \"Hello \" > | tr -d \"\\n\" | > file1.txt\n          - echo \"world\" > file2.txt\n    script: cat file1.txt file2.txt | grep -q 'Hello world'\n\n    ```\n\n    Aus Gründen der Übersichtlichkeit gehen wir davon aus, dass diese Dateien\n    auf dem Host vorhanden sind und werden sie in den folgenden Beispielen nicht\n    erstellen.\n\n    ## Ergebnisse von Builds zum Herunterladen bereitstellen\n\n    Die nächste Anforderung besteht darin, den Code zu paketieren, bevor wir ihn\n    an unsere Kunden senden. Lass uns auch diesen Teil des\n    Softwareentwicklungsprozesses automatisieren!\n\n    Alles, was wir machen müssen, ist, einen weiteren Job für CI zu definieren.\n    Nennen wir den Auftrag mal „Package\":\n\n    ```yaml\n\n    test:\n      script: cat file1.txt file2.txt | grep -q 'Hello world'\n\n    package:\n      script: cat file1.txt file2.txt | gzip > package.gz\n    ```\n\n    Nun haben wir zwei Tabs:\n\n    ![Two tabs - generated from two\n    jobs](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/two_tabs.png)\n\n    Wir haben jedoch vergessen anzugeben, dass die neue Datei ein Build-Artefakt\n    ist, damit sie heruntergeladen werden kann. Wir können dies beheben, indem\n    wir einen Abschnitt für Artifacts hinzufügen:\n\n    ```yaml\n\n    test:\n      script: cat file1.txt file2.txt | grep -q 'Hello world'\n\n    package:\n      script: cat file1.txt file2.txt | gzip > packaged.gz\n      artifacts:\n        paths:\n        - packaged.gz\n    ```\n\n    Checking... it is there:\n\n    ![Checking the download\n    button](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/artifacts.png)\n\n    So klappt's. Wir haben jedoch noch ein Problem zu lösen: Die Aufträge laufen\n    parallel, aber wir wollen unsere Anwendung nicht paketieren, wenn unsere\n    Tests fehlschlagen.\n\n    ## Aufträge der Reihe nach ausführen\n\n    Der Auftrag „Paket\" soll nur ausgeführt werden, wenn die Tests erfolgreich\n    sind. Definieren wir die Reihenfolge, indem wir stages angeben:\n\n    ```yaml\n\n    stages:\n      - test\n      - package\n\n    test:\n      stage: test\n      script: cat file1.txt file2.txt | grep -q 'Hello world'\n\n    package:\n      stage: package\n      script: cat file1.txt file2.txt | gzip > packaged.gz\n      artifacts:\n        paths:\n        - packaged.gz\n    ```\n\n    Das sollte funktionieren!\n\n    Außerdem haben wir vergessen zu erwähnen, dass die Zusammenstellung (die in\n    unserem Fall durch Verkettung dargestellt wird) eine Weile dauert, sodass\n    wir sie nicht zweimal ausführen wollen. Definieren wir also einen separaten\n    Schritt dafür:\n\n    ```yaml\n\n    stages:\n      - compile\n      - test\n      - package\n\n    compile:\n      stage: compile\n      script: cat file1.txt file2.txt > compiled.txt\n      artifacts:\n        paths:\n        - compiled.txt\n\n    test:\n      stage: test\n      script: cat compiled.txt | grep -q 'Hello world'\n\n    package:\n      stage: package\n      script: cat compiled.txt | gzip > packaged.gz\n      artifacts:\n        paths:\n        - packaged.gz\n    ```\n\n    Lass uns jetzt auf unsere Artifacts an:\n\n    ![Unnecessary\n    artifact](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/clean-artifacts.png)\n\n    Wir brauchen diese „Kompilierungsdatei\" nicht zum Herunterladen. Deshalb\n    lassen wir unsere temporären Artefakte ablaufen, indem wir expire_in auf „20\n    Minuten\" setzen:\n\n    ```yaml\n\n    compile:\n      stage: compile\n      script: cat file1.txt file2.txt > compiled.txt\n      artifacts:\n        paths:\n        - compiled.txt\n        expire_in: 20 minutes\n    ```\n\n    Jetzt sieht unsere Konfiguration ziemlich beeindruckend aus:\n\n    - Wir haben drei aufeinanderfolgende Phasen zum Kompilieren, Testen und\n    Paketieren unserer Anwendung.\n\n    - Wir übergeben die kompilierte Anwendung an die nächsten Stufen, damit die\n    Kompilierung nicht zweimal ausgeführt werden muss (und somit schneller\n    läuft).\n\n    - Wir speichern eine paketierte Version unserer Anwendung in\n    Build-Artefakten für die weitere Verwendung.\n\n    ## Welches Docker Image muss verwendet werden?\n\n    Es scheint, dass unsere Builds immer noch langsam sind. Werfen wir einen\n    Blick auf die Protokolle.\n\n    ![ruby3.1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/ruby-31.png)\n\n    Was ist Ruby 3.1?\n\n    GitLab.com verwendet Docker-Images, um unsere\n    [Builds](/blog/shared-runners/) auszuführen, und\n    [standardmäßig](https://docs.gitlab.com/user/gitlab_com/#shared-runners)\n    wird das [`ruby:3.1`](https://hub.docker.com/_/ruby/)-Image verwendet.\n    Dieses Image enthält natürlich viele Pakete, die wir nicht brauchen. Nach\n    einer Minute des Googlens finden wir heraus, dass es ein Image namens\n    [`alpine`](https://hub.docker.com/_/alpine/) gibt, das ein fast leeres\n    Linux-Image ist.\n\n    Wir geben also explizit an, dass wir dieses Image verwenden wollen, indem\n    wir image: alpine` to `.gitlab-ci.yml`.\n\n    Wir haben so drei Minuten gespart:\n\n    ![Build speed\n    improved](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/speed.png)\n\n    Es sieht so aus, als gäbe es viele öffentliche Images:\n\n    - [mysql](https://hub.docker.com/_/mysql/)\n\n    - [Python](https://hub.docker.com/_/python/)\n\n    - [Java](https://hub.docker.com/_/java/)\n\n    - [php](https://hub.docker.com/_/php/)\n\n    Wir können also einfach eines für unseren Technologie-Stack nehmen. Es ist\n    sinnvoll, ein Image anzugeben, das keine zusätzliche Software enthält, da\n    dies die Downloadzeit verringert.\n\n    ## Umgang mit komplexen Szenarien\n\n    Nehmen wir nun aber an, wir haben neue Kund(inn)en, die möchten, dass wir\n    unsere Anwendung in ein .iso-Image statt in ein .gz-Image packen. ISO-Images\n    können mit dem Befehl\n    [mkisofs](http://www.w3big.com/linux/linux-comm-mkisofs.html) erstellt\n    werden. Da CI die ganze Arbeit erledigt, können wir einfach einen weiteren\n    Job hinzufügen. Darauf basierend sollte unsere Konfiguration so aussehen:\n\n    ```yaml\n\n    image: alpine\n\n\n    stages:\n      - compile\n      - test\n      - package\n\n    # ... \"compile\" and \"test\" jobs are skipped here for the sake of compactness\n\n\n    pack-gz:\n      stage: package\n      script: cat compiled.txt | gzip > packaged.gz\n      artifacts:\n        paths:\n        - packaged.gz\n\n    pack-iso:\n      stage: package\n      script:\n      - mkisofs -o ./packaged.iso ./compiled.txt\n      artifacts:\n        paths:\n        - packaged.iso\n    ```\n\n    Beachte, dass die Auftragsnamen nicht unbedingt gleich sein sollten. Wären\n    sie identisch, wäre es nicht möglich, die Aufträge innerhalb derselben Phase\n    des Softwareentwicklungsprozesses parallel laufen zu lassen. Sollte es daher\n    doch mal vorkommen, kannst du das getrost als Zufall betrachten.\n\n    Wie dem auch sei, zurück zu unserem Job, da läuft es nicht recht rund – der\n    Build schlägt fehl:\n\n    ![Failed build because of missing\n    mkisofs](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/mkisofs.png)\n\n    `mkisofs` ist nicht im `alpine` Image mit dabei, also müssenw ir es erstmal\n    installieren.\n\n    ## Umgang mit fehlender Software/Paketen\n\n    Laut der [Alpine Linux\n    website](https://pkgs.alpinelinux.org/contents?file=mkisofs&path=&name=&branch=edge&repo=&arch=)\n    ist mkisofs Teil der Pakete xorriso und cdrkit. Dies sind die Befehle, die\n    wir ausführen müssen, um ein Paket zu installieren:\n\n    ```bash\n\n    echo \"ipv6\" >> /etc/modules  # enable networking\n\n    apk update                   # update packages list\n\n    apk add xorriso              # install package\n\n    ```\n\n    Für CI sind dies die gleichen Befehle wie für alle anderen. Die vollständige\n    Liste der Befehle, die wir dem Skriptabschnitt übergeben müssen, sollte wie\n    folgt aussehen:\n\n    ```yml\n\n    script:\n\n    - echo \"ipv6\" >> /etc/modules\n\n    - apk update\n\n    - apk add xorriso\n\n    - mkisofs -o ./packaged.iso ./compiled.txt\n\n    ```\n\n    Um es jedoch semantisch korrekt zu machen, sollten wir die Befehle, die sich\n    auf die Paketinstallation beziehen, in before_script unterbringen. Beachte,\n    dass, wenn du before_script auf der obersten Ebene einer Konfiguration\n    verwendest, die Befehle vor allen Aufträgen ausgeführt werden. In unserem\n    Fall wollen wir nur, dass sie vor einem bestimmten Auftrag ausgeführt\n    werden.\n\n    ## Directed Acyclic Graphs: Schnellere und flexiblere Pipelines\n\n    Wir haben die Stufen so definiert, dass die Paketaufgaben nur ausgeführt\n    werden, wenn die Tests bestanden wurden. Was aber, wenn wir die\n    Phasenabfolge ein wenig aufbrechen und einige Aufträge früher ausführen\n    wollen, auch wenn sie in einer späteren Phase definiert sind? In einigen\n    Fällen kann die herkömmliche Phasenabfolge die Gesamtausführungszeit der\n    Pipeline verlangsamen.\n\n    Stell dir vor, dass unsere Testphase einige umfangreichere Tests enthält,\n    deren Ausführung viel Zeit in Anspruch nimmt und diese Tests nicht unbedingt\n    mit den Paketaufgaben zusammenhängen. In diesem Fall wäre es effizienter,\n    wenn die Paketaufgaben nicht auf den Abschluss dieser Tests warten müssten,\n    bevor sie beginnen können. An dieser Stelle kommen Directed Acyclic Graphs\n    (DAG) ins Spiel: Um die Phasenreihenfolge für bestimmte Aufträge zu\n    unterbrechen, kannst du Abhängigkeiten von Aufgaben definieren, die die\n    reguläre Phasenreihenfolge übergehen.\n\n    GitLab verfügt über ein spezielles Keyword „needs\", das Abhängigkeiten\n    zwischen Aufträgen schafft und es ermöglicht, Aufträge früher auszuführen,\n    sobald ihre abhängigen Aufträge abgeschlossen sind.\n\n    Im folgenden Beispiel werden die Paketaufgaben ausgeführt, sobald der\n    Testjob abgeschlossen ist. Wenn also in Zukunft jemand weitere Tests in der\n    Testphase hinzufügt, beginnen die Paketjobs zu laufen, bevor die neuen\n    Testjobs abgeschlossen sind\n\n    ```yaml\n\n    pack-gz:\n      stage: package\n      script: cat compiled.txt | gzip > packaged.gz\n      needs: [\"test\"]\n      artifacts:\n        paths:\n        - packaged.gz\n\n    pack-iso:\n      stage: package\n      before_script:\n      - echo \"ipv6\" >> /etc/modules\n      - apk update\n      - apk add xorriso\n      script:\n      - mkisofs -o ./packaged.iso ./compiled.txt\n      needs: [\"test\"]\n      artifacts:\n        paths:\n        - packaged.iso\n    ```\n\n    Unsere finale Version von: `.gitlab-ci.yml`:\n\n    ```yaml\n\n    image: alpine\n\n\n    stages:\n      - compile\n      - test\n      - package\n\n    compile:\n      stage: compile\n      before_script:\n          - echo \"Hello  \" | tr -d \"\\n\" > file1.txt\n          - echo \"world\" > file2.txt\n      script: cat file1.txt file2.txt > compiled.txt\n      artifacts:\n        paths:\n        - compiled.txt\n        expire_in: 20 minutes\n\n    test:\n      stage: test\n      script: cat compiled.txt | grep -q 'Hello world'\n\n    pack-gz:\n      stage: package\n      script: cat compiled.txt | gzip > packaged.gz\n      needs: [\"test\"]\n      artifacts:\n        paths:\n        - packaged.gz\n\n    pack-iso:\n      stage: package\n      before_script:\n      - echo \"ipv6\" >> /etc/modules\n      - apk update\n      - apk add xorriso\n      script:\n      - mkisofs -o ./packaged.iso ./compiled.txt\n      needs: [\"test\"]\n      artifacts:\n        paths:\n        - packaged.iso\n    ```\n\n    Wir haben gerade eine Pipeline erstellt! Wir haben drei sequenzielle Stufen,\n    die Aufträge `pack-gz` und `pack-iso` innerhalb der package-Stufe laufen\n    parallel:\n\n    ![Pipelines\n    illustration](https://about.gitlab.com/images/blogimages/the-basics-of-gitlab-ci/pipeline.png)\n\n    ## Wie wertest du deine Pipeline auf?\n\n    So kannst du deine Pipeline aufwerten.\n\n    ### Automatisierte Tests in CI-Pipelines einbinden\n\n    Eine wichtige Regel der DevOps-Strategie für die Softwareentwicklung besteht\n    darin, wirklich großartige Anwendungen mit erstaunlicher Benutzererfahrung\n    zu entwickeln. Fügen wir also einige Tests in unsere CI-Pipeline ein, um\n    Fehler frühzeitig im gesamten Prozess zu erkennen. Auf diese Weise können\n    wir Probleme beheben, bevor sie zu groß werden und bevor wir an einem neuen\n    Projekt weiterarbeiten.\n\n    GitLab macht uns das Leben leichter, indem es fertige Vorlagen für\n    verschiedene [Tests](https://docs.gitlab.com/ci/testing/) anbietet.\n    Alles, was wir tun müssen, ist, diese Vorlagen in unsere CI-Konfiguration\n    aufzunehmen.\n\n    In diesem Beispiel schließen wir auch\n    [Accessibility-Tests](https://docs.gitlab.com/ci/testing/accessibility_testing/)\n    ein:\n\n    ```yaml\n\n    stages:\n      - accessibility\n\n    variables:\n      a11y_urls: \"https://about.gitlab.com https://www.example.com\"\n\n    include:\n      - template: \"Verify/Accessibility.gitlab-ci.yml\"\n    ```\n\n    Passe die Variable a11y_urls an, um die URLs der Webseiten aufzulisten, die\n    mit [Pa11y](https://pa11y.org/) und der\n    [Codequalität](https://docs.gitlab.com/ci/testing/code_quality/)\n    getestet werden sollen.\n\n    ```yaml\n       include:\n       - template: Jobs/Code-Quality.gitlab-ci.yml\n    ```\n\n    Mit GitLab kannst du den Testbericht direkt im Widget-Bereich der\n    Zusammenführungsanforderung sehen. Wenn du die Codeüberprüfung, den\n    Pipelinestatus und die Testergebnisse an einem Ort hast, wird alles\n    reibungsloser und effizienter.\n\n    ![Accessibility\n    report](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_10.56.41.png)\n\n    \u003Ccenter>\u003Ci>Widget für die Zusammenführung von\n    Accessibility-Anfragen\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n    ![Code quality widget in\n    MR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.00.25.png)\n\n    \u003Ccenter>\u003Ci>Widget für Zusammenführungsanfragen in Codequalität\u003C/i>\u003C/center>\n\n    ### Matrix-Builds\n\n    In einigen Fällen müssen wir unsere Anwendung in verschiedenen\n    Konfigurationen, Betriebssystemversionen, Programmiersprachenversionen usw.\n    testen. In diesen Fällen verwenden wir den\n    [parallel:matrix](https://docs.gitlab.com/ci/yaml/#parallelmatrix)-Build,\n    um unsere Anwendung in verschiedenen Kombinationen parallel mit einer\n    Job-Konfiguration zu testen. In diesem Artikel werden wir unseren Code mit\n    verschiedenen Python-Versionen unter Verwendung des Schlüsselworts matrix\n    testen.\n\n    ```yaml\n\n    python-req:\n      image: python:$VERSION\n      stage: lint\n      script:\n        - pip install -r requirements_dev.txt\n        - chmod +x ./build_cpp.sh\n        - ./build_cpp.sh\n      parallel:\n        matrix:\n          - VERSION: ['3.8', '3.9', '3.10', '3.11']   # https://hub.docker.com/_/python\n    ```\n\n    Während der Pipeline-Ausführung wird dieser Auftrag viermal parallel\n    ausgeführt, wobei jedes Mal ein anderes Python-Image verwendet wird (siehe\n    unten):\n\n    ![Matrix job\n    running](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-02_at_11.12.48.png)\n\n    ### Unit-Tests\n\n    #### Was sind Unit-Tests?\n\n    Unit-Tests sind kleine, gezielte Tests, die einzelne Komponenten oder\n    Funktionen von Software prüfen, um sicherzustellen, dass sie wie erwartet\n    funktionieren. Sie sind wichtig, um Fehler in einem frühen Stadium des\n    Softwareentwicklungsprozesses aufzuspüren und zu überprüfen, ob jeder Teil\n    des Codes für sich genommen korrekt funktioniert.\n\n    Beispiel: Stell dir vor, du entwickelst eine Taschenrechner-App. Ein\n    Unit-Test für die Additionsfunktion würde prüfen, ob 2 + 2 gleich 4 ist.\n    Wenn dieser Test erfolgreich ist, bestätigt er, dass die Additionsfunktion\n    korrekt funktioniert.\n\n    #### Best Practices für Unit-Tests\n\n    Wenn die Tests fehlschlagen, schlägt die Pipeline fehl und die Benutzer\n    werden benachrichtigt. Entwickler(innen) müssen die Auftragsprotokolle, die\n    in der Regel Tausende von Zeilen enthalten, überprüfen und feststellen, wo\n    die Tests fehlgeschlagen sind, um sie zu korrigieren. Diese Prüfung ist\n    zeitaufwendig und ineffizient.\n\n    Du kannst deinen Auftrag so konfigurieren, dass er\n    [Unit-Test-Berichte](https://docs.gitlab.com/ci/testing/unit_test_reports/)\n    verwendet. GitLab zeigt die Berichte in der Zusammenführungsanforderung und\n    auf der Detailseite der Pipeline an, sodass du den Fehler einfacher und\n    schneller identifizieren kannst, ohne das gesamte Protokoll überprüfen zu\n    müssen.\n\n    ##### JUnit Test-Report\n\n    Dies ist ein beispielhafter JUnit Test-Report:\n\n    ![pipelines JUnit test report v13\n    10](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674097/Blog/Content%20Images/pipelines_junit_test_report_v13_10.png){:\n    .shadow.center}\n\n    ### Strategien für Integrations- und End-to-End-Tests\n\n    Zusätzlich zu unserer regulären Entwicklungsroutine ist es sehr wichtig,\n    eine spezielle Pipeline nur für Integrations- und End-to-End-Tests\n    einzurichten. Damit wird überprüft, ob alle verschiedenen Teile unseres\n    Codes reibungslos zusammenarbeiten, einschließlich der\n    [Microservices](https://about.gitlab.com/topics/microservices/), der\n    UI-Tests und aller anderen Komponenten.\n\n    Wir führen diese Tests jede\n    [Nacht](https://docs.gitlab.com/ci/pipelines/schedules/) durch. Wir\n    können es so einrichten, dass die Ergebnisse automatisch an einen speziellen\n    [Slack](https://docs.gitlab.com/user/project/integrations/gitlab_slack_application/#notification-events)-Kanal\n    gesendet werden. Auf diese Weise können die Entwickler(innen), wenn sie am\n    nächsten Tag kommen, schnell alle Probleme erkennen. Es geht darum, Probleme\n    frühzeitig zu erkennen und zu beheben!\n\n    ### Testumgebung\n\n    Für einige der Tests benötigen wir möglicherweise eine Testumgebung, um\n    unsere Anwendungen ordnungsgemäß zu testen. Mit GitLab CI/CD können wir die\n    Bereitstellung von Testumgebungen automatisieren und so eine Menge Zeit\n    sparen. Da es in diesem Blog hauptsächlich um CI geht, werde ich nicht näher\n    darauf eingehen, aber du kannst diesen Abschnitt in der\n    [GitLab-Dokumentation](https://docs.gitlab.com/topics/release_your_application/)\n    nachlesen.\n\n    ## Implementierung von Sicherheitsscans in CI-Pipelines\n\n    Folgend siehst du die Möglichkeiten zur Implementierung von Sicherheitsscans\n    in CI-Pipelines.\n\n    ### SAST und DAST-Integration\n\n    Wir legen großen Wert darauf, dass unser Code sicher ist. Wenn unsere\n    letzten Änderungen Schwachstellen aufweisen, wollen wir das so schnell wie\n    möglich wissen. Sicherheitsscans sind hier eine sinnvolle Lösung und wir\n    empfehlen dir, sie auch in deine Pipeline aufzunehmen. Sie überprüfen den\n    Code bei jeder Übertragung und warnen dich vor möglichen Risiken. Wir haben\n    eine Produktübersicht zusammengestellt, die dich durch das Hinzufügen von\n    Scans, einschließlich statischer Anwendungssicherheitstests\n    ([SAST](https://docs.gitlab.com/user/application_security/sast/)) und\n    dynamischer Anwendungssicherheitstests\n    ([DAST](https://docs.gitlab.com/user/application_security/dast/)), zu\n    deiner CI-Pipeline führt.\n\n    __Klicke__ auf das Bild unten, um zur Übersicht zu gelangen.\n\n    [![Scans product\n    tour](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-14_at_13.44.42.png)](https://gitlab.navattic.com/gitlab-scans)\n\n    Außerdem können wir mithilfe von KI noch tiefer in Schwachstellen eindringen\n    und Vorschläge zu ihrer Behebung erhalten.\n\n    Weitere Informationen findest du in dieser Demo.\n\n    [![product tour explain vulnerability\n    ](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674096/Blog/Content%20Images/Screenshot_2024-04-14_at_13.50.24.png)](https://tech-marketing.gitlab.io/static-demos/pt-explain-vulnerability.html)\n\n    ## Zusammenfassung\n\n    Es gibt noch viel mehr zu erläutern, aber lass uns hier erst einmal\n    aufhören.\n\n    Alle Beispiele sind bewusst einfach gehalten, um das Konzept GitLab CI\n    vorzustellen, ohne die Dinge zu verkomplizieren. Fassen wir zusammen, was\n    wir gelernt haben:\n\n    1. Um Arbeit an GitLab CI zu delegieren, solltest du einen oder mehrere\n    [Jobs](https://docs.gitlab.com/ci/jobs/) in.gitlab-ci.yml. definieren.\n\n    2. Jobs sollten Namen haben – also denk dir was Gutes aus! Jeder Auftrag\n    enthält eine Reihe von Regeln und Anweisungen für GitLab CI, die durch\n    spezielle Schlüsselwörter definiert sind.\n\n    3. Aufträge können nacheinander, parallel oder ungeordnet über\n    [DAG](https://docs.gitlab.com/ci/directed_acyclic_graph/)\n    ausgeführt werden.\n\n    4. Du kannst Dateien zwischen Aufträgen weitergeben und sie in\n    Build-Artefakten speichern, sodass sie über die Schnittstelle\n    heruntergeladen werden können.\n\n    5. Du kannst Dateien zwischen Aufträgen weitergeben und sie in\n    Build-Artefakten speichern, sodass sie über die Schnittstelle\n    heruntergeladen werden können.\n\n    Nachstehend findest du eine genauere Beschreibung der von uns verwendeten\n    Begriffe und Schlüsselwörter sowie Links zu den entsprechenden Dokumenten.\n\n    ### Beschreibungen der Keywords\n\n    {: #keywords}\n\n    | Keyword/term       | Beschreibung | |---------------|--------------------|\n\n    | [.gitlab-ci.yml](https://docs.gitlab.com/ci/yaml/) | Datei mit allen\n    Definitionen dazu, wie dein Projekt aufgebaut sein sollte |\n\n    | [script](https://docs.gitlab.com/ci/yaml/#script)        | Definiert\n    ein Shell-Script, das ausgeführt werden soll |\n\n    | [before_script](https://docs.gitlab.com/ci/yaml/#before_script) | Wird\n    verwendet, um den Befehl zu definieren, der vor (allen) Aufträgen ausgeführt\n    werden soll |\n\n    |\n    [image](https://docs.gitlab.com/ci/docker/using_docker_images/#what-is-image)\n    | Definiert das zu verwendende Docker-Image |\n\n    | [stages](https://docs.gitlab.com/ci/yaml/#stages)         | Legt eine\n    Pipelinestufe fest (Standard: test) |\n\n    | [artifacts](https://docs.gitlab.com/ci/yaml/#artifacts)     | Definiert\n    eine Liste von Build-Artifacts |\n\n    |\n    [artifacts:expire_in](https://docs.gitlab.com/ci/yaml/#artifactsexpire_in)\n    | Wird verwendet, um hochgeladene Artifacts nach der angegebenen Zeit zu\n    löschen |\n\n    | [needs](https://docs.gitlab.com/ci/yaml/#needs) | Dient zur Definition\n    von Abhängigkeiten zwischen Aufträgen und ermöglicht die Ausführung von\n    Aufträgen außerhalb der Reihenfolge |\n\n    | [pipelines](https://about.gitlab.com/topics/ci-cd/cicd-pipeline/) | Eine\n    Pipeline ist eine Gruppe von Builds, die stufenweise (Batches) ausgeführt\n    werden |\n  category: engineering\n  tags:\n    - CI\n    - tutorial\n  updatedDate: '2025-05-16'\nconfig:\n  slug: basics-of-gitlab-ci-updated\n  featured: false\n  template: BlogPost\n",{"title":5,"description":17,"ogTitle":5,"ogDescription":17,"noIndex":14,"ogImage":19,"ogUrl":32,"ogSiteName":33,"ogType":34,"canonicalUrls":32},"https://about.gitlab.com/blog/basics-of-gitlab-ci-updated","https://about.gitlab.com","article","de-de/blog/basics-of-gitlab-ci-updated",[37,23],"ci",[22,23],"XYdLGTGun5k-1jByTjfCQVKPFD-IhzGH3iwLQ2_7iVA",{"data":41},{"logo":42,"freeTrial":47,"sales":52,"login":57,"items":62,"search":372,"minimal":406,"duo":424,"switchNav":433,"pricingDeployment":444},{"config":43},{"href":44,"dataGaName":45,"dataGaLocation":46},"/de-de/","gitlab logo","header",{"text":48,"config":49},"Kostenlose Testversion anfordern",{"href":50,"dataGaName":51,"dataGaLocation":46},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de&glm_content=default-saas-trial/","free trial",{"text":53,"config":54},"Vertrieb kontaktieren",{"href":55,"dataGaName":56,"dataGaLocation":46},"/de-de/sales/","sales",{"text":58,"config":59},"Anmelden",{"href":60,"dataGaName":61,"dataGaLocation":46},"https://gitlab.com/users/sign_in/","sign in",[63,90,187,192,293,353],{"text":64,"config":65,"cards":67},"Plattform",{"dataNavLevelOne":66},"platform",[68,74,82],{"title":64,"description":69,"link":70},"Die intelligente Orchestrierungsplattform für DevSecOps",{"text":71,"config":72},"Die Plattform erkunden",{"href":73,"dataGaName":66,"dataGaLocation":46},"/de-de/platform/",{"title":75,"description":76,"link":77},"GitLab Duo Agent Platform","Agentische KI für den gesamten Software-Lebenszyklus",{"text":78,"config":79},"Lerne GitLab Duo kennen",{"href":80,"dataGaName":81,"dataGaLocation":46},"/de-de/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":83,"description":84,"link":85},"Warum GitLab?","Erfahre, warum sich Unternehmen für GitLab entscheiden",{"text":86,"config":87},"Mehr erfahren",{"href":88,"dataGaName":89,"dataGaLocation":46},"/de-de/why-gitlab/","why gitlab",{"text":91,"left":28,"config":92,"link":94,"lists":98,"footer":169},"Produkt",{"dataNavLevelOne":93},"solutions",{"text":95,"config":96},"Alle Lösungen anzeigen",{"href":97,"dataGaName":93,"dataGaLocation":46},"/de-de/solutions/",[99,124,147],{"title":100,"description":101,"link":102,"items":107},"Automatisierung","CI/CD und Automatisierung zur Beschleunigung der Bereitstellung",{"config":103},{"icon":104,"href":105,"dataGaName":106,"dataGaLocation":46},"AutomatedCodeAlt","/de-de/solutions/delivery-automation/","automated software delivery",[108,112,115,120],{"text":109,"config":110},"CI/CD",{"href":111,"dataGaLocation":46,"dataGaName":109},"/de-de/solutions/continuous-integration/",{"text":75,"config":113},{"href":80,"dataGaLocation":46,"dataGaName":114},"gitlab duo agent platform - product menu",{"text":116,"config":117},"Quellcodeverwaltung",{"href":118,"dataGaLocation":46,"dataGaName":119},"/de-de/solutions/source-code-management/","Source Code Management",{"text":121,"config":122},"Automatische Softwarebereitstellung",{"href":105,"dataGaLocation":46,"dataGaName":123},"Automated software delivery",{"title":125,"description":126,"link":127,"items":132},"Sicherheit","Entwickle Code schneller ohne Abstriche bei der Sicherheit",{"config":128},{"href":129,"dataGaName":130,"dataGaLocation":46,"icon":131},"/de-de/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[133,137,142],{"text":134,"config":135},"Anwendungssicherheitstests",{"href":129,"dataGaName":136,"dataGaLocation":46},"Application security testing",{"text":138,"config":139},"Schutz der Software-Lieferkette",{"href":140,"dataGaLocation":46,"dataGaName":141},"/de-de/solutions/supply-chain/","Software supply chain security",{"text":143,"config":144},"Software-Compliance",{"href":145,"dataGaName":146,"dataGaLocation":46},"/de-de/solutions/software-compliance/","software compliance",{"title":148,"link":149,"items":154},"Auswertung",{"config":150},{"icon":151,"href":152,"dataGaName":153,"dataGaLocation":46},"DigitalTransformation","/de-de/solutions/visibility-measurement/","visibility and measurement",[155,159,164],{"text":156,"config":157},"Sichtbarkeit und Auswertung",{"href":152,"dataGaLocation":46,"dataGaName":158},"Visibility and Measurement",{"text":160,"config":161},"Wertstrommanagement",{"href":162,"dataGaLocation":46,"dataGaName":163},"/de-de/solutions/value-stream-management/","Value Stream Management",{"text":165,"config":166},"Analysen und Einblicke",{"href":167,"dataGaLocation":46,"dataGaName":168},"/de-de/solutions/analytics-and-insights/","Analytics and insights",{"title":170,"items":171},"GitLab für",[172,177,182],{"text":173,"config":174},"Enterprise",{"href":175,"dataGaLocation":46,"dataGaName":176},"/de-de/enterprise/","enterprise",{"text":178,"config":179},"Kleinunternehmen",{"href":180,"dataGaLocation":46,"dataGaName":181},"/de-de/small-business/","small business",{"text":183,"config":184},"Öffentlicher Sektor",{"href":185,"dataGaLocation":46,"dataGaName":186},"/de-de/solutions/public-sector/","public sector",{"text":188,"config":189},"Preise",{"href":190,"dataGaName":191,"dataGaLocation":46,"dataNavLevelOne":191},"/de-de/pricing/","pricing",{"text":193,"config":194,"link":196,"lists":200,"feature":280},"Ressourcen",{"dataNavLevelOne":195},"resources",{"text":197,"config":198},"Alle Ressourcen anzeigen",{"href":199,"dataGaName":195,"dataGaLocation":46},"/de-de/resources/",[201,234,252],{"title":202,"items":203},"Erste Schritte",[204,209,214,219,224,229],{"text":205,"config":206},"Installieren",{"href":207,"dataGaName":208,"dataGaLocation":46},"/de-de/install/","install",{"text":210,"config":211},"Kurzanleitungen",{"href":212,"dataGaName":213,"dataGaLocation":46},"/de-de/get-started/","quick setup checklists",{"text":215,"config":216},"Lernen",{"href":217,"dataGaLocation":46,"dataGaName":218},"https://university.gitlab.com/","learn",{"text":220,"config":221},"Produktdokumentation",{"href":222,"dataGaName":223,"dataGaLocation":46},"https://docs.gitlab.com/","product documentation",{"text":225,"config":226},"Best-Practice-Videos",{"href":227,"dataGaName":228,"dataGaLocation":46},"/de-de/getting-started-videos/","best practice videos",{"text":230,"config":231},"Integrationen",{"href":232,"dataGaName":233,"dataGaLocation":46},"/de-de/integrations/","integrations",{"title":235,"items":236},"Entdecken",[237,242,247],{"text":238,"config":239},"Kundenerfolge",{"href":240,"dataGaName":241,"dataGaLocation":46},"/de-de/customers/","customer success stories",{"text":243,"config":244},"Blog",{"href":245,"dataGaName":246,"dataGaLocation":46},"/de-de/blog/","blog",{"text":248,"config":249},"Remote",{"href":250,"dataGaName":251,"dataGaLocation":46},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":253,"items":254},"Vernetzen",[255,260,265,270,275],{"text":256,"config":257},"GitLab Services",{"href":258,"dataGaName":259,"dataGaLocation":46},"/de-de/services/","services",{"text":261,"config":262},"Community",{"href":263,"dataGaName":264,"dataGaLocation":46},"/community/","community",{"text":266,"config":267},"Forum",{"href":268,"dataGaName":269,"dataGaLocation":46},"https://forum.gitlab.com/","forum",{"text":271,"config":272},"Veranstaltungen",{"href":273,"dataGaName":274,"dataGaLocation":46},"/events/","events",{"text":276,"config":277},"Partner",{"href":278,"dataGaName":279,"dataGaLocation":46},"/de-de/partners/","partners",{"background":281,"textColor":282,"text":283,"image":284,"link":288},"#2f2a6b","#fff","Perspektiven für die Softwareentwicklung der Zukunft",{"altText":285,"config":286},"The Source Promo-Karte",{"src":287},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":289,"config":290},"Aktuelles",{"href":291,"dataGaName":292,"dataGaLocation":46},"/de-de/the-source/","the source",{"text":294,"config":295,"lists":297},"Unternehmen",{"dataNavLevelOne":296},"company",[298],{"items":299},[300,305,311,313,318,323,328,333,338,343,348],{"text":301,"config":302},"Über",{"href":303,"dataGaName":304,"dataGaLocation":46},"/de-de/company/","about",{"text":306,"config":307,"footerGa":310},"Karriere",{"href":308,"dataGaName":309,"dataGaLocation":46},"/jobs/","jobs",{"dataGaName":309},{"text":271,"config":312},{"href":273,"dataGaName":274,"dataGaLocation":46},{"text":314,"config":315},"Geschäftsführung",{"href":316,"dataGaName":317,"dataGaLocation":46},"/company/team/e-group/","leadership",{"text":319,"config":320},"Team",{"href":321,"dataGaName":322,"dataGaLocation":46},"/company/team/","team",{"text":324,"config":325},"Handbuch",{"href":326,"dataGaName":327,"dataGaLocation":46},"https://handbook.gitlab.com/","handbook",{"text":329,"config":330},"Investor Relations",{"href":331,"dataGaName":332,"dataGaLocation":46},"https://ir.gitlab.com/","investor relations",{"text":334,"config":335},"Trust Center",{"href":336,"dataGaName":337,"dataGaLocation":46},"/de-de/security/","trust center",{"text":339,"config":340},"AI Transparency Center",{"href":341,"dataGaName":342,"dataGaLocation":46},"/de-de/ai-transparency-center/","ai transparency center",{"text":344,"config":345},"Newsletter",{"href":346,"dataGaName":347,"dataGaLocation":46},"/company/contact/#contact-forms","newsletter",{"text":349,"config":350},"Presse",{"href":351,"dataGaName":352,"dataGaLocation":46},"/press/","press",{"text":354,"config":355,"lists":356},"Kontakt",{"dataNavLevelOne":296},[357],{"items":358},[359,362,367],{"text":53,"config":360},{"href":55,"dataGaName":361,"dataGaLocation":46},"talk to sales",{"text":363,"config":364},"Support-Portal",{"href":365,"dataGaName":366,"dataGaLocation":46},"https://support.gitlab.com","support portal",{"text":368,"config":369},"Kundenportal",{"href":370,"dataGaName":371,"dataGaLocation":46},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":373,"login":374,"suggestions":381},"Schließen",{"text":375,"link":376},"Um Repositorys und Projekte zu durchsuchen, melde dich an bei",{"text":377,"config":378},"gitlab.com",{"href":60,"dataGaName":379,"dataGaLocation":380},"search login","search",{"text":382,"default":383},"Vorschläge",[384,386,391,393,398,403],{"text":75,"config":385},{"href":80,"dataGaName":75,"dataGaLocation":380},{"text":387,"config":388},"Codevorschläge (KI)",{"href":389,"dataGaName":390,"dataGaLocation":380},"/de-de/solutions/code-suggestions/","Code Suggestions (AI)",{"text":109,"config":392},{"href":111,"dataGaName":109,"dataGaLocation":380},{"text":394,"config":395},"GitLab auf AWS",{"href":396,"dataGaName":397,"dataGaLocation":380},"/de-de/partners/technology-partners/aws/","GitLab on AWS",{"text":399,"config":400},"GitLab auf Google Cloud",{"href":401,"dataGaName":402,"dataGaLocation":380},"/de-de/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":83,"config":404},{"href":88,"dataGaName":405,"dataGaLocation":380},"Why GitLab?",{"freeTrial":407,"mobileIcon":412,"desktopIcon":417,"secondaryButton":420},{"text":408,"config":409},"Kostenlos testen",{"href":410,"dataGaName":51,"dataGaLocation":411},"https://gitlab.com/-/trials/new/","nav",{"altText":413,"config":414},"GitLab-Symbol",{"src":415,"dataGaName":416,"dataGaLocation":411},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":413,"config":418},{"src":419,"dataGaName":416,"dataGaLocation":411},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":202,"config":421},{"href":422,"dataGaName":423,"dataGaLocation":411},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de/get-started/","get started",{"freeTrial":425,"mobileIcon":429,"desktopIcon":431},{"text":426,"config":427},"Mehr über GitLab Duo erfahren",{"href":80,"dataGaName":428,"dataGaLocation":411},"gitlab duo",{"altText":413,"config":430},{"src":415,"dataGaName":416,"dataGaLocation":411},{"altText":413,"config":432},{"src":419,"dataGaName":416,"dataGaLocation":411},{"button":434,"mobileIcon":439,"desktopIcon":441},{"text":435,"config":436},"/Option",{"href":437,"dataGaName":438,"dataGaLocation":411},"#contact","switch",{"altText":413,"config":440},{"src":415,"dataGaName":416,"dataGaLocation":411},{"altText":413,"config":442},{"src":443,"dataGaName":416,"dataGaLocation":411},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":445,"mobileIcon":450,"desktopIcon":452},{"text":446,"config":447},"Zurück zur Preisübersicht",{"href":190,"dataGaName":448,"dataGaLocation":411,"icon":449},"back to pricing","GoBack",{"altText":413,"config":451},{"src":415,"dataGaName":416,"dataGaLocation":411},{"altText":413,"config":453},{"src":419,"dataGaName":416,"dataGaLocation":411},{"title":455,"button":456,"config":461},"Sieh dir an, wie agentische KI die Softwarebereitstellung transformiert",{"text":457,"config":458},"GitLab Transcend jetzt ansehen",{"href":459,"dataGaName":460,"dataGaLocation":46},"/de-de/events/transcend/virtual/","transcend event",{"layout":462,"icon":463,"disabled":28},"release","AiStar",{"data":465},{"text":466,"source":467,"edit":473,"contribute":478,"config":483,"items":488,"minimal":691},"Git ist eine Marke von Software Freedom Conservancy und unsere Verwendung von „GitLab“ erfolgt unter Lizenz.",{"text":468,"config":469},"Quelltext der Seite anzeigen",{"href":470,"dataGaName":471,"dataGaLocation":472},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":474,"config":475},"Diese Seite bearbeiten",{"href":476,"dataGaName":477,"dataGaLocation":472},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":479,"config":480},"Beteilige dich",{"href":481,"dataGaName":482,"dataGaLocation":472},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":484,"facebook":485,"youtube":486,"linkedin":487},"https://x.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[489,534,587,629,656],{"title":188,"links":490,"subMenu":505},[491,495,500],{"text":492,"config":493},"Tarife anzeigen",{"href":190,"dataGaName":494,"dataGaLocation":472},"view plans",{"text":496,"config":497},"Vorteile von Premium",{"href":498,"dataGaName":499,"dataGaLocation":472},"/de-de/pricing/premium/","why premium",{"text":501,"config":502},"Vorteile von Ultimate",{"href":503,"dataGaName":504,"dataGaLocation":472},"/de-de/pricing/ultimate/","why ultimate",[506],{"title":354,"links":507},[508,510,512,514,519,524,529],{"text":53,"config":509},{"href":55,"dataGaName":56,"dataGaLocation":472},{"text":363,"config":511},{"href":365,"dataGaName":366,"dataGaLocation":472},{"text":368,"config":513},{"href":370,"dataGaName":371,"dataGaLocation":472},{"text":515,"config":516},"Status",{"href":517,"dataGaName":518,"dataGaLocation":472},"https://status.gitlab.com/","status",{"text":520,"config":521},"Nutzungsbedingungen",{"href":522,"dataGaName":523,"dataGaLocation":472},"/terms/","terms of use",{"text":525,"config":526},"Datenschutzerklärung",{"href":527,"dataGaName":528,"dataGaLocation":472},"/de-de/privacy/","privacy statement",{"text":530,"config":531},"Cookie-Einstellungen",{"dataGaName":532,"dataGaLocation":472,"id":533,"isOneTrustButton":28},"cookie preferences","ot-sdk-btn",{"title":91,"links":535,"subMenu":544},[536,540],{"text":537,"config":538},"DevSecOps-Plattform",{"href":73,"dataGaName":539,"dataGaLocation":472},"devsecops platform",{"text":541,"config":542},"KI-unterstützte Entwicklung",{"href":80,"dataGaName":543,"dataGaLocation":472},"ai-assisted development",[545],{"title":546,"links":547},"Themen",[548,552,557,562,567,572,577,582],{"text":109,"config":549},{"href":550,"dataGaName":551,"dataGaLocation":472},"/de-de/topics/ci-cd/","cicd",{"text":553,"config":554},"GitOps",{"href":555,"dataGaName":556,"dataGaLocation":472},"/de-de/topics/gitops/","gitops",{"text":558,"config":559},"DevOps",{"href":560,"dataGaName":561,"dataGaLocation":472},"/de-de/topics/devops/","devops",{"text":563,"config":564},"Versionskontrolle",{"href":565,"dataGaName":566,"dataGaLocation":472},"/de-de/topics/version-control/","version control",{"text":568,"config":569},"DevSecOps",{"href":570,"dataGaName":571,"dataGaLocation":472},"/de-de/topics/devsecops/","devsecops",{"text":573,"config":574},"Cloud-nativ",{"href":575,"dataGaName":576,"dataGaLocation":472},"/de-de/topics/cloud-native/","cloud native",{"text":578,"config":579},"KI für das Programmieren",{"href":580,"dataGaName":581,"dataGaLocation":472},"/de-de/topics/devops/ai-for-coding/","ai for coding",{"text":583,"config":584},"Agentische KI",{"href":585,"dataGaName":586,"dataGaLocation":472},"/de-de/topics/agentic-ai/","agentic ai",{"title":588,"links":589},"Lösungen",[590,593,595,600,604,607,610,613,615,617,619,624],{"text":134,"config":591},{"href":129,"dataGaName":592,"dataGaLocation":472},"Application Security Testing",{"text":121,"config":594},{"href":105,"dataGaName":106,"dataGaLocation":472},{"text":596,"config":597},"Agile Entwicklung",{"href":598,"dataGaName":599,"dataGaLocation":472},"/de-de/solutions/agile-delivery/","agile delivery",{"text":601,"config":602},"SCM",{"href":118,"dataGaName":603,"dataGaLocation":472},"source code management",{"text":109,"config":605},{"href":111,"dataGaName":606,"dataGaLocation":472},"continuous integration & delivery",{"text":160,"config":608},{"href":162,"dataGaName":609,"dataGaLocation":472},"value stream management",{"text":553,"config":611},{"href":612,"dataGaName":556,"dataGaLocation":472},"/de-de/solutions/gitops/",{"text":173,"config":614},{"href":175,"dataGaName":176,"dataGaLocation":472},{"text":178,"config":616},{"href":180,"dataGaName":181,"dataGaLocation":472},{"text":183,"config":618},{"href":185,"dataGaName":186,"dataGaLocation":472},{"text":620,"config":621},"Bildungswesen",{"href":622,"dataGaName":623,"dataGaLocation":472},"/de-de/solutions/education/","education",{"text":625,"config":626},"Finanzdienstleistungen",{"href":627,"dataGaName":628,"dataGaLocation":472},"/de-de/solutions/finance/","financial services",{"title":193,"links":630},[631,633,635,637,640,642,644,646,648,650,652,654],{"text":205,"config":632},{"href":207,"dataGaName":208,"dataGaLocation":472},{"text":210,"config":634},{"href":212,"dataGaName":213,"dataGaLocation":472},{"text":215,"config":636},{"href":217,"dataGaName":218,"dataGaLocation":472},{"text":220,"config":638},{"href":222,"dataGaName":639,"dataGaLocation":472},"docs",{"text":243,"config":641},{"href":245,"dataGaName":246,"dataGaLocation":472},{"text":238,"config":643},{"href":240,"dataGaName":241,"dataGaLocation":472},{"text":248,"config":645},{"href":250,"dataGaName":251,"dataGaLocation":472},{"text":256,"config":647},{"href":258,"dataGaName":259,"dataGaLocation":472},{"text":261,"config":649},{"href":263,"dataGaName":264,"dataGaLocation":472},{"text":266,"config":651},{"href":268,"dataGaName":269,"dataGaLocation":472},{"text":271,"config":653},{"href":273,"dataGaName":274,"dataGaLocation":472},{"text":276,"config":655},{"href":278,"dataGaName":279,"dataGaLocation":472},{"title":294,"links":657},[658,660,662,664,666,668,670,675,680,682,684,686],{"text":301,"config":659},{"href":303,"dataGaName":296,"dataGaLocation":472},{"text":306,"config":661},{"href":308,"dataGaName":309,"dataGaLocation":472},{"text":314,"config":663},{"href":316,"dataGaName":317,"dataGaLocation":472},{"text":319,"config":665},{"href":321,"dataGaName":322,"dataGaLocation":472},{"text":324,"config":667},{"href":326,"dataGaName":327,"dataGaLocation":472},{"text":329,"config":669},{"href":331,"dataGaName":332,"dataGaLocation":472},{"text":671,"config":672},"Nachhaltigkeit",{"href":673,"dataGaName":674,"dataGaLocation":472},"/sustainability/","Sustainability",{"text":676,"config":677},"Vielfalt, Inklusion und Zugehörigkeit",{"href":678,"dataGaName":679,"dataGaLocation":472},"/de-de/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":334,"config":681},{"href":336,"dataGaName":337,"dataGaLocation":472},{"text":344,"config":683},{"href":346,"dataGaName":347,"dataGaLocation":472},{"text":349,"config":685},{"href":351,"dataGaName":352,"dataGaLocation":472},{"text":687,"config":688},"Transparenzerklärung zu moderner Sklaverei",{"href":689,"dataGaName":690,"dataGaLocation":472},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":692},[693,695,698],{"text":520,"config":694},{"href":522,"dataGaName":523,"dataGaLocation":472},{"text":696,"config":697},"Cookies",{"dataGaName":532,"dataGaLocation":472,"id":533,"isOneTrustButton":28},{"text":525,"config":699},{"href":527,"dataGaName":528,"dataGaLocation":472},[701],{"id":702,"title":9,"body":26,"config":703,"content":705,"description":26,"extension":25,"meta":709,"navigation":28,"path":710,"seo":711,"stem":712,"__hash__":713},"blogAuthors/en-us/blog/authors/itzik-gan-baruch.yml",{"template":704},"BlogAuthor",{"name":9,"config":706},{"headshot":707,"ctfId":708},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749658921/Blog/Author%20Headshots/iganbaruch-headshot.jpg","iganbaruch",{},"/en-us/blog/authors/itzik-gan-baruch",{},"en-us/blog/authors/itzik-gan-baruch","bz9VMiTQ1ixvnoxUFk0jiUcnLG3oQsymgXNCqyRqfsk",[715,728,742],{"content":716,"config":726},{"title":717,"description":718,"authors":719,"heroImage":721,"date":722,"body":723,"category":11,"tags":724},"CI/CD-Observability im Unternehmensmaßstab aufbauen","Dieser Praxisleitfaden zu GitLab Pipeline Analytics hilft Self-Managed-Nutzern, mit Prometheus und Grafana operationale Insights zu gewinnen.",[720],"Paul Meresanu","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28","CI/CD-Optimierung beginnt mit Transparenz. Eine erfolgreiche DevOps-Plattform\nim Unternehmensmaßstab umfasst das Verständnis von Pipeline-Performance,\nJob-Ausführungsmustern und quantifizierbaren operationalen Insights – insbesondere\nfür Unternehmen, die GitLab Self-Managed betreiben.\n\nUm GitLab-Kund(inn)en dabei zu helfen, den vollen Nutzen ihrer Plattform\nauszuschöpfen, haben wir die GitLab CI/CD Observability-Lösung als Teil unseres\nPlatform Excellence-Programms entwickelt. Sie verwandelt rohe Pipeline-Metriken\nin handlungsrelevante operationale Erkenntnisse.\n\nEin führendes Finanzdienstleistungsunternehmen hat gemeinsam mit GitLabs Customer\nSuccess Architect Transparenz über seine GitLab Self-Managed-Deployments\ngewonnen. Gemeinsam haben wir eine containerisierte Observability-Lösung\nimplementiert, die den Open-Source-gitlab-ci-pipelines-exporter mit\nunternehmensgerechter Prometheus- und Grafana-Infrastruktur kombiniert.\n\nIn diesem Artikel werden die Herausforderungen beim Pipeline-Management im\nUnternehmensmaßstab erläutert – und wie GitLab CI/CD Observability diese mit\neiner praxisnahen End-to-End-Implementierung adressiert.\n\n\n## Die Herausforderung: CI/CD-Performance messen\n\nVor der Implementierung einer Observability-Lösung sollte die\nMessdimension klar definiert sein:\n\n* **Welche Metriken sind relevant?** Pipeline-Dauer, Job-Erfolgsraten,\n  Queue-Zeiten, Runner-Auslastung\n* **Wer braucht Transparenz?** Entwickler(innen), DevOps-Engineers,\n  Plattformteams, Führungsebene\n* **Welche Entscheidungen werden damit getroffen?** Infrastrukturinvestitionen,\n  Engpass-Behebung, Kapazitätsplanung\n\n\n## Lösungsarchitektur: Ein vollständiges Dashboard-Set für Observability\n\nNach dem Deployment stellt der Observability-Stack ein Set von\nGrafana-Dashboards bereit, das Echtzeit- und historische Transparenz über die\nCI/CD-Plattform bietet. Ein typisches Deployment umfasst:\n\n* **Pipeline Overview Dashboard:** Eine übergeordnete Ansicht mit Gesamtzahl\n  der Pipeline-Läufe, Erfolgs-/Fehlerquoten über die Zeit (als gestapelte\n  Balken- oder Zeitreihencharts) und Trends bei der durchschnittlichen\n  Pipeline-Dauer. Panels verwenden farbcodierte Statusindikatoren (Grün für\n  Erfolg, Rot für Fehler, Gelb für Abbruch), damit Plattformteams\n  Verschlechterungen auf einen Blick erkennen.\n* **Job Performance Dashboard:** Drill-down-Panels mit Verteilungen der\n  einzelnen Job-Dauern (Histogramm), den 10 langsamsten Jobs nach\n  Durchschnittsdauer und Job-Fehler-Heatmaps nach Projekt und Stage. Hier\n  identifizieren Teams konkrete Engpass-Jobs, die sich zu optimieren lohnen.\n* **Runner & Infrastructure Dashboard:** Kombiniert Node-Exporter-Host-Metriken\n  (CPU, Arbeitsspeicher, Disk) mit Pipeline-Queue-Zeit-Daten, um\n  Infrastruktur-Sättigung mit Pipeline-Wartezeiten zu korrelieren. Nützlich\n  für Kapazitätsplanungsentscheidungen wie die Skalierung von Runner-Pools oder\n  das Upgrade von Instanzgrößen.\n* **Deployment Frequency Dashboard:** Verfolgt Deployment-Anzahl und\n  -Dauer über die Zeit pro Umgebung, abgestimmt auf DORA-Metriken. Hilft\n  der Engineering-Führungsebene, Lieferdurchsatz und Environment-Drift\n  (Commits hinter main) zu bewerten.\n\nJedes Dashboard wird automatisch über Grafanas dateibasiertes Provisioning\nbereitgestellt, sodass es konsistent über alle Umgebungen hinweg deployed wird.\nDie Dashboards lassen sich über Grafana-Variablen weiter anpassen, um nach\nProjekt, Ref/Branch oder Zeitraum zu filtern.\n\n![Lösungsarchitektur](https://res.cloudinary.com/about-gitlab-com/image/upload/v1777382608/Blog/Imported/blog-building-ci-cd-observability-stack-for-gitlab-self-managed/image1.png)\n\nDie Lösung benötigt zwei Exporter:\n\n* **Pipeline Exporter:** Erfasst CI/CD-Metriken über die GitLab API\n  (Pipeline-Dauer, Job-Status, Deployments)\n* **Node Exporter:** Erfasst Host-Metriken (CPU, Arbeitsspeicher, Disk)\n  für die Infrastruktur-Korrelation\n\n**Voraussetzungen:**\n\n* GitLab Self-Managed Version 18.1+\n* **Container-Orchestrierungsplattform:** Ein Kubernetes-Cluster (empfohlen\n  für Unternehmens-Deployments) oder eine Container-Runtime wie Docker/Podman\n  für kleinere Umgebungen oder Proof-of-Concept-Deployments. Die primäre\n  Deployment-Anleitung unten zielt auf Kubernetes; eine Docker-Compose-Alternative\n  ist im Anhang für lokales Testen und Evaluation verfügbar\n* GitLab Personal Access Token (Scope **read_api**)\n\nDie vollständige Implementierungsanleitung mit allen Kubernetes-Manifesten\nfolgt direkt im Anschluss.\n\n\n## Kubernetes-Deployment (empfohlen)\n\nFür Unternehmensumgebungen wird jede Komponente als separates Deployment in\neinem dedizierten Namespace deployed. Dieser Ansatz integriert sich in\nbestehende Cluster-Infrastruktur, Secrets-Management und Network-Policies.\n\n### 1. Namespace und Secret erstellen\n\n```bash\nkubectl create namespace gitlab-observability\n\n# GitLab-Token-Secret erstellen (siehe Abschnitt Secrets-Management\n# für unternehmensgerechte Ansätze mit externen Secret-Operatoren)\nkubectl create secret generic gitlab-token \\\n  --from-literal=token=glpat-xxxxxxxxxxxx \\\n  -n gitlab-observability\n```\n\n### 2. Pipeline Exporter deployen\n\n```yaml\n# exporter-deployment.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: gitlab-ci-pipelines-exporter\n  namespace: gitlab-observability\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: gitlab-ci-pipelines-exporter\n  template:\n    metadata:\n      labels:\n        app: gitlab-ci-pipelines-exporter\n    spec:\n      containers:\n        - name: exporter\n          image: mvisonneau/gitlab-ci-pipelines-exporter:latest\n          ports:\n            - containerPort: 8080\n          env:\n            - name: GCPE_GITLAB_TOKEN\n              valueFrom:\n                secretKeyRef:\n                  name: gitlab-token\n                  key: token\n            - name: GCPE_CONFIG\n              value: /etc/gcpe/config.yml\n          volumeMounts:\n            - name: config\n              mountPath: /etc/gcpe\n      volumes:\n        - name: config\n          configMap:\n            name: gcpe-config\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: gitlab-ci-pipelines-exporter\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: gitlab-ci-pipelines-exporter\n  ports:\n    - port: 8080\n      targetPort: 8080\n```\n\n### 3. Node Exporter deployen (DaemonSet)\n\n```yaml\n# node-exporter-daemonset.yaml\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: node-exporter\n  namespace: gitlab-observability\nspec:\n  selector:\n    matchLabels:\n      app: node-exporter\n  template:\n    metadata:\n      labels:\n        app: node-exporter\n    spec:\n      containers:\n        - name: node-exporter\n          image: prom/node-exporter:latest\n          ports:\n            - containerPort: 9100\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: node-exporter\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: node-exporter\n  ports:\n    - port: 9100\n      targetPort: 9100\n```\n\n### 4. Prometheus deployen\n\n```yaml\n# prometheus-deployment.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prometheus\n  namespace: gitlab-observability\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: prometheus\n  template:\n    metadata:\n      labels:\n        app: prometheus\n    spec:\n      containers:\n        - name: prometheus\n          image: prom/prometheus:latest\n          ports:\n            - containerPort: 9090\n          volumeMounts:\n            - name: config\n              mountPath: /etc/prometheus\n      volumes:\n        - name: config\n          configMap:\n            name: prometheus-config\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: prometheus\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: prometheus\n  ports:\n    - port: 9090\n      targetPort: 9090\n```\n\n### 5. Grafana deployen\n\nDas folgende Grafana-Deployment startet mit deaktivierter Authentifizierung\n(`GF_AUTH_ANONYMOUS_ENABLED: true`) für den einfachen Einstieg.\n\n**Diese Einstellung erlaubt jedem mit Netzwerkzugang, alle Dashboards ohne\nAnmeldung einzusehen.** Für Produktions-Deployments diese Variable entfernen\noder auf false setzen und einen geeigneten Authentifizierungs-Provider\n(LDAP, SAML/SSO oder OAuth) konfigurieren, um den Zugriff auf autorisierte\nNutzende zu beschränken.\n\n```yaml\n# grafana-deployment.yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: grafana\n  namespace: gitlab-observability\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: grafana\n  template:\n    metadata:\n      labels:\n        app: grafana\n    spec:\n      containers:\n        - name: grafana\n          image: grafana/grafana:10.0.0\n          ports:\n            - containerPort: 3000\n          env:\n            # Für Produktion ENTFERNEN oder auf 'false' setzen.\n            # Bei 'true' können alle Nutzenden mit Netzwerkzugang\n            # Dashboards ohne Authentifizierung einsehen.\n            - name: GF_AUTH_ANONYMOUS_ENABLED\n              value: 'true'\n          volumeMounts:\n            - name: dashboards-provider\n              mountPath: /etc/grafana/provisioning/dashboards\n            - name: datasources\n              mountPath: /etc/grafana/provisioning/datasources\n            - name: dashboards\n              mountPath: /var/lib/grafana/dashboards\n      volumes:\n        - name: dashboards-provider\n          configMap:\n            name: grafana-dashboards-provider\n        - name: datasources\n          configMap:\n            name: grafana-datasources\n        - name: dashboards\n          configMap:\n            name: grafana-dashboards\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: grafana\n  namespace: gitlab-observability\nspec:\n  selector:\n    app: grafana\n  ports:\n    - port: 3000\n      targetPort: 3000\n```\n\n### 6. Network Policy setzen\n\nDen Inter-Pod-Traffic auf die erforderlichen Kommunikationspfade beschränken:\n\n```yaml\n# network-policy.yaml\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: observability-policy\n  namespace: gitlab-observability\nspec:\n  podSelector: {}\n  policyTypes:\n    - Ingress\n  ingress:\n    # Prometheus scrapt Exporter und Node-Exporter\n    - from:\n        - podSelector:\n            matchLabels:\n              app: prometheus\n      ports:\n        - port: 8080\n        - port: 9100\n    # Grafana fragt Prometheus ab\n    - from:\n        - podSelector:\n            matchLabels:\n              app: grafana\n      ports:\n        - port: 9090\n```\n\n### 7. Validieren\n\n```bash\nkubectl get pods -n gitlab-observability\nkubectl port-forward svc/grafana 3000:3000 -n gitlab-observability\ncurl http://localhost:3000/api/health\n```\n\n\n## Konfigurationsreferenz\n\n### Exporter-Konfiguration\n\n```yaml\n# gitlab-ci-pipelines-exporter.yml (ConfigMap: gcpe-config)\nlog:\n  level: info\ngitlab:\n  url: https://gitlab.your-domain.com\n  maximum_requests_per_second: 10\nproject_defaults:\n  pull:\n    pipeline:\n      jobs:\n        enabled: true\nwildcards:\n  - owner:\n      name: your-group-name\n      kind: group\n    archived: false\n```\n\n### Prometheus-Konfiguration\n\n```yaml\n# prometheus.yml (ConfigMap: prometheus-config)\nglobal:\n  scrape_interval: 15s\nscrape_configs:\n  - job_name: 'gitlab-ci-pipelines-exporter'\n    static_configs:\n      - targets: ['gitlab-ci-pipelines-exporter:8080']\n  - job_name: 'node-exporter'\n    static_configs:\n      - targets: ['node-exporter:9100']\n```\n\n### Grafana-Datenquellen\n\n```yaml\n# datasources.yml (ConfigMap: grafana-datasources)\napiVersion: 1\ndatasources:\n  - name: Prometheus\n    type: prometheus\n    access: proxy\n    url: http://prometheus:9090\n    isDefault: true\n# dashboards.yml (ConfigMap: grafana-dashboards-provider)\napiVersion: 1\nproviders:\n  - name: 'default'\n    folder: 'GitLab CI/CD'\n    type: file\n    options:\n      path: /var/lib/grafana/dashboards\n```\n\n\n## Wichtige Metriken\n\n### Pipeline-Exporter-Metriken\n\n| Metrik | Beschreibung |\n| :---- | :---- |\n| `gitlab_ci_pipeline_duration_seconds` | Pipeline-Ausführungszeit |\n| `gitlab_ci_pipeline_status` | Pipeline-Erfolg/-Fehler nach Projekt |\n| `gitlab_ci_pipeline_job_duration_seconds` | Einzelne Job-Ausführungszeit |\n| `gitlab_ci_pipeline_job_status` | Job-Erfolgs-/-Fehlerstatus |\n| `gitlab_ci_pipeline_job_artifact_size_bytes` | Artifact-Speicherverbrauch |\n| `gitlab_ci_pipeline_coverage` | Code-Coverage-Prozentsatz |\n| `gitlab_ci_environment_deployment_count` | Deployment-Häufigkeit |\n| `gitlab_ci_environment_deployment_duration_seconds` | Deployment-Ausführungszeit |\n| `gitlab_ci_environment_behind_commits_count` | Environment-Drift gegenüber main |\n\n### Node-Exporter-Metriken\n\n| Metrik | Beschreibung |\n| :---- | :---- |\n| `node_cpu_seconds_total` | CPU-Auslastung |\n| `node_memory_MemAvailable_bytes` | Verfügbarer Arbeitsspeicher |\n| `node_filesystem_avail_bytes` | Verfügbarer Festplattenspeicher |\n| `node_load1` | 1-Minuten-Lastdurchschnitt |\n\n\n## Fehlerbehebung\n\n### Grafana-Plugin-Installation in Air-gapped-Umgebungen\n\nFür Offline-Umgebungen Plugins manuell installieren. Beispiel für Kubernetes:\n\n```bash\n# Plugin-ZIP in den Grafana-Pod kopieren\nkubectl cp grafana-polystat-panel-2.1.16.zip \\\n  gitlab-observability/grafana-\u003Cpod-id>:/tmp/\n# Plugin entpacken\nkubectl exec -it -n gitlab-observability deploy/grafana -- \\\n  sh -c \"unzip /tmp/grafana-polystat-panel-2.1.16.zip -d /var/lib/grafana/plugins/\"\n# Grafana-Pod neu starten\nkubectl rollout restart deployment/grafana -n gitlab-observability\n# Installation prüfen\nkubectl exec -it -n gitlab-observability deploy/grafana -- \\\n  ls -al /var/lib/grafana/plugins/\n```\n\n\n## Unternehmensaspekte\n\nFür regulierte Branchen gilt:\n\n* **Token-Sicherheit:** GitLab Personal Access Tokens in einem dedizierten\n  Secrets-Manager speichern, nicht hartcodiert in ConfigMaps. Token-Rotation\n  durchsetzen und den Scope auf **read\\_api** beschränken.\n* **Netzwerksegmentierung:** Hinter einem Reverse Proxy mit TLS-Terminierung\n  deployen. In Kubernetes einen Ingress-Controller mit automatisierter\n  Zertifikatsbereitstellung verwenden.\n* **Authentifizierung:** Grafana mit dem Identity Provider der Organisation\n  konfigurieren (SAML, LDAP oder OAuth/OIDC), um rollenbasierte\n  Zugriffskontrolle auf Dashboards durchzusetzen.\n\n\n## Warum GitLab?\n\nGitLabs API-First-Design ermöglicht individuelle Observability-Lösungen, die\nnative Funktionen wie Value Stream Analytics und DORA-Metriken ergänzen. Die\noffene Architektur erlaubt es Unternehmen, bewährte Open-Source-Werkzeuge –\nwie den gitlab-ci-pipelines-exporter – direkt in bestehende\nUnternehmensinfrastruktur zu integrieren, ohne etablierte Workflows zu\nunterbrechen.\n\nMit wachsender Observability-Reife bieten GitLabs eingebaute\nObservability-Funktionen einen natürlichen nächsten Schritt – tiefere,\nintegrierte Transparenz ohne zusätzliche Werkzeuge. Mehr zu den nativen\nPlattformfunktionen unter\n[GitLab Observability](https://docs.gitlab.com/operations/observability/observability/).\n",[109,725,23],"product",{"featured":14,"template":15,"slug":727},"how-to-build-ci-cd-observability-at-scale",{"content":729,"config":740},{"body":730,"title":731,"description":732,"authors":733,"heroImage":735,"date":736,"category":11,"tags":737},"## Abschnitt 1: Das Modell verstehen\n*Für Engineering-Leads und Entscheidungsträger: Konzept, Anwendungsfälle und Architekturprinzipien. Konfigurationsdetails folgen in Abschnitt 2.*\n\nDie meisten CI/CD-Werkzeuge können einen Build ausführen und ein Deployment anstoßen. Der Unterschied zeigt sich erst dann, wenn die Delivery-Anforderungen komplexer werden: ein Monorepo mit einem Dutzend Services, Microservices über mehrere Repositories verteilt, Deployments in Dutzende von Umgebungen gleichzeitig – oder ein Platform-Team, das organisationsweite Standards durchsetzen will, ohne dabei zum Engpass zu werden.\n\nGitLabs Pipeline-Modell wurde für genau diese Komplexität entwickelt. Parent-Child-Pipelines, DAG-Execution, dynamische Pipeline-Generierung, Multi-Project-Trigger, Merge-Request-Pipelines mit Merged-Results-Verarbeitung und CI/CD Components lösen jeweils eine eigene Klasse von Problemen. Da sich diese Bausteine kombinieren lassen, erschließt das vollständige Modell mehr als nur kürzere Pipeline-Laufzeiten.\n\nDieser Artikel beschreibt die fünf Muster, bei denen das Modell seine Stärken deutlich zeigt – jeweils zugeordnet zu einem konkreten Engineering-Szenario. Konfigurationen und Implementierungsdetails folgen in Abschnitt 2.\n\n### 1. Monorepos: Parent-Child-Pipelines und DAG-Execution\n\n**Das Problem:** Ein Monorepo enthält Frontend, Backend und Dokumentation. Jeder Commit löst einen vollständigen Rebuild aller Komponenten aus – auch wenn sich nur eine README-Datei geändert hat.\n\nGitLab kombiniert zwei sich ergänzende Mechanismen: [Parent-Child-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#parent-child-pipelines) ermöglichen es einer übergeordneten Pipeline, isolierte Child-Pipelines zu starten. [DAG-Execution via `needs`](https://docs.gitlab.com/ci/yaml/#needs) bricht die starre Stage-Reihenfolge auf und startet Jobs, sobald ihre Abhängigkeiten abgeschlossen sind – nicht erst, wenn alle Jobs einer Stage fertig sind.\n\nEine Parent-Pipeline erkennt, welche Teile des Repos sich geändert haben, und löst ausschließlich die betroffenen Child-Pipelines aus. Jeder Service verwaltet seine eigene Pipeline-Konfiguration; Änderungen in einem Service können keine anderen beeinflussen. Damit bleibt die Komplexität beherrschbar, während das Repository und das Team wachsen.\n\nEinen technischen Aspekt gilt es dabei zu kennen: Wenn mehrere Dateien an einen einzelnen `trigger: include:`-Block übergeben werden, fusioniert GitLab sie zu einer einzigen Child-Pipeline-Konfiguration. Jobs aus diesen Dateien teilen denselben Pipeline-Kontext und können sich gegenseitig per `needs:` referenzieren – das ist die Voraussetzung für die DAG-Optimierung. Werden die Dateien stattdessen auf separate Trigger-Jobs aufgeteilt, entsteht jeweils eine isolierte Pipeline, und dateiübergreifende `needs:`-Referenzen funktionieren nicht.\n\nIn großen Monorepos lassen sich Pipeline-Laufzeiten durch DAG-Execution deutlich reduzieren, da Jobs nicht mehr auf unabhängige Arbeitsschritte in derselben Stage warten.\n\n### 2. Microservices: Cross-Repo-Pipelines über mehrere Projekte\n\n**Das Problem:** Frontend und Backend leben in separaten Repositories. Wenn das Frontend-Team eine Änderung ausliefert, ist nicht erkennbar, ob sie die Backend-Integration beeinträchtigt – und umgekehrt.\n\n[Multi-Project-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#multi-project-pipelines) ermöglichen es, aus einem Projekt heraus eine Pipeline in einem anderen Projekt auszulösen und auf das Ergebnis zu warten. Das auslösende Projekt sieht die verknüpfte Downstream-Pipeline direkt in seiner eigenen Pipeline-Ansicht.\n\nIn der Praxis erstellt die Frontend-Pipeline ein API-Contract-Artifact und veröffentlicht es, bevor die Backend-Pipeline ausgelöst wird. Das Backend ruft dieses Artifact über die [Jobs API](https://docs.gitlab.com/api/jobs/#download-a-single-artifact-file-from-specific-tag-or-branch) ab und validiert es, bevor weitere Schritte erlaubt sind. Wird eine Breaking Change erkannt, schlägt die Backend-Pipeline fehl – und mit ihr die Frontend-Pipeline. Probleme, die bisher erst in der Produktion sichtbar wurden, werden damit im Pipeline-Prozess abgefangen. Die Abhängigkeit zwischen Services wird sichtbar, nachvollziehbar und aktiv verwaltbar.\n\n![Cross-project pipelines](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738762/Blog/Imported/hackathon-fake-blog-post-s/image4_h6mfsb.png \"Cross-project pipelines\") *Cross-project pipelines*\n\n### 3. Multi-Tenant/Matrix-Deployments: Dynamische Child-Pipelines\n\n**Das Problem:** Dieselbe Anwendung wird in 15 Kundenumgebungen, drei Cloud-Regionen oder den Stages Dev/Staging/Prod deployed. Manuelle Anpassungen je Umgebung führen zu Konfigurationsdrift. Eine separate Pipeline pro Umgebung ist von Anfang an nicht wartbar.\n\n[Dynamische Child-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_pipelines/#dynamic-child-pipelines) generieren die Pipeline-Struktur zur Laufzeit. Ein Job führt ein Skript aus, das eine YAML-Datei erzeugt – und diese YAML-Datei wird zur Pipeline für den nächsten Schritt. Die Pipeline-Struktur selbst wird damit zu Daten.\n\nDas Generierungsskript iteriert über eine `ENVIRONMENTS`-Variable, statt jede Umgebung fest zu kodieren. Eine neue Umgebung lässt sich durch Anpassen der Variable hinzufügen – ohne Änderungen an der Pipeline-Konfiguration selbst. Trigger-Jobs erben mit `extends:` eine gemeinsame Template-Konfiguration, sodass `strategy: depend` einmal definiert und nicht für jeden Trigger-Job wiederholt wird. Ein `when: manual`-Gate für das Produktions-Deployment ist direkt in den Pipeline-Graph integriert.\n\nPlatform-Teams nutzen dieses Muster, um Dutzende von Umgebungen zu verwalten, ohne Pipeline-Logik zu duplizieren.\n\n![Dynamic pipeline](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738765/Blog/Imported/hackathon-fake-blog-post-s/image7_wr0kx2.png \"Dynamic pipeline\")\n\n### 4. MR-First-Delivery: Merge-Request-Pipelines, Merged-Results und Workflow-Routing\n\n**Das Problem:** Die Pipeline läuft bei jedem Push auf jeden Branch. Aufwändige Tests werden auf Feature-Branches ausgeführt, die nie gemergt werden. Gleichzeitig gibt es keine Garantie, dass das Getestete dem entspricht, was nach dem Merge auf `main` tatsächlich landet.\n\nGitLab kombiniert drei ineinandergreifende Mechanismen: [Merge-Request-Pipelines](https://docs.gitlab.com/ci/pipelines/merge_request_pipelines/) laufen ausschließlich dann, wenn ein Merge Request existiert – nicht bei jedem Branch-Push. Allein dadurch entfällt ein erheblicher Anteil unnötiger Compute-Ausführungen. [Merged-Results-Pipelines](https://docs.gitlab.com/ci/pipelines/merged_results_pipelines/) gehen einen Schritt weiter: GitLab erstellt einen temporären Merge-Commit aus dem Branch und dem aktuellen Ziel-Branch und führt die Pipeline dagegen aus. Getestet wird damit das tatsächliche Ergebnis des Merges – nicht der Branch in Isolation. [Workflow-Rules](https://docs.gitlab.com/ci/yaml/workflow/) definieren schließlich, welcher Pipeline-Typ unter welchen Bedingungen ausgeführt wird. Die `$CI_OPEN_MERGE_REQUESTS`-Guard verhindert dabei, dass für einen Branch mit offenem MR doppelte Pipelines ausgelöst werden.\n\nDas Ergebnis ist ein Pipeline-Verhalten, das sich je nach Kontext unterscheidet: Ein Push auf einen Feature-Branch ohne offenen MR führt nur Lint und Unit-Tests aus. Sobald ein MR geöffnet wird, wechseln die Workflow-Rules auf eine MR-Pipeline mit der vollständigen Test-Suite gegen das Merged-Result. Ein Merge auf `main` stellt ein manuelles Produktions-Deployment in die Warteschlange. Der Nightly-Scan läuft einmalig als geplante Pipeline – nicht bei jedem Commit.\n\nMerged-Results-Pipelines fangen dabei die Klasse von Fehlern ab, die erst nach einem Merge sichtbar werden – bevor sie `main` erreichen.\n\n### 5. Governed Pipelines: CI/CD Components\n\n**Das Problem:** Das Platform-Team hat den richtigen Weg für Build, Test und Deploy definiert. Jedes Anwendungsteam pflegt jedoch eine eigene `.gitlab-ci.yml` mit subtilen Abweichungen. Security-Scanning wird übersprungen. Deployment-Standards driften. Audits werden aufwändig.\n\n[CI/CD Components](https://docs.gitlab.com/ci/components/) ermöglichen es Platform-Teams, versionierte, wiederverwendbare Pipeline-Bausteine zu veröffentlichen. Anwendungsteams binden sie mit einer einzigen `include:`-Zeile ein – kein Copy-Paste, kein Drift. Components sind über den [CI/CD Catalog](https://docs.gitlab.com/ci/components/#cicd-catalog) auffindbar, sodass Teams bewährte Bausteine finden und übernehmen können, ohne das Platform-Team direkt einschalten zu müssen.\n\nDrei Zeilen `include:` ersetzen hunderte von duplizierten YAML-Zeilen. Das Platform-Team kann einen Security-Fix in einer neuen Komponentenversion veröffentlichen – Teams steigen auf ihrem eigenen Zeitplan um, oder das Platform-Team fixiert alle auf eine Mindestversion. In beiden Fällen propagiert eine Änderung organisationsweit, statt repo-für-repo angewendet zu werden.\n\nKombiniert mit [Resource Groups](https://docs.gitlab.com/ci/resource_groups/) zur Vermeidung konkurrierender Deployments und [Protected Environments](https://docs.gitlab.com/ci/environments/protected_environments/) für Freigabe-Gates entsteht eine governed Delivery-Plattform, auf der **Compliance der Standard ist, nicht die Ausnahme**. Platform-Teams setzen Vorgaben durch, ohne zum Engpass zu werden.\n\n![Component pipeline (imported jobs)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738776/Blog/Imported/hackathon-fake-blog-post-s/image2_pizuxd.png \"Component pipeline (imported jobs)\")\n\n## Das Modell als Ganzes\n\nKeines dieser Muster existiert isoliert. Der Wert von GitLabs Pipeline-Modell liegt in der Kombinierbarkeit seiner Bausteine:\n\n- Ein Monorepo nutzt Parent-Child-Pipelines, und jede Child-Pipeline nutzt DAG-Execution.\n- Eine Microservices-Plattform nutzt Multi-Project-Pipelines, und jedes Projekt nutzt MR-Pipelines mit Merged-Results.\n- Eine governed Plattform nutzt CI/CD Components, um die obigen Muster organisationsweit zu standardisieren.\n\nDie meisten Teams entdecken eines dieser Muster, wenn sie auf ein konkretes Problem stoßen. Teams, die das vollständige Modell verstehen, entwickeln daraus eine Delivery-Infrastruktur, die tatsächlich abbildet, wie ihre Engineering-Organisation arbeitet – und mit ihr wächst.\n\n## Weitere Muster\n\nDas Pipeline-Modell geht über die fünf vorgestellten Muster hinaus:\n\n- [Review Apps mit dynamischen Umgebungen](https://docs.gitlab.com/ci/environments/) erstellen für jeden Feature-Branch eine Live-Vorschau und räumen sie automatisch auf, wenn der MR geschlossen wird.\n- [Caching- und Artifact-Strategien](https://docs.gitlab.com/ci/caching/) sind nach der strukturellen Arbeit häufig der direkteste Weg zur weiteren Laufzeitoptimierung – ohne die Pipeline-Struktur zu verändern.\n- [Geplante und API-ausgelöste Pipelines](https://docs.gitlab.com/ci/pipelines/schedules/) eignen sich für Workloads, die nicht bei jedem Code-Push laufen sollten: Nightly-Security-Scans, Compliance-Reports und Release-Automatisierung lassen sich als geplante oder [API-ausgelöste](https://docs.gitlab.com/ci/triggers/) Pipelines mit `$CI_PIPELINE_SOURCE`-Routing modellieren.\n\n> [GitLab Ultimate kostenlos testen](https://about.gitlab.com/de-de/free-trial/) und Pipeline-Logik ab heute einsetzen.\n\n## Für deutsche Unternehmen: Regulatorischer Kontext\n\nTeams, die Pipeline-Governance nach Muster 5 einführen, adressieren dabei möglicherweise auch Anforderungen, die regulatorische Frameworks an sichere Softwareentwicklungsprozesse stellen.\n\nCI/CD Components mit erzwungenen Security-Gates könnten Anforderungen an sichere Entwicklungsprozesse betreffen – beispielsweise in Bereichen, die Frameworks wie NIS2, ISO 27001 oder BSI IT-Grundschutz an den Software-Entwicklungslebenszyklus adressieren. Protected Environments und Resource Groups betreffen ähnliche Themen im Bereich Änderungskontrolle und Umgebungstrennung, wie sie in Governance-Frameworks typischerweise explizit formuliert sind.\n\nMulti-Project-Pipelines mit API-Contract-Validierung (Muster 2) schaffen Sichtbarkeit über Service-Abhängigkeiten hinweg – ein Aspekt, den Frameworks zur Lieferkettensicherheit adressieren.\n\nMerged-Results-Pipelines (Muster 4) dokumentieren automatisch, dass das tatsächliche Merge-Ergebnis getestet wurde, nicht nur der Feature-Branch in Isolation. Dies könnte Anforderungen an nachvollziehbare Änderungsprozesse betreffen, wie sie in Change-Management-Kontrollen verschiedener Sicherheitsframeworks formuliert sind.\n\nFür konkrete Compliance-Anforderungen im eigenen regulatorischen Umfeld empfiehlt sich Rücksprache mit entsprechender Fachberatung.\n\n## Abschnitt 2: Konfiguration und Implementierung\n\n*Für Entwicklungsteams und DevOps-Praktiker: ausgewählte Konfigurationsbeispiele zu den Mustern 1, 4 und 5. Für vollständige Konfigurationen aller Muster: [englischer Originalartikel](https://about.gitlab.com/blog/5-ways-gitlab-pipeline-logic-solves-real-engineering-problems/).*\n\nDie folgenden Konfigurationen sind illustrativ aufgebaut. Die Skripte verwenden `echo`-Befehle, um das Wesentliche sichtbar zu halten. Für den produktiven Einsatz werden die `echo`-Befehle durch die tatsächlichen Build-, Test- und Deploy-Schritte ersetzt.\n\n### Muster 1: Parent-Child-Pipelines und DAG-Execution\n\nEine Parent-Pipeline erkennt Änderungen und löst nur die betroffenen Child-Pipelines aus:\n\n```yaml # .gitlab-ci.yml stages:\n  - trigger\n\ntrigger-services:\n  stage: trigger\n  trigger:\n    include:\n      - local: '.gitlab/ci/api-service.yml'\n      - local: '.gitlab/ci/web-service.yml'\n      - local: '.gitlab/ci/worker-service.yml'\n    strategy: depend\n```\n\nInnerhalb der Child-Pipeline ermöglicht `needs:` DAG-Execution – der Test startet, sobald der Build abgeschlossen ist, ohne auf andere Jobs in derselben Stage zu warten:\n\n```yaml # .gitlab/ci/api-service.yml stages:\n  - build\n  - test\n\nbuild-api:\n  stage: build\n  script:\n    - echo \"Building API service\"\n\ntest-api:\n  stage: test\n  needs: [build-api]\n  script:\n    - echo \"Running API tests\"\n```\n\n![Local downstream pipelines](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738759/Blog/Imported/hackathon-fake-blog-post-s/image3_vwj3rz.png \"Local downstream pipelines\")\n\n### Muster 4: MR-First-Delivery\n\nWorkflow-Rules, MR-Pipelines und Merged-Results zusammen ergeben ein kontextabhängiges Pipeline-Verhalten:\n\n```yaml # .gitlab-ci.yml workflow:\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS\n      when: never\n    - if: $CI_COMMIT_BRANCH\n    - if: $CI_PIPELINE_SOURCE == \"schedule\"\n\nstages:\n  - fast-checks\n  - expensive-tests\n  - deploy\n\nlint-code:\n  stage: fast-checks\n  script:\n    - echo \"Running linter\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"push\"\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\nunit-tests:\n  stage: fast-checks\n  script:\n    - echo \"Running unit tests\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"push\"\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\nintegration-tests:\n  stage: expensive-tests\n  script:\n    - echo \"Running integration tests (15 min)\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\ne2e-tests:\n  stage: expensive-tests\n  script:\n    - echo \"Running E2E tests (30 min)\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"merge_request_event\"\n    - if: $CI_COMMIT_BRANCH == \"main\"\n\nnightly-comprehensive-scan:\n  stage: expensive-tests\n  script:\n    - echo \"Running full nightly suite (2 hours)\"\n  rules:\n    - if: $CI_PIPELINE_SOURCE == \"schedule\"\n\ndeploy-production:\n  stage: deploy\n  script:\n    - echo \"Deploying to production\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == \"main\"\n      when: manual\n```\n\n![Conditional pipelines (within a branch with no MR)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738768/Blog/Imported/hackathon-fake-blog-post-s/image6_dnfcny.png \"Conditional pipelines (within a branch with no MR)\")\n\n![Conditional pipelines (within an MR)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738772/Blog/Imported/hackathon-fake-blog-post-s/image1_wyiafu.png \"Conditional pipelines (within an MR)\")\n\n![Conditional pipelines (on the main branch)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1775738774/Blog/Imported/hackathon-fake-blog-post-s/image5_r6lkfd.png \"Conditional pipelines (on the main branch)\")\n\n### Muster 5: CI/CD Components\n\nEine Komponentendefinition aus einer gemeinsamen Bibliothek:\n\n```yaml # templates/deploy.yml spec:\n  inputs:\n    stage:\n      default: deploy\n    environment:\n      default: production\n--- deploy-job:\n  stage: $[[ inputs.stage ]]\n  script:\n    - echo \"Deploying $APP_NAME to $[[ inputs.environment ]]\"\n    - echo \"Deploy URL: $DEPLOY_URL\"\n  environment:\n    name: $[[ inputs.environment ]]\n```\n\nSo bindet ein Anwendungsteam die Komponenten ein:\n\n```yaml # Application repo: .gitlab-ci.yml variables:\n  APP_NAME: \"my-awesome-app\"\n  DEPLOY_URL: \"https://api.example.com\"\n\ninclude:\n  - component: gitlab.com/my-org/component-library/build@v1.0.6\n  - component: gitlab.com/my-org/component-library/test@v1.0.6\n  - component: gitlab.com/my-org/component-library/deploy@v1.0.6\n    inputs:\n      environment: staging\n\nstages:\n  - build\n  - test\n  - deploy\n```\n\n### Orientierung zu den Mustern 2 und 3\n\n**Muster 2 (Multi-Project-Pipelines):** Das Frontend-Repository publiziert ein API-Contract-Artifact und löst anschließend die Backend-Pipeline aus. Das Backend ruft das Artifact über die GitLab Jobs API ab und validiert es. Der `integration-test`-Job läuft dabei nur dann, wenn er von einer Upstream-Pipeline ausgelöst wurde (`$CI_PIPELINE_SOURCE == \"pipeline\"`), nicht bei einem eigenständigen Push. Die Frontend-Projekt-ID wird als CI/CD-Variable gesetzt, um Hardcoding zu vermeiden. Vollständige Konfigurationen beider Repositories: [englischer Originalartikel](https://about.gitlab.com/blog/5-ways-gitlab-pipeline-logic-solves-real-engineering-problems/#2-microservices-cross-repo-multi-project-pipelines).\n\n**Muster 3 (Dynamische Child-Pipelines):** Ein `generate-config`-Job erzeugt zur Laufzeit environment-spezifische YAML-Dateien. Trigger-Jobs nutzen `extends:` für gemeinsam genutzte Konfiguration und `needs:` für sequenzielle Promotion (dev → staging → prod mit manuellem Gate). Vollständige Konfiguration: [englischer Originalartikel](https://about.gitlab.com/blog/5-ways-gitlab-pipeline-logic-solves-real-engineering-problems/#3-multi-tenant--matrix-deployments-dynamic-child-pipelines).\n\n## Weiterführende Artikel\n\n- [Variable and artifact sharing in GitLab parent-child pipelines](https://about.gitlab.com/blog/variable-and-artifact-sharing-in-gitlab-parent-child-pipelines/)\n- [CI/CD inputs: Secure and preferred method to pass parameters to a pipeline](https://about.gitlab.com/blog/ci-cd-inputs-secure-and-preferred-method-to-pass-parameters-to-a-pipeline/)\n- [Tutorial: How to set up your first GitLab CI/CD component](https://about.gitlab.com/blog/tutorial-how-to-set-up-your-first-gitlab-ci-cd-component/)\n- [How to include file references in your CI/CD components](https://about.gitlab.com/blog/how-to-include-file-references-in-your-ci-cd-components/)\n- [FAQ: GitLab CI/CD Catalog](https://about.gitlab.com/blog/faq-gitlab-ci-cd-catalog/)\n- [Building a GitLab CI/CD pipeline for a monorepo the easy way](https://about.gitlab.com/blog/building-a-gitlab-ci-cd-pipeline-for-a-monorepo-the-easy-way/)\n- [A CI/CD component builder's journey](https://about.gitlab.com/blog/a-ci-component-builders-journey/)\n- [CI/CD Catalog goes GA: No more building pipelines from scratch](https://about.gitlab.com/blog/ci-cd-catalog-goes-ga-no-more-building-pipelines-from-scratch/)","5 GitLab-Pipeline-Muster für komplexe Engineering-Herausforderungen","Wie Parent-Child-Pipelines, DAG-Execution, MR-Pipelines und CI/CD Components komplexe Delivery-Probleme lösen – von Monorepos bis zur governed Plattform.",[734],"Omid Khan","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772721753/frfsm1qfscwrmsyzj1qn.png","2026-04-09",[109,738,23,739],"DevOps platform","features",{"featured":28,"template":15,"slug":741},"5-ways-gitlab-pipeline-logic-solves-real-engineering-problems",{"content":743,"config":752},{"title":744,"description":745,"authors":746,"heroImage":748,"date":749,"body":750,"category":11,"tags":751},"GitLab Container Virtual Registry mit Docker Hardened Images einrichten","Mehrere Registries hinter einem Endpunkt – GitLab Container Virtual Registry mit Docker Hardened Images, Caching und Audit-Trail.",[747],"Tim Rizzi","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772111172/mwhgbjawn62kymfwrhle.png","2026-03-12","Wer im Plattformteam arbeitet, kennt solche Gespräche:\n\n*„Security sagt: Wir müssen gehärtete Base-Images verwenden.\"*\n\n*„Prima – wo trage ich jetzt die Credentials für noch eine weitere Registry ein?\"*\n\n*„Und wie stellen wir sicher, dass alle sie auch wirklich nutzen?\"*\n\nOder diese hier:\n\n*„Warum sind unsere Builds so langsam?\"*\n\n*„Wir pullen dasselbe 500-MB-Image in jedem einzelnen Job neu von Docker Hub.\"*\n\n*„Kann man die nicht irgendwo cachen?\"*\n\nIch arbeite bei GitLab an der [Container Virtual Registry](https://docs.gitlab.com/user/packages/virtual_registry/container/) – einem Pull-Through-Cache, der vor den vorgelagerten Registries sitzt: Docker Hub, dhi.io (Docker Hardened Images), MCR und Quay. Teams erhalten einen einzigen Endpunkt zum Pullen. Images werden beim ersten Abruf gecacht; alle nachfolgenden Pulls kommen aus dem Cache. Das Entwicklungsteam muss nicht wissen, aus welchem Upstream ein bestimmtes Image stammt.\n\nDieser Artikel zeigt die Einrichtung der Container Virtual Registry – mit Docker Hardened Images als konkretem Anwendungsfall, da diese Kombination für Teams mit Sicherheitsanforderungen besonders naheliegt.\n\n## Das Problem: Registry-Wildwuchs im Plattformteam\n\nDie Plattformteams, mit denen ich spreche, verwalten Container-Images über drei bis fünf Registries:\n\n- **Docker Hub** für die meisten Base-Images\n- **dhi.io** für Docker Hardened Images (sicherheitskritische Workloads)\n- **MCR** für .NET- und Azure-Tooling\n- **Quay.io** für das Red-Hat-Ökosystem\n- **Interne Registries** für proprietäre Images\n\nJede davon hat eigene Authentifizierungsmechanismen, unterschiedliche Netzwerklatenz und eine eigene Pfadstruktur für Images.\n\nCI/CD-Konfigurationen füllen sich mit registry-spezifischer Logik. Credential-Management wird zum eigenständigen Projekt. Und jeder Pipeline-Job lädt dieselben Base-Images erneut über das Netz – obwohl sie sich seit Wochen nicht geändert haben.\n\nContainer Virtual Registry konsolidiert das: eine Registry-URL, ein Authentifizierungsfluss über GitLab, gecachte Images aus GitLab-Infrastruktur statt wiederholter Internet-Traversierung.\n\n## Funktionsweise\n\nDas Modell ist geradlinig:\n\n```text\n\nPipeline ruft ab:\n  gitlab.com/virtual_registries/container/1000016/python:3.13\n\nVirtual Registry prüft:\n  1. Im Cache vorhanden? → Direkt zurückgeben\n  2. Nein? → Vom Upstream laden, cachen, zurückgeben\n\n\n```\n\nUpstreams werden in Prioritätsreihenfolge konfiguriert. Bei einem eingehenden Pull-Request durchsucht die Virtual Registry die Upstreams der Reihe nach, bis das Image gefunden wird. Das Ergebnis wird für einen konfigurierbaren Zeitraum gecacht – standardmäßig 24 Stunden.\n\n\n```text\n\n┌─────────────────────────────────────────────────────────┐ │                    CI/CD Pipeline                       │ │                          │                              │ │                          ▼                              │ │   gitlab.com/virtual_registries/container/\u003Cid>/image   │ └─────────────────────────────────────────────────────────┘\n                           │\n                           ▼\n┌─────────────────────────────────────────────────────────┐ │            Container Virtual Registry                   │ │                                                         │ │  Upstream 1: Docker Hub ────────────────┐               │ │  Upstream 2: dhi.io (Hardened) ────────┐│               │ │  Upstream 3: MCR ─────────────────────┐││               │ │  Upstream 4: Quay.io ────────────────┐│││               │ │                                      ││││               │ │                    ┌─────────────────┴┴┴┴──┐            │ │                    │        Cache          │            │ │                    │  (manifests + layers) │            │ │                    └───────────────────────┘            │ └─────────────────────────────────────────────────────────┘\n\n```\n\n## Was das konkret bringt – besonders mit Docker Hardened Images\n\n[Docker Hardened Images](https://docs.docker.com/dhi/) zeichnen sich durch minimale Angriffsfläche, nahezu keine bekannten CVEs, vollständige Software Bills of Materials (SBOMs) und SLSA-Provenance aus. Für Teams, die Base-Images für sicherheitskritische Workloads evaluieren, gehören sie auf die Shortlist.\n\nDer Wechsel zu dhi.io erzeugt jedoch dieselbe operative Reibung wie jede neue Registry:\n\n- **Credential-Verteilung**: Docker-Credentials müssen auf alle Systeme verteilt werden, die Images von dhi.io abrufen.\n- **CI/CD-Anpassungen**: Jede Pipeline muss für die Authentifizierung mit dhi.io aktualisiert werden.\n- **Akzeptanzproblem**: Ohne zentrale Steuerung greifen Teams weiterhin auf reguläre Images zurück.\n- **Fehlende Transparenz**: Ob Teams tatsächlich die gehärteten Varianten nutzen, ist kaum nachvollziehbar.\n\nDie Virtual Registry löst jeden dieser Punkte:\n\n**Einzelne Credential**: Teams authentifizieren sich bei GitLab. Die Virtual Registry übernimmt die Upstream-Authentifizierung. Docker-Credentials werden einmalig auf Registry-Ebene konfiguriert und gelten für alle Pulls.\n\n**Keine per-Team-CI/CD-Änderungen**: Pipelines auf die Virtual Registry zeigen lassen – fertig. Die Upstream-Konfiguration ist zentralisiert.\n\n**Schrittweise Einführung**: Da Images mit ihrem vollständigen Pfad gecacht werden, ist im Cache sichtbar, was tatsächlich abgerufen wird. Wird `library/python:3.11` statt der gehärteten Variante gepullt, ist das erkennbar.\n\n**Audit-Trail**: Der Cache zeigt exakt, welche Images aktiv genutzt werden – nachvollziehbar für Compliance-Zwecke und als Grundlage für das Verständnis der tatsächlichen Infrastruktur-Abhängigkeiten.\n\nWer das Konzept verstanden hat und die Einrichtung zu einem späteren Zeitpunkt in Angriff nimmt: Die wesentlichen Konzepte sind damit abgedeckt. Die technische Konfiguration folgt im nächsten Abschnitt.\n\n## Einrichtung\n\nDie folgende Einrichtung nutzt den Python-Client aus dem Demo-Projekt.\n\n### Virtual Registry erstellen\n\n```python\nfrom virtual_registry_client import VirtualRegistryClient\nclient = VirtualRegistryClient()\nregistry = client.create_virtual_registry(\n    group_id=\"785414\",  # ID der obersten Gruppe\n    name=\"platform-images\",\n    description=\"Cached container images for platform teams\"\n)\nprint(f\"Registry ID: {registry['id']}\") # Diese ID wird für die Pull-URL benötigt\n```\n\n### Docker Hub als Upstream hinzufügen\n\nFür offizielle Images wie Alpine, Python usw.:\n\n```python\n\ndocker_upstream = client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://registry-1.docker.io\",\n    name=\"Docker Hub\",\n    cache_validity_hours=24\n)\n\n```\n\n### Docker Hardened Images (dhi.io) hinzufügen\n\nDocker Hardened Images werden auf `dhi.io` gehostet – einer separaten Registry mit Authentifizierungspflicht:\n\n```python\n\ndhi_upstream = client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://dhi.io\",\n    name=\"Docker Hardened Images\",\n    username=\"your-docker-username\",\n    password=\"your-docker-access-token\",\n    cache_validity_hours=24\n)\n\n```\n\n### Weitere Upstreams hinzufügen\n\n```python\n\n# MCR für .NET-Teams client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://mcr.microsoft.com\",\n    name=\"Microsoft Container Registry\",\n    cache_validity_hours=48\n)\n# Quay für das Red-Hat-Ökosystem client.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://quay.io\",\n    name=\"Quay.io\",\n    cache_validity_hours=24\n)\n\n```\n\n### CI/CD aktualisieren\n\nEine `.gitlab-ci.yml`, die über die Virtual Registry pullt:\n\n```yaml\n\nvariables:\n  VIRTUAL_REGISTRY_ID: \u003Cyour_virtual_registry_ID>\n\n  \nbuild:\n  image: docker:24\n  services:\n    - docker:24-dind\n  before_script:\n    # Authentifizierung bei GitLab – Upstream-Auth wird übernommen\n    - echo \"${CI_JOB_TOKEN}\" | docker login -u gitlab-ci-token --password-stdin gitlab.com\n  script:\n    # Alle Pulls laufen über die zentrale Virtual Registry\n    \n    # Offizielle Docker Hub Images (library/-Präfix erforderlich)\n    - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/library/alpine:latest\n    \n    # Docker Hardened Images von dhi.io (kein Präfix nötig)\n    - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/python:3.13\n    \n    # .NET von MCR\n    - docker pull gitlab.com/virtual_registries/container/${VIRTUAL_REGISTRY_ID}/dotnet/sdk:8.0\n\n\n```\n\n### Image-Pfadformate\n\nVerschiedene Registries verwenden unterschiedliche Pfadkonventionen:\n\n| Registry | Beispiel-Pull-URL |\n|----------|-------------------|\n| Docker Hub (offiziell) | `.../library/python:3.11-slim` |\n| Docker Hardened Images (dhi.io) | `.../python:3.13` |\n| MCR | `.../dotnet/sdk:8.0` |\n| Quay.io | `.../prometheus/prometheus:latest` |\n\n### Funktionsprüfung\n\nNach einigen Pulls lässt sich der Cache überprüfen:\n\n```python\n\nupstreams = client.list_registry_upstreams(registry['id']) for upstream in upstreams:\n    entries = client.list_cache_entries(upstream['id'])\n    print(f\"{upstream['name']}: {len(entries)} cached entries\")\n\n\n```\n\n## Messergebnisse\n\nTestergebnisse beim Pullen über die Virtual Registry:\n\n| Messgröße | Ohne Cache | Mit warmem Cache |\n|-----------|------------|-----------------|\n| Pull-Zeit (Alpine) | 10,3 s | 4,2 s |\n| Pull-Zeit (Python 3.13 DHI) | 11,6 s | ~4 s |\n| Netzwerk-Roundtrips zum Upstream | Jeder Pull | Nur Cache-Misses |\n\nDer erste Pull hat dieselbe Dauer – das Image muss vom Upstream geladen werden. Jeder weitere Pull innerhalb der Cache-Gültigkeitsdauer kommt direkt aus GitLab-Storage: kein Netzwerk-Hop zu Docker Hub, dhi.io, MCR oder einer anderen Registry.\n\nBei Teams mit vielen Pipeline-Jobs pro Tag summiert sich das zu einem messbaren Gewinn bei den Build-Laufzeiten.\n\n## Praktische Hinweise\n\n### Cache-Gültigkeit\n\nDer Standard sind 24 Stunden. Für sicherheitskritische Images, bei denen Patches schnell verfügbar sein sollen, empfiehlt sich ein kürzeres Intervall:\n\n```python\n\nclient.create_upstream(\n    registry_id=registry['id'],\n    url=\"https://dhi.io\",\n    name=\"Docker Hardened Images\",\n    username=\"your-username\",\n    password=\"your-token\",\n    cache_validity_hours=12\n)\n\n```\n\nFür stabile Images mit fixen Versions-Tags ist ein längeres Intervall problemlos.\n\n### Upstream-Priorität\n\nUpstreams werden der Reihe nach geprüft. Bei gleichnamigen Images in verschiedenen Registries gewinnt der erste passende Upstream.\n\n### Limits\n\n- Maximal 20 Virtual Registries pro Gruppe\n- Maximal 20 Upstreams pro Virtual Registry\n\n## Konfiguration über die Oberfläche\n\nVirtual Registries und Upstreams lassen sich auch direkt in der GitLab-Oberfläche einrichten – ohne API-Aufrufe. Unter **Einstellungen > Pakete und Registries > Virtual Registry** der jeweiligen Gruppe stehen folgende Optionen zur Verfügung:\n\n- Virtual Registries erstellen und verwalten\n- Upstreams hinzufügen, bearbeiten und neu anordnen\n- Cache anzeigen und verwalten\n- Überblick, welche Images abgerufen werden\n\n## Ausblick\n\nIn Entwicklung:\n\n- **Allow/Deny-Listen**: Regex-basierte Steuerung, welche Images aus welchen Upstreams abgerufen werden dürfen.\n\nContainer Virtual Registry befindet sich in der Beta-Phase. Die Funktion wird produktiv eingesetzt und wird weiterentwickelt – Feedback fließt direkt in die Priorisierung ein.\n\n## Feedback\n\nWer als Plattformteam mit Registry-Wildwuchs zu kämpfen hat: Ich möchte verstehen, wie die aktuelle Situation aussieht.\n\n- Wie viele Upstream-Registries werden verwaltet?\n- Wo liegt der größte Schmerzpunkt?\n- Würde ein solcher Ansatz helfen – und falls nicht: Was fehlt?\n\nErfahrungen und Rückmeldungen gerne im [Container Virtual Registry Feedback-Issue](https://gitlab.com/gitlab-org/gitlab/-/work_items/589630) teilen.\n\n## Weiterführende Ressourcen\n\n- [Neue GitLab-Metriken und Registry-Funktionen zur Optimierung von CI/CD-Pipelines](https://about.gitlab.com/de-de/blog/new-gitlab-metrics-and-registry-features-help-reduce-ci-cd-bottlenecks/)\n- [Container Virtual Registry – Dokumentation](https://docs.gitlab.com/user/packages/virtual_registry/container/)\n- [Container Virtual Registry – API](https://docs.gitlab.com/api/container_virtual_registries/)\n\n## Für deutsche Unternehmen könnte dies folgende Themen betreffen\n\nTeams, die sicherheitsgehärtete Base-Images mit vollständigen SBOMs und SLSA-Provenance einsetzen, haben möglicherweise auch Compliance-Überlegungen – beispielsweise in Bereichen wie Sicherheit der Software-Lieferkette, Nachvollziehbarkeit von Image-Abhängigkeiten und zentralem Audit-Trail.\n\nRegulatorische Frameworks wie NIS2 und der Cyber Resilience Act adressieren ähnliche Themen rund um Software-Lieferketten und SBOM-Transparenz. Für konkrete Compliance-Anforderungen empfiehlt sich Rücksprache mit entsprechender Fachberatung.",[23,725,739],{"featured":14,"template":15,"slug":753},"using-gitlab-container-virtual-registry-with-docker-hardened-images",{"promotions":755},[756,770,781,793],{"id":757,"categories":758,"header":760,"text":761,"button":762,"image":767},"ai-modernization",[759],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":763,"config":764},"Get your AI maturity score",{"href":765,"dataGaName":766,"dataGaLocation":246},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":768},{"src":769},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":771,"categories":772,"header":773,"text":761,"button":774,"image":778},"devops-modernization",[725,571],"Are you just managing tools or shipping innovation?",{"text":775,"config":776},"Get your DevOps maturity score",{"href":777,"dataGaName":766,"dataGaLocation":246},"/assessments/devops-modernization-assessment/",{"config":779},{"src":780},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":782,"categories":783,"header":785,"text":761,"button":786,"image":790},"security-modernization",[784],"security","Are you trading speed for security?",{"text":787,"config":788},"Get your security maturity score",{"href":789,"dataGaName":766,"dataGaLocation":246},"/assessments/security-modernization-assessment/",{"config":791},{"src":792},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":794,"paths":795,"header":798,"text":799,"button":800,"image":805},"github-azure-migration",[796,797],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":801,"config":802},"See how GitLab compares to GitHub",{"href":803,"dataGaName":804,"dataGaLocation":246},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":806},{"src":780},{"header":808,"blurb":809,"button":810,"secondaryButton":815},"Beginne noch heute, schneller zu entwickeln","Entdecke, was dein Team mit der intelligenten Orchestrierungsplattform für DevSecOps erreichen kann.\n",{"text":811,"config":812},"Kostenlosen Test starten",{"href":813,"dataGaName":51,"dataGaLocation":814},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/de-de/","feature",{"text":53,"config":816},{"href":55,"dataGaName":56,"dataGaLocation":814},1777493578568]