Модуль 1. Базовая теория Kubernetes для дата-нагрузок
Картина целиком
В BI/DWH мы обычно запускаем два типа нагрузок:
- Стейтлес: веб-интерфейсы BI, оркестраторы, воркеры ETL/ELT, REST-сервисы.
- Стейтфул: СУБД (Postgres/ClickHouse), брокеры/кластерные движки (Kafka/Trino), файловые/объектные хранилища.
В Kubernetes эти типы решаются разными примитивами:
- Стейтлес → Deployment + Service + HPA.
- Стейтфул → StatefulSet + PVC (через CSI) + PDB/топология.
Дальше — по порядку.
Pod — атом выполнения
Что это: контейнер(ы) + сеть/файловая система + спецификация ресурсов. Минимальная единица, которая запускается и умирает.
Ключевые моменты для данных:
- Один Pod = один IP, общий loopback для всех контейнеров в Pod (sidecar-паттерны для логов/прокси/бэкапов).
- Liveness/Readiness/Startup пробы обязательны для корректных обновлений и балансировки.
- Поды не «вечно живут»: планируйте устойчивость на уровне контроллеров (Deployment/StatefulSet).
Мини-пример Pod c readiness/liveness:
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
spec:
containers:
- name: app
image: ghcr.io/nginxinc/nginx-unprivileged:stable-alpine
ports:
- containerPort: 8080
readinessProbe:
httpGet: { path: /, port: 8080 }
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet: { path: /, port: 8080 }
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
Deployment — стейтлес-контроллер
Что это: контроллер, который управляет набором идентичных Pod’ов (реплик) и обновляет их «по-живому».
Важно для BI/ETL:
- RollingUpdate без простоя при корректных readiness-пробах.
- Легко масштабировать вручную или через HPA.
- Хорош для веб-слоёв BI, оркестрации, API.
Пример Deployment + ClusterIP Service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deploy
spec:
replicas: 3
selector: { matchLabels: { app: web } }
template:
metadata: { labels: { app: web } }
spec:
containers:
- name: web
image: ghcr.io/nginxinc/nginx-unprivileged:stable-alpine
ports: [{ containerPort: 8080 }]
readinessProbe: { httpGet: { path: "/", port: 8080 }, initialDelaySeconds: 3 }
resources:
requests: { cpu: "200m", memory: "256Mi" }
limits: { cpu: "1", memory: "512Mi" }
---
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
selector: { app: web }
ports: [{ port: 80, targetPort: 8080 }]
type: ClusterIP
StatefulSet — состояние, упорядоченность, персистентность
Что это: контроллер для приложений со состоянием: фиксированные имена Pod’ов (web-0, web-1), предсказуемый порядок запуска/обновления, привязанные диски.
Для СУБД и шардов:
- Каждый Pod имеет собственный PVC по шаблону.
- Можно задавать PodManagementPolicy (Parallel/OrderedReady).
- Важны PodDisruptionBudget и TopologySpreadConstraints.
Пример StatefulSet PostgreSQL (упрощённо):
apiVersion: v1
kind: Service
metadata:
name: pg-headless
spec:
clusterIP: None
selector: { app: pg }
ports: [{ name: pg, port: 5432, targetPort: 5432 }]
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: pg
spec:
serviceName: pg-headless
replicas: 1
selector: { matchLabels: { app: pg } }
template:
metadata: { labels: { app: pg } }
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports: [{ containerPort: 5432, name: pg }]
env:
- { name: POSTGRES_DB, value: demo }
- { name: POSTGRES_USER, value: demo }
- { name: POSTGRES_PASSWORD, valueFrom: { secretKeyRef: { name: pg-secret, key: password } } }
volumeMounts:
- { name: pgdata, mountPath: /var/lib/postgresql/data }
resources:
requests: { cpu: "500m", memory: "1Gi" }
limits: { cpu: "2", memory: "2Gi" }
volumeClaimTemplates:
- metadata: { name: pgdata }
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 10Gi } }
storageClassName: standard
Для HA используйте Patroni/операторы, отдельные пулы нод и бэкапы.
Service — стабильная точка доступа
Что это: виртуальный IP/имя для группы Pod’ов (через селектор). Типы: ClusterIP, NodePort, LoadBalancer, Headless (clusterIP: None).
Практика:
- Для внутренних API и БД — ClusterIP/Headless.
- Для внешнего трафика — Ingress/Gateway (см. ниже).
Ingress и Gateway API — публикация наружу
Ingress — правила L7 (HTTP/HTTPS), требуются Ingress-контроллеры (часто NGINX, HAProxy).
Gateway API — более новый стандарт (Gateway/GatewayClass/HTTPRoute/TCPRoute), гибче для сложных топологий и мульти-тенантности.
Пример Ingress (NGINX):
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: web.local
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: web-svc, port: { number: 80 } } }
Для prod: TLS-терминация, mTLS внутри, rate limit, WAF-аннотации.
PVC/CSI — персистентные тома
PVC (PersistentVolumeClaim) — запрос диска; PV — реализация; CSI — интерфейс к сториджу (Ceph/Rook, NFS, локальные диски, облачные диски).
Режимы доступа: RWO (одна нода), RWX (несколько нод), ROX (read-only many).
Замечания для данных:
- Журналы БД — на быстрых локальных дисках/NVMe; реплики/снепшоты — на сетевом сторидже.
- Для lakehouse-слоёв используйте объектное S3 (MinIO/Ceph RGW).
Пример статического PV+PVC (универсально даже без динамики):
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
spec:
capacity: { storage: 10Gi }
accessModes: ["ReadWriteOnce"]
hostPath: { path: /mnt/k8s/pv1 }
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-hostpath
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 10Gi } }
volumeName: pv-hostpath
Requests/Limits и QoS-классы
Requests — «минимум» ресурсов для планировщика; Limits — «потолок».
От них зависят QoS-классы:
- Guaranteed — requests=limits для CPU и RAM → минимальный риск вытеснения.
- Burstable — есть requests, но ниже limits (типично для веб/ETL).
- BestEffort — вообще без requests/limits (в проде не используем).
Практический эффект:
- Слишком низкие requests → Pod может оказаться на перегруженной ноде → throttling и время ETL растёт.
- Слишком высокие → низкая утилизация кластера.
HPA/VPA — авто-скейлинг
- HPA (Horizontal Pod Autoscaler) масштабирует число Pod’ов по метрикам (CPU/RAM/кастомные, очередь Kafka, latency). Нужен metrics-server и/или адаптеры (KEDA).
- VPA (Vertical Pod Autoscaler) рекомендует/меняет requests/limits (на проде применяют осторожно, чаще — режим «recommendation» с ручным апдейтом).
Пример HPA по CPU:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-deploy
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Node Pools, Taints/Tolerations, Affinity
Node pool — логическая группа нод (отдельные инстансы/лейблы) под конкретные нагрузки: pool=stateless, pool=database, pool=etl.
Taints/Tolerations — механизм «не пускать кого попало».
- Пример: пометить БД-ноды db=true:NoSchedule, а Pod БД дать toleration — остальные туда не поедут.
Affinity/Anti-Affinity — управление размещением:
- NodeAffinity: «ставь на pool=etl».
- PodAntiAffinity: «не клади две реплики рядом».
Пример taint и toleration:
# на ноде для БД:
kubectl taint nodes node-db pool=db:NoSchedule
spec:
tolerations:
- key: "pool"
operator: "Equal"
value: "db"
effect: "NoSchedule"
nodeSelector:
pool: "db"
Классы QoS и эвикшены: как не потерять SLA
- Под угрозой эвикшена в первую очередь BestEffort, затем Burstable, последними — Guaranteed.
- Для критичных сервисов (координатор Trino, primary Postgres) стремимся к Guaranteed, указываем PDB, включаем TopologySpreadConstraints.
Практика (лабораторные)
Ниже — «скелет» практики. Команды и манифесты даны так, чтобы их можно было выполнить локально (kind) или перенести в тестовый кластер. Если нет динамического сториджа — используйте статические PV/PVC (пример выше).
Лаб 1. Кластер kind на 3 ноды (1 control-plane + 2 worker)
- Установите kind и kubectl.
- Создайте кластер:
cat > kind-3n.yaml <<'EOF' kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker - role: worker EOF kind create cluster --name dataplatform --config kind-3n.yaml kubectl get nodes -o wide
- (Опционально) Установите metrics-server и Ingress-контроллер (NGINX), чтобы работал HPA и Ingress.
Лаб 2. Деплой тестового веб-сервиса (Deployment + Service + Ingress)
- Примените манифесты из разделов 2/4/5 (Deployment/Service/Ingress).
- Пропишите в /etc/hosts запись web.local на адрес ingress-контроллера (или используйте NodePort).
- Проверьте:
kubectl get deploy,svc,ingress kubectl port-forward svc/web-svc 8080:80 # если без ingress curl -I http://localhost:8080
- Нагрузите сервис (k6/hey/ab) и посмотрите метрики CPU.
Лаб 3. Деплой stateful БД (Postgres) с PVC
- Создайте Secret с паролем:
kubectl create secret generic pg-secret --from-literal=password='P@ssw0rd!'
- Если нет динамического сториджа — создайте PV/PVC (пример «pvc-hostpath»).
- Примените StatefulSet + Headless Service (пример в разделе 3).
- Проверьте:
kubectl get sts,pods,pvc,pv kubectl exec -it pg-0 -- psql -U demo -d demo -c "select version();"
Лаб 4. Включаем HPA
- Убедитесь, что metrics-server установлен.
- Примените HPA (раздел 8).
- Нагрузите веб-сервис → наблюдайте масштабирование kubectl get hpa -w.
Лаб 5. Node pools и taints
- Пометьте одну из нод как «db»:
kubectl label nodes $(kubectl get nodes -o name | sed -n '2p' | cut -d'/' -f2) pool=db kubectl taint nodes $(kubectl get nodes -o name | sed -n '2p' | cut -d'/' -f2) pool=db:NoSchedule
- Добавьте nodeSelector + tolerations в StatefulSet Postgres и kubectl apply -f.
- Убедитесь, что Pod БД встал на нужную ноду:
kubectl get pod -o wide
Лаб 6. QoS и ресурсы
- В веб-Deployment уменьшите requests и посмотрите, как планировщик начнет плотнее размещать Pod’ы.
- Искусственно перегрузите ноду — понаблюдайте throttling (kubectl top pods, события эвикшенов).
Риски и как их избежать (в контексте примитивов)
-
Неверные readiness-пробы → выкат «в пустоту», 502/таймауты.
- Решение: корректные probe, startupProbe для холодного старта; выдержанные maxUnavailable.
- Отсутствие PDB/топологии → массовый ребаланс при обновлениях нод.
- Решение: PodDisruptionBudget, TopologySpreadConstraints, плановые окна.
- Решение: профилировать, фиксировать базовые requests, включать HPA; для критичных — QoS Guaranteed.
- Решение: валидировать StorageClass (тип дисков, IOPS), делить журналы/данные, RWX — только когда оправдано.
- Решение: metrics-server, кастомные метрики (очереди, latency), KEDA по событиям.
- Решение: отдельные пулы нод, taints/tolerations, node affinity.
- Ошибочные Requests/Limits → либо перерасход, либо throttling и OOMKill.
- Неучтенный сторидж → RWO на нескольких репликах, тонкие PV, медленный backend.
- HPA без метрик/адаптеров → «слепое» масштабирование.
- Смешивание стейтфул/стейтлес на одних нодах → конкуренция за IO.
Что запомнить
- Deployment для стейтлес, StatefulSet для стейтфул.
- Service — единственная точка входа в набор Pod’ов; наружу — через Ingress/Gateway.
- PVC/CSI — фундамент для данных; планируйте диски как архитектуру, а не «после».
- Requests/Limits задают судьбу Pod’ов (QoS, планирование, эвикшены).
- HPA/VPA дают экономию и SLA, но только при корректных метриках.
- Node pools + taints/affinity — реальный контроль размещения.



