When a Kubernetes cluster’s nodes are drained for maintenance (e.g. kubectl drain, cluster-autoscaler
node consolidation, or a managed node-group upgrade on EKS/GKE/AKS), the node’s pods are evicted via the
Kubernetes Eviction API. Without any
protection, draining multiple nodes in parallel can evict multiple replicas of the same PhoenixAI component
(FE/CN/FE Proxy) at once, breaking FE quorum or dropping CN capacity and causing a service outage.
Starting from this version, the operator automatically creates a
PodDisruptionBudget (PDB) for each of the
FE, CN, and FE Proxy components, named <component-statefulset-or-deployment-name>-pdb
(e.g. phoenixaicluster-sample-fe-pdb). The PDB sets maxUnavailable: 1, meaning the Kubernetes API server
will reject (and kubectl drain will retry/wait on) any voluntary eviction that would take down more than
one replica of that component at a time — regardless of how many nodes are being drained concurrently.
The behavior is controlled by the operator flag --enable-pod-disruption-budget:
phoenixAIOperator.enablePodDisruptionBudget: true (the chart’s RBAC already includes the required
policy/poddisruptionbudgets permissions). Set it to false to turn the feature off; the change rolls
only the operator pod, never FE/CN.operator.yaml users: the released operator.yaml ships with the flag enabled and the matching
RBAC rule, so the default experience matches Helm.policy/poddisruptionbudgets permissions still reconciles cleanly.
This is the recommended setting while migrating an installation whose RBAC has not been updated yet.Note: turning the flag off after it has been on does not delete the PDBs the operator already created
(with the feature off it deliberately never touches that API). Remove them manually if you no longer want
them: kubectl delete pdb <name>-fe-pdb <name>-cn-pdb -n <namespace>.
This is created once and never overwritten: if a PDB with that name already exists — whether from a previous reconcile, or supplied directly by you — the operator leaves it alone. This means:
maxUnavailable for a big CN cluster, or a
quorum-aware minAvailable for FE), just create your own PodDisruptionBudget under the same name before
the operator does (or edit the operator-created one afterwards) — the operator will never revert your
changes. If you’re using the Helm chart, the generic resources: [] value
(see values.yaml) is a convenient place to declare a
custom PDB alongside the rest of your release.Because the PDB will make kubectl drain block/retry rather than immediately evict a pod that would
violate it, keep the following in mind during a Kubernetes node upgrade:
kubectl drain --timeout=<N> and treat a
Cannot evict pod as it would violate the pod's disruption budget message as an expected signal to wait,
not an error to work around.kubectl drain --disable-eviction (or otherwise force-delete pods) to “unblock” a drain —
this bypasses the PDB entirely and reintroduces the exact simultaneous-eviction risk the PDB protects
against.A PDB limits how many replicas can be evicted at once, but it doesn’t control where replicas are
scheduled in the first place. If multiple replicas of the same component happen to land on the same node,
draining that single node can still be as disruptive as the PDB allows. For additional protection, consider
setting a soft topology spread constraint (or pod anti-affinity) on components you want to spread across
nodes/zones, using the existing topologySpreadConstraints field already available on each component spec
(FE shown, the same field exists on phoenixAICnSpec and phoenixAIFeProxySpec):
apiVersion: phoenixdata.ai/v1
kind: PhoenixAICluster
metadata:
name: phoenixaicluster-sample
spec:
phoenixAIFeSpec:
replicas: 3
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
# ScheduleAnyway is a soft constraint: it's honored best-effort and never leaves pods
# unschedulable, unlike DoNotSchedule, which can strand pods Pending on small clusters.
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/component: fe
The operator does not set this by default, because a hard topology constraint
(whenUnsatisfiable: DoNotSchedule) can make pods Pending forever on clusters with fewer nodes than
replicas (e.g. small trial or dev clusters) — a worse outcome than the problem it’s meant to prevent. Using
ScheduleAnyway gets you the spreading benefit without that risk.