All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m0s
CI / backend-tests (pull_request) Successful in 17m25s
Публичный лендинг meraocenka.ru лежал 30–90 с на КАЖДОМ деплое (#3274). Причина не в скорости подмены контейнера: она стоит полсекунды. `docker compose up -d` со СПИСКОМ сервисов работает в две фазы — сначала create (старый контейнер каждого сервиса останавливается и УДАЛЯЕТСЯ, иначе занято container_name), потом start, в порядке зависимостей и с ожиданием их условий. Между фазами старого фронта уже нет, а новый ещё не запущен. Прод, 10.09, два деплоя подряд (docker inspect .Created/.StartedAt): пачка сервисов: tradein-backend создан 15:01:40 → запущен 15:02:10 (30 с), в логе Caddy три 503 на лендинге: 15:01:46/:52 и 15:02:06; ОДИН сервис: tradein-frontend создан 16:42:17.5 → запущен 16:42:18.0 (0,5 с), 503 в логе нет ни одного. Тот же двухфазный порядок воспроизведён на стенде (реальный образ фронта + Caddy 2): соседи создаются сразу, стартуют через 41 с. Поэтому frontend убран из общего `up -d $SERVICES` и пересоздаётся своей командой после пачки: в его графе один сервис, create и start идут подряд. Остаток ~0,5 с добирает ретрай подключения в Caddy — отдельным коммитом, он мержится своим путём (caddy_only → graceful reload, без пересборки). Проба для замера на живом деплое — scripts/probe-deploy-window.sh: считает коды и САМУЮ ДЛИННУЮ серию не-200 в секундах, 000 отдельной строкой (его даёт и отбой периметра, не только простой). Запускать НА хосте прода: с внешнего адреса частая серия сама ловит 30–50 % 000. Refs #3274
99 lines
5.7 KiB
Bash
Executable file
99 lines
5.7 KiB
Bash
Executable file
#!/bin/sh
|
||
# Проба окна недоступности при деплое (#3274).
|
||
#
|
||
# ЗАЧЕМ. Статус джобы деплоя зелёный независимо от того, видел ли посетитель
|
||
# ошибку: подмена контейнера происходит ВНУТРИ успешного прогона. Единственный
|
||
# честный замер — непрерывный опрос публичного адреса во время деплоя и подсчёт
|
||
# САМОЙ ДЛИННОЙ СЕРИИ подряд идущих не-200. Приёмка #3274 сформулирована именно
|
||
# так (комментарий от 05.09): «серия не-2xx/000 на / в окне мержа < 2 с».
|
||
#
|
||
# ГДЕ ЗАПУСКАТЬ — НА САМОМ ХОСТЕ (ssh poincare), не с ноутбука. Боевой периметр
|
||
# отбивает частые серии с одного внешнего адреса: замер 11.09 дал 30–50 %
|
||
# `000` с внешнего IP при интервале 0 и ноль при 2 с, а с самого хоста — 0/10
|
||
# без пауз. Иначе защита периметра читается как простой сервиса.
|
||
#
|
||
# ПОЧЕМУ 000 СЧИТАЕТСЯ ОТДЕЛЬНО. curl отдаёт `000`, когда HTTP-ответа не было
|
||
# вообще (TCP/TLS не встал, таймаут). Это может быть и простой (Caddy
|
||
# пересоздан), и отбой периметра — сваливать его в одну кучу с 502/503 нельзя,
|
||
# иначе своя же защита попадёт в числа простоя.
|
||
#
|
||
# Запуск:
|
||
# scripts/probe-deploy-window.sh [URL] [ИНТЕРВАЛ_С] [ДЛИТЕЛЬНОСТЬ_С] [ФАЙЛ]
|
||
# scripts/probe-deploy-window.sh --summary <ФАЙЛ> # пересчитать сводку
|
||
# scripts/probe-deploy-window.sh --selftest # проверка счётчика серий
|
||
#
|
||
# Пример (окно деплоя, 10 минут с шагом 200 мс):
|
||
# ssh poincare 'nohup /opt/gendesign/scripts/probe-deploy-window.sh \
|
||
# https://meraocenka.ru/ 0.2 600 /tmp/probe-mera.tsv >/dev/null 2>&1 &'
|
||
set -eu
|
||
|
||
# Сводка по TSV (epoch<TAB>ISO-время<TAB>код<TAB>секунды): сколько чего, и главное — самая
|
||
# длинная серия подряд идущих не-200 в СЕКУНДАХ (по меткам времени самих
|
||
# образцов, а не «число образцов × интервал»: curl с таймаутом растягивает шаг,
|
||
# и умножение занизило бы реальную длину окна).
|
||
summarize() {
|
||
awk -F'\t' '
|
||
# Серия закрывается первым 200. Длина в секундах считается ТОЛЬКО в END:
|
||
# средний шаг известен лишь после прохода, а внутри цикла он ещё 0 — из-за
|
||
# этого первая версия занижала окно ровно на один шаг (поймал --selftest).
|
||
{ total++; code[$3]++
|
||
if ($3 == "200") {
|
||
if (streak > max_n) { max_n = streak; max_first = first_bad_ts; max_last = last_bad_ts; max_at = first_bad_at }
|
||
streak = 0
|
||
} else {
|
||
if (!streak) { first_bad_ts = $1; first_bad_at = $2 }
|
||
streak++; last_bad_ts = $1
|
||
}
|
||
if (prev_ts && $1 - prev_ts < 60) { gaps += $1 - prev_ts; gapn++ }
|
||
prev_ts = $1
|
||
}
|
||
END {
|
||
step = (gapn ? gaps / gapn : 0)
|
||
if (streak > max_n) { max_n = streak; max_first = first_bad_ts; max_last = last_bad_ts; max_at = first_bad_at }
|
||
printf "образцов: %d, шаг ~%.2f с\n", total, step
|
||
for (c in code) printf " %s: %d\n", c, code[c]
|
||
printf "максимальная серия не-200: %d образцов ≈ %.1f с (начало %s)\n", \
|
||
max_n, (max_n ? max_last - max_first + step : 0), (max_at ? max_at : "-")
|
||
}
|
||
' "$1"
|
||
}
|
||
|
||
if [ "${1:-}" = "--summary" ]; then
|
||
summarize "$2"
|
||
exit 0
|
||
fi
|
||
|
||
if [ "${1:-}" = "--selftest" ]; then
|
||
tmp=$(mktemp)
|
||
# 10 образцов с шагом 1 с: три подряд не-200 (2-я…4-я секунды) → серия ≈ 3 с.
|
||
printf '1000\tT0\t200\t0.01\n1001\tT1\t502\t0.01\n1002\tT2\t000\t5.00\n1003\tT3\t503\t0.01\n1004\tT4\t200\t0.01\n1005\tT5\t200\t0.01\n1006\tT6\t502\t0.01\n1007\tT7\t200\t0.01\n' >"$tmp"
|
||
out=$(summarize "$tmp")
|
||
rm -f "$tmp"
|
||
echo "$out"
|
||
echo "$out" | grep -q "максимальная серия не-200: 3 образцов ≈ 3.0 с" || {
|
||
echo "SELFTEST FAILED: серия посчитана неверно" >&2
|
||
exit 1
|
||
}
|
||
echo "$out" | grep -q " 000: 1" || { echo "SELFTEST FAILED: 000 не выделен отдельно" >&2; exit 1; }
|
||
echo "SELFTEST OK"
|
||
exit 0
|
||
fi
|
||
|
||
URL=${1:-https://meraocenka.ru/}
|
||
INTERVAL=${2:-1}
|
||
DURATION=${3:-900}
|
||
OUT=${4:-/tmp/probe-deploy-window.tsv}
|
||
|
||
: >"$OUT"
|
||
echo "проба: $URL каждые ${INTERVAL}с в течение ${DURATION}с → $OUT"
|
||
end=$(( $(date +%s) + DURATION ))
|
||
while [ "$(date +%s)" -lt "$end" ]; do
|
||
# --max-time 5: зависший запрос не должен растягивать шаг пробы на минуты.
|
||
# -o /dev/null: тело не нужно, важны код и время.
|
||
line=$(curl -sS -o /dev/null --max-time 5 -w '%{http_code}\t%{time_total}' "$URL" 2>/dev/null || echo "000 0")
|
||
printf '%s\t%s\t%s\n' "$(date +%s.%N)" "$(date -u +%H:%M:%SZ)" "$line" >>"$OUT"
|
||
sleep "$INTERVAL"
|
||
done
|
||
|
||
echo "── сводка ─────────────────────────────────────────"
|
||
summarize "$OUT"
|