Skip to content

fix(scuttle): keep containerd up while clearing pods; fail if unreachable - #153

Merged
anthr76 merged 1 commit into
mainfrom
fix/scuttle-clear-pods-order
Oct 24, 2026
Merged

anthr76 merged 1 commit into
mainfrom
fix/scuttle-clear-pods-order

Conversation

@anthr76

@anthr76 anthr76 commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Why

clearPodsOnDelete (#152) fired during the Oct 22 auto-upgrades but cleared nothing. From worker-4's journal at 04:49 UTC:

  • switch-to-configuration stopped containerd, docker-kubelet and docker in one transaction.
  • scuttle drained (gave up on a PDB), deleted the node, and scuttle-clear-pods correctly logged Node worker-4 was deleted.
  • Both crictl calls then failed with validate CRI v1 runtime API ... Unavailable because containerd was already stopped. || true and the - prefix on ExecStopPost made the unit exit successfully.
  • Containers outlive containerd (shims), so every pod kept its old podCIDR address. The next Cilium restart (Oct 24) cut them all off, taking down Ceph mon quorum and 5 OSDs.

Changes

  • New criService option (default containerd.service). With clearPodsOnDelete, scuttle is ordered After= it, so on a shared stop transaction systemd keeps containerd running until ExecStopPost finishes.
  • scuttle-clear-pods waits up to ~30s for the CRI endpoint and exits non-zero if it is unreachable. The - prefix is dropped from ExecStopPost, so this shows up as a failed unit (and a failed nixos-upgrade) instead of a silent no-op.
  • Logs how many sandboxes remain after clearing (warning only, since a still-running kubelet may legitimately recreate them).

Verification

  • nix eval on worker-1 and worker-4: scuttle.after = ["multi-user.target","docker-kubelet.service","network-online.target","containerd.service"]; ExecStopPost has no - prefix.
  • Not tested on a live node. The real test is the next auto-upgrade that also changes containerd; check journalctl -u scuttle for All pod/Pod sandboxes remaining and that pod IPs match the node's podCIDR afterwards.

Not addressed

scuttle still deletes the Node when the drain fails on a PDB. The pod clear covers the networking side of that.

@anthr76
anthr76 merged commit db20762 into main Oct 24, 2026
21 of 26 checks passed
@anthr76
anthr76 deleted the fix/scuttle-clear-pods-order branch October 24, 2026 20:53
anthr76 added a commit that referenced this pull request Oct 25, 2026
…#155)

## Why

During tonight's rolling reboots the clear-pods step (ExecStopPost of
scuttle) logged `DeadlineExceeded` on most sandbox stop/remove calls and
left 32–71 sandboxes per node, while still exiting 0. Reboots hid it; a
kubelet restart without a reboot (the Oct 22 case) would leave stale-IP
pods again.

Two causes:
1. **crictl's per-RPC deadline defaults to 2s** (`--timeout`). `crictl
stop --timeout 30` and `rmp` on a busy node exceed it and fail with
`DeadlineExceeded`.
2. **It ran while kubelet was still up.** scuttle is `After=kubelet`, so
on a kubelet stop scuttle (and its ExecStopPost) stops *first*. A live
kubelet can recreate sandboxes underneath the clear, which is also why
#153 could only warn about leftovers.

## Changes

- crictl gets `--timeout 60s`.
- The clear moves out of scuttle into a new `scuttle-clear-pods.service`
(oneshot, `RemainAfterExit`, `ExecStop=clearPods`), `PartOf`/`WantedBy`
kubelet, `Before=kubelet`, `After=criService`. Stop order on any kubelet
stop (restart, switch, shutdown) becomes: scuttle (drain, delete Node) →
kubelet → scuttle-clear-pods → containerd.
- Sandboxes are stopped (`stopp`) and removed (`rmp -f`) individually in
up to 3 passes; if any remain, the unit exits 1. With kubelet already
stopped, leftovers are a real failure.
- `restartIfChanged = false` on the new unit too, so deploying/changing
it never runs the clear mid-switch.
- scuttle drops the `After=containerd` / `ExecStopPost` from #153; the
UID record (`/run/scuttle/node-uid`) is unchanged.

## Verification

- `nix eval` of worker-3: `scuttle-clear-pods.service` has
`After=containerd.service`, `Before=docker-kubelet.service`,
`PartOf=docker-kubelet.service`, `WantedBy=multi-user.target
docker-kubelet.service`, `X-RestartIfChanged=false`; scuttle keeps
`BindsTo`/`After` kubelet and `X-RestartIfChanged=false`.
- **Not tested on a live node.** Suggested test on one worker after
deploy: `systemctl restart docker-kubelet`, then `journalctl -u scuttle
-u scuttle-clear-pods` should show the Node deleted → `All pod sandboxes
removed`, and every pod IP should match the node's podCIDR.

## Not addressed

A plain `systemctl restart docker-kubelet` stops scuttle via `BindsTo`
but doesn't start it again (it is only `WantedBy=multi-user.target`).
Pre-existing; worth a `PartOf` follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant