Оригинал: CloudNativePG Recipe 11 — Isolating PostgreSQL Workloads in Kubernetes with Kind — 16 августа 2024.
Продолжение Рецепта 10: taints, tolerations и pod anti-affinity для полной изоляции PostgreSQL в Kubernetes.
В Рецепте 10 мы настроили многонодовый Kind-кластер с выделенными PostgreSQL-узлами через node labels и nodeSelector. Но одних меток недостаточно — они направляют PostgreSQL на нужные узлы, но не запрещают другим нагрузкам приземляться на те же узлы.
В этой статье решаем две задачи:
- Запретить любым не-PostgreSQL нагрузкам запускаться на postgres-узлах.
- Гарантировать, что инстансы одного PostgreSQL-кластера распределены по разным узлам.
Для этого используем taint node-role.kubernetes.io/postgres и pod anti-affinity.
Прежде чем начать
Ознакомьтесь с Рецептом 10 — он даёт фундамент для этого руководства.
Taints на многонодовом кластере
Расширим конфигурацию Kind: к каждому postgres-узлу добавим taint через kubeadmConfigPatches:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: multi-node-taints-template
nodes:
# Control Plane
- role: control-plane
# Инфраструктура / Приложения
- role: worker
labels:
infra.node.kubernetes.io:
- role: worker
labels:
app.node.kubernetes.io:
# PostgreSQL-узлы (3)
- role: worker
labels:
postgres.node.kubernetes.io:
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
taints:
- key: node-role.kubernetes.io/postgres
effect: NoSchedule
- role: worker
labels:
postgres.node.kubernetes.io:
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
taints:
- key: node-role.kubernetes.io/postgres
effect: NoSchedule
- role: worker
labels:
postgres.node.kubernetes.io:
kubeadmConfigPatches:
- |
kind: JoinConfiguration
nodeRegistration:
taints:
- key: node-role.kubernetes.io/postgres
effect: NoSchedule
# Запасной узел
- role: workerОбратите внимание на дополнительный «запасной» узел — его назначение описано ниже.
Создаём кластер:
kind create cluster --config multi-node-taints-template.yaml --name cnpgНазначаем метки ролей:
kubectl label node -l postgres.node.kubernetes.io node-role.kubernetes.io/postgres=
kubectl label node -l infra.node.kubernetes.io node-role.kubernetes.io/infra=
kubectl label node -l app.node.kubernetes.io node-role.kubernetes.io/app=Проверка:
kubectl get nodesNAME STATUS ROLES AGE VERSION
cnpg-control-plane Ready control-plane 5m14s v1.35.0
cnpg-worker Ready infra 5m1s v1.35.0
cnpg-worker2 Ready app 5m1s v1.35.0
cnpg-worker3 Ready postgres 5m v1.35.0
cnpg-worker4 Ready postgres 5m1s v1.35.0
cnpg-worker5 Ready postgres 5m1s v1.35.0
cnpg-worker6 Ready <none> 5m1s v1.35.0Проверяем taint на postgres-узле:
kubectl describe node cnpg-worker5 | grep '^Taints'Изоляция CNPG-кластеров на postgres-узлах
Цели:
- PostgreSQL запускается только на postgres-узлах — nodeSelector (как в Рецепте 10).
- На postgres-узлах ничего кроме PostgreSQL — taint
NoSchedule+ toleration в манифесте кластера. - Каждый инстанс одного кластера — на отдельном узле — pod anti-affinity с
topologyKey: kubernetes.io/hostname.
Всё это выражается в .spec.affinity:
affinity:
nodeSelector:
node-role.kubernetes.io/postgres: ""
tolerations:
- key: node-role.kubernetes.io/postgres
operator: Exists
effect: NoSchedule
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname
podAntiAffinityType: requiredЗапуск
Устанавливаем оператор CloudNativePG:
curl -sSfL \
https://raw.githubusercontent.com/cloudnative-pg/artifacts/main/manifests/operator-manifest.yaml | \
kubectl apply --server-side -f -Убеждаемся, что оператор запустился не на postgres-узле (taints работают):
kubectl get pods -A -o wideСоздаём PostgreSQL-кластер:
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example
spec:
instances: 3
storage:
size: 1Gi
affinity:
nodeSelector:
node-role.kubernetes.io/postgres: ""
tolerations:
- key: node-role.kubernetes.io/postgres
operator: Exists
effect: NoSchedule
enablePodAntiAffinity: true
topologyKey: kubernetes.io/hostname
podAntiAffinityType: requiredkubectl apply -f cluster-example.yamlПроверяем размещение:
kubectl get pods -o wideNAME READY STATUS RESTARTS AGE IP NODE ...
cluster-example-1 1/1 Running 0 3m12s 10.244.7.4 cnpg-worker3 ...
cluster-example-2 1/1 Running 0 2m34s 10.244.1.4 cnpg-worker5 ...
cluster-example-3 1/1 Running 0 113s 10.244.4.4 cnpg-worker4 ...Все три инстанса — на разных postgres-узлах. Изоляция работает.
Проверка anti-affinity
Масштабируем до 4 инстансов:
kubectl scale cluster --replicas 4 cluster-exampleЧетвёртый под зависнет в Pending — свободных postgres-узлов нет, а anti-affinity запрещает размещение двух инстансов на одном узле:
kubectl describe pod cluster-example-4Это ожидаемое поведение — подтверждение корректности настройки.
Что дальше?
Два варианта выхода из ситуации:
Добавление нового узла для PostgreSQL
Используем запасной узел cnpg-worker6. Назначаем ему taint и label:
kubectl taint node cnpg-worker6 node-role.kubernetes.io/postgres=:NoSchedule
kubectl label node cnpg-worker6 node-role.kubernetes.io/postgres=После этого четвёртый инстанс будет успешно создан на cnpg-worker6.
Откат к трём инстансам
Если добавлять узел не нужно:
kubectl scale cluster --replicas 3 cluster-example
kubectl delete job cluster-example-4-join
kubectl delete pvc cluster-example-4Кластер вернётся в здоровое состояние.
Заключение
Комбинация taints + tolerations + pod anti-affinity обеспечивает полную изоляцию PostgreSQL-нагрузок:
- На postgres-узлах работает только PostgreSQL.
- Каждый инстанс кластера размещён на отдельном физическом узле — отказ одного узла не затрагивает кворум.
- Оператор CloudNativePG гарантированно запущен вне postgres-узлов, что исключает конфликт при failover.
Такой подход реализует чёткое разделение ответственности: DBA управляют ресурсами Cluster (nodeSelector, tolerations, anti-affinity), а инфраструктурная команда — узлами (labels, taints). Разделение упрощает управление и способствует сотрудничеству.
Благодаря портативности Kubernetes эти методы применимы к любой платформе — частной, публичной, гибридной или managed.