IaC und ITIL v4

Automatisierung und IT-Service-Management gelten oft als Gegensätze: Hier die schnelle Pipeline, dort das Change Advisory Board mit Wochenrhythmus. Richtig umgesetzt ergänzen sie sich. Die Pipeline liefert genau das, was ITIL verlangt – bewertete, genehmigte, dokumentierte und nachvollziehbare Änderungen –, nur automatisch, vollständig und ohne Medienbruch.

Zurück zur IaC-Übersicht · Grundlagen unter ITIL v4

Die Leitprinzipien in der IaC-Praxis

ITIL v4 formuliert sieben Leitprinzipien. Jedes hat eine direkte Entsprechung in Infrastructure as Code.

Leitprinzip Umsetzung mit IaC
Focus on value Server sind Mittel zum Zweck; der Wert ist der Service, nicht die Maschine
Start where you are Bestehende Ressourcen per terraform import übernehmen statt neu bauen
Progress iteratively with feedback Kleine Merge Requests, jeder mit Plan und Prüfung
Collaborate and promote visibility Änderungen sind als Code für alle lesbar, Reviews finden im MR statt
Think and work holistically Netz, Compute, Image und Konfiguration in einer durchgängigen Kette
Keep it simple and practical Ein Weg für alle Änderungen: die Pipeline
Optimize and automate Erst den Ablauf standardisieren, dann automatisieren

Welche ITIL-Praktiken betroffen sind

Praxis Pet-Welt (manuell) Cattle-Welt (IaC)
Change Enablement Ticket, CAB-Termin, Admin setzt um Merge Request, automatische Bewertung, Pipeline setzt um
Release Management Wartungsfenster, Installationsanleitung Versionierte Image-Version in der Compute Gallery
Deployment Management Handarbeit auf jedem Server Rolling Upgrade des Scale Sets
Service Configuration Management CMDB wird von Hand gepflegt und veraltet State, Tags und Resource Graph liefern den Ist-Zustand
Incident Management Admin loggt sich ein, repariert Health Probe erkennt, Automatic Repair ersetzt
Problem Management Ursachen gehen im Einzelfall unter Häufung automatischer Repairs zeigt Muster
Monitoring and Event Management Überwachung einzelner Hosts Überwachung des Service und der Soll-Ist-Abweichung (Drift)
Information Security Management Wer hat wann was geändert? Oft unklar Git-Historie, Pipeline-Protokoll, Anmeldeprotokolle der Identitäten

Change Enablement

Change-Typen

ITIL v4 unterscheidet drei Arten von Changes. Die Pipeline bildet alle drei ab – aber auf einem technischen Weg.

Change-Typ ITIL-Definition In der Pipeline
Standard Vorab bewertet, geringes Risiko, festgelegtes Verfahren Plan enthält nur unkritische Änderungen → MR-Freigabe genügt
Normal Einzeln zu bewerten und zu genehmigen Plan enthält Löschungen, Ersetzungen oder kritische Ressourcen → zusätzliche Change-Genehmigung
Emergency Muss schnellstmöglich umgesetzt werden Beschleunigter MR mit nachgelagerter Prüfung (siehe unten)

Der entscheidende Schritt: Nicht der Mensch klassifiziert den Change, sondern der Plan. terraform plan weiß exakt, welche Ressourcen angelegt, geändert, ersetzt oder gelöscht werden. Daraus lässt sich der Change-Typ regelbasiert ableiten.

Was als Standard Change gilt

Ein Change ist ein Standard Change, wenn alle folgenden Bedingungen erfüllt sind:

  1. Alle Prüfungen der Pipeline sind grün (Format, Validierung, Lint, Checkov).
  2. Der Plan löscht oder ersetzt keine Ressource.
  3. Der Plan ändert keine Ressource eines als kritisch eingestuften Typs (Netz, Firewall, Gallery, Ressourcengruppen).
  4. Die Änderung wurde im Merge Request von einer zweiten Person freigegeben.

Typische Standard Changes im Lab sind das Skalieren des Scale Sets und das Ausrollen einer neuen Image-Version – beides In-Place-Updates, die Azure batchweise und mit Health Probe absichert.

Diese Regeln einmal formal zu genehmigen, ist der eigentliche Change-Management-Akt. Danach wendet die Pipeline sie bei jedem Lauf gleich an.

Automatische Klassifizierung

Das Skript wertet den Plan im JSON-Format aus, bestimmt den Change-Typ und erzeugt einen Change-Datensatz als Markdown.

classify_change.py
#!/usr/bin/env python3
"""Klassifiziert einen Terraform-Plan als ITIL-Change-Typ.
 
Aufruf: classify_change.py <plan.json> <stack>
Erzeugt: change-record.md und change.env (für GitLab-dotenv-Reports)
"""
import datetime
import json
import os
import sys
 
# Änderungen an diesen Typen sind immer ein Normal Change
CRITICAL_TYPES = {
    "azurerm_resource_group",
    "azurerm_virtual_network",
    "azurerm_subnet",
    "azurerm_network_security_group",
    "azurerm_subnet_network_security_group_association",
    "azurerm_shared_image_gallery",
    "azurerm_shared_image",
}
 
 
def classify(plan):
    changes, reasons = [], []
    for rc in plan.get("resource_changes", []):
        actions = rc["change"]["actions"]
        if actions in (["no-op"], ["read"]):
            continue
        changes.append((rc["address"], rc["type"], actions))
        if "delete" in actions:
            art = "wird ersetzt" if "create" in actions else "wird gelöscht"
            reasons.append(f"{rc['address']} {art}")
        elif rc["type"] in CRITICAL_TYPES:
            reasons.append(f"{rc['address']}: kritischer Ressourcentyp")
 
    if not changes:
        return "none", changes, reasons
    return ("normal" if reasons else "standard"), changes, reasons
 
 
def main():
    plan_file, stack = sys.argv[1], sys.argv[2]
    with open(plan_file, encoding="utf-8") as fh:
        plan = json.load(fh)
 
    change_type, changes, reasons = classify(plan)
 
    lines = [
        f"# Change-Datensatz – Schicht {stack}",
        "",
        f"- Zeitpunkt: {datetime.datetime.now(datetime.timezone.utc):%Y-%m-%d %H:%M} UTC",
        f"- Change-Typ: **{change_type}**",
        f"- Commit: {os.environ.get('CI_COMMIT_SHA', 'lokal')}",
        f"- Merge Request: {os.environ.get('CI_MERGE_REQUEST_IID', '-')}",
        f"- Ausgelöst von: {os.environ.get('GITLAB_USER_LOGIN', 'lokal')}",
        "",
        "## Geplante Änderungen",
    ]
    lines += [f"- `{addr}`: {'/'.join(act)}" for addr, _, act in changes] or ["- keine"]
    if reasons:
        lines += ["", "## Gründe für Normal Change"] + [f"- {r}" for r in reasons]
 
    with open("change-record.md", "w", encoding="utf-8") as fh:
        fh.write("\n".join(lines) + "\n")
    with open("change.env", "w", encoding="utf-8") as fh:
        fh.write(f"CHANGE_TYPE_{stack.upper()}={change_type}\n")
 
    print(f"[{stack}] Change-Typ: {change_type}")
    for r in reasons:
        print(f"  - {r}")
 
 
if __name__ == "__main__":
    main()

Das Skript liegt als ci/classify_change.py im Repository. Die Liste CRITICAL_TYPES ist selbst eine Change-Management-Entscheidung und deshalb über CODEOWNERS geschützt.

Einbindung in die Pipeline

Ergänzungen zur IaC-Pipeline:

.gitlab-ci.yml
stages: [validate, security, image, plan, change, apply, drift]

.plan:
  # ... bisheriger Inhalt, am Ende des script-Blocks ergänzen:
  script:
    - *tf_init
    - terraform plan -lock=false -out=tfplan
    - terraform show -no-color tfplan > plan.txt
    - terraform show -json tfplan > plan-full.json
    - python3 ../ci/classify_change.py plan-full.json "$STACK"
    # ... jq-Zusammenfassung für das MR-Widget wie bisher
  artifacts:
    paths: ["$STACK/tfplan", "$STACK/plan.txt", "$STACK/change-record.md"]
    reports:
      terraform: "$STACK/plan.json"
      dotenv: "$STACK/change.env"
    expire_in: 1 week

change:gate:
  stage: change
  needs: ["plan:base", "plan:workload"]
  script:
    - echo "Change-Typ base=$CHANGE_TYPE_BASE workload=$CHANGE_TYPE_WORKLOAD"
    - |
      if [ "$CHANGE_TYPE_BASE" = "normal" ] || [ "$CHANGE_TYPE_WORKLOAD" = "normal" ]; then
        case ",$CI_MERGE_REQUEST_LABELS," in
          *,change-approved,*)
            echo "Normal Change: Genehmigung liegt vor." ;;
          *)
            echo "Normal Change: Label 'change-approved' fehlt – Genehmigung erforderlich."
            exit 1 ;;
        esac
      else
        echo "Standard Change: MR-Freigabe genügt."
      fi
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Zusammen mit der Projekteinstellung „Pipelines must succeed“ lässt sich ein Normal Change nicht mergen, bevor die Genehmigung vorliegt. Nach dem Setzen des Labels muss die MR-Pipeline neu gestartet werden.

Grenzen der Lab-Lösung: Ein Label kann jeder mit Reporter-Rechten setzen. Im Produktivbetrieb prüft der Gate-Job stattdessen per API im ITSM-Werkzeug (z. B. ServiceNow oder Jira Service Management), ob der im MR referenzierte Change genehmigt ist. Das Prinzip bleibt gleich: Die Pipeline fragt nach, der Mensch genehmigt im dafür vorgesehenen System.

Emergency Changes

Auch im Notfall gilt: Kein Handeingriff im Azure-Portal. Jeder manuelle Eingriff erzeugt Drift, die später niemand mehr erklären kann.

  1. Hotfix-Branch, Merge Request mit Kennzeichnung emergency im Titel
  2. Verkürzte Freigabe durch eine vorab benannte Person der Rufbereitschaft
  3. Pipeline läuft vollständig durch – Validierung und Plan dauern Minuten, nicht Stunden
  4. Nachträgliche Bewertung im nächsten regulären Change-Review

Für den Fall, dass die Pipeline selbst ausfällt, gibt es einen Break-Glass-Zugang: ein Notfallkonto mit Schreibrechten, dessen Nutzung alarmiert wird. Danach wird die manuelle Änderung per Merge Request in den Code übernommen; der nächtliche Drift-Check stellt sicher, dass das nicht vergessen wird.

Service Configuration Management

Was ist ein Configuration Item?

In der Pet-Welt ist jeder Server ein CI mit eigenem Lebenslauf. In der Cattle-Welt verschiebt sich die Granularität: Einzelne Instanzen leben Stunden oder Tage und sind austauschbar. Sie als CI zu pflegen, erzeugt nur Rauschen.

Pet-Welt: CI Cattle-Welt: CI
Server zeus Service „Webshop“, umgesetzt durch Scale Set vmss-iaclab-web
Installierte Softwarestände Image-Version 1.1.0 (enthält den vollständigen Softwarestand)
Konfigurationsdateien Commit im Repository
Handgepflegte Abhängigkeiten Abhängigkeiten aus Terraform-Code und Resource Graph

Tags als CI-Attribute

Damit Azure-Ressourcen einem Service zugeordnet werden können, bekommen alle Ressourcen einheitliche Tags. Das Schema wird in locals.tags beider Schichten ergänzt:

locals {
  tags = {
    service     = "iac-lab-web"      # Service-Zuordnung in der CMDB
    owner       = "plattform-team"   # verantwortliche Organisationseinheit
    criticality = "low"              # Grundlage für Priorisierung von Incidents
    managed_by  = "terraform"
    repo        = "gitlab.com/<gruppe>/iac-lab-azure"
    layer       = "workload"
  }
}

Eine Azure Policy prüft, ob die Pflicht-Tags vorhanden sind. Die Policy startet im Prüfmodus (enforce = false), damit sie nichts blockiert, bis alle Ressourcen konform sind:

data "azurerm_policy_definition" "require_tag" {
  display_name = "Require a tag on resources"
}
 
resource "azurerm_resource_group_policy_assignment" "require_service_tag" {
  name                 = "require-tag-service"
  resource_group_id    = azurerm_resource_group.web.id
  policy_definition_id = data.azurerm_policy_definition.require_tag.id
  enforce              = false   # erst prüfen, später erzwingen
  parameters = jsonencode({
    tagName = { value = "service" }
  })
}

Damit ist auch die Regel selbst Code, versioniert und über die Pipeline geändert – Policy as Code.

Ist-Zustand für die CMDB

Der Azure Resource Graph liefert den aktuellen Bestand aller Ressourcen eines Services in Sekunden:

az graph query -q "
  Resources
  | where tags.service == 'iac-lab-web'
  | project name, type, location, resourceGroup,
            owner=tostring(tags.owner), criticality=tostring(tags.criticality)
" --output table

Als nächtlicher Pipeline-Job mit –output json lässt sich das Ergebnis in die CMDB importieren. Drei Quellen ergänzen sich:

Quelle Liefert
Terraform-Code Soll-Zustand und Absicht (was sein soll)
Terraform-State Was Terraform verwaltet und zuletzt gesehen hat
Resource Graph Was in Azure tatsächlich existiert (was ist)

Ressourcen, die im Resource Graph auftauchen, aber keinen Tag managed_by = terraform tragen, sind Kandidaten für Schatten-IT oder vergessene Handarbeit.

Incident und Problem Management

Nachvollziehbarkeit und Audit

Für jede Änderung lässt sich lückenlos beantworten, was, wann, von wem, warum und mit wessen Genehmigung geändert wurde:

Frage Nachweis
Was? Diff im Merge Request, plan.txt, change-record.md
Wann? Zeitstempel von Merge und Apply-Job
Wer? Autor des MR, Auslöser des Apply-Jobs
Warum? Beschreibung des MR, verknüpftes Ticket
Genehmigt von? MR-Freigaben, Change-Genehmigung
Wie in Azure? Aktivitätsprotokoll, Anmeldeprotokolle der Workload-Identität
Vorheriger Stand? Git-Historie, versionierter Terraform-State

In regulierten Umfeldern, etwa im Finanzsektor unter DORA, verlangen die Aufsichtsvorgaben ein dokumentiertes, geprüftes und genehmigtes IKT-Änderungsmanagement. Eine Pipeline erfüllt diese Anforderungen strukturell besser als ein manueller Prozess, weil sie keinen Schritt auslassen kann. Wichtig sind passende Aufbewahrungsfristen für Pipeline-Protokolle und Artefakte.

Kennzahlen für kontinuierliche Verbesserung

Die vier Kennzahlen aus der DevOps-Forschung (DORA – DevOps Research and Assessment, nicht zu verwechseln mit der EU-Verordnung DORA) passen direkt zur ITIL-Praxis Continual Improvement:

Kennzahl Frage Quelle in der Pipeline
Deployment Frequency Wie oft wird erfolgreich ausgerollt? Anzahl erfolgreicher Apply-Jobs
Lead Time for Changes Wie lange vom Commit bis zur Produktion? Zeit zwischen erstem Commit und Apply
Change Failure Rate Welcher Anteil der Changes führt zu Störungen? Rollbacks, Hotfixes je Apply
Time to Restore Service Wie schnell ist der Service nach einer Störung wieder da? Dauer bis zum erfolgreichen Rollback/Repair

Ergänzend aus ITIL-Sicht: Anteil Standard Changes an allen Changes (Ziel: steigend) und Anzahl Drift-Meldungen (Ziel: null).

Weiterführend