Статьи

Базовая конфигурация Kubernetes для продакшен-нагрузок

Requests, пробы, disruption budgets, корректное завершение и безопасность подов: настройки, которые должны быть у продакшен-Deployment до первого инцидента.

Примеры обобщённые и иллюстративные. Они не взяты из среды какого-либо клиента: подстройте пороги и параметры под свою систему.

Зачем нужна базовая конфигурация

Kubernetes запускает почти что угодно почти без настроек, и в этом проблема: значения по умолчанию рассчитаны на то, чтобы начать, а не на то, чтобы не падать. Настройки ниже дёшевы в первый день и дорого обходятся, если их обнаруживают во время сбоя. Они относятся к Deployment без состояния; нагрузкам с состоянием нужно больше.

Requests и limits

Планировщик размещает поды по их requests, а Horizontal Pod Autoscaler считает утилизацию относительно них. Под без requests размещается вслепую и не может масштабироваться по CPU.

Память несжимаема: контейнер, превысивший лимит памяти, принудительно завершается. Задайте лимит памяти равным request или близким к нему, чтобы под никогда не планировался на память, которую не сможет удержать. С CPU иначе — контейнер выше лимита CPU троттлится, и это проявляется как задержка. Многие команды задают requests по CPU и не ставят лимит CPU для сервисов, чувствительных к задержке; что бы вы ни выбрали, измеряйте троттлинг.

Три пробы — три задачи

  • Readiness решает, получает ли под трафик. Осторожно с общими зависимостями: если все поды одновременно не пройдут readiness, у сервиса не останется ни одного endpoint.
  • Liveness решает, перезапускать ли контейнер. Ограничьте её самим процессом: если она проверяет базу данных, сбой базы перезапустит все поды сразу.
  • Startup придерживает остальные пробы, пока медленное приложение запускается, вместо того чтобы раздувать задержку liveness.

Пережить drain узлов и отказ зоны

Обновления узлов и автомасштабирование постоянно вытесняют поды. PodDisruptionBudget ограничивает, сколько реплик добровольное прерывание может остановить одновременно, а topology spread constraints распределяют реплики по разным зонам, чтобы отказ одной зоны не унёс весь сервис.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  maxUnavailable: 1
  selector:
    matchLabels: { app: api }

Корректное завершение

Когда под удаляется, его исключение из endpoints сервиса и остановка контейнеров (сначала хук preStop, затем SIGTERM) начинаются одновременно. Приложение, которое завершается сразу, теряет запросы, всё ещё направляемые к нему. Добавьте короткую паузу в preStop, чтобы балансировщики успели обновиться, обрабатывайте SIGTERM, завершая текущую работу, и держите terminationGracePeriodSeconds больше, чем пауза плюс время дренажа.

Контекст безопасности и образы

Запускайте от непривилегированного пользователя, отключите все Linux capabilities, запретите повышение привилегий и монтируйте корневую файловую систему только на чтение; Pod Security Standard restricted обеспечивает большую часть этого на уровне namespace. Ссылайтесь на образы по digest, а не по изменяемому тегу, чтобы работало ровно то, что тестировалось.

Всё вместе

Deployment с применённой базовой конфигурацией. Значения условные: подбирайте requests по измеренному потреблению, а не по этому примеру.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      terminationGracePeriodSeconds: 45
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels: { app: api }
      securityContext:
        runAsNonRoot: true
        seccompProfile: { type: RuntimeDefault }
      containers:
        - name: api
          image: registry.example.com/api@sha256:<digest>
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits: { memory: 256Mi }
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 10
            failureThreshold: 3
          startupProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 5
            failureThreshold: 30
          lifecycle:
            preStop:
              sleep: { seconds: 5 }   # native sleep action, Kubernetes 1.30+
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities: { drop: ["ALL"] }
Контакты

Нужно второе мнение о вашей системе?

Пришлите короткое описание системы. Опытный инженер ответит письменно в течение рабочего дня.

Обсудить инфраструктуру