Skip to content

fix(kubevirt)!: evict non-migratable VMs instead of blocking the drain - #4403

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/kubevirt-eviction-live-migrate-if-possible
Sep 24, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
fix/kubevirt-eviction-live-migrate-if-possible

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

A node drain now completes when a VM on that node cannot live-migrate. The VM is shut down and started again instead of holding the drain forever. VMs that can migrate still migrate. The change is the cluster-wide evictionStrategy in the KubeVirt CR, from LiveMigrate to LiveMigrateIfPossible.

The drain is held by virt-api's eviction webhook, not by a PodDisruptionBudget. With LiveMigrate and a VMI that is not live-migratable, the webhook denies every eviction with HTTP 429 (pod-eviction-admitter.go#L147-L150). kubectl drain retries a 429 until --timeout, which is infinite by default. KubeVirt v1.9.0 does not create PDBs for VMIs at all; its disruption-budget controller only deletes old ones (disruptionbudget.go#L353-L406). With LiveMigrateIfPossible the same webhook lets the eviction through for such a VMI (L152-L155). The VM then comes back according to its runStrategy.

RWO disks are not the only case. A VM with a passthrough GPU is not migratable either, unless the VGPULiveMigration gate is on and the GPU is a mediated device (vm.go#L1310-L1319). The gate is off in Cozystack, so any vm-instance with gpus set blocked the drain of its node until now.

In KubeVirt a drain and a launcher update go through the same eviction. After a KubeVirt upgrade the workload updater live-migrates outdated VMIs that can migrate and evicts the rest, because workloadUpdateMethods lists Evict as the fallback (workload-updater.go#L421-L424). The eviction call (L614-L620) hits the same webhook as a drain. So a vm-instance VM that cannot migrate either blocks both, which is what happens today, or is restarted by both, which is what happens after this PR once the VM runs under the new strategy.

This has a cost. A non-migratable VM now restarts on a drain. That covers VMs on RWO storage and VMs with GPU or host device passthrough. The webhook does not look at who asked for the eviction, so this holds for any eviction through the API, not only a drain. A VM whose disk is bound to the drained node, for example on a LINSTOR class without DRBD, stays down until the node is back.

The upgrade itself restarts nothing, and existing VMs switch only after their next restart. KubeVirt writes the cluster strategy into the VMI spec when the VMI is created (defaults.go#L208-L211), and the webhook reads that field first (migrations.go#L116-L122). A VMI that was running before the upgrade keeps LiveMigrate, so a drain still waits on it and the updater's eviction still gets 429. After one restart the VMI carries LiveMigrateIfPossible. From then on every KubeVirt bump shuts it down through the updater, in batches of 10 every minute, the batchEvictionSize and batchEvictionInterval set in the CR (kubevirt-cr.yaml#L139-L140). Reading the source, a CPU socket increase on such a VM under vmRolloutStrategy: LiveUpdate goes the same way, since the VCPUChange condition sends it to the updater (L320-L325). I have not run that one.

I kept Evict even so, because kubernetes-nodes workers depend on it. They set evictionStrategy: External, and for that strategy the webhook marks the VMI for evacuation instead of evicting it (pod-eviction-admitter.go#L156-L157). CAPK sees the mark on a VMI that cannot migrate, drains the tenant node and deletes the VMI (machine.go#L414-L454). The worker VM has runStrategy: Always, so KubeVirt starts it again on the new launcher. That is how a GPU worker or any other non-migratable worker gets the new launcher. Without Evict those workers would stay on the old launcher for good. Worker behaviour does not change with this PR, and the new test pins the method list so the fallback is not dropped later.

vm-instance renders no evictionStrategy and has no value for it, so a tenant has no way to opt out of either restart. KubeVirt would honour a per-VM value over the cluster one. I filed #4419 to expose it.

I picked this over the options in the issue because KubeVirt already decides migratability per VMI at eviction time. The platform CR does not need to know anything about storage, and a chart shipping a VM does not have to remember an override. Deriving the strategy from the disk access mode in a chart would also miss the GPU case.

To get the new drain behaviour for a non-migratable VM that is already running, restart it once after the upgrade. Until then a drain of its node still blocks, as it does today.

The comment in the CR is on the same line as the key on purpose. make update inserts blocks right before the evictionStrategy: line, so a comment above the key would get separated from it.

A VM with runStrategy: RerunOnFailure, Manual or Once may stay off after an eviction. If the guest shuts down cleanly on the ACPI request within its grace period, the VMI ends Succeeded. RerunOnFailure restarts only a failed VMI, and Manual and Once restart nothing on their own (vm.go#L1039-L1149). Once stays off even after an unclean stop. Only the default Always brings the VM back in every case.

What I did not verify. I have not drained a node with such a VM on a live cluster, and I have not watched the updater restart them during an upgrade. Everything above comes from the KubeVirt v1.9.0 and CAPK v0.1.10 source. I did not check how talosctl upgrade handles a drain that keeps getting 429. I also did not check how the cluster autoscaler treats a node with a non-migratable VM now that its launcher pod can be evicted. The pod has no safe-to-evict annotation, so for a VM on node-local storage that likely comes down to the autoscaler's skip-nodes-with-local-storage setting. Finally, I did not check whether the upstream VMCannotBeEvicted alert is deployed in Cozystack. For non-migratable VMs it stops firing after this change, since they are evictable now.

Closes #4360

Screenshots

No UI change.

Downstream repositories

Release note

fix(kubevirt)!: VMs that cannot live-migrate no longer block a node drain. The cluster-wide eviction strategy is now LiveMigrateIfPossible instead of LiveMigrate. VMs that can migrate still migrate. VMs that cannot, for example with a ReadWriteOnce disk or GPU or host device passthrough, are shut down and started again by their runStrategy. This applies to any pod eviction, not only a drain. A VM whose disk is bound to the drained node stays down until the node returns. A VM that is already running keeps the old strategy until its next restart, so restart non-migratable VMs once after upgrading if their nodes need to drain. The KubeVirt workload update uses the same eviction: once a non-migratable vm-instance VM runs under the new strategy, every later KubeVirt upgrade shuts it down, 10 VMs per minute. A VM with runStrategy RerunOnFailure, Manual or Once that shuts down cleanly on eviction stays off until it is started again. kubernetes-nodes workers are not affected.

@github-actions github-actions Bot added area/virtualization Issues or PRs related to virtualization (kubevirt, cdi, vmi, vm-import) kind/breaking-change Indicates the change introduces a breaking API or behaviour change kind/bug Categorizes issue or PR as related to a bug size/XS This PR changes 0-9 lines, ignoring generated files labels Sep 23, 2026
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: cozystack/cozystack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 4f98c8d1-9287-4144-a123-ce886d5e6d9e

📥 Commits

Reviewing files that changed from the base of the PR and between 6918234 and 25d4a8c.

📒 Files selected for processing (2)
  • packages/system/kubevirt/templates/kubevirt-cr.yaml
  • packages/system/kubevirt/tests/kubevirt_cr_test.yaml

Included review availability: Your plan provides up to 8 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The KubeVirt CR template now sets configuration.evictionStrategy to LiveMigrateIfPossible. It retains LiveMigrate and Evict as workload update methods. Helm unit tests check both rendered settings.

Changes

KubeVirt eviction strategy

Layer / File(s) Summary
Eviction and workload update defaults
packages/system/kubevirt/templates/kubevirt-cr.yaml, packages/system/kubevirt/tests/kubevirt_cr_test.yaml
The template sets evictionStrategy to LiveMigrateIfPossible and includes LiveMigrate and Evict as workload update methods. A comment describes the Evict method for non-migratable VMs during a KubeVirt upgrade. Tests verify both rendered settings.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 25d4a

API evictions can now proceed for VMs that cannot migrate. Those VMs may also restart during later KubeVirt updates through the configured Evict method; this PR does not itself change the operator version, and no additional merge-blocking issue is established.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #4360 identifies the cluster-wide LiveMigrate default as the platform-level problem for VMs with non-migratable disks. The template changes spec.configuration.evictionStrategy to `LiveMigrat…
Out of Scope Changes check ✅ Passed The pull request changes only the KubeVirt CR template and its Helm rendering tests. The added comment explains the related Evict behavior for non-migratable VMs and the existing kubernetes-nodes …
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: non-migratable VMs are evicted instead of blocking node drains.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

NOT LGTM

The flip also switches on the workload updater's Evict fallback. The release that ships it also bumps the launcher, so the platform upgrade itself will restart every non-migratable VM, and the release note does not say so.

Findings

  • [MAJOR] packages/system/kubevirt/templates/kubevirt-cr.yaml:126, the upgrade that ships this restarts every non-migratable VM, 10 per minute, with no drain

Claim mismatches

[PARTIAL] "Running VMs get this behaviour on upgrade without a restart". The strategy applies without a restart, but the updater restarts non-migratable VMs during that same upgrade (see the finding above).

[PARTIAL] "A VM that sets its own evictionStrategy keeps it". vm-instance renders no evictionStrategy and exposes no value for it, so a tenant who would rather have the drain wait than lose a VM with a node-bound disk has no way to opt out.

Caveats

  • Nothing was run on a live cluster. The admitter, updater and migratability behaviour was read from KubeVirt v1.9.0 source. A drain with an RWO VM and a GPU VM through cozystack-pr-test is still owed.
  • CPU hotplug under vmRolloutStrategy: LiveUpdate: handleCPUChangeRequest has no migratability gate (memory has one, vm.go:3474). A socket increase on a non-migratable VM therefore sets VCPUChange, which the updater now turns into an eviction. A CPU edit then restarts the VM within about a minute. This was reasoned from source, not run.
  • "comes back according to its runStrategy" holds for the Always default, but vm-instance also offers RerunOnFailure and Manual. If the guest honours the ACPI shutdown within the 30s grace period, the VMI ends Succeeded and neither strategy starts it again, so such a VM stays off after an eviction. This was read from source, not run; it may be worth one sentence in the release note.
  • cluster-autoscaler-hetzner/-azure scale-down is no longer refused for nodes that host non-migratable VMs. The launcher pod has no safe-to-evict annotation in v1.9.0, so what stops a node-local-disk VM from being evicted off a node that is then deleted is CA's skip-nodes-with-local-storage default. I did not verify that here.
  • Verified: the admitter lines cited in the body, that the PDB controller only deletes, and GPU migratability. kubernetes-nodes pins External, and vm-instance is the only other VM producer. update_idempotency_test.sh PASS under GNU make 4.4.1/sed 4.9 with the trailing comment in place. helm unittest 32/32. Reverting the value turns the new test red.

Recommended follow-ups

  • Run cozystack-pr-test from v1.6.3 with an RWO VM and a GPU VM running, and watch SuccessfulEvict events from the workload updater during the upgrade.

{{- toYaml . | nindent 6 }}
{{- end }}
evictionStrategy: LiveMigrate
evictionStrategy: LiveMigrateIfPossible # a VMI that cannot live-migrate is evicted instead of blocking the node drain

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MAJOR] the upgrade that ships this restarts every non-migratable VM, 10 per minute, with no drain

The same CR keeps workloadUpdateMethods: [LiveMigrate, Evict]. In KubeVirt v1.9.0 the workload updater evicts every running VMI whose launcher image is outdated and that is not migratable (workload-updater.go:421-425). Until now that eviction got a 429. After this change it goes through. The v1.9.0 bump (3e370e7) is not in any release tag yet (v1.6.3 ships v1.8.4), so the next platform upgrade makes every VMI outdated in the same step that flips the strategy. Every VM on an RWO disk or with a passthrough GPU/hostDevice is then restarted, 10 per minute, while the upgrade converges, and again on every later KubeVirt bump. The release note says running VMs get the new behaviour without a restart. Either say in the release note that this upgrade restarts non-migratable VMs, or drop Evict from workloadUpdateMethods, or ship the flip in a release that does not also bump the launcher.

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/kubevirt-eviction-live-migrate-if-possible branch from dd169cd to 6918234 Compare September 23, 2026 14:23
@github-actions github-actions Bot added size/S This PR changes 10-29 lines, ignoring generated files and removed size/XS This PR changes 0-9 lines, ignoring generated files labels Sep 23, 2026
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the fix/kubevirt-eviction-live-migrate-if-possible branch 2 times, most recently from 7f69afe to 4a7bc86 Compare September 23, 2026 14:32
The cluster-wide evictionStrategy was LiveMigrate. For a VMI that is
not live-migratable (a ReadWriteOnce disk, a passthrough GPU, a host
device), virt-api's eviction webhook answers every eviction with 429,
so a node drain retries forever and never completes.

LiveMigrateIfPossible keeps live migration for every VMI that can
migrate and lets the eviction through for the rest. The VM is shut
down and started again according to its runStrategy. A VM whose disk
is bound to the drained node stays down until the node returns.
KubeVirt writes the cluster strategy into a VMI's spec when the VMI
is created, so a VMI started before the upgrade keeps LiveMigrate and
still blocks the drain until it is restarted once.

A drain and a launcher update after a KubeVirt upgrade go through the
same eviction, so the workload updater's Evict fallback shuts down
non-migratable VMs started under the new strategy on every later
KubeVirt upgrade, where it used to get the same 429. Evict stays
anyway: for kubernetes-nodes workers, which use evictionStrategy
External, that eviction marks the VMI for evacuation, CAPK drains the
tenant node and deletes the VMI, and the VM starts it again on the new
launcher. The test pins the method list so the fallback is not dropped
to avoid the shutdowns.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <[email protected]>
@lexfrei

Copy link
Copy Markdown
Contributor Author

IvanHunters Your finding changed the decision on Evict, though the timing turned out a bit different at v1.9.0. The VMI mutating webhook writes the cluster strategy into the VMI spec at creation (vmi-mutator.go:123 calling defaults.go:208-211), and the eviction admitter reads that spec field before the cluster config (pod-eviction-admitter.go:138, via migrations.go:116-122). So VMIs running before the upgrade keep LiveMigrate, and the updater's eviction during that upgrade still gets a 429 at pod-eviction-admitter.go:147-150. Restarts through the updater start only on the next KubeVirt bump, and only for VMs that have been restarted once under the new strategy. I first tried dropping Evict, but that strands kubernetes-nodes workers that cannot migrate: with External the eviction only marks the VMI for evacuation, and CAPK then drains the tenant node and deletes the VMI so it comes back on the new launcher. So Evict stays, the test pins it, and the body and release note now state that cost with the correct timing, plus a one-time restart step for operators who need drains to work right away. The tenant opt-out is #4419.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

LGTM with non-blocking notes

My earlier finding about the upgrade restarting non-migratable VMs is withdrawn, you were right: the VMI mutator stamps the cluster strategy into the VMI spec at create (defaults.go:208-211, same in v1.8.4 and v1.9.0) and LiveMigrate has been in the CR since 0.30.1, so running VMIs keep it until their next restart. The corrected body and release note match the source. What is left is wording in the new comments and one test suggestion, none of it blocking.

Findings

  • [MINOR] packages/system/kubevirt/templates/kubevirt-cr.yaml:126, the comments state the new behaviour without its upgrade precondition
  • [MINOR] packages/system/kubevirt/templates/kubevirt-cr.yaml:134, the worker comment describes a restart, but with the shipped CAPK and MHC the Machine gets replaced
  • [MINOR] packages/system/kubevirt/tests/kubevirt_cr_test.yaml:41, the new cases pin the CR values, not the eviction behaviour

Claim mismatches

[PARTIAL] "holding the drain forever". True for a bare kubectl drain, but both Talos paths cap it at 5 minutes (DrainTimeout in pkg/kubernetes/kubernetes.go and DefaultDrainTimeout in talosctl's nodedrain, both 5 minutes in v1.13.0), so today a Talos upgrade over such a VM stops waiting after 5m rather than hanging.

[PARTIAL] The CAPK link (machine.go#L414-L454, v0.1.10) is the unpatched upstream; the migratable/non-migratable split cozystack runs comes from cozystack's patches/0001-backport-eviction-live-migration.diff.

[PARTIAL] The body mentions only the VMCannotBeEvicted alert going quiet. Under LiveMigrateIfPossible the kubevirt_vmi_non_evictable metric and virt-handler's "EvictionStrategy is set but vmi is not migratable" Warning event also stop for these VMs, so nothing tells a tenant their VM will restart on the next drain (VMIMigratableOnEviction returns false for them, migrations.go:124-136). Read from source, not run.

Caveats

Not run live by you or me: a drain with a non-migratable VM, the updater restart on a later KubeVirt bump, and the CPU-socket hotplug path. helm unittest passes 33/33 on this head.

{{- toYaml . | nindent 6 }}
{{- end }}
evictionStrategy: LiveMigrate
evictionStrategy: LiveMigrateIfPossible # a VMI that cannot live-migrate is evicted instead of blocking the node drain

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MINOR] the comments state the new behaviour without its upgrade precondition

The inline comment says a non-migratable VMI "is evicted instead of blocking the node drain", and the block at 131-135 says Evict "shuts down VMs that cannot migrate". On an upgraded cluster neither holds for a VMI created before the upgrade: its spec keeps LiveMigrate, the admitter reads that first (migrations.go:116-122), and drains still get 429. Someone debugging a stuck drain reads the opposite. Something like "for VMIs created after this change; restart older ones once" would fix it.

# Evict shuts down VMs that cannot migrate on a KubeVirt upgrade, but it is
# also how such a kubernetes-nodes worker (evictionStrategy External) gets
# the new launcher without a rollout: the eviction marks the VMI for
# evacuation, CAPK drains the tenant node and deletes the VMI, and the VM

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MINOR] the worker comment describes a restart, but with the shipped CAPK and MHC the Machine gets replaced

The CAPK build cozystack ships (patches/0001-backport-eviction-live-migration.diff) has IsTerminal() return true for a VMI that is not migratable and carries EvacuationNodeName (pkg/kubevirt/machine.go:124-131), and the controller turns that into FailureReason on the KubevirtMachine. Its own comment says this is so "the MachineHealthCheck and MachineSet controllers will gracefully take the VM down", and kubernetes-nodes puts an MHC on every group (nodegroup.yaml:573, maxUnhealthy: 50%). So in a group of two or more the worker is remediated into a new Machine, not "the VM starts it again". Pre-existing behaviour, and the conclusion (Evict is still needed) holds, but the comment and the PR body point the reader at the wrong mechanism.

- notExists:
path: spec.configuration.migrations

- it: sets the cluster evictionStrategy to LiveMigrateIfPossible

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MINOR] the new cases pin the CR values, not the eviction behaviour

Both go red when the value or Evict is reverted, which is good, but a KubeVirt bump that changes how the mutator or admitter treats LiveMigrateIfPossible stays green. A cheap follow-up: in the vminstance chainsaw suite, give the VM an RWO filesystem PVC, assert the VMI's .spec.evictionStrategy, and POST a dry-run Eviction to the launcher pod. I have not run that step.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 76b3652 into main Sep 24, 2026
20 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/kubevirt-eviction-live-migrate-if-possible branch September 24, 2026 11:57
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Sep 28, 2026
<!-- Thank you for making a contribution! Here are some tips for you:
- Use Conventional Commits for the PR title: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scopes are not an exhaustive list — pick the most specific scope for
the change and extend the list when a genuinely new area appears.
Examples:
- System components: dashboard, platform, operator, cilium, kube-ovn,
linstor, fluxcd, cluster-api
- Managed apps: postgres, mariadb, redis, kafka, clickhouse,
virtual-machine, kubernetes
- Development and maintenance: api, hack, tests, ci, docs, maintenance
- Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or
add a `BREAKING CHANGE:` footer
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `kind/backport` if it's a bugfix that needs to be
backported to a previous version.
-->

## What this PR does

This fixes two comments in the KubeVirt CR that #4403 got wrong. It
follows up on the [review of
#4403](#4403 (review)),
and I checked both findings against the source first. Only comments
change, the rendered CR is the same.

The comments didn't say that the new eviction behaviour applies only to
VMIs created after the switch. The VMI mutator copies the cluster
`evictionStrategy` into the VMI spec at creation (KubeVirt v1.9.0
`pkg/defaults/defaults.go:208-211`), and the eviction admitter reads the
spec first. So a non-migratable VMI created before the switch still has
`LiveMigrate` and keeps blocking the drain. The workload updater can't
evict it either, and it stays on the old launcher until its VM restarts.

The worker comment described the wrong mechanism. When a VMI can't
migrate and has `evacuationNodeName` set, CAPK's `IsTerminal()` returns
true and the controller sets `FailureReason` on the KubevirtMachine.
This is upstream CAPK code at the pinned commit, the cozystack patch
doesn't touch it. Every kubernetes-nodes group has a MachineHealthCheck,
and CAPI v1.10.1 treats `FailureReason` as unhealthy, so the Machine
gets replaced. If `maxUnhealthy` holds remediation back (at the default
`50%`, a group of one), the VM starts a new VMI in place. CAPK never
clears `FailureReason` though, so the Machine stays failed until
remediation is allowed.

The comment stays on the `evictionStrategy:` line, because `make update`
inserts blocks right before it. `helm unittest` passes 33/33 and
`update_idempotency_test.sh` passes. `make update` leaves the file
byte-identical.

The third finding (tests pin values, not behaviour) is tracked in #4422.

### Screenshots

<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->

No UI change.

### Downstream repositories

<!--
Cozystack is upstream for repositories that are NOT kept in sync with it
automatically, and nothing in CI compares the two sides, so a change
here can break them silently.

Which change forces what, and which file to touch, is the trigger map in
docs/agents/contributing.md under "Downstream Repositories". Walk it
before you tick anything.

IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM
TO FILL IN:

1. Walk the trigger map against the actual diff of this PR, file by
file. Do not decide from the PR title or from your memory of what you
changed.
2. For every repository you tick, open the follow-up PR there and link
it on the same line. A ticked box with no link claims work that does not
exist, which is worse than an empty box. Search that repository for an
open PR or issue covering it first, and link that instead of filing a
duplicate.
3. When the follow-up is out of scope, or needs a decision that is not
yours to make, open an issue in that repository instead, link that, and
say so in the PR body. Do not push a speculative PR into another
repository just to fill a line here.
4. Do not tick "No downstream repository is affected" to make the
checklist go away. If you are unsure, leave every box empty and say so
in the PR body, so a human decides.
-->

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
docs(kubevirt): the KubeVirt CR comments now say that a VMI created before the evictionStrategy change keeps LiveMigrate until its VM restarts, and that the MachineHealthCheck replaces a non-migratable kubernetes-nodes worker when the workload updater evicts it.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Clarified that existing VM instances retain the eviction strategy they
were created with, and may need to be restarted before they can migrate
during a drain.
* Expanded guidance on how eviction of non-migratable workers affects
machine health and replacement, including when remediation is delayed by
the `maxUnhealthy` limit.
  * No rendered configuration values changed.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
IvanHunters added a commit that referenced this pull request Oct 5, 2026
## What this PR does

Part of #4422.

The e2e suite now asks KubeVirt whether it would let a VM launcher pod
be evicted, without evicting it. Until now nothing in e2e evicted a
launcher pod, so the drain behaviour set up in #4403 was known only from
reading KubeVirt. Once the `vminstance` VM is running, the suite sends a
dry-run Eviction (`dryRun=All`) for its virt-launcher pod and checks the
answer. Nothing gets deleted.

A dry run still goes through the KubeVirt eviction webhook, which is
registered with `sideEffects: NoneOnDryRun` in v1.9.0. With
`LiveMigrateIfPossible` its answer follows the VMI's `LiveMigratable`
condition. That condition depends on storage, so the test reads it
instead of assuming it:

- `LiveMigratable=True`: the eviction must be refused with the webhook's
`Eviction triggered evacuation of VMI "<ns>/<name>"` message (HTTP 429).
A drain retries on that while the VM migrates away.
- `LiveMigratable=False`: the eviction must be allowed. The old
`LiveMigrate` value refused this case with "is not live-migratable".
KubeVirt v1.9.0 creates no PodDisruptionBudget for a VMI, so no PDB can
refuse it either.
- Anything else, including a missing condition, fails the test.

The allowed answer alone proves little, because the webhook also lets an
eviction through under `None`. So the test also asserts
`evictionStrategy: LiveMigrateIfPossible` in the VMI spec, where
virt-api copies the cluster value at creation. After the dry run it
checks that the launcher pod is the same object and not terminating, and
that the VMI has no `evacuationNodeName`.

On the PR lane only the `False` branch runs. Post-install prep pins
`StorageProfile/local` to RWO because DRBD is off there, and an RWO disk
makes the VMI non-migratable. Nightly and tag runs use `replicated`,
where I expect the `True` branch, but I have not seen this suite run on
that lane yet. A real drain and a workload update after a KubeVirt bump
are still open in #4422.

I ran the script against a stub `kubectl`. Both expected answers pass,
and it fails on every wrong answer I tried, such as a refusal for a
non-migratable VMI, a PDB-style 429, a missing condition, two launcher
pods, or a replaced pod after the dry run. `chainsaw lint` (v0.2.15) and
shellcheck pass.

### Screenshots

Not a UI change.

### Downstream repositories

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

The diff touches only `hack/e2e-chainsaw/vminstance/` and the suite
README, and nothing in the trigger map covers either.

### Release note

```release-note
test(vminstance): the e2e suite now dry-run evicts the VM launcher pod and checks the KubeVirt eviction decision against the VMI's LiveMigratable condition.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Added end-to-end coverage for dry-run eviction of a running virtual
machine’s launcher pod. The test checks the VM’s live-migration policy
and `LiveMigratable` condition, expecting eviction to be refused when
the VM is live-migratable and accepted otherwise.
* Verified that the dry run leaves the launcher pod unchanged and does
not record an evacuation node on the VM.
* **Documentation**
* Documented the test’s behavior and the local `jq` requirement for
running it.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/virtualization Issues or PRs related to virtualization (kubevirt, cdi, vmi, vm-import) kind/breaking-change Indicates the change introduces a breaking API or behaviour change kind/bug Categorizes issue or PR as related to a bug size/S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kubevirt: the cluster-wide evictionStrategy: LiveMigrate applies to VMs whose disks cannot live-migrate

2 participants