[{"data":1,"prerenderedAt":709},["ShallowReactive",2],{"/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments/":3,"navigation-de-de":39,"banner-de-de":458,"footer-de-de":470,"Olivier Dupré":680,"next-steps-de-de":694},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":29,"_id":32,"_type":33,"title":34,"_source":35,"_file":36,"_stem":37,"_extension":38},"/de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Kontinuierliche Bereitstellung in fünf Umgebungen mithilfe von untergeordneten Pipelines","Erfahre, wie du die kontinuierliche Bereitstellung in verschiedenen Umgebungen – darunter temporäre, sofort einsatzbereite Sandboxes – mit einem minimalistischen GitLab-Workflow verwalten kannst.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097012/Blog/Hero%20Images/Blog/Hero%20Images/AdobeStock_397632156_3Ldy1urjMStQCl4qnOBvE0_1750097011626.jpg","https://about.gitlab.com/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Kontinuierliche Bereitstellung in fünf Umgebungen mithilfe von untergeordneten Pipelines\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Olivier Dupré\"}],\n        \"datePublished\": \"2024-09-26\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22,"updatedDate":28},[18],"Olivier Dupré","2024-09-26","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\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/ee/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/ee/user/project/protected_branches.html), 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/ee/user/project/protected_tags.html), 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/ee/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/ee/ci/pipelines/downstream_pipelines.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/ee/ci/yaml/index.html#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/ee/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/ee/ci/variables/predefined_variables.html) 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/ee/ci/variables/#mask-a-cicd-variable), [sie zu schützen](https://docs.gitlab.com/ee/ci/variables/#protect-a-cicd-variable) und [sie in Umgebungen zu übertragen](https://docs.gitlab.com/ee/ci/environments/index.html#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/ee/user/project/codeowners/) gesteuert werden, um festzulegen, wer sie ändern darf.\n\n```\n[Production owners] \nvars/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: \n  environment: \n    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: \n    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: \n    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/ee/ci/jobs/job_control.html#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/ee/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",[23,24,25,26,27],"CI/CD","CI","CD","DevSecOps platform","tutorial","2024-11-05",{"slug":30,"featured":6,"template":31},"using-child-pipelines-to-continuously-deploy-to-five-environments","BlogPost","content:de-de:blog:using-child-pipelines-to-continuously-deploy-to-five-environments.yml","yaml","Using Child Pipelines To Continuously Deploy To Five Environments","content","de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments.yml","de-de/blog/using-child-pipelines-to-continuously-deploy-to-five-environments","yml",{"_path":40,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"data":42,"_id":454,"_type":33,"title":455,"_source":35,"_file":456,"_stem":457,"_extension":38},"/shared/de-de/main-navigation","de-de",{"logo":43,"freeTrial":48,"sales":53,"login":58,"items":63,"search":395,"minimal":431,"duo":445},{"config":44},{"href":45,"dataGaName":46,"dataGaLocation":47},"/de-de/","gitlab logo","header",{"text":49,"config":50},"Kostenlose Testversion anfordern",{"href":51,"dataGaName":52,"dataGaLocation":47},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":54,"config":55},"Vertrieb kontaktieren",{"href":56,"dataGaName":57,"dataGaLocation":47},"/de-de/sales/","sales",{"text":59,"config":60},"Anmelden",{"href":61,"dataGaName":62,"dataGaLocation":47},"https://gitlab.com/users/sign_in/","sign in",[64,108,206,211,316,376],{"text":65,"config":66,"cards":68,"footer":91},"Plattform",{"dataNavLevelOne":67},"platform",[69,75,83],{"title":65,"description":70,"link":71},"Die umfassendste KI-basierte DevSecOps-Plattform",{"text":72,"config":73},"Erkunde unsere Plattform",{"href":74,"dataGaName":67,"dataGaLocation":47},"/de-de/platform/",{"title":76,"description":77,"link":78},"GitLab Duo (KI)","Entwickle Software schneller mit KI in jeder Phase der Entwicklung",{"text":79,"config":80},"Lerne GitLab Duo kennen",{"href":81,"dataGaName":82,"dataGaLocation":47},"/de-de/gitlab-duo/","gitlab duo ai",{"title":84,"description":85,"link":86},"Gründe, die für GitLab sprechen","10 Gründe, warum Unternehmen sich für GitLab entscheiden",{"text":87,"config":88},"Mehr erfahren",{"href":89,"dataGaName":90,"dataGaLocation":47},"/de-de/why-gitlab/","why gitlab",{"title":92,"items":93},"Erste Schritte mit",[94,99,104],{"text":95,"config":96},"Platform Engineering",{"href":97,"dataGaName":98,"dataGaLocation":47},"/de-de/solutions/platform-engineering/","platform engineering",{"text":100,"config":101},"Entwicklererfahrung",{"href":102,"dataGaName":103,"dataGaLocation":47},"/de-de/developer-experience/","Developer experience",{"text":105,"config":106},"MLOps",{"href":107,"dataGaName":105,"dataGaLocation":47},"/de-de/topics/devops/the-role-of-ai-in-devops/",{"text":109,"left":110,"config":111,"link":113,"lists":117,"footer":188},"Produkt",true,{"dataNavLevelOne":112},"solutions",{"text":114,"config":115},"Alle Lösungen anzeigen",{"href":116,"dataGaName":112,"dataGaLocation":47},"/de-de/solutions/",[118,143,166],{"title":119,"description":120,"link":121,"items":126},"Automatisierung","CI/CD und Automatisierung zur Beschleunigung der Bereitstellung",{"config":122},{"icon":123,"href":124,"dataGaName":125,"dataGaLocation":47},"AutomatedCodeAlt","/de-de/solutions/delivery-automation/","automated software delivery",[127,130,134,139],{"text":23,"config":128},{"href":129,"dataGaLocation":47,"dataGaName":23},"/de-de/solutions/continuous-integration/",{"text":131,"config":132},"KI-unterstützte Entwicklung",{"href":81,"dataGaLocation":47,"dataGaName":133},"AI assisted development",{"text":135,"config":136},"Quellcodeverwaltung",{"href":137,"dataGaLocation":47,"dataGaName":138},"/de-de/solutions/source-code-management/","Source Code Management",{"text":140,"config":141},"Automatisierte Softwarebereitstellung",{"href":124,"dataGaLocation":47,"dataGaName":142},"Automated software delivery",{"title":144,"description":145,"link":146,"items":151},"Sicherheit","Entwickle schneller, ohne die Sicherheit zu gefährden",{"config":147},{"href":148,"dataGaName":149,"dataGaLocation":47,"icon":150},"/de-de/solutions/security-compliance/","security and compliance","ShieldCheckLight",[152,156,161],{"text":153,"config":154},"Sicherheit und Compliance",{"href":148,"dataGaLocation":47,"dataGaName":155},"Security & Compliance",{"text":157,"config":158},"Schutz der Software-Lieferkette",{"href":159,"dataGaLocation":47,"dataGaName":160},"/de-de/solutions/supply-chain/","Software supply chain security",{"text":162,"config":163},"Compliance und Governance",{"href":164,"dataGaLocation":47,"dataGaName":165},"/de-de/solutions/continuous-software-compliance/","Compliance and governance",{"title":167,"link":168,"items":173},"Bewertung",{"config":169},{"icon":170,"href":171,"dataGaName":172,"dataGaLocation":47},"DigitalTransformation","/de-de/solutions/visibility-measurement/","visibility and measurement",[174,178,183],{"text":175,"config":176},"Sichtbarkeit und Bewertung",{"href":171,"dataGaLocation":47,"dataGaName":177},"Visibility and Measurement",{"text":179,"config":180},"Wertstrommanagement",{"href":181,"dataGaLocation":47,"dataGaName":182},"/de-de/solutions/value-stream-management/","Value Stream Management",{"text":184,"config":185},"Analysen und Einblicke",{"href":186,"dataGaLocation":47,"dataGaName":187},"/de-de/solutions/analytics-and-insights/","Analytics and insights",{"title":189,"items":190},"GitLab für",[191,196,201],{"text":192,"config":193},"Enterprise",{"href":194,"dataGaLocation":47,"dataGaName":195},"/de-de/enterprise/","enterprise",{"text":197,"config":198},"Kleinunternehmen",{"href":199,"dataGaLocation":47,"dataGaName":200},"/de-de/small-business/","small business",{"text":202,"config":203},"den öffentlichen Sektor",{"href":204,"dataGaLocation":47,"dataGaName":205},"/de-de/solutions/public-sector/","public sector",{"text":207,"config":208},"Preise",{"href":209,"dataGaName":210,"dataGaLocation":47,"dataNavLevelOne":210},"/de-de/pricing/","pricing",{"text":212,"config":213,"link":215,"lists":219,"feature":303},"Ressourcen",{"dataNavLevelOne":214},"resources",{"text":216,"config":217},"Alle Ressourcen anzeigen",{"href":218,"dataGaName":214,"dataGaLocation":47},"/de-de/resources/",[220,253,275],{"title":221,"items":222},"Erste Schritte",[223,228,233,238,243,248],{"text":224,"config":225},"Installieren",{"href":226,"dataGaName":227,"dataGaLocation":47},"/de-de/install/","install",{"text":229,"config":230},"Kurzanleitungen",{"href":231,"dataGaName":232,"dataGaLocation":47},"/de-de/get-started/","quick setup checklists",{"text":234,"config":235},"Lernen",{"href":236,"dataGaLocation":47,"dataGaName":237},"https://university.gitlab.com/","learn",{"text":239,"config":240},"Produktdokumentation",{"href":241,"dataGaName":242,"dataGaLocation":47},"https://docs.gitlab.com/","product documentation",{"text":244,"config":245},"Best-Practice-Videos",{"href":246,"dataGaName":247,"dataGaLocation":47},"/de-de/getting-started-videos/","best practice videos",{"text":249,"config":250},"Integrationen",{"href":251,"dataGaName":252,"dataGaLocation":47},"/de-de/integrations/","integrations",{"title":254,"items":255},"Entdecken",[256,261,265,270],{"text":257,"config":258},"Kundenerfolge",{"href":259,"dataGaName":260,"dataGaLocation":47},"/de-de/customers/","customer success stories",{"text":262,"config":263},"Blog",{"href":264,"dataGaName":5,"dataGaLocation":47},"/de-de/blog/",{"text":266,"config":267},"Remote",{"href":268,"dataGaName":269,"dataGaLocation":47},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":271,"config":272},"TeamOps",{"href":273,"dataGaName":274,"dataGaLocation":47},"/de-de/teamops/","teamops",{"title":276,"items":277},"Vernetzen",[278,283,288,293,298],{"text":279,"config":280},"GitLab-Services",{"href":281,"dataGaName":282,"dataGaLocation":47},"/de-de/services/","services",{"text":284,"config":285},"Community",{"href":286,"dataGaName":287,"dataGaLocation":47},"/community/","community",{"text":289,"config":290},"Forum",{"href":291,"dataGaName":292,"dataGaLocation":47},"https://forum.gitlab.com/","forum",{"text":294,"config":295},"Veranstaltungen",{"href":296,"dataGaName":297,"dataGaLocation":47},"/events/","events",{"text":299,"config":300},"Partner",{"href":301,"dataGaName":302,"dataGaLocation":47},"/de-de/partners/","partners",{"backgroundColor":304,"textColor":305,"text":306,"image":307,"link":311},"#2f2a6b","#fff","Perspektiven für die Softwareentwicklung der Zukunft",{"altText":308,"config":309},"the source promo card",{"src":310},"/images/navigation/the-source-promo-card.svg",{"text":312,"config":313},"Lies die News",{"href":314,"dataGaName":315,"dataGaLocation":47},"/de-de/the-source/","the source",{"text":317,"config":318,"lists":320},"Unternehmen",{"dataNavLevelOne":319},"company",[321],{"items":322},[323,328,334,336,341,346,351,356,361,366,371],{"text":324,"config":325},"Über",{"href":326,"dataGaName":327,"dataGaLocation":47},"/de-de/company/","about",{"text":329,"config":330,"footerGa":333},"Karriere",{"href":331,"dataGaName":332,"dataGaLocation":47},"/jobs/","jobs",{"dataGaName":332},{"text":294,"config":335},{"href":296,"dataGaName":297,"dataGaLocation":47},{"text":337,"config":338},"Geschäftsführung",{"href":339,"dataGaName":340,"dataGaLocation":47},"/company/team/e-group/","leadership",{"text":342,"config":343},"Team",{"href":344,"dataGaName":345,"dataGaLocation":47},"/company/team/","team",{"text":347,"config":348},"Handbuch",{"href":349,"dataGaName":350,"dataGaLocation":47},"https://handbook.gitlab.com/","handbook",{"text":352,"config":353},"Investor Relations",{"href":354,"dataGaName":355,"dataGaLocation":47},"https://ir.gitlab.com/","investor relations",{"text":357,"config":358},"Trust Center",{"href":359,"dataGaName":360,"dataGaLocation":47},"/de-de/security/","trust center",{"text":362,"config":363},"AI Transparency Center",{"href":364,"dataGaName":365,"dataGaLocation":47},"/de-de/ai-transparency-center/","ai transparency center",{"text":367,"config":368},"Newsletter",{"href":369,"dataGaName":370,"dataGaLocation":47},"/company/contact/","newsletter",{"text":372,"config":373},"Presse",{"href":374,"dataGaName":375,"dataGaLocation":47},"/press/","press",{"text":377,"config":378,"lists":379},"Kontakt",{"dataNavLevelOne":319},[380],{"items":381},[382,385,390],{"text":54,"config":383},{"href":56,"dataGaName":384,"dataGaLocation":47},"talk to sales",{"text":386,"config":387},"Support",{"href":388,"dataGaName":389,"dataGaLocation":47},"/support/","get help",{"text":391,"config":392},"Kundenportal",{"href":393,"dataGaName":394,"dataGaLocation":47},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":396,"login":397,"suggestions":404},"Schließen",{"text":398,"link":399},"Um Repositories und Projekte zu durchsuchen, melde dich an bei",{"text":400,"config":401},"gitlab.com",{"href":61,"dataGaName":402,"dataGaLocation":403},"search login","search",{"text":405,"default":406},"Vorschläge",[407,410,415,417,422,427],{"text":76,"config":408},{"href":81,"dataGaName":409,"dataGaLocation":403},"GitLab Duo (AI)",{"text":411,"config":412},"Code Suggestions (KI)",{"href":413,"dataGaName":414,"dataGaLocation":403},"/de-de/solutions/code-suggestions/","Code Suggestions (AI)",{"text":23,"config":416},{"href":129,"dataGaName":23,"dataGaLocation":403},{"text":418,"config":419},"GitLab auf AWS",{"href":420,"dataGaName":421,"dataGaLocation":403},"/de-de/partners/technology-partners/aws/","GitLab on AWS",{"text":423,"config":424},"GitLab auf Google Cloud",{"href":425,"dataGaName":426,"dataGaLocation":403},"/de-de/partners/technology-partners/google-cloud-platform/","GitLab on Google Cloud",{"text":428,"config":429},"Warum GitLab?",{"href":89,"dataGaName":430,"dataGaLocation":403},"Why GitLab?",{"freeTrial":432,"mobileIcon":437,"desktopIcon":442},{"text":433,"config":434},"Kostenlos testen",{"href":435,"dataGaName":52,"dataGaLocation":436},"https://gitlab.com/-/trials/new/","nav",{"altText":438,"config":439},"GitLab-Symbol",{"src":440,"dataGaName":441,"dataGaLocation":436},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":438,"config":443},{"src":444,"dataGaName":441,"dataGaLocation":436},"/images/brand/gitlab-logo-type.svg",{"freeTrial":446,"mobileIcon":450,"desktopIcon":452},{"text":447,"config":448},"Erfahre mehr über GitLab Duo",{"href":81,"dataGaName":449,"dataGaLocation":436},"gitlab duo",{"altText":438,"config":451},{"src":440,"dataGaName":441,"dataGaLocation":436},{"altText":438,"config":453},{"src":444,"dataGaName":441,"dataGaLocation":436},"content:shared:de-de:main-navigation.yml","Main Navigation","shared/de-de/main-navigation.yml","shared/de-de/main-navigation",{"_path":459,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"title":460,"button":461,"config":465,"_id":467,"_type":33,"_source":35,"_file":468,"_stem":469,"_extension":38},"/shared/de-de/banner","GitLab Duo Agent Platform ist jetzt in öffentlicher Beta!",{"text":87,"config":462},{"href":463,"dataGaName":464,"dataGaLocation":47},"/de-de/gitlab-duo/agent-platform/","duo banner",{"layout":466},"release","content:shared:de-de:banner.yml","shared/de-de/banner.yml","shared/de-de/banner",{"_path":471,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"data":472,"_id":676,"_type":33,"title":677,"_source":35,"_file":678,"_stem":679,"_extension":38},"/shared/de-de/main-footer",{"text":473,"source":474,"edit":480,"contribute":485,"config":490,"items":495,"minimal":668},"Git ist eine Marke von Software Freedom Conservancy und unsere Verwendung von „GitLab“ erfolgt unter Lizenz.",{"text":475,"config":476},"Quelltext der Seite anzeigen",{"href":477,"dataGaName":478,"dataGaLocation":479},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":481,"config":482},"Diese Seite bearbeiten",{"href":483,"dataGaName":484,"dataGaLocation":479},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":486,"config":487},"Beteilige dich",{"href":488,"dataGaName":489,"dataGaLocation":479},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":491,"facebook":492,"youtube":493,"linkedin":494},"https://x.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[496,519,574,604,638],{"title":65,"links":497,"subMenu":502},[498],{"text":499,"config":500},"DevSecOps-Plattform",{"href":74,"dataGaName":501,"dataGaLocation":479},"devsecops platform",[503],{"title":207,"links":504},[505,509,514],{"text":506,"config":507},"Tarife anzeigen",{"href":209,"dataGaName":508,"dataGaLocation":479},"view plans",{"text":510,"config":511},"Vorteile von Premium",{"href":512,"dataGaName":513,"dataGaLocation":479},"/de-de/pricing/premium/","why premium",{"text":515,"config":516},"Vorteile von Ultimate",{"href":517,"dataGaName":518,"dataGaLocation":479},"/de-de/pricing/ultimate/","why ultimate",{"title":520,"links":521},"Lösungen",[522,527,530,532,537,542,546,549,552,557,559,561,564,569],{"text":523,"config":524},"Digitale Transformation",{"href":525,"dataGaName":526,"dataGaLocation":479},"/de-de/topics/digital-transformation/","digital transformation",{"text":153,"config":528},{"href":148,"dataGaName":529,"dataGaLocation":479},"security & compliance",{"text":140,"config":531},{"href":124,"dataGaName":125,"dataGaLocation":479},{"text":533,"config":534},"Agile Entwicklung",{"href":535,"dataGaName":536,"dataGaLocation":479},"/de-de/solutions/agile-delivery/","agile delivery",{"text":538,"config":539},"Cloud-Transformation",{"href":540,"dataGaName":541,"dataGaLocation":479},"/de-de/topics/cloud-native/","cloud transformation",{"text":543,"config":544},"SCM",{"href":137,"dataGaName":545,"dataGaLocation":479},"source code management",{"text":23,"config":547},{"href":129,"dataGaName":548,"dataGaLocation":479},"continuous integration & delivery",{"text":179,"config":550},{"href":181,"dataGaName":551,"dataGaLocation":479},"value stream management",{"text":553,"config":554},"GitOps",{"href":555,"dataGaName":556,"dataGaLocation":479},"/de-de/solutions/gitops/","gitops",{"text":192,"config":558},{"href":194,"dataGaName":195,"dataGaLocation":479},{"text":197,"config":560},{"href":199,"dataGaName":200,"dataGaLocation":479},{"text":562,"config":563},"Öffentlicher Sektor",{"href":204,"dataGaName":205,"dataGaLocation":479},{"text":565,"config":566},"Bildungswesen",{"href":567,"dataGaName":568,"dataGaLocation":479},"/de-de/solutions/education/","education",{"text":570,"config":571},"Finanzdienstleistungen",{"href":572,"dataGaName":573,"dataGaLocation":479},"/de-de/solutions/finance/","financial services",{"title":212,"links":575},[576,578,580,582,585,587,590,592,594,596,598,600,602],{"text":224,"config":577},{"href":226,"dataGaName":227,"dataGaLocation":479},{"text":229,"config":579},{"href":231,"dataGaName":232,"dataGaLocation":479},{"text":234,"config":581},{"href":236,"dataGaName":237,"dataGaLocation":479},{"text":239,"config":583},{"href":241,"dataGaName":584,"dataGaLocation":479},"docs",{"text":262,"config":586},{"href":264,"dataGaName":5,"dataGaLocation":479},{"text":257,"config":588},{"href":589,"dataGaName":260,"dataGaLocation":479},"/customers/",{"text":266,"config":591},{"href":268,"dataGaName":269,"dataGaLocation":479},{"text":279,"config":593},{"href":281,"dataGaName":282,"dataGaLocation":479},{"text":271,"config":595},{"href":273,"dataGaName":274,"dataGaLocation":479},{"text":284,"config":597},{"href":286,"dataGaName":287,"dataGaLocation":479},{"text":289,"config":599},{"href":291,"dataGaName":292,"dataGaLocation":479},{"text":294,"config":601},{"href":296,"dataGaName":297,"dataGaLocation":479},{"text":299,"config":603},{"href":301,"dataGaName":302,"dataGaLocation":479},{"title":317,"links":605},[606,608,610,612,614,616,618,622,627,629,631,633],{"text":324,"config":607},{"href":326,"dataGaName":319,"dataGaLocation":479},{"text":329,"config":609},{"href":331,"dataGaName":332,"dataGaLocation":479},{"text":337,"config":611},{"href":339,"dataGaName":340,"dataGaLocation":479},{"text":342,"config":613},{"href":344,"dataGaName":345,"dataGaLocation":479},{"text":347,"config":615},{"href":349,"dataGaName":350,"dataGaLocation":479},{"text":352,"config":617},{"href":354,"dataGaName":355,"dataGaLocation":479},{"text":619,"config":620},"Sustainability",{"href":621,"dataGaName":619,"dataGaLocation":479},"/sustainability/",{"text":623,"config":624},"Vielfalt, Inklusion und Zugehörigkeit",{"href":625,"dataGaName":626,"dataGaLocation":479},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":357,"config":628},{"href":359,"dataGaName":360,"dataGaLocation":479},{"text":367,"config":630},{"href":369,"dataGaName":370,"dataGaLocation":479},{"text":372,"config":632},{"href":374,"dataGaName":375,"dataGaLocation":479},{"text":634,"config":635},"Transparenzerklärung zu moderner Sklaverei",{"href":636,"dataGaName":637,"dataGaLocation":479},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":639,"links":640},"Nimm Kontakt auf",[641,644,646,648,653,658,663],{"text":642,"config":643},"Sprich mit einem Experten/einer Expertin",{"href":56,"dataGaName":57,"dataGaLocation":479},{"text":386,"config":645},{"href":388,"dataGaName":389,"dataGaLocation":479},{"text":391,"config":647},{"href":393,"dataGaName":394,"dataGaLocation":479},{"text":649,"config":650},"Status",{"href":651,"dataGaName":652,"dataGaLocation":479},"https://status.gitlab.com/","status",{"text":654,"config":655},"Nutzungsbedingungen",{"href":656,"dataGaName":657,"dataGaLocation":479},"/terms/","terms of use",{"text":659,"config":660},"Datenschutzerklärung",{"href":661,"dataGaName":662,"dataGaLocation":479},"/de-de/privacy/","privacy statement",{"text":664,"config":665},"Cookie-Einstellungen",{"dataGaName":666,"dataGaLocation":479,"id":667,"isOneTrustButton":110},"cookie preferences","ot-sdk-btn",{"items":669},[670,672,674],{"text":654,"config":671},{"href":656,"dataGaName":657,"dataGaLocation":479},{"text":659,"config":673},{"href":661,"dataGaName":662,"dataGaLocation":479},{"text":664,"config":675},{"dataGaName":666,"dataGaLocation":479,"id":667,"isOneTrustButton":110},"content:shared:de-de:main-footer.yml","Main Footer","shared/de-de/main-footer.yml","shared/de-de/main-footer",[681],{"_path":682,"_dir":683,"_draft":6,"_partial":6,"_locale":7,"content":684,"config":688,"_id":690,"_type":33,"title":691,"_source":35,"_file":692,"_stem":693,"_extension":38},"/en-us/blog/authors/olivier-dupr","authors",{"name":18,"config":685},{"headshot":686,"ctfId":687},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1750713474/cj6odchlpoqxbibenvye.png","4VIckvQsyfNxEtz4pM42aP",{"template":689},"BlogAuthor","content:en-us:blog:authors:olivier-dupr.yml","Olivier Dupr","en-us/blog/authors/olivier-dupr.yml","en-us/blog/authors/olivier-dupr",{"_path":695,"_dir":41,"_draft":6,"_partial":6,"_locale":7,"header":696,"eyebrow":697,"blurb":698,"button":699,"secondaryButton":703,"_id":705,"_type":33,"title":706,"_source":35,"_file":707,"_stem":708,"_extension":38},"/shared/de-de/next-steps","Stelle jetzt bessere Software schneller bereit","Mehr als 50 % der Fortune-100-Unternehmen vertrauen GitLab","Erlebe, was dein Team mit der intelligenten\n\n\nDevSecOps-Plattform erreichen kann.\n",{"text":49,"config":700},{"href":701,"dataGaName":52,"dataGaLocation":702},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":54,"config":704},{"href":56,"dataGaName":57,"dataGaLocation":702},"content:shared:de-de:next-steps.yml","Next Steps","shared/de-de/next-steps.yml","shared/de-de/next-steps",1753475283455]