Skip to content

fix: isMisconfiguredPdb to properly handle "outdated" currentHealth - #1127

Merged
gardener-prow[bot] merged 3 commits into
gardener:masterfrom
Kumm-Kai:push-zkxvlzwknxyx
Aug 11, 2026
Merged

fix: isMisconfiguredPdb to properly handle "outdated" currentHealth#1127
gardener-prow[bot] merged 3 commits into
gardener:masterfrom
Kumm-Kai:push-zkxvlzwknxyx

Conversation

@Kumm-Kai

@Kumm-Kai Kumm-Kai commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:
/kind bug

We see many pod disruption budget ... is misconfigured and requires zero voluntary evictions for PDBs that are (when all pods are healthy) actually not misconfigured.

This happens when the MCM gets the PDB between a pod getting evicted and the PDB controller reconciling the PDB.

Currently the MCM only takes into account the perspective of the PDB controller. But the eviction calls also cause changes in the PDB object:

  • decrement disruptionsAllowed
  • add pod to disruptedPods

More details about the changes in the PDB object:

Details

Everything is running:

{
  "apiVersion": "policy/v1",
  "kind": "PodDisruptionBudget",
  "metadata": {
    "creationTimestamp": "2026-04-27T15:44:24Z",
    "generation": 1,
    "labels": {
      "app": "kubernetes",
      "role": "apiserver"
    },
    "name": "kube-apiserver",
    "namespace": "shoot--xxx--xxx",
    "resourceVersion": "11475386785",
    "uid": "9931059a-5e59-47ba-b1d1-33211c5f8c2d"
  },
  "spec": {
    "maxUnavailable": 1,
    "selector": {
      "matchLabels": {
        "app": "kubernetes",
        "role": "apiserver"
      }
    },
    "unhealthyPodEvictionPolicy": "AlwaysAllow"
  },
  "status": {
    "conditions": [
      {
        "lastTransitionTime": "2026-07-25T12:33:41Z",
        "message": "",
        "observedGeneration": 1,
        "reason": "SufficientPods",
        "status": "True",
        "type": "DisruptionAllowed"
      }
    ],
    "currentHealthy": 3,
    "desiredHealthy": 2,
    "disruptionsAllowed": 1,
    "expectedPods": 3,
    "observedGeneration": 1
  }
}

After evict call:

{
  "apiVersion": "policy/v1",
  "kind": "PodDisruptionBudget",
  "metadata": {
    "creationTimestamp": "2026-04-27T15:44:24Z",
    "generation": 1,
    "labels": {
      "app": "kubernetes",
      "role": "apiserver"
    },
    "name": "kube-apiserver",
    "namespace": "shoot--xxx--xxx",
    "resourceVersion": "11550070628",
    "uid": "9931059a-5e59-47ba-b1d1-33211c5f8c2d"
  },
  "spec": {
    "maxUnavailable": 1,
    "selector": {
      "matchLabels": {
        "app": "kubernetes",
        "role": "apiserver"
      }
    },
    "unhealthyPodEvictionPolicy": "AlwaysAllow"
  },
  "status": {
    "conditions": [
      {
        "lastTransitionTime": "2026-07-28T12:34:25Z",
        "message": "",
        "observedGeneration": 1,
        "reason": "InsufficientPods",
        "status": "False",
        "type": "DisruptionAllowed"
      }
    ],
    "currentHealthy": 3,
    "desiredHealthy": 2,
    "disruptedPods": {
      "kube-apiserver-65cbbb5d5d-zmm29": "2026-07-28T12:34:25Z"
    },
    "disruptionsAllowed": 0,
    "expectedPods": 3,
    "observedGeneration": 1
  },
  "timestamp": "2026-07-28T12:34:25Z"
}

Evict call:

  • decrements disruptionsAllowed by 1
  • adds to be evicted pod to disruptedPods
  • currentHealthy remains at 3!

-> Before my PR: Misconfigured as currentHealthy >= expectedPods
-> After my PR: Not misconfigured as len(disruptedPods) == 0


First PDB controller reconcile after eviction:

{
  "apiVersion": "policy/v1",
  "kind": "PodDisruptionBudget",
  "metadata": {
    "creationTimestamp": "2026-04-27T15:44:24Z",
    "generation": 1,
    "labels": {
      "app": "kubernetes",
      "role": "apiserver"
    },
    "name": "kube-apiserver",
    "namespace": "shoot--xxx--xxx",
    "resourceVersion": "11550071136",
    "uid": "9931059a-5e59-47ba-b1d1-33211c5f8c2d"
  },
  "spec": {
    "maxUnavailable": 1,
    "selector": {
      "matchLabels": {
        "app": "kubernetes",
        "role": "apiserver"
      }
    },
    "unhealthyPodEvictionPolicy": "AlwaysAllow"
  },
  "status": {
    "conditions": [
      {
        "lastTransitionTime": "2026-07-28T12:34:25Z",
        "message": "",
        "observedGeneration": 1,
        "reason": "InsufficientPods",
        "status": "False",
        "type": "DisruptionAllowed"
      }
    ],
    "currentHealthy": 2,
    "desiredHealthy": 2,
    "disruptionsAllowed": 0,
    "expectedPods": 3,
    "observedGeneration": 1
  },
  "timestamp": "2026-07-28T12:34:26Z"
}

PDB controller:

  • Removes pods from disruptedPods that are getting deleted DeletionTimestamp != nil
  • Recalculates currentHealthy -> now 2
  • expectedPods remains at 3

-> Not misconfigured as not all pods are healthy


Replacement pod healthy and PDB controller reconcile done (same as the first one):

{
  "apiVersion": "policy/v1",
  "kind": "PodDisruptionBudget",
  "metadata": {
    "creationTimestamp": "2026-04-27T15:44:24Z",
    "generation": 1,
    "labels": {
      "app": "kubernetes",
      "role": "apiserver"
    },
    "name": "kube-apiserver",
    "namespace": "shoot--xxx--xxx",
    "resourceVersion": "11550090088",
    "uid": "9931059a-5e59-47ba-b1d1-33211c5f8c2d"
  },
  "spec": {
    "maxUnavailable": 1,
    "selector": {
      "matchLabels": {
        "app": "kubernetes",
        "role": "apiserver"
      }
    },
    "unhealthyPodEvictionPolicy": "AlwaysAllow"
  },
  "status": {
    "conditions": [
      {
        "lastTransitionTime": "2026-07-28T12:35:24Z",
        "message": "",
        "observedGeneration": 1,
        "reason": "SufficientPods",
        "status": "True",
        "type": "DisruptionAllowed"
      }
    ],
    "currentHealthy": 3,
    "desiredHealthy": 2,
    "disruptionsAllowed": 1,
    "expectedPods": 3,
    "observedGeneration": 1
  },
  "timestamp": "2026-07-28T12:35:24Z"
}

Which issue(s) this PR fixes:
None

Special notes for your reviewer:

Release note:

Prevent `error while evicting pod X: pod disruption budget X/X is misconfigured and requires zero voluntary evictions` when pods are being evicted

@gardener-prow gardener-prow Bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/M Denotes a PR that changes 30-99 lines, ignoring generated files. cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. labels Jul 29, 2026
@Kumm-Kai
Kumm-Kai marked this pull request as ready for review July 30, 2026 09:22
@Kumm-Kai
Kumm-Kai requested a review from a team as a code owner July 30, 2026 09:22
@gardener-prow gardener-prow Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 30, 2026
@Kumm-Kai

Copy link
Copy Markdown
Contributor Author

/kind bug

@gardener-prow gardener-prow Bot added kind/bug Bug and removed do-not-merge/needs-kind Indicates a PR lacks a `kind/foo` label and requires one. labels Jul 30, 2026

@aaronfern aaronfern left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR @Kumm-Kai!
A few comments from me, otherwise it looks good

Comment thread pkg/util/provider/drain/drain_test.go Outdated
// Example PDB from etcd-druid (3 replicas)
pdb = &policyv1.PodDisruptionBudget{
Spec: policyv1.PodDisruptionBudgetSpec{
// MaxUnavailable: new(intstr.FromInt(1)),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
// MaxUnavailable: new(intstr.FromInt(1)),

Comment thread pkg/util/provider/drain/drain_test.go Outdated
CurrentHealthy: 3,
DisruptionsAllowed: 1, // `CurrentHealthy` - `DesiredHealthy`
},
// `ExpectedPods` - `MaxUnavailable`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seems to have been kept by error

Suggested change
// `ExpectedPods` - `MaxUnavailable`

Comment thread pkg/util/provider/drain/drain.go Outdated

return pdb.Status.ExpectedPods > 0 && pdb.Status.CurrentHealthy >= pdb.Status.ExpectedPods && pdb.Status.DisruptionsAllowed == 0
hasPods := pdb.Status.ExpectedPods > 0
// `>=` instead of `==` (because of an in progress scale down `CurrentHealthy` can be larger than `ExpectedPods` current might be larger than expected

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you please improve this comment? The current might be larger than expected part is a bit redundant

Comment thread pkg/util/provider/drain/drain_test.go Outdated
var pdb *policyv1.PodDisruptionBudget
Context("normal", func() {
It("returns false", func() {
// Example PDB from etcd-druid (3 replicas)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This comment isn't required/helpful 🤷🏻

@Kumm-Kai
Kumm-Kai requested a review from aaronfern August 3, 2026 16:42

@r4mek r4mek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR @Kumm-Kai !
Just one question.

Comment thread pkg/util/provider/drain/drain.go
Comment thread pkg/util/provider/drain/drain.go
Comment thread pkg/util/provider/drain/drain.go Outdated
Comment thread pkg/util/provider/drain/drain.go Outdated
@gardener-prow gardener-prow Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Aug 7, 2026
@Kumm-Kai
Kumm-Kai requested a review from r4mek August 7, 2026 09:18
@r4mek

r4mek commented Aug 10, 2026

Copy link
Copy Markdown
Member

/lgtm

@gardener-prow gardener-prow Bot added the lgtm Indicates that a PR is ready to be merged. label Aug 10, 2026
@gardener-prow

gardener-prow Bot commented Aug 10, 2026

Copy link
Copy Markdown

LGTM label has been added.

DetailsGit tree hash: bc640978206d858dc90a7090ae5d1190d6daaff8

@aaronfern

Copy link
Copy Markdown
Member

/lgtm
/approve

@gardener-prow

gardener-prow Bot commented Aug 11, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: aaronfern

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@gardener-prow gardener-prow Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 11, 2026
@gardener-prow
gardener-prow Bot merged commit 580d98f into gardener:master Aug 11, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cla: yes Indicates the PR's author has signed the cla-assistant.io CLA. kind/bug Bug lgtm Indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants