Kubernetes Collector
Parse Kubernetes YAML manifests and render Helm charts to extract workloads, containers, resource limits, probes, PodDisruptionBudgets, autoscalers, and NetworkPolicies. Capture kubectl commands in CI for deployment traceability.
k8s to your lunar-config.yml:uses: github://earthly/lunar-lib/collectors/k8s@v1.0.5
What This Integration Collects
This integration includes 3 collectors that gather metadata from your systems.
k8s
Parses all Kubernetes manifests in the repository using kubeconform validation and collects:
- Manifest validity and parse errors
- Workloads (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs)
- Container specs with resource requests/limits, probes, and shutdown hooks
- PodDisruptionBudgets (PDBs)
- HorizontalPodAutoscalers (HPAs) and KEDA ScaledObjects
- NetworkPolicies (pod selector, policy types, egress rules)
Helm chart templates are rendered by the
helmsub-collector instead.
helm
Renders each Helm chart in the repository with helm template, once per values
set in helm_values or derived from its file names with helm_values_chains,
and parses the output the same way as a static manifest.
Rendered objects land in the same .k8s arrays, tagged with the chart and values
files that produced them. A chart no values set applies to is rendered with its
own values.yaml only to check that it builds: it gets a .k8s.manifests[] entry
and no objects. A chart that fails to render is recorded as an invalid manifest,
so the valid check fails instead of every check skipping.
cicd
Records every kubectl command executed in CI pipelines along with the kubectl client version. Writes command strings and version info to .k8s.cicd for audit trails and deployment traceability (kubectl apply, kubectl rollout, etc.).
How Collectors Fit into Lunar
Lunar watches your code and CI/CD systems to collect SDLC data from config files, test results, IaC, deployment configurations, security scans, and more.
Collectors are the automatic data-gathering layer. They extract structured metadata from your repositories and pipelines, feeding it into Lunar's centralized database where guardrails evaluate it to enforce your engineering standards.
Learn How Lunar Works →Example Collected Data
This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:
{
"k8s": {
"source": {
"tool": "kubeconform",
"version": "0.8.0"
},
"manifests": [
{
"path": "deploy/deployment.yaml",
"valid": true,
"resources": [
{"kind": "Deployment", "name": "payment-api", "namespace": "payments", "api_version": "apps/v1"}
]
},
{
"path": "charts/worker",
"render": {"chart": "charts/worker", "values": ["values.yaml", "values-prod.yaml"], "validated_only": false},
"valid": true,
"resources": [
{"kind": "Deployment", "name": "worker", "namespace": "default", "api_version": "apps/v1"},
{"kind": "PodDisruptionBudget", "name": "worker", "namespace": "default", "api_version": "policy/v1"},
{"kind": "ScaledObject", "name": "worker", "namespace": "default", "api_version": "keda.sh/v1alpha1"}
]
},
{
"path": "charts/reports",
"render": {"chart": "charts/reports", "values": ["values.yaml"], "validated_only": true},
"valid": false,
"error": "helm template: An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: postgresql"
}
],
"workloads": [
{
"kind": "Deployment",
"name": "payment-api",
"namespace": "payments",
"path": "deploy/deployment.yaml",
"replicas": 1,
"replicas_set": false,
"pod_labels": {"app": "payment-api", "tier": "api"},
"pod_annotations": {"prometheus.io/scrape": "true"},
"host_users": false,
"host_network": false,
"host_pid": false,
"host_ipc": false,
"termination_grace_period_seconds": 60,
"topology_spread_constraints": [
{
"topology_key": "topology.kubernetes.io/zone",
"max_skew": 1,
"when_unsatisfiable": "ScheduleAnyway",
"label_selector": {"matchLabels": {"app": "payment-api"}},
"match_label_keys": ["pod-template-hash"]
}
],
"containers": [
{
"name": "api",
"image": "gcr.io/acme/payment-api:v1.2.3",
"has_resources": true,
"has_requests": true,
"has_limits": true,
"cpu_request": "100m",
"cpu_limit": "500m",
"memory_request": "128Mi",
"memory_limit": "512Mi",
"has_liveness_probe": true,
"has_readiness_probe": true,
"liveness_probe": {"handler": "httpGet", "port": 8080, "path": "/livez", "initial_delay_seconds": 0, "period_seconds": 10, "timeout_seconds": 2, "failure_threshold": 3},
"readiness_probe": {"handler": "httpGet", "port": 8080, "path": "/readyz", "initial_delay_seconds": 0, "period_seconds": 5, "timeout_seconds": 2, "failure_threshold": 3},
"startup_probe": null,
"has_prestop": true,
"runs_as_non_root": true,
"read_only_root_fs": true,
"privileged": false
}
],
"init_containers": []
},
{
"kind": "Deployment",
"name": "worker",
"namespace": "default",
"path": "charts/worker/templates/deployment.yaml",
"render": {"chart": "charts/worker", "values": ["values.yaml", "values-prod.yaml"]},
"replicas": 1,
"replicas_set": false,
"pod_labels": {"app.kubernetes.io/name": "worker"},
"pod_annotations": {},
"host_users": true,
"host_network": false,
"host_pid": false,
"host_ipc": false,
"termination_grace_period_seconds": 30,
"topology_spread_constraints": [],
"containers": [
{
"name": "worker",
"image": "ghcr.io/acme/worker:2.4.0",
"has_resources": true,
"has_requests": true,
"has_limits": true,
"cpu_request": "250m",
"cpu_limit": "1",
"memory_request": "256Mi",
"memory_limit": "512Mi",
"has_liveness_probe": true,
"has_readiness_probe": true,
"liveness_probe": {"handler": "httpGet", "port": "http", "path": "/healthz", "initial_delay_seconds": 0, "period_seconds": 10, "timeout_seconds": 1, "failure_threshold": 3},
"readiness_probe": {"handler": "httpGet", "port": "http", "path": "/healthz", "initial_delay_seconds": 0, "period_seconds": 10, "timeout_seconds": 1, "failure_threshold": 3},
"startup_probe": null,
"has_prestop": false,
"runs_as_non_root": true,
"read_only_root_fs": false,
"privileged": false
}
],
"init_containers": [
{
"name": "migrate",
"image": "ghcr.io/acme/worker:2.4.0",
"has_resources": false,
"has_requests": false,
"has_limits": false,
"cpu_request": null,
"cpu_limit": null,
"memory_request": null,
"memory_limit": null,
"has_liveness_probe": false,
"has_readiness_probe": false,
"liveness_probe": null,
"readiness_probe": null,
"startup_probe": null,
"has_prestop": false,
"runs_as_non_root": true,
"read_only_root_fs": false,
"privileged": false
}
]
}
],
"pdbs": [
{
"name": "payment-api-pdb",
"namespace": "payments",
"path": "deploy/pdb.yaml",
"selector": {"matchLabels": {"app": "payment-api"}},
"target_workload": "payment-api",
"min_available": 2,
"max_unavailable": null
},
{
"name": "worker",
"namespace": "default",
"path": "charts/worker/templates/pdb.yaml",
"render": {"chart": "charts/worker", "values": ["values.yaml", "values-prod.yaml"]},
"selector": {"matchLabels": {"app.kubernetes.io/name": "worker"}},
"target_workload": "worker",
"min_available": null,
"max_unavailable": 1
}
],
"hpas": [
{
"name": "payment-api-hpa",
"namespace": "payments",
"path": "deploy/hpa.yaml",
"target_workload": "payment-api",
"target_kind": "Deployment",
"min_replicas": 3,
"max_replicas": 10
}
],
"scaled_objects": [
{
"name": "worker",
"namespace": "default",
"path": "charts/worker/templates/scaledobject.yaml",
"render": {"chart": "charts/worker", "values": ["values.yaml", "values-prod.yaml"]},
"target_workload": "worker",
"target_kind": "Deployment",
"min_replicas": 2,
"max_replicas": 20
}
],
"network_policies": [
{
"name": "egress-no-metadata",
"namespace": "payments",
"path": "deploy/netpol.yaml",
"pod_selector": {},
"policy_types": ["Egress"],
"egress": [
{"to": [{"ipBlock": {"cidr": "0.0.0.0/0", "except": ["169.254.169.254/32"]}}]}
]
}
],
"cicd": {
"cmds": [
{"cmd": "kubectl apply -f deploy/", "version": "1.29.2"},
{"cmd": "kubectl rollout status deployment/payment-api", "version": "1.29.2"}
],
"source": {"tool": "kubectl", "integration": "ci"}
}
}
}
Configuration
Configure this collector in your lunar-config.yml.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
find_command
|
Optional |
find . -type f \( -name '*.yaml' -o -name '*.yml' \)
|
Command to find K8s manifest files (must output one file path per line) |
helm_find_command
|
Optional |
find . -type f \( -name 'Chart.yaml' -o -name 'Chart.yml' \)
|
Command to find the Helm charts to render, as Chart.yaml paths (one per line). A subchart in another chart's charts/ directory is rendered with its parent, not on its own. |
helm_values
|
Required | — | Values sets to check each chart's objects with, one per line. A line lists values files, relative to the chart directory, applied in order on top of the chart's values.yaml; a glob gives one render per matching file, and a line of just `values.yaml` checks the defaults. A chart skips lines whose files it doesn't have. A chart no line or chain applies to is only checked to build: its values.yaml render records validity and no objects. `#` starts a comment. |
helm_values_chains
|
Required | — | Derive values sets from each chart's file names: `overlays` or `all`. Empty (the default) is off. An overlay extends the ones its name continues at a dash (values-prod-eu.yaml extends values-prod.yaml), and each values-*.yaml that nothing extends is one chain: values.yaml, the overlays it extends, then itself. A chart with no values-*.yaml stays build-only under `overlays`, and is checked with values.yaml alone under `all`, which is only right where every chart is a deployed app, not a chart library. Adds to the sets in helm_values. |
helm_dependency_build
|
Optional |
false
|
Run `helm dependency build` before rendering a chart whose remote dependencies aren't vendored in its charts/ directory (true/false). Needs network access to the chart repositories. When off, such a chart fails to render and is reported as invalid. Local file:// dependencies are always built. |
helm_api_versions
|
Required | — | Comma-separated API versions to render charts as if the cluster served them (`helm template --api-versions`), for charts that only render an object when its CRD is installed (e.g. "keda.sh/v1alpha1"). Core Kubernetes APIs are always available. |
Documentation
View on GitHubKubernetes Collector
Parses Kubernetes manifests, renders Helm charts, and tracks kubectl commands in CI.
Overview
This collector finds all Kubernetes YAML manifests in a repository and validates them using kubeconform. It renders each Helm chart with helm template and parses the output the same way, so the k8s policy checks what a chart deploys; to check the chart itself (lint, version, pinned dependencies), use the helm collector. It extracts workloads (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs) with their container specs, resource limits, probes and shutdown settings, plus PodDisruptionBudgets, HorizontalPodAutoscalers, KEDA ScaledObjects and NetworkPolicies. It also intercepts kubectl commands during CI runs so deployment invocations (apply, rollout, etc.) are recorded alongside the manifest data.
Collected Data
This collector writes to the following Component JSON paths:
| Path | Type | Description |
|---|---|---|
.k8s.source |
object | Tool metadata (tool name and version) |
.k8s.manifests[] |
array | Parsed manifests with validity and resources: one entry per static file, and one per chart render |
.k8s.workloads[] |
array | Workload resources with pod_labels, pod_annotations, replica, scheduling and shutdown settings, and containers[] / init_containers[] (resources, probes, security context) |
.k8s.pdbs[] |
array | PodDisruptionBudgets with their full label selector (target_workload is deprecated) |
.k8s.hpas[] |
array | HorizontalPodAutoscalers |
.k8s.scaled_objects[] |
array | KEDA ScaledObjects, with the workload they scale and their replica range |
.k8s.network_policies[] |
array | NetworkPolicies: pod_selector, effective policy_types (API-server default when unset), and egress rules as written |
.k8s.cicd |
object | kubectl CI command tracking (commands + client version) |
Entries that come from a Helm chart carry render: the chart directory and the values files it was rendered with. Their path is the chart template that produced them, such as charts/api/templates/deployment.yaml; for a packaged dependency it names the template inside the package.
Collectors
This integration provides the following collectors (use include to select a subset):
| Collector | Description |
|---|---|
k8s |
Parses Kubernetes manifests, workloads, PodDisruptionBudgets, autoscalers, and NetworkPolicies |
helm |
Renders Helm charts and parses the output into the same paths |
cicd |
Tracks all kubectl commands executed in CI pipelines (apply, rollout, etc.) |
Helm charts
The helm collector renders each chart it finds (a directory with a Chart.yaml) with helm template, offline. Library charts aren't rendered, and a subchart in another chart's charts/ directory is rendered as part of its parent. The k8s collector still skips templated files, so a chart's templates are only ever read through its render.
The k8s checks only see a chart's objects for the values it is deployed with, which you list in helm_values, one render per line:
helm_values: |
values.yaml # the defaults, when that's what you deploy
values-staging.yaml
values-prod.yaml values-prod-eu.yaml # files apply in order, on top of values.yaml
ci/*-values.yaml # a glob gives one render per matching file
Paths are relative to the chart directory, and every line applies to every chart. A chart skips the lines whose files it doesn't have.
When overlays are named for what they stack on, helm_values_chains derives the sets instead of listing them. An overlay extends the ones its name continues at a dash (values-prod-eu.yaml extends values-prod.yaml, not values-pro.yaml), and each overlay that nothing extends is one chain: values.yaml, the overlays it extends, then itself. So values-prod-eu.yaml renders as values.yaml values-prod.yaml values-prod-eu.yaml, and values-staging.yaml as values.yaml values-staging.yaml. With overlays, a chart with no values-*.yaml stays build-only, which keeps chart libraries quiet across a whole fleet. With all, such a chart is checked with values.yaml alone, the full layering a CI that applies overlays by file name ships; use it only where every chart is a deployed app. Chains add to helm_values, and identical sets render once.
A chart that no line or chain applies to, including every chart when both are unset, is rendered with its values.yaml only to check that it builds: it gets a .k8s.manifests[] entry with render.validated_only: true and contributes no objects. Chart defaults are often meant to be filled in by whoever deploys the chart (a chart library ships resources: {} and its PodDisruptionBudget disabled), so checking them would fail what the chart's owner can't fix.
Charts render against helm's built-in Kubernetes version and core APIs. A chart that only renders an object when its CRD is installed, such as a KEDA ScaledObject behind .Capabilities.APIVersions.Has "keda.sh/v1alpha1", needs that API listed in helm_api_versions.
The values sets of one chart are alternative deployments of the same release, so policies never match a workload to a PodDisruptionBudget, autoscaler or NetworkPolicy from another values set of its own chart. Objects from plain manifests and from other charts still match.
A chart that fails to render gets a .k8s.manifests[] entry with valid: false and helm's error, so the valid check fails instead of every check skipping. A render that applies a values set is also validated with kubeconform, like a static manifest. Remote dependencies must be vendored in the chart's charts/ directory, or set helm_dependency_build: true to fetch them first, which needs network access to the chart repositories. Local file:// dependencies are built either way.
Installation
Add to your lunar-config.yml:
collectors:
- uses: github://earthly/lunar-lib/collectors/k8s@v1.0.0
on: ["domain:your-domain"] # Or use tags like [kubernetes, backend]
include:
- k8s # Plain manifests
- helm # Helm charts, rendered
- cicd # kubectl commands in CI
# with:
# find_command: "find ./deploy -name '*.yaml'" # Custom find command
# helm_values: |
# values-prod.yaml
# helm_values_chains: "overlays" # Or derive the values sets from file names
# helm_dependency_build: "true" # Fetch remote chart dependencies (needs network)
# helm_api_versions: "keda.sh/v1alpha1" # Render objects gated on a CRD
Open Source
This collector is open source and available on GitHub. Contribute improvements, report issues, or fork it for your own use.
Common Use Cases
Explore guardrails that use data from Kubernetes Collector.
Ready to Automate Your Standards?
See how Lunar can turn your AGENTS.md, engineering wiki, compliance docs, or postmortem action items into automated guardrails with our 300+ built-in guardrails.