Skip to content

docs: document how the k8s reporter handles job and cronjob pods - #391

Open
dangrondahl wants to merge 8 commits into
mainfrom
docs/k8s-job-cronjob-reporting
Open

docs: document how the k8s reporter handles job and cronjob pods#391
dangrondahl wants to merge 8 commits into
mainfrom
docs/k8s-job-cronjob-reporting

Conversation

@dangrondahl

Copy link
Copy Markdown
Contributor

Closes #387

Adds a What gets reported section to tutorials/report_k8s_envs.md, answering the customer question "how are K8s Jobs and CronJobs handled — is it detected by the reporter?".

What the section covers

  • Snapshots are pod-level and workload-kind agnostic — a Job pod is reported exactly like a Deployment pod.
  • A table of which pod phases are reported (Running, Failed) and which are not (Succeeded, Pending, Unknown).
  • Where owner references end up: recorded on the snapshot, readable from kosli get snapshot --output json, not in the table output.
  • What this means for Job / CronJob pods: captured only while running, so short-lived jobs are sampled at best and completed runs leave no trace.
  • Whether a captured run produces churn — depends on whether the job's image digest is unique to the job.
  • The provenance-flicker consequence for environments with a provenance-requiring policy.
  • Guidance: a dedicated namespace (excluded, or reported to its own environment), or flow attestations if the goal is a record of job runs.

Verified against source

Against kosli-dev/cli internal/kube/kube.go and kosli-dev/server:

Claim Source
Only Running / Failed pods reported processPods in internal/kube/kube.go
Failed pod skipped with a warning when a container has no image ID NewPodData
Owner references reach Kosli and are stored per pod PodData.OwnersK8SArtifact.ownerspods[name]["owners"] in environment_snapshots.py
Owners exposed on the snapshot API, and in the CLI's JSON but not its table SnapshotArtifact.pods, printSnapshotAsTable
Instance-count-only differences do not create a snapshot _annotate_common_artifact / changed_except_kosli_cli
Filtering is namespace-only snapshotK8S.go flags and k8sEnvironmentConfig

Checks

  • mint broken-links — no new broken links. The one reported failure (/getting_started/service-accounts in tutorials/working_with_controls.mdx) is pre-existing and unrelated.
  • pytest tests/ — 44 passed.
  • No navigation change needed; the page is already at config/navigation.json:100.

Follow-up, not in this PR

client_reference/kosli_snapshot_k8s.md is generated, and its long description still says only "The reported data includes pod container images digests and creation timestamps" with no mention of pod phases. Adding the phase constraint there needs a change to snapshotK8SLongDesc in kosli-dev/cli's cmd/kosli/snapshotK8S.go.

@dangrondahl
dangrondahl requested a review from a team as a code owner September 7, 2026 10:01
@mintlify

mintlify Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
kosli 🟢 Ready View Preview Sep 7, 2026, 10:03 AM

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dangrondahl's task in 3m 1s —— View job


Doc review — round 5

  • Read all four changed files at branch head (8cdb81d)
  • Verified the new page is in config/navigation.json and questioned its tab/group
  • Checked redirects (none required — no page moved relative to main)
  • Verified every anchor and cross-page link by hand
  • Cross-checked chart values and CLI flags against helm/k8s_reporter/configuration.mdx and client_reference/kosli_snapshot_k8s.md

The split holds up: #running-multiple-reporters and #handling-job-workloads both resolve, the intra-page reference at line 71 now points at a section on its own page rather than across the split, and nothing else in the repo linked to the tutorial anchor that moved (only getting_started/environments.md:61, helm/k8s_reporter/karpenter.mdx:41, labs/lab-05-runtime-controls.mdx:235 and tutorials/repositories.md:132 link to the tutorial, all page-level). No relative links. administration/managing_environments/overview.md keeps its path, so the retitle needs no redirect. Chart keys (excludeNamespaces, namespacesRegex) and the mutual-exclusion constraint at line 71 both match the generated reference.

Moving ## Running multiple reporters here also makes the Administration placement read better than it did last round — a page that carries reporter topology guidance alongside the mechanics is closer to an operations page than the pure explanation page I was arguing about. I'm dropping that objection; the only residue is the title's Understand prefix, which now reads oddly against its three how-to siblings.


New this push

1. The page's intro and description no longer describe the pageadministration/managing_environments/kubernetes_reporting.md:3 and :6. Both enumerate the contents and both stop at Jobs/CronJobs, so the section this push added is invisible in the opening paragraph and in search results — even though tutorials/report_k8s_envs.md:184 now sends readers here specifically for it. Suggestion inline.

Still open from round 4 (not re-commented inline)

2. The reporter-namespace remedy still doesn't do what the prose promises — lines 54–62. Line 54 says "install the reporter into a namespace of its own and exclude that namespace"; the three commands create kosli, put the secret there, and install with -n kosli against an unchanged values.yaml. At the whole-cluster scope this page assumes (line 52), the reporter is then in a new namespace and still reporting itself, so the provenance failure the <Note> exists to explain is untouched. Nothing tells the reader to add excludeNamespaces: ["kosli"] to the reporterConfig.environments entry. This is the one finding where acting on the page as written leaves the reader worse off than not acting.

3. The automatic-cleanup framing is now an incomplete sweep across two pages. Line 39 establishes that nothing removes a Failed pod by defaultttlSecondsAfterFinished is unset unless you set it, and failedJobsHistoryLimit replaces the retained Job rather than deleting it. Three other places still promise cleanup happens on its own:

Location Wording
kubernetes_reporting.md:35 "until the failed pod is cleaned up"
kubernetes_reporting.md:48 "until it is garbage-collected"
tutorials/report_k8s_envs.md:178 "until Kubernetes cleans it up"

The split made this worse rather than better: two of the three are the section lead-in and the <Warning> — the sentences a reader skims — and the third is now the only thing tutorial readers see on the subject, with the correction a click away.

4. backoffLimit off by one — line 39. It is a retry count, so a failing Job with restartPolicy: Never leaves backoffLimit + 1 pods, seven at the default, not six. And "each is reported" understates the connection to the churn rules three paragraphs down: those seven pods share an image, so Kosli reports one artifact with an instance count of seven, not seven artifacts.


Findings 2–4 were reported at 12:11–12:12 UTC and are unchanged at head; I've left those threads rather than duplicating them. Nothing new on the phase table, the churn rules, the policy example or the multiple-reporters table.
· branch docs/k8s-job-cronjob-reporting

Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
@dangrondahl

Copy link
Copy Markdown
Contributor Author

Addressed all five findings from the doc review — the two Improvements and all three Suggestions. Pushed as docs: address review on k8s job/cronjob section; individual replies are on each inline thread.

Finding 3 was summary-only, so answering it here: applied. The point was correct and it was the most substantive of the three suggestions — "a snapshot is a list of pods" described a different data model than the .artifacts[].pods jq path three lines below and the instance-count churn rules further down. Reworded to separate the two facts: the reporter collects pods and is blind to workload kind, and in the snapshot those pods are grouped under the artifact whose image they run. The second paragraph now says explicitly that this is why the churn rules turn on image digests rather than individual pods, which is what makes the later section follow.

Two verifications behind the changes, since both Improvements rested on claims I had not checked when writing the section:

  • reporterConfig.environments per-entry excludeNamespaces / namespaces — confirmed at helm/k8s_reporter/configuration.mdx:85.
  • artifacts.provenance.exceptions taking a list of single-key if objects — confirmed at policy-reference/environment_policy.mdx:35.

Checks: mint broken-links reports no new broken links (the single failure, /getting_started/service-accounts in tutorials/working_with_controls.mdx, is pre-existing and untouched by this PR), and pytest tests/ passes 44.

Nothing disputed — all five were valid.

Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
Comment thread tutorials/report_k8s_envs.md Outdated
@dangrondahl

Copy link
Copy Markdown
Contributor Author

Restructured — the section had outgrown its doc type, so the mechanics now live on their own page.

Why

Four review rounds grew ## What gets reported monotonically: 529 → 683 → 884 → 1091 → 1239 words. At that point it was ~2× the tutorial it was appended to (## Report a snapshot is 628 words) and 56% of the page, against an issue that asked for "a short section". Every finding was correct — the reviewer was judging claims, not shape, so nothing in the loop could push back on length or doc type.

By Diátaxis the section had become three types wearing a tutorial's clothes: reference (the phase table), explanation (churn model, cron sampling, the image-name-keyed exemption), and how-to (the three remedies). A learner following "report a snapshot" was reading ttlSecondsAfterFinished and backoffLimit before reaching "What you've accomplished".

What changed

New: understand_kosli/kubernetes_snapshots.md — "Kubernetes environment snapshots". All the verified content, unchanged in substance: what gets reported, Jobs and CronJobs, handling job workloads. Nav: Documentation ▸ Understand Kosli, after how_kosli_works (which already covers "Monitoring runtime: Environments, Snapshots, and Policies" at a shallower level).

Trimmed: tutorials/report_k8s_envs.md — the section is now a 125-word pointer covering pod-level scope, the reported phases, and the short-lived-workload consequence, then links out. Also added the new page to the closing "From here you can" list.

before after
## Report a snapshot 628 628
## What gets reported 1239 125

Anchors: the cross-reference to #running-multiple-reporters is now /tutorials/report_k8s_envs#running-multiple-reporters; #handling-job-workloads stays same-page on the new page. Genericised the tutorial-specific k8s-tutorial / tutorial-values.yaml names on the explanation page, and the reporter-namespace note now refers to "the install steps in the tutorial" rather than "earlier on this page".

Nothing was dropped

All 14 findings from the four rounds remain applied — this is placement only, not a revert. mint broken-links shows no new broken links (the single failure, /getting_started/service-accounts in tutorials/working_with_controls.mdx, is pre-existing and untouched), and pytest tests/ passes 44 including the navigation-integrity check for the new page.

One judgment call worth flagging: at 1322 words the new page is larger than any existing understand_kosli/ page (controls.md is the current largest at 1100). That seems defensible now that it is the right doc type, but say the word if you would rather see ## Jobs and CronJobs (735 words) tightened.


* A job that starts and finishes between two snapshots never appears at all. As a rough guide, a run is captured about as often as its runtime divides into the snapshot interval — so on the Helm chart's default `*/5 * * * *` schedule a job that runs for a few seconds is almost always missed, while one that runs for most of the interval is almost always caught. That guide assumes the job starts at an arbitrary point in the interval. A `CronJob` whose schedule shares a period with the reporter's does not sample randomly at all: it is caught on nearly every run or on nearly none, depending on which of the two fires first.
* A job that happens to be running when a snapshot is taken appears in that snapshot and is gone from the next one.
* A job that **fails** is the exception. `Failed` is a terminal phase, so the pod stays in it — and keeps being reported — for as long as it exists, and nothing removes it by default. `ttlSecondsAfterFinished` is unset unless you set it, so a standalone Job's failed pods survive until the Job is deleted; a `CronJob` always retains its most recent failed Job (`failedJobsHistoryLimit`, default `1`). A Job with `restartPolicy: Never` that keeps failing leaves one `Failed` pod per attempt, up to `backoffLimit` (default `6`), and each is reported. Until they are gone, a failed run is not a flicker: Kosli keeps showing the dead pods' artifact as running in the environment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement — two numbers in this bullet are ones a reader plans capacity and cleanup around, and both are off.

  1. backoffLimit is the number of retries, so a Job that keeps failing makes backoffLimit + 1 attempts — seven Failed pods at the default, not six. "up to backoffLimit (default 6)" undercounts by one.
  2. Those pods all run the same image, so Kosli reports them as one artifact with an instance count of seven, not as seven artifacts. Saying so explicitly is what ties this bullet to the churn rules three paragraphs down, which turn on exactly that distinction.
Suggested change
* A job that **fails** is the exception. `Failed` is a terminal phase, so the pod stays in it — and keeps being reported — for as long as it exists, and nothing removes it by default. `ttlSecondsAfterFinished` is unset unless you set it, so a standalone Job's failed pods survive until the Job is deleted; a `CronJob` always retains its most recent failed Job (`failedJobsHistoryLimit`, default `1`). A Job with `restartPolicy: Never` that keeps failing leaves one `Failed` pod per attempt, up to `backoffLimit` (default `6`), and each is reported. Until they are gone, a failed run is not a flicker: Kosli keeps showing the dead pods' artifact as running in the environment.
* A job that **fails** is the exception. `Failed` is a terminal phase, so the pod stays in it — and keeps being reported — for as long as it exists, and nothing removes it by default. `ttlSecondsAfterFinished` is unset unless you set it, so a standalone Job's failed pods survive until the Job is deleted; a `CronJob` always retains its most recent failed Job (`failedJobsHistoryLimit`, default `1`). A Job with `restartPolicy: Never` that keeps failing leaves one `Failed` pod per attempt`backoffLimit` retries plus the first try, so seven at the default — and every one of them is reported, as instances of the same artifact. Until they are deleted, a failed run is not a flicker: Kosli keeps showing the dead pods' artifact as running in the environment.

Fix this →

* If the job runs the same image as a long-running workload, only the instance count changes. Instance-count-only differences do not create a snapshot, so the run is invisible — and because the report is discarded, the pod's owner references are not stored either. Owner references only reach Kosli for runs that produce a snapshot.

<Warning>
If the job's image was never attested to a Kosli flow, it is reported as an artifact with no provenance. Under an [environment policy](/policy-reference/environment_policy) that requires provenance, snapshots taken while a job was running are non-compliant and snapshots taken between runs are compliant — so compliance appears to flicker. A failed run is worse than a flicker: the `Failed` pod is reported in every snapshot until it is garbage-collected, holding the environment non-compliant for as long as it survives.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement — this <Warning> still says the Failed pod is reported "until it is garbage-collected", which is the framing line 39 exists to correct: nothing garbage-collects it by default (ttlSecondsAfterFinished is unset, and a CronJob's failedJobsHistoryLimit replaces the retained Job rather than removing it).

The mismatch matters because the callout is the sentence a reader skims and acts on: as written it says "wait and it clears itself", when the bullet above says "it stays until you delete it". The callout should carry the stronger claim.

Suggested change
If the job's image was never attested to a Kosli flow, it is reported as an artifact with no provenance. Under an [environment policy](/policy-reference/environment_policy) that requires provenance, snapshots taken while a job was running are non-compliant and snapshots taken between runs are compliant — so compliance appears to flicker. A failed run is worse than a flicker: the `Failed` pod is reported in every snapshot until it is garbage-collected, holding the environment non-compliant for as long as it survives.
If the job's image was never attested to a Kosli flow, it is reported as an artifact with no provenance. Under an [environment policy](/policy-reference/environment_policy) that requires provenance, snapshots taken while a job was running are non-compliant and snapshots taken between runs are compliant — so compliance appears to flicker. A failed run is worse than a flicker: nothing deletes a `Failed` pod by default, so it is reported in every snapshot until you remove it — holding the environment non-compliant until then.

Fix this →

Comment on lines +51 to +65
<Note>
The reporter deployed by the Helm chart is itself a `CronJob`, and at the default whole-cluster scope it is running whenever it takes a snapshot — so it appears in its own snapshots. Kosli ignores the reporter's image (`ghcr.io/kosli-dev/cli`) when deciding whether a snapshot is worth saving, so the reporter's own pods never create snapshots or start and exit events. The match is on the image name, so if you override `image.repository` to mirror the reporter image into your own registry, its pods stop being ignored and each run shows up as an artifact starting and exiting.

The reporter is *not* exempt from compliance evaluation, though: under a policy that requires provenance it counts as an artifact without provenance — persistently, not intermittently. Either waive provenance for its image, or install the reporter into a namespace of its own and exclude that namespace — subject to the same include/exclude constraint as the [first option below](#handling-job-workloads).

The API token secret is namespace-scoped, so a dedicated namespace needs its own copy of it. Create both before installing:

```shell
kubectl create namespace kosli
kubectl create secret generic kosli-api-token -n kosli --from-literal=apikey=<your-kosli-api-token>
helm install kosli-reporter kosli/k8s-reporter -n kosli -f values.yaml
```

The install steps in the [tutorial](/tutorials/report_k8s_envs) pass no `-n`, so they put the secret and the reporter in whichever namespace you are currently in — usually alongside the workloads you came there to report. Excluding *that* namespace would drop those workloads from the environment too.
</Note>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement — the remedy in this <Note> still doesn't do what the prose promises, and now that the content lives on its own page there's an easy fix for both halves.

  1. Line 54 says "install the reporter into a namespace of its own and exclude that namespace", but the three commands only do the first half: they create kosli, put the secret there, and install with -n kosli using an unchanged values.yaml. Nothing tells the reader to add excludeNamespaces: ["kosli"] to the reporterConfig.environments entry — and at the whole-cluster scope this page assumes (line 52), the reporter is then in a new namespace and still reporting itself, so the provenance failure the Note exists to explain is untouched. The include/exclude cross-reference explains the constraint but never says to make the edit.
  2. This is now four paragraphs and a three-command procedure inside a callout, on a page whose other callout is one paragraph. CLAUDE.md asks for <Tip>/<Note>/<Warning> "sparingly", and a callout this size stops reading as an aside — it reads as the section it actually is. A ## The reporter's own pods heading between "Jobs and CronJobs" and "Handling job workloads" would also make it linkable, which the Note's own #handling-job-workloads back-reference suggests you want.

Suggested shape: promote to a section, keep paragraph 1 as-is, and name the values edit before the commands — e.g. "Add excludeNamespaces: ["kosli"] to the environment entry, then create the namespace and secret before installing:".

Fix this →


A snapshot lists the **pods** running in scope. The reporter is blind to workload kind — a `Job` pod is reported exactly like a `Deployment` pod — and only pods in the `Running` and `Failed` phases are reported; `Succeeded` and `Pending` pods are not.

That matters most for short-lived workloads. A `Job` or `CronJob` pod is captured only while it is running, so a job that starts and finishes between two snapshots never appears, and a successful run leaves no trace in the environment. A pod that *fails* is the exception — it keeps being reported until Kubernetes cleans it up.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion — "until Kubernetes cleans it up" carries the same implication of automatic cleanup that the explanation page corrects (understand_kosli/kubernetes_snapshots.md:39: nothing removes a Failed pod by default). A tutorial reader who only reads this summary will wait for a cleanup that never comes.

Suggested change
That matters most for short-lived workloads. A `Job` or `CronJob` pod is captured only while it is running, so a job that starts and finishes between two snapshots never appears, and a successful run leaves no trace in the environment. A pod that *fails* is the exception — it keeps being reported until Kubernetes cleans it up.
That matters most for short-lived workloads. A `Job` or `CronJob` pod is captured only while it is running, so a job that starts and finishes between two snapshots never appears, and a successful run leaves no trace in the environment. A pod that *fails* is the exception — nothing deletes it by default, so it keeps being reported until you remove it.

Fix this →

Comment thread config/navigation.json
Comment on lines +61 to 65
"group": "Environments",
"pages": [
"administration/managing_environments/overview"
"administration/managing_environments/overview",
"administration/managing_environments/kubernetes_reporting"
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion — worth re-checking this placement now that it has moved. The page is explanation: its title starts "Understand", its description is "What the Kosli Kubernetes reporter captures…", and four of its five sections are mechanics and consequences rather than steps. doc-write's placement table maps "a concept, or the reasoning behind a design" to Documentation ▸ Understand Kosli, and Administration to "a task an org admin performs (users, roles, auth, org-wide settings)" — which is what its three sibling groups (Authentication & access, Users & roles, Managing Custom Attestation Types) all are.

The group it landed in also now holds two unrelated things: a Terraform how-to (which needed its title changed to Managing environments with Terraform to make room) and reporter mechanics. A reader who wonders why their CronJob never appears will reach this page from the tutorial link, but won't find it browsing Understand Kosli next to how_kosli_works, which is where the shallower version of the same model already lives.

If the intent was to keep it near environment operations, Documentation ▸ Tutorials ▸ Reporting environments — beside tutorials/report_k8s_envs itself — is closer to that than Administration, and doesn't require the sibling page to be retitled.

Fix this →

description: 'What the Kosli Kubernetes reporter captures, which pod phases it reports, and what that means for Job and CronJob workloads.'
---

A Kubernetes environment snapshot records what was running in your cluster at a moment in time. This page explains what the reporter actually collects, which pods it skips, and why short-lived workloads such as `Job` and `CronJob` behave differently from long-running ones. To set reporting up in the first place, see [Report Kubernetes environments to Kosli](/tutorials/report_k8s_envs).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion — this push moved ## Running multiple reporters onto the page, but neither this intro nor the description frontmatter (line 3) mentions it. Both enumerate the page's contents — "what the reporter actually collects, which pods it skips, and why short-lived workloads such as Job and CronJob behave differently" — so the page now under-describes itself by a whole section.

It matters more than a stale summary usually would, because tutorials/report_k8s_envs.md:184 now sends readers here specifically for reporter topologies. That arriving intent is the one thing the opening paragraph doesn't acknowledge, and the description is what shows in search results.

Suggested change
A Kubernetes environment snapshot records what was running in your cluster at a moment in time. This page explains what the reporter actually collects, which pods it skips, and why short-lived workloads such as `Job` and `CronJob` behave differently from long-running ones. To set reporting up in the first place, see [Report Kubernetes environments to Kosli](/tutorials/report_k8s_envs).
A Kubernetes environment snapshot records what was running in your cluster at a moment in time. This page explains what the reporter actually collects, which pods it skips, why short-lived workloads such as `Job` and `CronJob` behave differently from long-running ones, and which multi-reporter setups produce meaningful snapshots. To set reporting up in the first place, see [Report Kubernetes environments to Kosli](/tutorials/report_k8s_envs).

The description needs the same extension — e.g. "…what that means for Job and CronJob workloads, and which multi-reporter setups are supported."

Fix this →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: document how the K8s reporter handles Job and CronJob pods

1 participant