fix(tradein/deploy): не уходить в drain, когда пересоздавать нечего (#2679)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
`SCRAPER_RECREATE` истинно и на infra-правках (compose / workflow / deploy/**), а те почти всегда собирают тот же образ по кэшу: digest не меняется, `up -d` выходит no-op — и за него платили пятиминутным ожиданием слива scrape_runs, прерывая сбор. Теперь после `docker compose pull` (порядок важен: до pull'а под :latest ещё старый образ) сравниваем ID подтянутого образа с тем, на котором бежит tradein-scraper. Совпало — печатаем «пересоздавать нечего» и идём дальше без drain'а; не совпало или контейнера/тега нет — drain как раньше. Заодно закрыт ложный startup-reap: чекпоинт и reap завязаны на тот же признак и больше не выполняются, когда recreate'а не было. Иначе прогон, переживший таймаут drain'а в НЕ пересозданном контейнере, помечался бы 'cancelled', продолжая работать. scraper остаётся в $SERVICES в обоих случаях — при совпавшем образе `up -d` no-op, но правка самого compose (env/лимиты сервиса) так всё же доезжает. Сверка образов: «контейнера нет» и «контейнер отстал» теперь разные сообщения — это разные аварии и чинятся по-разному; отсутствие tradein-backend (эталона) тоже отдельная строка. Refs #2679
This commit is contained in:
parent
15ca70ad70
commit
bb6e5c7e41
1 changed files with 59 additions and 16 deletions
|
|
@ -294,6 +294,9 @@ jobs:
|
|||
uses: appleboy/ssh-action@v1.0.3
|
||||
env:
|
||||
IMAGE_TAG: latest
|
||||
# Нужен на VPS, чтобы спросить у демона ID подтянутого образа и не
|
||||
# уходить в drain, когда пересоздавать нечего (см. ниже, #2679).
|
||||
IMAGE_BACKEND: ${{ env.IMAGE_BACKEND }}
|
||||
GHCR_PAT: ${{ secrets.GHCR_PAT }}
|
||||
# #2679: backend / scraper / tgbot — ОДИН И ТОТ ЖЕ образ
|
||||
# gendesign-tradein-backend (см. docker-compose.prod.yml: три сервиса,
|
||||
|
|
@ -316,7 +319,7 @@ jobs:
|
|||
username: ${{ secrets.DEPLOY_USER }}
|
||||
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
port: ${{ secrets.DEPLOY_PORT }}
|
||||
envs: IMAGE_TAG,GHCR_PAT,SCRAPER_RECREATE,GITHUB_SHA
|
||||
envs: IMAGE_TAG,IMAGE_BACKEND,GHCR_PAT,SCRAPER_RECREATE,GITHUB_SHA
|
||||
script: |
|
||||
set -euo pipefail
|
||||
cd /opt/gendesign
|
||||
|
|
@ -497,8 +500,42 @@ jobs:
|
|||
# с browser/backend/frontend, отдельного graceful-drain не требует.
|
||||
SERVICES="browser backend frontend tgbot"
|
||||
SCRAPER_STOP_TS=""
|
||||
scraper_stale=""
|
||||
if [ "${SCRAPER_RECREATE:-true}" = "true" ]; then
|
||||
echo "→ backend-образ мог пересобраться — scraper пересоздаётся вместе с backend (#2679);"
|
||||
# Пересоздавать нечего — и ждать нечего (#2679). SCRAPER_RECREATE
|
||||
# истинно и на infra-правках (compose / workflow / deploy/**), а те
|
||||
# почти всегда собирают ТОТ ЖЕ образ по кэшу: digest не меняется,
|
||||
# `up -d` выходит no-op — и платить за него пятиминутным drain'ом,
|
||||
# прерывая многочасовой сбор, не за что. Сравниваем, на том ли
|
||||
# образе бежит scraper, что уже лежит в локальном демоне.
|
||||
# ПОРЯДОК ВАЖЕН: только ПОСЛЕ `docker compose pull` (шаг выше) —
|
||||
# до pull'а под тегом :latest ещё старый образ, сравнение всегда
|
||||
# «совпало» и drain пропускался бы как раз тогда, когда он нужен.
|
||||
# Заодно чинит ложный startup-reap: чекпоинт/reap ниже завязаны на
|
||||
# ЭТОТ же признак и больше не выполняются, когда recreate'а не было
|
||||
# (иначе живой прогон с heartbeat старше чекпоинта помечался бы
|
||||
# 'cancelled', продолжая работать).
|
||||
pulled_image=$(docker image inspect -f '{{.Id}}' "$IMAGE_BACKEND:$IMAGE_TAG" 2>/dev/null || echo "")
|
||||
running_image=$(docker inspect -f '{{.Image}}' tradein-scraper 2>/dev/null || echo "")
|
||||
if [ -n "$pulled_image" ] && [ "$pulled_image" = "$running_image" ]; then
|
||||
echo "→ образ scraper'а не изменился ($pulled_image) — пересоздавать нечего,"
|
||||
echo " drain пропускаем, in-flight прогоны не трогаем"
|
||||
else
|
||||
scraper_stale="yes"
|
||||
fi
|
||||
# scraper в $SERVICES в обоих случаях: при совпавшем образе `up -d`
|
||||
# — no-op, но правка самого compose (env/лимиты сервиса) так всё же
|
||||
# доезжает. Ceiling: такой config-only recreate идёт БЕЗ drain'а —
|
||||
# страхуют SIGTERM-drain (#1182) + stop_grace_period 120s, а строку
|
||||
# прогона подчистит периодический 6h zombie-reaper.
|
||||
SERVICES="$SERVICES scraper"
|
||||
else
|
||||
echo "→ backend-образ в этом деплое не пересобирался — tradein-scraper не трогаем"
|
||||
echo " (сверка образов ниже всё равно проверит, что он не отстал)"
|
||||
fi
|
||||
|
||||
if [ -n "$scraper_stale" ]; then
|
||||
echo "→ новый backend-образ — scraper пересоздаётся вместе с backend (#2679);"
|
||||
echo " ждём слива in-flight scrape_runs (до 5 мин)"
|
||||
drained=""
|
||||
for i in $(seq 1 30); do
|
||||
|
|
@ -536,16 +573,11 @@ jobs:
|
|||
SCRAPER_STOP_TS="$(docker compose -p gendesign-tradein -f docker-compose.prod.yml exec -T postgres \
|
||||
psql -U "${TRADEIN_POSTGRES_USER:-tradein}" -d tradein -tAc "SELECT NOW();" 2>/dev/null | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" || SCRAPER_STOP_TS=""
|
||||
echo "→ scraper checkpoint ts (DB clock): ${SCRAPER_STOP_TS:-unknown}"
|
||||
|
||||
SERVICES="$SERVICES scraper"
|
||||
else
|
||||
echo "→ backend-образ в этом деплое не пересобирался — tradein-scraper не трогаем"
|
||||
echo " (сверка образов ниже всё равно проверит, что он не отстал)"
|
||||
fi
|
||||
|
||||
docker compose -p gendesign-tradein -f docker-compose.prod.yml up -d --no-deps $SERVICES
|
||||
|
||||
if [ "${SCRAPER_RECREATE:-true}" = "true" ] && [ -n "${SCRAPER_STOP_TS:-}" ]; then
|
||||
if [ -n "$scraper_stale" ] && [ -n "${SCRAPER_STOP_TS:-}" ]; then
|
||||
echo "→ Startup-reap (#1951): помечаем orphaned running-строки, замороженные recreate'ом"
|
||||
# NB: psql `-c` НЕ поддерживает `:'var'`-подстановку (переменная доходит до
|
||||
# сервера как литерал → syntax error, см. комментарий выше про TRADEIN_READER_PASSWORD)
|
||||
|
|
@ -707,20 +739,31 @@ jobs:
|
|||
# узнать об этом надо в момент деплоя, а не через месяц. exit 1 идёт
|
||||
# ДО записи .tradein-deployed-sha → следующий прогон возьмёт ту же
|
||||
# базу и пересоберёт всё накопленное (тот же приём, что в health-check).
|
||||
backend_image=$(docker inspect -f '{{.Image}}' tradein-backend 2>/dev/null || echo "missing")
|
||||
# «Контейнера нет» и «контейнер отстал» — разные аварии и чинятся
|
||||
# по-разному, поэтому сообщения различаются явно.
|
||||
backend_image=$(docker inspect -f '{{.Image}}' tradein-backend 2>/dev/null || echo "")
|
||||
image_mismatch=""
|
||||
if [ -z "$backend_image" ]; then
|
||||
echo "ERROR: контейнера tradein-backend нет — сверять образы не с чем."
|
||||
image_mismatch="yes"
|
||||
fi
|
||||
for svc in scraper tgbot; do
|
||||
svc_image=$(docker inspect -f '{{.Image}}' "tradein-$svc" 2>/dev/null || echo "missing")
|
||||
if [ "$svc_image" != "$backend_image" ]; then
|
||||
echo "ERROR: tradein-$svc на образе $svc_image, tradein-backend — на $backend_image"
|
||||
svc_image=$(docker inspect -f '{{.Image}}' "tradein-$svc" 2>/dev/null || echo "")
|
||||
if [ -z "$svc_image" ]; then
|
||||
echo "ERROR: контейнера tradein-$svc НЕТ (удалён или не создавался) — это не отставший"
|
||||
echo " образ, а неполный стек: сервис не работает вообще."
|
||||
image_mismatch="yes"
|
||||
elif [ -n "$backend_image" ] && [ "$svc_image" != "$backend_image" ]; then
|
||||
echo "ERROR: tradein-$svc ОТСТАЛ: работает на $svc_image, tradein-backend — на $backend_image"
|
||||
image_mismatch="yes"
|
||||
fi
|
||||
done
|
||||
if [ -n "$image_mismatch" ]; then
|
||||
echo "ERROR: образы backend-семейства разошлись — деплой FAILED (#2679)."
|
||||
echo " Лечение вручную: docker compose -p gendesign-tradein \\"
|
||||
echo " -f /opt/gendesign/tradein-mvp/docker-compose.prod.yml \\"
|
||||
echo " up -d --force-recreate --no-deps scraper tgbot"
|
||||
echo "ERROR: backend-семейство не на одном образе — деплой FAILED (#2679)."
|
||||
echo " Лечение вручную (поднимет отсутствующие, пересоздаст отставшие):"
|
||||
echo " docker compose -p gendesign-tradein \\"
|
||||
echo " -f /opt/gendesign/tradein-mvp/docker-compose.prod.yml \\"
|
||||
echo " up -d --force-recreate --no-deps backend scraper tgbot"
|
||||
exit 1
|
||||
fi
|
||||
echo "→ образы совпадают: backend/scraper/tgbot на $backend_image."
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue