EnsureGlobalPullSecret Flaky Failure Investigation
Investigation of flaky test failures blocking CI release payloads for OCP 5.0
Last updated: 2026-07-29 · Investigators: Alessandro Rossi, Ahmed Abdalla (parallel track)
Job: periodic-ci-openshift-hypershift-release-5.0-periodics-e2e-aws-ovn
Pass rate: ~93.5% (10 failures / 154 runs over 14 days, per Sippy)
Bryan flagged that HyperShift tests were frequently blocking CI release payloads for OCP 5.0. Ahmed identified EnsureGlobalPullSecret as a critical flake and began investigation. Due to time constraints, he handed off with a Claude Code handoff gist. Alessandro continued the deep-dive analysis.
EnsureGlobalPullSecret runs as part of TestCreateCluster / TestNodePool_HostedCluster0 and validates the full lifecycle of the Global Pull Secret feature across 7 phases: in-place pull secret propagation without NodePool rollout, CAPI label checks, additional-pull-secret CRUD, HCCO reconciliation of the global-pull-secret DaemonSet, on-disk kubelet config.json verification via a privileged kubelet-config-verifier DaemonSet, pod image pull verification, and cleanup.
Key code: test/e2e/util/util.go:1958-2257 (test body), test/e2e/util/globalps.go (DaemonSet helpers), waitForDaemonSetReady at util.go:2283 (20-min polling loop, 10s interval).
| Pattern | Count | Description |
|---|---|---|
| DaemonSet stuck | 10/13 | A DaemonSet fails to reach full readiness (N-1/N pods ready) within the 20-min timeout. Either kubelet-config-verifier (7 runs, 1/3 or 2/3) or global-pull-secret-syncer (4 runs, 0/3 or 2/3). |
| Outliers | 3/13 | Pod stuck Pending, global-pull-secret not deleted within 30s, or not found after 30s. |
| Cascading | 6/13 | Secondary failures in Check_if_GlobalPullSecret_secret_is_in_the_right_place_at_Dataplane — consequence, not cause (gomega scoping issue). |
By analyzing HCP namespace event dumps and node YAML artifacts from run 2082061384283066368, we observed that the network-node-identity pod kill at ~12:07:10 coincides with a broad node-level cordon, suggesting a node drain rather than a targeted pod delete. This has only been analyzed on a single run and needs cross-run validation.
The management cluster for this CI job is itself a HyperShift HostedCluster (773085beb4-mgmt, OCP 4.22.0-ec.5) running on the CI infrastructure. The job runs with REQUEST_SERVING_COMPONENT_TEST=true, which causes test/e2e/util/requestserving.go:SetupReqServingClusterNodePools() to create additional NodePools, including non-request-serving NodePools.
| Node | NodePool | Created | Cordoned? | Cordoned At |
|---|---|---|---|---|
ip-10-0-15-202 | mgmt-us-east-1a (original) | 11:23:45 | No | — |
ip-10-0-17-1 | mgmt-us-east-1b (original) | 11:23:14 | No | — |
ip-10-0-46-59 | mgmt-us-east-1c (original) | 11:23:05 | No | — |
ip-10-0-0-27 | mgmt-non-reqserving-wrjwf | 11:37:07 | YES | 12:07:10 |
ip-10-0-18-73 | mgmt-non-reqserving-4wv5n | 11:36:57 | YES | 12:07:10 |
spec.unschedulable: true and taint node.kubernetes.io/unschedulable:NoSchedule in their YAML dumps. The manager field owner is CAPI. The cordon appears to have happened ~30 minutes after node creation, which correlates with the observed pod kill window. However, since node YAML dumps are point-in-time snapshots (not event logs), we cannot definitively establish the exact cordon timestamp or causation chain from these artifacts alone.
Around 12:07:10, 92 events fired in the HCP namespace. Multiple pods that had been running on ip-10-0-0-27 were replaced and rescheduled to ip-10-0-46-59. The simultaneity suggests a node-level event, but the exact trigger has not been confirmed:
| Original Pod | From Node | Replacement | To Node |
|---|---|---|---|
network-node-identity-...-qhtf7 | ip-10-0-0-27 | ...-ctnf9 | ip-10-0-46-59 |
cloud-network-config-controller-...-v9cg9 | ip-10-0-0-27 | ...-8wgvz | ip-10-0-46-59 |
kube-controller-manager-...-hjfxc | ip-10-0-0-27 | (rescheduled) | ip-10-0-46-59 |
multus-admission-controller, hosted-cluster-config-operator, ovnkube-control-plane, aws-ebs-csi-driver-controller, ignition-server-proxy | |||
kube-apiserver, etcd, oauth-openshift, openshift-apiserver) have PodDisruptionBudgets and were observed running on the original (stable) nodes. network-node-identity has no PDB.pod.network-node-identity.openshift.io:9743) is a mutating admission webhook — when down, ALL new pod sandbox creation in the guest cluster is blocked.Unable to apply 4.22.0-ec.5). Another possibility is the NonRequestServingNodeAutoscaler (hypershift-operator/controllers/scheduler/aws/autoscaler.go) scaling down MachineSets based on ClusterSizingConfiguration. The management cluster dump was scrubbed for sensitive data, so we cannot confirm the mechanism.
| Time (UTC) | Event |
|---|---|
| 11:14:27 | Management cluster 773085beb4-mgmt creation starts (HostedCluster on CI infra, OCP 4.22.0-ec.5, 3 original NodePools in us-east-1a/b/c) |
| ~11:36:57 | Non-request-serving nodes come up: ip-10-0-0-27 and ip-10-0-18-73 |
| 11:38:17 | network-node-identity Deployment created (gen 1, rev 1). Pod qhtf7 scheduled on ip-10-0-0-27 |
| 11:38:21 | Init container succeeds. All containers running. Pod healthy. |
| ~11:39:00 | Deployment Progressing: "ReplicaSet has successfully progressed." |
| 12:07:10 | DISRUPTION 92 events across 8+ deployments. Pod qhtf7 killed (Normal/Killing for all 3 containers). Node YAML dumps show ip-10-0-0-27 and ip-10-0-18-73 cordoned (exact cordon timestamp not independently confirmed). |
| 12:07:10 | ReplicaSet creates ctnf9 on ip-10-0-46-59 |
| 12:07 - 12:22 | Init container hosted-cluster-kubecfg-setup crash loop: illegal base64 data at input byte 3 (72-73 failures, ~15 min) |
| 12:07 - 12:26 | Guest cluster pods fail sandbox creation: failed calling webhook "pod.network-node-identity.openshift.io": connection refused |
| ~12:26:32 | Replacement pod Ready. Webhook back online. |
| ~12:27+ | waitForDaemonSetReady 20-min timeout exceeded. EnsureGlobalPullSecret fails. |
Observed on 4/4 runs analyzed in depth. When the webhook is unreachable, new pod sandbox creation in the guest cluster fails with failed calling webhook "pod.network-node-identity.openshift.io": connection refused. Multiple guest namespaces affected: openshift-ingress-canary, openshift-insights, openshift-dns.
Observed on 4/4 runs analyzed in depth. The replacement pod's init container hosted-cluster-kubecfg-setup fails 72-73 times over ~15 min with illegal base64 data at input byte 3. This turns what should be a ~30-second restart into a ~20-minute outage. The corrupted Secret and why it takes ~15 min to stabilize is still unknown.
On run 2082061384283066368, we observed that the pod kill at ~12:07:10 coincides with 92 simultaneous events across 8+ deployments, and that the non-request-serving node YAML dumps show spec.unschedulable: true. This is consistent with a node drain, but:
shouldCheckForStaleCerts() returns false for CPOv2 clusters (hostedcluster_controller.go:2577). OCP 5.0 uses CPOv2. Deployment dump confirms: no hypershift.openshift.io/restart-date annotation present.
In 4/4 runs analyzed, all images reported as "already present on machine". Image pull was not a contributing factor.
Initially hypothesized failures always occur on the same node. Verified across runs that nodes are different ephemeral EC2 instances — the failure is not node-specific but is tied to the non-request-serving NodePool role.
Ahmed investigated a potentially different symptom on the presubmit e2e-aws job: the global-pull-secret-syncer DaemonSet has observedGeneration stuck at 2 while generation is 3, suggesting the DaemonSet controller is not processing the latest spec update.
resourceVersion to the polling log in waitForDaemonSetReadyobservedGeneration mismatch symptom (presubmit e2e-aws) may be a different manifestation of the same underlying problem, or an entirely separate issue. The presubmit job uses a different management cluster setup. Cross-validation has not been performed.
| # | Question | Impact | How to Answer |
|---|---|---|---|
| 1 | Exact trigger for the CAPI node drain: NodePool config hash change or autoscaler? | High | Check parent cluster HyperShift operator logs; need unscrubbed mgmt cluster dump |
| 2 | Why does hosted-cluster-kubecfg-setup get illegal base64 data at input byte 3? | High | Identify which Secret (service-network-admin-kubeconfig?) has invalid data and why it takes ~15 min to stabilize after pod rescheduling |
| 3 | Is this CAPI drain pattern consistent across all 13 failed runs? | Medium | Repeat HCP namespace event analysis on additional runs |
| 4 | Does Ahmed's presubmit observedGeneration mismatch share the same root cause? | Medium | Cross-validate with presubmit management cluster architecture |
| 5 | Should CNO create a PDB for network-node-identity? | Medium | PDBs prevent voluntary eviction but not involuntary node failure; discuss with networking team |
resourceVersion logging + full KCM/HCCO log capturenetwork-node-identity on original (stable) management cluster nodesnetwork-node-identity in CNO2082061384283066368| Event | File |
|---|---|
| RS created pod qhtf7 (11:38:17) | network-node-identity-8658d6576c.18c671231f4c5790.yaml |
| RS created pod ctnf9 (12:07:10) | network-node-identity-8658d6576c.18c672b695314da8.yaml |
| Killing webhook (12:07:10) | ...qhtf7.18c672b6954280f2.yaml |
| Killing token-minter (12:07:10) | ...qhtf7.18c672b695437cfe.yaml |
| Killing approver (12:07:10) | ...qhtf7.18c672b69543e79a.yaml |
| KCM rescheduled to ip-10-0-46-59 | kube-controller-manager-...-hjfxc.18c672b689fc5636.yaml |
| CNCC original on ip-10-0-0-27 | cloud-network-config-controller-...-v9cg9.18c6711dfaa177c8.yaml |
| CNCC replacement on ip-10-0-46-59 | cloud-network-config-controller-...-8wgvz.18c672b679ffd722.yaml |