CNTRLPLANE-3921

EnsureGlobalPullSecret Flaky Failure Investigation

Investigation of flaky test failures blocking CI release payloads for OCP 5.0

CNTRLPLANE-3921 Ahmed's Handoff Gist PR #9167 (remove v1) PR #9073 (port to v2)

Last updated: 2026-07-29 · Investigators: Alessandro Rossi, Ahmed Abdalla (parallel track)

Context

Background

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.

Slack Threads

What the Test Does

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).

Failure Pattern (13 Runs Analyzed)

PatternCountDescription
DaemonSet stuck10/13A 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).
Outliers3/13Pod stuck Pending, global-pull-secret not deleted within 30s, or not found after 30s.
Cascading6/13Secondary failures in Check_if_GlobalPullSecret_secret_is_in_the_right_place_at_Dataplane — consequence, not cause (gomega scoping issue).

Key Observations (Single Run: 2082061384283066368)

Observed: Node Cordon and Pod Eviction

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.

Management cluster node inventory at dump time:
NodeNodePoolCreatedCordoned?Cordoned At
ip-10-0-15-202mgmt-us-east-1a (original)11:23:45No
ip-10-0-17-1mgmt-us-east-1b (original)11:23:14No
ip-10-0-46-59mgmt-us-east-1c (original)11:23:05No
ip-10-0-0-27mgmt-non-reqserving-wrjwf11:37:07YES12:07:10
ip-10-0-18-73mgmt-non-reqserving-4wv5n11:36:57YES12:07:10
Observation: Both non-request-serving nodes show 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.
Observed: Simultaneous Pod Replacements

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 PodFrom NodeReplacementTo Node
network-node-identity-...-qhtf7ip-10-0-0-27...-ctnf9ip-10-0-46-59
cloud-network-config-controller-...-v9cg9ip-10-0-0-27...-8wgvzip-10-0-46-59
kube-controller-manager-...-hjfxcip-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
Why network-node-identity Would Be Uniquely Affected (If Node Drain Is the Trigger)
  • Critical CP components (kube-apiserver, etcd, oauth-openshift, openshift-apiserver) have PodDisruptionBudgets and were observed running on the original (stable) nodes. network-node-identity has no PDB.
  • Its webhook (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.
  • The replacement pod's init container crashes in a loop for ~15 min, turning a brief restart into a prolonged outage.
Possible triggers for the node cordon (unverified): One possibility is a NodePool config hash change triggered by CVO applying MachineConfig updates. The build log shows CVO was stuck on cluster version rollout for ~14 min (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.

Hypothesized Failure Chain (Based on Single Run)

Non-request-serving nodes cordoned (observed in node YAML dumps, trigger unknown) | +--> [HYPOTHESIS] HCP pods on those nodes evicted (92 events at ~12:07:10) | +--> [OBSERVED] network-node-identity pod killed (no PDB protection) | +--> [OBSERVED] ReplicaSet creates replacement pod on ip-10-0-46-59 | +--> [OBSERVED, 4/4 runs] Init container hosted-cluster-kubecfg-setup crashes "illegal base64 data at input byte 3" 72-73 failures over ~15 min (cause unknown) | +--> [OBSERVED, 4/4 runs] Webhook pod.network-node-identity.openshift.io:9743 unreachable for ~15-20 min | +--> [OBSERVED, 4/4 runs] Guest cluster: new pod sandbox creation blocked "failed calling webhook: connection refused" | +--> [OBSERVED, 10/13 runs] DaemonSet stuck at N-1/N ready pods | +--> waitForDaemonSetReady 20-min timeout exceeded | +--> EnsureGlobalPullSecret FAILS

Detailed Timeline (Run 2082061384283066368)

Time (UTC)Event
11:14:27Management 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:57Non-request-serving nodes come up: ip-10-0-0-27 and ip-10-0-18-73
11:38:17network-node-identity Deployment created (gen 1, rev 1). Pod qhtf7 scheduled on ip-10-0-0-27
11:38:21Init container succeeds. All containers running. Pod healthy.
~11:39:00Deployment Progressing: "ReplicaSet has successfully progressed."
12:07:10DISRUPTION 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:10ReplicaSet creates ctnf9 on ip-10-0-46-59
12:07 - 12:22Init container hosted-cluster-kubecfg-setup crash loop: illegal base64 data at input byte 3 (72-73 failures, ~15 min)
12:07 - 12:26Guest cluster pods fail sandbox creation: failed calling webhook "pod.network-node-identity.openshift.io": connection refused
~12:26:32Replacement pod Ready. Webhook back online.
~12:27+waitForDaemonSetReady 20-min timeout exceeded. EnsureGlobalPullSecret fails.

Hypotheses Tested

OBSERVED 4/4 H1: network-node-identity webhook down blocks new pods

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 4/4 H4: Init container base64 crash loop extends outage

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.

OBSERVED H5: Management cluster node disruption causes pod kill

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:

  • Node YAML dumps are point-in-time snapshots, not event logs — the exact cordon timestamp is inferred, not directly observed
  • The trigger for the cordon (config hash change vs autoscaler vs other) is unknown
  • The management cluster dump was scrubbed, preventing verification of the parent cluster operator logs
Analyzed on 1 run only. Cross-run validation is needed to determine whether this pattern is consistent across the other 12 failed runs.
DISPROVED H2: KAS cert rotation triggers RestartDateAnnotation

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.

DISPROVED H3: Image pull latency on replacement pod

In 4/4 runs analyzed, all images reported as "already present on machine". Image pull was not a contributing factor.

DISPROVED Same-node hypothesis

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's Parallel Investigation (Presubmit e2e-aws)

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.

Hypotheses Ahmed disproved:
  1. DaemonSet controller lag (mismatch persists for the full 20 min)
  2. Node taint/label issues
  3. Resource quota exhaustion
  4. PodDisruptionBudget interference
  5. Webhook blocking DaemonSet pod creation
Proposed observability improvements:
  1. Add resourceVersion to the polling log in waitForDaemonSetReady
  2. Ensure full KCM/HCCO log capture (currently truncated to ~3.5 min)
  3. Add reconcile result logging in the globalps controller
Relationship to our findings: Ahmed's observedGeneration 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.

Open Questions

#QuestionImpactHow to Answer
1Exact trigger for the CAPI node drain: NodePool config hash change or autoscaler?HighCheck parent cluster HyperShift operator logs; need unscrubbed mgmt cluster dump
2Why does hosted-cluster-kubecfg-setup get illegal base64 data at input byte 3?HighIdentify which Secret (service-network-admin-kubeconfig?) has invalid data and why it takes ~15 min to stabilize after pod rescheduling
3Is this CAPI drain pattern consistent across all 13 failed runs?MediumRepeat HCP namespace event analysis on additional runs
4Does Ahmed's presubmit observedGeneration mismatch share the same root cause?MediumCross-validate with presubmit management cluster architecture
5Should CNO create a PDB for network-node-identity?MediumPDBs prevent voluntary eviction but not involuntary node failure; discuss with networking team

Artifact Links

Primary analyzed run: 2082061384283066368
Key event YAMLs analyzed:
EventFile
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-59kube-controller-manager-...-hjfxc.18c672b689fc5636.yaml
CNCC original on ip-10-0-0-27cloud-network-config-controller-...-v9cg9.18c6711dfaa177c8.yaml
CNCC replacement on ip-10-0-46-59cloud-network-config-controller-...-8wgvz.18c672b679ffd722.yaml