[{"data":1,"prerenderedAt":822},["ShallowReactive",2],{"/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments":3,"navigation-de-de":46,"banner-de-de":459,"footer-de-de":469,"blog-post-authors-de-de-Olivier Dupré":704,"blog-related-posts-de-de-using-child-pipelines-to-continuously-deploy-to-five-environments":719,"blog-promotions-de-de":759,"next-steps-de-de":812},{"id":4,"title":5,"authorSlugs":6,"authors":8,"body":10,"category":11,"categorySlug":11,"config":12,"content":16,"date":20,"description":17,"extension":28,"externalUrl":29,"featured":14,"heroImage":19,"isFeatured":14,"meta":30,"navigation":31,"path":32,"publishedDate":20,"rawbody":33,"seo":34,"slug":13,"stem":38,"tagSlugs":39,"tags":44,"template":15,"updatedDate":27,"__hash__":45},"blogPosts/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments.yml","Kontinuierliche Bereitstellung in fünf Umgebungen mithilfe von untergeordneten Pipelines",[7],"olivier-dupr",[9],"Olivier Dupré","Manchmal brauchen DevSecOps-Teams die Möglichkeit, die kontinuierliche Bereitstellung über mehrere Umgebungen übergreifend zu verwalten, ohne dabei ihre Workflows zu verändern. Die [DevSecOps-Plattform von GitLab](https://about.gitlab.com/de-de/) macht dies mit einem minimalistischen Ansatz möglich, unter anderem für temporäre, sofort einsatzbereite Sandboxes. In diesem Artikel erfährst du, wie du die kontinuierliche Bereitstellung der Infrastruktur mit Terraform in verschiedenen Umgebungen ausführen kannst.\n\n> **Nahezu 100 % Uptime: NVIDIA skaliert mit GitLab global – ohne Ausfallzeiten** Verteilte Teams bei NVIDIA profitieren mit GitLab Geo von schneller verfügbaren Repositories, häufigeren Upgrades ohne Ausfallzeiten und global-synchronen sowie -sicheren Projekten. Erfahre, wie GitLab Premium hilft, Stabilität, Transparenz und Effizienz zu verbessern. **[Erfolgsstory lesen](https://about.gitlab.com/de-de/customers/nvidia/)**\n\nDiese Strategie kann einfach auf andere Projekte umgesetzt werden, egal, ob es sich um Infrastructure as Code (IaC), die auf einer anderen Technologie wie [Pulumi](https://www.pulumi.com/) oder [Ansible](https://www.ansible.com/) basiert, um Quellcode in beliebigen Sprachen oder ein Monorepo handelt, bei dem viele Sprachen gemischt verwendet werden.\n\nDie letzte Pipeline, die du am Ende dieses Tutorials hast, stellt Folgendes bereit:\n\n* Eine temporäre **Review-Umgebung** für jeden Feature-Branch.\n* Eine **Integrationsumgebung**, die einfach zu löschen und vom Haupt-Branch aus bereitzustellen ist.\n* Eine **QA-Umgebung**, die ebenfalls von dem Haupt-Branch bereitgestellt wird, um Qualitätssicherungsschritte durchzuführen.\n* Eine **Staging-Umgebung**, die für jedes Tag bereitgestellt wird. Dies ist die letzte Runde vor der Produktion.\n* Eine **Produktionsumgebung**, die direkt nach der Staging-Umgebung folgt. Diese wird zur Demonstration manuell ausgelöst, kann aber auch kontinuierlich bereitgestellt werden.\n\n>Hier findest du die Legende für die Flussdiagramme in diesem Artikel:\n> * Runde Boxen sind die GitLab-Branches.\n> * Eckige Boxen sind die Umgebungen.\n> * Der Text auf den Pfeilen sind die Aktionen, die von einem Feld zum nächsten fließen sollen.\n> * Eckige Quadrate sind Entscheidungsschritte.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\n    D -->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\n    D -->|tag| G(X.Y.Z)\n    F -->|validate| G\n\n    G -->|auto deploy| H[staging]\n    H -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\nBei jedem Schritt erfährst du das [Warum](#why) und das [Was](#what), bevor du zum [Wie](#how) übergehst. Dies wird dir helfen, dieses Tutorial vollständig zu verstehen und zu replizieren.\n\n## Warum\n\n* [Kontinuierliche Integration](https://about.gitlab.com/de-de/topics/ci-cd/#what-is-continuous-integration-ci) ist fast ein De-facto-Standard. Die meisten Unternehmen haben CI-Pipelines implementiert oder sind bereit, ihre Arbeitsweise zu standardisieren.\n\n* [Kontinuierliche Bereitstellung](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-delivery-cd), wobei Artefakte in ein Repository oder eine Registry am Ende der CI-Pipeline gepusht werden, ist ebenfalls beliebt.\n\n* Kontinuierliche Bereitstellung, die weiter geht und diese Artefakte automatisch bereitstellt, ist allerdings weniger verbreitet. Wenn, dann wird sie vor allem im Bereich von Anwendungen implementiert. Wenn es um die kontinuierliche Bereitstellung von Infrastruktur geht, scheint das Bild weniger klar zu sein und es dreht sich vieles um das Management mehrerer Umgebungen. Im Gegensatz dazu scheint das Testen, Sichern und Überprüfen des Infrastruktur-Codes schwieriger zu sein. Dies ist eines der Felder, in denen DevOps noch nicht ausgereift ist. Ein anderer Anwendungsbereich ist, die Sicherheit im Vorfeld zu kontrollieren und Sicherheitsteams sowie – was noch wichtiger ist – Sicherheitsbedenken früher in den Lebenszyklus der Bereitstellung zu integrieren und so von DevOps auf ***DevSecOps*** upzugraden.\n\nAngesichts dessen wirst du in diesem Tutorial eine einfache und doch effiziente Möglichkeit erarbeiten, DevSecOps für deine Infrastruktur zu implementieren, indem du beispielsweise Ressourcen in fünf Umgebungen bereitstellst und dich schrittweise von der Entwicklung bis zur Produktion vorarbeitest.\n\n__Hinweis:__ Auch wenn ich einen FinOps-Ansatz und eine Reduktion der Umgebungen befürworte, gibt es manchmal gute Gründe, mehr als nur Entwicklung, Staging und Produktion aufrechtzuerhalten. Bitte passe die folgenden Beispiele an deine Bedürfnisse an.\n\n## Was\n\nDer Aufstieg der Cloud-Technologie hat die Nutzung von IaC vorangetrieben. Ansible und Terraform gehörten in diesem Bereich zu den Pionieren. OpenTofu, Pulumi, AWS CDK, Google Deploy Manager und viele andere folgten.\n\nIaC gilt als perfekte Lösung, um sich bei der Bereitstellung von Infrastruktur sicher zu fühlen. Du kannst sie testen, bereitstellen und immer wieder abspielen, bis du dein Ziel erreicht hast.\n\nLeider sehen wir oft, dass Unternehmen für jede ihrer Zielumgebungen mehrere Branches oder sogar Repositories unterhalten. Und hier beginnen die Probleme. Sie setzen einen Prozess nicht mehr durch. Sie stellen nicht mehr sicher, dass Änderungen in der Produktions-Codebase in früheren Umgebungen genauestens getestet wurden. Und sie beginnen, Drifts von einer Umgebung in die andere zu erleben.\n\nMir wurde klar, dass dieses Tutorial notwendig war, als auf einer Konferenz alle Teilnehmenden sagten, dass sie keinen Workflow haben, der durchsetzt, dass Infrastruktur genau getestet wird, bevor sie für die Produktion bereitgestellt wird. Und sie waren sich alle einig, dass sie manchmal den Code direkt in die Produktion patchen. Klar geht das schnell, aber ist es auch sicher? Wie meldet man an frühere Umgebungen zurück? Wie stellt man sicher, dass es keine Nebeneffekte gibt? Wie kontrolliert man, ob man das Unternehmen in Gefahr bringt, wenn neue Sicherheitslücken zu schnell in die Produktion gepusht werden?\n\nDie Frage, *warum* DevOps-Teams direkt in die Produktion implementieren, ist hier entscheidend. Liegt es daran, dass die Pipeline effizienter oder schneller sein könnte? Gibt es keine Automatisierung? Oder, noch schlimmer, gibt es *keine Möglichkeit, außerhalb der Produktion genau zu testen*?\n\nIm nächsten Abschnitt erfährst du, wie du Automatisierung für deine Infrastruktur implementieren und sicherstellen kannst, dass dein DevOps-Team effektiv testet, bevor etwas in eine Umgebung gepusht wird, die sich auf andere auswirkt. Du wirst sehen, wie dein Code gesichert und seine Bereitstellung durchgehend kontrolliert wird.\n\n## Wie\n\nWie bereits erwähnt, gibt es heutzutage viele Programmiersprachen für IaC, und wir können ganz einfach nicht *alle* in einem einzigen Artikel behandeln. Ich werde mich also auf einen grundlegenden Terraform-Code konzentrieren, der auf Version 1.4 läuft. Bitte fixiere dich nicht auf die Programmiersprache für IaC selbst, sondern auf den Prozess, den du für dein eigenes Ökosystem umsetzen kannst.\n\n### Der Terraform-Code\n\nBeginnen wir mit einem grundlegenden Terraform-Code.\n\nWir werden auf AWS bereitstellen, eine virtuelle private Cloud (VPC), die ein virtuelles Netzwerk ist. In dieser VPC werden wir ein öffentliches und ein privates Subnetz bereitstellen. Wie der Name schon sagt, handelt es sich um Subnetze der Haupt-VPC. Abschließend fügen wir eine EC2-Instanz (Elastic Cloud Compute; eine virtuelle Maschine) zum öffentlichen Subnetz hinzu.\n\nDies zeigt, wie vier Ressourcen bereitgestellt werden können, ohne zu komplex zu werden. Die Idee ist, sich auf die Pipeline zu konzentrieren, nicht auf den Code.\n\nHier ist das Ziel, das wir für dein Repository erreichen möchten.\n\n![Ziel für Repository](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750097033415.png)\n\nGehen wir Schritt für Schritt vor.\n\nZuerst deklarieren wir alle Ressourcen in der Datei `terraform/main.tf`:\n\n```terraform\nprovider \"aws\" {\n  region = var.aws_default_region\n}\n\nresource \"aws_vpc\" \"main\" {\n  cidr_block = var.aws_vpc_cidr\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n\nresource \"aws_subnet\" \"public_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_public_subnet_cidr\n\n  tags = {\n    Name = \"Public Subnet\"\n  }\n}\nresource \"aws_subnet\" \"private_subnet\" {\n  vpc_id     = aws_vpc.main.id\n  cidr_block = var.aws_private_subnet_cidr\n\n  tags = {\n    Name = \"Private Subnet\"\n  }\n}\n\nresource \"aws_instance\" \"sandbox\" {\n  ami           = var.aws_ami_id\n  instance_type = var.aws_instance_type\n\n  subnet_id = aws_subnet.public_subnet.id\n\n  tags = {\n    Name     = var.aws_resources_name\n  }\n}\n```\n\nWie du sehen kannst, sind für diesen Code einige Variablen erforderlich, die wir also in der Datei `terraform/variables.tf` deklarieren:\n\n```terraform\nvariable \"aws_ami_id\" {\n  description = \"The AMI ID of the image being deployed.\"\n  type        = string\n}\n\nvariable \"aws_instance_type\" {\n  description = \"The instance type of the VM being deployed.\"\n  type        = string\n  default     = \"t2.micro\"\n}\n\nvariable \"aws_vpc_cidr\" {\n  description = \"The CIDR of the VPC.\"\n  type        = string\n  default     = \"10.0.0.0/16\"\n}\n\nvariable \"aws_public_subnet_cidr\" {\n  description = \"The CIDR of the public subnet.\"\n  type        = string\n  default     = \"10.0.1.0/24\"\n}\n\nvariable \"aws_private_subnet_cidr\" {\n  description = \"The CIDR of the private subnet.\"\n  type        = string\n  default     = \"10.0.2.0/24\"\n}\n\nvariable \"aws_default_region\" {\n  description = \"Default region where resources are deployed.\"\n  type        = string\n  default     = \"eu-west-3\"\n}\n\nvariable \"aws_resources_name\" {\n  description = \"Default name for the resources.\"\n  type        = string\n  default     = \"demo\"\n}\n```\n\nAuf der IaC-Seite sind wir damit auch schon fast fertig. Was fehlt, ist eine Möglichkeit, die Terraform-Zustände zu teilen. Für diejenigen, die es nicht wissen: Terraform funktioniert schematisch wie folgt:\n\n* `plan` überprüft die Unterschiede zwischen dem aktuellen Status der Infrastruktur und dem, was im Code definiert ist. Dann gibt es die Unterschiede aus.\n* `apply` wendet die Unterschiede im `plan` an und aktualisiert den Status.\n\nIn der ersten Runde ist der Status leer, dann wird er mit den Details (ID usw.) der von Terraform angewendeten Ressourcen gefüllt.\n\nDas Problem ist: Wo wird dieser Zustand gespeichert? Wie können wir ihn teilen, damit mehrere Entwickler(innen) am Code zusammenarbeiten können?\n\nDie Lösung ist ziemlich einfach: Nutze GitLab, um den Status über ein [Terraform-HTTP-Backend](https://docs.gitlab.com/user/infrastructure/iac/ /terraform_state.html) zu speichern und freizugeben.\n\nDer erste Schritt bei der Verwendung dieses Backends besteht darin, die einfachste Datei, nämlich `terraform/backend.tf` zu erstellen. Der zweite Schritt erfolgt in der Pipeline.\n\n```terraform\nterraform {\n  backend \"http\" {\n  }\n}\n```\n\nEt voilà! Wir haben einen minimalen Terraform-Code, um diese vier Ressourcen bereitzustellen. Wir werden die Variablenwerte zur Laufzeit bereitstellen, also machen wir das später.\n\n### Der Workflow\n\nDer Workflow, den wir jetzt implementieren werden, sieht folgendermaßen aus:\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\n    D -->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\n    D -->|tag| G(X.Y.Z)\n    F -->|validate| G\n\n    G -->|auto deploy| H[staging]\n    H -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n1. Erstelle einen **Feature-Branch**. Dadurch werden alle Scanner kontinuierlich auf dem Code ausgeführt, um sicherzustellen, dass er immer konform und gesichert bleibt. Dieser Code wird kontinuierlich in der temporären Umgebung `review/feature_branch` mit dem Namen des aktuellen Branches bereitgestellt. Dies ist eine sichere Umgebung, in der die Entwickler(innen) und IT-Betriebsteams ihren Code ohne Auswirkungen auf andere testen können. Hier setzen wir auch den Prozess durch, z. B. indem Code Reviews durchgesetzt und Scanner ausgeführt werden, damit die Qualität und Sicherheit des Codes akzeptabel sind und deine Assets nicht gefährdet werden. Die von diesem Branch bereitgestellte Infrastruktur wird automatisch zerstört, wenn der Branch geschlossen wird. So behältst du dein Budget unter Kontrolle.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    A(main) -->|new feature| B(feature_X)\n\n    B -->|auto deploy| C[review/feature_X]\n    B -->|merge| D(main)\n    C -->|destroy| D\n\u003C/pre>\n\n2. Nach der Genehmigung wird der Feature-Branch mit dem Main-Branch **zusammengeführt**. Dies ist ein [geschützter Branch](https://docs.gitlab.com/user/project/protected_branches/), in den niemand pushen kann. Dies ist obligatorisch, um sicherzustellen, dass jede Änderungsanfrage an die Produktion gründlich getestet wird. Dieser Branch wird auch kontinuierlich bereitgestellt. Das Ziel hier ist die Umgebung `integration`. Damit diese Umgebung etwas stabiler bleibt, wird das Löschen nicht automatisiert, sondern kann manuell ausgelöst werden.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main) -->|auto deploy| E[integration]\n\u003C/pre>\n\n3. Von dort aus ist eine manuelle Genehmigung erforderlich, um die nächste Bereitstellung auszulösen. Dadurch wird der Haupt-Branch in der Umgebung `qa` bereitgestellt. Hier habe ich eine Regel festgelegt, um das Löschen aus der Pipeline zu verhindern. Diese Umgebung sollte ziemlich stabil sein (schließlich ist es bereits die dritte Umgebung), und ich möchte das versehentliche Löschen verhindern. Du kannst die Regeln gerne an deine Prozesse anpassen.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main)-->|auto deploy| E[integration]\n    E -->|manual| F[qa]\n\u003C/pre>\n\n4. Um fortzufahren, müssen wir den Code **taggen**. Wir setzen hier auf [geschützte Tags](https://docs.gitlab.com/user/project/protected_tags/), um sicherzustellen, dass nur eine bestimmte Gruppe von Benutzer(inne)n in diese letzten beiden Umgebungen bereitstellen darf. Dadurch wird sofort eine Bereitstellung in der Umgebung `staging` ausgelöst.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    D(main) -->|tag| G(X.Y.Z)\n    F[qa] -->|validate| G\n\n    G -->|auto deploy| H[staging]\n\u003C/pre>\n\n\n5. Schließlich landen wir bei `production`. Wenn es um Infrastruktur geht, ist es oft schwierig, sie schrittweise bereitzustellen (10 %, 25 % usw.), sodass wir gleich die gesamte Infrastruktur bereitstellen werden. Dennoch steuern wir diese Bereitstellung, indem dieser letzte Schritt manuell ausgelöst wird. Und um maximale Kontrolle über diese hochkritische Umgebung zu erzwingen, werden wir sie als [geschützte Umgebung](https://docs.gitlab.com/ci/environments/protected_environments/ .html) kontrollieren.\n\n\u003Cpre class=\"mermaid\">\nflowchart LR\n    H[staging] -->|manual| I{plan}\n    I -->|manual| J[production]\n\u003C/pre>\n\n### Die Pipeline\n\nUm den oben genannten [Workflow](#the-workflow) zu implementieren, implementieren wir jetzt eine Pipeline mit zwei [Downstream-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_p/ ipelines.html).\n\n#### Die Haupt-Pipeline\n\nBeginnen wir mit der Haupt-Pipeline. Dies ist diejenige, die automatisch bei jedem **Push zu einem Feature-Branch**, jedem Zusammenführen zum Standard-Branch** oder jedem **Tag** ausgelöst wird. *Die Pipeline*, die eine echte **kontinuierliche Bereitstellung** in den folgenden Umgebungen durchführt: `dev`, `integration` und `staging`. Und das wird in der Datei `.gitlab-ci.yml` im Stamm deines Projekts deklariert.\n\n![Repository-Ziel](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750097033417.png)\n\n```yml\nStages:\n  - test\n  - environments\n\n.environment:\n  stage: environments\n  variables:\n    TF_ROOT: terraform\n    TF_CLI_ARGS_plan: \"-var-file=../vars/$variables_file.tfvars\"\n  trigger:\n    include: .gitlab-ci/.first-layer.gitlab-ci.yml\n    strategy: depend             # Wait for the triggered pipeline to successfully complete\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nreview:\n  extends: .environment\n  variables:\n    environment: review/$CI_COMMIT_REF_SLUG\n    TF_STATE_NAME: $CI_COMMIT_REF_SLUG\n    variables_file: review\n    TF_VAR_aws_resources_name: $CI_COMMIT_REF_SLUG  # Used in the tag Name of the resources deployed, to easily differenciate them\n  rules:\n    - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\nintegration:\n  extends: .environment\n  variables:\n    environment: integration\n    TF_STATE_NAME: $environment\n    variables_file: $environment\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nstaging:\n  extends: .environment\n  variables:\n    environment: staging\n    TF_STATE_NAME: $environment\n    variables_file: $environment\n  rules:\n    - if: $CI_COMMIT_TAG\n\n#### TWEAK\n# This tweak is needed to display vulnerability results in the merge widgets.\n# As soon as this issue https://gitlab.com/gitlab-org/gitlab/-/issues/439700 is resolved, the `include` instruction below can be removed.\n# Until then, the SAST IaC scanners will run in the downstream pipelines, but their results will not be available directly in the merge request widget, making it harder to track them.\n# Note: This workaround is perfectly safe and will not slow down your pipeline.\ninclude:\n  - template: Security/SAST-IaC.gitlab-ci.yml\n#### END TWEAK\n\n```\n\nDiese Pipeline läuft nur in zwei Phasen: `test` und `environments`. Erstere wird benötigt, damit der *TWEAK* Scanner ausführen kann. Zweitere löst eine untergeordnete Pipeline mit einem anderen Satz von Variablen für jeden oben definierten Fall aus (Push zum Branch, Zusammenführen zum Standard-Branch oder Tag).\n\nWir fügen hier eine Abhängigkeit mit dem Schlüsselwort [strategy:depend](https://docs.gitlab.com/ci/yaml/#triggerstrategy) zu unserer untergeordneten Pipeline hinzu, sodass die Pipeline-Ansicht in GitLab erst aktualisiert wird, wenn die Bereitstellung abgeschlossen ist.\n\nWie du hier sehen kannst, definieren wir einen Basisjob, [hidden](https://docs.gitlab.com/ci/jobs/#hide-jobs), und erweitern ihn um bestimmte Variablen und Regeln, um nur eine Bereitstellung für jede Zielumgebung auszulösen.\n\nNeben den [vordefinierten Variablen](https://docs.gitlab.com/ci/variables/predefined_variables.htm/ l) verwenden wir zwei neue Einträge, die wir definieren müssen:\n1. [Die für jede Umgebung spezifischen Variablen](#the-variable-definitions): `../vars/$variables_file.tfvars`\n2. [Die untergeordnete Pipeline](#the-child-pipeline), definiert in `.gitlab-ci/.first-layer.gitlab-ci.yml`\n\nBeginnen wir mit dem kleinsten Teil, den Variablendefinitionen.\n\n### Die Variablendefinitionen\n\nWir werden hier zwei Lösungen mischen, um Terraform Variablen zur Verfügung zu stellen:\n\n* Die erste nutzt [.tfvars-Dateien](https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files) für alle nicht sensiblen Eingaben, die in GitLab gespeichert werden sollten.\n\n![Lösung 1 zur Bereitstellung von Variablen für Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097033419.png)\n\n* Die zweite verwendet [Umgebungsvariablen](https://developer.hashicorp.com/terraform/language/values/variables#environment-variables) mit dem Präfix `TF_VAR`. Diese zweite Möglichkeit, Variablen zu injizieren, die mit der GitLab-Fähigkeit verbunden sind, [Variablen zu maskieren](https://docs.gitlab.com/ci/variables/#mask-a-cicd-variable), [sie zu schützen](https://docs.gitlab.com/ci/variables/#protect-a-cicd-variable) und [sie in Umgebungen zu übertragen](https://docs.gitlab.com/ci/environments/#limit-the-environment-scope-of-a-cicd-variable), ist eine leistungsstarke Lösung, um **Datenlecks sensibler Informationen zu verhindern**. (Wenn du das private CIDR deiner Produktion als sehr sensibel betrachtest, könntest du es so schützen, indem du sicherstellst, dass es nur für die Umgebung `production` verfügbar ist, für Pipelines, die gegen geschützte Branches und Tags ausgeführt werden, und dass sein Wert in den Protokollen des Jobs maskiert ist.)\n\n![Lösung 2 zur Bereitstellung von Variablen für Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097034/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097033422.png)\n\nDarüber hinaus sollte jede Variablendatei über eine [Datei `CODEOWNERS`](https://docs.gitlab.com/user/project/codeowners/) gesteuert werden, um festzulegen, wer sie ändern darf.\n\n```text\n[Production owners] vars/production.tfvars @operations-group\n\n[Staging owners]\nvars/staging.tfvars @odupre @operations-group\n\n[CodeOwners owners]\nCODEOWNERS @odupre\n```\n\nDieser Artikel ist kein Terraform-Training, daher halten wir das kurz und zeigen hier einfach die Datei `vars/review.tfvars`. Die nachfolgenden Umgebungsdateien sind sich natürlich sehr ähnlich. Lege hier einfach die nicht-sensiblen Variablen und ihre Werte fest.\n\n```shell\naws_vpc_cidr = \"10.1.0.0/16\"\naws_public_subnet_cidr = \"10.1.1.0/24\"\naws_private_subnet_cidr = \"10.1.2.0/24\"\n```\n\n#### Die untergeordnete Pipeline\n\nHier wird die eigentliche Arbeit erledigt. Sie ist also etwas komplexer als die erste Pipeline. Es gibt aber auch hier keine Schwierigkeit, die wir nicht gemeinsam überwinden können!\n\nWie wir bei der Definition der [Haupt-Pipeline](#the-main-pipeline) gesehen haben, wird diese Downstream-Pipeline in der Datei `.gitlab-ci/.first-layer.gitlab-ci.yml` deklariert.\n\n![In Datei deklarierte Downstream-Pipeline](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750097033424.png)\n\nZerlegen wir sie in kleine Stücke. Am Ende sehen wir dann das große Ganze.\n\n##### Terraform-Befehle ausführen und den Code sichern\n\nZuerst wollen wir eine Pipeline für Terraform ausführen. GitLab ist Open Source. Unsere Terraform-Vorlage ist also auch Open Source. Du kannst sie einfach einbeziehen. Dies erreichst du mit folgendem Code-Schnipsel:\n\n```yml\ninclude:\n  - template: Terraform.gitlab-ci.yml\n```\n\nDiese Vorlage führt für dich die Terraform-Prüfungen für die Formatierung durch und validiert deinen Code, bevor er geplant und angewendet wird. Es ermöglicht dir auch, das zu zerstören, was du bereitgestellt hast.\n\nDa GitLab eine vereinheitlichte DevSecOps-Plattform ist, fügen wir dieser Vorlage automatisch zwei Sicherheitsscanner hinzu, um potenzielle Bedrohungen in deinem Code zu finden und dich zu warnen, bevor du ihn in den nächsten Umgebungen bereitstellst.\n\nJetzt, da wir unseren Code überprüft, gesichert, erstellt und bereitgestellt haben, folgen ein paar Tricks.\n\n##### Zwischenspeicher zwischen Jobs teilen\n\nWir werden Job-Ergebnisse zwischenspeichern, um sie in folgenden Pipeline-Jobs wiederzuverwenden. Dies ist einfach, denn du musst nur den folgenden Code hinzufügen:\n\n```yml\ndefault:\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: cache-$CI_COMMIT_REF_SLUG\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n\n```\n\nHier definieren wir einen anderen Zwischenspeicher für jeden Commit und greifen bei Bedarf auf den Namen des Haupt-Branchs zurück.\n\nWenn wir uns die Vorlagen, die wir verwenden, genau ansehen, stellen wir fest, dass sie einige Regeln haben, die zu kontrollieren sind, wenn Jobs ausgeführt werden. Wir wollen alle Kontrollen (sowohl QA als auch Sicherheit) in allen Branchen ausführen. Wir werden diese Einstellungen also überschreiben.\n\n##### Kontrollen in allen Branches ausführen\n\nGitLab-Vorlagen sind eine leistungsstarke Funktion, bei der man auch nur einen Teil der Vorlage überschreiben kann. Hier wollen wir die Regeln einiger Jobs überschreiben, um immer Qualitäts- und Sicherheitskontrollen durchzuführen. Alles andere, was für diese Jobs definiert ist, bleibt wie in der Vorlage definiert.\n\n```yml\nfmt:\n  rules:\n    - when: always\n\nvalidate:\n  rules:\n    - when: always\n\nkics-iac-sast:\n  rules:\n    - when: always\n\niac-sast:\n  rules:\n    - when: always\n```\n\nDa wir nun die Qualitäts- und Sicherheitskontrollen durchgesetzt haben, wollen wir unterscheiden, wie sich die Hauptumgebungen (Integration und Staging) im [Workflow](#the-workflow) und Review-Umgebungen verhalten. Beginnen wir mit der Definition des Verhaltens der Hauptumgebung. Wir werden dann diese Konfiguration für die Review-Umgebungen optimieren.\n\n##### CD für Integration und Staging\n\nWie zuvor definiert, möchten wir den Haupt-Branch und die Tags in diesen beiden Umgebungen bereitstellen. Wir fügen Regeln hinzu, um das sowohl bei den Jobs `build` als auch `deploy` zu kontrollieren. Dann wollen wir `destroy` nur für `integration` aktivieren, da wir definiert haben, dass `staging` zu kritisch ist, um mit einem einzigen Klick gelöscht zu werden. Das ist fehleranfällig, was wir nicht wollen.\n\nSchließlich verknüpfen wir den Job `deploy` mit dem Job `destroy`, damit wir die Umgebung direkt von der GitLab-GUI aus mit `stop` stoppen können.\n\nDie `GIT_STRATEGY` soll verhindern, dass der Code beim Zerstören aus dem Quell-Branch im Runner abgerufen wird. Dies würde fehlschlagen, wenn der Branch manuell gelöscht wurde. Daher verlassen wir uns auf den Zwischenspeicher, um alles zu erhalten, was wir zum Ausführen der Terraform-Anweisungen benötigen.\n\n```yml\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env (integration or staging) when merging to default branch or tagging. Second layer environments (qa and production) will be controlled manually\n  environment:   environment:     name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n```\n\nWie gesagt müssen diese Matches in `integration` und `staging` bereitstellen. Uns fehlt jedoch immer noch eine temporäre Umgebung, in der die Entwickler(innen) ihren Code ohne Auswirkungen auf andere erleben und validieren können. Hier findet die Bereitstellung in der Umgebung `review` statt.\n\n##### CD für Review-Umgebungen\n\nDie Bereitstellung in der Review-Umgebung unterscheidet sich nicht allzu sehr von der Bereitstellung in `integration` und `staging`. Wir werden also erneut die Möglichkeit von GitLab nutzen, hier nur Teile der Jobdefinition zu überschreiben.\n\nZuerst legen wir Regeln fest, um diese Jobs nur in Feature-Branches auszuführen.\n\nDann verknüpfen wir den Job `deploy_review` mit `destroy_review`. Dies ermöglicht es uns, die Umgebung **manuell** von der GitLab-Bedienoberfläche aus zu stoppen und – was noch wichtiger ist – es wird **automatisch die Zerstörung der Umgebung ausgelöst**, wenn der Feature-Branch geschlossen wird. Dies ist eine gute FinOps-Praxis, um dir zu helfen, deine Betriebsausgaben zu kontrollieren.\n\nDa Terraform eine Plandatei benötigt, um eine Infrastruktur zu zerstören (genau wie es eine solche Datei benötigt, um eine Infrastruktur aufzubauen), fügen wir eine Abhängigkeit von `destroy_review` zu `build_review` hinzu, um Artefakte abzurufen.\n\nSchließlich sehen wir hier, dass der Name der Umgebung auf `$environment` festgelegt ist. Es wurde in der [Haupt-Pipeline](#the-main-pipeline) auf `review/$CI_COMMIT_REF_SLUG` gesetzt und mit der Anweisung `trigger:forward:yaml_variables:true` an diese untergeordnete Pipeline weitergeleitet.\n\n```yml\nbuild_review:\n  extends: build\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndeploy_review:\n  extends: deploy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: start\n    on_stop: destroy_review\n    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndestroy_review:\n  extends: destroy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy staging\n      when: never\n    - when: manual\n```\n\nZusammenfassend können wir also sagen, dass wir jetzt eine Pipeline haben, die Folgendes kann:\n\n* Temporäre Review-Umgebungen bereitstellen, die automatisch gelöscht werden, wenn der Feature-Branch geschlossen wird\n* Den **Standard-Branch** kontinuierlich auf `integration` bereitstellen\n* Die **Tags** kontinuierlich auf `staging` bereitstellen\n\nFügen wir nun eine zusätzliche Ebene hinzu, auf der wir diesmal mit einem manuellen Auslöser in den Umgebungen `qa` und `production` bereitstellen werden.\n\n##### Kontinuierliche Bereitstellung in QA und Produktion\n\nDa nicht jedes Unternehmen kontinuierlich in der Produktion bereitstellen möchte, fügen wir den nächsten beiden Bereitstellungen eine manuelle Validierung hinzu. Aus einer reinen **CD**-Perspektive würden wir diesen Auslöser nicht hinzufügen, aber betrachte dies als Gelegenheit, zu lernen, wie man Jobs von anderen Auslösern aus ausführt.\n\nBisher haben wir eine [untergeordnete Pipeline](#the-child-pipeline) aus der [Haupt-Pipeline](#the-main-pipeline) gestartet, um alle Bereitstellungen auszuführen.\n\nDa wir andere Bereitstellungen aus dem Standard-Branch und den Tags ausführen möchten, fügen wir eine weitere Ebene für diese zusätzlichen Schritte hinzu. Hier gibt es nichts Neues. Wir wiederholen einfach genau das, was wir nur für die [Haupt-Pipeline](#the-main-pipeline) gemacht haben. Auf diese Weise kannst du so viele Ebenen bearbeiten, wie du brauchst. Ich habe schon einmal bis zu neun Umgebungen gesehen.\n\nWir wollen hier nicht über die Vorteile diskutieren, die es mit sich bringt, weniger Umgebungen zu haben. Der hier verwendete Prozess macht es jedenfalls sehr einfach, die gleiche Pipeline von der Anfangsphase bis zur endgültigen Lieferung zu implementieren, während deine Pipeline-Definition einfach und in kleine, einfach zu wartende Teile aufgeteilt bleibt.\n\nUm hier Variablenkonflikte zu vermeiden, verwenden wir nur neue Variablennamen, um den Terraform-Status und die Eingabedatei zu identifizieren.\n\n```yml\n.2nd_layer:\n  stage: 2nd_layer\n  variables:\n    TF_ROOT: terraform\n  trigger:\n    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n    # strategy: depend            # Do NOT wait for the downstream pipeline to finish to mark upstream pipeline as successful. Andernfalls schlagen alle Pipelines fehl, wenn eine Pipeline-Zeitüberschreitung vor der Bereitstellung auf die 2. Ebene erreicht wurde.\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nqa:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: qa\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nproduction:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: production\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_TAG\n```\n\n**Ein wichtiger Trick ist hier die Strategie, die für die neue Downstream-Pipeline verwendet wird.** Wir belassen `trigger:strategy` auf ihrem Standardwert. Andernfalls würde die [Haupt-Pipeline](#the-main-pipeline) warten, bis deine [Pipeline der zweiten Ebene](#the-grand-child-pipeline) abgeschlossen ist. Bei einem manuellen Auslöser kann dies sehr lange dauern und das Lesen und Verstehen deines Pipeline-Dashboards erschweren.\n\nDu hast dich wahrscheinlich schon gefragt, was der Inhalt der Datei `.gitlab-ci/.second-layer.gitlab-ci.yml` ist, die wir hier anführen. Wir gehen im nächsten Abschnitt darauf ein.\n\n##### Vollständige Pipeline-Definition auf der ersten Ebene\n\nWenn du eine vollständige Ansicht dieser ersten Ebene möchtest (gespeichert in `.gitlab-ci/.first-layer.gitlab-ci.yml`), erweitere einfach den Abschnitt unten.\n\n```yml\nvariables:\n  TF_VAR_aws_ami_id: $AWS_AMI_ID\n  TF_VAR_aws_instance_type: $AWS_INSTANCE_TYPE\n  TF_VAR_aws_default_region: $AWS_DEFAULT_REGION\n\ninclude:\n  - template: Terraform.gitlab-ci.yml\n\ndefault:\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: cache-$CI_COMMIT_REF_SLUG\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n\nstages:\n  - validate\n  - test\n  - build\n  - deploy\n  - cleanup\n  - 2nd_layer       # Use to deploy a 2nd environment on both the main branch and on the tags\n\nfmt:\n  rules:\n    - when: always\n\nvalidate:\n  rules:\n    - when: always\n\nkics-iac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: on_success\n\niac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: on_success\n\n###########################################################################################################\n## Integration env. and Staging. env\n##  * Auto-deploy to Integration on merge to main.\n##  * Auto-deploy to Staging on tag.\n##  * Integration can be manually destroyed if TF_DESTROY is set to true.\n##  * Destroy of next env. is not automated to prevent errors.\n###########################################################################################################\nbuild:  # terraform plan\n  environment:\n    name: $TF_STATE_NAME\n    action: prepare\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndeploy: # terraform apply --> automatically deploy on corresponding env (integration or staging) when merging to default branch or tagging. Second layer environments (qa and production) will be controlled manually\n  environment:     name: $TF_STATE_NAME\n    action: start\n    on_stop: destroy\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG\n\ndestroy:\n  extends: .terraform:destroy\n  variables:\n    GIT_STRATEGY: none\n  dependencies:\n    - build\n  environment:\n    name: $TF_STATE_NAME\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY == \"true\" # Manually destroy integration env.\n      when: manual\n###########################################################################################################\n\n###########################################################################################################\n## Dev env.\n##  * Temporary environment. Lives and dies with the Merge Request.\n##  * Auto-deploy on push to feature branch.\n##  * Auto-destroy on when Merge Request is closed.\n###########################################################################################################\nbuild_review:\n  extends: build\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndeploy_review:\n  extends: deploy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: start\n    on_stop: destroy_review\n    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n  rules:\n    - if: $CI_COMMIT_TAG\n      when: never\n    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n      when: on_success\n\ndestroy_review:\n  extends: destroy\n  dependencies:\n    - build_review\n  environment:\n    name: $environment\n    action: stop\n  rules:\n    - if: $CI_COMMIT_TAG  # Do not destroy production\n      when: never\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy staging\n      when: never\n    - when: manual\n###########################################################################################################\n\n###########################################################################################################\n## Second layer\n##  * Deploys from main branch to qa env.\n##  * Deploys from tag to production.\n###########################################################################################################\n.2nd_layer:\n  stage: 2nd_layer\n  variables:\n    TF_ROOT: terraform\n  trigger:\n    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n    # strategy: depend            # Do NOT wait for the downstream pipeline to finish to mark upstream pipeline as successful. Otherwise, all pipelines will fail when reaching the pipeline timeout before deployment to 2nd layer.\n    forward:\n      yaml_variables: true      # Forward variables defined in the trigger job\n      pipeline_variables: true  # Forward manual pipeline variables and scheduled pipeline variables\n\nqa:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: qa\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\nproduction:\n  extends: .2nd_layer\n  variables:\n    TF_STATE_NAME_2: production\n    environment: $TF_STATE_NAME_2\n    TF_CLI_ARGS_plan_2: \"-var-file=../vars/$TF_STATE_NAME_2.tfvars\"\n  rules:\n    - if: $CI_COMMIT_TAG\n###########################################################################################################\n```\n\nIn dieser Phase stellen wir bereits sicher in drei Umgebungen bereit. Das ist meine persönliche Idealempfehlung. Wenn du jedoch mehr Umgebungen benötigst, füge diese deiner CD-Pipeline hinzu.\n\nDu hast sicherlich schon bemerkt, dass wir eine Downstream-Pipeline mit dem Stichwort `trigger:include` einbinden. Dazu gehört die Datei `.gitlab-ci/.second-layer.gitlab-ci.yml`. Da wir fast die gleiche Pipeline ausführen wollen, ist ihr Inhalt offensichtlich sehr ähnlich zu dem, den wir oben detailliert beschrieben haben. Der Hauptvorteil bei der Definition dieser [Pipeline der zweiten Ebene](#the-grand-child-pipeline) ist, dass sie allein besteht, was die Definition von Variablen und Regeln erleichtert.\n\n### Die Pipeline der zweiten Ebene\n\nDiese Pipeline der zweiten Ebene ist eine brandneue Pipeline. Daher muss es die Definition der ersten Ebene nachahmen mit:\n\n* [Aufnahme der Terraform-Vorlage](#run-terraform-commands-and-secure-the-code).\n* [Durchsetzung von Sicherheitskontrollen](#run-controls-on-all-branches). Bei der Terraform-Validierung handelt es sich um Duplikate der ersten Ebene. Sicherheitsscanner können jedoch Bedrohungen finden, die noch nicht vorhanden waren, als die Scanner zuvor ausgeführt wurden (z. B. wenn du einige Tage nach der Bereitstellung im Staging in der Produktion bereitstellst).\n* [Überschreiben von Build- und Bereitstellungs-Jobs, um spezifische Regeln festzulegen](#cd-to-review-environments). Beachte bitte, dass die Phase `destroy` nicht mehr automatisiert ist, um zu schnelle Löschvorgänge zu verhindern.\n\nWie oben erläutert, wurden `TF_STATE_NAME` und `TF_CLI_ARGS_plan` von der [Haupt-Pipeline](# the-main-pipeline) zur [untergeordneten Pipeline](#the-child-pipeline) bereitgestellt. Wir brauchten einen weiteren Variablennamen, um diese Werte von der [untergeordneten Pipeline](#the-child-pipeline) hierher, also an die [Pipeline der zweiten Ebene](#the-grand-child-pipeline), zu übergeben. Deshalb werden sie in der untergeordneten Pipeline mit dem Postfix `_2` versehen und der Wert wird während des `before_script` hier zurück in die entsprechende Variable kopiert.\n\nDa wir oben bereits jeden Schritt aufgeschlüsselt haben, können wir hier direkt auf die breite Ansicht der globalen Definition der zweiten Ebene zoomen (gespeichert in `.gitlab-ci/.second-layer.gitlab-ci.yml`).\n\n```yml\n# Use to deploy a second environment on both the default branch and the tags.\n\ninclude:\n  template: Terraform.gitlab-ci.yml\n\nstages:\n  - validate\n  - test\n  - build\n  - deploy\n\nfmt:\n  rules:\n    - when: never\n\nvalidate:\n  rules:\n    - when: never\n\nkics-iac-sast:\n  rules:\n    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n      when: never\n    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n      when: never\n    - when: always\n\n###########################################################################################################\n## QA env. and Prod. env\n##  * Manually trigger build and auto-deploy in QA\n##  * Manually trigger both build and deploy in Production\n##  * Destroy of these env. is not automated to prevent errors.\n###########################################################################################################\nbuild:  # terraform plan\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment:\n    name: $TF_STATE_NAME_2\n    action: prepare\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - when: manual\n\ndeploy: # terraform apply\n  cache:  # Use a shared cache or tagged runners to ensure terraform can run on apply and destroy\n    - key: $TF_STATE_NAME_2\n      fallback_keys:\n        - cache-$CI_DEFAULT_BRANCH\n      paths:\n        - .\n  environment:     name: $TF_STATE_NAME_2\n    action: start\n  before_script:  # Hack to set new variable values on the second layer, while still using the same variable names. Otherwise, due to variable precedence order, setting new value in the trigger job, does not cascade these new values to the downstream pipeline\n    - TF_STATE_NAME=$TF_STATE_NAME_2\n    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n  rules:\n    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n    - if: $CI_COMMIT_TAG && $TF_AUTO_DEPLOY == \"true\"\n    - if: $CI_COMMIT_TAG\n      when: manual\n###########################################################################################################\n```\n\nEt voilà. **Wir sind bereit.** Du kannst die Art und Weise ändern, wie du deine Jobausführungen kontrollierst, indem du bspw. die Möglichkeit von GitLab nutzt, [einen Job zu verzögern](https://docs.gitlab.com/ci/jobs/job_control/#run-a-job-after-a-delay), bevor du ihn in der Produktion bereitstellst.\n\n## Probiere es selbst\n\nWir haben endlich unser Ziel erreicht. Wir sind jetzt in der Lage, **Bereitstellungen in fünf verschiedenen Umgebungen** zu kontrollieren, wobei nur die **Feature-Branches**, der **Haupt-Branch** und **Tags** verwendet werden.\n* Wir verwenden Open-Source-Vorlagen von GitLab intensiv wieder, um Effizienz und Sicherheit in unseren Pipelines zu gewährleisten.\n* Wir nutzen GitLab-Vorlagen, um nur die Blöcke zu überschreiben, die eine benutzerdefinierte Kontrolle benötigen.\n* Wir haben die Pipeline in kleine Teile aufgeteilt und kontrollieren die Downstream-Pipelines so, dass sie genau dem entsprechen, was wir brauchen.\n\nAb hier gehört die Bühne ganz dir. Du kannst beispielsweise die Haupt-Pipeline einfach aktualisieren, um Downstream-Pipelines für deinen Software-Quellcode mit dem Schlüsselwort [trigger:rules:changes](https://docs.gitlab.com/ci/yaml/#ruleschanges) auszulösen. Und verwende je nach den aufgetretenen Änderungen eine andere [Vorlage](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates/). Aber das ist eine andere Geschichte.","engineering",{"slug":13,"featured":14,"template":15},"using-child-pipelines-to-continuously-deploy-to-five-environments",false,"BlogPost",{"title":5,"description":17,"authors":18,"heroImage":19,"date":20,"body":10,"category":11,"tags":21,"updatedDate":27},"Erfahre, wie du die kontinuierliche Bereitstellung in verschiedenen Umgebungen – darunter temporäre, sofort einsatzbereite Sandboxes – mit einem minimalistischen GitLab-Workflow verwalten kannst.",[9],"https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097012/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_397632156_3Ldy1urjMStQCl4qnOBvE0_1750097011626.jpg","2024-09-26",[22,23,24,25,26],"CI/CD","CI","CD","DevSecOps platform","tutorial","2024-11-05","yml",null,{},true,"/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","seo:\n  title: >-\n    Kontinuierliche Bereitstellung in fünf Umgebungen mithilfe von\n    untergeordneten Pipelines\n  description: 'Erfahre, wie du die kontinuierliche Bereitstellung in\n    verschiedenen Umgebungen – darunter temporäre, sofort einsatzbereite\n    Sandboxes – mit einem minimalistischen GitLab-Workflow verwalten kannst.'\n  ogTitle: >-\n    Kontinuierliche Bereitstellung in fünf Umgebungen mithilfe von\n    untergeordneten Pipelines\n  ogDescription: 'Erfahre, wie du die kontinuierliche Bereitstellung in\n    verschiedenen Umgebungen – darunter temporäre, sofort einsatzbereite\n    Sandboxes – mit einem minimalistischen GitLab-Workflow verwalten kannst.'\n  noIndex: false\n  ogImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097012/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_397632156_3Ldy1urjMStQCl4qnOBvE0_1750097011626.jpg\n  ogUrl: >-\n    https://about.gitlab.com/blog/using-child-pipelines-to-continuously-deploy-to-five-environments\n  ogSiteName: https://about.gitlab.com\n  ogType: article\n  canonicalUrls: >-\n    https://about.gitlab.com/blog/using-child-pipelines-to-continuously-deploy-to-five-environments\ncontent:\n  title: >-\n    Kontinuierliche Bereitstellung in fünf Umgebungen mithilfe von\n    untergeordneten Pipelines\n  description: 'Erfahre, wie du die kontinuierliche Bereitstellung in\n    verschiedenen Umgebungen – darunter temporäre, sofort einsatzbereite\n    Sandboxes – mit einem minimalistischen GitLab-Workflow verwalten kannst.'\n  authors:\n    - Olivier Dupré\n  heroImage: >-\n    https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097012/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_397632156_3Ldy1urjMStQCl4qnOBvE0_1750097011626.jpg\n  date: '2024-09-26'\n  body: \"Manchmal brauchen DevSecOps-Teams die Möglichkeit, die kontinuierliche\n    Bereitstellung über mehrere Umgebungen übergreifend zu verwalten, ohne dabei\n    ihre Workflows zu verändern. Die [DevSecOps-Plattform von\n    GitLab](https://about.gitlab.com/de-de/) macht dies mit einem\n    minimalistischen Ansatz möglich, unter anderem für temporäre, sofort\n    einsatzbereite Sandboxes. In diesem Artikel erfährst du, wie du die\n    kontinuierliche Bereitstellung der Infrastruktur mit Terraform in\n    verschiedenen Umgebungen ausführen kannst.\n\n\n    > **Nahezu 100 % Uptime: NVIDIA skaliert mit GitLab global – ohne\n    Ausfallzeiten** Verteilte Teams bei NVIDIA profitieren mit GitLab Geo von\n    schneller verfügbaren Repositories, häufigeren Upgrades ohne Ausfallzeiten\n    und global-synchronen sowie -sicheren Projekten. Erfahre, wie GitLab Premium\n    hilft, Stabilität, Transparenz und Effizienz zu verbessern. **[Erfolgsstory\n    lesen](https://about.gitlab.com/de-de/customers/nvidia/)**\n\n\n    Diese Strategie kann einfach auf andere Projekte umgesetzt werden, egal, ob\n    es sich um Infrastructure as Code (IaC), die auf einer anderen Technologie\n    wie [Pulumi](https://www.pulumi.com/) oder\n    [Ansible](https://www.ansible.com/) basiert, um Quellcode in beliebigen\n    Sprachen oder ein Monorepo handelt, bei dem viele Sprachen gemischt\n    verwendet werden.\n\n\n    Die letzte Pipeline, die du am Ende dieses Tutorials hast, stellt Folgendes\n    bereit:\n\n\n    * Eine temporäre **Review-Umgebung** für jeden Feature-Branch.\n\n    * Eine **Integrationsumgebung**, die einfach zu löschen und vom Haupt-Branch\n    aus bereitzustellen ist.\n\n    * Eine **QA-Umgebung**, die ebenfalls von dem Haupt-Branch bereitgestellt\n    wird, um Qualitätssicherungsschritte durchzuführen.\n\n    * Eine **Staging-Umgebung**, die für jedes Tag bereitgestellt wird. Dies ist\n    die letzte Runde vor der Produktion.\n\n    * Eine **Produktionsumgebung**, die direkt nach der Staging-Umgebung folgt.\n    Diese wird zur Demonstration manuell ausgelöst, kann aber auch\n    kontinuierlich bereitgestellt werden.\n\n\n    >Hier findest du die Legende für die Flussdiagramme in diesem Artikel:\n\n    > * Runde Boxen sind die GitLab-Branches.\n\n    > * Eckige Boxen sind die Umgebungen.\n\n    > * Der Text auf den Pfeilen sind die Aktionen, die von einem Feld zum\n    nächsten fließen sollen.\n\n    > * Eckige Quadrate sind Entscheidungsschritte.\n\n\n    \u003Cpre class=\\\"mermaid\\\">\n\n    flowchart LR\n\n    \\    A(main) -->|new feature| B(feature_X)\n\n\n    \\    B -->|auto deploy| C[review/feature_X]\n\n    \\    B -->|merge| D(main)\n\n    \\    C -->|destroy| D\n\n\n    \\    D -->|auto deploy| E[integration]\n\n    \\    E -->|manual| F[qa]\n\n\n    \\    D -->|tag| G(X.Y.Z)\n\n    \\    F -->|validate| G\n\n\n    \\    G -->|auto deploy| H[staging]\n\n    \\    H -->|manual| I{plan}\n\n    \\    I -->|manual| J[production]\n\n    \u003C/pre>\n\n\n    Bei jedem Schritt erfährst du das [Warum](#why) und das [Was](#what), bevor\n    du zum [Wie](#how) übergehst. Dies wird dir helfen, dieses Tutorial\n    vollständig zu verstehen und zu replizieren.\n\n\n    ## Warum\n\n\n    * [Kontinuierliche\n    Integration](https://about.gitlab.com/de-de/topics/ci-cd/#what-is-continuou\\\n    s-integration-ci) ist fast ein De-facto-Standard. Die meisten Unternehmen\n    haben CI-Pipelines implementiert oder sind bereit, ihre Arbeitsweise zu\n    standardisieren.\n\n\n    * [Kontinuierliche\n    Bereitstellung](https://about.gitlab.com/topics/ci-cd/#what-is-continuous-d\\\n    elivery-cd), wobei Artefakte in ein Repository oder eine Registry am Ende\n    der CI-Pipeline gepusht werden, ist ebenfalls beliebt.\n\n\n    * Kontinuierliche Bereitstellung, die weiter geht und diese Artefakte\n    automatisch bereitstellt, ist allerdings weniger verbreitet. Wenn, dann wird\n    sie vor allem im Bereich von Anwendungen implementiert. Wenn es um die\n    kontinuierliche Bereitstellung von Infrastruktur geht, scheint das Bild\n    weniger klar zu sein und es dreht sich vieles um das Management mehrerer\n    Umgebungen. Im Gegensatz dazu scheint das Testen, Sichern und Überprüfen des\n    Infrastruktur-Codes schwieriger zu sein. Dies ist eines der Felder, in denen\n    DevOps noch nicht ausgereift ist. Ein anderer Anwendungsbereich ist, die\n    Sicherheit im Vorfeld zu kontrollieren und Sicherheitsteams sowie – was noch\n    wichtiger ist – Sicherheitsbedenken früher in den Lebenszyklus der\n    Bereitstellung zu integrieren und so von DevOps auf ***DevSecOps***\n    upzugraden.\n\n\n    Angesichts dessen wirst du in diesem Tutorial eine einfache und doch\n    effiziente Möglichkeit erarbeiten, DevSecOps für deine Infrastruktur zu\n    implementieren, indem du beispielsweise Ressourcen in fünf Umgebungen\n    bereitstellst und dich schrittweise von der Entwicklung bis zur Produktion\n    vorarbeitest.\n\n\n    __Hinweis:__ Auch wenn ich einen FinOps-Ansatz und eine Reduktion der\n    Umgebungen befürworte, gibt es manchmal gute Gründe, mehr als nur\n    Entwicklung, Staging und Produktion aufrechtzuerhalten. Bitte passe die\n    folgenden Beispiele an deine Bedürfnisse an.\n\n\n    ## Was\n\n\n    Der Aufstieg der Cloud-Technologie hat die Nutzung von IaC vorangetrieben.\n    Ansible und Terraform gehörten in diesem Bereich zu den Pionieren. OpenTofu,\n    Pulumi, AWS CDK, Google Deploy Manager und viele andere folgten.\n\n\n    IaC gilt als perfekte Lösung, um sich bei der Bereitstellung von\n    Infrastruktur sicher zu fühlen. Du kannst sie testen, bereitstellen und\n    immer wieder abspielen, bis du dein Ziel erreicht hast.\n\n\n    Leider sehen wir oft, dass Unternehmen für jede ihrer Zielumgebungen mehrere\n    Branches oder sogar Repositories unterhalten. Und hier beginnen die\n    Probleme. Sie setzen einen Prozess nicht mehr durch. Sie stellen nicht mehr\n    sicher, dass Änderungen in der Produktions-Codebase in früheren Umgebungen\n    genauestens getestet wurden. Und sie beginnen, Drifts von einer Umgebung in\n    die andere zu erleben.\n\n\n    Mir wurde klar, dass dieses Tutorial notwendig war, als auf einer Konferenz\n    alle Teilnehmenden sagten, dass sie keinen Workflow haben, der durchsetzt,\n    dass Infrastruktur genau getestet wird, bevor sie für die Produktion\n    bereitgestellt wird. Und sie waren sich alle einig, dass sie manchmal den\n    Code direkt in die Produktion patchen. Klar geht das schnell, aber ist es\n    auch sicher? Wie meldet man an frühere Umgebungen zurück? Wie stellt man\n    sicher, dass es keine Nebeneffekte gibt? Wie kontrolliert man, ob man das\n    Unternehmen in Gefahr bringt, wenn neue Sicherheitslücken zu schnell in die\n    Produktion gepusht werden?\n\n\n    Die Frage, *warum* DevOps-Teams direkt in die Produktion implementieren, ist\n    hier entscheidend. Liegt es daran, dass die Pipeline effizienter oder\n    schneller sein könnte? Gibt es keine Automatisierung? Oder, noch schlimmer,\n    gibt es *keine Möglichkeit, außerhalb der Produktion genau zu testen*?\n\n\n    Im nächsten Abschnitt erfährst du, wie du Automatisierung für deine\n    Infrastruktur implementieren und sicherstellen kannst, dass dein DevOps-Team\n    effektiv testet, bevor etwas in eine Umgebung gepusht wird, die sich auf\n    andere auswirkt. Du wirst sehen, wie dein Code gesichert und seine\n    Bereitstellung durchgehend kontrolliert wird.\n\n\n    ## Wie\n\n\n    Wie bereits erwähnt, gibt es heutzutage viele Programmiersprachen für IaC,\n    und wir können ganz einfach nicht *alle* in einem einzigen Artikel\n    behandeln. Ich werde mich also auf einen grundlegenden Terraform-Code\n    konzentrieren, der auf Version 1.4 läuft. Bitte fixiere dich nicht auf die\n    Programmiersprache für IaC selbst, sondern auf den Prozess, den du für dein\n    eigenes Ökosystem umsetzen kannst.\n\n\n    ### Der Terraform-Code\n\n\n    Beginnen wir mit einem grundlegenden Terraform-Code.\n\n\n    Wir werden auf AWS bereitstellen, eine virtuelle private Cloud (VPC), die\n    ein virtuelles Netzwerk ist. In dieser VPC werden wir ein öffentliches und\n    ein privates Subnetz bereitstellen. Wie der Name schon sagt, handelt es sich\n    um Subnetze der Haupt-VPC. Abschließend fügen wir eine EC2-Instanz (Elastic\n    Cloud Compute; eine virtuelle Maschine) zum öffentlichen Subnetz hinzu.\n\n\n    Dies zeigt, wie vier Ressourcen bereitgestellt werden können, ohne zu\n    komplex zu werden. Die Idee ist, sich auf die Pipeline zu konzentrieren,\n    nicht auf den Code.\n\n\n    Hier ist das Ziel, das wir für dein Repository erreichen möchten.\n\n\n    ![Ziel für\n    Repository](https://res.cloudinary.com/about-gitlab-com/image/upload/v17500\\\n    97033/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_175009703\\\n    3415.png)\n\n\n    Gehen wir Schritt für Schritt vor.\n\n\n    Zuerst deklarieren wir alle Ressourcen in der Datei `terraform/main.tf`:\n\n\n    ```terraform\n\n    provider \\\"aws\\\" {\n\n    \\  region = var.aws_default_region\n\n    }\n\n\n    resource \\\"aws_vpc\\\" \\\"main\\\" {\n\n    \\  cidr_block = var.aws_vpc_cidr\n\n\n    \\  tags = {\n\n    \\    Name     = var.aws_resources_name\n\n    \\  }\n\n    }\n\n\n    resource \\\"aws_subnet\\\" \\\"public_subnet\\\" {\n\n    \\  vpc_id     = aws_vpc.main.id\n\n    \\  cidr_block = var.aws_public_subnet_cidr\n\n\n    \\  tags = {\n\n    \\    Name = \\\"Public Subnet\\\"\n\n    \\  }\n\n    }\n\n    resource \\\"aws_subnet\\\" \\\"private_subnet\\\" {\n\n    \\  vpc_id     = aws_vpc.main.id\n\n    \\  cidr_block = var.aws_private_subnet_cidr\n\n\n    \\  tags = {\n\n    \\    Name = \\\"Private Subnet\\\"\n\n    \\  }\n\n    }\n\n\n    resource \\\"aws_instance\\\" \\\"sandbox\\\" {\n\n    \\  ami           = var.aws_ami_id\n\n    \\  instance_type = var.aws_instance_type\n\n\n    \\  subnet_id = aws_subnet.public_subnet.id\n\n\n    \\  tags = {\n\n    \\    Name     = var.aws_resources_name\n\n    \\  }\n\n    }\n\n    ```\n\n\n    Wie du sehen kannst, sind für diesen Code einige Variablen erforderlich, die\n    wir also in der Datei `terraform/variables.tf` deklarieren:\n\n\n    ```terraform\n\n    variable \\\"aws_ami_id\\\" {\n\n    \\  description = \\\"The AMI ID of the image being deployed.\\\"\n\n    \\  type        = string\n\n    }\n\n\n    variable \\\"aws_instance_type\\\" {\n\n    \\  description = \\\"The instance type of the VM being deployed.\\\"\n\n    \\  type        = string\n\n    \\  default     = \\\"t2.micro\\\"\n\n    }\n\n\n    variable \\\"aws_vpc_cidr\\\" {\n\n    \\  description = \\\"The CIDR of the VPC.\\\"\n\n    \\  type        = string\n\n    \\  default     = \\\"10.0.0.0/16\\\"\n\n    }\n\n\n    variable \\\"aws_public_subnet_cidr\\\" {\n\n    \\  description = \\\"The CIDR of the public subnet.\\\"\n\n    \\  type        = string\n\n    \\  default     = \\\"10.0.1.0/24\\\"\n\n    }\n\n\n    variable \\\"aws_private_subnet_cidr\\\" {\n\n    \\  description = \\\"The CIDR of the private subnet.\\\"\n\n    \\  type        = string\n\n    \\  default     = \\\"10.0.2.0/24\\\"\n\n    }\n\n\n    variable \\\"aws_default_region\\\" {\n\n    \\  description = \\\"Default region where resources are deployed.\\\"\n\n    \\  type        = string\n\n    \\  default     = \\\"eu-west-3\\\"\n\n    }\n\n\n    variable \\\"aws_resources_name\\\" {\n\n    \\  description = \\\"Default name for the resources.\\\"\n\n    \\  type        = string\n\n    \\  default     = \\\"demo\\\"\n\n    }\n\n    ```\n\n\n    Auf der IaC-Seite sind wir damit auch schon fast fertig. Was fehlt, ist eine\n    Möglichkeit, die Terraform-Zustände zu teilen. Für diejenigen, die es nicht\n    wissen: Terraform funktioniert schematisch wie folgt:\n\n\n    * `plan` überprüft die Unterschiede zwischen dem aktuellen Status der\n    Infrastruktur und dem, was im Code definiert ist. Dann gibt es die\n    Unterschiede aus.\n\n    * `apply` wendet die Unterschiede im `plan` an und aktualisiert den Status.\n\n\n    In der ersten Runde ist der Status leer, dann wird er mit den Details (ID\n    usw.) der von Terraform angewendeten Ressourcen gefüllt.\n\n\n    Das Problem ist: Wo wird dieser Zustand gespeichert? Wie können wir ihn\n    teilen, damit mehrere Entwickler(innen) am Code zusammenarbeiten können?\n\n\n    Die Lösung ist ziemlich einfach: Nutze GitLab, um den Status über ein\n    [Terraform-HTTP-Backend](https://docs.gitlab.com/user/infrastructure/iac/\n    /terraform_state.html) zu speichern und freizugeben.\n\n\n    Der erste Schritt bei der Verwendung dieses Backends besteht darin, die\n    einfachste Datei, nämlich `terraform/backend.tf` zu erstellen. Der zweite\n    Schritt erfolgt in der Pipeline.\n\n\n    ```terraform\n\n    terraform {\n\n    \\  backend \\\"http\\\" {\n\n    \\  }\n\n    }\n\n    ```\n\n\n    Et voilà! Wir haben einen minimalen Terraform-Code, um diese vier Ressourcen\n    bereitzustellen. Wir werden die Variablenwerte zur Laufzeit bereitstellen,\n    also machen wir das später.\n\n\n    ### Der Workflow\n\n\n    Der Workflow, den wir jetzt implementieren werden, sieht folgendermaßen aus:\n\n\n    \u003Cpre class=\\\"mermaid\\\">\n\n    flowchart LR\n\n    \\    A(main) -->|new feature| B(feature_X)\n\n\n    \\    B -->|auto deploy| C[review/feature_X]\n\n    \\    B -->|merge| D(main)\n\n    \\    C -->|destroy| D\n\n\n    \\    D -->|auto deploy| E[integration]\n\n    \\    E -->|manual| F[qa]\n\n\n    \\    D -->|tag| G(X.Y.Z)\n\n    \\    F -->|validate| G\n\n\n    \\    G -->|auto deploy| H[staging]\n\n    \\    H -->|manual| I{plan}\n\n    \\    I -->|manual| J[production]\n\n    \u003C/pre>\n\n\n    1. Erstelle einen **Feature-Branch**. Dadurch werden alle Scanner\n    kontinuierlich auf dem Code ausgeführt, um sicherzustellen, dass er immer\n    konform und gesichert bleibt. Dieser Code wird kontinuierlich in der\n    temporären Umgebung `review/feature_branch` mit dem Namen des aktuellen\n    Branches bereitgestellt. Dies ist eine sichere Umgebung, in der die\n    Entwickler(innen) und IT-Betriebsteams ihren Code ohne Auswirkungen auf\n    andere testen können. Hier setzen wir auch den Prozess durch, z. B. indem\n    Code Reviews durchgesetzt und Scanner ausgeführt werden, damit die Qualität\n    und Sicherheit des Codes akzeptabel sind und deine Assets nicht gefährdet\n    werden. Die von diesem Branch bereitgestellte Infrastruktur wird automatisch\n    zerstört, wenn der Branch geschlossen wird. So behältst du dein Budget unter\n    Kontrolle.\n\n\n    \u003Cpre class=\\\"mermaid\\\">\n\n    flowchart LR\n\n    \\    A(main) -->|new feature| B(feature_X)\n\n\n    \\    B -->|auto deploy| C[review/feature_X]\n\n    \\    B -->|merge| D(main)\n\n    \\    C -->|destroy| D\n\n    \u003C/pre>\n\n\n    2. Nach der Genehmigung wird der Feature-Branch mit dem Main-Branch\n    **zusammengeführt**. Dies ist ein [geschützter\n    Branch](https://docs.gitlab.com/user/project/protected_branches/), in\n    den niemand pushen kann. Dies ist obligatorisch, um sicherzustellen, dass\n    jede Änderungsanfrage an die Produktion gründlich getestet wird. Dieser\n    Branch wird auch kontinuierlich bereitgestellt. Das Ziel hier ist die\n    Umgebung `integration`. Damit diese Umgebung etwas stabiler bleibt, wird das\n    Löschen nicht automatisiert, sondern kann manuell ausgelöst werden.\n\n\n    \u003Cpre class=\\\"mermaid\\\">\n\n    flowchart LR\n\n    \\    D(main) -->|auto deploy| E[integration]\n\n    \u003C/pre>\n\n\n    3. Von dort aus ist eine manuelle Genehmigung erforderlich, um die nächste\n    Bereitstellung auszulösen. Dadurch wird der Haupt-Branch in der Umgebung\n    `qa` bereitgestellt. Hier habe ich eine Regel festgelegt, um das Löschen aus\n    der Pipeline zu verhindern. Diese Umgebung sollte ziemlich stabil sein\n    (schließlich ist es bereits die dritte Umgebung), und ich möchte das\n    versehentliche Löschen verhindern. Du kannst die Regeln gerne an deine\n    Prozesse anpassen.\n\n\n    \u003Cpre class=\\\"mermaid\\\">\n\n    flowchart LR\n\n    \\    D(main)-->|auto deploy| E[integration]\n\n    \\    E -->|manual| F[qa]\n\n    \u003C/pre>\n\n\n    4. Um fortzufahren, müssen wir den Code **taggen**. Wir setzen hier auf\n    [geschützte\n    Tags](https://docs.gitlab.com/user/project/protected_tags/), um\n    sicherzustellen, dass nur eine bestimmte Gruppe von Benutzer(inne)n in diese\n    letzten beiden Umgebungen bereitstellen darf. Dadurch wird sofort eine\n    Bereitstellung in der Umgebung `staging` ausgelöst.\n\n\n    \u003Cpre class=\\\"mermaid\\\">\n\n    flowchart LR\n\n    \\    D(main) -->|tag| G(X.Y.Z)\n\n    \\    F[qa] -->|validate| G\n\n\n    \\    G -->|auto deploy| H[staging]\n\n    \u003C/pre>\n\n\n\n    5. Schließlich landen wir bei `production`. Wenn es um Infrastruktur geht,\n    ist es oft schwierig, sie schrittweise bereitzustellen (10 %, 25 % usw.),\n    sodass wir gleich die gesamte Infrastruktur bereitstellen werden. Dennoch\n    steuern wir diese Bereitstellung, indem dieser letzte Schritt manuell\n    ausgelöst wird. Und um maximale Kontrolle über diese hochkritische Umgebung\n    zu erzwingen, werden wir sie als [geschützte\n    Umgebung](https://docs.gitlab.com/ci/environments/protected_environments/\n    .html) kontrollieren.\n\n\n    \u003Cpre class=\\\"mermaid\\\">\n\n    flowchart LR\n\n    \\    H[staging] -->|manual| I{plan}\n\n    \\    I -->|manual| J[production]\n\n    \u003C/pre>\n\n\n    ### Die Pipeline\n\n\n    Um den oben genannten [Workflow](#the-workflow) zu implementieren,\n    implementieren wir jetzt eine Pipeline mit zwei\n    [Downstream-Pipelines](https://docs.gitlab.com/ci/pipelines/downstream_p/\n    ipelines.html).\n\n\n    #### Die Haupt-Pipeline\n\n\n    Beginnen wir mit der Haupt-Pipeline. Dies ist diejenige, die automatisch bei\n    jedem **Push zu einem Feature-Branch**, jedem Zusammenführen zum\n    Standard-Branch** oder jedem **Tag** ausgelöst wird. *Die Pipeline*, die\n    eine echte **kontinuierliche Bereitstellung** in den folgenden Umgebungen\n    durchführt: `dev`, `integration` und `staging`. Und das wird in der Datei\n    `.gitlab-ci.yml` im Stamm deines Projekts deklariert.\n\n\n    ![Repository-Ziel](https://res.cloudinary.com/about-gitlab-com/image/upload\\\n    /v1750097033/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_17\\\n    50097033417.png)\n\n\n    ```yml\n\n    Stages:\n\n    \\  - test\n\n    \\  - environments\n\n\n    .environment:\n\n    \\  stage: environments\n\n    \\  variables:\n\n    \\    TF_ROOT: terraform\n\n    \\    TF_CLI_ARGS_plan: \\\"-var-file=../vars/$variables_file.tfvars\\\"\n\n    \\  trigger:\n\n    \\    include: .gitlab-ci/.first-layer.gitlab-ci.yml\n\n    \\    strategy: depend             # Wait for the triggered pipeline to\n    successfully complete\n\n    \\    forward:\n\n    \\      yaml_variables: true      # Forward variables defined in the trigger\n    job\n\n    \\      pipeline_variables: true  # Forward manual pipeline variables and\n    scheduled pipeline variables\n\n\n    review:\n\n    \\  extends: .environment\n\n    \\  variables:\n\n    \\    environment: review/$CI_COMMIT_REF_SLUG\n\n    \\    TF_STATE_NAME: $CI_COMMIT_REF_SLUG\n\n    \\    variables_file: review\n\n    \\    TF_VAR_aws_resources_name: $CI_COMMIT_REF_SLUG  # Used in the tag Name\n    of the resources deployed, to easily differenciate them\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\n\n    integration:\n\n    \\  extends: .environment\n\n    \\  variables:\n\n    \\    environment: integration\n\n    \\    TF_STATE_NAME: $environment\n\n    \\    variables_file: $environment\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n\n    staging:\n\n    \\  extends: .environment\n\n    \\  variables:\n\n    \\    environment: staging\n\n    \\    TF_STATE_NAME: $environment\n\n    \\    variables_file: $environment\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG\n\n\n    #### TWEAK\n\n    # This tweak is needed to display vulnerability results in the merge\n    widgets.\n\n    # As soon as this issue https://gitlab.com/gitlab-org/gitlab/-/issues/439700\n    is resolved, the `include` instruction below can be removed.\n\n    # Until then, the SAST IaC scanners will run in the downstream pipelines,\n    but their results will not be available directly in the merge request\n    widget, making it harder to track them.\n\n    # Note: This workaround is perfectly safe and will not slow down your\n    pipeline.\n\n    include:\n\n    \\  - template: Security/SAST-IaC.gitlab-ci.yml\n\n    #### END TWEAK\n\n\n    ```\n\n\n    Diese Pipeline läuft nur in zwei Phasen: `test` und `environments`. Erstere\n    wird benötigt, damit der *TWEAK* Scanner ausführen kann. Zweitere löst eine\n    untergeordnete Pipeline mit einem anderen Satz von Variablen für jeden oben\n    definierten Fall aus (Push zum Branch, Zusammenführen zum Standard-Branch\n    oder Tag).\n\n\n    Wir fügen hier eine Abhängigkeit mit dem Schlüsselwort\n    [strategy:depend](https://docs.gitlab.com/ci/yaml/#triggerstra\\\n    tegy) zu unserer untergeordneten Pipeline hinzu, sodass die Pipeline-Ansicht\n    in GitLab erst aktualisiert wird, wenn die Bereitstellung abgeschlossen ist.\n\n\n    Wie du hier sehen kannst, definieren wir einen Basisjob,\n    [hidden](https://docs.gitlab.com/ci/jobs/#hide-jobs), und erweitern ihn\n    um bestimmte Variablen und Regeln, um nur eine Bereitstellung für jede\n    Zielumgebung auszulösen.\n\n\n    Neben den [vordefinierten\n    Variablen](https://docs.gitlab.com/ci/variables/predefined_variables.htm/\n    l) verwenden wir zwei neue Einträge, die wir definieren müssen:\n\n    1. [Die für jede Umgebung spezifischen\n    Variablen](#the-variable-definitions): `../vars/$variables_file.tfvars`\n\n    2. [Die untergeordnete Pipeline](#the-child-pipeline), definiert in\n    `.gitlab-ci/.first-layer.gitlab-ci.yml`\n\n\n    Beginnen wir mit dem kleinsten Teil, den Variablendefinitionen.\n\n\n    ### Die Variablendefinitionen\n\n\n    Wir werden hier zwei Lösungen mischen, um Terraform Variablen zur Verfügung\n    zu stellen:\n\n\n    * Die erste nutzt\n    [.tfvars-Dateien](https://developer.hashicorp.com/terraform/language/values\\\n    /variables#variable-definitions-tfvars-files) für alle nicht sensiblen\n    Eingaben, die in GitLab gespeichert werden sollten.\n\n\n    ![Lösung 1 zur Bereitstellung von Variablen für\n    Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v175009\\\n    7034/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097033\\\n    419.png)\n\n\n    * Die zweite verwendet\n    [Umgebungsvariablen](https://developer.hashicorp.com/terraform/language/val\\\n    ues/variables#environment-variables) mit dem Präfix `TF_VAR`. Diese zweite\n    Möglichkeit, Variablen zu injizieren, die mit der GitLab-Fähigkeit verbunden\n    sind, [Variablen zu\n    maskieren](https://docs.gitlab.com/ci/variables/#mask-a-cicd-variable),\n    [sie zu\n    schützen](https://docs.gitlab.com/ci/variables/#protect-a-cicd-variable)\n    und [sie in Umgebungen zu\n    übertragen](https://docs.gitlab.com/ci/environments/#limit-the\\\n    -environment-scope-of-a-cicd-variable), ist eine leistungsstarke Lösung, um\n    **Datenlecks sensibler Informationen zu verhindern**. (Wenn du das private\n    CIDR deiner Produktion als sehr sensibel betrachtest, könntest du es so\n    schützen, indem du sicherstellst, dass es nur für die Umgebung `production`\n    verfügbar ist, für Pipelines, die gegen geschützte Branches und Tags\n    ausgeführt werden, und dass sein Wert in den Protokollen des Jobs maskiert\n    ist.)\n\n\n    ![Lösung 2 zur Bereitstellung von Variablen für\n    Terraform](https://res.cloudinary.com/about-gitlab-com/image/upload/v175009\\\n    7034/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097033\\\n    422.png)\n\n\n    Darüber hinaus sollte jede Variablendatei über eine [Datei\n    `CODEOWNERS`](https://docs.gitlab.com/user/project/codeowners/) gesteuert\n    werden, um festzulegen, wer sie ändern darf.\n\n\n    ```text\n\n    [Production owners]\\\n\n    vars/production.tfvars @operations-group\n\n\n    [Staging owners]\n\n    vars/staging.tfvars @odupre @operations-group\n\n\n    [CodeOwners owners]\n\n    CODEOWNERS @odupre\n\n    ```\n\n\n    Dieser Artikel ist kein Terraform-Training, daher halten wir das kurz und\n    zeigen hier einfach die Datei `vars/review.tfvars`. Die nachfolgenden\n    Umgebungsdateien sind sich natürlich sehr ähnlich. Lege hier einfach die\n    nicht-sensiblen Variablen und ihre Werte fest.\n\n\n    ```shell\n\n    aws_vpc_cidr = \\\"10.1.0.0/16\\\"\n\n    aws_public_subnet_cidr = \\\"10.1.1.0/24\\\"\n\n    aws_private_subnet_cidr = \\\"10.1.2.0/24\\\"\n\n    ```\n\n\n    #### Die untergeordnete Pipeline\n\n\n    Hier wird die eigentliche Arbeit erledigt. Sie ist also etwas komplexer als\n    die erste Pipeline. Es gibt aber auch hier keine Schwierigkeit, die wir\n    nicht gemeinsam überwinden können!\n\n\n    Wie wir bei der Definition der [Haupt-Pipeline](#the-main-pipeline) gesehen\n    haben, wird diese Downstream-Pipeline in der Datei\n    `.gitlab-ci/.first-layer.gitlab-ci.yml` deklariert.\n\n\n    ![In Datei deklarierte\n    Downstream-Pipeline](https://res.cloudinary.com/about-gitlab-com/image/uplo\\\n    ad/v1750097033/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_\\\n    1750097033424.png)\n\n\n    Zerlegen wir sie in kleine Stücke. Am Ende sehen wir dann das große Ganze.\n\n\n    ##### Terraform-Befehle ausführen und den Code sichern\n\n\n    Zuerst wollen wir eine Pipeline für Terraform ausführen. GitLab ist Open\n    Source. Unsere Terraform-Vorlage ist also auch Open Source. Du kannst sie\n    einfach einbeziehen. Dies erreichst du mit folgendem Code-Schnipsel:\n\n\n    ```yml\n\n    include:\n\n    \\  - template: Terraform.gitlab-ci.yml\n\n    ```\n\n\n    Diese Vorlage führt für dich die Terraform-Prüfungen für die Formatierung\n    durch und validiert deinen Code, bevor er geplant und angewendet wird. Es\n    ermöglicht dir auch, das zu zerstören, was du bereitgestellt hast.\n\n\n    Da GitLab eine vereinheitlichte DevSecOps-Plattform ist, fügen wir dieser\n    Vorlage automatisch zwei Sicherheitsscanner hinzu, um potenzielle\n    Bedrohungen in deinem Code zu finden und dich zu warnen, bevor du ihn in den\n    nächsten Umgebungen bereitstellst.\n\n\n    Jetzt, da wir unseren Code überprüft, gesichert, erstellt und bereitgestellt\n    haben, folgen ein paar Tricks.\n\n\n    ##### Zwischenspeicher zwischen Jobs teilen\n\n\n    Wir werden Job-Ergebnisse zwischenspeichern, um sie in folgenden\n    Pipeline-Jobs wiederzuverwenden. Dies ist einfach, denn du musst nur den\n    folgenden Code hinzufügen:\n\n\n    ```yml\n\n    default:\n\n    \\  cache:  # Use a shared cache or tagged runners to ensure terraform can\n    run on apply and destroy\n\n    \\    - key: cache-$CI_COMMIT_REF_SLUG\n\n    \\      fallback_keys:\n\n    \\        - cache-$CI_DEFAULT_BRANCH\n\n    \\      paths:\n\n    \\        - .\n\n\n    ```\n\n\n    Hier definieren wir einen anderen Zwischenspeicher für jeden Commit und\n    greifen bei Bedarf auf den Namen des Haupt-Branchs zurück.\n\n\n    Wenn wir uns die Vorlagen, die wir verwenden, genau ansehen, stellen wir\n    fest, dass sie einige Regeln haben, die zu kontrollieren sind, wenn Jobs\n    ausgeführt werden. Wir wollen alle Kontrollen (sowohl QA als auch\n    Sicherheit) in allen Branchen ausführen. Wir werden diese Einstellungen also\n    überschreiben.\n\n\n    ##### Kontrollen in allen Branches ausführen\n\n\n    GitLab-Vorlagen sind eine leistungsstarke Funktion, bei der man auch nur\n    einen Teil der Vorlage überschreiben kann. Hier wollen wir die Regeln\n    einiger Jobs überschreiben, um immer Qualitäts- und Sicherheitskontrollen\n    durchzuführen. Alles andere, was für diese Jobs definiert ist, bleibt wie in\n    der Vorlage definiert.\n\n\n    ```yml\n\n    fmt:\n\n    \\  rules:\n\n    \\    - when: always\n\n\n    validate:\n\n    \\  rules:\n\n    \\    - when: always\n\n\n    kics-iac-sast:\n\n    \\  rules:\n\n    \\    - when: always\n\n\n    iac-sast:\n\n    \\  rules:\n\n    \\    - when: always\n\n    ```\n\n\n    Da wir nun die Qualitäts- und Sicherheitskontrollen durchgesetzt haben,\n    wollen wir unterscheiden, wie sich die Hauptumgebungen (Integration und\n    Staging) im [Workflow](#the-workflow) und Review-Umgebungen verhalten.\n    Beginnen wir mit der Definition des Verhaltens der Hauptumgebung. Wir werden\n    dann diese Konfiguration für die Review-Umgebungen optimieren.\n\n\n    ##### CD für Integration und Staging\n\n\n    Wie zuvor definiert, möchten wir den Haupt-Branch und die Tags in diesen\n    beiden Umgebungen bereitstellen. Wir fügen Regeln hinzu, um das sowohl bei\n    den Jobs `build` als auch `deploy` zu kontrollieren. Dann wollen wir\n    `destroy` nur für `integration` aktivieren, da wir definiert haben, dass\n    `staging` zu kritisch ist, um mit einem einzigen Klick gelöscht zu werden.\n    Das ist fehleranfällig, was wir nicht wollen.\n\n\n    Schließlich verknüpfen wir den Job `deploy` mit dem Job `destroy`, damit wir\n    die Umgebung direkt von der GitLab-GUI aus mit `stop` stoppen können.\n\n\n    Die `GIT_STRATEGY` soll verhindern, dass der Code beim Zerstören aus dem\n    Quell-Branch im Runner abgerufen wird. Dies würde fehlschlagen, wenn der\n    Branch manuell gelöscht wurde. Daher verlassen wir uns auf den\n    Zwischenspeicher, um alles zu erhalten, was wir zum Ausführen der\n    Terraform-Anweisungen benötigen.\n\n\n    ```yml\n\n    build:  # terraform plan\n\n    \\  environment:\n\n    \\    name: $TF_STATE_NAME\n\n    \\    action: prepare\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n    \\    - if: $CI_COMMIT_TAG\n\n\n    deploy: # terraform apply --> automatically deploy on corresponding env\n    (integration or staging) when merging to default branch or tagging. Second\n    layer environments (qa and production) will be controlled manually\n\n    \\  environment:\\\n\n    \\  environment:\\\n\n    \\    name: $TF_STATE_NAME\n\n    \\    action: start\n\n    \\    on_stop: destroy\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n    \\    - if: $CI_COMMIT_TAG\n\n\n    destroy:\n\n    \\  extends: .terraform:destroy\n\n    \\  variables:\n\n    \\    GIT_STRATEGY: none\n\n    \\  dependencies:\n\n    \\    - build\n\n    \\  environment:\n\n    \\    name: $TF_STATE_NAME\n\n    \\    action: stop\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG  # Do not destroy production\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY ==\n    \\\"true\\\" # Manually destroy integration env.\n\n    \\      when: manual\n\n    ```\n\n\n    Wie gesagt müssen diese Matches in `integration` und `staging`\n    bereitstellen. Uns fehlt jedoch immer noch eine temporäre Umgebung, in der\n    die Entwickler(innen) ihren Code ohne Auswirkungen auf andere erleben und\n    validieren können. Hier findet die Bereitstellung in der Umgebung `review`\n    statt.\n\n\n    ##### CD für Review-Umgebungen\n\n\n    Die Bereitstellung in der Review-Umgebung unterscheidet sich nicht allzu\n    sehr von der Bereitstellung in `integration` und `staging`. Wir werden also\n    erneut die Möglichkeit von GitLab nutzen, hier nur Teile der Jobdefinition\n    zu überschreiben.\n\n\n    Zuerst legen wir Regeln fest, um diese Jobs nur in Feature-Branches\n    auszuführen.\n\n\n    Dann verknüpfen wir den Job `deploy_review` mit `destroy_review`. Dies\n    ermöglicht es uns, die Umgebung **manuell** von der GitLab-Bedienoberfläche\n    aus zu stoppen und – was noch wichtiger ist – es wird **automatisch die\n    Zerstörung der Umgebung ausgelöst**, wenn der Feature-Branch geschlossen\n    wird. Dies ist eine gute FinOps-Praxis, um dir zu helfen, deine\n    Betriebsausgaben zu kontrollieren.\n\n\n    Da Terraform eine Plandatei benötigt, um eine Infrastruktur zu zerstören\n    (genau wie es eine solche Datei benötigt, um eine Infrastruktur aufzubauen),\n    fügen wir eine Abhängigkeit von `destroy_review` zu `build_review` hinzu, um\n    Artefakte abzurufen.\n\n\n    Schließlich sehen wir hier, dass der Name der Umgebung auf `$environment`\n    festgelegt ist. Es wurde in der [Haupt-Pipeline](#the-main-pipeline) auf\n    `review/$CI_COMMIT_REF_SLUG` gesetzt und mit der Anweisung\n    `trigger:forward:yaml_variables:true` an diese untergeordnete Pipeline\n    weitergeleitet.\n\n\n    ```yml\n\n    build_review:\n\n    \\  extends: build\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\n    \\      when: on_success\n\n\n    deploy_review:\n\n    \\  extends: deploy\n\n    \\  dependencies:\n\n    \\    - build_review\n\n    \\  environment:\n\n    \\    name: $environment\n\n    \\    action: start\n\n    \\    on_stop: destroy_review\n\n    \\    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\n    \\      when: on_success\n\n\n    destroy_review:\n\n    \\  extends: destroy\n\n    \\  dependencies:\n\n    \\    - build_review\n\n    \\  environment:\n\n    \\    name: $environment\n\n    \\    action: stop\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG  # Do not destroy production\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy\n    staging\n\n    \\      when: never\n\n    \\    - when: manual\n\n    ```\n\n\n    Zusammenfassend können wir also sagen, dass wir jetzt eine Pipeline haben,\n    die Folgendes kann:\n\n\n    * Temporäre Review-Umgebungen bereitstellen, die automatisch gelöscht\n    werden, wenn der Feature-Branch geschlossen wird\n\n    * Den **Standard-Branch** kontinuierlich auf `integration` bereitstellen\n\n    * Die **Tags** kontinuierlich auf `staging` bereitstellen\n\n\n    Fügen wir nun eine zusätzliche Ebene hinzu, auf der wir diesmal mit einem\n    manuellen Auslöser in den Umgebungen `qa` und `production` bereitstellen\n    werden.\n\n\n    ##### Kontinuierliche Bereitstellung in QA und Produktion\n\n\n    Da nicht jedes Unternehmen kontinuierlich in der Produktion bereitstellen\n    möchte, fügen wir den nächsten beiden Bereitstellungen eine manuelle\n    Validierung hinzu. Aus einer reinen **CD**-Perspektive würden wir diesen\n    Auslöser nicht hinzufügen, aber betrachte dies als Gelegenheit, zu lernen,\n    wie man Jobs von anderen Auslösern aus ausführt.\n\n\n    Bisher haben wir eine [untergeordnete Pipeline](#the-child-pipeline) aus der\n    [Haupt-Pipeline](#the-main-pipeline) gestartet, um alle Bereitstellungen\n    auszuführen.\n\n\n    Da wir andere Bereitstellungen aus dem Standard-Branch und den Tags\n    ausführen möchten, fügen wir eine weitere Ebene für diese zusätzlichen\n    Schritte hinzu. Hier gibt es nichts Neues. Wir wiederholen einfach genau\n    das, was wir nur für die [Haupt-Pipeline](#the-main-pipeline) gemacht haben.\n    Auf diese Weise kannst du so viele Ebenen bearbeiten, wie du brauchst. Ich\n    habe schon einmal bis zu neun Umgebungen gesehen.\n\n\n    Wir wollen hier nicht über die Vorteile diskutieren, die es mit sich bringt,\n    weniger Umgebungen zu haben. Der hier verwendete Prozess macht es jedenfalls\n    sehr einfach, die gleiche Pipeline von der Anfangsphase bis zur endgültigen\n    Lieferung zu implementieren, während deine Pipeline-Definition einfach und\n    in kleine, einfach zu wartende Teile aufgeteilt bleibt.\n\n\n    Um hier Variablenkonflikte zu vermeiden, verwenden wir nur neue\n    Variablennamen, um den Terraform-Status und die Eingabedatei zu\n    identifizieren.\n\n\n    ```yml\n\n    .2nd_layer:\n\n    \\  stage: 2nd_layer\n\n    \\  variables:\n\n    \\    TF_ROOT: terraform\n\n    \\  trigger:\n\n    \\    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n\n    \\    # strategy: depend            # Do NOT wait for the downstream pipeline\n    to finish to mark upstream pipeline as successful. Andernfalls schlagen alle\n    Pipelines fehl, wenn eine Pipeline-Zeitüberschreitung vor der Bereitstellung\n    auf die 2. Ebene erreicht wurde.\n\n    \\    forward:\n\n    \\      yaml_variables: true      # Forward variables defined in the trigger\n    job\n\n    \\      pipeline_variables: true  # Forward manual pipeline variables and\n    scheduled pipeline variables\n\n\n    qa:\n\n    \\  extends: .2nd_layer\n\n    \\  variables:\n\n    \\    TF_STATE_NAME_2: qa\n\n    \\    environment: $TF_STATE_NAME_2\n\n    \\    TF_CLI_ARGS_plan_2: \\\"-var-file=../vars/$TF_STATE_NAME_2.tfvars\\\"\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n\n    production:\n\n    \\  extends: .2nd_layer\n\n    \\  variables:\n\n    \\    TF_STATE_NAME_2: production\n\n    \\    environment: $TF_STATE_NAME_2\n\n    \\    TF_CLI_ARGS_plan_2: \\\"-var-file=../vars/$TF_STATE_NAME_2.tfvars\\\"\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG\n\n    ```\n\n\n    **Ein wichtiger Trick ist hier die Strategie, die für die neue\n    Downstream-Pipeline verwendet wird.** Wir belassen `trigger:strategy` auf\n    ihrem Standardwert. Andernfalls würde die\n    [Haupt-Pipeline](#the-main-pipeline) warten, bis deine [Pipeline der zweiten\n    Ebene](#the-grand-child-pipeline) abgeschlossen ist. Bei einem manuellen\n    Auslöser kann dies sehr lange dauern und das Lesen und Verstehen deines\n    Pipeline-Dashboards erschweren.\n\n\n    Du hast dich wahrscheinlich schon gefragt, was der Inhalt der Datei\n    `.gitlab-ci/.second-layer.gitlab-ci.yml` ist, die wir hier anführen. Wir\n    gehen im nächsten Abschnitt darauf ein.\n\n\n    ##### Vollständige Pipeline-Definition auf der ersten Ebene\n\n\n    Wenn du eine vollständige Ansicht dieser ersten Ebene möchtest (gespeichert\n    in `.gitlab-ci/.first-layer.gitlab-ci.yml`), erweitere einfach den Abschnitt\n    unten.\n\n\n    ```yml\n\n    variables:\n\n    \\  TF_VAR_aws_ami_id: $AWS_AMI_ID\n\n    \\  TF_VAR_aws_instance_type: $AWS_INSTANCE_TYPE\n\n    \\  TF_VAR_aws_default_region: $AWS_DEFAULT_REGION\n\n\n    include:\n\n    \\  - template: Terraform.gitlab-ci.yml\n\n\n    default:\n\n    \\  cache:  # Use a shared cache or tagged runners to ensure terraform can\n    run on apply and destroy\n\n    \\    - key: cache-$CI_COMMIT_REF_SLUG\n\n    \\      fallback_keys:\n\n    \\        - cache-$CI_DEFAULT_BRANCH\n\n    \\      paths:\n\n    \\        - .\n\n\n    stages:\n\n    \\  - validate\n\n    \\  - test\n\n    \\  - build\n\n    \\  - deploy\n\n    \\  - cleanup\n\n    \\  - 2nd_layer       # Use to deploy a 2nd environment on both the main\n    branch and on the tags\n\n\n    fmt:\n\n    \\  rules:\n\n    \\    - when: always\n\n\n    validate:\n\n    \\  rules:\n\n    \\    - when: always\n\n\n    kics-iac-sast:\n\n    \\  rules:\n\n    \\    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n\n    \\      when: never\n\n    \\    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n\n    \\      when: never\n\n    \\    - when: on_success\n\n\n    iac-sast:\n\n    \\  rules:\n\n    \\    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n\n    \\      when: never\n\n    \\    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n\n    \\      when: never\n\n    \\    - when: on_success\n\n\n    ###########################################################################\\\n    ################################\n\n    ## Integration env. and Staging. env\n\n    ##  * Auto-deploy to Integration on merge to main.\n\n    ##  * Auto-deploy to Staging on tag.\n\n    ##  * Integration can be manually destroyed if TF_DESTROY is set to true.\n\n    ##  * Destroy of next env. is not automated to prevent errors.\n\n    ###########################################################################\\\n    ################################\n\n    build:  # terraform plan\n\n    \\  environment:\n\n    \\    name: $TF_STATE_NAME\n\n    \\    action: prepare\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n    \\    - if: $CI_COMMIT_TAG\n\n\n    deploy: # terraform apply --> automatically deploy on corresponding env\n    (integration or staging) when merging to default branch or tagging. Second\n    layer environments (qa and production) will be controlled manually\n\n    \\  environment:\\\n\n    \\    name: $TF_STATE_NAME\n\n    \\    action: start\n\n    \\    on_stop: destroy\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n    \\    - if: $CI_COMMIT_TAG\n\n\n    destroy:\n\n    \\  extends: .terraform:destroy\n\n    \\  variables:\n\n    \\    GIT_STRATEGY: none\n\n    \\  dependencies:\n\n    \\    - build\n\n    \\  environment:\n\n    \\    name: $TF_STATE_NAME\n\n    \\    action: stop\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG  # Do not destroy production\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $TF_DESTROY ==\n    \\\"true\\\" # Manually destroy integration env.\n\n    \\      when: manual\n\n    ###########################################################################\\\n    ################################\n\n\n    ###########################################################################\\\n    ################################\n\n    ## Dev env.\n\n    ##  * Temporary environment. Lives and dies with the Merge Request.\n\n    ##  * Auto-deploy on push to feature branch.\n\n    ##  * Auto-destroy on when Merge Request is closed.\n\n    ###########################################################################\\\n    ################################\n\n    build_review:\n\n    \\  extends: build\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\n    \\      when: on_success\n\n\n    deploy_review:\n\n    \\  extends: deploy\n\n    \\  dependencies:\n\n    \\    - build_review\n\n    \\  environment:\n\n    \\    name: $environment\n\n    \\    action: start\n\n    \\    on_stop: destroy_review\n\n    \\    # url: https://$CI_ENVIRONMENT_SLUG.example.com\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH\n\n    \\      when: on_success\n\n\n    destroy_review:\n\n    \\  extends: destroy\n\n    \\  dependencies:\n\n    \\    - build_review\n\n    \\  environment:\n\n    \\    name: $environment\n\n    \\    action: stop\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG  # Do not destroy production\n\n    \\      when: never\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # Do not destroy\n    staging\n\n    \\      when: never\n\n    \\    - when: manual\n\n    ###########################################################################\\\n    ################################\n\n\n    ###########################################################################\\\n    ################################\n\n    ## Second layer\n\n    ##  * Deploys from main branch to qa env.\n\n    ##  * Deploys from tag to production.\n\n    ###########################################################################\\\n    ################################\n\n    .2nd_layer:\n\n    \\  stage: 2nd_layer\n\n    \\  variables:\n\n    \\    TF_ROOT: terraform\n\n    \\  trigger:\n\n    \\    include: .gitlab-ci/.second-layer.gitlab-ci.yml\n\n    \\    # strategy: depend            # Do NOT wait for the downstream pipeline\n    to finish to mark upstream pipeline as successful. Otherwise, all pipelines\n    will fail when reaching the pipeline timeout before deployment to 2nd layer.\n\n    \\    forward:\n\n    \\      yaml_variables: true      # Forward variables defined in the trigger\n    job\n\n    \\      pipeline_variables: true  # Forward manual pipeline variables and\n    scheduled pipeline variables\n\n\n    qa:\n\n    \\  extends: .2nd_layer\n\n    \\  variables:\n\n    \\    TF_STATE_NAME_2: qa\n\n    \\    environment: $TF_STATE_NAME_2\n\n    \\    TF_CLI_ARGS_plan_2: \\\"-var-file=../vars/$TF_STATE_NAME_2.tfvars\\\"\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n\n    production:\n\n    \\  extends: .2nd_layer\n\n    \\  variables:\n\n    \\    TF_STATE_NAME_2: production\n\n    \\    environment: $TF_STATE_NAME_2\n\n    \\    TF_CLI_ARGS_plan_2: \\\"-var-file=../vars/$TF_STATE_NAME_2.tfvars\\\"\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_TAG\n\n    ###########################################################################\\\n    ################################\n\n    ```\n\n\n    In dieser Phase stellen wir bereits sicher in drei Umgebungen bereit. Das\n    ist meine persönliche Idealempfehlung. Wenn du jedoch mehr Umgebungen\n    benötigst, füge diese deiner CD-Pipeline hinzu.\n\n\n    Du hast sicherlich schon bemerkt, dass wir eine Downstream-Pipeline mit dem\n    Stichwort `trigger:include` einbinden. Dazu gehört die Datei\n    `.gitlab-ci/.second-layer.gitlab-ci.yml`. Da wir fast die gleiche Pipeline\n    ausführen wollen, ist ihr Inhalt offensichtlich sehr ähnlich zu dem, den wir\n    oben detailliert beschrieben haben. Der Hauptvorteil bei der Definition\n    dieser [Pipeline der zweiten Ebene](#the-grand-child-pipeline) ist, dass sie\n    allein besteht, was die Definition von Variablen und Regeln erleichtert.\n\n\n    ### Die Pipeline der zweiten Ebene\n\n\n    Diese Pipeline der zweiten Ebene ist eine brandneue Pipeline. Daher muss es\n    die Definition der ersten Ebene nachahmen mit:\n\n\n    * [Aufnahme der\n    Terraform-Vorlage](#run-terraform-commands-and-secure-the-code).\n\n    * [Durchsetzung von Sicherheitskontrollen](#run-controls-on-all-branches).\n    Bei der Terraform-Validierung handelt es sich um Duplikate der ersten Ebene.\n    Sicherheitsscanner können jedoch Bedrohungen finden, die noch nicht\n    vorhanden waren, als die Scanner zuvor ausgeführt wurden (z. B. wenn du\n    einige Tage nach der Bereitstellung im Staging in der Produktion\n    bereitstellst).\n\n    * [Überschreiben von Build- und Bereitstellungs-Jobs, um spezifische Regeln\n    festzulegen](#cd-to-review-environments). Beachte bitte, dass die Phase\n    `destroy` nicht mehr automatisiert ist, um zu schnelle Löschvorgänge zu\n    verhindern.\n\n\n    Wie oben erläutert, wurden `TF_STATE_NAME` und `TF_CLI_ARGS_plan` von der\n    [Haupt-Pipeline](# the-main-pipeline) zur [untergeordneten\n    Pipeline](#the-child-pipeline) bereitgestellt. Wir brauchten einen weiteren\n    Variablennamen, um diese Werte von der [untergeordneten\n    Pipeline](#the-child-pipeline) hierher, also an die [Pipeline der zweiten\n    Ebene](#the-grand-child-pipeline), zu übergeben. Deshalb werden sie in der\n    untergeordneten Pipeline mit dem Postfix `_2` versehen und der Wert wird\n    während des `before_script` hier zurück in die entsprechende Variable\n    kopiert.\n\n\n    Da wir oben bereits jeden Schritt aufgeschlüsselt haben, können wir hier\n    direkt auf die breite Ansicht der globalen Definition der zweiten Ebene\n    zoomen (gespeichert in `.gitlab-ci/.second-layer.gitlab-ci.yml`).\n\n\n    ```yml\n\n    # Use to deploy a second environment on both the default branch and the\n    tags.\n\n\n    include:\n\n    \\  template: Terraform.gitlab-ci.yml\n\n\n    stages:\n\n    \\  - validate\n\n    \\  - test\n\n    \\  - build\n\n    \\  - deploy\n\n\n    fmt:\n\n    \\  rules:\n\n    \\    - when: never\n\n\n    validate:\n\n    \\  rules:\n\n    \\    - when: never\n\n\n    kics-iac-sast:\n\n    \\  rules:\n\n    \\    - if: $SAST_DISABLED == 'true' || $SAST_DISABLED == '1'\n\n    \\      when: never\n\n    \\    - if: $SAST_EXCLUDED_ANALYZERS =~ /kics/\n\n    \\      when: never\n\n    \\    - when: always\n\n\n    ###########################################################################\\\n    ################################\n\n    ## QA env. and Prod. env\n\n    ##  * Manually trigger build and auto-deploy in QA\n\n    ##  * Manually trigger both build and deploy in Production\n\n    ##  * Destroy of these env. is not automated to prevent errors.\n\n    ###########################################################################\\\n    ################################\n\n    build:  # terraform plan\n\n    \\  cache:  # Use a shared cache or tagged runners to ensure terraform can\n    run on apply and destroy\n\n    \\    - key: $TF_STATE_NAME_2\n\n    \\      fallback_keys:\n\n    \\        - cache-$CI_DEFAULT_BRANCH\n\n    \\      paths:\n\n    \\        - .\n\n    \\  environment:\n\n    \\    name: $TF_STATE_NAME_2\n\n    \\    action: prepare\n\n    \\  before_script:  # Hack to set new variable values on the second layer,\n    while still using the same variable names. Otherwise, due to variable\n    precedence order, setting new value in the trigger job, does not cascade\n    these new values to the downstream pipeline\n\n    \\    - TF_STATE_NAME=$TF_STATE_NAME_2\n\n    \\    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n\n    \\  rules:\n\n    \\    - when: manual\n\n\n    deploy: # terraform apply\n\n    \\  cache:  # Use a shared cache or tagged runners to ensure terraform can\n    run on apply and destroy\n\n    \\    - key: $TF_STATE_NAME_2\n\n    \\      fallback_keys:\n\n    \\        - cache-$CI_DEFAULT_BRANCH\n\n    \\      paths:\n\n    \\        - .\n\n    \\  environment:\\\n\n    \\    name: $TF_STATE_NAME_2\n\n    \\    action: start\n\n    \\  before_script:  # Hack to set new variable values on the second layer,\n    while still using the same variable names. Otherwise, due to variable\n    precedence order, setting new value in the trigger job, does not cascade\n    these new values to the downstream pipeline\n\n    \\    - TF_STATE_NAME=$TF_STATE_NAME_2\n\n    \\    - TF_CLI_ARGS_plan=$TF_CLI_ARGS_plan_2\n\n    \\  rules:\n\n    \\    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH\n\n    \\    - if: $CI_COMMIT_TAG && $TF_AUTO_DEPLOY == \\\"true\\\"\n\n    \\    - if: $CI_COMMIT_TAG\n\n    \\      when: manual\n\n    ###########################################################################\\\n    ################################\n\n    ```\n\n\n    Et voilà. **Wir sind bereit.** Du kannst die Art und Weise ändern, wie du\n    deine Jobausführungen kontrollierst, indem du bspw. die Möglichkeit von\n    GitLab nutzt, [einen Job zu\n    verzögern](https://docs.gitlab.com/ci/jobs/job_control/#run-a-job-af\\\n    ter-a-delay), bevor du ihn in der Produktion bereitstellst.\n\n\n    ## Probiere es selbst\n\n\n    Wir haben endlich unser Ziel erreicht. Wir sind jetzt in der Lage,\n    **Bereitstellungen in fünf verschiedenen Umgebungen** zu kontrollieren,\n    wobei nur die **Feature-Branches**, der **Haupt-Branch** und **Tags**\n    verwendet werden.\n\n    * Wir verwenden Open-Source-Vorlagen von GitLab intensiv wieder, um\n    Effizienz und Sicherheit in unseren Pipelines zu gewährleisten.\n\n    * Wir nutzen GitLab-Vorlagen, um nur die Blöcke zu überschreiben, die eine\n    benutzerdefinierte Kontrolle benötigen.\n\n    * Wir haben die Pipeline in kleine Teile aufgeteilt und kontrollieren die\n    Downstream-Pipelines so, dass sie genau dem entsprechen, was wir brauchen.\n\n\n    Ab hier gehört die Bühne ganz dir. Du kannst beispielsweise die\n    Haupt-Pipeline einfach aktualisieren, um Downstream-Pipelines für deinen\n    Software-Quellcode mit dem Schlüsselwort\n    [trigger:rules:changes](https://docs.gitlab.com/ci/yaml/#ruleschanges)\n    auszulösen. Und verwende je nach den aufgetretenen Änderungen eine andere\n    [Vorlage](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/\\\n    templates/). Aber das ist eine andere Geschichte.\"\n  category: engineering\n  tags:\n    - CI/CD\n    - CI\n    - CD\n    - DevSecOps platform\n    - tutorial\n  updatedDate: '2024-11-05'\nconfig:\n  slug: using-child-pipelines-to-continuously-deploy-to-five-environments\n  featured: false\n  template: BlogPost\n",{"title":5,"description":17,"ogTitle":5,"ogDescription":17,"noIndex":14,"ogImage":19,"ogUrl":35,"ogSiteName":36,"ogType":37,"canonicalUrls":35},"https://about.gitlab.com/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","https://about.gitlab.com","article","de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments",[40,41,42,43,26],"cicd","ci","cd","devsecops-platform",[22,23,24,25,26],"1DNZl9mX6iXBBcrPOxiAK_9y9oX5KgG-KivtKKOxvQA",{"data":47},{"logo":48,"freeTrial":53,"sales":58,"login":63,"items":68,"search":377,"minimal":411,"duo":429,"switchNav":438,"pricingDeployment":449},{"config":49},{"href":50,"dataGaName":51,"dataGaLocation":52},"/de-de/","gitlab logo","header",{"text":54,"config":55},"Kostenlose Testversion anfordern",{"href":56,"dataGaName":57,"dataGaLocation":52},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de&glm_content=default-saas-trial/","free trial",{"text":59,"config":60},"Vertrieb kontaktieren",{"href":61,"dataGaName":62,"dataGaLocation":52},"/de-de/sales/","sales",{"text":64,"config":65},"Anmelden",{"href":66,"dataGaName":67,"dataGaLocation":52},"https://gitlab.com/users/sign_in/","sign in",[69,96,192,197,298,358],{"text":70,"config":71,"cards":73},"Plattform",{"dataNavLevelOne":72},"platform",[74,80,88],{"title":70,"description":75,"link":76},"Die intelligente Orchestrierungsplattform für DevSecOps",{"text":77,"config":78},"Die Plattform erkunden",{"href":79,"dataGaName":72,"dataGaLocation":52},"/de-de/platform/",{"title":81,"description":82,"link":83},"GitLab Duo Agent Platform","Agentische KI für den gesamten Software-Lebenszyklus",{"text":84,"config":85},"Lerne GitLab Duo kennen",{"href":86,"dataGaName":87,"dataGaLocation":52},"/de-de/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":89,"description":90,"link":91},"Warum GitLab?","Erfahre, warum sich Unternehmen für GitLab entscheiden",{"text":92,"config":93},"Mehr erfahren",{"href":94,"dataGaName":95,"dataGaLocation":52},"/de-de/why-gitlab/","why gitlab",{"text":97,"left":31,"config":98,"link":100,"lists":104,"footer":174},"Produkt",{"dataNavLevelOne":99},"solutions",{"text":101,"config":102},"Alle Lösungen anzeigen",{"href":103,"dataGaName":99,"dataGaLocation":52},"/de-de/solutions/",[105,129,152],{"title":106,"description":107,"link":108,"items":113},"Automatisierung","CI/CD und Automatisierung zur Beschleunigung der Bereitstellung",{"config":109},{"icon":110,"href":111,"dataGaName":112,"dataGaLocation":52},"AutomatedCodeAlt","/de-de/solutions/delivery-automation/","automated software delivery",[114,117,120,125],{"text":22,"config":115},{"href":116,"dataGaLocation":52,"dataGaName":22},"/de-de/solutions/continuous-integration/",{"text":81,"config":118},{"href":86,"dataGaLocation":52,"dataGaName":119},"gitlab duo agent platform - product menu",{"text":121,"config":122},"Quellcodeverwaltung",{"href":123,"dataGaLocation":52,"dataGaName":124},"/de-de/solutions/source-code-management/","Source Code Management",{"text":126,"config":127},"Automatische Softwarebereitstellung",{"href":111,"dataGaLocation":52,"dataGaName":128},"Automated software delivery",{"title":130,"description":131,"link":132,"items":137},"Sicherheit","Entwickle Code schneller ohne Abstriche bei der Sicherheit",{"config":133},{"href":134,"dataGaName":135,"dataGaLocation":52,"icon":136},"/de-de/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[138,142,147],{"text":139,"config":140},"Anwendungssicherheitstests",{"href":134,"dataGaName":141,"dataGaLocation":52},"Application security testing",{"text":143,"config":144},"Schutz der Software-Lieferkette",{"href":145,"dataGaLocation":52,"dataGaName":146},"/de-de/solutions/supply-chain/","Software supply chain security",{"text":148,"config":149},"Software-Compliance",{"href":150,"dataGaName":151,"dataGaLocation":52},"/de-de/solutions/software-compliance/","software compliance",{"title":153,"link":154,"items":159},"Auswertung",{"config":155},{"icon":156,"href":157,"dataGaName":158,"dataGaLocation":52},"DigitalTransformation","/de-de/solutions/visibility-measurement/","visibility and measurement",[160,164,169],{"text":161,"config":162},"Sichtbarkeit und Auswertung",{"href":157,"dataGaLocation":52,"dataGaName":163},"Visibility and Measurement",{"text":165,"config":166},"Wertstrommanagement",{"href":167,"dataGaLocation":52,"dataGaName":168},"/de-de/solutions/value-stream-management/","Value Stream Management",{"text":170,"config":171},"Analysen und Einblicke",{"href":172,"dataGaLocation":52,"dataGaName":173},"/de-de/solutions/analytics-and-insights/","Analytics and insights",{"title":175,"items":176},"GitLab für",[177,182,187],{"text":178,"config":179},"Enterprise",{"href":180,"dataGaLocation":52,"dataGaName":181},"/de-de/enterprise/","enterprise",{"text":183,"config":184},"Kleinunternehmen",{"href":185,"dataGaLocation":52,"dataGaName":186},"/de-de/small-business/","small business",{"text":188,"config":189},"Öffentlicher Sektor",{"href":190,"dataGaLocation":52,"dataGaName":191},"/de-de/solutions/public-sector/","public sector",{"text":193,"config":194},"Preise",{"href":195,"dataGaName":196,"dataGaLocation":52,"dataNavLevelOne":196},"/de-de/pricing/","pricing",{"text":198,"config":199,"link":201,"lists":205,"feature":285},"Ressourcen",{"dataNavLevelOne":200},"resources",{"text":202,"config":203},"Alle Ressourcen anzeigen",{"href":204,"dataGaName":200,"dataGaLocation":52},"/de-de/resources/",[206,239,257],{"title":207,"items":208},"Erste Schritte",[209,214,219,224,229,234],{"text":210,"config":211},"Installieren",{"href":212,"dataGaName":213,"dataGaLocation":52},"/de-de/install/","install",{"text":215,"config":216},"Kurzanleitungen",{"href":217,"dataGaName":218,"dataGaLocation":52},"/de-de/get-started/","quick setup checklists",{"text":220,"config":221},"Lernen",{"href":222,"dataGaLocation":52,"dataGaName":223},"https://university.gitlab.com/","learn",{"text":225,"config":226},"Produktdokumentation",{"href":227,"dataGaName":228,"dataGaLocation":52},"https://docs.gitlab.com/","product documentation",{"text":230,"config":231},"Best-Practice-Videos",{"href":232,"dataGaName":233,"dataGaLocation":52},"/de-de/getting-started-videos/","best practice videos",{"text":235,"config":236},"Integrationen",{"href":237,"dataGaName":238,"dataGaLocation":52},"/de-de/integrations/","integrations",{"title":240,"items":241},"Entdecken",[242,247,252],{"text":243,"config":244},"Kundenerfolge",{"href":245,"dataGaName":246,"dataGaLocation":52},"/de-de/customers/","customer success stories",{"text":248,"config":249},"Blog",{"href":250,"dataGaName":251,"dataGaLocation":52},"/de-de/blog/","blog",{"text":253,"config":254},"Remote",{"href":255,"dataGaName":256,"dataGaLocation":52},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":258,"items":259},"Vernetzen",[260,265,270,275,280],{"text":261,"config":262},"GitLab Services",{"href":263,"dataGaName":264,"dataGaLocation":52},"/de-de/services/","services",{"text":266,"config":267},"Community",{"href":268,"dataGaName":269,"dataGaLocation":52},"/community/","community",{"text":271,"config":272},"Forum",{"href":273,"dataGaName":274,"dataGaLocation":52},"https://forum.gitlab.com/","forum",{"text":276,"config":277},"Veranstaltungen",{"href":278,"dataGaName":279,"dataGaLocation":52},"/events/","events",{"text":281,"config":282},"Partner",{"href":283,"dataGaName":284,"dataGaLocation":52},"/de-de/partners/","partners",{"background":286,"textColor":287,"text":288,"image":289,"link":293},"#2f2a6b","#fff","Perspektiven für die Softwareentwicklung der Zukunft",{"altText":290,"config":291},"The Source Promo-Karte",{"src":292},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":294,"config":295},"Aktuelles",{"href":296,"dataGaName":297,"dataGaLocation":52},"/de-de/the-source/","the source",{"text":299,"config":300,"lists":302},"Unternehmen",{"dataNavLevelOne":301},"company",[303],{"items":304},[305,310,316,318,323,328,333,338,343,348,353],{"text":306,"config":307},"Über",{"href":308,"dataGaName":309,"dataGaLocation":52},"/de-de/company/","about",{"text":311,"config":312,"footerGa":315},"Karriere",{"href":313,"dataGaName":314,"dataGaLocation":52},"/jobs/","jobs",{"dataGaName":314},{"text":276,"config":317},{"href":278,"dataGaName":279,"dataGaLocation":52},{"text":319,"config":320},"Geschäftsführung",{"href":321,"dataGaName":322,"dataGaLocation":52},"/company/team/e-group/","leadership",{"text":324,"config":325},"Team",{"href":326,"dataGaName":327,"dataGaLocation":52},"/company/team/","team",{"text":329,"config":330},"Handbuch",{"href":331,"dataGaName":332,"dataGaLocation":52},"https://handbook.gitlab.com/","handbook",{"text":334,"config":335},"Investor Relations",{"href":336,"dataGaName":337,"dataGaLocation":52},"https://ir.gitlab.com/","investor relations",{"text":339,"config":340},"Trust Center",{"href":341,"dataGaName":342,"dataGaLocation":52},"/de-de/security/","trust center",{"text":344,"config":345},"AI Transparency Center",{"href":346,"dataGaName":347,"dataGaLocation":52},"/de-de/ai-transparency-center/","ai transparency center",{"text":349,"config":350},"Newsletter",{"href":351,"dataGaName":352,"dataGaLocation":52},"/company/contact/#contact-forms","newsletter",{"text":354,"config":355},"Presse",{"href":356,"dataGaName":357,"dataGaLocation":52},"/press/","press",{"text":359,"config":360,"lists":361},"Kontakt",{"dataNavLevelOne":301},[362],{"items":363},[364,367,372],{"text":59,"config":365},{"href":61,"dataGaName":366,"dataGaLocation":52},"talk to sales",{"text":368,"config":369},"Support-Portal",{"href":370,"dataGaName":371,"dataGaLocation":52},"https://support.gitlab.com","support portal",{"text":373,"config":374},"Kundenportal",{"href":375,"dataGaName":376,"dataGaLocation":52},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":378,"login":379,"suggestions":386},"Schließen",{"text":380,"link":381},"Um Repositorys und Projekte zu durchsuchen, melde dich an bei",{"text":382,"config":383},"gitlab.com",{"href":66,"dataGaName":384,"dataGaLocation":385},"search login","search",{"text":387,"default":388},"Vorschläge",[389,391,396,398,403,408],{"text":81,"config":390},{"href":86,"dataGaName":81,"dataGaLocation":385},{"text":392,"config":393},"Codevorschläge (KI)",{"href":394,"dataGaName":395,"dataGaLocation":385},"/de-de/solutions/code-suggestions/","Code Suggestions (AI)",{"text":22,"config":397},{"href":116,"dataGaName":22,"dataGaLocation":385},{"text":399,"config":400},"GitLab auf AWS",{"href":401,"dataGaName":402,"dataGaLocation":385},"/de-de/partners/technology-partners/aws/","GitLab on AWS",{"text":404,"config":405},"GitLab auf Google Cloud",{"href":406,"dataGaName":407,"dataGaLocation":385},"/de-de/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":89,"config":409},{"href":94,"dataGaName":410,"dataGaLocation":385},"Why GitLab?",{"freeTrial":412,"mobileIcon":417,"desktopIcon":422,"secondaryButton":425},{"text":413,"config":414},"Kostenlos testen",{"href":415,"dataGaName":57,"dataGaLocation":416},"https://gitlab.com/-/trials/new/","nav",{"altText":418,"config":419},"GitLab-Symbol",{"src":420,"dataGaName":421,"dataGaLocation":416},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":418,"config":423},{"src":424,"dataGaName":421,"dataGaLocation":416},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":207,"config":426},{"href":427,"dataGaName":428,"dataGaLocation":416},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/de-de/get-started/","get started",{"freeTrial":430,"mobileIcon":434,"desktopIcon":436},{"text":431,"config":432},"Mehr über GitLab Duo erfahren",{"href":86,"dataGaName":433,"dataGaLocation":416},"gitlab duo",{"altText":418,"config":435},{"src":420,"dataGaName":421,"dataGaLocation":416},{"altText":418,"config":437},{"src":424,"dataGaName":421,"dataGaLocation":416},{"button":439,"mobileIcon":444,"desktopIcon":446},{"text":440,"config":441},"/Option",{"href":442,"dataGaName":443,"dataGaLocation":416},"#contact","switch",{"altText":418,"config":445},{"src":420,"dataGaName":421,"dataGaLocation":416},{"altText":418,"config":447},{"src":448,"dataGaName":421,"dataGaLocation":416},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":450,"mobileIcon":455,"desktopIcon":457},{"text":451,"config":452},"Zurück zur Preisübersicht",{"href":195,"dataGaName":453,"dataGaLocation":416,"icon":454},"back to pricing","GoBack",{"altText":418,"config":456},{"src":420,"dataGaName":421,"dataGaLocation":416},{"altText":418,"config":458},{"src":424,"dataGaName":421,"dataGaLocation":416},{"title":460,"button":461,"config":466},"Sieh dir an, wie agentische KI die Softwarebereitstellung transformiert",{"text":462,"config":463},"GitLab Transcend jetzt ansehen",{"href":464,"dataGaName":465,"dataGaLocation":52},"/de-de/events/transcend/virtual/","transcend event",{"layout":467,"icon":468,"disabled":31},"release","AiStar",{"data":470},{"text":471,"source":472,"edit":478,"contribute":483,"config":488,"items":493,"minimal":695},"Git ist eine Marke von Software Freedom Conservancy und unsere Verwendung von „GitLab“ erfolgt unter Lizenz.",{"text":473,"config":474},"Quelltext der Seite anzeigen",{"href":475,"dataGaName":476,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":479,"config":480},"Diese Seite bearbeiten",{"href":481,"dataGaName":482,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":484,"config":485},"Beteilige dich",{"href":486,"dataGaName":487,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":489,"facebook":490,"youtube":491,"linkedin":492},"https://x.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[494,539,591,633,660],{"title":193,"links":495,"subMenu":510},[496,500,505],{"text":497,"config":498},"Tarife anzeigen",{"href":195,"dataGaName":499,"dataGaLocation":477},"view plans",{"text":501,"config":502},"Vorteile von Premium",{"href":503,"dataGaName":504,"dataGaLocation":477},"/de-de/pricing/premium/","why premium",{"text":506,"config":507},"Vorteile von Ultimate",{"href":508,"dataGaName":509,"dataGaLocation":477},"/de-de/pricing/ultimate/","why ultimate",[511],{"title":359,"links":512},[513,515,517,519,524,529,534],{"text":59,"config":514},{"href":61,"dataGaName":62,"dataGaLocation":477},{"text":368,"config":516},{"href":370,"dataGaName":371,"dataGaLocation":477},{"text":373,"config":518},{"href":375,"dataGaName":376,"dataGaLocation":477},{"text":520,"config":521},"Status",{"href":522,"dataGaName":523,"dataGaLocation":477},"https://status.gitlab.com/","status",{"text":525,"config":526},"Nutzungsbedingungen",{"href":527,"dataGaName":528,"dataGaLocation":477},"/terms/","terms of use",{"text":530,"config":531},"Datenschutzerklärung",{"href":532,"dataGaName":533,"dataGaLocation":477},"/de-de/privacy/","privacy statement",{"text":535,"config":536},"Cookie-Einstellungen",{"dataGaName":537,"dataGaLocation":477,"id":538,"isOneTrustButton":31},"cookie preferences","ot-sdk-btn",{"title":97,"links":540,"subMenu":549},[541,545],{"text":542,"config":543},"DevSecOps-Plattform",{"href":79,"dataGaName":544,"dataGaLocation":477},"devsecops platform",{"text":546,"config":547},"KI-unterstützte Entwicklung",{"href":86,"dataGaName":548,"dataGaLocation":477},"ai-assisted development",[550],{"title":551,"links":552},"Themen",[553,556,561,566,571,576,581,586],{"text":22,"config":554},{"href":555,"dataGaName":40,"dataGaLocation":477},"/de-de/topics/ci-cd/",{"text":557,"config":558},"GitOps",{"href":559,"dataGaName":560,"dataGaLocation":477},"/de-de/topics/gitops/","gitops",{"text":562,"config":563},"DevOps",{"href":564,"dataGaName":565,"dataGaLocation":477},"/de-de/topics/devops/","devops",{"text":567,"config":568},"Versionskontrolle",{"href":569,"dataGaName":570,"dataGaLocation":477},"/de-de/topics/version-control/","version control",{"text":572,"config":573},"DevSecOps",{"href":574,"dataGaName":575,"dataGaLocation":477},"/de-de/topics/devsecops/","devsecops",{"text":577,"config":578},"Cloud-nativ",{"href":579,"dataGaName":580,"dataGaLocation":477},"/de-de/topics/cloud-native/","cloud native",{"text":582,"config":583},"KI für das Programmieren",{"href":584,"dataGaName":585,"dataGaLocation":477},"/de-de/topics/devops/ai-for-coding/","ai for coding",{"text":587,"config":588},"Agentische KI",{"href":589,"dataGaName":590,"dataGaLocation":477},"/de-de/topics/agentic-ai/","agentic ai",{"title":592,"links":593},"Lösungen",[594,597,599,604,608,611,614,617,619,621,623,628],{"text":139,"config":595},{"href":134,"dataGaName":596,"dataGaLocation":477},"Application Security Testing",{"text":126,"config":598},{"href":111,"dataGaName":112,"dataGaLocation":477},{"text":600,"config":601},"Agile Entwicklung",{"href":602,"dataGaName":603,"dataGaLocation":477},"/de-de/solutions/agile-delivery/","agile delivery",{"text":605,"config":606},"SCM",{"href":123,"dataGaName":607,"dataGaLocation":477},"source code management",{"text":22,"config":609},{"href":116,"dataGaName":610,"dataGaLocation":477},"continuous integration & delivery",{"text":165,"config":612},{"href":167,"dataGaName":613,"dataGaLocation":477},"value stream management",{"text":557,"config":615},{"href":616,"dataGaName":560,"dataGaLocation":477},"/de-de/solutions/gitops/",{"text":178,"config":618},{"href":180,"dataGaName":181,"dataGaLocation":477},{"text":183,"config":620},{"href":185,"dataGaName":186,"dataGaLocation":477},{"text":188,"config":622},{"href":190,"dataGaName":191,"dataGaLocation":477},{"text":624,"config":625},"Bildungswesen",{"href":626,"dataGaName":627,"dataGaLocation":477},"/de-de/solutions/education/","education",{"text":629,"config":630},"Finanzdienstleistungen",{"href":631,"dataGaName":632,"dataGaLocation":477},"/de-de/solutions/finance/","financial services",{"title":198,"links":634},[635,637,639,641,644,646,648,650,652,654,656,658],{"text":210,"config":636},{"href":212,"dataGaName":213,"dataGaLocation":477},{"text":215,"config":638},{"href":217,"dataGaName":218,"dataGaLocation":477},{"text":220,"config":640},{"href":222,"dataGaName":223,"dataGaLocation":477},{"text":225,"config":642},{"href":227,"dataGaName":643,"dataGaLocation":477},"docs",{"text":248,"config":645},{"href":250,"dataGaName":251,"dataGaLocation":477},{"text":243,"config":647},{"href":245,"dataGaName":246,"dataGaLocation":477},{"text":253,"config":649},{"href":255,"dataGaName":256,"dataGaLocation":477},{"text":261,"config":651},{"href":263,"dataGaName":264,"dataGaLocation":477},{"text":266,"config":653},{"href":268,"dataGaName":269,"dataGaLocation":477},{"text":271,"config":655},{"href":273,"dataGaName":274,"dataGaLocation":477},{"text":276,"config":657},{"href":278,"dataGaName":279,"dataGaLocation":477},{"text":281,"config":659},{"href":283,"dataGaName":284,"dataGaLocation":477},{"title":299,"links":661},[662,664,666,668,670,672,674,679,684,686,688,690],{"text":306,"config":663},{"href":308,"dataGaName":301,"dataGaLocation":477},{"text":311,"config":665},{"href":313,"dataGaName":314,"dataGaLocation":477},{"text":319,"config":667},{"href":321,"dataGaName":322,"dataGaLocation":477},{"text":324,"config":669},{"href":326,"dataGaName":327,"dataGaLocation":477},{"text":329,"config":671},{"href":331,"dataGaName":332,"dataGaLocation":477},{"text":334,"config":673},{"href":336,"dataGaName":337,"dataGaLocation":477},{"text":675,"config":676},"Nachhaltigkeit",{"href":677,"dataGaName":678,"dataGaLocation":477},"/sustainability/","Sustainability",{"text":680,"config":681},"Vielfalt, Inklusion und Zugehörigkeit",{"href":682,"dataGaName":683,"dataGaLocation":477},"/de-de/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":339,"config":685},{"href":341,"dataGaName":342,"dataGaLocation":477},{"text":349,"config":687},{"href":351,"dataGaName":352,"dataGaLocation":477},{"text":354,"config":689},{"href":356,"dataGaName":357,"dataGaLocation":477},{"text":691,"config":692},"Transparenzerklärung zu moderner Sklaverei",{"href":693,"dataGaName":694,"dataGaLocation":477},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":696},[697,699,702],{"text":525,"config":698},{"href":527,"dataGaName":528,"dataGaLocation":477},{"text":700,"config":701},"Cookies",{"dataGaName":537,"dataGaLocation":477,"id":538,"isOneTrustButton":31},{"text":530,"config":703},{"href":532,"dataGaName":533,"dataGaLocation":477},[705],{"id":706,"title":707,"body":29,"config":708,"content":710,"description":29,"extension":28,"meta":714,"navigation":31,"path":715,"seo":716,"stem":717,"__hash__":718},"blogAuthors/en-us/blog/authors/olivier-dupr.yml","Olivier Dupr",{"template":709},"BlogAuthor",{"name":9,"config":711},{"headshot":712,"ctfId":713},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1750713474/cj6odchlpoqxbibenvye.png","4VIckvQsyfNxEtz4pM42aP",{},"/en-us/blog/authors/olivier-dupr",{},"en-us/blog/authors/olivier-dupr","KYUHajPcOeVlPPyPD8D4H56u7iQpJSPInWLi38Y1NA0",[720,733,747],{"content":721,"config":731},{"title":722,"description":723,"authors":724,"heroImage":726,"date":727,"body":728,"category":11,"tags":729},"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.",[725],"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",[22,730,26],"product",{"featured":14,"template":15,"slug":732},"how-to-build-ci-cd-observability-at-scale",{"content":734,"config":745},{"body":735,"title":736,"description":737,"authors":738,"heroImage":740,"date":741,"category":11,"tags":742},"## 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.",[739],"Omid Khan","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772721753/frfsm1qfscwrmsyzj1qn.png","2026-04-09",[22,743,26,744],"DevOps platform","features",{"featured":31,"template":15,"slug":746},"5-ways-gitlab-pipeline-logic-solves-real-engineering-problems",{"content":748,"config":757},{"title":749,"description":750,"authors":751,"heroImage":753,"date":754,"body":755,"category":11,"tags":756},"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.",[752],"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.",[26,730,744],{"featured":14,"template":15,"slug":758},"using-gitlab-container-virtual-registry-with-docker-hardened-images",{"promotions":760},[761,775,786,798],{"id":762,"categories":763,"header":765,"text":766,"button":767,"image":772},"ai-modernization",[764],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":768,"config":769},"Get your AI maturity score",{"href":770,"dataGaName":771,"dataGaLocation":251},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":773},{"src":774},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":776,"categories":777,"header":778,"text":766,"button":779,"image":783},"devops-modernization",[730,575],"Are you just managing tools or shipping innovation?",{"text":780,"config":781},"Get your DevOps maturity score",{"href":782,"dataGaName":771,"dataGaLocation":251},"/assessments/devops-modernization-assessment/",{"config":784},{"src":785},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":787,"categories":788,"header":790,"text":766,"button":791,"image":795},"security-modernization",[789],"security","Are you trading speed for security?",{"text":792,"config":793},"Get your security maturity score",{"href":794,"dataGaName":771,"dataGaLocation":251},"/assessments/security-modernization-assessment/",{"config":796},{"src":797},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":799,"paths":800,"header":803,"text":804,"button":805,"image":810},"github-azure-migration",[801,802],"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":806,"config":807},"See how GitLab compares to GitHub",{"href":808,"dataGaName":809,"dataGaLocation":251},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":811},{"src":785},{"header":813,"blurb":814,"button":815,"secondaryButton":820},"Beginne noch heute, schneller zu entwickeln","Entdecke, was dein Team mit der intelligenten Orchestrierungsplattform für DevSecOps erreichen kann.\n",{"text":816,"config":817},"Kostenlosen Test starten",{"href":818,"dataGaName":57,"dataGaLocation":819},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/de-de/","feature",{"text":59,"config":821},{"href":61,"dataGaName":62,"dataGaLocation":819},1777493571993]