Skip to content

feat(destroy-addons): graceful teardown for both providers + Crossplane deletion-ordering (consolidates #641, fixes #593/#34/#35/#36) - #830

Merged
allamand merged 7 commits into
release/v0.3.0-rc3from
feat/kind-kro-ack-hub-destroy-addons
Sep 1, 2026
Merged

feat(destroy-addons): graceful teardown for both providers + Crossplane deletion-ordering (consolidates #641, fixes #593/#34/#35/#36)#830
allamand merged 7 commits into
release/v0.3.0-rc3from
feat/kind-kro-ack-hub-destroy-addons

Conversation

@allamand

@allamand allamand commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What

Graceful, ordered teardown for the redeploy issues (#593, #34/#35/#36), now consolidating #641 so #641 can be closed. Covers both cluster providers plus the Crossplane composition.

kind-kro-ack hub:destroy-addons (original #830, now upgraded)

Ordered reverse-of-provision teardown so on-hub ACK/KRO/Crossplane controllers delete their managed AWS resources while still alive:

  1. app workloads (non-default/platform projects) → 2. spoke clusters → 3. hub addons (reverse sync-wave, skip infra) → 4. infra stack LAST (crossplane/kro/ack-*) → AWS sweep net (prefix-scoped IAM roles/policies, ALBs scoped by elbv2.k8s.aws/cluster tag, delivery sources).
    Now adopts fix(destroy-addons): dynamic addon discovery and composition deletion ordering #641's technique: dynamic AppSet discovery (kubectl, not static registry files) + ownerReference-based child-app detection + skip finalizer/deletion waits when no children (no more per-addon 30s/120s timeouts).

kind-crossplane hub:destroy-addons (ported from #641)

Same rewrite: dynamic discovery + sync-wave sort, ownerReference child detection, agent-platform chain phase, 2>&1 for error visibility, no ignore_error.

Crossplane composition — deletion ordering (from #641)

Adds 6 Usage resources (natgw-uses-eip, route-uses-igw, route-uses-natgw, rta-uses-rt, cluster-uses-subnets, cluster-uses-vpc) to abstractions/crossplane/platform-cluster so composed AWS resources delete in dependency order (avoids orphaned EIP/NAT/VPC). + DELETION-ORDERING.md.

Relationship to #641

Supersedes #641 (which targeted feature/agent-platform with a divergent resource-groups/ layout). Its changes are re-applied here adapted to the rc3 structure (abstractions/crossplane/platform-cluster). #641's core.yaml external-dns probes are already present on rc3; its abstractions.yaml exclude is N/A to the rc3 layout — both intentionally omitted.

Validation

Targets release/v0.3.0-rc3. Fixes #593, #34, #35, #36.

…troy

kind-kro-ack's destroy deleted the hub cluster claim directly, which killed the
on-hub ACK/KRO/Crossplane controllers and orphaned the AWS resources they managed
(team IAM roles/policies, ALBs, DynamoDB, spoke clusters) -> redeploy conflicts on
a reused account (EntityAlreadyExists, DuplicateLoadBalancerName, delivery-source
ConflictException).

- Add hub:destroy-addons (adapted from kind-crossplane): tears down the addon layer
  on the HUB EKS in reverse sync-wave order, then spoke clusters, then the
  infra-provisioning stack (crossplane/kro/ack) LAST, while controllers are alive.
  All-best-effort; no-op if the hub is already gone. Also satisfies the missing
  provider-contract task (#593).
- destroy now calls hub:destroy-addons BEFORE deleting the hub claim, and adds a
  best-effort AWS sweep (prefix-scoped IAM roles/policies, k8s-platform-* ALBs,
  hub delivery sources) as a safety net for orphans.

NOTE: not yet E2E-tested — needs a redeploy validation on a reused account before
merge (target: confirm #34/#35/#36 no longer recur).
…b addons -> infra

Per review: destroy must follow the reverse of provisioning.
- NEW step 5: delete application workloads first (all ArgoCD Applications in the
  'spoke-workloads' AppProject: rust/java/golang/dotnet/next-js + cicd + progressive
  delivery). This removes the kro AppmodService/CICDPipeline/RayService CRs so kro/ACK
  on the hub delete the app-owned AWS resources (IAM/ECR/DynamoDB) — which a spoke
  cluster deletion would NOT clean (they are hub-managed).
- step 6: delete spoke clusters (now BEFORE hub addons).
- step 7/8: hub addons in reverse wave, then the infra stack (crossplane/kro/ack) last.

Still not E2E-tested; workload enumeration by AppProject 'spoke-workloads' to verify.
… kro-c1)

Read-only inspection of a live kro-c1 deployment surfaced 3 bugs in the initial
version:
- Workloads span multiple AppProjects (rust-project, java-project, cpu-ray-project,
  spoke-workloads) not just spoke-workloads -> now delete all Applications whose
  project is not 'default'/'platform'.
- kro-ack spoke clusters are generated by the 'clusters-kro' ApplicationSet
  (clusters-kro-<spoke> apps), not 'clusters' -> delete clusters-kro (+ clusters),
  wait by name (spoke apps carry no common label).
- ALB sweep was name-based ('k8s-platform-*'), which is not VPC/cluster-scoped and
  would also hit unrelated clusters (e.g. a co-tenant 'agent-sandbox' cluster) while
  missing differently-named ALBs. Now scoped by the elbv2.k8s.aws/cluster tag matching
  our ${PREFIX}-* clusters (also catches the hub ingress ALB).

Still needs an E2E redeploy test on a reused account to confirm #34/#35/#36.
@allamand
allamand changed the base branch from v0.3.0-rc3 to release/v0.3.0-rc3 September 1, 2026 14:02
…wnerRef child detection (both providers) + Crossplane Usage deletion-ordering

- kind-crossplane hub:destroy-addons: port #641 rewrite (dynamic AppSet
  discovery via kubectl + sync-wave sort, ownerReference-based child-app
  detection, skip finalizer/deletion waits when no children, agent-platform
  chain phase, drop ignore_error / use 2>&1 for visibility)
- kind-kro-ack hub:destroy-addons: adopt the same dynamic-discovery +
  ownerReference technique (steps 7 & 8), keeping the ordered
  workloads->spokes->addons->infra->AWS-sweep teardown; drop the static
  registry-file ADDONS computation
- platform-cluster composition: add 6 Usage resources (natgw-uses-eip,
  route-uses-igw, route-uses-natgw, rta-uses-rt, cluster-uses-subnets,
  cluster-uses-vpc) to enforce reverse deletion ordering and avoid orphaned
  EIP/NAT/VPC on teardown
- add DELETION-ORDERING.md documenting the Usage fix

Supersedes #641 (which targeted feature/agent-platform); adapted to the rc3
structure (abstractions/crossplane/platform-cluster path).
@allamand allamand changed the title feat(#593): kind-kro-ack hub:destroy-addons + graceful teardown (fixes redeploy #34/#35/#36) feat(destroy-addons): graceful teardown for both providers + Crossplane deletion-ordering (consolidates #641, fixes #593/#34/#35/#36) Sep 1, 2026
@allamand allamand closed this Sep 1, 2026
@allamand allamand reopened this Sep 1, 2026
…list

Addresses the blocking review point: the destroy-time IAM sweep matched every
${PREFIX}-* role/policy by name, which on a reused/self-paced account could
delete the IDE execution role (*-team-stack-*/*SharedRole*, CFN-managed),
cluster/provider roles, and the #831 argo-rollouts roles.

Now a role/policy is swept only if it BOTH starts with the prefix AND carries an
ACK ownership tag (services.k8s.aws/*), and never if its name matches the
infra/bootstrap/CFN denylist. Also: renumber the duplicate step 8 (fleet-secrets
-> 9, leftover -> 10) and add a keep-in-sync note to INFRA_STACK.
@allamand

allamand commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — addressed the blocker in a9527e6.

🔴 #1 IAM sweep blast radius (fixed): the destroy-time sweep no longer matches roles/policies by name alone. A role/policy is now swept only if it BOTH starts with the prefix AND carries an ACK ownership tag (services.k8s.aws/*), and never if its name matches an infra/bootstrap/CFN denylist:
-team-stack-, SharedRole, -cluster-mgmt-, -cluster-role$, -cluster-node-role$, -crossplane-provider, Crossplane.*ProviderRole, -argo-rollouts.
So the IDE execution role, cluster/provider roles, and the #831 argo-rollouts roles are protected; only ACK-created orphans (the #34 EntityAlreadyExists culprits) are removed.

🟢 cosmetics (fixed): renumbered the duplicate step 8 (fleet-secrets → 9, leftover → 10); added a keep-in-sync note to INFRA_STACK.

Deferred (follow-ups, not blocking) — agree they're worth doing:

Still not E2E-tested (destructive teardown) — a full provision→destroy on a reused account is the real validation for #34/#35/#36.

@allamand allamand closed this Sep 1, 2026
@allamand allamand reopened this Sep 1, 2026
@allamand

allamand commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Code review — graceful teardown (kind-kro-ack / kind-crossplane) + Crossplane Usage deletion-ordering

Read all 4 files and validated 3 points against Team 10's live hub (Crossplane v2.2.1).

Verdict

Solid, well-documented, structured and defensive (best-effort / idempotent). The teardown logic and the Usage approach are correct. One point to fix before merge (IAM sweep blast radius — now fixed, see below), a few medium robustness items, and minor nits.

✅ Strengths (verified)

  • Teardown order is correct: stop regeneration (bootstrap / cluster-addons appsets) → workloads → spokes → hub addons → infra stack LAST → AWS sweep. Consistent with keeping the kro/ACK/Crossplane controllers alive to delete their AWS resources.
  • Usage resources are the right fix for the EIP/NATGW and VPC/Subnet delete races (Crossplane has no native delete ordering). matchControllerRef: true correctly scopes to the PlatformCluster instance. DELETION-ORDERING.md is excellent.
  • Version compat validated: hub on Crossplane v2.2.1, CRD usages.protection.crossplane.io present → the protection.crossplane.io/v1beta1 Usages will create. Note they are also created at provision time, so this was a create-path risk too — cleared here.
  • apiVersions consistent (classic trap, checked): the Usage of/by match the real MRs — EC2 v1beta1, EKS Cluster v1beta2. So the ordering actually engages (a mismatch would silently disable the protection).
  • Live addon discovery (kubectl, not the static registry) + ownerReference-based child detection → robust.

🔴 Blocker — FIXED (a9527e6)

The prefix-based IAM sweep was too broad. Deleting every ${PREFIX}-* role/policy by name matched, on the live hub: the IDE execution role *-team-stack-*PEEKSSharedRole* (CFN-managed — deleting it breaks the CFN stack delete), the infra/bootstrap roles (*-cluster-mgmt-*, *-cluster-role, *-cluster-node-role, *-crossplane-provider*, *Crossplane*ProviderRole), and the *-argo-rollouts roles from #831.
Fix: a role/policy is now swept only if it both starts with the prefix and carries an ACK ownership tag (services.k8s.aws/*), and never if its name matches an explicit infra/bootstrap/CFN denylist.

🟠 Medium (recommended follow-ups, not blocking)

  1. Strip-finalizers fallback can orphan AWS resources. On timeout the workloads/leftover steps do finalizers:[] + delete --wait=false; if ArgoCD hasn't cascaded, the kro/ACK CRs (AppmodService, CICDPipeline, DynamoDB, ECR…) aren't deleted → ACK won't clean the AWS side. The final sweep only covers ALB + IAM + delivery-sources (not DynamoDB/ECR/EFS). → lengthen the graceful wait before stripping, or extend the sweep.
  2. agent-platform handled differently between providers. kind-crossplane preserves it (preserveResourcesOnDeletion:true); kind-kro-ack only puts it in SKIP (never deleted). Intentional? If it owns AWS resources, the kro-ack path orphans them. Clarify/align.
  3. INFRA_STACK diverges from the hardcoded ordered list (maintainability): the SKIP list (step 7), the explicit reverse-order list (step 8), and INFRA_STACK are three places to keep in sync. Added a note; consider centralizing.
  4. Pagination: confirm aws iam list-roles/list-policies cover all pages (AWS CLI v2 auto-paginates by default when --max-items is unset).

🟢 Nits

  • Duplicate # 8. in kind-kro-ack — fixed (fleet-secrets → 9, leftover → 10).
  • Spoke wait loop grep -E 'clusters-kro-|clusters-.*spoke' assumes "spoke" in the app name — fine for peeks-spoke-*, fragile in general.
  • ignore_error: true everywhere → destroy always succeeds even on a partial teardown (residual AWS cost hidden). Consider a final "remaining resources" report (elbv2/iam/eks/amp list).
  • The ALB sweep doesn't clean orphaned target groups / SGs (minor).

TL;DR

Mergeable now that the IAM sweep is scoped (the only real risk). Points 2–5 are recommended robustness improvements; the rest is cosmetic. The Usage approach and teardown order are correct and compatible with the deployed Crossplane v2.2.1.

Consistency with the kind-kro-ack sweep: the kind-crossplane destroy sweeps
roles/policies by cluster-name prefix; add the DENY_RE denylist so it also skips
the infra/bootstrap/CFN/provider/cluster roles and the #831 argo-rollouts roles
(shared prefix but must survive teardown / a reused-account redeploy).
@allamand

allamand commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Applied the reviewer's consistency recommendation (a) in the latest commit: the kind-crossplane destroy sweep now carries the same DENY_RE denylist as the kind-kro-ack sweep, so it skips the infra/bootstrap/CFN/provider/cluster roles and the #831 -argo-rollouts roles (they share the cluster-name prefix but must survive teardown / a reused-account redeploy). Kept the broad "catch crossplane orphans by prefix" intent — just added the guard. Mediums #2#5 remain open follow-ups.

@allamand allamand closed this Sep 1, 2026
@allamand allamand reopened this Sep 1, 2026
…weep (#2/#3)

#3: mirror the kind-crossplane agent-platform phase into kind-kro-ack so the
AppSet is stopped (preserveResources) before its child apps are orphaned —
otherwise agent-platform sat only in the step-7 SKIP and its AppSet would
regenerate the workloads deleted in step 5. No-op when agent-platform is absent
(the case on rc3); keeps the two providers symmetric.

#2: add a prefix-scoped, ACK-tag-guarded ECR repository sweep to the destroy
AWS net (repos named ${PREFIX}/<app>[/cache]). DynamoDB tables (arbitrary
user-provided names, no deployment-scoping tag) are intentionally NOT swept to
avoid the same blast-radius as #1; documented inline.
@allamand

allamand commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Follow-ups from the review addressed:

Remaining known trade-off: the strip-finalizers fallback can still orphan a stuck DynamoDB table on a hard timeout (by design, to not hang teardown) — surfaced, not swept.

@allamand allamand closed this Sep 1, 2026
@allamand allamand reopened this Sep 1, 2026
@allamand
allamand merged commit 6557461 into release/v0.3.0-rc3 Sep 1, 2026
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.

1 participant