StatefulSet, PersistentVolume, Jobs и CronJobs

↑ Этап 5 · Kubernetes

Желательно~10 мин чтенияУровень: middle

Зачем это на собесе

Запуск БД и очередей в Kubernetes: нужно понимать StatefulSet, PV/PVC, Jobs и их ограничения.

StatefulSet

Контроллер для приложений с состоянием: в отличие от Deployment даёт

  • стабильные сетевые имена: db-0, db-1, db-2 (порядковые), сохраняются при пересоздании;
  • стабильное хранилище: у каждой реплики свой PVC (volumeClaimTemplates), привязывается к этой реплике;
  • упорядоченный запуск/остановка/обновление (podManagementPolicy: OrderedReady или Parallel);
  • headless Service для DNS каждого пода: db-0.db.clinic.svc.cluster.local.
apiVersion: v1
kind: Service
metadata: { name: pg }
spec: { clusterIP: None, selector: { app: pg }, ports: [{ port: 5432 }] }
---
apiVersion: apps/v1
kind: StatefulSet
metadata: { name: pg }
spec:
  serviceName: pg
  replicas: 3
  selector: { matchLabels: { app: pg } }
  updateStrategy: { type: RollingUpdate, rollingUpdate: { partition: 0 } }
  template:
    metadata: { labels: { app: pg } }
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: postgres
          image: postgres:17
          ports: [{ containerPort: 5432 }]
          env: [{ name: POSTGRES_PASSWORD, valueFrom: { secretKeyRef: { name: pg, key: password } } }]
          volumeMounts: [{ name: data, mountPath: /var/lib/postgresql/data }]
          readinessProbe: { exec: { command: ["pg_isready", "-U", "postgres"] } }
  volumeClaimTemplates:
    - metadata: { name: data }
      spec:
        accessModes: [ReadWriteOnce]
        storageClassName: fast-ssd
        resources: { requests: { storage: 100Gi } }

Особенности:

  • PVC не удаляются при удалении StatefulSet (защита данных; политика persistentVolumeClaimRetentionPolicy);
  • StatefulSet не настраивает репликацию сам: кластеризацию БД обеспечивает приложение/оператор;
  • partition в rolling update — поэтапное обновление (canary для stateful);
  • масштабирование вниз удаляет поды с наибольшим индексом.

Для БД в K8s лучше использовать операторы (CloudNativePG, Zalando Postgres Operator, Crunchy, Strimzi для Kafka, Redis Operator, ClickHouse Operator): они автоматизируют репликацию, failover, бэкапы, обновления. Альтернатива — управляемые БД вне кластера (часто надёжнее).

PersistentVolume и PersistentVolumeClaim

ОбъектРоль
PersistentVolume (PV)кусок хранилища в кластере (диск, NFS, облачный том), cluster-scoped
PersistentVolumeClaim (PVC)запрос приложения на хранилище (размер, режим доступа, класс)
StorageClass«профиль» для динамического создания PV (provisioner, тип диска, reclaimPolicy, volumeBindingMode, allowVolumeExpansion)
CSI-драйверплагин хранилища (AWS EBS, GCE PD, Ceph, Longhorn, OpenEBS, local-path)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: { name: fast-ssd }
provisioner: ebs.csi.aws.com
parameters: { type: gp3, iops: "6000" }
reclaimPolicy: Retain                      # Delete по умолчанию: удалит диск вместе с PVC
volumeBindingMode: WaitForFirstConsumer    # привязка после выбора ноды (учёт зоны!)
allowVolumeExpansion: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: uploads }
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: fast-ssd
  resources: { requests: { storage: 20Gi } }

Access modes: ReadWriteOnce (один узел), ReadOnlyMany, ReadWriteMany (несколько узлов: NFS, CephFS, EFS), ReadWriteOncePod (один под). Блочные диски облака — RWO и привязаны к зоне доступности: под с PVC может запускаться только в той же зоне.

reclaimPolicy: Retain (данные сохраняются после удаления PVC, ручная очистка) / Delete (том удаляется). Для критичных данных — Retain и снапшоты.

Расширение тома: увеличить spec.resources.requests.storage (при allowVolumeExpansion). VolumeSnapshot (CSI) — снимки и клонирование; бэкапы: Velero, Kasten, операторы БД.

Другие типы томов: emptyDir (временный, живёт с подом; medium: Memory для tmpfs), hostPath (каталог ноды: опасно), configMap/secret/projected, local PV (локальные NVMe).

Job и CronJob

Job — разовая задача «запустить до успешного завершения».

apiVersion: batch/v1
kind: Job
metadata: { name: migrate }
spec:
  backoffLimit: 2                   # число повторов при ошибке
  activeDeadlineSeconds: 600
  ttlSecondsAfterFinished: 3600     # автоудаление
  completions: 1
  parallelism: 1
  template:
    spec:
      restartPolicy: Never          # или OnFailure (Always для Job недопустимо)
      containers:
        - name: migrate
          image: registry.example.com/clinic/migrator:1.4.2
          envFrom: [{ secretRef: { name: db-migration } }]

Режимы: одиночный, параллельный с completions/parallelism, indexed (completionMode: Indexed, JOB_COMPLETION_INDEX), work queue.

CronJob — Job по расписанию:

apiVersion: batch/v1
kind: CronJob
metadata: { name: backup }
spec:
  schedule: "0 3 * * *"
  timeZone: "Europe/Moscow"
  concurrencyPolicy: Forbid          # Allow | Forbid | Replace — что делать при наложении
  startingDeadlineSeconds: 300       # окно запуска после пропуска
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  suspend: false
  jobTemplate:
    spec:
      backoffLimit: 1
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: registry.example.com/tools/pg-backup:1.0
              args: ["--dest", "s3://backups/pg"]

Подводные камни CronJob: часовой пояс (по умолчанию UTC/контроллер), идемпотентность (Job может запуститься дважды/пропуститься), concurrencyPolicy, мониторинг невыполненных/упавших (kube-state-metrics: kube_cronjob_status_last_successful_time, алерты), накопление завершённых подов (ttlSecondsAfterFinished), ресурсы и сетевые политики для задач, «слишком много пропущенных запусков» (100) блокирует CronJob.

DaemonSet

Под на каждой ноде (или подмножестве): агенты логов (Fluent Bit, Promtail), мониторинг (node-exporter), CNI, CSI-узловые драйверы, ingress на hostNetwork. Обновление по rolling, tolerations для master/taint-нод.

Практики для stateful

  • стабильная идентичность и хранилище нужны не всем — часто проще вынести состояние в управляемые сервисы;
  • антиaffinity по зонам/нодам для реплик; PDB maxUnavailable: 1;
  • бэкапы и проверка восстановления (VolumeSnapshot, Velero, PITR БД);
  • graceful shutdown с достаточным terminationGracePeriodSeconds;
  • не полагаться на hostPath/local без понимания привязки к ноде;
  • мониторинг заполнения томов (kubelet_volume_stats_*), алерты, авторасширение;
  • производительность диска (IOPS) и класс хранилища под нагрузку;
  • обновление Kubernetes/нод: drain с учётом PDB и перепривязкой томов;
  • тестировать сценарии: потеря ноды/зоны, восстановление из бэкапа.

Вопросы с ответами