TGViewer
About Python [ru] About Python [ru] @python_tesst · 6.44K subscribers
Post #2775 387
⁣Как корректно гасить Python-сервис, не теряя запросы

Когда твой сервис работает под k8s, рано или поздно придёт SIGTERM. Или ты сам его пошлёшь при деплое. Если не подготовиться — пользователи увидят 502, а фоновые задачи просто исчезнут.

Graceful shutdown — это не про SIGKILL. Это про то, чтобы сервис перестал принимать новое, дал время доделать текущее и только потом умер.

1. Ловим сигналы ОС

Берём SIGTERM или SIGINT. Сделать это в asyncio можно так:

import asyncio, signal

async def shutdown(sig, loop):
tasks = [t for t in asyncio.all_tasks()
if t is not asyncio.current_task()]
[task.cancel() for task in tasks]
await asyncio.gather(*tasks, return_exceptions=True)
loop.stop()

loop = asyncio.get_event_loop()
for sig in (signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(
sig, lambda s=sig: asyncio.create_task(shutdown(s, loop)))


Типичная ошибка — ожидать, что loop.add_signal_handler решит всё за тебя. Для in-flight запросов этого мало: фоновые задачи с долгим циклом просто отменятся, а не завершатся.

2. Health-check без блокировки

Как только пришёл сигнал, health-check должен показать "не готов". Иначе k8s продолжит слать трафик, пока не убьёт контейнер принудительно.

async def health_handler(request):
return web.Response(
text="OK" if app['is_healthy'] else "Stopping")

async def on_shutdown(app):
app['is_healthy'] = False


Только флаг — никаких блокирующих проверок. Тrade-off: быстрый ответ против точного отражения состояния. Для production-реалий это оправдано.

3. Draining in-flight запросов

Используй счётчик с asyncio.Lock. Каждый обработчик увеличивает счётчик при старте и уменьшает после завершения. В shutdown’е жди, пока счётчик не станет 0:

active_requests = 0
lock = asyncio.Lock()

async def handle_request(request):
async with lock:
active_requests += 1
try:
pass # твой код
finally:
async with lock:
active_requests -= 1

async def wait_for_drain():
while True:
async with lock:
if active_requests == 0:
break
await asyncio.sleep(0.5)


Но если запрос завис на 10 минут, сервис будет висеть. Тут нужен таймаут.

4. Таймаут на завершение

async def graceful_shutdown(timeout=30):
try:
await asyncio.wait_for(wait_for_drain(), timeout=timeout)
except asyncio.TimeoutError:
print("Drain timeout, force stop")


30 секунд — типичное значение для k8s. Если не успели, пусть оркестратор решает. Лучше потерять пару запросов, чем висеть вечно. Практический совет: настрой terminationGracePeriodSeconds в манифесте с запасом на 5-10 секунд.

Вывод: Safe-stopping — это тройной механизм: флаг health-check, ожидание дампа активных соединений и таймаут принудительного выхода, который защищает от зависания сервиса.
More from @python_tesst
  1. Sep 29, 2026Claude Sonnet 5.5 — уже не слив, релиз официально состоялся Прайс не трогали: $2 за миллио…
  2. Sep 29, 2026Post #3194
  3. Sep 29, 2026Дженсен Хуанг выкатил NVIDIA Open Agent Safety Platform — и притащил с собой 100+ партнеро…
  4. Sep 29, 2026Стикмену теперь под силу снести любой сайт. Чувак под это навайбкодил целую игруху: грузиш…
  5. Sep 28, 2026Post #3191
  6. Sep 27, 2026OpenAI метит в подписку за 500 баксов Что там в описании тарифа? Пока что от ChatGPT Pro о…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →