Skip to content

linode: make the tagged-StorageClass swap race-safe and non-fatal - #1312

Closed
travagliad wants to merge 1 commit into
mainfrom
claude/pmm-qa-hooks-permissions-verify-pt9u2v
Closed

linode: make the tagged-StorageClass swap race-safe and non-fatal#1312
travagliad wants to merge 1 commit into
mainfrom
claude/pmm-qa-hooks-permissions-verify-pt9u2v

Conversation

@travagliad

Copy link
Copy Markdown
Contributor

Problem

Follow-up to #1311, addressing a review finding on the just-merged change.

create-lke swapped the tagged StorageClass in with kubectl delete … ; kubectl apply …. That is fragile under set -euo pipefail: LKE's addon reconciler can recreate the (untagged) default linode-block-storage-retain in the window between the two commands. The apply then tries to update an existing StorageClass, hits updates to parameters are forbidden (a StorageClass's parameters are immutable — the very premise of #1311), and the script exits non-zero with the cluster already created and billing. The EXIT trap only tags/diagnoses, so nothing tears the cluster down until the 24h TTL reaper — a worse outcome than the leak #1311 closes.

Change

Recreate the StorageClass with kubectl replace --force (a single delete+create, so immutable parameters are replaced rather than rejected), wrapped so it is non-fatal: one retry, then a warning. Losing the race with the reconciler now degrades to an untagged volume — still covered by the imperative tagging backstop (tag-lke-resources.sh at provision and before delete) — instead of a failed provision that orphans the cluster.

Validation

  • shellcheck -S warning and bash -n clean.
  • Behavior reasoning: in the race case, replace --force's create step meets LKE's freshly-recreated SC (AlreadyExists) and the retry recreates it; if both attempts lose, a default StorageClass still exists (LKE's untagged one), so the provision proceeds and volumes fall back to imperative tagging — never a dead, billing cluster.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DBhvZuEUQU8ym347Xat4Yy


Generated by Claude Code

Follow-up to #1311 (review). The delete-then-apply of linode-block-storage-retain
was fragile: under set -euo pipefail, if LKE's addon reconciler recreates the
untagged StorageClass in the window between the two commands, the apply fails with
"updates to parameters are forbidden" (parameters are immutable) and the script
exits with the cluster already created and billing -- a worse outcome than the
leak this closes.

Recreate it with `kubectl replace --force` (one step, tolerates immutable
parameters), retried once and non-fatal: losing the race now degrades to an
untagged volume covered by the imperative tagging backstop, never a failed
provision that orphans the cluster.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBhvZuEUQU8ym347Xat4Yy
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 12 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 3ced6a07-2427-4e98-ab83-6cc90e416f59

📥 Commits

Reviewing files that changed from the base of the PR and between c2fb1a4 and bc77ca5.

📒 Files selected for processing (1)
  • .claude/skills/linode-ha-provisioning/scripts/create-lke-pmm-ha.sh

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.

@travagliad travagliad closed this Sep 3, 2026
@travagliad
travagliad deleted the claude/pmm-qa-hooks-permissions-verify-pt9u2v branch September 3, 2026 14:33
Comment on lines +137 to +140
# `replace --force` recreates it in one step (a plain apply would hit "parameters
# forbidden"); non-fatal with one retry so losing a race with LKE's addon reconciler
# degrades to an untagged volume (imperative tagging backstop) -- never a failed
# provision that leaks the already-created cluster.

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.

🟡 These four lines explain the mechanism and why apply was rejected — that is the PR body's job, and it already says all of this verbatim. Lines 135-136 above already carry the invariant that motivates the whole block (parameters immutable → volumes born untagged), so the only thing left worth keeping in the file is why losing the swap is survivable, not how replace --force differs from apply or what the failure would have cost.

Suggested change
# `replace --force` recreates it in one step (a plain apply would hit "parameters
# forbidden"); non-fatal with one retry so losing a race with LKE's addon reconciler
# degrades to an untagged volume (imperative tagging backstop) -- never a failed
# provision that leaks the already-created cluster.
# An untagged SC here is survivable: tag-lke-resources.sh tags the volumes
# imperatively at provision and before delete.

allowVolumeExpansion: true
EOF
}
_apply_tagged_sc || { sleep 3; _apply_tagged_sc || log "WARN: tagged StorageClass not applied; volumes fall back to imperative tagging"; }

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.

🟡 The graceful-degradation claim only holds for the one failure mode the PR body reasons about. replace --force is delete-then-create as two API calls, so if the create half fails for any reason other than the reconciler having recreated the SC (transient API error, webhook, apiserver hiccup), the delete has already succeeded and the cluster is left with no linode-block-storage-retain and no default StorageClass. Both attempts then fail the same way, and the WARN says "volumes fall back to imperative tagging" when in fact no volumes get provisioned at all: the deps/PMM PVCs stay Pending and the run burns to kubectl rollout status --timeout=20m (line 233) before it fails — a billing cluster held ~20 minutes longer than the fast set -e exit this replaces.

Make the degradation the code's guarantee rather than an assumption about which half failed — assert a default SC exists before continuing, and fail fast if it does not:

Suggested change
_apply_tagged_sc || { sleep 3; _apply_tagged_sc || log "WARN: tagged StorageClass not applied; volumes fall back to imperative tagging"; }
_apply_tagged_sc || { sleep 3; _apply_tagged_sc || log "WARN: tagged StorageClass not applied; volumes fall back to imperative tagging"; }
kubectl get storageclass linode-block-storage-retain >/dev/null

@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

.claude/** has no review reference in this repo, so this diff got the cross-cutting checks only — no provisioning-specific review of the LKE bring-up path.

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.

2 participants