Настроить health checks для LLM

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить health checks для LLM

1. Цель задачи

Научиться проектировать и реализовывать три типа health-эндпоинтов для LLM-сервиса (liveness, readiness, deep health) и настраивать соответствующие probes в Kubernetes. В результате микросервис корректно сообщает K8s о своём состоянии, что позволяет платформе автоматически перезапускать нездоровые поды и не направлять трафик на неподготовленные реплики.

Ключевой результат Эндпоинты /health, /ready, /deep работают в контейнере, K8s манифесты используют эти probes, и kubectl describe pod показывает Liveness: success, Readiness: success.


2. Исходные данные

Что нужноОткуда взять
LLM-сервис (минимальный)Написать самостоятельно: Python + FastAPI + заглушка для LLM
DockerУстановленный Docker Desktop или Docker Engine
Kubernetes (локальный)minikube, kind или Docker Desktop с K8s
Утилиты для тестовcurl, kubectl, k9s (опционально)

Если нет реального K8s — симулируем:

  1. Установите kind (Kubernetes in Docker) одной командой: brew install kind (macOS) или choco install kind (Windows).
  2. Создайте кластер: kind create cluster --name health-check.
  3. После работы удалите: kind delete cluster --name health-check.

Если нет LLM — используем заглушку:
FastAPI приложение с эндпоинтом /generate, который отвечает {"response": "Hello, I'm an LLM stub."}. Имитация загрузки модели — задержка 2-3 секунды при старте.


3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияPython 3.11+Реализация LLM-сервиса
Веб-фреймворкFastAPI + uvicornЭндпоинты health checks
Модель (заглушка)random / постоянный ответИмитация работы LLM
КонтейнеризацияDockerУпаковка сервиса
Оркестрацияkind / minikubeЛокальный Kubernetes
Нагрузочное тестированиеcurl, hey, wrkПроверка стабильности
Мониторинг (опционально)kubectl, k9sНаблюдение за probes

4. Этапы выполнения

Этап 1: Создание LLM-сервиса с тремя эндпоинтами (45–60 минут)

Действия

  1. Создайте структуру проекта

    health-check-llm/
    ├── app/
    │   ├── __init__.py
    │   ├── main.py
    │   └── services/
    │       ├── __init__.py
    │       └── llm_stub.py
    ├── Dockerfile
    ├── requirements.txt
    └── k8s/
        └── deployment.yaml
    
  2. Реализуйте services/llm_stub.py

    • Симуляция загрузки модели: time.sleep(3) в конструкторе.
    • Метод generate(prompt: str) -> str: возвращает заглушку.
    • Метод check_deep_health() -> bool: выполняет синтетический запрос к generate и проверяет, что ответ не пустой.
  3. Реализуйте main.py

    from fastapi import FastAPI, HTTPException
    from services.llm_stub import LLMStub
    import time
    
    app = FastAPI()
    model = None
    start_time = None
    
    @app.on_event("startup")
    async def startup():
        global model, start_time
        model = LLMStub()
        start_time = time.time()
    
    @app.get("/health")
    async def liveness():
        # Просто отвечает 200, если процесс жив
        return {"status": "alive"}
    
    @app.get("/ready")
    async def readiness():
        # Модель готова принимать запросы (прошло >3 сек с запуска)
        if model and time.time() - start_time > 3:
            return {"status": "ready"}
        raise HTTPException(status_code=503, detail="Model not ready yet")
    
    @app.get("/deep")
    async def deep_health():
        if model and model.check_deep_health():
            return {"status": "healthy", "model": "ok"}
        raise HTTPException(status_code=503, detail="Deep health check failed")
    
  4. Проверьте локально

    uvicorn app.main:app --port 8080
    curl http://localhost:8080/health
    curl http://localhost:8080/ready  # первые 3 секунды — 503
    curl http://localhost:8080/deep   # после инициализации — 200
    

Ожидаемый результат этапа Локально запущенный FastAPI сервер отвечает на все три эндпоинта корректными кодами (200 или 503).


Этап 2: Контейнеризация и тестирование образа (30 минут)

Действия

  1. Создайте Dockerfile

    FROM python:3.11-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    COPY app/ .
    EXPOSE 8080
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
    
  2. Создайте requirements.txt

    fastapi==0.115.0
    uvicorn==0.30.0
    
  3. Соберите образ и проверьте

    docker build -t health-check-llm:latest .
    docker run -d -p 8080:8080 health-check-llm:latest
    curl http://localhost:8080/health
    curl http://localhost:8080/ready   # 503 в первые секунды
    docker stop <container>
    

Ожидаемый результат этапа Docker образ собран и локально отвечает корректно.


Этап 3: Развёртывание в Kubernetes с Probes (1–1.5 часа)

Действия

  1. Создайте k8s/deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: llm-health
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: llm-health
      template:
        metadata:
          labels:
            app: llm-health
          annotations:
            prometheus.io/scrape: "true"
            prometheus.io/port: "8080"
        spec:
          containers:
          - name: llm
            image: health-check-llm:latest
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 8080
            livenessProbe:
              httpGet:
                path: /health
                port: 8080
              initialDelaySeconds: 5
              periodSeconds: 10
            readinessProbe:
              httpGet:
                path: /ready
                port: 8080
              initialDelaySeconds: 10
              periodSeconds: 5
            startupProbe:
              httpGet:
                path: /health
                port: 8080
              initialDelaySeconds: 3
              periodSeconds: 2
              failureThreshold: 10
    
  2. Загрузите образ в кластер (для kind нужно предварительно загрузить образ):

    kind load docker-image health-check-llm:latest --name health-check
    
  3. Примените манифест

    kubectl apply -f k8s/deployment.yaml
    
  4. Проверьте статус подов и probes

    kubectl get pods
    # Ждём, пока STATUS станет Running
    kubectl describe pod <pod-name> | grep -A5 "Liveness\|Readiness\|Startup"
    
  5. Создайте Service для доступа

    apiVersion: v1
    kind: Service
    metadata:
      name: llm-health-svc
    spec:
      selector:
        app: llm-health
      ports:
      - port: 80
        targetPort: 8080
    

    Примените и проверьте доступ через kubectl port-forward.

Ожидаемый результат этапа Под проходит liveness/readiness пробы, kubectl describe показывает Liveness: success и Readiness: success.


Этап 4: Проверка сценариев нездоровья (30–45 минут)

Действия

  1. Сымитируйте сбой liveness — остановите процесс внутри контейнера:

    kubectl exec -it <pod-name> -- /bin/sh
    # внутри контейнера
    kill 1
    # выйдите
    

    Через некоторое время (период liveness + порог) K8s перезапустит под.
    Проверьте: kubectl get pods — увидите перезапуски (RESTARTS > 0).

  2. Сымитируйте readiness false — отключите модель (например, установите флаг):

    • Добавьте временный эндпоинт /fail в приложение (на время эксперимента), который меняет глобальную переменную ready = False.
    • Или просто остановите startup-задержку — в нашем случае /ready вернёт 503, если старт не завершён.
    • Убедитесь, что Service не направляет трафик на неподготовленный под: kubectl get endpoints — под должен быть not ready.
  3. Проверьте /deep в реальном времени

    • Отправьте GET-запрос на /deep через порт-форвард:
      kubectl port-forward svc/llm-health-svc 8888:80
      curl http://localhost:8888/deep
      
    • Если глубокий чек фейлится (например, модель вернула пустую строку), probe можно настроить на failureThreshold: 2.

Ожидаемый результат этапа Доказано, что K8s корректно перезапускает поды после сбоя liveness и исключает поды из сервиса при readiness false.


Этап 5: Настройка мониторинга проб (опционально, 20 минут)

Действия

  1. Установите Prometheus и Grafana (можно через kube-prometheus-stack).
  2. Добавьте аннотации к поду (уже есть в манифесте).
  3. Импортируйте дашборд с метриками kube_pod_status_ready и prober_probe_total.
  4. Создайте алерт на слишком частые перезапуски (RestartCount > 3 за 5 минут).

Ожидаемый результат этапа В Grafana отображаются статусы readiness и количество перезапусков.


5. Критерии приемки (Definition of Done)

  • Реализованы эндпоинты /health, /ready, /deep в FastAPI с корректными HTTP кодами (200 или 503).
  • Docker образ собран и запускается, все три эндпоинта отвечают.
  • Манифест Deployment содержит livenessProbe, readinessProbe и startupProbe.
  • Pod успешно проходит startup probe (<10 попыток) и readiness probe (<5 попыток).
  • После принудительного убийства процесса внутри контейнера (kill 1) под перезапускается K8s (RESTARTS > 0).
  • /ready возвращает 503 первые 3 секунды после старта, затем 200 (readiness delayed).
  • Service не направляет трафик на под, пока readiness probe не даст 200.
  • /deep проверяет реальную функциональность модели (синтетический запрос).
  • Все файлы (исходники, Dockerfile, k8s манифесты) версионированы в Git.

6. Ожидаемый результат

Основные артефакты

  • Репозиторий с кодом (FastAPI приложение).
  • Docker образ health-check-llm:latest.
  • Манифест deployment.yaml с probes.
  • Подтверждение (скриншот или лог), что kubectl describe pod показывает Liveness: success и Readiness: success.

Опциональные результаты

  • Дашборд Grafana с метриками проб.
  • Pipeline CI/CD (например, GitHub Actions), который автоматически разворачивает в kind и прогоняет health-check тесты.

7. Возможные сложности и их решение

СложностьРешение
Kind не видит локальный образИспользовать kind load docker-image <имя> перед apply
Startup probe затягивается, pod уходит в CrashLoopBackOffУвеличить initialDelaySeconds и failureThreshold для startupProbe
/ready постоянно 503 после стартаПроверить логи: возможно задержка >5 сек, увеличить initialDelaySeconds readinessProbe
Deep health check падает из-за тайм-аутаСделать timeout в 5 секунд на генерацию; если модель не отвечает — 503
В Windows/Mac Docker Desktop с K8s не включёнОтдельно установить minikube или kind

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Создание сервиса50 мин
Этап 2: Контейнеризация30 мин
Этап 3: Развёртывание + Probes1 ч 15 мин
Этап 4: Проверка сценариев40 мин
Этап 5 (опционально): Мониторинг20 мин
Итого~3 ч 35 мин

При первом выполнении рекомендуется заложить до 5 часов с учётом отладки.


9. Связанные вопросы из базы знаний

ВопросТема
12Как настроить liveness probe в Kubernetes?
45Какие порты открывать в Docker контейнере для FastAPI?
87Чем отличается readiness от liveness probe?
134Как эмулировать долгую загрузку модели при старте?
211Как симулировать сбой контейнера для теста?
278Что такое startup probe и когда её использовать?
345Как сделать deep health check для LLM?
512Как настроить Prometheus для мониторинга pod health?
678Как в kind загрузить локальный Docker образ?
799Как написать тест в pytest для health endpoints?

10. Чек-лист самопроверки

  • Я реализовал все три эндпоинта и протестировал их локально через curl.
  • Docker образ собирается и при запуске корректно отвечает на /health.
  • Манифест Deployment содержит все три probe с разумными задержками.
  • Я развернул приложение в локальном кластере (kind/minikube) и проверил статус через kubectl get pods.
  • Я принудительно убил процесс в контейнере и убедился, что pod перезапускается.