Skip to content

feat(lifecycle): standalone control plane lifecycle - #7344

Merged
cuisongliu merged 57 commits into
labring:mainfrom
zijiren233:feat/standalone-control-plane-lifecycle
Oct 28, 2026
Merged

cuisongliu merged 57 commits into
labring:mainfrom
zijiren233:feat/standalone-control-plane-lifecycle

Conversation

@zijiren233

@zijiren233 zijiren233 commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Adds a managed lifecycle for kubeadm control planes that run static Pods without Node registration or mirror Pods. Registered mode remains the default. Existing clusters can switch in either direction while retaining Sealos scaling, upgrade, certificate and reset commands.

Usage

sealos switch control-plane standalone --check-only
sealos switch control-plane standalone
sealos switch control-plane registered -y

Switching supports cluster selection, per-host timeout and the existing confirmation mechanism. Controller options belong to the standalone subcommand. Existing clusters must use switch to change modes; changing the mode through apply is rejected.

For a new cluster, set the mode in its Clusterfile and use sealos apply -f Clusterfile:

spec:
  controlPlaneMode: standalone
  routeController:
    image: ghcr.io/labring-sigs/route-controller:0.1.0

Sealos generates the controller manifest, dedicated ServiceAccount/read-only RBAC and a private kubeconfig on each master, including added masters. Kubernetes issues the token through a ServiceAccount token Secret; controller credentials contain no administrator certificate or key. An explicit --route-controller-kubeconfig selects administrator-managed credentials instead. Existing custom deployments can select automatic credentials with --route-controller-kubeconfig='' and, if already standalone, --update-controller.

--update-controller updates an active controller's image, credential/config paths or reserved route table/protocol. Unspecified settings retain their saved values; the image override is persisted in the Clusterfile. The default image is ghcr.io/labring-sigs/route-controller:0.1.0.

Lifecycle behavior

Operation Behavior
Fresh standalone install Compose public kubeadm certificate, kubeconfig and static-manifest phases; start standalone kubelet; generate controller credentials after the API starts; publish worker-compatible configuration. Initial master joins do not wait for networking that is installed later.
Add/delete masters Manage static components and stacked-etcd membership without temporary Node registration. Added members join as learners before promotion. Removal checks surviving quorum; explicit deletion also removes any retained converted Node with an identity precondition. The first-master deletion restriction remains.
Add/delete workers Reuse registered-worker lifecycle, with retry and identity guards for managed standalone operations.
sealos run <kubernetes-image> Upgrade masters sequentially, publish configuration, then upgrade workers and existing CoreDNS/kube-proxy. Reject downgrades and skipped minor versions; retain newer etcd versions.
Certificates Retain certificate commands; restart static components through CRI.
Reset Preserve best-effort cleanup and timestamped Clusterfile archival. Successful host cleanup also removes mode state so later installation cannot reuse stale baselines. Inspect logged cleanup failures before reinstalling.

Mode conversion and recovery

All masters pass preflight before sequential conversion. Host baselines preserve original kubelet settings and Node metadata; the Clusterfile mode is committed after all masters complete. Locks, an API Lease and persistent journals coordinate operations and retries. Lease cancellation propagates through active SSH/SFTP operations and Kubernetes mutations.

Standalone conversion updates the active kubelet configuration and installs a systemd override that clears API credentials and disables registration. It starts route-controller and restarts kubelet immediately. It does not drain or delete Nodes, and does not wait for controller readiness during conversion. Administrators handle workload migration, stale Node/network cleanup and host reboots. Explicit controller updates and joins into an established cluster wait for controller readiness.

Reverse conversion obtains fresh kubelet credentials through the CSR API, removes the controller and its owned routes, removes generated host credentials, and restores registered settings. Shared controller RBAC remains available for reuse. Node metadata is restored under a temporary scheduling taint, followed by Node Ready and mirror Pod checks. Previously registered hosts return to their original service configuration; fresh standalone hosts receive a managed registered baseline.

Upgrades use public kubeadm CLI phases, CRI v1, etcd v3 and client-go, without kubeadm internal imports. Only kubeadm documents enter configuration migration; component settings are preserved separately and checked through native phase decoding. This avoids rejecting newer default fields that older kubeadm/kubelet accept with warnings. This composition is maintained by Sealos; upstream kubeadm does not provide a standalone lifecycle. Manifest staging uses a private mount namespace. Configuration backups and local-etcd snapshots are retained, but failures do not automatically restore etcd or downgrade components. Workers are cordoned without eviction; administrator cordons are preserved.

Requirements and scope

  • Kubernetes 1.28+, Linux/systemd, standard kubeadm paths, CRI v1 and matching updated Sealos/rootfs sealctl. Upgrade staging requires unshare and mount.
  • Sealos does not configure CNI. Administrators own CNI exclusions, forwarding and Pod/Service connectivity. Component readiness alone does not establish network compatibility.
  • Bootstrap requires shared CA/service-account keys. External-CA-only and rootless bootstrap are unsupported. Reverse conversion requires a working kubelet certificate signer.
  • External etcd is not upgraded. The guarded etcd 3.5-to-3.6 transition requires all members and the snapshot tool at a supported 3.5.32+ release.
  • Script-managed standalone installations without a Sealos baseline are not automatically adopted. Failed initial installations require reset before reinstalling; other interrupted operations resume through their original commands. Submit mixed additions/removals separately.

Validation

Relevant package tests and diff-scoped lint pass. Real-cluster tests of automatic credentials cover missing-file preflight without mutations, registered-to-standalone conversion, control-plane deletion/readdition, restricted API permissions and reverse-conversion cleanup. Two fresh multi-master installations on Kubernetes 1.28.15 completed with worker-only Node registration. Registered reset followed by reinstallation confirmed mode-state cleanup without manual intervention. A subsequent same-version sealos run upgraded all masters and workers successfully. Native configuration checks accept newer unknown fields, reject invalid field types, and leave active kubelet configuration and process unchanged.

Pod IP and DNS Service access passed in this round. API ClusterIP access from standalone hosts still requires CNI troubleshooting; this PR does not configure CNI forwarding or masquerading.

Earlier integration rounds cover ordinary/standalone scaling, repeated mode conversion, same-version retries and sequential Kubernetes upgrades from 1.28 through 1.33.6. These are historical coverage, not a full rerun of every scenario at the current head. External etcd, the 3.5-to-3.6 transition and broader failure/patch layouts still need dedicated coverage. Opt-in integration fixtures are separate from ordinary PR CI.

Operational details and test entry points are in lifecycle/pkg/runtime/kubernetes/standalone/README.md and TESTING.md. Current CI results are reported in the PR Checks tab.

@pull-request-size

Copy link
Copy Markdown

Whoa! Easy there, Partner!

This PR is too big. Please break it up into smaller PRs.

@zijiren233 zijiren233 changed the title feat: standalone control plane lifecycle feat(lifecycle): standalone control plane lifecycle Oct 8, 2026
@zijiren233
zijiren233 requested a balanced review from Copilot October 12, 2026 03:34

Copilot AI left a comment

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.

🟡 Changes recommended

Lease cancellation is not consistently propagated through remote I/O and Kubernetes API operations, allowing lifecycle mutations after lock ownership is lost.

6 open findings
What changed in this PR

Adds standalone control-plane lifecycle management, including mode conversion, upgrades, membership changes, recovery journals, and diagnostics.

Changes:

  • Adds standalone bootstrap, upgrade, reset, routing, and worker lifecycle flows.
  • Adds lifecycle locking, recovery, cancellation handling, and CLI commands.
  • Expands unit, integration, and E2E coverage.
File Description
lifecycle/​test/​e2e/​testhelper/​utils/​utils.go Logs host diagnostics on failure.
lifecycle/​test/​e2e/​testhelper/​utils/​diagnostics.go Adds host and K3s diagnostics.
lifecycle/​test/​e2e/​testhelper/​cmd/​sealosCmdOpts_timeout_test.go Removes obsolete option tests.
lifecycle/​test/​e2e/​testhelper/​cmd/​sealosCmdOpts_test.go Strengthens command argument tests.
lifecycle/​test/​e2e/​k3s_test.go Updates K3s image and readiness handling.
lifecycle/​test/​e2e/​e2e_test.go Reserves Kubernetes service ports.
lifecycle/​pkg/​types/​v1beta1/​zz_generated.deepcopy.go Adds generated route-controller copies.
lifecycle/​pkg/​types/​v1beta1/​utils.go Adds mode helpers and rootfs replacement cleanup.
lifecycle/​pkg/​types/​v1beta1/​utils_test.go Tests same-version rootfs replacement.
lifecycle/​pkg/​types/​v1beta1/​cluster.go Defines standalone mode configuration.
lifecycle/​pkg/​runtime/​kubernetes/​worker_maintenance.go Adds identity-safe worker maintenance.
lifecycle/​pkg/​runtime/​kubernetes/​upgrade_test.go Extends upgrade configuration tests.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​TESTING.md Documents standalone validation.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​testdata/​mode-controller.Dockerfile Builds controller test fixture.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​route_controller_test.go Tests generated controller manifests.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​reset_baseline.go Reconstructs reset state safely.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​mode_test.go Tests mode conversion invariants.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​mode_routes_other.go Rejects routing on non-Linux systems.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​mode_routes_linux.go Manages owned Linux routes.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​mode_csr_test.go Tests CSR approval races and identity.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​mode_config.go Converts and persists kubelet configuration.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​membership.go Manages etcd membership and quorum.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​maintenance.go Adds host maintenance locking.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​files.go Adds recoverable file replacement.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​files_test.go Tests partial replacement recovery.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​e2e_test.go Adds opt-in standalone fixture activation.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​controller_update.go Implements controller updates.
lifecycle/​pkg/​runtime/​kubernetes/​standalone/​bootstrap_validation.go Validates bootstrap layouts.
lifecycle/​pkg/​runtime/​kubernetes/​standalone_removal.go Implements standalone master removal.
lifecycle/​pkg/​runtime/​kubernetes/​standalone_removal_test.go Tests removal identity safeguards.
lifecycle/​pkg/​runtime/​kubernetes/​standalone_mode_test.go Tests mode routing and membership validation.
lifecycle/​pkg/​runtime/​kubernetes/​runtime.go Dispatches standalone lifecycle operations.
lifecycle/​pkg/​runtime/​kubernetes/​runtime_version_test.go Tests Kubernetes version selection.
lifecycle/​pkg/​runtime/​kubernetes/​runtime_getter.go Adds rootfs version fallback.
lifecycle/​pkg/​runtime/​kubernetes/​node.go Makes worker joins/deletions resumable.
lifecycle/​pkg/​runtime/​kubernetes/​maintenance_context.go Wraps SSH operations with cancellation.
lifecycle/​pkg/​runtime/​kubernetes/​maintenance_context_test.go Tests maintenance cancellation.
lifecycle/​pkg/​runtime/​kubernetes/​kubeconfig.go Restarts static-pod containers safely.
lifecycle/​pkg/​runtime/​kubernetes/​kubeconfig_test.go Tests static-pod restart commands.
lifecycle/​pkg/​runtime/​kubernetes/​kubeadm.go Preserves endpoints and runtime settings.
lifecycle/​pkg/​runtime/​kubernetes/​control_plane_mode_test.go Tests cluster-wide mode conversion.
lifecycle/​pkg/​runtime/​kubernetes/​certs.go Restarts API servers after certificate updates.
lifecycle/​pkg/​guest/​guest.go Skips conflicting hooks on standalone masters.
lifecycle/​pkg/​clusterfile/​pre_process.go Supports recovery inventory loading.
lifecycle/​pkg/​clusterfile/​maintenance.go Adds local lifecycle locks and atomic writes.
lifecycle/​pkg/​clusterfile/​maintenance_test.go Tests mode locking and journals.
lifecycle/​pkg/​clusterfile/​maintenance_api.go Adds API leases and transition guards.
lifecycle/​pkg/​clusterfile/​join_preflight.go Journals host preflight completion.
lifecycle/​pkg/​clusterfile/​join_preflight_test.go Tests resumable preflight checks.
lifecycle/​pkg/​clusterfile/​clusterfile.go Adds reset-recovery loading option.
lifecycle/​pkg/​bootstrap/​maintenance_context_test.go Tests bootstrap deadlines.
lifecycle/​pkg/​bootstrap/​context.go Propagates maintenance context.
lifecycle/​pkg/​bootstrap/​bootstrap.go Journals standalone rootfs preflights.
lifecycle/​pkg/​apply/​reset.go Enables interrupted reset recovery.
lifecycle/​pkg/​apply/​processor/​standalone_test.go Tests reset and scaling recovery.
lifecycle/​pkg/​apply/​processor/​standalone_reset.go Adds resumable reset pipeline.
lifecycle/​pkg/​apply/​processor/​rootfs_selection_test.go Tests committed rootfs selection.
lifecycle/​pkg/​apply/​processor/​maintenance_context.go Connects processors to cancellation.
lifecycle/​pkg/​apply/​processor/​interface.go Removes obsolete rootfs mounts.
lifecycle/​pkg/​apply/​processor/​install.go Propagates lifecycle context during installs.
lifecycle/​pkg/​apply/​processor/​delete.go Uses resumable standalone deletion.
lifecycle/​pkg/​apply/​processor/​create.go Propagates lifecycle context during creation.
lifecycle/​pkg/​apply/​applydrivers/​standalone_lifecycle_test.go Tests inventory recovery and synchronization.
lifecycle/​pkg/​apply/​applydrivers/​control_plane_mode_test.go Tests lifecycle mode guards.
lifecycle/​cmd/​sealos/​cmd/​switch.go Adds the sealos switch command group.
lifecycle/​cmd/​sealos/​cmd/​root.go Registers the switch command.
lifecycle/​cmd/​sealos/​cmd/​control_plane_mode.go Adds cluster-wide mode-switch CLI.
lifecycle/​cmd/​sealos/​cmd/​control_plane_mode_test.go Tests switch flags and confirmation.
lifecycle/​cmd/​sealos/​cmd/​cert.go Guards certificate maintenance with locks.
lifecycle/​cmd/​sealctl/​cmd/​standalone.go Adds host bootstrap/reset utilities.
lifecycle/​cmd/​sealctl/​cmd/​standalone_upgrade.go Adds local standalone upgrades.
lifecycle/​cmd/​sealctl/​cmd/​root.go Registers standalone sealctl commands.
lifecycle/​cmd/​sealctl/​cmd/​control_plane_mode.go Adds host-local mode switching.
lifecycle/​cmd/​sealctl/​cmd/​control_plane_mode_test.go Tests host switch flag isolation.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lifecycle/pkg/bootstrap/context.go Outdated
Comment thread lifecycle/pkg/runtime/kubernetes/maintenance_context.go Outdated
Comment thread lifecycle/pkg/runtime/kubernetes/standalone_upgrade.go
Comment thread lifecycle/pkg/runtime/kubernetes/worker_maintenance.go Outdated
Comment thread lifecycle/pkg/runtime/kubernetes/standalone/e2e_test.go Outdated
Comment thread lifecycle/pkg/runtime/kubernetes/standalone/upgrade.go Outdated
@cuisongliu cuisongliu added this to the v5.2 milestone Oct 13, 2026
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 14.28571% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.49%. Comparing base (fa0fbc7) to head (ba4070a).
⚠️ Report is 26 commits behind head on main.

Files with missing lines Patch % Lines
lifecycle/pkg/apply/reset.go 14.28% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7344      +/-   ##
==========================================
- Coverage   61.86%   61.49%   -0.38%     
==========================================
  Files           8        8              
  Lines         653      657       +4     
==========================================
  Hits          404      404              
- Misses        202      206       +4     
  Partials       47       47              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cuisongliu
cuisongliu marked this pull request as draft October 16, 2026 02:30
@zijiren233
zijiren233 force-pushed the feat/standalone-control-plane-lifecycle branch 3 times, most recently from e631e55 to 8d94416 Compare October 17, 2026 02:00
Generate dedicated read-only controller credentials for installation and mode conversion. Preserve native kubeadm component decoding during bootstrap and upgrades, and remove stale mode state during registered reset.
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
…otstrap

Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
…ts and early RBAC

Merging a Clusterfile that restates default values appended duplicate
enforceNodeAllocatable entries; kubelet rejects the duplicated list and
refuses to start during bootstrap. Deduplicate the list after each merge.

The controller credential readiness probe aborted on Forbidden before a
fresh API server bootstrapped its default RBAC roles, failing the whole
apply. Keep polling instead; the host context still bounds the wait.
Add --delete-master-nodes to 'sealos switch control-plane standalone'. Each
master deletes its own registered Node object right after its kubelet runs
standalone, guarded by the recorded registration UID, so the pod garbage
collector can remove orphaned workloads. The flag defaults to off and is
rejected for registered mode; reruns are idempotent.
@zijiren233
zijiren233 force-pushed the feat/standalone-control-plane-lifecycle branch from 8d94416 to ecbfafd Compare October 17, 2026 04:25
A conversion to standalone only inherited routeController.image from the
committed Clusterfile, while a fresh standalone install honored every
field. Custom kubeconfig, config, table and protocol selections were
silently dropped: a conversion after adding a master fell back to
managed credentials without any warning, and an operator-provided
kubeconfig had to be repeated on every invocation.

Conversions now inherit each committed setting the operator did not
select with a flag, and the mode commit persists the effective settings
next to controlPlaneMode so later conversions and node additions reuse
them. Registered conversions keep leaving the committed settings
untouched.
The --check-only conversion preflight validated host files but never
exercised admission, so a policy denying the managed route-controller
RBAC only surfaced after a conversion had started and paused mid-flight.

Preflight now server-side dry-runs every object the managed credential
flow creates: the ServiceAccount, ClusterRole, ClusterRoleBinding, Role,
RoleBinding and the token Secret. Validating admission sees dry-run
requests, so denials appear in --check-only output with the offending
object and webhook, while nothing is persisted. Existing objects are
accepted; controllers rendered with a custom kubeconfig keep skipping
credential work entirely.

The admin client construction is shared with the real credential flow,
and the token Secret dry-run carries the service-account name annotation
API validation requires.
Clusters on cloud or overlay CNIs never consume the host routing table
the controller programs, and the control plane resolves pod IPs
directly, so deploying it there only costs an image, a static pod, and
a kubeconfig. Add --route-controller-enabled (spec.routeController.
enabled; nil keeps today's behavior) to sealos and sealctl.

Disabling stops the static pod, removes its manifest, and drops the
routes the controller owned; managed credentials are left in place for
a later re-enable. The choice is committed to the Clusterfile and
inherited by later conversions, installs, and joins, and every
bootstrap, readiness, and credential step is skipped while disabled.
Masters lose direct Service ClusterIP access, which is the documented
trade-off.
@zijiren233
zijiren233 force-pushed the feat/standalone-control-plane-lifecycle branch from f0e79dd to c6d0341 Compare October 21, 2026 07:27
Disabling stopped the static pod, its manifest, and its routes, but
left the managed kubeconfig on every host and the shared
ServiceAccount, token Secret, and RBAC objects in the API. A disabled
cluster therefore kept a live credential it no longer used.

A disabling update now records whether the replaced deployment used
managed credentials and drops the generated kubeconfig with the rest
of the host state. Once every host of a switch finishes disabled, the
operator-side sealos deletes the shared identity; deleting it per host
would revoke the token controllers on the remaining hosts still use.
Objects without the managed label are refused, missing ones count as
removed, and re-enabling recreates everything from scratch.
@zijiren233
zijiren233 force-pushed the feat/standalone-control-plane-lifecycle branch from 7853c4e to 85a9d9d Compare October 21, 2026 08:12
The controller scopes its routes with --pod-cidr, --service-cidr and
--router-cidr, but sealos only offered a config file path: the file had
to be distributed to every host by hand, a later switch regenerated
the manifest around it, and a joining master failed its preflight
until the file appeared. Operator policy like the worker CIDR allowed
to serve as a next hop deserves first-class management.

Add --route-controller-{pod,service,router}-cidr to sealos and sealctl.
Values are validated at the CLI, rendered as controller arguments,
inherited from the committed Clusterfile by later conversions, installs
and joins, and written back as spec.routeController.{podCIDR,
serviceCIDR,routerCIDR}. Passing an explicit empty flag clears a
selection and removes the key, restoring automatic discovery; an unset
flag keeps the saved value. --interface stays per-host and is not
persisted.
Installing a standalone cluster with the controller disabled wrote a
baseline state whose ControllerDisabled stayed false while its manifest
was empty: controller-config then failed with an invalid baseline, and
a joining master consumed that broken seed. The join path also tried
to pre-pull the controller image before bootstrap even when the plan
deployed none, failing on the empty image name.

Record ControllerDisabled from the bootstrap plan next to the manifest
it explains, and pull the controller image only when the plan enables
it. Verified by reinstalling the cluster from scratch with the
controller disabled and joining two masters: no manifest, no
credentials, no readiness wait, and a usable controller-config.
@zijiren233
zijiren233 marked this pull request as ready for review October 27, 2026 10:01
The public ECR gallery caps anonymous data transfer and shared GitHub
runner addresses have exhausted the quota, so every run fails the
remote pull specs with 429 toomanyrequests. mirror.gcr.io serves the
same Docker Hub images without an anonymous quota; all four benchmark
tags are available there.
@cuisongliu
cuisongliu merged commit f219979 into labring:main Oct 28, 2026
98 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🤖 says: cherry pick action finished successfully 🎉!
See: https://github.com/labring/sealos/actions/runs/40667741545

cuisongliu pushed a commit that referenced this pull request Oct 28, 2026
feat(lifecycle): standalone control plane lifecycle (#7344)

* feat: add standalone control plane lifecycle

* fix: do not block mode switch on route readiness

* feat: add standalone control-plane lifecycle

* chore(standalone): add fixture license headers

* fix(standalone): preserve fixture shebang

* fix(standalone): annotate intentional probes and commands

* fix(standalone): complete security checks and lifecycle regression

* test(e2e): allow K3s pods time to start

* test(e2e): align command option tests with current CLI

* test(e2e): capture host state before failure cleanup

* test(e2e): retain failure diagnostics under sudo

* fix(kubernetes): restart containers when reloading API certificates

* test(e2e): reserve service ports from ephemeral allocation

* test(e2e): capture K3s startup diagnostics before reset

* test(e2e): use K3s release with CoreDNS cache fix

* fix(lifecycle): preserve standalone recovery and reset behavior

* style(lifecycle): format recovery tests with repository formatters

* refactor: simplify standalone lifecycle helpers and documentation

* refactor: simplify lifecycle inventory and worker identity checks



* refactor(lifecycle): group control-plane switches under dedicated subcommand

* fix(lifecycle): align standalone kubeconfig handling with upstream

* fix(lifecycle): satisfy lint after upstream rebase

* test(lifecycle): preallocate switch command arguments

* fix(lifecycle): remove stale Nodes when deleting standalone masters

* chore(lifecycle): use standard full license headers

* feat(lifecycle): persist custom route controller images

* fix lifecycle review findings

* chore(lifecycle): align standalone gosec annotations

* chore(lifecycle): annotate controlled upgrade command execution

* fix(lifecycle): preserve newer etcd during standalone upgrades

* style(lifecycle): format etcd preservation message

* docs(lifecycle): record sequential upgrade validation and port requirements

* fix(lifecycle): publish standalone worker config without a Node

* docs(lifecycle): shorten standalone usage and testing guides

* fix(lifecycle): sync sealctl before control-plane switch

* style(lifecycle): format sealctl switch synchronization

* fix(lifecycle): preserve best-effort standalone reset behavior

* fix(lifecycle): discard previous reset completion on mode conversion

* feat(lifecycle): provision standalone controller credentials

Generate dedicated read-only controller credentials for installation and mode conversion. Preserve native kubeadm component decoding during bootstrap and upgrades, and remove stale mode state during registered reset.

* refactor(lifecycle): reuse standalone reset file cleanup

* docs(lifecycle): remove hard wrapping from standalone paragraphs

* refactor(lifecycle): simplify standalone file checks and host execution



* test(lifecycle): handle retained Nodes in membership checks



* fix(lifecycle): refresh shared certificates before adding masters



* test(lifecycle): cover both token cache refresh paths



* fix(lifecycle): terminate temporary registry with sync context



* docs(lifecycle): clarify regression coverage and network prerequisites



* fix(lifecycle): preserve cluster signing material after standalone bootstrap



* fix(lifecycle): recover fresh standalone installs from doubled defaults and early RBAC

Merging a Clusterfile that restates default values appended duplicate
enforceNodeAllocatable entries; kubelet rejects the duplicated list and
refuses to start during bootstrap. Deduplicate the list after each merge.

The controller credential readiness probe aborted on Forbidden before a
fresh API server bootstrapped its default RBAC roles, failing the whole
apply. Keep polling instead; the host context still bounds the wait.

* feat(switch): opt-in Node object cleanup for standalone conversion

Add --delete-master-nodes to 'sealos switch control-plane standalone'. Each
master deletes its own registered Node object right after its kubelet runs
standalone, guarded by the recorded registration UID, so the pod garbage
collector can remove orphaned workloads. The flag defaults to off and is
rejected for registered mode; reruns are idempotent.

* fix(switch): inherit and commit route-controller settings

A conversion to standalone only inherited routeController.image from the
committed Clusterfile, while a fresh standalone install honored every
field. Custom kubeconfig, config, table and protocol selections were
silently dropped: a conversion after adding a master fell back to
managed credentials without any warning, and an operator-provided
kubeconfig had to be repeated on every invocation.

Conversions now inherit each committed setting the operator did not
select with a flag, and the mode commit persists the effective settings
next to controlPlaneMode so later conversions and node additions reuse
them. Registered conversions keep leaving the committed settings
untouched.

* feat(switch): admission preflight for managed credentials

The --check-only conversion preflight validated host files but never
exercised admission, so a policy denying the managed route-controller
RBAC only surfaced after a conversion had started and paused mid-flight.

Preflight now server-side dry-runs every object the managed credential
flow creates: the ServiceAccount, ClusterRole, ClusterRoleBinding, Role,
RoleBinding and the token Secret. Validating admission sees dry-run
requests, so denials appear in --check-only output with the offending
object and webhook, while nothing is persisted. Existing objects are
accepted; controllers rendered with a custom kubeconfig keep skipping
credential work entirely.

The admin client construction is shared with the real credential flow,
and the token Secret dry-run carries the service-account name annotation
API validation requires.

* feat(switch): allow disabling the route controller

Clusters on cloud or overlay CNIs never consume the host routing table
the controller programs, and the control plane resolves pod IPs
directly, so deploying it there only costs an image, a static pod, and
a kubeconfig. Add --route-controller-enabled (spec.routeController.
enabled; nil keeps today's behavior) to sealos and sealctl.

Disabling stops the static pod, removes its manifest, and drops the
routes the controller owned; managed credentials are left in place for
a later re-enable. The choice is committed to the Clusterfile and
inherited by later conversions, installs, and joins, and every
bootstrap, readiness, and credential step is skipped while disabled.
Masters lose direct Service ClusterIP access, which is the documented
trade-off.

* feat(switch): clean up credentials when disabling route-controller

Disabling stopped the static pod, its manifest, and its routes, but
left the managed kubeconfig on every host and the shared
ServiceAccount, token Secret, and RBAC objects in the API. A disabled
cluster therefore kept a live credential it no longer used.

A disabling update now records whether the replaced deployment used
managed credentials and drops the generated kubeconfig with the rest
of the host state. Once every host of a switch finishes disabled, the
operator-side sealos deletes the shared identity; deleting it per host
would revoke the token controllers on the remaining hosts still use.
Objects without the managed label are refused, missing ones count as
removed, and re-enabling recreates everything from scratch.

* feat(switch): persist route-controller CIDR selection

The controller scopes its routes with --pod-cidr, --service-cidr and
--router-cidr, but sealos only offered a config file path: the file had
to be distributed to every host by hand, a later switch regenerated
the manifest around it, and a joining master failed its preflight
until the file appeared. Operator policy like the worker CIDR allowed
to serve as a next hop deserves first-class management.

Add --route-controller-{pod,service,router}-cidr to sealos and sealctl.
Values are validated at the CLI, rendered as controller arguments,
inherited from the committed Clusterfile by later conversions, installs
and joins, and written back as spec.routeController.{podCIDR,
serviceCIDR,routerCIDR}. Passing an explicit empty flag clears a
selection and removes the key, restoring automatic discovery; an unset
flag keeps the saved value. --interface stays per-host and is not
persisted.

* fix(switch): keep the disabled baseline consistent on bootstrap

Installing a standalone cluster with the controller disabled wrote a
baseline state whose ControllerDisabled stayed false while its manifest
was empty: controller-config then failed with an invalid baseline, and
a joining master consumed that broken seed. The join path also tried
to pre-pull the controller image before bootstrap even when the plan
deployed none, failing on the empty image name.

Record ControllerDisabled from the bootstrap plan next to the manifest
it explains, and pull the controller image only when the plan enables
it. Verified by reinstalling the cluster from scratch with the
controller disabled and joining two masters: no manifest, no
credentials, no readiness wait, and a usable controller-config.

* test(e2e): pull benchmark images from mirror.gcr.io

The public ECR gallery caps anonymous data transfer and shared GitHub
runner addresses have exhausted the quota, so every run fails the
remote pull specs with 429 toomanyrequests. mirror.gcr.io serves the
same Docker Hub images without an anonymous quota; all four benchmark
tags are available there.

---------

Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
Co-authored-by: zijiren <84728412+zijiren233@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants