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.