Design: sparse per-item backup outcomes for observability
Summary
Expose a stable per-item backup outcome view for one backup run: {GVR, namespace, name, outcome, message} — so integrators don't regex results.gz or stitch BSL files themselves (#9377 comment).
This is mostly merge + structure at finalize, not more logs.gz text.
Problem
PartiallyFailed backups only expose a job-level count on the CR:
status:
phase: PartiallyFailed
errors: 2Per-item signal exists but is fragmented across backup storage:
| File | Per-item? | Outcome signal | Gap |
|---|---|---|---|
resource-list.json.gz |
Inventory | Denominator | No pass/fail |
results.gz |
Errors | Sync fail/warn strings | Unstructured; name: ambiguous; async holes (#9377) |
volumeinfo.json.gz |
PVC | Structured result | PVC-only; can be stale |
itemoperations.json.gz |
Async ops | Structured status | Partial; may disagree with results |
<backup>.tar.gz |
Manifests | Content | No outcome |
velero-backup.json |
Job | Status snapshot | May predate async finalize |
Backup.status |
Job | Phase, error counts | No per-item rows |
Same name: format, different kinds:
error executing custom action (groupResource=widgets.example.io, namespace=ns, name=app-1): ...
name: /app-1-data message: /PVC ns/app-1-data has no volume backing this claimProposal
One backup → one snapshot at finalize (after async work). Immutable.
Merge existing sources into one view:
resource-list— scoperesults— sync errors (parse strings; prefer structured fields when Velero records failures)volumeinfo— PVC resultsitemoperations— async failures (wins over staleresults/volumeinfo)
Sparse output — only non-success rows + summary; success implicit (in resource-list, not in failure sets, volumeinfo OK for PVCs):
{
"summary": { "totalItems": 8420, "succeeded": 8408, "failed": 10, "warnings": 2, "skipped": 0 },
"items": [
{
"group": "widgets.example.io", "resource": "widgets", "version": "v1",
"namespace": "ns", "name": "app-1", "outcome": "failed",
"message": "persistentvolumeclaims \"app-1-data\" not found"
}
]
}Where new capture is needed (only if merge can't cover it): per-hook outcomes, explicit skipped with reason, structured error tuples at write time instead of string-only.
Delivery (TBD): versioned pkg/ API; optional persisted file (e.g. <backup>-item-outcomes.json.gz). Summary counts on Backup.status OK; per-item rows on the CR are not.
Success criteria
- List all failed/warned items with explicit GVR, namespace, name, message — without ad hoc
results.gzparsing - Async failures in the same merged view (#9377)
- Documented versioned contract
- Sparse default: size scales with failure count
Non-goals
- Workload rollup (VM, StatefulSet, Helm) — downstream
- Restore outcomes — follow-up
- Cross-backup history / diff between schedules
- Dense per-item success list on disk or CR
Open questions
- v1:
pkg/API only, or persisted artifact too? skippedvs implicit success?- One row per API object (PVC stays on PVC) vs collapse to parent?
- Gaps requiring new capture beyond merge — hooks, skips, others?
Related
- #9377 — async metadata consistency
- #10310 — node-agent/datamover errors in describe
Can share a multi-object PartiallyFailed reproducer and per-failure source matrix if useful.
Source: velero-io/velero