Home/Modul 5

⚙️ Modul 5: CI/CD Pipelines

Lernziele: Sie verstehen CI/CD-Konzepte vollständig, kennen die GitLab CI/CD Architektur im Detail, können komplexe Pipelines für Netzwerk-Automatisierung erstellen und beherrschen Best Practices für sichere, wartbare Pipelines.

⏱️ Geschätzte Dauer: 180 Minuten | 📊 Schwierigkeit: Mittel bis Fortgeschritten

📋 Inhaltsverzeichnis

  • 1. CI/CD Konzepte verstehen
  • 2. GitLab CI/CD Architektur
  • 3. .gitlab-ci.yml Syntax im Detail
  • 4. Pipeline-Variablen & Secrets
  • 5. Environments & Deployments
  • 6. Manual Approvals & Gates
  • 7. Netzwerk-Pipeline komplett
  • 8. Praxis-Übungen (10 Übungen)
  • 9. Troubleshooting
  • 10. Best Practices

1. CI/CD Konzepte verstehen

Bevor wir in die Technik eintauchen, müssen wir verstehen, warum CI/CD so revolutionär für IT-Operations ist. Stellen Sie sich vor, Sie arbeiten in einer Autofabrik...

Die Fließband-Analogie

In einer modernen Autofabrik läuft kein einziges Fahrzeug vom Band, ohne mehrereQualitätsprüfungen durchlaufen zu haben:

Rohbau
Teile zusammen
Schweißen
Verbinden
QC #1
Prüfung
Lackieren
Finish
QC #2
Prüfung
Auslieferung
Zum Kunden

Genau so funktioniert CI/CD: Ihr Code ist das Produkt, die Pipeline ist das Fließband, und die Tests sind die Qualitätskontrolleure.

Die drei Säulen: CI, CD und CD

Ja, es gibt tatsächlich zwei CDs – und das verwirrt viele Anfänger. Lassen Sie uns das klären:

CI

Continuous Integration

Definition: Entwickler integrieren ihren Code mehrmals täglich in einen gemeinsamen Branch. Jede Integration wird automatisch gebaut und getestet.

Analogie: Stellen Sie sich ein Team vor, das ein Puzzle baut. Statt dass jeder stundenlang an seinem Teil arbeitet und am Ende hofft, dass alles zusammenpasst, prüft jeder sofort, ob sein neues Teil zum Gesamtbild passt.

Ziel: Fehler früh erkennen, "Integration Hell" vermeiden

CD

Continuous Delivery

Definition: Code wird automatisch durch alle Test-Stages geschickt und ist jederzeit bereit für Production – aber ein Mensch drückt den finalen Deploy-Button.

Analogie: Das Auto ist fertig gebaut, geprüft, gewaschen und steht im Auslieferungsbereich. Aber der Versandleiter muss noch "Freigabe" erteilen, bevor der LKW losfährt.

Ziel: Jederzeit release-fähig sein, aber mit menschlicher Kontrolle

CD+

Continuous Deployment

Definition: Jede Änderung, die alle Tests besteht, wirdautomatisch in Production deployed. Kein manueller Eingriff nötig.

Analogie: Das Fließband läuft komplett automatisch – vom Rohstoff bis zur Auslieferung an den Kunden. Menschen überwachen nur noch, greifen aber nicht ein.

Ziel: Maximale Geschwindigkeit, minimale menschliche Fehler

ℹ️Was ist für Netzwerke richtig?
In der Netzwerkautomatisierung ist Continuous Delivery der Sweet Spot. Sie wollen automatische Tests und Validierung, aber bei kritischer Infrastruktur sollte ein Mensch das finale "Go" geben. Continuous Deployment ist für Webapps – nicht für Core-Router.

Der traditionelle Weg vs. CI/CD

❌ Traditionell (Wasserfall)

  1. 1. Entwickler arbeiten wochenlang isoliert
  2. 2. Großer "Big Bang" Merge am Ende
  3. 3. Manuelles Testen über Tage/Wochen
  4. 4. Änderungsantrag, CAB-Meeting, Approval
  5. 5. Manuelles Deployment am Wochenende
  6. 6. Hoffen, dass alles klappt...

⏱️ Release-Zyklus: Wochen bis Monate

✅ CI/CD (Agile)

  1. 1. Kleine Änderungen, häufige Commits
  2. 2. Automatischer Build bei jedem Push
  3. 3. Automatische Tests in Sekunden
  4. 4. Automatische Dokumentation & Reports
  5. 5. One-Click Deployment (oder automatisch)
  6. 6. Automatisches Rollback bei Problemen

⏱️ Release-Zyklus: Stunden bis Tage

Warum CI/CD für Netzwerke?

Netzwerk-Engineers fragen oft: "Warum brauchen wir das? Wir sind doch keine Software-Entwickler!" – Die Antwort:

🔍
Frühe Fehler

Syntax-Fehler werden beim Commit erkannt, nicht erst beim Deployment

📋
Audit Trail

Jede Änderung ist dokumentiert: Wer, wann, was, warum

🔄
Reproduzierbar

Gleicher Code = gleiches Ergebnis, jedes Mal

Easy Rollback

Bei Problemen: Ein Klick zurück zur letzten Version

2. GitLab CI/CD Architektur

Um GitLab CI/CD effektiv zu nutzen, müssen Sie verstehen, wie die einzelnen Komponenten zusammenspielen. Es ist wie ein Orchester – jedes Instrument hat seine Rolle.

Die Kernkomponenten

GitLab CI/CD Architektur
📁
.gitlab-ci.yml

Die Partitur – definiert was gespielt wird

🎻
GitLab Server

Der Dirigent – koordiniert alles

🎺
Runners

Die Musiker – führen die Arbeit aus

GitLab Runners verstehen

Ein Runner ist der Arbeiter, der Ihre Jobs tatsächlich ausführt. GitLab selbst koordiniert nur – die Arbeit passiert auf den Runners.

🌍 Shared Runners

  • • Von GitLab bereitgestellt (gitlab.com)
  • • Für alle Projekte verfügbar
  • • Begrenzte Minuten pro Monat (Free Tier)
  • • Keine spezielle Konfiguration nötig

Use Case: Kleine Projekte, Standard-Builds

🏠 Specific Runners

  • • Selbst gehostet (On-Premise)
  • • Nur für bestimmte Projekte
  • • Volle Kontrolle über Ressourcen
  • • Zugriff auf interne Netze möglich

Use Case: Enterprise, Netzwerk-Labs, Security

Executors – Wie Jobs laufen

Der Executor bestimmt, wie ein Runner die Jobs ausführt:

ExecutorBeschreibungUse Case
dockerJeder Job läuft in einem frischen Docker-Container⭐ Standard, isoliert, reproduzierbar
shellJobs laufen direkt auf der Runner-MaschineZugriff auf lokale Tools/Hardware
kubernetesJobs laufen als Pods in K8sSkalierung, Cloud-Native
sshVerbindet sich per SSH zu einem ServerLegacy-Systeme, spezielle Hardware
💡Empfehlung für Netzwerk-Automatisierung
Nutzen Sie den Docker-Executor mit einem benutzerdefinierten Image, das alle Netzwerk-Tools enthält (Ansible, Python, Netmiko, etc.). So haben Sie eine saubere, reproduzierbare Umgebung für jeden Job.

Artifacts – Dateien zwischen Jobs teilen

Artifacts sind Dateien, die ein Job produziert und die an andere Jobs weitergegeben oder heruntergeladen werden können. Denken Sie an sie als "Arbeitsergebnisse":

📄 .gitlab-ci.yml
# Job 1: Erstellt einen Report
generate-report:
  stage: test
  script:
    - ansible-playbook check-network.yml > network-report.txt
    - echo "Prüfung am $(date)" >> network-report.txt
  artifacts:
    paths:
      - network-report.txt     # Diese Datei wird gespeichert
    expire_in: 1 week          # Nach einer Woche löschen
    when: always               # Auch bei Fehler speichern

# Job 2: Nutzt den Report aus Job 1
analyze-report:
  stage: deploy
  script:
    - cat network-report.txt   # Datei ist verfügbar!
    - grep -c "OK" network-report.txt

Artifact-Typen

📄 paths

Beliebige Dateien und Ordner. Wird als ZIP herunterladbar.

📊 reports

Strukturierte Reports (JUnit, Coverage) die GitLab visualisiert.

🔗 dependencies

Steuert, welche Artifacts ein Job herunterladen darf.

Cache – Build-Zeiten optimieren

Cache ist wie der Browser-Cache: Dateien werden zwischen Pipeline-Runs wiederverwendet, um Zeit zu sparen. Anders als Artifacts:

📦 Artifacts

  • • Zwischen Jobs einer Pipeline
  • • Werden heruntergeladen und gespeichert
  • • Für Ergebnisse (Reports, Binaries)
  • • Zuverlässig, garantiert vorhanden

💾 Cache

  • • Zwischen Pipeline-Runs
  • • Bleibt auf dem Runner (lokal)
  • • Für Dependencies (pip, npm)
  • • Best-effort, kann fehlen
📄 .gitlab-ci.yml
# Cache für Python Dependencies
default:
  cache:
    key: python-deps              # Eindeutiger Schlüssel
    paths:
      - .cache/pip                # pip Cache-Ordner
      - venv/                     # Virtual Environment
    policy: pull-push             # Lesen und schreiben
  
install-deps:
  stage: prepare
  script:
    - python -m venv venv
    - source venv/bin/activate
    - pip install --cache-dir=.cache/pip -r requirements.txt
    # Beim nächsten Run sind die Packages schon da!

3. .gitlab-ci.yml Syntax im Detail

Die .gitlab-ci.yml ist das Herzstück Ihrer CI/CD Pipeline. Lassen Sie uns jeden wichtigen Bestandteil durchgehen.

Grundstruktur

📄 .gitlab-ci.yml
# 1. STAGES - Die Phasen Ihrer Pipeline
stages:
  - validate    # Erste Phase
  - test        # Zweite Phase
  - deploy      # Dritte Phase

# 2. GLOBAL DEFAULTS - Gilt für alle Jobs
default:
  image: python:3.11
  tags:
    - docker
  before_script:
    - pip install -q ansible

# 3. GLOBAL VARIABLES - Überall verfügbar
variables:
  ANSIBLE_HOST_KEY_CHECKING: "false"
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

# 4. JOBS - Die eigentliche Arbeit
my-first-job:
  stage: validate
  script:
    - echo "Hello World!"
    - python --version

Stages – Die Reihenfolge

Stages definieren die Reihenfolge, in der Jobs ausgeführt werden:

stages:
  - prepare      # 1. Vorbereitung (Dependencies)
  - validate     # 2. Validierung (Syntax, Lint)
  - test         # 3. Tests (Unit, Integration)
  - build        # 4. Build (Docker Image, etc.)
  - deploy       # 5. Deployment
  - verify       # 6. Post-Deployment Tests
  - cleanup      # 7. Aufräumen
ℹ️Stage-Regeln
  • Jobs der gleichen Stage laufen parallel
  • Die nächste Stage startet erst, wenn die vorherige erfolgreich war
  • Ein fehlgeschlagener Job stoppt die Pipeline (außer bei allow_failure: true)

Jobs – Die Arbeitspakete

Jeder Job ist ein eigenständiges Arbeitspaket. Hier sind alle wichtigen Attribute:

📄 .gitlab-ci.yml
mein-job:
  # Welche Stage?
  stage: test
  
  # Welches Docker-Image?
  image: ansible/ansible:latest
  
  # Auf welchen Runnern? (Tags müssen matchen)
  tags:
    - docker
    - linux
  
  # VOR dem eigentlichen Script
  before_script:
    - pip install yamllint
    - echo "Vorbereitung fertig"
  
  # DAS EIGENTLICHE SCRIPT (Pflichtfeld!)
  script:
    - yamllint config.yaml
    - ansible-lint playbook.yml
  
  # NACH dem Script (auch bei Fehler)
  after_script:
    - echo "Aufräumen..."
    - rm -rf tmp/
  
  # Variablen nur für diesen Job
  variables:
    DEBUG: "true"
  
  # Dateien speichern
  artifacts:
    paths:
      - results/
    expire_in: 1 day
  
  # Wann soll der Job laufen?
  only:
    - main
    - develop
  
  # Wann NICHT?
  except:
    - tags
  
  # Manueller Trigger?
  when: manual
  
  # Darf fehlschlagen?
  allow_failure: false
  
  # Timeout
  timeout: 30 minutes
  
  # Wiederholungen bei Fehler
  retry:
    max: 2
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

Rules – Moderne Bedingungen

rules ist der moderne Ersatz für only/except und bietet viel mehr Flexibilität:

📄 .gitlab-ci.yml
deploy-production:
  stage: deploy
  script:
    - ansible-playbook deploy.yml
  rules:
    # Regel 1: Auf main-Branch → Manual
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
      allow_failure: false
    
    # Regel 2: Bei Tags die mit "v" starten → Automatisch
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+/
      when: on_success
    
    # Regel 3: Merge Requests → Nur wenn Änderungen in bestimmten Dateien
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      changes:
        - playbooks/**/*
        - inventory/**/*
      when: on_success
    
    # Regel 4: Alles andere → Nicht ausführen
    - when: never

Wichtige CI/CD Variablen für Rules

VariableBeispielBeschreibung
$CI_COMMIT_BRANCHmain, developAktueller Branch
$CI_COMMIT_TAGv1.2.3Git Tag (falls vorhanden)
$CI_PIPELINE_SOURCEpush, merge_request_event, webWas hat die Pipeline getriggert?
$CI_MERGE_REQUEST_TARGET_BRANCH_NAMEmainZiel-Branch des MRs

needs – Parallele Abhängigkeiten

Mit needs können Sie Jobs in verschiedenen Stages parallel laufen lassen, wenn sie voneinander abhängen:

📄 .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

# Diese beiden Jobs laufen parallel
build-frontend:
  stage: build
  script: npm run build
  artifacts:
    paths: [dist/]

build-backend:
  stage: build
  script: go build -o app

# Dieser Job WARTET auf beide
integration-test:
  stage: test
  needs:
    - build-frontend
    - build-backend
  script: ./run-tests.sh

# Dieser Job braucht NUR das Backend
deploy-api:
  stage: deploy
  needs:
    - build-backend      # Startet, sobald build-backend fertig ist!
  script: ./deploy-api.sh
Mit "needs" - Schneller!
build-frontend
build-backend
integration-test
deploy-api ⚡

deploy-api startet sofort nach build-backend, ohne auf build-frontend zu warten!

extends – Templates wiederverwenden

Mit extends können Sie wiederkehrende Konfigurationen in Templates auslagern (DRY-Prinzip):

📄 .gitlab-ci.yml
# Template (beginnt mit "." → wird nicht ausgeführt)
.ansible-template:
  image: ansible/ansible:latest
  before_script:
    - pip install -q jmespath netaddr
    - ansible --version
  variables:
    ANSIBLE_FORCE_COLOR: "true"
  tags:
    - docker

# Jobs erben vom Template
lint-playbooks:
  extends: .ansible-template
  stage: validate
  script:
    - ansible-lint playbooks/

syntax-check:
  extends: .ansible-template
  stage: validate
  script:
    - ansible-playbook --syntax-check playbooks/*.yml

dry-run:
  extends: .ansible-template
  stage: test
  script:
    - ansible-playbook --check playbooks/deploy.yml
💡Template Best Practice
Beginnen Sie Template-Namen mit einem Punkt (.template). Diese werden von GitLab als "versteckt" behandelt und nicht als eigenständige Jobs ausgeführt.

4. Pipeline-Variablen & Secrets Management

Variablen machen Ihre Pipelines flexibel und sicher. Sie können Werte zentral definieren, sensible Daten schützen und Pipelines dynamisch steuern.

Variablen-Hierarchie

GitLab CI/CD Variablen können an verschiedenen Stellen definiert werden. Spätere Definitionen überschreiben frühere:

Prio 1Instance-Level (Admin → Settings → CI/CD)
Prio 2Group-Level (Gruppe → Settings → CI/CD)
Prio 3Project-Level (Project → Settings → CI/CD)
Prio 4.gitlab-ci.yml (variables:)
Prio 5 ⭐Job-Level (im Job selbst)

Variablen in .gitlab-ci.yml

📄 .gitlab-ci.yml
# Globale Variablen (für alle Jobs)
variables:
  NDFC_HOST: "10.1.1.100"
  ANSIBLE_HOST_KEY_CHECKING: "false"
  ENVIRONMENT: "staging"

stages:
  - test
  - deploy

# Job mit eigenen Variablen (überschreibt globale)
test-staging:
  stage: test
  variables:
    ENVIRONMENT: "staging"    # Überschreibt global
    DEBUG: "true"             # Nur für diesen Job
  script:
    - echo "Environment: $ENVIRONMENT"
    - echo "Debug: $DEBUG"
    - echo "Host: $NDFC_HOST"

deploy-production:
  stage: deploy
  variables:
    ENVIRONMENT: "production"
  script:
    - echo "Deploying to $ENVIRONMENT on $NDFC_HOST"

Secrets sicher speichern

⚠️Niemals Secrets im Code!
Passwörter, API-Keys und Tokens gehören NIEMALS in die .gitlab-ci.yml oder irgendeine Datei im Repository! Sie gehören in die GitLab CI/CD Variables (verschlüsselt gespeichert).

Schritt für Schritt: Secrets einrichten

  1. 1. In GitLab: Settings → CI/CD → Variables → "Add Variable"
  2. 2. Variable konfigurieren:
    • Key: NDFC_PASSWORD (Großbuchstaben, Unterstriche)
    • Value: Ihr geheimes Passwort
    • • ☑️ Mask variable: Wird in Logs als [MASKED] angezeigt
    • • ☑️ Protect variable: Nur auf protected Branches verfügbar
  3. 3. In Pipeline nutzen: Als $NDFC_PASSWORD
📄 .gitlab-ci.yml
deploy-to-ndfc:
  stage: deploy
  variables:
    # Öffentliche Variablen OK
    NDFC_HOST: "10.1.1.100"
    NDFC_USER: "admin"
    # NDFC_PASSWORD kommt aus GitLab CI/CD Variables!
  script:
    # Die Variable ist automatisch verfügbar
    - echo "Connecting to $NDFC_HOST as $NDFC_USER"
    - ansible-playbook -e "ndfc_password=$NDFC_PASSWORD" deploy.yml
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Variable-Typen in GitLab

🔤 Variable (Standard)

Wird als Umgebungsvariable exportiert:

$MY_VAR
📄 File

Inhalt wird in temporäre Datei geschrieben:

cat $MY_FILE
📄 .gitlab-ci.yml
# Beispiel: SSH Key als File-Variable
# In GitLab: Type = "File", Key = "SSH_PRIVATE_KEY"

deploy-via-ssh:
  stage: deploy
  script:
    # $SSH_PRIVATE_KEY ist der PFAD zur temporären Datei!
    - chmod 600 "$SSH_PRIVATE_KEY"
    - ssh -i "$SSH_PRIVATE_KEY" admin@switch.local "show run"

Dynamische Variablen

Sie können Variablen auch dynamisch während der Pipeline erstellen:

📄 .gitlab-ci.yml
# Variablen an nachfolgende Jobs weitergeben
generate-version:
  stage: prepare
  script:
    - VERSION="1.0.$(date +%Y%m%d)"
    - echo "VERSION=$VERSION" >> build.env
  artifacts:
    reports:
      dotenv: build.env    # Macht Variablen verfügbar

use-version:
  stage: build
  needs:
    - generate-version
  script:
    - echo "Building version $VERSION"    # $VERSION ist verfügbar!

5. Environments & Deployments

Environments in GitLab repräsentieren Ihre Zielumgebungen (Staging, Production) und ermöglichen Tracking, History und Rollbacks.

Environments definieren

📄 .gitlab-ci.yml
stages:
  - deploy

deploy-staging:
  stage: deploy
  script:
    - ansible-playbook -i inventory/staging deploy.yml
  environment:
    name: staging
    url: https://staging.network.local    # Optional: Link in GitLab UI

deploy-production:
  stage: deploy
  script:
    - ansible-playbook -i inventory/production deploy.yml
  environment:
    name: production
    url: https://network.local
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual

Was bringt das?

📊
Dashboard

Überblick aller Environments in Operations → Environments

📜
History

Wer hat wann was deployed?

Rollback

Ein-Klick Rollback zu vorherigen Versionen

🔐
Protection

Nur bestimmte User dürfen deployen

Dynamische Environments

Für Feature Branches können Sie dynamische Environments erstellen, die automatisch wieder gelöscht werden:

📄 .gitlab-ci.yml
# Review App pro Branch
deploy-review:
  stage: deploy
  script:
    - echo "Deploying $CI_COMMIT_REF_SLUG"
    - ./deploy-review.sh "$CI_COMMIT_REF_SLUG"
  environment:
    name: review/$CI_COMMIT_REF_SLUG    # z.B. review/feature-xyz
    url: https://$CI_COMMIT_REF_SLUG.review.local
    on_stop: stop-review                 # Cleanup-Job
  rules:
    - if: $CI_COMMIT_BRANCH != "main" && $CI_COMMIT_BRANCH != "develop"

stop-review:
  stage: deploy
  script:
    - ./cleanup-review.sh "$CI_COMMIT_REF_SLUG"
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  rules:
    - if: $CI_COMMIT_BRANCH != "main" && $CI_COMMIT_BRANCH != "develop"
      when: manual

6. Manual Approvals & Gates

Für kritische Infrastruktur wollen Sie nicht, dass jede Änderung automatisch deployed wird. Manual Approvals schaffen die nötige Kontrolle.

Einfacher manueller Trigger

📄 .gitlab-ci.yml
deploy-production:
  stage: deploy
  script:
    - ansible-playbook deploy.yml
  environment:
    name: production
  when: manual    # Erfordert Klick in GitLab UI
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Approval mit Blockierung

Standardmäßig läuft die Pipeline weiter, auch wenn ein manueller Job noch nicht gestartet wurde. Mit allow_failure: false wird die Pipeline blockiert:

📄 .gitlab-ci.yml
stages:
  - validate
  - approve
  - deploy

lint-config:
  stage: validate
  script:
    - yamllint config.yaml

# GATE: Blockiert bis jemand approved
approval-gate:
  stage: approve
  script:
    - echo "Deployment approved by $GITLAB_USER_LOGIN at $(date)"
  when: manual
  allow_failure: false    # ⚠️ Pipeline wartet hier!

deploy-network:
  stage: deploy
  script:
    - ansible-playbook deploy.yml
  needs:
    - approval-gate    # Startet erst nach Approval!

Mehrere Approver für kritische Changes

Für besonders kritische Deployments können Sie mehrere Approvals erfordern:

📄 .gitlab-ci.yml
stages:
  - test
  - approve
  - deploy

# Zwei unabhängige Approvals erforderlich
approval-team-lead:
  stage: approve
  script:
    - echo "Approved by Team Lead: $GITLAB_USER_LOGIN"
  when: manual
  allow_failure: false

approval-network-admin:
  stage: approve
  script:
    - echo "Approved by Network Admin: $GITLAB_USER_LOGIN"
  when: manual
  allow_failure: false

deploy-core:
  stage: deploy
  script:
    - ansible-playbook deploy-core-switches.yml
  needs:
    - approval-team-lead
    - approval-network-admin    # Beide müssen approved haben!

Protected Environments

🛡️ Environment Protection einrichten

  1. 1. Settings → CI/CD → Protected Environments
  2. 2. "Protect an environment" → "production"
  3. 3. "Allowed to deploy" → Nur Senior Engineers
  4. 4. Optional: "Required approvals" → 2

7. Vollständige Netzwerk-Pipeline

Jetzt setzen wir alles zusammen! Diese Pipeline ist ein komplettes Beispiel für Netzwerk-Automatisierung mit VXLAN EVPN Fabric Deployment.

Pipeline-Architektur

Stage 1
VALIDATE
  • • yamllint
  • • ansible-lint
  • • syntax-check
Stage 2
TEST
  • • dry-run
  • • pre-checks
  • • diff-report
Stage 3
DEPLOY
  • • ⚠️ manual gate
  • • deploy fabric
  • • deploy networks
Stage 4
VERIFY
  • • ping-tests
  • • bgp-checks
  • • compliance

Die komplette .gitlab-ci.yml

📄 .gitlab-ci.yml
# ============================================
# VXLAN EVPN Fabric Deployment Pipeline
# ============================================

stages:
  - validate
  - test
  - approve
  - deploy
  - verify

# ============================================
# GLOBAL CONFIGURATION
# ============================================

variables:
  # Ansible Settings
  ANSIBLE_FORCE_COLOR: "true"
  ANSIBLE_HOST_KEY_CHECKING: "false"
  ANSIBLE_STDOUT_CALLBACK: "yaml"
  
  # NDFC Connection (Secrets aus GitLab CI/CD Variables!)
  NDFC_HOST: "10.1.1.100"
  NDFC_USER: "admin"
  # NDFC_PASSWORD → kommt aus CI/CD Variables
  
  # Pipeline Settings
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

default:
  image: python:3.11-slim
  tags:
    - docker
  before_script:
    - pip install --quiet ansible-core yamllint ansible-lint jmespath
  cache:
    key: pip-cache
    paths:
      - .cache/pip

# ============================================
# TEMPLATES
# ============================================

.ansible-base:
  before_script:
    - pip install --quiet ansible-core yamllint ansible-lint jmespath netaddr
    - ansible-galaxy collection install cisco.dcnm --force
    - ansible --version

# ============================================
# STAGE 1: VALIDATE
# ============================================

yaml-lint:
  stage: validate
  script:
    - echo "🔍 Prüfe YAML-Syntax..."
    - yamllint -c .yamllint.yml .
  rules:
    - changes:
        - "**/*.yml"
        - "**/*.yaml"

ansible-lint:
  stage: validate
  extends: .ansible-base
  script:
    - echo "🔍 Prüfe Ansible Best Practices..."
    - ansible-lint playbooks/ --exclude .cache
  rules:
    - changes:
        - playbooks/**/*
        - roles/**/*
  allow_failure: true    # Warnungen blockieren nicht

syntax-check:
  stage: validate
  extends: .ansible-base
  script:
    - echo "🔍 Prüfe Playbook-Syntax..."
    - |
      for playbook in playbooks/*.yml; do
        echo "Checking $playbook..."
        ansible-playbook --syntax-check "$playbook"
      done
  artifacts:
    when: on_failure
    paths:
      - ansible.log
    expire_in: 1 day

# ============================================
# STAGE 2: TEST
# ============================================

dry-run-fabric:
  stage: test
  extends: .ansible-base
  script:
    - echo "🧪 Dry-Run Fabric Deployment..."
    - ansible-playbook playbooks/deploy-fabric.yml --check --diff
  environment:
    name: staging
  artifacts:
    paths:
      - dry-run-report.txt
    expire_in: 1 week
  rules:
    - if: $CI_COMMIT_BRANCH =~ /^(main|develop)$/

pre-deployment-checks:
  stage: test
  extends: .ansible-base
  script:
    - echo "🔍 Pre-Deployment Checks..."
    - ansible-playbook playbooks/pre-checks.yml
    - echo "✅ Alle Switches erreichbar"
    - echo "✅ NDFC API verfügbar"
    - echo "✅ Keine Konflikte mit bestehendem Fabric"
  artifacts:
    reports:
      junit: pre-check-results.xml
    when: always

generate-diff-report:
  stage: test
  extends: .ansible-base
  script:
    - echo "📊 Generiere Änderungsbericht..."
    - ansible-playbook playbooks/generate-diff.yml
    - cat fabric-changes.md
  artifacts:
    paths:
      - fabric-changes.md
    expose_as: "Fabric Changes Report"
    expire_in: 30 days
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

# ============================================
# STAGE 3: APPROVE (Manual Gate)
# ============================================

approval-gate:
  stage: approve
  script:
    - echo "✅ Deployment freigegeben von: $GITLAB_USER_LOGIN"
    - echo "📅 Zeitpunkt: $(date -Iseconds)"
    - echo "🔖 Commit: $CI_COMMIT_SHA"
  when: manual
  allow_failure: false    # Pipeline wartet!
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production
    action: prepare

# ============================================
# STAGE 4: DEPLOY
# ============================================

deploy-fabric:
  stage: deploy
  extends: .ansible-base
  script:
    - echo "🚀 Deploying Fabric to NDFC..."
    - |
      ansible-playbook playbooks/deploy-fabric.yml         -e "ndfc_host=$NDFC_HOST"         -e "ndfc_user=$NDFC_USER"         -e "ndfc_password=$NDFC_PASSWORD"
    - echo "✅ Fabric Deployment abgeschlossen"
  needs:
    - approval-gate
  environment:
    name: production
    url: https://$NDFC_HOST
  artifacts:
    paths:
      - deployment-log.txt
    when: always
    expire_in: 90 days
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy-networks:
  stage: deploy
  extends: .ansible-base
  script:
    - echo "🌐 Deploying Networks & VRFs..."
    - ansible-playbook playbooks/deploy-networks.yml
  needs:
    - deploy-fabric
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# ============================================
# STAGE 5: VERIFY (Post-Deployment)
# ============================================

verify-connectivity:
  stage: verify
  extends: .ansible-base
  script:
    - echo "🔍 Verifiziere Konnektivität..."
    - ansible-playbook playbooks/verify-connectivity.yml
    - echo "✅ Alle Ping-Tests erfolgreich"
  needs:
    - deploy-networks
  artifacts:
    reports:
      junit: connectivity-report.xml
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

verify-bgp:
  stage: verify
  extends: .ansible-base
  script:
    - echo "🔍 Verifiziere BGP EVPN Status..."
    - ansible-playbook playbooks/verify-bgp.yml
    - cat bgp-status.txt
  needs:
    - deploy-fabric
  artifacts:
    paths:
      - bgp-status.txt
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

compliance-check:
  stage: verify
  extends: .ansible-base
  script:
    - echo "📋 Compliance Check..."
    - ansible-playbook playbooks/compliance-check.yml
  allow_failure: true
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# ============================================
# ROLLBACK (Manuell bei Bedarf)
# ============================================

rollback:
  stage: deploy
  extends: .ansible-base
  script:
    - echo "⏪ Rolling back to previous state..."
    - ansible-playbook playbooks/rollback.yml
    - echo "✅ Rollback abgeschlossen"
  when: manual
  allow_failure: true
  environment:
    name: production
    action: stop
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Unterstützende Dateien

📄 .yamllint.yml
# YAMLlint Konfiguration
extends: default

rules:
  line-length:
    max: 120
    level: warning
  truthy:
    allowed-values: ['true', 'false', 'yes', 'no']
  comments:
    min-spaces-from-content: 1
  indentation:
    spaces: 2
    indent-sequences: true
📄 .ansible-lint
# Ansible-lint Konfiguration
skip_list:
  - yaml[line-length]
  - name[missing]

warn_list:
  - experimental

exclude_paths:
  - .cache/
  - .git/

8. Praxis-Übungen

Jetzt sind Sie dran! Die folgenden 10 Übungen führen Sie von einer einfachen "Hello World" Pipeline bis zu einer produktionsreifen Netzwerk-Automatisierung.

🔧Übung 1: Hello World Pipeline (10 Min)

Erstellen Sie Ihre erste CI/CD Pipeline in GitLab.

Aufgabe:

  1. Erstellen Sie ein neues Projekt in GitLab (oder nutzen Sie ein bestehendes)
  2. Erstellen Sie die Datei .gitlab-ci.yml im Root des Repositories
  3. Committen und pushen Sie
  4. Beobachten Sie die Pipeline in GitLab (CI/CD → Pipelines)
📄 .gitlab-ci.yml
# Ihre erste Pipeline!
stages:
  - test

hello-world:
  stage: test
  image: alpine:latest
  script:
    - echo "🎉 Hello World from CI/CD!"
    - echo "Repository: $CI_PROJECT_NAME"
    - echo "Branch: $CI_COMMIT_BRANCH"
    - echo "Commit: $CI_COMMIT_SHORT_SHA"
    - echo "Triggered by: $GITLAB_USER_NAME"

Erwartetes Ergebnis:

Ein grüner Pipeline-Status und die Echo-Ausgaben im Job-Log.

🔧Übung 2: YAML-Lint Job hinzufügen (15 Min)

Erweitern Sie die Pipeline um automatische YAML-Validierung.

Aufgabe:

  1. Erstellen Sie eine YAML-Testdatei network-config.yaml
  2. Fügen Sie einen yamllint Job zur Pipeline hinzu
  3. Bauen Sie absichtlich einen Fehler ein und beobachten Sie
  4. Korrigieren Sie den Fehler
📄 network-config.yaml
# Beispiel Netzwerk-Konfiguration
fabric:
  name: DC-Fabric
  bgp_asn: 65001
  
spine_switches:
  - name: spine-01
    mgmt_ip: 10.1.1.1
  - name: spine-02
    mgmt_ip: 10.1.1.2
    
leaf_switches:
  - name: leaf-01
    mgmt_ip: 10.1.2.1
    role: border
📄 .gitlab-ci.yml
stages:
  - validate
  - test

yaml-lint:
  stage: validate
  image: python:3.11-slim
  script:
    - pip install yamllint
    - echo "Checking all YAML files..."
    - yamllint . -d relaxed
  allow_failure: false

hello-world:
  stage: test
  image: alpine:latest
  script:
    - echo "YAML validation passed! ✅"
  needs:
    - yaml-lint

Experiment:

Fügen Sie eine Zeile mit falschem Indent ein (3 Spaces statt 2) und beobachten Sie den Fehler.

🔧Übung 3: Multi-Stage Pipeline (20 Min)

Bauen Sie eine Pipeline mit mehreren Stages und Abhängigkeiten.

📄 .gitlab-ci.yml
stages:
  - prepare
  - validate
  - test
  - report

# Stage 1: Vorbereitung
install-tools:
  stage: prepare
  image: python:3.11
  script:
    - pip install yamllint ansible-core
    - echo "Tools installiert"
    - pip freeze > requirements-frozen.txt
  artifacts:
    paths:
      - requirements-frozen.txt
    expire_in: 1 hour

# Stage 2: Validierung (2 Jobs parallel!)
validate-yaml:
  stage: validate
  image: python:3.11
  script:
    - pip install yamllint
    - yamllint network-config.yaml

validate-schema:
  stage: validate
  image: python:3.11
  script:
    - echo "Schema validation would go here"
    - echo "Both jobs run in parallel!"

# Stage 3: Tests
run-tests:
  stage: test
  image: python:3.11
  needs:
    - validate-yaml      # Nur von diesem abhängig
  script:
    - echo "Running tests..."
    - echo "Using frozen requirements from prepare stage"
    - cat requirements-frozen.txt

# Stage 4: Report
generate-report:
  stage: report
  image: alpine:latest
  script:
    - echo "# Pipeline Report" > report.md
    - echo "Date: $(date)" >> report.md
    - echo "Status: SUCCESS" >> report.md
    - cat report.md
  artifacts:
    paths:
      - report.md
    expire_in: 30 days

Beobachten Sie:

  • Wie die Stages nacheinander laufen
  • Wie validate-yaml und validate-schema parallel laufen
  • Wie Artifacts weitergegeben werden
🔧Übung 4: Variablen und Secrets (15 Min)

Lernen Sie den sicheren Umgang mit Variablen und Secrets.

Schritt 1: Geheime Variable anlegen

  1. GitLab → Settings → CI/CD → Variables
  2. Add Variable: SECRET_API_KEY = super-secret-123
  3. ☑️ Mask variable aktivieren

Schritt 2: Pipeline mit Variablen

📄 .gitlab-ci.yml
variables:
  # Öffentliche Variablen (OK im Code)
  API_URL: "https://api.example.com"
  LOG_LEVEL: "INFO"

stages:
  - test

show-variables:
  stage: test
  image: alpine:latest
  variables:
    JOB_SPECIFIC: "nur für diesen Job"
  script:
    # Öffentliche Variablen
    - echo "API URL: $API_URL"
    - echo "Log Level: $LOG_LEVEL"
    - echo "Job Variable: $JOB_SPECIFIC"
    
    # CI/CD vordefinierte Variablen
    - echo "Pipeline ID: $CI_PIPELINE_ID"
    - echo "Commit Author: $CI_COMMIT_AUTHOR"
    
    # Geheime Variable (wird als [MASKED] angezeigt!)
    - echo "Secret: $SECRET_API_KEY"
    
    # NIE das tun (Secret würde geleakt):
    # - cat file.txt | grep $SECRET_API_KEY  # ❌
    
test-with-secret:
  stage: test
  image: python:3.11
  script:
    - |
      python3 << 'EOF'
      import os
      secret = os.environ.get('SECRET_API_KEY', 'not set')
      print(f"Secret length: {len(secret)} chars")
      # In echtem Code: API-Call mit dem Secret
      EOF

Was passiert:

Das Secret wird im Log als [MASKED] angezeigt – die Maskierung funktioniert!

🔧Übung 5: Docker-Image in Pipeline (20 Min)

Nutzen Sie ein benutzerdefiniertes Docker-Image mit vorinstallierten Tools.

Option A: Öffentliches Image nutzen

📄 .gitlab-ci.yml
stages:
  - test

ansible-test:
  stage: test
  # Fertiges Ansible-Image von Docker Hub
  image: cytopia/ansible:latest
  script:
    - ansible --version
    - ansible-galaxy collection list
    - echo "Ready for Network Automation!"

Option B: Eigenes Image bauen (advanced)

📄 Dockerfile
FROM python:3.11-slim

# Netzwerk-Tools installieren
RUN pip install --no-cache-dir \
    ansible-core \
    netmiko \
    napalm \
    paramiko \
    jmespath \
    yamllint \
    ansible-lint

# Ansible Collections installieren
RUN ansible-galaxy collection install \
    cisco.dcnm \
    cisco.nxos \
    ansible.netcommon

WORKDIR /workspace
📄 .gitlab-ci.yml
stages:
  - build
  - test

build-image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - main

use-custom-image:
  stage: test
  image: $CI_REGISTRY_IMAGE:latest
  script:
    - ansible --version
    - python -c "import netmiko; print('Netmiko ready!')"
🔧Übung 6: Ansible Dry-Run in Pipeline (25 Min)

Testen Sie Ansible Playbooks ohne tatsächliche Änderungen.

Schritt 1: Beispiel-Playbook erstellen

📄 playbooks/hello.yml
---
- name: Hello World Playbook
  hosts: localhost
  gather_facts: true
  
  tasks:
    - name: Show greeting
      ansible.builtin.debug:
        msg: "Hello from {{ ansible_hostname }}!"
        
    - name: Create a test file (will be checked, not created)
      ansible.builtin.file:
        path: /tmp/test-file.txt
        state: touch

Schritt 2: Pipeline mit Dry-Run

📄 .gitlab-ci.yml
stages:
  - validate
  - test

ansible-syntax:
  stage: validate
  image: cytopia/ansible:latest
  script:
    - ansible-playbook --syntax-check playbooks/hello.yml

ansible-dry-run:
  stage: test
  image: cytopia/ansible:latest
  script:
    - echo "🧪 Running Ansible in Check Mode (Dry-Run)..."
    - ansible-playbook playbooks/hello.yml --check --diff
    - echo "✅ Dry-Run completed - no changes made!"
  artifacts:
    paths:
      - ansible.log
    when: always

Was passiert:

  • --syntax-check prüft nur die Syntax, führt nichts aus
  • --check simuliert die Ausführung ("was würde passieren?")
  • --diff zeigt die Unterschiede an, die gemacht würden
🔧Übung 7: Manual Deployment Gate (15 Min)

Implementieren Sie einen manuellen Freigabe-Schritt.

📄 .gitlab-ci.yml
stages:
  - test
  - approve
  - deploy

automated-tests:
  stage: test
  image: alpine:latest
  script:
    - echo "Running automated tests..."
    - sleep 2
    - echo "✅ All tests passed!"

# DER GATE - Pipeline wartet hier!
manual-approval:
  stage: approve
  script:
    - echo "======================================"
    - echo "✅ DEPLOYMENT APPROVED"
    - echo "======================================"
    - echo "Approved by: $GITLAB_USER_LOGIN"
    - echo "Approved at: $(date)"
    - echo "Commit: $CI_COMMIT_SHORT_SHA"
  when: manual
  allow_failure: false    # ⚠️ WICHTIG: Pipeline blockiert!

deploy-to-prod:
  stage: deploy
  image: alpine:latest
  needs:
    - manual-approval
  script:
    - echo "🚀 Deploying to production..."
    - sleep 2
    - echo "✅ Deployment successful!"
  environment:
    name: production

So testen Sie:

  1. Pushen Sie die Änderungen
  2. Beobachten Sie, wie die Pipeline nach "test" anhält
  3. Der "approve" Job zeigt einen "Play" Button ▶️
  4. Klicken Sie auf den Button → Deploy startet
🔧Übung 8: Artifacts speichern und herunterladen (20 Min)

Generieren Sie Reports und machen Sie sie downloadbar.

📄 .gitlab-ci.yml
stages:
  - generate
  - analyze
  - report

generate-data:
  stage: generate
  image: python:3.11
  script:
    - mkdir -p output
    - |
      python3 << 'EOF'
      import json
      from datetime import datetime
      
      data = {
          "timestamp": datetime.now().isoformat(),
          "pipeline_id": "$CI_PIPELINE_ID",
          "devices_checked": 5,
          "status": "healthy",
          "findings": []
      }
      
      with open("output/health-check.json", "w") as f:
          json.dump(data, f, indent=2)
      print("Generated health-check.json")
      EOF
    - cat output/health-check.json
  artifacts:
    paths:
      - output/
    expire_in: 1 week

analyze-data:
  stage: analyze
  image: python:3.11
  needs:
    - generate-data
  script:
    - ls -la output/
    - cat output/health-check.json
    - |
      python3 << 'EOF'
      import json
      
      with open("output/health-check.json") as f:
          data = json.load(f)
      
      print(f"Status: {data['status']}")
      print(f"Devices checked: {data['devices_checked']}")
      
      # Markdown Report erstellen
      with open("output/report.md", "w") as f:
          f.write("# Network Health Report\n\n")
          f.write(f"**Status:** {data['status']}\n\n")
          f.write(f"**Devices:** {data['devices_checked']}\n")
      EOF
  artifacts:
    paths:
      - output/
    expose_as: "Network Reports"    # Zeigt Download-Button in MR!
    expire_in: 30 days

final-summary:
  stage: report
  image: alpine:latest
  needs:
    - analyze-data
  script:
    - echo "=== Final Report ==="
    - cat output/report.md

So laden Sie Artifacts herunter:

  1. In der Pipeline → Job → rechts "Browse" oder "Download"
  2. Bei Merge Requests: "Network Reports" Link direkt im MR
  3. API: GET /projects/:id/jobs/:job_id/artifacts
🔧Übung 9: Pipeline Fehler debuggen (15 Min)

Lernen Sie, wie man Pipeline-Fehler findet und behebt.

Häufige Fehler und Lösungen:

📄 .gitlab-ci.yml
stages:
  - debug

# Fehler 1: Exit Code nicht 0
# Lösung: || true oder set +e
handle-errors:
  stage: debug
  image: alpine:latest
  script:
    # ❌ Bricht ab wenn grep nichts findet:
    # - grep "pattern" file.txt
    
    # ✅ Ignoriert Fehler:
    - grep "pattern" file.txt || true
    
    # ✅ Oder: Exit-Status speichern
    - |
      if grep -q "pattern" file.txt; then
        echo "Pattern gefunden"
      else
        echo "Pattern nicht gefunden (OK)"
      fi

# Fehler 2: Fehlende Dependencies
# Lösung: before_script nutzen
check-dependencies:
  stage: debug
  image: python:3.11
  before_script:
    - pip install --quiet pyyaml
  script:
    - python -c "import yaml; print('YAML OK')"

# Fehler 3: Debug-Ausgaben hinzufügen
verbose-debug:
  stage: debug
  image: alpine:latest
  script:
    - set -x    # Zeigt jeden Befehl an
    - echo "Debug Mode aktiv"
    - ls -la
    - pwd
    - env | sort    # Alle Umgebungsvariablen

# Fehler 4: Interaktive Debug-Session (advanced)
# Nur für selbst-gehostete Runner!
# interactive-debug:
#   stage: debug
#   script:
#     - sleep 3600    # 1 Stunde warten für SSH/Debug
#   when: manual

Debug-Checkliste:

  1. Job-Log lesen: Scroll zu "Running with gitlab-runner"
  2. Exit-Code prüfen: Welcher Befehl hatte Error?
  3. Variablen prüfen: env | sort einbauen
  4. Artifacts prüfen: Logs als Artifact speichern
  5. Lokal testen: Gleichen Befehl in Docker ausführen
🔧Übung 10: Vollständige Netzwerk-Pipeline bauen (45 Min)

Das große Finale! Bauen Sie eine produktionsreife Pipeline.

Dateistruktur:

my-network-project/
├── .gitlab-ci.yml
├── .yamllint.yml
├── inventory/
│   ├── staging.yml
│   └── production.yml
├── playbooks/
│   ├── deploy.yml
│   ├── verify.yml
│   └── rollback.yml
├── group_vars/
│   └── all.yml
└── README.md

Inventory (Beispiel):

📄 inventory/staging.yml
all:
  children:
    switches:
      hosts:
        switch-01:
          ansible_host: 10.1.1.1
        switch-02:
          ansible_host: 10.1.1.2
  vars:
    ansible_network_os: cisco.nxos.nxos
    ansible_connection: network_cli

Playbooks:

📄 playbooks/deploy.yml
---
- name: Deploy Network Configuration
  hosts: switches
  gather_facts: false
  
  tasks:
    - name: Show target device
      ansible.builtin.debug:
        msg: "Configuring {{ inventory_hostname }}"
        
    # In Produktion: Echte Netzwerk-Module nutzen
    - name: Configure VLAN (Example)
      ansible.builtin.debug:
        msg: "Would configure VLANs here"

Die komplette Pipeline:

📄 .gitlab-ci.yml
# ============================================
# PRODUCTION-READY NETWORK PIPELINE
# ============================================

stages:
  - validate
  - test
  - approve
  - deploy
  - verify

variables:
  ANSIBLE_FORCE_COLOR: "true"
  ANSIBLE_HOST_KEY_CHECKING: "false"

default:
  image: cytopia/ansible:latest
  tags:
    - docker
  before_script:
    - ansible --version

# --- VALIDATE ---
lint:
  stage: validate
  script:
    - pip install yamllint ansible-lint
    - yamllint .
    - ansible-lint playbooks/

syntax:
  stage: validate
  script:
    - ansible-playbook --syntax-check playbooks/*.yml

# --- TEST ---
dry-run-staging:
  stage: test
  script:
    - ansible-playbook -i inventory/staging.yml playbooks/deploy.yml --check
  environment:
    name: staging

# --- APPROVE ---
approval:
  stage: approve
  script:
    - echo "Approved by $GITLAB_USER_LOGIN at $(date)"
  when: manual
  allow_failure: false
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# --- DEPLOY ---
deploy-production:
  stage: deploy
  script:
    - ansible-playbook -i inventory/production.yml playbooks/deploy.yml
  needs:
    - approval
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# --- VERIFY ---
post-deploy-check:
  stage: verify
  script:
    - ansible-playbook -i inventory/production.yml playbooks/verify.yml
  needs:
    - deploy-production
  artifacts:
    paths:
      - verification-report.txt
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# --- ROLLBACK (Manual) ---
rollback:
  stage: deploy
  script:
    - ansible-playbook -i inventory/production.yml playbooks/rollback.yml
  when: manual
  allow_failure: true
  environment:
    name: production
    action: stop

✅ Checkliste für produktionsreif:

  • ☐ Alle YAML-Dateien werden gelintet
  • ☐ Syntax-Check für alle Playbooks
  • ☐ Dry-Run gegen Staging-Umgebung
  • ☐ Manual Approval Gate
  • ☐ Deployment nur auf main-Branch
  • ☐ Post-Deployment Verification
  • ☐ Rollback-Option vorhanden
  • ☐ Secrets in CI/CD Variables

9. Troubleshooting

Pipelines können aus vielen Gründen fehlschlagen. Hier sind die häufigsten Probleme und ihre Lösungen.

Häufige Pipeline-Fehler

❌ "This job is stuck"

Ursache: Kein passender Runner verfügbar (Tags stimmen nicht überein)

Lösung: Prüfen Sie Settings → CI/CD → Runners. Entfernen Sietags: oder nutzen Sie Tags, die Ihre Runner haben.

❌ "Job failed: exit code 1"

Ursache: Ein Befehl im Script hat einen Fehler zurückgegeben

Lösung: Job-Log lesen. Suchen Sie nach dem ersten roten Eintrag. Oft: Fehlende Dependencies, falsche Pfade, Typos.

❌ "yaml invalid"

Ursache: Syntax-Fehler in .gitlab-ci.yml

Lösung: Nutzen Sie den CI/CD Lint (CI/CD → Editor → "Validate"). Achten Sie auf Einrückung (2 Spaces, keine Tabs)!

❌ "Variable is empty"

Ursache: Protected Variable auf non-protected Branch

Lösung: Entweder Variable "unprotected" machen, oder auf protected Branch testen (main).

❌ "Artifacts too large"

Ursache: Artifact-Größe überschreitet Limit (100 MB default)

Lösung: Nur relevante Dateien als Artifact speichern. Nutzen Sie exclude: in paths.

Debug-Strategien

📄 .gitlab-ci.yml
debug-job:
  stage: test
  script:
    # 1. Alle Umgebungsvariablen anzeigen
    - env | sort
    
    # 2. Verzeichnisstruktur zeigen
    - pwd
    - ls -la
    - find . -type f -name "*.yml"
    
    # 3. Befehle mit Verbose-Modus
    - set -x    # Zeigt jeden Befehl vor Ausführung
    - pip install ansible
    - set +x
    
    # 4. Fehler abfangen und trotzdem weitermachen
    - ./might-fail.sh || echo "Fehler ignoriert"
    
    # 5. Exit-Code speichern
    - |
      ./important-script.sh
      EXIT_CODE=$?
      echo "Exit Code war: $EXIT_CODE"
      if [ $EXIT_CODE -ne 0 ]; then
        echo "Fehler aufgetreten, aber wir machen weiter"
      fi
  artifacts:
    when: always    # Auch bei Fehler Artifacts speichern
    paths:
      - "*.log"

Pipeline lokal testen

# gitlab-runner lokal installieren (macOS)
brew install gitlab-runner

# Oder mit Docker (alle Plattformen)
docker pull gitlab/gitlab-runner:latest

# Pipeline lokal ausführen
gitlab-runner exec docker my-job-name

# Mit Variablen
gitlab-runner exec docker my-job-name \
  --env "MY_VAR=value" \
  --env "SECRET=test-secret"

10. Best Practices

Diese Best Practices haben sich in Enterprise-Umgebungen bewährt und helfen, wartbare, sichere und effiziente Pipelines zu bauen.

Pipeline-Design

✅ Do

  • • Schnelle Jobs zuerst (Fail Fast)
  • • Kurze, fokussierte Jobs
  • • Templates für Wiederverwendung
  • • Aussagekräftige Job-Namen
  • • Artifacts für wichtige Outputs
  • • Cache für Dependencies

❌ Don't

  • • Secrets im Code speichern
  • • Große Monolith-Jobs
  • allow_failure: true überall
  • • Unbegrenzte Artifact-Retention
  • • Hardcoded Pfade/IPs
  • • Fehlende Timeouts

Security Best Practices

📄 .gitlab-ci.yml
# ✅ GUTE Security-Praktiken

variables:
  # Nie Secrets hier!
  PUBLIC_CONFIG: "ok"
  
deploy-secure:
  stage: deploy
  script:
    # Secrets kommen aus CI/CD Variables
    - echo "$DB_PASSWORD" | some-tool --password-stdin
    
    # Niemals so:
    # - some-tool --password="$DB_PASSWORD"  # ❌ Sichtbar in Process List!
    
    # Secrets in Dateien schreiben (temporär)
    - echo "$SSH_KEY" > /tmp/key && chmod 600 /tmp/key
    - ssh -i /tmp/key server
    - rm /tmp/key
    
  # Nur auf protected Branches deployen
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    
  # Nur bestimmte Runner nutzen (für sensible Jobs)
  tags:
    - secure-runner
    
  # Job-Token einschränken
  id_tokens:
    OIDC_TOKEN:
      aud: https://vault.company.com

Performance-Tipps

TippErsparnisWie
Cache nutzen~50% Build-Zeitcache: paths: [.cache/]
Kleine Images~30% Pull-Zeitpython:3.11-slim statt python:3.11
needs: statt StagesParallelitätneeds: [job1] statt auf ganze Stage warten
Interruptible JobsRunner-Zeitinterruptible: true (bei neuem Push abbrechen)

Checkliste für neue Pipelines

📋 Pipeline Review Checklist

Struktur:

  • ☐ Stages sind sinnvoll benannt
  • ☐ Jobs haben beschreibende Namen
  • ☐ Templates werden verwendet
  • ☐ Timeouts sind gesetzt

Security:

  • ☐ Keine Secrets im Code
  • ☐ Protected Variables korrekt
  • ☐ Deploy nur auf main
  • ☐ Manual Gates vorhanden

Reliability:

  • ☐ Artifacts werden gespeichert
  • ☐ Fehler werden abgefangen
  • ☐ Rollback ist möglich
  • ☐ Post-Deploy Verification

Performance:

  • ☐ Cache ist konfiguriert
  • ☐ Kleine Docker Images
  • ☐ Jobs laufen parallel wo möglich
  • ☐ Keine unnötigen Stages

Zusammenfassung

🎓 Das haben Sie in diesem Modul gelernt

Konzepte:

  • ✅ CI/CD Unterschied verstanden
  • ✅ GitLab Architektur (Runner, Executor)
  • ✅ Stages, Jobs, Pipelines
  • ✅ Artifacts vs. Cache

Praktische Skills:

  • ✅ .gitlab-ci.yml schreiben
  • ✅ Variablen und Secrets nutzen
  • ✅ Manual Approvals einrichten
  • ✅ Netzwerk-Pipeline bauen

🔑 Key Takeaways

  • 1. CI/CD ist ein Fließband für Code – automatische Qualitätsprüfung bei jedem Commit
  • 2. .gitlab-ci.yml ist die "Partitur" Ihrer Pipeline
  • 3. Secrets gehören in CI/CD Variables, NIEMALS in den Code
  • 4. Für Netzwerke: Continuous Delivery mit Manual Gates ist der Sweet Spot
  • 5. Fail Fast: Schnelle Checks zuerst, teure Operationen später
💡Nächste Schritte
In Modul 6 werden wir das Gelernte anwenden und eine echte VXLAN EVPN Fabric mit einer vollständigen CI/CD Pipeline deployen!