Repository navigation
Description
Describe the bug
Changing a platform-level value that most applications never read, such as publishing.apiServerEndpoint, upgrades every application HelmRelease in every tenant, and each of those upgrades starts Jobs again even though the chart renders byte-for-byte the same manifest. On a production cluster with ~1,580 HelmReleases this created about 80 Jobs within a minute and saturated pod creation for a while.
Two mechanisms, neither of which is a content hash picking up unrelated values:
_clusteris copied into each namespace'scozystack-valuesSecret and fed to every HelmRelease throughvaluesFrom. Flux upgrades a release whenever its values digest changes, regardless of whether the render changes. So any_clusterkey change upgrades every app, including apps that never read that key (hereapi-server-endpointis read only bypackages/extra/info, andexpose-servicesby none of the apps below).- On that no-op upgrade, Jobs run again:
post-install,post-upgradehooks run on every upgrade by Helm's definition:postgres<release>-init-job,kubernetes<release>-oidc-bootstrap, the monitoringoidc-usershook.- The main-phase Jobs
kubernetes<release>-bootstrap-token-tenant-<hash>andkubernetes-nodes(formerlykubernetes)<release>-talos-reconcile-<group>-<hash>keep the same content-hash name, butttlSecondsAfterFinished: 600deleted them minutes after their previous run. Helm finds a resource of the new manifest missing from the cluster and creates it again. The template comment anticipates this ("only an actual upgrade does"), but a platform-level value change makes it an upgrade of every cluster at once.
Environment
- Cozystack v1.6.5 (the templates involved are unchanged in this respect on
mainat 55aad6f) - Provider: on-prem
To Reproduce
- Run a platform with a few Postgres and Kubernetes applications.
- Change
publishing.apiServerEndpoint(or any other_clusterkey) in the platform Package. - Watch
kubectl get jobs -A --sort-by=.metadata.creationTimestamp.
Expected behaviour
A platform value that an application does not use does not run that application's Jobs again, and ideally does not upgrade its release at all.
Actual behaviour
Measured on the cluster above after changing publishing.apiServerEndpoint (https://api.<domain>:443 → https://api.<other-domain>:6443; the same change also dropped api from expose-services). Jobs created within the following minute:
| Job | Chart | Kind | Count |
|---|---|---|---|
<release>-talos-reconcile-<group>-<hash> |
kubernetes | main phase, TTL 600 s | 27 |
<release>-oidc-bootstrap |
kubernetes | post-install,post-upgrade hook | 20 |
<release>-init-job |
postgres | post-install,post-upgrade hook | 15 |
<release>-bootstrap-token-tenant-<hash> |
kubernetes | main phase, TTL 600 s | 15 |
monitoring-system-oidc-users |
monitoring | post-install,post-upgrade hook | 1 |
For the 36 releases behind those Jobs, I compared the manifest stored in the last two Helm release Secrets (the revision before the change and the one it produced). 31 are byte-identical. The other 5 are Postgres releases whose only difference is the order of the database names in the init script, a separate rendering bug fixed in #5091. Example, one kubernetes release, revisions 49 → 50:
_cluster.api-server-endpoint: https://api.<domain>:443 -> https://api.<other-domain>:6443
_cluster.expose-services: api,dashboard,... -> dashboard,...
manifest diff: 0 lines
Jobs in manifest (both): <release>-bootstrap-token-tenant-53dbf6, <release>-talos-reconcile-md0-f308bf
talos-reconcile Job creationTimestamp: the minute of the change
The same holds with helm template on v1.6.5, rendering packages/apps/kubernetes and packages/apps/postgres with two _cluster blocks that differ only in api-server-endpoint: the only differences in the output are the random values helm template generates for <release>-talos-secrets (no lookup offline), and the Job names are the same:
a: kubernetes-x-bootstrap-token-tenant-b741a0 kubernetes-x-talos-reconcile-md0-8d5cb1 kubernetes-x-pre-cleanup kubernetes-x-oidc-bootstrap
b: kubernetes-x-bootstrap-token-tenant-b741a0 kubernetes-x-talos-reconcile-md0-8d5cb1 kubernetes-x-pre-cleanup kubernetes-x-oidc-bootstrap
postgres (a, b): postgres-x-init-job, no diff
Additional context
Options, roughly from the root cause outward. They are design choices for maintainers, which is why this is an issue and not a PR:
- Do not upgrade an app for a
_clusterkey it does not read. For instance, let anApplicationDefinitiondeclare the_clusterkeys its chart consumes and project only those into the values it receives, or split the rarely read keys (api-server-endpoint,expose-services, branding) out of the block every app gets. This removes the upgrade storm itself, Jobs or not. - Make the main-phase Jobs survive a successful run. With no
ttlSecondsAfterFinishedon success, a no-op upgrade finds the Complete Job in place and does nothing. The TTL exists so that a Failed Job is retried on the next upgrade (fix(kubernetes): run the Talos reconcile Job in the main install phase #3145, fix(kubernetes): break bootstrap-token Job out of Helm hook to unblock fresh installs #3887); keeping that needs something other than a TTL that applies to both outcomes, e.g. apodFailurePolicywith a largebackoffLimit, or a periodic cleanup of Failed Jobs only. - Hooks. fix(postgres): run init-job in main phase and wait for the superuser secret #3923 moves the postgres init Job to the main phase with a content-hash name; it would then inherit the TTL behaviour above, so the two decisions interact.
oidc-bootstrapand the monitoringoidc-usershook would need the same treatment to stop running on every upgrade.
Related: #2497 (umbrella on bootstrap workarounds), #4342 (another source of upgrades the user never asked for).
Checklist
- I have checked the documentation
- I have searched for similar issues
- I have included all required information
- I have provided clear steps to reproduce
- I have included relevant logs
Activity
fix(postgres): render the declared databases in a stable order (#5091)
fix(postgres): render the declared databases in a stable order (#5091)
fix(postgres): render the declared databases in a stable order (#5091)