Skip to content

feat: add optional PodDisruptionBudget for vector aggregators - #229

Merged
aa1ex merged 2 commits into
kaasops:mainfrom
stigglor:feat/aggregator-pdb
Jul 2, 2026
Merged

feat: add optional PodDisruptionBudget for vector aggregators#229
aa1ex merged 2 commits into
kaasops:mainfrom
stigglor:feat/aggregator-pdb

Conversation

@stigglor

@stigglor stigglor commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Adds an opt-in PodDisruptionBudget for VectorAggregator and ClusterVectorAggregator, configured per aggregator via spec.podDisruptionBudget.

podDisruptionBudget:
  enabled: false
  maxUnavailable: 1              # or minAvailable
  unhealthyPodEvictionPolicy: AlwaysAllow

Disabled by default, so upgrades leave existing aggregators untouched. unhealthyPodEvictionPolicy defaults to AlwaysAllow so unhealthy pods cannot block a node drain. The budget is removed when disabled or scaled to one replica.

Closes #228

@stigglor
stigglor marked this pull request as ready for review June 25, 2026 06:09
@aa1ex

aa1ex commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Hi, and thanks for the contribution!

The HPA autoscaling work (#230) just merged, so this PR now conflicts with main and needs a rebase. Most of it is mechanical (shared RBAC and the aggregator controller wiring).

One real change beyond the textual conflicts: spec.replicas is now a *int32 (it was int32), so the replicas <= 1 check in pdb.go needs a small update for the pointer. One more thing worth handling: when autoscaling is on, the replica count is set by the HPA, not by spec.replicas, so that same check can skip the PDB for an aggregator that is actually running several replicas. Happy to help with that part if useful.

What do you think?

The operator now creates a PodDisruptionBudget for VectorAggregator and
ClusterVectorAggregator deployments when more than one replica is expected.
The budget keeps at most one pod unavailable during voluntary disruptions
such as node drains. When an aggregator runs a single replica or is scaled
back down, the operator removes any budget it previously created.

When autoscaling is enabled the effective replica count comes from the HPA,
so the decision uses spec.autoscaling.maxReplicas; otherwise it uses the
static spec.replicas (now a *int32), treating an unset value as one replica.

Both aggregator types share the same controller, so one reconcile step
covers them. Operator RBAC gains permission to manage poddisruptionbudgets
in the policy API group, applied in the kubebuilder markers, the generated
role, and the helm clusterrole.

Closes kaasops#228
@stigglor
stigglor force-pushed the feat/aggregator-pdb branch from af287d0 to 0b6a2fc Compare June 30, 2026 03:19
@stigglor

Copy link
Copy Markdown
Contributor Author

Hi, and thanks for the contribution!

The HPA autoscaling work (#230) just merged, so this PR now conflicts with main and needs a rebase. Most of it is mechanical (shared RBAC and the aggregator controller wiring).

One real change beyond the textual conflicts: spec.replicas is now a *int32 (it was int32), so the replicas <= 1 check in pdb.go needs a small update for the pointer. One more thing worth handling: when autoscaling is on, the replica count is set by the HPA, not by spec.replicas, so that same check can skip the PDB for an aggregator that is actually running several replicas. Happy to help with that part if useful.

What do you think?

Cool, thanks for the info. I've fixed up the PR regarding those points you mentioned. Lmk if the HPA check is as you expected.

@aa1ex

aa1ex commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

The autoscaling check is as I expected: maxReplicas when autoscaling is on, otherwise replicas (unset = 1). The rebase and *int32 are clean.

One thing before merge. With maxUnavailable: 1, one not-Ready pod drives disruptionsAllowed to 0, so the remaining Ready pod can't be evicted and draining its node never completes while the other replica stays unhealthy. Because the PodDisruptionBudget is created by default, every existing aggregator with more than one effective replica (replicas > 1, or autoscaling maxReplicas > 1) gets one on the next operator upgrade. The problem surfaces later, the first time someone drains that node.

The #228 use case is fine; I'd make it safe by default:

  • Put it behind a default-off flag (--enable-aggregator-pdb, like --enable-config-optimization) so an upgrade doesn't change existing aggregators.
  • Let maxUnavailable be configurable instead of hardcoded 1 (the follow-up you noted in Include PodDisruptionBudget for VectorAggregator and ClusterVectorAggregator #228).
  • Set unhealthyPodEvictionPolicy: AlwaysAllow so not-Ready pods stay evictable regardless of the budget. This only helps once the unhealthy replicas already exceed the budget (with two replicas, when both are unhealthy); it doesn't unblock the case above, where the blocked pod is the Ready one.

@stigglor

stigglor commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

The autoscaling check is as I expected: maxReplicas when autoscaling is on, otherwise replicas (unset = 1). The rebase and *int32 are clean.

One thing before merge. With maxUnavailable: 1, one not-Ready pod drives disruptionsAllowed to 0, so the remaining Ready pod can't be evicted and draining its node never completes while the other replica stays unhealthy. Because the PodDisruptionBudget is created by default, every existing aggregator with more than one effective replica (replicas > 1, or autoscaling maxReplicas > 1) gets one on the next operator upgrade. The problem surfaces later, the first time someone drains that node.

The #228 use case is fine; I'd make it safe by default:

  • Put it behind a default-off flag (--enable-aggregator-pdb, like --enable-config-optimization) so an upgrade doesn't change existing aggregators.
  • Let maxUnavailable be configurable instead of hardcoded 1 (the follow-up you noted in Include PodDisruptionBudget for VectorAggregator and ClusterVectorAggregator #228).
  • Set unhealthyPodEvictionPolicy: AlwaysAllow so not-Ready pods stay evictable regardless of the budget. This only helps once the unhealthy replicas already exceed the budget (with two replicas, when both are unhealthy); it doesn't unblock the case above, where the blocked pod is the Ready one.

Thanks, that's a fair concern. Making it default-on was the wrong call for exactly the drain scenario you describe. I'd like to fold all three points into a single podDisruptionBudget block on the aggregator spec rather than a global flag:

podDisruptionBudget:
  enabled: false
  maxUnavailable: 1
  unhealthyPodEvictionPolicy: AlwaysAllow

enabled defaults to false, so upgrades don't touch existing aggregators. maxUnavailable (or minAvailable) becomes configurable, and unhealthyPodEvictionPolicy defaults to AlwaysAllow. It keeps the PDB config in one place and gives per-aggregator control, which felt cleaner than a global flag that still couldn't carry maxUnavailable.

Agreed that none of this fully removes the blocked-drain case, since that's really what a PDB is for. This just makes it opt-in and gives an escape hatch.

Happy to go with a --enable-aggregator-pdb flag instead if you'd rather match the config-optimization convention (this also prevents a CR change, which I originally called out trying to prevent when logging the issue). Let me know which you prefer and I'll push it.

@aa1ex

aa1ex commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Let's go with the block over a flag, that's the cleaner fit.

Move the budget behind spec.podDisruptionBudget so upgrades leave existing
aggregators untouched. The budget is created only when enabled and the
effective replica count is above one, and is removed otherwise.

Make minAvailable and maxUnavailable configurable, defaulting to
maxUnavailable of 1, and default unhealthyPodEvictionPolicy to AlwaysAllow
so not-Ready pods stay evictable and cannot block a node drain.
@stigglor stigglor changed the title feat: add PodDisruptionBudget for vector aggregators with replicas > 1 feat: add optional PodDisruptionBudget for vector aggregators Jul 2, 2026
@aa1ex aa1ex self-assigned this Jul 2, 2026

@aa1ex aa1ex left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@aa1ex
aa1ex merged commit a509011 into kaasops:main Jul 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include PodDisruptionBudget for VectorAggregator and ClusterVectorAggregator

3 participants