Merge pull request 'fix(caddy): окно деплоя — 503 «Сервис обновляется» с Retry-After вместо голого 502 на публичных путях МЕРЫ' (#3348) from fix/3274-deploy-window-error-page into main
All checks were successful
Deploy Infra Host / sync-infra-host (push) Successful in 8s
Deploy / changes (push) Successful in 13s
perimeter-smoke-mera / smoke (push) Successful in 21s
Deploy / build-backend (push) Successful in 54s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 52s
Deploy / build-frontend (push) Successful in 48s
Deploy / deploy (push) Successful in 5m31s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 17s
All checks were successful
Deploy Infra Host / sync-infra-host (push) Successful in 8s
Deploy / changes (push) Successful in 13s
perimeter-smoke-mera / smoke (push) Successful in 21s
Deploy / build-backend (push) Successful in 54s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 52s
Deploy / build-frontend (push) Successful in 48s
Deploy / deploy (push) Successful in 5m31s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 17s
This commit is contained in:
commit
2d4f669d4b
4 changed files with 118 additions and 7 deletions
54
caddy/deploy-window.caddy.snippet
Normal file
54
caddy/deploy-window.caddy.snippet
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# caddy/deploy-window.caddy.snippet — ответ на окно деплоя (#3274)
|
||||
#
|
||||
# Импортируется ВНУТРЬ `handle_errors 502 503 504 { ... }` (см. apps.caddy):
|
||||
# сам по себе снипет ничего не перехватывает, он только решает, ЧТО отдать,
|
||||
# когда апстрим не отвечает.
|
||||
#
|
||||
# ЧТО ЭТО ЛЕЧИТ, А ЧТО НЕТ. Каждый деплой tradein-frontend/tradein-backend
|
||||
# оставляет окно 30–90 с, в котором контейнера просто нет: Caddy набирает
|
||||
# новый апстрим сразу, тот ещё не слушает (замер по access-логам, #3274 —
|
||||
# все 502 кластеризуются на окнах мержа, duration < 2 мс = мгновенный отказ
|
||||
# соединения). Снипет НЕ УБИРАЕТ окно — он меняет то, что видит человек и
|
||||
# клиент внутри окна. Настоящее лечение (готовность нового контейнера до
|
||||
# переключения) — п.1 issue, решение владельца, здесь его нет.
|
||||
#
|
||||
# ПОЧЕМУ 503, А НЕ 502. 502 значит «апстрим ответил мусором» — постоянная
|
||||
# поломка; поисковик по нему выкидывает страницу из индекса, клиентские
|
||||
# библиотеки не ретраят. 503 + `Retry-After: 30` — стандартный код «временно
|
||||
# недоступен, приходи через 30 секунд»: Googlebot держит страницу в индексе,
|
||||
# HTTP-клиенты понимают, что повтор осмыслен.
|
||||
#
|
||||
# ПОЧЕМУ ДВА ТЕЛА. `/trade-in/api/*` вызывают из JS и внешних клиентов — они
|
||||
# парсят JSON, и HTML-страница у них превращается в ошибку разбора вместо
|
||||
# читаемого статуса. Всё остальное открывает человек браузером.
|
||||
#
|
||||
# ВНЕШНИХ РЕСУРСОВ В СТРАНИЦЕ НЕТ ВООБЩЕ — ни шрифта, ни CSS-файла, ни
|
||||
# картинки. В окне деплоя они пришли бы с того же мёртвого апстрима, и
|
||||
# страница-заглушка отрисовалась бы голым текстом. Отсюда же инлайновые
|
||||
# `style=` вместо блока `<style>`: фигурные скобки в теле `respond` Caddy
|
||||
# пытается разобрать как плейсхолдеры.
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@deployWindowApi path /trade-in/api/*
|
||||
handle @deployWindowApi {
|
||||
header Content-Type "application/json; charset=utf-8"
|
||||
header Retry-After "30"
|
||||
respond `{"detail":"Сервис обновляется, повторите запрос через минуту","error":"service_unavailable","retry_after":30}` 503
|
||||
}
|
||||
|
||||
handle {
|
||||
header Content-Type "text/html; charset=utf-8"
|
||||
header Retry-After "30"
|
||||
respond `<!doctype html>
|
||||
<html lang="ru">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Сервис обновляется</title>
|
||||
<body style="margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:#fff;color:#111">
|
||||
<main style="max-width:30rem;padding:2rem;text-align:center">
|
||||
<h1 style="font-size:1.25rem;font-weight:600;margin:0 0 .75rem">Сервис обновляется</h1>
|
||||
<p style="margin:0;line-height:1.6;color:#444">Это занимает около минуты. Обновите страницу чуть позже — введённые данные не потеряются.</p>
|
||||
</main>
|
||||
` 503
|
||||
}
|
||||
|
|
@ -267,6 +267,32 @@ gendsgn.ru {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Окно деплоя (#3274) — ТОЛЬКО для /trade-in. Каждый мерж в tradein
|
||||
# оставляет 30–90 с, в которые контейнера нет и посетитель видит голый 502
|
||||
# (замер по access-логам: 23 × 502 на этом домене за 4 суток, все —
|
||||
# на окнах деплоя). Снипет подменяет это на 503 + Retry-After и читаемое
|
||||
# тело; разбор «что лечится, а что нет» — в самом снипете.
|
||||
#
|
||||
# ГЕЙТ ПО ПУТИ ОБЯЗАТЕЛЕН: `handle_errors` объявляется на весь site-блок,
|
||||
# а этот блок обслуживает ещё и Site Finder («Птица») — его апстримы
|
||||
# (backend, frontend) деплоятся отдельным пайплайном и в задачу не входят.
|
||||
# Пути вне матчера не попадают ни в один вложенный handle, ошибка остаётся
|
||||
# необработанной, и Caddy отдаёт ровно то, что отдавал раньше. Проверено на
|
||||
# живом Caddy 2.11.3 (dead-upstream 127.0.0.1:9): `/` и `/api/v1/*` —
|
||||
# прежний пустой 502, `/trade-in/*` — новый 503.
|
||||
#
|
||||
# Матчер здесь сверяется с ИСХОДНЫМ путём запроса, а не с переписанным:
|
||||
# для error-маршрута Caddy восстанавливает запрос, каким он пришёл. Поэтому
|
||||
# `/trade-in/api/*` внутри снипета матчится, хотя на основном маршруте до
|
||||
# падения апстрима уже отработал `uri strip_prefix /trade-in`. Тоже
|
||||
# проверено на стенде, а не выведено из документации.
|
||||
handle_errors 502 503 504 {
|
||||
@tradeinScope path /trade-in /trade-in/*
|
||||
handle @tradeinScope {
|
||||
import ../deploy-window.caddy.snippet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
www.gendsgn.ru {
|
||||
|
|
@ -571,6 +597,19 @@ meraocenka.ru {
|
|||
handle {
|
||||
respond 404
|
||||
}
|
||||
|
||||
# Окно деплоя (#3274). В отличие от gendsgn.ru гейт по пути не нужен: все
|
||||
# апстримы этого блока — контейнеры МЕРЫ (tradein-frontend/tradein-backend),
|
||||
# чей деплой и создаёт окно. 15 × 502 за 4 суток, все на окнах мержа.
|
||||
#
|
||||
# Белый список выше это НЕ ослабляет. Во-первых, `handle_errors`
|
||||
# срабатывает только на перечисленные статусы, а отказ белого списка —
|
||||
# 404. Во-вторых, `respond 404` пишет ответ напрямую и ошибкой маршрута
|
||||
# вообще не является. Проверено на стенде: при мёртвом апстриме `/admin`
|
||||
# по-прежнему отдаёт 404, а не страницу обновления.
|
||||
handle_errors 502 503 504 {
|
||||
import ../deploy-window.caddy.snippet
|
||||
}
|
||||
}
|
||||
|
||||
# Домены-спутники МЕРА → 301 на канонический meraocenka.ru.
|
||||
|
|
|
|||
|
|
@ -778,6 +778,9 @@ services:
|
|||
# роняя ВСЕ сайты хоста, а не только metrics.gendsgn.ru.
|
||||
- ./caddy/metrics-ingest.caddy.snippet:/etc/caddy/caddy/metrics-ingest.caddy.snippet:ro
|
||||
- ./caddy/metrics-ui.caddy.snippet:/etc/caddy/caddy/metrics-ui.caddy.snippet:ro
|
||||
# То же самое для страницы окна деплоя (#3274): caddy/sites/apps.caddy
|
||||
# импортирует её как `import ../deploy-window.caddy.snippet`.
|
||||
- ./caddy/deploy-window.caddy.snippet:/etc/caddy/caddy/deploy-window.caddy.snippet:ro
|
||||
# Untracked локальные site-блоки (см. import в конце Caddyfile). Каталог
|
||||
# держится в git через caddy/local/.gitignore — иначе docker создал бы
|
||||
# отсутствующий bind-source сам, root-owned пустышкой.
|
||||
|
|
|
|||
|
|
@ -52,15 +52,22 @@ check() {
|
|||
}
|
||||
|
||||
check_post() {
|
||||
local desc="$1" url="$2" body="$3" expected="$4"
|
||||
local code
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \
|
||||
local desc="$1" url="$2" body="$3" expected="$4" reject="${5:-}"
|
||||
local out code head_and_body
|
||||
# -i: заголовки попадают в вывод вместе с телом — по ним отличаем ответ
|
||||
# приложения от заглушки Caddy (см. $reject у вызова payments/notify).
|
||||
out=$(curl -s -i -w '\n%{http_code}' --max-time 15 \
|
||||
-X POST -H 'Content-Type: application/json' -d "$body" "$url" 2>/dev/null)
|
||||
if [ "$code" = "$expected" ]; then
|
||||
echo "PASS: $desc ($url -> $code)"
|
||||
else
|
||||
code=${out##*$'\n'}
|
||||
head_and_body=${out%$'\n'*}
|
||||
if [ "$code" != "$expected" ]; then
|
||||
echo "FAIL: $desc ($url -> got '${code:-<no response>}', expected $expected)"
|
||||
fail=1
|
||||
elif [ -n "$reject" ] && printf '%s' "$head_and_body" | grep -qEi "$reject"; then
|
||||
echo "FAIL: $desc ($url -> $code, но ответ от заглушки окна деплоя, а не от приложения)"
|
||||
fail=1
|
||||
else
|
||||
echo "PASS: $desc ($url -> $code)"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -251,8 +258,16 @@ check "meraocenka.ru payments/checkout — must 404 (Caddy не проксиру
|
|||
# маршрут существует (не 404 — старый образ) И что приём платежей выключен
|
||||
# (`payments_enabled=False`). 200 здесь означал бы, что флаг включили, не тронув
|
||||
# этот смоук. GET → 405 закрепляет, что путь принимает только POST.
|
||||
#
|
||||
# С #3274 голый код 503 больше не доказывает ничего: Caddy сам отдаёт 503 на
|
||||
# окне деплоя (`handle_errors` -> caddy/deploy-window.caddy.snippet), и тогда
|
||||
# запрос до приложения не дошёл вовсе. Отличаем по признакам заглушки —
|
||||
# заголовок `Retry-After: 30` и `"error":"service_unavailable"` в теле; у
|
||||
# приложения тело `{"detail":"payments are disabled"}` и никакого Retry-After.
|
||||
# Совпал код, но пришла заглушка → FAIL, а не молчаливый PASS.
|
||||
check_post "trade-in payments/notify — 503 anonymous (маршрут есть, приём выключен)" \
|
||||
"$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" '{}' 503
|
||||
"$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" '{}' 503 \
|
||||
'service_unavailable|^retry-after: *30'
|
||||
check "trade-in payments/notify — 405 на GET (только POST)" \
|
||||
"$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" 405
|
||||
check "trade-in payments/checkout — 401 anonymous (не публичный по построению)" \
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue