Repository navigation
Description
Summary
Fresh Talos worker VMs come up healthy, apid responds, kubelet service is Running/OK, but nodes never register in the workload cluster. All API calls from kubelet are silently rejected as system:anonymous, including the CSR that would give kubelet its client certificate. Machines stay Provisioned indefinitely; the workload cluster ends up with zero Nodes.
Root cause: the workload cluster's kube-system namespace does not contain the bootstrap-token-<id> Secret that matches the token embedded in the TalosConfig bootstrap data. The token is generated and referenced by TalosConfig (cluster.token: <id>.<secret>), but nothing on the Cozystack side creates the corresponding bootstrap-token Secret inside the workload cluster.
Symptom overlaps with #3513 (0 nodes registered, machines Provisioned, VMIs healthy), but the failure mode is deterministic on affected clusters, not flaky. #3513 tracks other manifestations of the same "no nodes register" outcome.
Reproduction / evidence
On an affected tenant Kubernetes CR (a prod cluster that ran for weeks with the same misconfig):
$ kubectl --context host --namespace tenant-x get machines
NAME CLUSTER NODENAME PROVIDERID PHASE AGE VERSION
ht-apps-abc-def kubevirt://... Provisioned 11d v1.33.13
ht-apps-abc-ghi kubevirt://... Provisioned 11d v1.33.13
$ kubectl --context tenantkubeconfig get nodes
No resources found
$ kubectl --context host --namespace tenant-x get vmi
# all Running, agent connected, IPs assigned
talosctl service kubelet on each worker:
STATE Running
HEALTH OK
EVENTS [Waiting]: task "kubelet" failed: exit code 1
(last log "No valid client certificate is found and the server is
responsive, exiting.")
talosctl logs kubelet (looping every ~7s):
"Failed to ensure lease exists":
leases.coordination.k8s.io "ht-apps-abc-def" is forbidden:
User "system:anonymous" cannot get resource "leases" ...
"Unable to register node with API server":
nodes "ht-apps-abc-def" is forbidden:
User "system:anonymous" cannot get resource "nodes" ...
"Failed while requesting a signed certificate from the control plane":
cannot create certificate signing request: Unauthorized
bootstrap-token present in the TalosConfig secret:
$ kubectl --context host --namespace tenant-x \
get secret ht-apps-abc-def-bootstrap-data \
--output=jsonpath='{.data.value}' | base64 -d | grep -E 'token:'
token: <id>.<secret> # machine.token (Talos join)
token: mcvbbi.<secret> # cluster.token (kubelet bootstrap) <- this one
Same mcvbbi id is in ${release}-talos-secrets under bootstrapTokenId.
bootstrap-token-mcvbbi Secret in workload kube-system:
$ kubectl --context tenantkubeconfig --namespace kube-system \
get secret --field-selector=type=bootstrap.kubernetes.io/token
No resources found in kube-system namespace.
Comparison with a working cluster
Same chart version, another tenant that has been healthy since day one:
$ kubectl --context tenantkubeconfig-staging --namespace kube-system \
get secret --field-selector=type=bootstrap.kubernetes.io/token
NAME TYPE DATA AGE
bootstrap-token-ac7loi bootstrap.kubernetes.io/token 6 11d
ac7loi matches bootstrapTokenId in that tenant's ${release}-talos-secrets. The difference between working and broken clusters is exactly whether this Secret exists.
Fix that unblocks the cluster
Manually create the missing Secret in the workload cluster:
apiVersion: v1
kind: Secret
metadata:
name: bootstrap-token-<id>
namespace: kube-system
type: bootstrap.kubernetes.io/token
stringData:
token-id: <id>
token-secret: <secret>
usage-bootstrap-authentication: "true"
usage-bootstrap-signing: "true"
auth-extra-groups: system:bootstrappers:kubeadm:default-node-tokenValues taken from ${release}-talos-secrets.data.bootstrapTokenId / bootstrapTokenSecret.
After applying: kubelet immediately transitions to authenticated, CSR is auto-approved by kubeadm:node-autoapprove-bootstrap (present in the workload cluster), nodes become Ready, workloads move.
Suggested cozystack change
Either:
-
talos-reconcileJob should alsokubectl applya bootstrap-token Secret against the workload cluster (it already assembles the samebootstrapTokenId/Secretvalues), or -
The tenant
Kuberneteschart should provision this Secret at cluster-init time and re-provision it if the underlying${release}-talos-secretsrotates.
Currently the CSR-approver ClusterRoleBinding (kubeadm:node-autoapprove-bootstrap) is present but useless without the matching bootstrap-token Secret.
Environment
- Cozystack v1.6.x, Kamaji-backed tenant
Kubernetesclusters - Chart version:
0.0.0+7c18a8ec4e4d - Reproduced on two independent tenant clusters (long-running prod, not a fresh e2e install)
- Not reproduced on a sibling tenant that was created earlier and has the Secret present
Notes
- Related: E2E: tenant worker bring-up misses the node-Ready budget with zero Nodes registered — survived #3455's fsync fix and the 12m→18m raise #3513 (same "0 nodes registered" outcome — different root cause though; E2E: tenant worker bring-up misses the node-Ready budget with zero Nodes registered — survived #3455's fsync fix and the 12m→18m raise #3513's traces point at pull/CRI issues before kubelet even connects, this one is post-connect auth failure)
- Related: talos-reconcile Job is OOMKilled at 256Mi and never completes #3519 (talos-reconcile Job OOM at 256Mi — orthogonal to this bug, but blocks the reconcile Job that could have applied the bootstrap-token fix)
Activity
IvanHunters commentedon Aug 17, 2026
Root cause located in cozystack code
The chart does ship this Job —
packages/apps/kubernetes/templates/talos/bootstrap-token-tenant-job.yaml(added in 39cdc88 on 2026-06-16, hardened in b5cc2ae and a4c6a07). So the intent is present.The problem is the hook lifecycle wired to it:
post-upgraderuns after Helm has finished waiting for the release's resources to become Ready. In the tenantkubernetesrelease, that wait includesMachineDeploymentreaching Ready, which requires nodes to register, which requires thebootstrap-token-<id>Secret this very Job is meant to create.The dependency chain deadlocks:
On the two affected tenant clusters we saw
HR.status.upgradeFailures = 5726— Helm was retrying the upgrade for ~1.5 days, never reaching the post-upgrade phase.The sibling tenant that works (
htstagingin our setup) was installed before this Job template landed, went through an early install where nodes came up by other means, and only afterwards had the post-upgrade hook fire successfully on a subsequent HR reconcile — so the Secret exists there, and the release loop is self-sustaining. Any freshly installed tenantKubernetesrelease, or any pre-existing one whose HR sits inUpgradeFailed, ends up in the deadlocked state we hit.Suggested fix
Move the hook to
pre-install,pre-upgrade(with a higher weight so it runs after the admin kubeconfig Secret is materialised). The Job only needs<release>-admin-kubeconfigand<release>-talos-secretsto exist — both are rendered before workloads, so the hook can safely run before the wait phase.Alternatively, drop the hook annotation entirely and let the Job be a regular resource with
ttlSecondsAfterFinished— Helm will apply it in the normal sync and it won't be gated on the release becoming Ready.Either shape breaks the deadlock.
pre-install,pre-upgradeis closer to the current author's intent (idempotent Job that runs on every reconcile).Note
#3519(talos-reconcile Job OOM at 256Mi) also affects the same chart, but the two are independent. Even with#3519fixed, this hook-ordering deadlock still traps any freshly installed tenant whose nodes cannot yet register.fix(kubernetes): break bootstrap-token Job out of Helm hook to unbloc…
fix(kubernetes): break bootstrap-token Job out of Helm hook to unbloc…
[Backport release-1.6] fix(kubernetes): break bootstrap-token Job out…