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