ci: смоук периметра МЕРЫ запускается сразу после деплоя
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m14s
CI / openapi-codegen-check (pull_request) Successful in 2m8s
CI / backend-tests (pull_request) Successful in 16m22s
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m14s
CI / openapi-codegen-check (pull_request) Successful in 2m8s
CI / backend-tests (pull_request) Successful in 16m22s
scripts/smoke-mera-perimeter.sh — единственная проверка, которая видит публичный периметр целиком (короткие адреса, 301 с длинных, публичный API, закрытость B2B-путей на публичном домене). Запускался он только ночным cron'ом 06:17 UTC, поэтому регресс жил до суток, и находил его либо тот же cron, либо владелец — ровно тот сценарий, против которого проверки и писались. Добавлен job `perimeter-smoke` в ОБА пайплайна: конфиг прокси (deploy.yml) и фронт МЕРЫ (deploy-tradein.yml) едут раздельно, сломать периметр может каждый. Отдельный job, а не шаг внутри deploy: «выкатили» и «периметр цел» — два разных вердикта, красный смоук не должен читаться как неудавшийся деплой. Идёт только при deploy.result == 'success' — поверх несостоявшейся выкатки проверять нечего. Перед смоуком — ожидание готовности по ДВУМ признакам (лэндинг 200 и API 401): `up -d --force-recreate` отдаёт управление раньше, чем бэкенд начинает отвечать, и без ожидания смоук ловил бы гонку, а не регресс. По истечении 150 с ожидание не падает, а печатает warning и пускает смоук — иначе «не успел подняться» и «периметр сломан» слились бы в один красный шаг. Дублирование 20 строк в двух пайплайнах осознанное: `workflow_call` под act_runner не гарантирован, а зависимость, которая может молча не сработать, здесь хуже повтора. Заодно: - ci.yml: гейт `bash -n` на все shell-скрипты (14 файлов) — shellcheck'а в репозитории нет, а опечатка в смоуке обнаружилась бы следующим утром и выглядела бы как регресс периметра. Гейт падает, если не нашёл ни одного файла: пустая маска дала бы зелёный шаг, который ничего не проверяет. - perimeter-smoke.yml: push-триггер на сам скрипт — правка проверяется сразу. Проверено: смоук против прода сейчас зелёный целиком, 27 из 27 проверок, то есть в пайплайн въезжает работающий гейт, а не заведомо красный. Опасение из задачи про внешний геокодер проверено по коду и оказалось у́же: `geocoder.suggest` — цепочка «кадастр → DaData → Nominatim → []», каждый внешний тир под `except Exception`, отказ и квота дают 200 с пустым списком. Покраснеть проверка может только на зависании дольше 15 с. Записал это в самом скрипте, ожидание не ослаблял. Closes #2917
This commit is contained in:
parent
b1106916a9
commit
db03e93781
5 changed files with 158 additions and 0 deletions
|
|
@ -113,6 +113,36 @@ jobs:
|
|||
python3 scripts/check-migration-lock-timeout.py --selftest
|
||||
python3 scripts/check-migration-lock-timeout.py
|
||||
|
||||
- name: "Guard: shell-скрипты синтаксически валидны (#2917)"
|
||||
# Соседям по этому job'у (caddy validate, lock_timeout) — тот же довод:
|
||||
# дёшево, на каждом PR, ловит опечатку до прода.
|
||||
#
|
||||
# ЗАЧЕМ ИМЕННО ЭТО. scripts/smoke-mera-perimeter.sh — единственная
|
||||
# проверка, которая видит публичный периметр МЕРЫ целиком, и до этого
|
||||
# PR она запускалась только ночным cron'ом. Опечатка в ней обнаружилась
|
||||
# бы следующим утром — и выглядела бы как регресс периметра, а не как
|
||||
# сломанный скрипт. Ни один линтер шелла в репозитории не стоит
|
||||
# (shellcheck нет), поэтому берём то, что есть в каждом образе: `bash -n`
|
||||
# разбирает файл, не исполняя его.
|
||||
#
|
||||
# ГРАНИЦА: `bash -n` ловит СИНТАКСИС, а не смысл — неверный URL или
|
||||
# перепутанный ожидаемый код он не увидит. Это не замена прогона,
|
||||
# а защита от того, что скрипт вообще не запустится.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
found=0
|
||||
for f in $(git ls-files 'scripts/*.sh' 'ops/*.sh' 'ops/**/*.sh'); do
|
||||
found=$((found + 1))
|
||||
bash -n "$f" || { echo "::error file=$f::синтаксическая ошибка в shell-скрипте"; exit 1; }
|
||||
done
|
||||
# Ноль файлов означал бы, что гейт молча ничего не проверяет —
|
||||
# ровно тот случай, когда зелёный шаг не значит ничего (#2871).
|
||||
if [ "$found" -eq 0 ]; then
|
||||
echo "::error::не найдено ни одного .sh — гейт бы прошёл впустую, проверь маску"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ синтаксис проверен у $found shell-скриптов"
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -1121,6 +1121,61 @@ jobs:
|
|||
# неважно, пропущен он (test/build упали) или упал сам (SSH/миграция/
|
||||
# health-check/сверка образов #2679). Красная точка встаёт именно там, где
|
||||
# решение реально принято, а не там, где она случайно оказалась по цепочке if.
|
||||
# ── Смоук публичного периметра МЕРЫ после выкатки (#2917) ──────────────────
|
||||
#
|
||||
# ЗАЧЕМ ЗДЕСЬ. scripts/smoke-mera-perimeter.sh — единственная проверка, которая
|
||||
# видит периметр целиком (короткие адреса, 301 с длинных, публичный API,
|
||||
# закрытость B2B-путей на публичном домене). До этого PR он запускался только
|
||||
# по cron'у 06:17 UTC, то есть регресс жил до суток и находил его либо ночной
|
||||
# прогон, либо владелец. Для правки, чья логика живёт в конфиге прокси, это
|
||||
# единственный настоящий гейт — и он был асинхронным.
|
||||
#
|
||||
# ПОЧЕМУ ОТДЕЛЬНЫЙ JOB, А НЕ ШАГ В deploy. Вердикты разные: «выкатили» и
|
||||
# «периметр цел» — два разных факта, и красный смоук не должен читаться как
|
||||
# неудавшийся деплой. Деплой к этому моменту уже прошёл; смоук говорит, что
|
||||
# именно получилось.
|
||||
#
|
||||
# ПОЧЕМУ ДУБЛИРУЕТСЯ В ДВУХ ПАЙПЛАЙНАХ. Конфиг прокси (deploy.yml) и фронт
|
||||
# МЕРЫ (deploy-tradein.yml) едут раздельно, и сломать периметр может каждый.
|
||||
# `workflow_call` под act_runner не гарантирован, поэтому 20 строк повторены
|
||||
# осознанно вместо зависимости, которая может молча не сработать.
|
||||
perimeter-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
# Только после РЕАЛЬНОЙ выкатки: при skipped/failed проверять нечего, а
|
||||
# красный смоук поверх несостоявшегося деплоя увёл бы разбор не туда.
|
||||
if: always() && needs.deploy.result == 'success'
|
||||
timeout-minutes: 6
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Дождаться, пока периметр отвечает после пересоздания контейнеров
|
||||
# `up -d --force-recreate` возвращает управление раньше, чем бэкенд
|
||||
# начинает отвечать. Без ожидания смоук ловил бы не регресс, а гонку.
|
||||
# Ждём ДВА признака: лэндинг (Caddy + фронт) и API (бэкенд поднялся) —
|
||||
# одного мало, Caddy отвечает раньше апстрима.
|
||||
run: |
|
||||
set -uo pipefail
|
||||
for i in $(seq 1 30); do
|
||||
page=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 https://meraocenka.ru/ || true)
|
||||
api=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 https://gendsgn.ru/trade-in/api/v1/me || true)
|
||||
if [ "$page" = "200" ] && [ "$api" = "401" ]; then
|
||||
echo "периметр отвечает (попытка $i): лэндинг $page, API $api"
|
||||
exit 0
|
||||
fi
|
||||
echo "ждём готовности, попытка $i/30: лэндинг '${page:-нет ответа}', API '${api:-нет ответа}'"
|
||||
sleep 5
|
||||
done
|
||||
# НЕ падаем здесь: вердикт должен вынести смоук, а не таймаут ожидания.
|
||||
# Иначе «не успел подняться» и «периметр сломан» слились бы в один
|
||||
# красный шаг без разбора.
|
||||
echo "::warning::за 150 с периметр так и не ответил ожидаемо — запускаем смоук, его вывод и будет диагнозом"
|
||||
|
||||
- name: Смоук периметра
|
||||
run: |
|
||||
chmod +x scripts/smoke-mera-perimeter.sh
|
||||
./scripts/smoke-mera-perimeter.sh
|
||||
|
||||
deploy-status:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, build-backend, build-frontend, build-browser, deploy]
|
||||
|
|
|
|||
|
|
@ -749,6 +749,61 @@ jobs:
|
|||
# если deploy не завершился success — неважно, пропущен он (build упал) или
|
||||
# упал сам (SSH/миграция/health-check). Красная точка встаёт именно там, где
|
||||
# решение реально принято, а не там, где она случайно оказалась по цепочке if.
|
||||
# ── Смоук публичного периметра МЕРЫ после выкатки (#2917) ──────────────────
|
||||
#
|
||||
# ЗАЧЕМ ЗДЕСЬ. scripts/smoke-mera-perimeter.sh — единственная проверка, которая
|
||||
# видит периметр целиком (короткие адреса, 301 с длинных, публичный API,
|
||||
# закрытость B2B-путей на публичном домене). До этого PR он запускался только
|
||||
# по cron'у 06:17 UTC, то есть регресс жил до суток и находил его либо ночной
|
||||
# прогон, либо владелец. Для правки, чья логика живёт в конфиге прокси, это
|
||||
# единственный настоящий гейт — и он был асинхронным.
|
||||
#
|
||||
# ПОЧЕМУ ОТДЕЛЬНЫЙ JOB, А НЕ ШАГ В deploy. Вердикты разные: «выкатили» и
|
||||
# «периметр цел» — два разных факта, и красный смоук не должен читаться как
|
||||
# неудавшийся деплой. Деплой к этому моменту уже прошёл; смоук говорит, что
|
||||
# именно получилось.
|
||||
#
|
||||
# ПОЧЕМУ ДУБЛИРУЕТСЯ В ДВУХ ПАЙПЛАЙНАХ. Конфиг прокси (deploy.yml) и фронт
|
||||
# МЕРЫ (deploy-tradein.yml) едут раздельно, и сломать периметр может каждый.
|
||||
# `workflow_call` под act_runner не гарантирован, поэтому 20 строк повторены
|
||||
# осознанно вместо зависимости, которая может молча не сработать.
|
||||
perimeter-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
# Только после РЕАЛЬНОЙ выкатки: при skipped/failed проверять нечего, а
|
||||
# красный смоук поверх несостоявшегося деплоя увёл бы разбор не туда.
|
||||
if: always() && needs.deploy.result == 'success'
|
||||
timeout-minutes: 6
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Дождаться, пока периметр отвечает после пересоздания контейнеров
|
||||
# `up -d --force-recreate` возвращает управление раньше, чем бэкенд
|
||||
# начинает отвечать. Без ожидания смоук ловил бы не регресс, а гонку.
|
||||
# Ждём ДВА признака: лэндинг (Caddy + фронт) и API (бэкенд поднялся) —
|
||||
# одного мало, Caddy отвечает раньше апстрима.
|
||||
run: |
|
||||
set -uo pipefail
|
||||
for i in $(seq 1 30); do
|
||||
page=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 https://meraocenka.ru/ || true)
|
||||
api=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 https://gendsgn.ru/trade-in/api/v1/me || true)
|
||||
if [ "$page" = "200" ] && [ "$api" = "401" ]; then
|
||||
echo "периметр отвечает (попытка $i): лэндинг $page, API $api"
|
||||
exit 0
|
||||
fi
|
||||
echo "ждём готовности, попытка $i/30: лэндинг '${page:-нет ответа}', API '${api:-нет ответа}'"
|
||||
sleep 5
|
||||
done
|
||||
# НЕ падаем здесь: вердикт должен вынести смоук, а не таймаут ожидания.
|
||||
# Иначе «не успел подняться» и «периметр сломан» слились бы в один
|
||||
# красный шаг без разбора.
|
||||
echo "::warning::за 150 с периметр так и не ответил ожидаемо — запускаем смоук, его вывод и будет диагнозом"
|
||||
|
||||
- name: Смоук периметра
|
||||
run: |
|
||||
chmod +x scripts/smoke-mera-perimeter.sh
|
||||
./scripts/smoke-mera-perimeter.sh
|
||||
|
||||
deploy-status:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-backend, build-worker, build-frontend, deploy]
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ on:
|
|||
schedule:
|
||||
# Раз в сутки, 06:17 UTC — вне пиков, время произвольное.
|
||||
- cron: '17 6 * * *'
|
||||
# #2917: правка самого смоука должна проверяться сразу, а не следующим утром.
|
||||
# Проверки read-only (curl по публичным адресам), поэтому прогонять их на
|
||||
# push в main безопасно и дёшево. Синтаксис скрипта отдельно гейтится в
|
||||
# ci.yml на каждом PR — здесь проверяется уже поведение против прода.
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'scripts/smoke-mera-perimeter.sh'
|
||||
- '.forgejo/workflows/perimeter-smoke.yml'
|
||||
|
||||
concurrency:
|
||||
group: perimeter-smoke-mera
|
||||
|
|
|
|||
|
|
@ -121,6 +121,15 @@ check "meraocenka.ru/_next/image — must 404 (не открываем опти
|
|||
# `/trade-in/api/*`. Зелёная только первая = API открыт целиком и тест это
|
||||
# пропустил (ровно та ошибка, ради которой в Caddyfile выбран отдельный
|
||||
# префикс, а не поимённый проброс v1-путей).
|
||||
# ПРО ВНЕШНЮЮ ЗАВИСИМОСТЬ (#2917). Опасение «упадёт DaData — покраснеет
|
||||
# смоук без всякого регресса» проверено по коду и оказалось у́же, чем
|
||||
# звучит: `geocoder.suggest` — это цепочка «кадастровый тир → DaData →
|
||||
# Nominatim → []», и КАЖДЫЙ внешний тир обёрнут в `except Exception`
|
||||
# (services/geocoder.py). Отказ, квота и 5xx провайдера дают пустой список
|
||||
# и HTTP 200 — проверка остаётся зелёной. Покраснеть она может только если
|
||||
# провайдер ВИСНЕТ дольше 15 с (--max-time у curl), то есть на зависании,
|
||||
# а не на отказе. Ослаблять ожидание не стали: 200 здесь проверяет
|
||||
# открытость пути анониму, ради которой проверка и написана.
|
||||
check_post "meraocenka.ru public suggest — 200 anonymous" "$BASE_MERA/trade-in/api/public/mera/suggest" '{"q":"Малышева"}' 200
|
||||
check_post "meraocenka.ru public coverage — 200 anonymous" "$BASE_MERA/trade-in/api/public/mera/coverage" '{"lat":56.838,"lon":60.597,"rooms":2,"area_m2":54}' 200
|
||||
check "meraocenka.ru v1 geocode — must stay 404" "$BASE_MERA/trade-in/api/v1/geocode/suggest?q=test" 404
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue