DevOps-bootcamp · Lektion

Idempotenta skript och logik för omförsök med backoff

Utforma åtgärder som är säkra att köra igen och lägg till exponentiell backoff för instabila externa anrop.

Lektion 4 av 413 steg

Idempotenta skript och logik för omförsök med backoff är en gratis lektion i DevOps-bootcamp på CoddyKit. Detta är lektion 4 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för DevOps-bootcamp, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i DevOps-bootcamp innehåller totalt 4 lektioner.

Vad är idempotens och varför är det viktigt

Idempotens innebär att samma operation kan köras flera gånger och ändå ger samma resultat som om den kördes en gång. I Bash-skript är detta avgörande eftersom skript kraschar, nätverk går ner och människor av misstag kör om saker.

  • Ett icke-idempotent skript som skapar en användare två gånger kan misslyckas eller skapa duplicerade data.
  • Ett idempotent skript kontrollerar först: finns detta redan?
  • Idempotenta skript är säkra att använda i cron-jobb, CI-pipelines och loopar för nya försök.

Grundregeln är: kontrollera innan Ni utför åtgärden. Alla destruktiva eller skapande operationer bör skyddas av ett förvillkorstest.

Skydda skapandet av filer och kataloger

Det vanligaste idempotensmönstret är att kontrollera om en resurs redan finns innan den skapas. Bash tillhandahåller korta enradsuttryck för detta.

  • [ -d dir ] — sant om katalogen finns
  • [ -f file ] — sant om den vanliga filen finns
  • mkdir -p — skapar katalogen endast om den saknas (inbyggd idempotens)

Föredra inbyggda flaggor som -p och --no-clobber framför manuella kontroller när de finns tillgängliga — de är atomiska och säkra mot tävlingsförhållanden.

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

CONFIG_DIR="$HOME/.myapp"
CONFIG_FILE="$CONFIG_DIR/config.ini"

# Idempotent: mkdir -p never fails if dir already exists
mkdir -p "$CONFIG_DIR"

# Idempotent: only write config if it doesn't exist yet
if [ ! -f "$CONFIG_FILE" ]; then
  echo '[defaults]' > "$CONFIG_FILE"
  echo 'timeout=30' >> "$CONFIG_FILE"
  echo "Created $CONFIG_FILE"
else
  echo "Config already exists, skipping."
fi

Idempotent hantering av användare och grupper

Systemadministrativa uppgifter som att lägga till användare eller grupper måste vara idempotenta — om skriptet körs igen på samma maskin ska det varken ge fel eller skapa dubbletter.

  • id username returnerar 0 om användaren finns
  • getent group groupname kontrollerar om en grupp finns
  • Omslut varje operation med en kontroll så att skriptet är säkert att köra om

Detta mönster utgör grunden för verktyg för konfigurationshantering som Ansible — varje uppgift är en kontrollerad, idempotent operation.

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

APP_USER="apprunner"
APP_GROUP="appgroup"

# Idempotent group creation
if ! getent group "$APP_GROUP" &>/dev/null; then
  groupadd "$APP_GROUP"
  echo "Group '$APP_GROUP' created."
else
  echo "Group '$APP_GROUP' already exists."
fi

# Idempotent user creation
if ! id "$APP_USER" &>/dev/null; then
  useradd -m -g "$APP_GROUP" -s /bin/bash "$APP_USER"
  echo "User '$APP_USER' created."
else
  echo "User '$APP_USER' already exists."
fi

Använda låsfiler för att förhindra samtidiga körningar

Även ett idempotent skript kan orsaka problem om två instanser körs samtidigt. En låsfil säkerställer att endast en instans körs åt gången.

  • Skapa en låsfil vid uppstart och ta bort den vid avslut.
  • Använd en trap för att rensa låset även om skriptet avbryts.
  • mkdir på en enda sökväg är atomiskt i de flesta Linux-filsystem — säkrare för låsning än touch.

Utan ett lås kan ett långsamt cron-jobb och en manuell omkörning krocka och förstöra delat tillstånd.

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

LOCKFILE="/tmp/myapp_deploy.lock"

# Atomic lock acquisition using mkdir
if ! mkdir "$LOCKFILE" 2>/dev/null; then
  echo "ERROR: Another instance is running (lock: $LOCKFILE). Exiting." >&2
  exit 1
fi

# Guarantee lock removal on any exit
trap 'rmdir "$LOCKFILE"; echo "Lock released."' EXIT

echo "Lock acquired. Running deployment..."
sleep 2   # simulate work
echo "Deployment complete."

Spåra slutförda steg med en tillståndsfil

För skript med flera steg (migreringar, installationer, provisionering) kan Ni spåra vilka steg som redan har slutförts med hjälp av en tillståndsfil. Varje steg kontrollerar tillståndsfilen innan det körs och skriver till den när det är klart.

  • Billigt och portabelt — ingen databas krävs.
  • Gör det möjligt för ett misslyckat skript att fortsätta där det slutade.
  • Lagra tillståndet på en förutsägbar plats, till exempel /var/lib/myapp/ eller ~/.myapp/state/.

Detta mönster används av stora verktyg som apt, cloud-init och ramverk för databas-migrering.

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

STATE_DIR="/tmp/myapp_state"
mkdir -p "$STATE_DIR"

run_step() {
  local step_name="$1"
  local step_cmd="$2"
  local marker="$STATE_DIR/${step_name}.done"

  if [ -f "$marker" ]; then
    echo "[SKIP] $step_name already completed."
    return 0
  fi

  echo "[RUN]  $step_name ..."
  eval "$step_cmd"
  touch "$marker"
  echo "[DONE] $step_name"
}

run_step "install_deps"   "echo 'Installing dependencies...'"
run_step "configure_db"   "echo 'Configuring database...'"
run_step "start_service"  "echo 'Starting service...'"

Introduktion till logik för nya försök

Externa anrop — HTTP-förfrågningar, DNS-sökningar och anrop till moln-API:er — är i grunden opålitliga. Ett enda fel bör inte avbryta hela skriptet. Logik för nya försök försöker automatiskt köra ett misslyckat kommando igen.

  • Naiv återförsökslogik: loopa N gånger tills kommandot lyckas.
  • Ange alltid ett maximalt antal försök för att undvika oändliga loopar.
  • Logga varje försök så att fel kan diagnostiseras.

Den enklaste funktionen för nya försök omsluter valfritt kommando och försöker köra det upp till ett fast antal gånger med en konstant fördröjning. Detta räcker för många användningsfall men har en kritisk brist under belastning — som Ni får se härnäst.

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

# Simple fixed-delay retry (3 attempts, 2s apart)
retry() {
  local max_attempts=3
  local delay=2
  local attempt=1

  until "$@"; do
    if (( attempt >= max_attempts )); then
      echo "ERROR: Command failed after $max_attempts attempts: $*" >&2
      return 1
    fi
    echo "Attempt $attempt failed. Retrying in ${delay}s..." >&2
    sleep "$delay"
    (( attempt++ ))
  done
}

# Example: retry a curl call
retry curl --silent --fail --max-time 5 https://httpbin.org/get -o /dev/null
echo "Request succeeded."

Problemet med den skenande hjorden

När många klienter försöker igen samtidigt efter ett fel skapar de en skenande hjord — alla försöker igen med samma fasta intervall, överbelastar servern exakt samtidigt och gör återhämtning omöjlig.

  • 100 skript försöker igen var femte sekund → 100 samtidiga förfrågningar var femte sekund.
  • Servern har redan problem; den synkroniserade belastningen förvärrar situationen.
  • Lösningen är exponentiell backoff: fördubbla väntetiden efter varje fel.
  • Lägg till jitter (slumpmässig variation) för att avsynkronisera återförsöken mellan klienterna.

Exponentiell backoff med jitter är branschstandard och används av AWS SDK:er, Google Cloud-klienter och alla större distribuerade system.

Implementera exponentiell backoff

Exponentiell backoff ökar väntetiden exponentiellt efter varje fel: 1 s, 2 s, 4 s, 8 s, 16 s ... Det ger fjärrsystemet tid att återhämta sig samtidigt som den totala belastningen minskar.

Formeln: delay = base * (2 ^ attempt)

  • Ange ett tak (maximal fördröjning) så att väntetiderna inte växer obegränsat.
  • Parametrar att justera: base_delay, max_delay, max_attempts.

Den här funktionen kan återanvändas – skicka valfritt kommando till den som argument.

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

retry_with_backoff() {
  local max_attempts="${RETRY_MAX_ATTEMPTS:-5}"
  local base_delay="${RETRY_BASE_DELAY:-1}"
  local max_delay="${RETRY_MAX_DELAY:-30}"
  local attempt=0
  local delay="$base_delay"

  until "$@"; do
    (( attempt++ ))
    if (( attempt >= max_attempts )); then
      echo "ERROR: '$*' failed after $max_attempts attempts." >&2
      return 1
    fi
    echo "Attempt $attempt failed. Backing off for ${delay}s..." >&2
    sleep "$delay"
    # Double the delay, but cap it
    delay=$(( delay * 2 ))
    (( delay > max_delay )) && delay=$max_delay
  done

  echo "Command succeeded on attempt $(( attempt + 1 ))."
}

retry_with_backoff echo "Simulated success"

Lägga till jitter i backoff

Jitter lägger till slumpmässighet i backoff-fördröjningen. Även med exponentiell backoff kommer alla klienter fortfarande att försöka igen samtidigt om de startar vid samma tidpunkt. Jitter bryter denna synkronisering.

Två vanliga jitter-strategier:

  • Full jitter: sleep random(0, cap) – största spridningen och lägsta toppbelastningen.
  • Equal jitter: sleep cap/2 + random(0, cap/2) – garanterar en minsta väntetid och förhindrar omedelbara upprepade försök.

I Bash använder du $RANDOM (0–32767) för att generera slumptal. Anpassa det till ditt fördröjningsintervall med moduloaritmetik.

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

retry_with_jitter() {
  local max_attempts="${1:-5}"; shift
  local base_delay=1
  local max_delay=32
  local attempt=0
  local cap=$base_delay

  until "$@"; do
    (( attempt++ ))
    if (( attempt >= max_attempts )); then
      echo "ERROR: Giving up after $max_attempts attempts." >&2
      return 1
    fi

    # Full jitter: random value in [0, cap]
    local jitter=$(( RANDOM % (cap + 1) ))
    echo "Attempt $attempt failed. Sleeping ${jitter}s (cap=${cap}s)..." >&2
    sleep "$jitter"

    # Grow cap exponentially, bounded by max_delay
    cap=$(( cap * 2 ))
    (( cap > max_delay )) && cap=$max_delay
  done
}

retry_with_jitter 4 curl --silent --fail --max-time 3 https://httpbin.org/get -o /dev/null
echo "Done."

Kombinera idempotens och återförsök i ett verkligt arbetsflöde

I produktionsskript samverkar idempotens och återförsökslogik. Ett typiskt distributionsarbetsflöde kan:

  1. Skaffa ett lås (förhindra samtidiga körningar)
  2. Kontrollera tillståndsfiler (hoppa över slutförda steg)
  3. Använda återförsök med backoff för externa anrop (nedladdning, API, DNS)
  4. Markera steg som slutförda först efter bekräftad framgång
  5. Frigöra låset via trap

Den här kombinationen gör skript säkra att köra igen när som helst – efter en krasch, en timeout eller ett manuellt avbrott – utan att lämna systemet i ett trasigt tillstånd.

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

STATE_DIR="/tmp/deploy_state" && mkdir -p "$STATE_DIR"
LOCK="/tmp/deploy.lock"

mkdir "$LOCK" 2>/dev/null || { echo "Already running."; exit 1; }
trap 'rmdir "$LOCK"' EXIT

step_done() { [ -f "$STATE_DIR/$1.done" ]; }
mark_done() { touch "$STATE_DIR/$1.done"; }

retry_backoff() {
  local attempt=0 delay=1
  until "${@:2}"; do
    (( ++attempt >= $1 )) && { echo "Failed after $1 attempts."; return 1; }
    echo "Retry $attempt in ${delay}s..."; sleep $delay; delay=$(( delay * 2 ))
  done
}

if ! step_done "download_artifact"; then
  retry_backoff 4 curl -fsSL https://httpbin.org/get -o /tmp/artifact.json
  mark_done "download_artifact"
  echo "[DONE] download_artifact"
else
  echo "[SKIP] download_artifact"
fi

echo "Deployment finished successfully."

Hantera fel som inte kan försöka igen

Alla fel bör inte leda till nya försök. Det är slöseri att försöka igen vid 404 Not Found eller 403 Forbidden – de kommer aldrig att lyckas utan mänsklig inblandning. Återförsökslogiken bör skilja mellan:

  • Övergående fel – nätverkstimeout, 503 Service Unavailable, DNS-fel → försök igen
  • Bestående fel – 401 Unauthorized, 404 Not Found, ogiltiga indata → misslyckas omedelbart

Med curl kontrollerar du HTTP-statuskoden och hoppar över återförsök för 4xx-svar. Använd --write-out '%{http_code}' för att fånga statusen separat från svarstexten.

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

fetch_with_retry() {
  local url="$1"
  local max_attempts=4
  local delay=1
  local attempt=0
  local http_code

  until http_code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url"); do
    : # curl itself failed (network error)
    (( ++attempt >= max_attempts )) && { echo "Network error, giving up."; return 1; }
    sleep $delay; delay=$(( delay * 2 ))
  done

  # Permanent client errors — do not retry
  if [[ "$http_code" =~ ^4 ]]; then
    echo "ERROR: HTTP $http_code for $url — not retrying." >&2
    return 1
  fi

  # Transient server errors — retry
  if [[ "$http_code" =~ ^5 ]]; then
    (( ++attempt >= max_attempts )) && { echo "Server error, giving up."; return 1; }
    echo "HTTP $http_code — backing off ${delay}s..."
    sleep $delay; delay=$(( delay * 2 ))
    fetch_with_retry "$url"
    return
  fi

  echo "HTTP $http_code — success."
}

fetch_with_retry "https://httpbin.org/status/200"

Kunskapstest: välja backoff-strategi

Ett distributionsskript laddar ned en releaseartefakt från en S3-bucket. Under en nylig incident misslyckades 80 pipelineinstanser samtidigt på grund av ett kortvarigt S3-avbrott. När S3 återhämtade sig efter 10 sekunder försökte alla 80 instanser igen i samma ögonblick, vilket orsakade ytterligare överbelastning och förlängde avbrottet med 3 minuter.

Vilken återförsöksstrategi skulle bäst förhindra denna kaskad av typen ”tjutande flock” vid framtida incidenter?

Sammanfattning: idempotens och återförsök med backoff

I den här lektionen har du lärt dig att utforma Bash-skript som är säkra att köra igen och tåliga mot övergående fel.

Idempotensmönster:

  • Skydda varje åtgärd med en kontroll av om något redan finns ([ -f ], [ -d ], id, getent).
  • Använd mkdir -p och andra inbyggda idempotenta flaggor när de finns tillgängliga.
  • Använd låsfiler (atomiskt mkdir) för att förhindra samtidiga körningar.
  • Använd tillståndsfiler (markörfiler per steg) för att kunna återuppta efter ett fel.

Mönster för återförsök med backoff:

  • Ange alltid ett maximalt antal försök – försök aldrig för evigt.
  • Använd exponentiell backoff: dubbla fördröjningen efter varje fel.
  • Lägg till jitter (slumpmässighet) för att förhindra synkronisering enligt ”tjutande flock”.
  • Skilj på övergående fel (försök igen) och bestående fel (misslyckas snabbt).

Genom att kombinera dessa mönster får du skript av produktionskvalitet: säkra, observerbara och självläkande.

Gratis att börja

Lär dig DevOps-bootcamp med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
142
Lektioner
568

Vanliga frågor

Är lektionen ”Idempotenta skript och logik för omförsök med backoff” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen DevOps-bootcamp, inklusive ”Idempotenta skript och logik för omförsök med backoff”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i DevOps-bootcamp innehåller totalt 4 lektioner.

Vad lär jag mig i ”Idempotenta skript och logik för omförsök med backoff”?

Utforma åtgärder som är säkra att köra igen och lägg till exponentiell backoff för instabila externa anrop. Ni övar på DevOps-bootcamp med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig DevOps-bootcamp?

Du behöver inga förkunskaper. Utbildningen i DevOps-bootcamp på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.

Hur lång tid tar lektionen ”Idempotenta skript och logik för omförsök med backoff”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här DevOps-bootcamp-lektionen?

Ja. Varje DevOps-bootcamp-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Strikt läge med set -euo pipefail
  2. Trap-hanterare för städning och signaler
  3. Säkra temporära filer och låskataloger
  4. Idempotenta skript och logik för omförsök med backoff
← Tillbaka till DevOps-bootcamp