Проверки состояния, барьеры готовности и циклы ожидания
Реализуйте циклы ожидания зависимостей и проверки работоспособности, чтобы контейнеризированные службы надёжно запускались
«Проверки состояния, барьеры готовности и циклы ожидания» — бесплатный урок Linux Command Line & Bash Scripting Mastery на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Linux Command Line & Bash Scripting Mastery, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Почему службы не запускаются
В контейнерных средах службы редко запускаются изолированно. Веб-приложению нужна готовая база данных, микрослужбе — доступный брокер сообщений, а обработчику — заполненный кэш, прежде чем он сможет обрабатывать задачи.
Без координации контейнеры запускаются параллельно, и первые запросы поступают до того, как зависимости становятся работоспособными. В результате возникают ошибки отказа в соединении, исключения обращения к нулевому указателю и повреждённое состояние при запуске, из-за которых требуется ручной перезапуск.
- Проверка работоспособности — продолжает ли процесс выполняться и не заблокирован ли он?
- Проверка готовности — готова ли служба принимать трафик?
- Цикл ожидания — сценарий запуска, который блокирует дальнейшее выполнение, пока зависимости не станут доступными
Kubernetes предоставляет встроенные механизмы проверок, но сценарии оболочки, лежащие в основе initContainers, оболочек entrypoint.sh и автономных проверок состояния, написаны на Bash. Их освоение — важный навык DevOps.
Шаблон ожидания
Простейший цикл ожидания зависимости проверяет целевой объект через фиксированные интервалы, пока тот не станет доступным. Каноническая форма использует цикл while вместе с nc (netcat) или curl для проверки порта TCP или конечной точки HTTP.
Ключевые решения по проектированию:
- Ограничение времени — прекращайте ожидание через N секунд, чтобы неисправная зависимость не блокировала под бесконечно
- Интервал задержки — делайте паузу между проверками, чтобы не перегружать восстанавливающуюся службу
- Код завершения — завершайтесь с кодом 1 при превышении времени ожидания, чтобы контейнер перезапустился (или инициализационный контейнер явно сообщил об ошибке)
Приведённый ниже фрагмент ожидает подключения к порту TCP не более 60 секунд, после чего запускает основной процесс.
#!/usr/bin/env bash
set -euo pipefail
HOST="${DB_HOST:-postgres}"
PORT="${DB_PORT:-5432}"
TIMEOUT=60
INTERVAL=2
ELAPSED=0
echo "[wait-for] Waiting for ${HOST}:${PORT}..."
until nc -z "$HOST" "$PORT" 2>/dev/null; do
if (( ELAPSED >= TIMEOUT )); then
echo "[wait-for] Timed out after ${TIMEOUT}s waiting for ${HOST}:${PORT}" >&2
exit 1
fi
echo "[wait-for] ${HOST}:${PORT} not ready — retrying in ${INTERVAL}s (${ELAPSED}s elapsed)"
sleep "$INTERVAL"
(( ELAPSED += INTERVAL ))
done
echo "[wait-for] ${HOST}:${PORT} is up — continuing"
exec "$@"Проверки готовности HTTP с помощью curl
TCP-соединение сообщает только о том, что порт открыт, но не о готовности приложения за ним обслуживать запросы. Многие службы предоставляют специальную конечную точку /health или /readyz, которая возвращает HTTP 200 только после инициализации всех внутренних подсистем.
Используйте curl --fail --silent --output /dev/null для проверки конечной точки готовности HTTP. Флаг --fail заставляет curl завершаться с кодом 22 при ответах 4xx/5xx, что позволяет корректно управлять логикой повторных попыток.
Важные флаги:
--fail— считать ошибки HTTP ошибками curl (ненулевой код завершения)--silent— подавлять вывод сведений о ходе выполнения--max-time N— ограничение времени одного запроса в секундах--retry N --retry-delay S— собственный механизм повторных попыток curl (полезен в простых случаях)
#!/usr/bin/env bash
set -euo pipefail
HEALTH_URL="${HEALTH_URL:-http://localhost:8080/healthz}"
TIMEOUT=90
INTERVAL=3
ELAPSED=0
echo "[probe] Polling readiness at ${HEALTH_URL}"
until curl --fail --silent --output /dev/null \
--max-time 2 "${HEALTH_URL}"; do
if (( ELAPSED >= TIMEOUT )); then
echo "[probe] Service not ready after ${TIMEOUT}s" >&2
exit 1
fi
printf '[probe] Not ready yet (%ds elapsed)\n' "$ELAPSED"
sleep "$INTERVAL"
(( ELAPSED += INTERVAL ))
done
echo "[probe] Service is ready"
exec "$@"Экспоненциальная задержка в циклах ожидания
Цикл повторных попыток с фиксированным интервалом постоянно обращается к восстанавливающейся службе с одинаковой частотой. Экспоненциальная задержка удваивает время ожидания после каждой попытки, снижая нагрузку во время восстановления и при этом быстро достигая результата, когда служба запускается без задержек.
Стандартная формула: sleep_time = min(base * 2^attempt, max_sleep). Компонент случайного смещения предотвращает эффект «стада», когда множество контейнеров перезапускается одновременно.
Этот шаблон используется в промышленных инструментах, таких как wait-for-it, при повторных попытках AWS SDK и в циклах согласования контроллеров Kubernetes.
#!/usr/bin/env bash
set -euo pipefail
HOST="${HOST:-redis}"
PORT="${PORT:-6379}"
MAX_ATTEMPTS=8
BASE_SLEEP=1
MAX_SLEEP=30
for attempt in $(seq 1 "$MAX_ATTEMPTS"); do
if nc -z "$HOST" "$PORT" 2>/dev/null; then
echo "[backoff] Connected to ${HOST}:${PORT} on attempt ${attempt}"
exec "$@"
fi
# Exponential backoff with jitter
raw=$(( BASE_SLEEP * (2 ** (attempt - 1)) ))
capped=$(( raw < MAX_SLEEP ? raw : MAX_SLEEP ))
jitter=$(( RANDOM % 3 ))
sleep_time=$(( capped + jitter ))
echo "[backoff] Attempt ${attempt}/${MAX_ATTEMPTS} failed — sleeping ${sleep_time}s"
sleep "$sleep_time"
done
echo "[backoff] ${HOST}:${PORT} unreachable after ${MAX_ATTEMPTS} attempts" >&2
exit 1Проверка нескольких зависимостей
У реальных приложений бывает несколько зависимостей: база данных, кэш, брокер сообщений и, возможно, внешний API. Последовательная проверка увеличивает время запуска. Лучше проверять все зависимости параллельно и ждать успешного завершения каждой.
Оператор Bash & переводит каждую проверку в фоновый режим, а wait собирает их коды завершения. Если какая-либо проверка завершается ошибкой, точка входа завершается с ненулевым кодом, что запускает перезапуск контейнера.
Ключевой приём: сохраняйте идентификаторы фоновых процессов с помощью $! и явно передавайте их в wait, чтобы проверять отдельные коды завершения.
#!/usr/bin/env bash
set -euo pipefail
wait_tcp() {
local host="$1" port="$2" timeout="${3:-30}"
local elapsed=0
until nc -z "$host" "$port" 2>/dev/null; do
(( elapsed >= timeout )) && { echo "TIMEOUT ${host}:${port}" >&2; return 1; }
sleep 2; (( elapsed += 2 ))
done
echo "[ok] ${host}:${port}"
}
# Launch all probes in parallel
wait_tcp postgres 5432 60 & PID_PG=$!
wait_tcp redis 6379 30 & PID_RD=$!
wait_tcp rabbitmq 5672 45 & PID_RQ=$!
# Collect results — fail fast if any probe failed
FAILED=0
for pid in $PID_PG $PID_RD $PID_RQ; do
wait "$pid" || (( FAILED++ ))
done
if (( FAILED > 0 )); then
echo "[entrypoint] ${FAILED} dependency probe(s) failed — aborting" >&2
exit 1
fi
echo "[entrypoint] All dependencies ready"
exec "$@"Проверка работоспособности и проверка готовности: разные сценарии для разных проверок
Kubernetes различает проверки работоспособности и проверки готовности, и они должны выполнять разные задачи:
- Проверка работоспособности — отвечает на вопрос: процесс всё ещё работает и не заблокирован ли он? Она должна выполняться быстро и проверять только внутреннее состояние, например наличие локального файла с PID или возврат локальной конечной точкой состояния кода 200. Если проверка работоспособности завершается ошибкой, контейнер завершается и перезапускается.
- Проверка готовности — отвечает на вопрос: должен ли этот под получать трафик? Она может проверять последующие зависимости. Если проверка готовности завершается ошибкой, под исключается из балансировщика нагрузки службы, но не перезапускается.
Никогда не помещайте медленные проверки зависимостей в проверки работоспособности. Кратковременный сбой базы данных ошибочно перезапустит все поды приложения и усугубит проблему.
#!/usr/bin/env bash
# liveness.sh — fast local-only check
# Used in: livenessProbe.exec.command
set -euo pipefail
PID_FILE="/var/run/app/app.pid"
HEALTH_URL="http://127.0.0.1:8080/internal/live"
# Check 1: process is running
[[ -f "$PID_FILE" ]] || { echo "PID file missing" >&2; exit 1; }
kill -0 "$(cat "$PID_FILE")" 2>/dev/null || { echo "Process dead" >&2; exit 1; }
# Check 2: local endpoint responds (2s timeout — never block)
curl --fail --silent --max-time 2 --output /dev/null "$HEALTH_URL" || {
echo "Liveness endpoint unresponsive" >&2
exit 1
}
echo "live"
exit 0Проверка startupProbe и initContainers
Kubernetes предлагает третий тип проверки: startupProbe. Она выполняется вместо проверок работоспособности и готовности до своего первого успешного завершения, давая медленно запускающимся приложениям, например приложениям с прогревом JVM или миграциями базы данных, время на инициализацию без ложных сбоев проверки работоспособности.
Для ожидания зависимостей initContainers часто удобнее сценариев точки входа. Они запускаются до контейнеров приложения, а Kubernetes автоматически обрабатывает повторные попытки и перезапуски. Образу инициализационного контейнера нужны только sh, nc или curl — можно использовать минимальный образ busybox или alpine.
Пример спецификации initContainer в манифесте пода:
# kubernetes/pod-with-init.yaml (illustrative — not runnable as bash)
# initContainers run sequentially before app containers
initContainers:
- name: wait-for-postgres
image: busybox:1.36
command:
- sh
- -c
- |
set -e
echo 'Waiting for postgres...'
until nc -z postgres 5432; do
echo 'postgres not ready — sleeping 2s'
sleep 2
done
echo 'postgres is up'
- name: run-migrations
image: myapp:latest
command: ['python', 'manage.py', 'migrate', '--noinput']
envFrom:
- secretRef:
name: app-secretsСценарий проверки состояния с выводом JSON
В промышленных системах часто объединяют состояние нескольких подсистем и предоставляют его в виде структурированного сообщения JSON. Его используют балансировщики нагрузки, оркестраторы и панели мониторинга.
Сценарий проверки состояния на Bash может напрямую формировать вывод JSON с помощью printf или jq. Код завершения по-прежнему управляет автоматизацией, а тело JSON предназначено для операторов и систем мониторинга.
Принято возвращать HTTP 200 с {"status":"ok"} при исправном состоянии и HTTP 503 с {"status":"degraded", "checks":{...}} при неполадках. Приведённый ниже сценарий предназначен для обслуживания лёгкой оболочкой HTTP, например socat, или для непосредственного вызова проверками выполнения команд Kubernetes.
#!/usr/bin/env bash
# health_check.sh — composite health with JSON output
set -uo pipefail
check_postgres() {
pg_isready -h "${DB_HOST:-postgres}" -p "${DB_PORT:-5432}" \
-U "${DB_USER:-app}" -t 2 &>/dev/null
}
check_redis() {
redis-cli -h "${REDIS_HOST:-redis}" -p "${REDIS_PORT:-6379}" \
PING 2>/dev/null | grep -q PONG
}
check_disk() {
local usage
usage=$(df / | awk 'NR==2{gsub(/%/,"",$5); print $5}')
(( usage < 90 ))
}
PG_OK=0; RD_OK=0; DSK_OK=0
check_postgres && PG_OK=1
check_redis && RD_OK=1
check_disk && DSK_OK=1
OVERALL=$(( PG_OK && RD_OK && DSK_OK ))
STATUS=$( (( OVERALL )) && echo 'ok' || echo 'degraded' )
printf '{"status":"%s","checks":{"postgres":%s,"redis":%s,"disk":%s}}\n' \
"$STATUS" "$PG_OK" "$RD_OK" "$DSK_OK"
(( OVERALL )) && exit 0 || exit 1Утилита ограничения времени с шаблоном предельного срока
Команда GNU timeout оборачивает любую команду и завершает её, если та не заканчивается за указанное время. Это самый простой способ установить жёсткий крайний срок для цикла ожидания или проверки состояния без ручного управления фоновыми заданиями.
timeout DURATION COMMAND [ARGS...]
Коды завершения timeout:
- 0 — команда успешно завершилась до истечения крайнего срока
- Код завершения команды — команда выполнилась, но вернула ненулевой код
- 124 — время выполнения команды истекло (отправлен SIGTERM)
- 137 — команда завершена с помощью SIGKILL (после
--kill-after)
Проверка кода завершения 124 позволяет вывести понятное сообщение о превышении времени ожидания вместо общей ошибки.
#!/usr/bin/env bash
set -euo pipefail
HOST="${DB_HOST:-postgres}"
PORT="${DB_PORT:-5432}"
DEADLINE=60 # seconds
# Inline poll loop, wrapped by timeout
timeout "$DEADLINE" bash -c "
until nc -z '$HOST' '$PORT' 2>/dev/null; do
echo '[wait] ${HOST}:${PORT} not ready...'
sleep 2
done
" && echo "[wait] ${HOST}:${PORT} is up" || {
code=$?
if (( code == 124 )); then
echo "[wait] Timed out after ${DEADLINE}s waiting for ${HOST}:${PORT}" >&2
else
echo "[wait] Probe failed with exit code ${code}" >&2
fi
exit "$code"
}
exec "$@"Самодостаточный шаблон ожидания на чистом Bash
Во многих минимальных образах контейнеров отсутствует nc (netcat). Чистый Bash умеет открывать TCP-соединения с помощью подстановки процесса через /dev/tcp — нестандартной, но широко поддерживаемой встроенной возможности Bash, для которой не нужны внешние инструменты.
Синтаксис: /dev/tcp/HOST/PORT — Bash открывает TCP-соединение при перенаправлении в этот путь или из него. Если соединение отклонено или время ожидания истекло, возникает ошибка с ненулевым кодом завершения.
Этот приём используется в популярном сценарии wait-for-it.sh, который поставляется со многими конфигурациями Docker Compose. Приведённая ниже реализация полностью автономна, и её можно COPY в любой файл Dockerfile.
#!/usr/bin/env bash
# wait-for-it.sh (pure bash, no nc/curl required)
set -uo pipefail
usage() { echo "Usage: $0 HOST:PORT [-t TIMEOUT] [-- COMMAND]"; exit 1; }
parse_hostport() {
HOST="${1%%:*}"
PORT="${1##*:}"
[[ -n "$HOST" && "$PORT" =~ ^[0-9]+$ ]] || usage
}
[[ $# -ge 1 ]] || usage
parse_hostport "$1"; shift
TIMEOUT=30
[[ "${1:-}" == "-t" ]] && { TIMEOUT="$2"; shift 2; }
[[ "${1:-}" == "--" ]] && shift
wait_for() {
local elapsed=0
while (( elapsed < TIMEOUT )); do
# Pure bash TCP probe — no nc, no curl
if (exec 3<>"/dev/tcp/${HOST}/${PORT}") 2>/dev/null; then
exec 3>&-
return 0
fi
sleep 1
(( elapsed++ ))
done
return 1
}
echo "Waiting for ${HOST}:${PORT} (timeout ${TIMEOUT}s)..."
if wait_for; then
echo "${HOST}:${PORT} is available"
[[ $# -gt 0 ]] && exec "$@"
else
echo "Timed out waiting for ${HOST}:${PORT}" >&2
exit 1
fiИнтеграция проверок в entrypoint.sh
Шаблон точки входа — стандартный способ объединить циклы ожидания, проверку переменных окружения и запуск процесса в одном удобном для сопровождения сценарии. Docker вызывает этот сценарий через ENTRYPOINT, а сценарий завершается командой exec "$@", передавая управление CMD с тем же PID и обеспечивая корректную передачу сигналов.
Сценарий промышленного уровня entrypoint.sh обычно:
- заранее проверяет обязательные переменные окружения и немедленно завершается при ошибке;
- запускает циклы ожидания зависимостей;
- выполняет миграции базы данных, если это необходимо;
- выполняет финальную самопроверку;
- передаёт управление с помощью
exec "$@".
Использование exec критически важно: оно заменяет процесс оболочки, поэтому приложение становится процессом PID 1 и напрямую получает от Docker/Kubernetes сигнал SIGTERM для корректного завершения работы.
#!/usr/bin/env bash
# docker/entrypoint.sh
set -euo pipefail
# ── 1. Validate required env vars ────────────────────────────────────
for var in DATABASE_URL REDIS_URL SECRET_KEY; do
[[ -n "${!var:-}" ]] || { echo "FATAL: ${var} is not set" >&2; exit 1; }
done
# ── 2. Parse DB host/port from DATABASE_URL ──────────────────────────
DB_HOST=$(echo "$DATABASE_URL" | sed -E 's|.*@([^:/]+).*|\1|')
DB_PORT=$(echo "$DATABASE_URL" | sed -E 's|.*:([0-9]+)/.*|\1|')
# ── 3. Wait for dependencies ─────────────────────────────────────────
timeout 60 bash -c "
until nc -z '${DB_HOST}' '${DB_PORT}' 2>/dev/null; do sleep 2; done
" || { echo "Database unreachable" >&2; exit 1; }
# ── 4. Run migrations ────────────────────────────────────────────────
echo "[entrypoint] Running migrations..."
python manage.py migrate --noinput
# ── 5. Hand off to CMD (exec preserves PID 1 for signal handling) ────
echo "[entrypoint] Starting application: $*"
exec "$@"Проверка знаний: проверки работоспособности и готовности
Проверьте, насколько Вы поняли основные понятия, рассмотренные в этом уроке.
Повторение: проверки состояния, ограничения готовности и циклы ожидания
В этом уроке Вы создали набор инструментов Bash для надёжной координации запуска контейнеров. Были рассмотрены следующие темы:
- Шаблон ожидания — цикл
until nc -z HOST PORTс жёстким ограничением времени и контролем прошедшего времени предотвращает бесконечную блокировку из-за неисправных зависимостей. - Проверки готовности HTTP —
curl --fail --max-timeподтверждает, что приложение действительно готово, а не просто имеет открытый порт. - Экспоненциальная задержка — удвоение интервала сна между повторными попытками снижает нагрузку, вызванную эффектом «стада», и позволяет восстанавливающимся службам стабилизироваться.
- Параллельная проверка зависимостей — перевод проверок в фоновый режим с помощью
&и сбор результатов черезwait $PIDсокращают задержку запуска при наличии нескольких зависимостей. - Проверка работоспособности и проверка готовности — проверки работоспособности должны быть быстрыми и выполняться только локально, а проверки готовности могут обращаться к последующим системам. Никогда не смешивайте их.
- startupProbe — защищает медленно запускающиеся приложения от ложных сбоев проверки работоспособности во время инициализации.
- Проверка через /dev/tcp — проверка TCP на чистом Bash не требует внешних инструментов и идеально подходит для минимальных образов контейнеров.
- entrypoint.sh — проверка переменных окружения, циклы ожидания, миграции и
exec "$@"образуют стандартный шаблон запуска контейнеризированной службы.
Эти шаблоны формируют основу самовосстанавливающихся контейнерных развёртываний промышленного уровня в Docker Compose, Kubernetes и ECS.
Часто задаваемые вопросы
Урок «Проверки состояния, барьеры готовности и циклы ожидания» бесплатный?
Да — полный текст урока «Проверки состояния, барьеры готовности и циклы ожидания» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Linux Command Line & Bash Scripting Mastery, подпишись на CoddyKit PRO. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.
Чему я научусь в уроке «Проверки состояния, барьеры готовности и циклы ожидания»?
Реализуйте циклы ожидания зависимостей и проверки работоспособности, чтобы контейнеризированные службы надёжно запускались Ты практикуешь Linux Command Line & Bash Scripting Mastery с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Linux Command Line & Bash Scripting Mastery?
Предыдущий опыт не требуется. Linux Command Line & Bash Scripting Mastery на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Проверки состояния, барьеры готовности и циклы ожидания»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Linux Command Line & Bash Scripting Mastery?
Да. Каждый урок Linux Command Line & Bash Scripting Mastery включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Компактные Dockerfile и точки входа Shell
- Шаблонизация конфигураций с envsubst и heredoc
- Создание облачных ресурсов через CLI и jq
- Проверки состояния, барьеры готовности и циклы ожидания