An audit script can find contexts and still print zero rows. That may be success, not a parser failure.
For Calico node IP audits, many scripts intentionally emit only mismatches between a Kubernetes node’s InternalIP and the Calico node annotation:
projectcalico.org/IPv4Address
If there are no mismatches, the target files can be empty.
Symptom
The audit finds contexts:
Contexts: site-a-ops-rke2 site-a-prod-rke2 site-a-uat-rke2
But reports zero targets:
Counts for DC=site-a:
all: 0
prod: 0
uat: 0
qa: 0
dev: 0
unknown: 0
Before assuming the node query broke, inspect the selection logic.
Common Mismatch Filter
The script may only print rows matching this condition:
.calico != "" and (.calico | split("/")[0]) != .nodeIP
That means it ignores:
- nodes with no Calico IPv4 annotation.
- nodes where the Calico IPv4 host portion matches the Kubernetes InternalIP.
So 0 can mean:
no remediation targets
not:
no nodes checked
Confirm The Raw Node Data
Use a direct query to print every node:
kubectl --context site-a-ops-rke2 get nodes -o json \
| jq -r '.items[]
| [
.metadata.name,
(.status.addresses[]? | select(.type == "InternalIP") | .address),
(.metadata.annotations["projectcalico.org/IPv4Address"] // "NONE"),
(.metadata.annotations["projectcalico.org/IPv4VXLANTunnelAddr"] // "NONE")
]
| @tsv' \
| column -t
Healthy shape:
node-a 192.0.2.10 192.0.2.10/24 192.0.2.50
node-b 192.0.2.11 192.0.2.11/24 192.0.2.51
The host portion of projectcalico.org/IPv4Address matches InternalIP.
When It Is A Problem
Investigate if you see:
node-a 192.0.2.10 198.51.100.10/24
That means Calico selected a different interface or subnet than Kubernetes considers the node InternalIP.
Typical remediation is not to patch every node annotation by hand. Prefer fixing the Tigera Installation autodetection policy so Calico selects the intended CIDR, then restart or roll the affected Calico components according to the platform runbook.
Improve Audit Output
Mismatch-only scripts are useful for automation, but confusing for humans. Add an optional verbose mode that prints all checked nodes with status:
MATCH
MISMATCH
MISSING_ANNOTATION
UNREACHABLE_CONTEXT
Example output:
context node internal_ip calico_ipv4 status
site-a-ops-rke2 node-a 192.0.2.10 192.0.2.10/24 MATCH
site-a-ops-rke2 node-b 192.0.2.11 198.51.100.10 MISMATCH
That keeps machine-readable target files small while giving operators confidence that the audit actually checked nodes.
One practical implementation is a --show-all flag that writes an additional report while preserving the existing mismatch-only outputs:
calico-ip-audit/site-a/mismatches.tsv
calico-ip-audit/site-a/targets.tsv
calico-ip-audit/site-a/targets-prod.tsv
calico-ip-audit/site-a/all-nodes.tsv
Useful counters:
checked nodes
matching nodes
missing annotations
unreachable contexts
mismatch targets
Add a small fixture test with fake kubectl output so the audit keeps producing both target files and all-node status files after future edits.
Operating Rule
For mismatch-only audits, zero targets is not enough evidence by itself.
Confirm at least once that contexts are reachable and raw node annotations match the intended relationship:
Kubernetes InternalIP == Calico IPv4Address host portion