Урок 06 · Kubernetes на практике

Health-пробы

Что-то не запустилось или осталось непонятным? Задайте вопрос в комментариях к разбору этого урока в канале — разберёмся вместе.

В уроке 3 ReplicaSet чинил Pod, только когда тот исчезал целиком. Но что если процесс внутри контейнера жив, отвечает на TCP-уровне, а сам при этом завис намертво или ещё не успел прогреться после старта? Снаружи для Kubernetes это выглядит как совершенно здоровый Pod. /health-эндпоинт, который вы добавили ещё в уроке 1 «на будущее», — и есть ответ на эту проблему: он даёт кластеру способ спросить приложение напрямую, а не гадать по факту существования процесса.

Liveness и readiness — разные пробы, разные действия

Пробы бывают нескольких видов, но в этом уроке важны две, и путать их — самая частая ошибка новичков:

  • Liveness-проба отвечает на вопрос «процесс вообще жив?». Если несколько проверок подряд проваливаются, kubelet перезапускает контейнер внутри того же Pod'а — это отдельный, более быстрый механизм самовосстановления, чем пересоздание всего Pod'а через ReplicaSet из урока 3.
  • Readiness-проба отвечает на другой вопрос — «готов ли Pod принимать трафik прямо сейчас?». Если она проваливается, никто ничего не перезапускает — Kubernetes просто временно убирает Pod из списка адресов Service (урок 4), пока проба снова не станет успешной.

Итого: liveness лечит зависшие процессы перезапуском, readiness защищает пользователей от Pod'а, который жив, но не готов отвечать — например, ещё не подключился к базе при старте.

Схема: один и тот же провал /health приводит к двум разным реакциям — перезапуску от liveness и исключению из Service от readiness POD /health HTTP 500 сломан вручную LIVENESS ПРОВАЛЕНА kubelet перезапускает контейнер через несколько неудач подряд READINESS ПРОВАЛЕНА Pod исключён из Service контейнер продолжает жить
Один и тот же неудачный ответ /health запускает две независимые реакции с разной скоростью и разными последствиями.
Источник Официальная документация — Kubernetes Docs: Configure Liveness, Readiness and Startup Probes

Практика: сломайте сервис руками и понаблюдайте за реакцией

Понадобится Deployment из урока 3 (2 реплики) и Service из урока 4.

  1. Обновите main.py из урока 1: добавьте флаг, который можно включить снаружи, и эндпоинт, который его переключает.
    from fastapi import FastAPI, HTTPException
    
    app = FastAPI()
    
    UNHEALTHY = False
    
    @app.get("/health")
    def health():
        if UNHEALTHY:
            raise HTTPException(status_code=500, detail="сломано специально")
        return {"status": "ok"}
    
    @app.post("/break")
    def break_health():
        global UNHEALTHY
        UNHEALTHY = True
        return {"status": "теперь /health будет отвечать ошибкой"}

    Первую строку и функцию app = FastAPI() замените целиком — остальной код (/ и старый /health) из урока 1 не трогайте, просто допишите тело /health и новый эндпоинт /break.

  2. Пересоберите образ и загрузите его в minikube:
    docker build -t hello-fastapi:0.3 .
    minikube image load hello-fastapi:0.3
  3. В deployment.yaml обновите тег образа на 0.3 и добавьте обе пробы к контейнеру:
    containers:
      - name: hello-fastapi
        image: hello-fastapi:0.3
        ports:
          - containerPort: 8000
        envFrom:
          - configMapRef:
              name: hello-fastapi-config
          - secretRef:
              name: hello-fastapi-secret
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5
          failureThreshold: 1

    Обе пробы стучатся в один и тот же /health, но с разным failureThreshold: readiness реагирует на первую же неудачу, liveness ждёт три подряд — поэтому их эффекты будет видно по отдельности, с разницей в несколько секунд.

  4. Примените манифест и дождитесь обновления:
    kubectl apply -f deployment.yaml
    kubectl rollout status deployment/hello-fastapi
  5. Получите адрес сервиса, как в уроке 4, и сломайте здоровье одной из двух реплик:
    URL=$(minikube service hello-fastapi --url)
    curl -X POST "$URL/break"

    Реплик две, а Service выбирает, кому отдать запрос, — так что /break с большой вероятностью попадёт только в одну из них. Вторая продолжит как ни в чём не бывало.

Понаблюдайте за обоими эффектами Сразу после /break запустите:
kubectl get pods --watch
Через несколько секунд у одного из Pod'ов READY кратковременно станет 0/1 — это сработала readiness-проба, и Service перестал слать туда трафик. Ещё через несколько секунд у того же Pod'а вырастет счётчик RESTARTS, а READY вернётся к 1/1 — это уже liveness: kubelet перезапустил контейнер, и вместе с новым процессом флаг UNHEALTHY сам сбросился в исходное состояние. Остановите наблюдение сочетанием Ctrl+C.
Лайфхак Не делайте liveness-пробу слишком «умной» — не проверяйте в ней соединение с базой данных или сторонним API. Если внешняя зависимость на минуту зависнет, liveness начнёт валиться у всех ваших Pod'ов одновременно, и kubelet зациклится в перезапусках процесса, который сам по себе ни в чём не виноват. Liveness должен проверять только сам процесс; связи с внешним миром — забота readiness или отдельной логики приложения.

Проверьте себя

Readiness-проба несколько раз подряд возвращает ошибку. Что произойдёт?

Напишите команду, которая применит обновлённый deployment.yaml с пробами:

Напишите команду, которая будет непрерывно показывать изменения статуса Pod'ов в реальном времени: