Skip to content

Commit d2d0451

Browse files
tiraboschiclaude
andcommitted
Refactor node diagnostics into gather_nodes, adopt PR kubevirt#241
Merge gather_node_diagnostics into gather_nodes to eliminate the double DaemonSet deployment that occurred when both scripts ran sequentially in the default gather pipeline. The merged script collects all per-node data (networking, SR-IOV, CNI, VFIO, audit, NFS tuning, kernel diagnostics, storage I/O, PSI pressure) in a single DaemonSet lifecycle with one privileged pod per node (~25s/node). Key changes: - Merge: absorb all 24 diagnostic files from gather_node_diagnostics plus all networking/SR-IOV/CNI/audit files from old gather_nodes into a single collect_node() function. Remove gather_node_diagnostics. - Parallelize: nodes are processed concurrently with MAX_PARALLEL_NODES cap (default 5) using background subshells. Within each node, oc exec calls are batched ~7 at a time. - Remove oc debug fallback: the slow path (~480s/node) that spawned individual debug pods is gone. If no DaemonSet pod is available for a node, it logs a warning and skips. - Fix DaemonSet wait: check numberReady + numberUnavailable >= desired instead of numberReady == desired, so unschedulable nodes don't cause a 300s wait for pods that will never start. - Timeout guards: every oc command is wrapped in timeout. The old gather_nodes had ~22 unprotected oc exec/get/apply/delete calls. - Namespace safety: check for stuck Terminating namespace before deploying; cleanup uses --wait=false to avoid blocking on namespace finalization. - Size caps: audit.log capped at 50 MB (skipped entirely in incident mode), QEMU log tar transfer capped at 50 MB, curl inside gather_prometheus_instant gets --connect-timeout/--max-time to prevent orphan processes. Adopt 4 collection scripts from PR kubevirt#241 for the default gather: - gather_clusterroles: ClusterRole RBAC baseline - gather_cnv_events: GuestPanicked/LivenessProbeFailed events - gather_prometheus_instant: 14 instant Prometheus health queries - gather_windows_nodes: Windows worker node posture Add shared KERNEL_REDFLAG_PATTERN to common.sh and lib_env shared helper providing output directory conventions and NODES list. Protect every oc command across all new scripts with timeout guards. Add a static analysis test that verifies all oc commands in collection scripts are timeout-protected, plus presence checks for each new script's output. This fully covers PR kubevirt#241's functionality — gather_nfs_node_tuning and gather_worker_kernel_log are absorbed into gather_nodes, the other 4 scripts adopted directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Simone Tiraboschi <stirabos@redhat.com>
1 parent 1c18513 commit d2d0451

11 files changed

Lines changed: 903 additions & 231 deletions

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ You will get a dump of:
1515
- The Hyperconverged Cluster Operator namespaces (and its children objects)
1616
- All namespaces (and their children objects) that belong to any KubeVirt resources
1717
- All KubeVirt CRD's definitions
18+
- Per-node system and storage diagnostics (dmesg, dmidecode, sysctl, NFS config, PSI counters, kernel red-flag scan)
19+
- CNV guest events (GuestPanicked, LivenessProbeFailed) across VM namespaces
20+
- Prometheus instant metrics (cluster utilization, VM phases, storage latencies, NFS counters)
21+
- ClusterRole definitions (cluster-reader posture, KubeVirt RBAC baseline)
22+
- Windows worker node posture data (when Windows nodes are present)
1823

1924
By default, the VMs definitions won't be included, but only the VM Instances' custom resources.
2025

@@ -69,11 +74,28 @@ Usage: oc adm must-gather --image=quay.io/kubevirt/must-gather -- /usr/bin/gathe
6974
> - ssp
7075
> - virtualmachines
7176
> - webhooks
77+
> - instancetypes
78+
> - virtualization
79+
> - cnv_events
80+
> - prometheus_instant
81+
> - clusterroles
82+
> - windows_nodes
7283
7384
> You can also choose to enable optional collectors combining one
7485
> or more of the following parameters:
7586
--images
7687
--vms_details
88+
89+
> Incident collection for a specific VM at a known time.
90+
> Unlike a full must-gather, this collects ONLY data pertinent to the
91+
> incident: right VM, right node, right time window. No cluster-wide
92+
> noise. Timeboxed to 10 minutes.
93+
--vm-incident --incident-time=<RFC3339 timestamp>
94+
Requires NS and VM environment variables. Skips all other collectors.
95+
Example:
96+
oc adm must-gather --image=quay.io/kubevirt/must-gather \
97+
-- NS=myns VM=myvm \
98+
/usr/bin/gather --vm-incident --incident-time=2026-06-06T06:06:00Z
7799
```
78100

79101
### Parallelism

collection-scripts/common.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export BASE_COLLECTION_PATH="${BASE_COLLECTION_PATH:-/must-gather}"
55
export PROS=${PROS:-5}
66
export INSTALLATION_NAMESPACE=${INSTALLATION_NAMESPACE:-kubevirt-hyperconverged}
77

8+
# Shared kernel red-flag grep pattern — used by gather_nodes and gather_vm_incident
9+
export KERNEL_REDFLAG_PATTERN='nfs:|NFS |nfs |NFSERR|server not responding|zero writ|call_transmit|cb path stale|not responding for|qemu|QEMU|Out of memory|oom-kill|Killed process.*qemu|task .* blocked|blocked for more than|stuck for|jiffies'
10+
811
function check_command {
912
if [[ -z "$USR_BIN_GATHER" ]]; then
1013
echo "This script should not be directly executed." 1>&2

collection-scripts/gather

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ function main() {
1919
"webhooks"
2020
"instancetypes"
2121
"virtualization"
22+
"cnv_events"
23+
"prometheus_instant"
24+
"clusterroles"
25+
"windows_nodes"
2226
)
2327
declare requested_scripts=("${mandatory_scripts[@]}")
2428
VM_INCIDENT=false
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
3+
# ClusterRole definitions for cluster-reader posture (CNV / KubeVirt RBAC baseline).
4+
# Output: cluster-scoped-resources/rbac.authorization.k8s.io/clusterroles/
5+
6+
# shellcheck disable=SC2034 # COLLECTION is used by lib_env after sourcing
7+
COLLECTION=cluster-scoped-resources/rbac.authorization.k8s.io
8+
9+
# shellcheck source=lib_env
10+
. /usr/bin/lib_env
11+
12+
mkdir -p "${DIR}/aggregated"
13+
14+
echo "INFO-CNV: clusterrole cluster-reader"
15+
timeout 30 oc get clusterrole cluster-reader -o yaml > "${DIR}/cluster-reader.yaml" 2>&1 || true
16+
17+
echo "INFO-CNV: clusterroles aggregated to cluster-reader"
18+
mapfile -t AGG_ROLES < <(timeout 30 oc get clusterroles -l rbac.authorization.k8s.io/aggregate-to-cluster-reader=true -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null | sort -u)
19+
printf '%s\n' "${AGG_ROLES[@]}" > "${DIR}/aggregated_role_names.txt"
20+
for role in "${AGG_ROLES[@]}"; do
21+
[[ -z "${role}" ]] && continue
22+
echo "INFO-CNV: aggregated clusterrole ${role}"
23+
timeout 30 oc get clusterrole "${role}" -o yaml > "${DIR}/aggregated/${role}.yaml" 2>&1 || true
24+
done
25+
26+
echo "INFO-CNV: CNV/kubevirt-related clusterroles"
27+
mapfile -t CNV_ROLES < <(timeout 30 oc get clusterroles -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null | \
28+
grep -iE 'kubevirt|hyperconverged|cnv|virt-api|virt-controller|virt-handler|hostpath-provisioner|kubevirt-hyperconverged' | sort -u)
29+
printf '%s\n' "${CNV_ROLES[@]}" > "${DIR}/cnv_related_role_names.txt"
30+
mkdir -p "${DIR}/cnv_related"
31+
for role in "${CNV_ROLES[@]}"; do
32+
[[ -z "${role}" ]] && continue
33+
echo "INFO-CNV: cnv clusterrole ${role}"
34+
timeout 30 oc get clusterrole "${role}" -o yaml > "${DIR}/cnv_related/${role}.yaml" 2>&1 || true
35+
done
36+
37+
echo "INFO-CNV: clusterrolebindings referencing cluster-reader"
38+
timeout 30 oc get clusterrolebindings -o json 2>/dev/null | jq -r '
39+
.items[]
40+
| select(.roleRef.name == "cluster-reader")
41+
| [.metadata.name, (.subjects[]? | (.kind + "/" + .name + (if .namespace then "@" + .namespace else "" end)))]
42+
| @tsv
43+
' > "${DIR}/cluster_reader_binding_subjects.tsv" 2>/dev/null || true
44+
45+
date -u +"%Y-%m-%dT%H:%M:%SZ" > "${DIR}/timestamp"
46+
47+
sync
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/bash
2+
3+
# Collect GuestPanicked and virt-launcher LivenessProbeFailed events from VM namespaces.
4+
# Output: workload-scoped-resources/cnv_events/
5+
6+
# shellcheck disable=SC2034 # COLLECTION is used by lib_env after sourcing
7+
COLLECTION=workload-scoped-resources
8+
9+
# shellcheck source=lib_env
10+
. /usr/bin/lib_env
11+
12+
if ! timeout 10 oc get ns "${INSTALLATION_NAMESPACE}" &>/dev/null; then
13+
echo "INFO-CNV: namespace ${INSTALLATION_NAMESPACE} not found, skipping CNV guest events"
14+
exit 0
15+
fi
16+
17+
declare -A NS_SEEN=()
18+
19+
_add_ns() {
20+
local ns="$1"
21+
[[ -z "${ns}" || "${ns}" == "null" ]] && return
22+
NS_SEEN["${ns}"]=1
23+
}
24+
25+
while IFS= read -r ns; do
26+
_add_ns "${ns}"
27+
done < <(timeout 30 oc get vmi -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' 2>/dev/null | sort -u)
28+
29+
while IFS= read -r ns; do
30+
_add_ns "${ns}"
31+
done < <(timeout 30 oc get vm -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' 2>/dev/null | sort -u)
32+
33+
while IFS= read -r ns; do
34+
_add_ns "${ns}"
35+
done < <(timeout 60 oc get pods -A -o json 2>/dev/null | jq -r '.items[] | select(.metadata.name | startswith("virt-launcher-")) | .metadata.namespace' | sort -u)
36+
37+
_add_ns "${INSTALLATION_NAMESPACE}"
38+
39+
if [[ ${#NS_SEEN[@]} -eq 0 ]]; then
40+
echo "INFO-CNV: no VM namespaces discovered, collecting ${INSTALLATION_NAMESPACE} events only"
41+
NS_SEEN["${INSTALLATION_NAMESPACE}"]=1
42+
fi
43+
44+
printf '%s\n' "${!NS_SEEN[@]}" | sort -u > "${DIR}/audited_namespaces.txt"
45+
46+
while IFS= read -r ns; do
47+
[[ -z "${ns}" ]] && continue
48+
echo "INFO-CNV: CNV events - namespace ${ns}"
49+
timeout 30 oc get events -n "${ns}" \
50+
--field-selector reason=GuestPanicked \
51+
-o yaml > "${DIR}/${ns}_GuestPanicked.yaml" 2>&1 || true
52+
53+
timeout 30 oc get events -n "${ns}" \
54+
--field-selector reason=LivenessProbeFailed \
55+
-o yaml > "${DIR}/${ns}_LivenessProbeFailed.yaml" 2>&1 || true
56+
done < <(sort -u "${DIR}/audited_namespaces.txt")
57+
58+
timeout 30 oc get events -A --field-selector reason=GuestPanicked \
59+
-o yaml > "${DIR}/all_GuestPanicked.yaml" 2>/dev/null || true
60+
61+
timeout 30 oc get events -A --field-selector reason=LivenessProbeFailed -o wide 2>/dev/null | \
62+
{ head -1; grep -E 'virt-launcher|VirtLauncher' || true; } \
63+
> "${DIR}/all_LivenessProbeFailed_virt_launcher_table" || true
64+
65+
date -u +"%Y-%m-%dT%H:%M:%SZ" > "${DIR}/timestamp"
66+
67+
sync

0 commit comments

Comments
 (0)