05 — Continuous Integration

Teil C — Wie man ausliefert. Zurück zur Übersicht.

Lernziel

Nach diesem Kapitel kannst du erklären, was Continuous Integration von „wir haben einen Buildserver„ unterscheidet, kennst die vier Voraussetzungen und die Zehn-Minuten-Regel. Du hast eine lauffähige CI-Pipeline für den KFZ-Rechner gebaut.

5.1 Was Continuous Integration wirklich bedeutet

Der Begriff wird fast immer zu eng verstanden. Viele Teams sagen „wir machen CI“ und meinen: Es gibt einen Server, der nach einem Push Tests ausführt.

Das ist die Werkzeugseite. Der eigentliche Inhalt steckt im Wort Integration: Alle Entwickler führen ihre Arbeit fortlaufend — mindestens täglich — im Hauptzweig zusammen, und jede Zusammenführung wird automatisch überprüft.

Die Prüffrage lautet nicht „Habt ihr Jenkins?„, sondern:

Wann hat der letzte Entwickler seine Arbeit zuletzt in main integriert?

Lautet die Antwort „vor drei Wochen, er ist noch auf seinem Branch“, dann findet unabhängig von jeder installierten Software keine Continuous Integration statt. Es gibt dann lediglich einen Server, der jeden Zweig für sich prüft, während die Zweige immer weiter auseinanderlaufen.

5.2 Die vier Voraussetzungen

  1. Alles liegt in der Versionsverwaltung. Code, Tests, Pipeline-Definition, Infrastrukturbeschreibung, Datenbankmigrationen. Alles, was zum Bauen und Betreiben nötig ist — mit Ausnahme der Geheimnisse.
  2. Der Build läuft automatisch. Ein Befehl, keine Handgriffe, keine Anleitung mit dreißig Schritten.
  3. Die Tests laufen automatisch. Aus Kapitel 4 — und zwar bei jedem Commit, nicht nachts.
  4. Die Rückmeldung kommt schnell. Unter zehn Minuten. Sonst arbeitet niemand damit.

5.3 Die Zehn-Minuten-Regel

Warum ausgerechnet zehn Minuten? Weil das die Zeitspanne ist, in der ein Mensch bei der Sache bleibt.

Dauer der Pipeline Was der Entwickler tut
unter 2 min wartet und schaut zu
2–10 min holt sich Kaffee, kommt zurück, arbeitet weiter
10–30 min fängt etwas Neues an — der Kontext ist weg
über 30 min schaut abends nach, vielleicht
über 60 min die Pipeline ist ein Ritual, kein Werkzeug

Ab etwa zwanzig Minuten kippt das Verhalten: Man wartet die Rückmeldung nicht mehr ab, sondern arbeitet weiter — und stapelt Änderungen auf einer möglicherweise kaputten Grundlage. Die Pipeline erfüllt ihren Zweck dann nicht mehr.

Was hilft, wenn es zu lang dauert:

5.4 Der Aufbau einer Commit-Pipeline

  Push
   │
   ▼
 ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
 │ Lint     │─►│ Unit-    │─►│ Build    │─►│ Sicher-  │
 │ Format   │  │ Tests    │  │ Artefakt │  │ heit     │
 │  ~10 s   │  │  ~30 s   │  │  ~2 min  │  │  ~1 min  │
 └──────────┘  └──────────┘  └──────────┘  └──────────┘
      ▲                                          │
      │                                          ▼
   schnell scheitern                       Artefakt in
   ist eine Tugend                          die Registry

Das Prinzip heißt fail fast: Die billigsten und schnellsten Prüfungen kommen zuerst. Es ergibt keinen Sinn, acht Minuten lang ein Container-Image zu bauen, wenn der Code einen Syntaxfehler enthält, den ein Linter in vier Sekunden findet.

5.5 Der gebrochene Hauptzweig

Aus der Fertigungsindustrie stammt das Bild der Reißleine am Fließband: Wer einen Fehler bemerkt, hält das gesamte Band an. Was zunächst absurd teuer klingt, ist billiger als die Alternative — nämlich hundert fehlerhafte Teile weiterlaufen zu lassen.

Übertragen auf die Softwareentwicklung:

Ein roter Build auf main hat Vorrang vor allem anderen.

Solange main rot ist, kann niemand verlässlich integrieren, niemand ausliefern und niemand wissen, ob der eigene Fehler neu ist oder von der Vorgängerin stammt. Jede weitere Zusammenführung baut auf unsicherem Grund auf.

Praktische Regeln, die sich bewährt haben:

  • Wer den Build bricht, repariert ihn — sofort, nicht morgen.
  • Gelingt die Reparatur nicht binnen etwa zehn Minuten, wird der auslösende Commit zurückgenommen. Das ist keine Kränkung, sondern Betriebshygiene.
  • Niemand geht mit rotem main in den Feierabend.

Das ist übrigens der Punkt, an dem sich Kultur und Technik berühren: Die Regel funktioniert nur in einem Umfeld, in dem ein gebrochener Build als normaler Vorgang gilt und nicht als persönliches Versagen. Wo der Verursacher mit spitzen Bemerkungen rechnen muss, wird niemand mehr häufig integrieren — und damit ist die Continuous Integration erledigt, bevor sie begonnen hat.

5.6 Lab: CI-Pipeline für den KFZ-Rechner

Beide gängigen Systeme im Vergleich — nimm das, was bei dir verfügbar ist.

Variante GitLab CI

.gitlab-ci.yml
stages:
  - pruefen
  - testen
  - bauen

default:
  image: python:3.12-slim
  cache:
    key:
      files:
        - requirements.txt
    paths:
      - .venv/

.python_vorbereiten: &python_vorbereiten
  before_script:
    - python -m venv .venv
    - source .venv/bin/activate
    - pip install --quiet --upgrade pip
    - pip install --quiet -r requirements.txt
 
# ---------- Stufe 1: schnelle Prüfungen ----------

lint:
  stage: pruefen
  <<: *python_vorbereiten
  script:
    - ruff check src/ tests/
    - ruff format --check src/ tests/
 
# ---------- Stufe 2: Tests ----------

unit-tests:
  stage: testen
  <<: *python_vorbereiten
  script:
    - pytest --junitxml=report.xml --cov=src --cov-report=xml
  coverage: '/TOTAL.*\s+(\d+%)$/'
  artifacts:
    when: always
    reports:
      junit: report.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml
    expire_in: 1 week
 
# ---------- Stufe 3: Artefakt ----------

container-bauen:
  stage: bauen
  image: quay.io/podman/stable
  before_script:
    - podman login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
  script:
    - podman build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
    - podman push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Variante GitHub Actions

.github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  pruefen:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip

      - name: Abhängigkeiten installieren
        run: pip install -r requirements.txt

      - name: Linting
        run: |
          ruff check src/ tests/
          ruff format --check src/ tests/

      - name: Tests
        run: pytest --cov=src --cov-report=term-missing

  bauen:
    needs: pruefen
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

      - name: An der Registry anmelden
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Image bauen und hochladen
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

Das zugehörige Containerfile

Containerfile
FROM registry.access.redhat.com/ubi9/python-312:latest

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY src/ ./src/

USER 1001
EXPOSE 8080

CMD ["python", "-m", "src.server"]

Aufgaben

  1. Bring die Pipeline zum Laufen und beobachte die Gesamtdauer.
  2. Baue einen Formatierungsfehler ein. Welche Stufe schlägt fehl, nach wie vielen Sekunden?
  3. Baue einen fachlichen Fehler ein — etwa einen falschen Rabattfaktor. Welche Stufe fängt ihn?
  4. Miss die Laufzeit mit und ohne Zwischenspeicher der Abhängigkeiten.
  5. Aktiviere den Branch-Schutz auf main, sodass nur zusammengeführt werden kann, wenn die Pipeline grün ist.

Der letzte Punkt ist der wichtigste. Eine Pipeline, deren Ergebnis man ignorieren kann, ist Dekoration. Erst der Branch-Schutz macht aus der Prüfung eine Zusage — und nimmt gleichzeitig dem einzelnen Entwickler die unangenehme Rolle, Kollegen auf rote Builds ansprechen zu müssen. Das erledigt jetzt die Maschine, und Maschinen nehmen einem das nicht übel.

5.7 Anti-Pattern

Anti-Pattern Folge Abhilfe
Nächtlicher Build Rückmeldung nach 14 Stunden bei jedem Push bauen
Roter main wird toleriert niemand weiß, was funktioniert Reißleine, Revert-Regel
Pipeline dauert 45 Minuten wird nicht mehr abgewartet parallelisieren, zwischenspeichern, aufteilen
Tests werden übersprungen, „ist eilig„ genau dann geht es schief keine Ausnahmen, technisch erzwingen
CI baut, aber niemand schaut hin Fehler bleiben liegen Benachrichtigung, Branch-Schutz
Pipeline nur auf Feature-Branches Integration findet nie statt main ist der Prüfstein
Pipeline im Web-Formular zusammengeklickt nicht versioniert, nicht nachvollziehbar Pipeline as Code

5.8 Reflexionsfrage

Wie lange dauert bei euch der Weg vom Push bis zur Rückmeldung „alles in Ordnung“ — und wie oft wird diese Rückmeldung tatsächlich abgewartet?


Weiter mit Kapitel 06 — Continuous Delivery und Deployment