Feature Flags in Bash-Skripten implementieren: riskante Änderungen kontrolliert ausrollen
AI generated
$_
#!/
Bash · Deployment · Rollout-Strategie · Automatisierung
Feature Flags in Bash-Skripten
Riskante Änderungen an Deployment- und Automatisierungsskripten kontrolliert ausrollen

Ein neuer Codepfad in einem produktiv genutzten Bash-Skript ist immer ein Risiko, egal wie sorgfältig er getestet wurde. Ein Feature Flag verwandelt dieses Alles-oder-Nichts-Risiko in eine steuerbare Entscheidung: Der neue Pfad existiert im Skript, ist aber standardmäßig aus, lässt sich gezielt für einzelne Läufe aktivieren und im Ernstfall mit einem einzigen Wert wieder abschalten.

15 Min. Lesezeit Konfigurationsdatei · case Bash 4.x · 5.x · Deployment

1. Warum Feature Flags auch in Shell-Skripten sinnvoll sind

In Anwendungscode sind Feature Flags längst Standard, um neue Funktionen unabhängig vom Deployment schrittweise auszurollen. In Bash-Skripten, die Deployments, Backups oder Wartungsaufgaben automatisieren, fehlt dieses Muster oft völlig, obwohl das Risiko dort mindestens genauso hoch ist: Ein fehlerhafter neuer Codepfad in einem Deployment-Skript kann eine ganze Produktionsumgebung lahmlegen, nicht nur eine einzelne Funktion in einer Anwendung.

Der Kerngedanke bleibt gleich: Neuer, riskanter Code wird nicht sofort für alle Ausführungen scharf geschaltet, sondern hinter einer Bedingung versteckt, die sich zentral umschalten lässt. Damit lässt sich ein neues Backup-Verfahren zunächst nur auf einem einzelnen Server testen, während alle anderen Server weiterhin den bewährten alten Pfad nutzen, ganz ohne zwei getrennte Skriptversionen pflegen zu müssen.

2. Feature Flags über Umgebungsvariablen steuern

Der einfachste Ansatz nutzt Umgebungsvariablen als Flags: Eine Variable wie FEATURE_NEW_BACKUP_STRATEGY wird auf 1 oder 0 gesetzt, und das Skript prüft diesen Wert an der Stelle, an der sich alter und neuer Codepfad trennen. Dieser Ansatz eignet sich besonders gut für Flags, die pro Ausführung unterschiedlich sein sollen, etwa um ein Flag nur für einen manuell gestarteten Testlauf zu aktivieren, ohne eine Datei anzufassen.

Wichtig ist eine konsistente Namenskonvention, damit Flags im Code sofort als solche erkennbar sind und nicht mit regulären Konfigurationsvariablen verwechselt werden. Ein Präfix wie FEATURE_ macht beim Lesen des Skripts sofort klar, dass diese Variable einen bedingten Codepfad steuert und nicht etwa einen Verbindungsparameter oder einen Pfad.


#!/usr/bin/env bash
set -euo pipefail

# Feature flag via environment variable, default OFF
readonly FEATURE_NEW_BACKUP_STRATEGY="${FEATURE_NEW_BACKUP_STRATEGY:-0}"

run_backup() {
  if [[ "$FEATURE_NEW_BACKUP_STRATEGY" == "1" ]]; then
    echo "Running NEW incremental backup strategy"
    # new, still-risky code path
  else
    echo "Running established full backup strategy"
    # old, proven code path
  fi
}

run_backup
# Test run: FEATURE_NEW_BACKUP_STRATEGY=1 ./backup.sh

3. Feature Flags über eine zentrale Konfigurationsdatei steuern

Sobald mehrere Flags gleichzeitig existieren oder ein Flag für mehrere Skriptläufe konstant bleiben soll, wird eine zentrale Konfigurationsdatei praktikabler als einzelne Umgebungsvariablen bei jedem Aufruf. Eine einfache flags.conf mit SCHLUESSEL=WERT-Zeilen, geladen wie eine .env-Datei, macht den aktuellen Rollout-Status aller Flags auf einen Blick sichtbar und versionierbar, wenn die Datei im selben Repository wie das Skript liegt.

Diese Konfigurationsdatei sollte dabei getrennt von der eigentlichen Anwendungs-Konfiguration liegen, damit ein Flag-Wechsel niemals versehentlich andere Einstellungen wie Datenbank-Zugangsdaten mit verändert. Ein eigener Abschnitt oder eine eigene Datei ausschließlich für Flags macht außerdem im Code-Review sofort sichtbar, welche Änderung tatsächlich einen Rollout betrifft.


#!/usr/bin/env bash
set -euo pipefail

# flags.conf:
#   FEATURE_NEW_BACKUP_STRATEGY=1
#   FEATURE_PARALLEL_UPLOAD=0

load_flags() {
  local flags_file="${1:-flags.conf}"
  [[ -f "$flags_file" ]] || return 0
  while IFS='=' read -r key value; do
    [[ -z "$key" || "$key" == \#* ]] && continue
    export "$key=$value"
  done < "$flags_file"
}

load_flags "flags.conf"
echo "Backup strategy flag: ${FEATURE_NEW_BACKUP_STRATEGY:-0}"

4. Bedingte Codepfade sauber strukturieren statt verschachtelte if-Ketten

Ein einzelnes Feature Flag lässt sich problemlos mit einer if-Abfrage abbilden, aber sobald mehrere Flags dieselbe Funktion beeinflussen, wächst die Verschachtelung schnell zu unübersichtlichen if-in-if-Konstrukten, die schwer zu testen und noch schwerer wieder zu entfernen sind. Sauberer ist es, jeden Codepfad als eigene, klar benannte Funktion zu schreiben und nur an einer einzigen zentralen Stelle zu entscheiden, welche Funktion tatsächlich aufgerufen wird.

Dieses Muster hat einen entscheidenden Nebeneffekt: Wird ein Feature Flag später endgültig zum Standard, muss nur die Auswahl-Stelle angepasst werden, nicht der komplette Funktionskörper, und die alte Funktion lässt sich anschließend gefahrlos löschen, weil sie sauber isoliert ist. Verschachtelter Code dagegen lässt sich selten ohne Risiko wieder auseinanderziehen.


#!/usr/bin/env bash
set -euo pipefail

deploy_legacy() {
  echo "Deploying via legacy rsync pipeline"
}

deploy_parallel() {
  echo "Deploying via new parallel upload pipeline"
}

# single decision point -- easy to find, easy to remove later
if [[ "${FEATURE_PARALLEL_UPLOAD:-0}" == "1" ]]; then
  deploy_parallel
else
  deploy_legacy
fi

5. Abgestuften Rollout simulieren: nicht alle Server gleichzeitig

Auch ohne ein echtes Feature-Flag-Backend lässt sich ein abgestufter Rollout in reinem Bash nachbilden, indem das Flag nicht global, sondern pro Host oder pro Ausführung ausgewertet wird. Ein einfacher Ansatz nutzt den Hostnamen oder eine feste Liste erlaubter Zielsysteme, um den neuen Codepfad zunächst nur dort zu aktivieren, während der Rest der Flotte unverändert bleibt.

Für einen echten Prozent-Rollout über viele gleichartige Instanzen hinweg eignet sich ein deterministischer Hash des Hostnamens, der in einen Wertebereich von null bis neunundneunzig abgebildet wird. Liegt dieser Wert unter der gewünschten Rollout-Quote, aktiviert das Skript das neue Verhalten, sonst nicht, konsistent bei jedem Lauf auf demselben Host.


#!/usr/bin/env bash
set -euo pipefail

readonly ROLLOUT_PERCENT=20  # roll out to ~20% of hosts

# deterministic per-host bucket: same host always lands in the same bucket
host_bucket() {
  local hash
  hash=$(echo -n "$(hostname)" | md5sum | cut -c1-4)
  echo $(( 16#$hash % 100 ))
}

if (( $(host_bucket) < ROLLOUT_PERCENT )); then
  echo "Host $(hostname) is in the rollout group"
else
  echo "Host $(hostname) stays on the stable path"
fi

6. Ein Kill-Switch für den Notfall: sofort zurück zum alten Verhalten

Der wichtigste Zweck eines Feature Flags in einem Automatisierungsskript ist nicht der geordnete Rollout, sondern der Notausstieg: Zeigt sich in Produktion ein Problem, muss das neue Verhalten sofort und ohne Skript-Änderung abschaltbar sein. Ein Kill-Switch, der einfach denselben Flag-Namen auf 0 zurücksetzt, sollte deshalb keine eigene Logik brauchen, sondern schlicht der Normalfall des Ausschaltens sein.

Damit ein Kill-Switch im Ernstfall tatsächlich sofort greift, darf das Flag nicht in irgendeiner Cache-Datei oder einem lang laufenden Prozess-Speicher hängen bleiben, sondern muss bei jedem Skriptaufruf neu aus der aktuellen Konfigurationsdatei oder Umgebungsvariable gelesen werden. Ein Skript, das ein Flag beim Start einmalig einliest und danach für Stunden weiterläuft, verzögert die Wirkung eines Notfall-Abschaltens unnötig.

7. Aktive Flags loggen: Nachvollziehbarkeit bei Fehlersuche

Wenn ein Skript mit aktiviertem Feature Flag ein unerwartetes Ergebnis liefert, ist die erste Frage bei der Fehlersuche fast immer, welche Flags zum Zeitpunkt des Laufs überhaupt aktiv waren. Ohne Logging dieser Information bleibt oft nur die Vermutung, weil sich der Zustand der Konfigurationsdatei zwischen dem fehlerhaften Lauf und der Untersuchung längst geändert haben kann.

Ein robustes Skript protokolliert deshalb zu Beginn jedes Laufs, mit welchen Flag-Werten es gestartet ist, idealerweise in derselben Logdatei wie der Rest der Ausgabe. Diese eine Zeile am Anfang des Logs macht spätere Fehlersuche erheblich schneller, weil sich die Ursache eines Problems sofort auf einen konkreten Flag-Zustand zurückführen lässt, statt ihn nachträglich rekonstruieren zu müssen.


#!/usr/bin/env bash
set -euo pipefail

log_active_flags() {
  local flag
  for flag in "${!FEATURE_@}"; do
    echo "[FLAGS] $flag=${!flag}"
  done
}

log_active_flags
# [FLAGS] FEATURE_NEW_BACKUP_STRATEGY=1
# [FLAGS] FEATURE_PARALLEL_UPLOAD=0

8. Flags wieder entfernen: technische Schulden aktiv abbauen

Ein Feature Flag, das ein Jahr nach dem vollständigen Rollout noch im Code steht, ist reine technische Schuld: Es zwingt jeden, der die Funktion liest, den inzwischen toten alten Codepfad mitzudenken, obwohl er nie mehr ausgeführt wird. Sobald ein neues Verhalten sich in Produktion bewährt hat und dauerhaft auf hundert Prozent steht, gehört der alte Pfad entfernt und das Flag selbst gelöscht.

Praktisch bewährt sich, jedes neu eingeführte Flag mit einem Ablaufdatum oder einem Ticket zu versehen, das explizit das Aufräumen einfordert, statt sich auf spätere Erinnerung zu verlassen. Ohne diese Disziplin sammeln sich in langlebigen Deployment-Skripten über die Jahre Dutzende toter Flags an, die niemand mehr zu entfernen wagt, weil unklar ist, ob sie noch irgendwo gebraucht werden.

9. Umgebungsvariable vs. Konfigurationsdatei vs. externer Flag-Dienst

Für ein einzelnes Skript oder wenige Flags reichen Umgebungsvariablen oder eine einfache Konfigurationsdatei völlig aus und lassen sich ohne zusätzliche Infrastruktur betreiben. Erst wenn viele Skripte auf vielen Servern dieselben Flags konsistent und in Echtzeit sehen müssen, lohnt sich der Blick auf einen externen Flag-Dienst, der zentrale Änderungen sofort an alle Ausführungsorte verteilt.

Mechanismus Reichweite Änderung wirkt Typischer Einsatz
Umgebungsvariable Ein Skriptaufruf Sofort beim nächsten Aufruf Einmaliger Testlauf, manuelle Aktivierung
Konfigurationsdatei Alle Läufe auf einem Host Beim nächsten Laden der Datei Dauerhafter Rollout-Status pro Server
Hostnamen-Bucket Prozentualer Anteil der Flotte Deterministisch, konsistent pro Host Schrittweiser Rollout über viele Server
Externer Flag-Dienst Alle Server in Echtzeit Sofort, ohne Deployment Große Flotten, zentrale Steuerung

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte, die in der Produktion zuverlässig laufen?

Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.

Code-Review

ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.

Refactoring

Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.

CI-Integration

ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.

10. Zusammenfassung

Feature Flags in Bash-Skripten: Das Wichtigste auf einen Blick

Flags klar benennen

Ein Präfix wie FEATURE_ macht sofort sichtbar, dass eine Variable einen bedingten Codepfad steuert und keine reguläre Konfiguration ist.

Ein zentraler Entscheidungspunkt

Codepfade als eigene Funktionen schreiben und nur an einer Stelle auswählen, statt if-in-if-Verschachtelung über das ganze Skript zu verteilen.

Kill-Switch ohne Verzögerung

Flags bei jedem Lauf neu einlesen, nicht einmalig cachen, damit ein Notfall-Abschalten sofort wirkt statt erst beim nächsten Neustart.

Flags aktiv aufräumen

Jedes Flag mit einem Ablaufdatum oder Ticket versehen und nach vollständigem Rollout inklusive altem Codepfad wieder entfernen.

11. FAQ: Feature Flags in Bash-Skripten: Das Wichtigste auf einen Blick

1Was ist ein Feature Flag in einem Bash-Skript?
Eine Bedingung, meist über eine Umgebungsvariable oder eine Konfigurationsdatei gesteuert, die entscheidet, ob ein neuer, noch riskanter Codepfad ausgeführt wird oder der bewährte alte Pfad.
2Wann sollte ich Umgebungsvariablen statt einer Konfigurationsdatei für Flags nutzen?
Für einmalige Testläufe oder wenn ein Flag nur für einen einzelnen Aufruf gelten soll. Für dauerhafte, für alle Läufe geltende Einstellungen ist eine Konfigurationsdatei praktischer.
3Wie vermeide ich verschachtelte if-Ketten bei mehreren Flags?
Jeden Codepfad als eigene, klar benannte Funktion schreiben und nur an einer einzigen zentralen Stelle entscheiden, welche Funktion aufgerufen wird.
4Wie baue ich einen Prozent-Rollout ohne externen Flag-Dienst?
Mit einem deterministischen Hash des Hostnamens, der in einen Wertebereich von null bis neunundneunzig abgebildet wird. Liegt der Wert unter der gewünschten Quote, aktiviert das Skript das neue Verhalten.
5Warum muss ein Kill-Switch bei jedem Lauf neu gelesen werden?
Ein einmalig beim Start eingelesenes und dann gecachtes Flag verzögert die Wirkung eines Notfall-Abschaltens. Erst ein Neueinlesen bei jedem Aufruf sorgt für sofortige Wirkung.
6Sollte ich aktive Flags loggen?
Ja. Eine Logzeile mit allen aktiven Flag-Werten zu Beginn jedes Laufs macht spätere Fehlersuche erheblich schneller, weil der Zustand zum Zeitpunkt des Fehlers nachvollziehbar bleibt.
7Was passiert, wenn ich alte Feature Flags nie entferne?
Sie werden zu technischer Schuld: Jeder, der den Code liest, muss den längst toten alten Pfad mitdenken, und das Skript wird über die Jahre unübersichtlicher, ohne dass ein Nutzen entsteht.
8Wie sollte ich Feature-Flag-Variablen benennen?
Mit einem einheitlichen Präfix wie FEATURE_, damit sie im Code sofort als bedingte Codepfad-Steuerung erkennbar sind und nicht mit regulären Konfigurationswerten verwechselt werden.
9Ab wann lohnt sich ein externer Flag-Dienst statt einer eigenen Lösung?
Sobald viele Skripte auf vielen Servern dieselben Flags konsistent und in Echtzeit sehen müssen, verteilt ein externer Dienst zentrale Änderungen zuverlässiger als eine manuell verteilte Konfigurationsdatei.
10Kann ich Feature Flags auch für Rollback nutzen?
Ja, das ist ihr wichtigster Zweck in Automatisierungsskripten. Ein Flag, das einfach auf den alten Wert zurückgesetzt wird, ist ein sofortiger Rollback ohne Skript-Änderung oder erneutes Deployment.