← Блог

Мониторинг health check endpoint в FastAPI для SaaS

FastAPI отдаёт 200 на всех маршрутах, пока пул соединений с БД не умрёт в 3 ночи. Монитор аптайма всё ещё зелёный — он ни разу не бил в реальный probe URL. Мониторинг health check в FastAPI закрывает дыру: два маленьких маршрута, честные коды ответа и внешняя проверка с той стороны, откуда ходят клиенты. Вы добавите /health и /ready, проверите их curl извне своей сети и зарегистрируете URL в StillOnline для алертов и публичной страницы статуса.

Краткий ответ

GET /health — liveness (процесс жив, без БД). GET /ready — readiness (ping БД или Redis с таймаутом 3–5 секунд). При сбое зависимости — HTTP 503, при норме — 200. Probe-маршруты публичные, ответ быстрее двух секунд, в StillOnline укажите URL, который означает «клиенты не могут пользоваться продуктом». Free: один проект, один URL, интервал пять минут, только код статуса — тарифы.

FastAPI — Python-фреймворк для HTTP API. Uvicorn — серверный процесс на вашем порту. Внешний монитор шлёт GET из публичного интернета по расписанию — как браузер без логина. Это не то же самое, что проверка внутри процесса: монитор подтверждает DNS, TLS и reverse proxy end-to-end.

Базовый стек-агностичный гайд — быстрый старт health URL. Здесь — готовые маршруты FastAPI, liveness vs readiness и подключение StillOnline без Kubernetes.

1. Что показывать в health-маршруте FastAPI (и что скрывать)

Ответ probe — маленький JSON, «табличка на двери», а не складской учёт.

ПолеВключать?Зачем
status (ok / down)ДаЯсный сигнал для логов и дежурного
timestamp (UTC ISO)ДаВидно, что ответ свежий
version или git SHAДа на /healthПомогает после деплоев
checks по зависимостямДа на /readyВидно, что упало
Пароли, API-ключи, внутренние хостыНетProbe часто без авторизации
Stack traceНетУтечка деталей сканерам

Делайте: модель ответа через Pydantic. Не делайте: те же сериализаторы, что в админке, на публичном пути.

Минимальный handler /health:

from datetime import datetime, timezone
from fastapi import APIRouter
from pydantic import BaseModel

router = APIRouter()

class HealthResponse(BaseModel):
    status: str
    timestamp: str
    version: str

@router.get("/health", response_model=HealthResponse)
async def health() -> HealthResponse:
    return HealthResponse(
        status="ok",
        timestamp=datetime.now(timezone.utc).isoformat(),
        version="1.0.0",
    )

Подключите через app.include_router(router). Зафиксируйте имена (/health и /ready, или /healthz, если так ждёт платформа) и не меняйте их молча после включения мониторов.

Делайте: /health без обращения к БД. Не делайте: SELECT 1 на liveness — лишний шум при кратком сбое БД.

2. Разделите /health и /ready для workers и пула БД

Liveness: «процесс принимает HTTP?» Readiness: «инстанс может обслуживать трафик?» В Kubernetes сбой liveness перезапускает pod, readiness убирает из балансировщика. На Railway или Fly.io без K8s разделение всё равно важно: Uvicorn жив, а каждый API-вызов висит из-за исчерпанного пула.

Цепочка probe: StillOnline GET → балансировщик → Uvicorn → /ready → параллельные проверки БД/Redis → 200 или 503 → страница статуса + алерт.

Readiness с async-проверкой БД (таймаут 5 с):

import asyncio
from fastapi import APIRouter, Response, status
from sqlalchemy import text

CHECK_TIMEOUT = 5.0

async def check_database() -> bool:
    try:
        conn = await asyncio.wait_for(engine.connect(), timeout=CHECK_TIMEOUT)
        async with conn:
            await conn.execute(text("SELECT 1"))
        return True
    except Exception:
        return False

@router.get("/ready")
async def ready(response: Response):
    if not await check_database():
        response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
        return {"status": "down", "checks": {"database": "fail"}}
    return {"status": "ok", "checks": {"database": "ok"}}

Делайте: проверки через asyncio.gather, чтобы уложиться в один таймаут. Не делайте: синхронные драйверы БД внутри async def без thread pool — Uvicorn блокируется.

Большинство API SaaS указывают в StillOnline именно /ready. Зафиксируйте выбор в README. Паттерны URL — в проверках для API-only SaaS.

3. Коды ответа, таймауты и auth на probe URL

На Free StillOnline смотрит только HTTP-код, не поля JSON. 200 — здорово, 503 — сбой зависимости. Для K8s успех — 200–399; 503 — честный сигнал readiness.

Цель для /health — доли секунды; /ready — до трёх секунд суммарно, на каждую проверку 3–5 секунд. Внешние мониторы часто обрываются около двух секунд — жёсткий потолок.

Cache-Control на /health:

@router.get("/health")
async def health(response: Response):
    response.headers["Cache-Control"] = "no-cache, no-store"
    return {"status": "ok"}

Делайте: исключите /health и /ready из JWT middleware. Не делайте: OAuth на probe — мониторы не шлют Authorization, получите ложные 401.

Проверка извне вашей сети:

curl -sS -o /dev/null -w "%{http_code}\n" https://api.example.com/health
curl -sS https://api.example.com/ready

Почините 301 и TLS до регистрации URL. Сравните с дизайном health endpoint и маршрутом Laravel, если стек смешанный.

4. Подключите проверки StillOnline и канал алертов

StillOnline по расписанию шлёт GET на полный HTTPS URL, обновляет страницу статуса и уведомляет владельца. Репозиторий не сканируется — вставляете точный URL вручную.

  1. Войдите на stillonline.tech/ru/app и создайте проект (один на Free).
  2. Добавьте проверку с полным URL, например https://api.yourproduct.com/ready. Метод GET, ожидаемый 200. На Free интервал около пяти минут.
  3. Подождите 2–3 цикла probe, прежде чем судить о стабильности.
  4. Включите алерты владельца в настройках. Free — один канал: email или Telegram или Slack. Pro ($9/мес) и Ultimate ($29/мес) — больше проверок и каналов — тарифы.
  5. Дайте ссылку на страницу статуса в support-доках.

Делайте: мониторьте публичный edge URL клиентов. Не делайте: localhost или внутренние hostname.

StillOnline дополняет in-cluster probe: ловите сбои DNS, TLS и балансировщика, которые pod изнутри не видит. На Free — около пяти минут между probe и два сбоя подряд до алерта.

5. Чеклист перед production

  1. Зафиксируйте канонические URL в README и runbook.
  2. Проверьте 503 при остановленной БД на staging; 200 после восстановления.
  3. Замерьте задержку с домашней сети, не только из дата-центра.
  4. Уберите секреты из JSON и access-логов на probe-путях.
  5. Запишите, какой URL смотрит StillOnline, и обновите при переименовании маршрута.
  6. Не тащите PyPI health-библиотеки, пока хватает простых маршрутов.

Делайте: health-маршруты в чеклисте каждого деплоя. Не делайте: только HEADStillOnline шлёт GET.

Что дальше

У вас маршруты FastAPI, которым можно доверять снаружи, и проверка StillOnline, которая разбудит, когда публичный URL падает. Добавьте вторую проверку для маркетингового сайта на другом хосте, расширьте тариф, когда нужно больше URL или каналов, и дайте ссылку на status page в футере.

Откройте дашборд StillOnline, вставьте URL /ready и включите канал, который вы читаете в 3 ночи.

Связанные материалы

FAQ

Должен ли health endpoint StillOnline бить в базу данных?

Не на /health. Ping БД или Redis — на /ready, если хотите, чтобы StillOnline показывал down при сбое зависимостей. Краткие сбои БД дают шум в алертах — подстройте таймауты.

Что мониторить в StillOnline — /health или /ready?

URL, который означает «клиенты не могут пользоваться продуктом». Большинство API SaaS выбирают /ready. Только /health — если проверки зависимостей дадут неприемлемые ложные срабатывания.

Как быстро должен отвечать /health для StillOnline?

Идеал — доли секунды. Держите меньше двух секунд с учётом TLS и географии probe.

Нужен ли PyPI-пакет для health в FastAPI?

Для MVP — нет. Хватает маршрутов и Pydantic. Библиотеки вроде fastapi-health — когда много условных проверок в K8s.