Блог "Дети ОИТа"
/поиск

Bash-скрипты для оркестратора: паттерны идемпотентности и обработка сбоев

Паттерны написания надёжных bash-скриптов для оркестрации: идемпотентность, обработка сбоев, race conditions, структурированное логирование.

Bash-скрипты для оркестратора: паттерны идемпотентности и обработка сбоев

Аннотация

Практическое руководство по написанию надёжных Bash-скриптов для оркестраторов (Ansible, systemd-run, Nomad/Consul, Kubernetes Job). Разбираются паттерны идемпотентности, lock-файлы, таймауты, ретраи, backoff, health-check и журналирование. Примеры адаптированы для CI/CD и production-раннеров.

1. Цели и ограничения

  • Идемпотентность: повторный запуск не ломает состояние.
  • Наблюдаемость: структурированное логирование, exit-коды, метрики.
  • Отказоустойчивость: таймауты, ретраи с экспоненциальным backoff, circuit breaker.
  • Безопасность: set -euo pipefail, проверка входных данных, минимальные привилегии.

2. Базовые гигиенические паттерны

set -euo pipefail
IFS=$'
	'

LOG_FILE="/var/log/orchestrator/$(basename "$0" .sh).log"
mkdir -p "$(dirname "$LOG_FILE")"

log() {
  echo "$(date --iso-8601=seconds) [${1^^}] $*" | tee -a "$LOG_FILE"
}

# Прекратить, если не в TTY и нет обязательных переменных
: "${ENV:?Environment not set}" 2>/dev/null || { log error "ENV missing"; exit 1; }

## ПИД-файл и блокировка
PIDFILE="/run/$(basename "$0").pid"
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
  log warn "Another instance is running (PID $(cat "$PIDFILE"))"
  exit 0
fi
echo $$ > "$PIDFILE"
trap 'rm -f "$PIDFILE"' EXIT

3. Идемпотентность

3.1 Проверка желаемого состояния

Вместо «создать всегда» — «создать, если отсутствует»:

if ! id "$USER" &>/dev/null; then
  useradd --system --home-dir "/var/lib/$USER" "$USER"
  log info "User $USER created"
else
  log info "User $USER already exists"
fi

3.2 Lock-файлы

LOCK="/run/$(basename "$0").lock"
exec 200>"$LOCK"
flock -n 200 || { log warn "Another instance is running"; exit 0; }
# автоматический unlock при выходе

3.3 Бандлы изменений

  • Собирайте все проверки до применения
  • Применяйте в транзакции где возможно: systemd-sysext, rpm --transaction, docker import/tag атомарны

4. Обработка сбоев

4.1 Таймауты

# timeout coreutil
if ! timeout 30s curl -fS "$URL" -o "$OUT"; then
  log error "Fetch failed или timed out"
  exit 1
fi

4.2 Ретраи с экспоненциальным backoff

max=5
delay=2
n=0
until cmd_succeeds; do
  ((n++))
  if (( n > max )); then log error "Max retries reached"; exit 1; fi
  log warn "Retry $n/$max в ${delay}s"
  sleep "$delay"
  delay=$((delay * 2))
done

4.3 Circuit Breaker

Ведите счётчик последовательных сбоев в /run или Redis; при пороге — переход в open на N секунд.

5. Журналирование и метрики

  • Строки в формате: timestamp level message key=value
  • Экспортируйте: длительность шагов, количество ретраев, размер данных
  • systemd-cat для интеграции с journald

6. Примеры для оркестраторов

6.1 Ansible module-wrapper

  • Скрипт возвращает JSON {changed: bool, msg: string}
  • Используйте argspec, проверку типов

6.2 Kubernetes Job

  • Liveness/readiness через sidecar или /healthz
  • TTL seconds after finished для очистки

6.3 Nomad/Consul

  • Сигналы SIGHUP для reload конфигурации
  • Consul KV как distributed lock

7. Чек-лист поставки

  • shellcheck пройден без ошибок
  • Тесты: успех, сбой, таймаут, повторный запуск
  • Lock и pid-файлы работают
  • Логирование в journald + файл
  • Метрики экспортятся
  • Runbook с exit-кодами

Заключение

Надёжные Bash-скрипты — это не «магия», а дисциплина: проверка состояния, блокировки, таймауты, ретраи и наблюдаемость. Эти паттерны легко переносятся между оркестраторов и CI/CD.