Hamburger Cross Icon
Kubernetes Collector - Lunar Collector

Kubernetes Collector

↓ Collector Stable OrchestrationContainersCi Cd

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.

Add 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.

Collector code

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 helm sub-collector instead.
kubernetes k8s deployment pods containers resources probes pdb hpa keda networkpolicy
Book a demo
Collector code

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.

helm helm template helm chart rendered manifests values files kubernetes
Book a demo
Collector ci-after-command

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.).

kubectl kubernetes ci kubectl apply kubectl rollout deployment tracking ci commands
Book a demo

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 →
1
↓ Collectors Gather Data This Integration
Triggered by code changes or CI pipelines, collectors extract metadata from config files, tool outputs, test results, and scans
2
{ } Centralized as JSON
All data merged into each component's unified metadata document
3
✓ Guardrails Enforce Standards
Real-time feedback in PRs and AI workflows

Example Collected Data

This collector writes structured metadata to the Component JSON. Here's an example of the data it produces:

{ } component.json Component JSON
{
  "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 GitHub

Kubernetes 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.

View Repository

Common Use Cases

Explore guardrails that use data from Kubernetes Collector.

+
Valid + Kubernetes Collector Validates that all Kubernetes manifests in the repository are syntactically...
→
+
Requests And Limits + Kubernetes Collector Ensures all containers have CPU and memory requests and limits defined. Missing...
→
+
Probes + Kubernetes Collector Requires liveness and readiness probes on all containers. Probes enable...
→
+
Min Replicas + Kubernetes Collector Enforces minimum replica counts on HorizontalPodAutoscalers. Ensures services...
→
+
Pdb + Kubernetes Collector Requires PodDisruptionBudgets for Deployments and StatefulSets. PDBs protect...
→
+
Non Root + Kubernetes Collector Requires containers to run as non-root users via securityContext. Running as...
→
+
Host Users + Kubernetes Collector Requires workload PodSpecs to set `hostUsers: false`. User Namespaces (GA in...
→
+
Host Network + Kubernetes Collector Fails when workload PodSpecs set `hostNetwork: true`. Sharing the host's network...
→
+
Host Pid + Kubernetes Collector Fails when workload PodSpecs set `hostPID: true`. Sharing the host's PID...
→
+
Host Ipc + Kubernetes Collector Fails when workload PodSpecs set `hostIPC: true`. Sharing the host's IPC...
→
+
Metadata Egress Blocked + Kubernetes Collector Requires a NetworkPolicy to keep each workload's pods from reaching the cloud...
→
+
Min Kubectl Version + Kubernetes Collector Ensures the kubectl version used in CI meets the minimum required version. Old...
→
+
Topology Spread + Kubernetes Collector Requires Deployments and StatefulSets that can run more than one replica (their...
→
+
Graceful Shutdown + Kubernetes Collector Requires Deployments and StatefulSets to drain before stopping: a preStop hook...
→
+
Pdb Budget + Kubernetes Collector Fails a PodDisruptionBudget that allows no voluntary disruption, which blocks...
→
+
No Static Replicas + Kubernetes Collector Fails a Deployment or StatefulSet that sets spec.replicas while an HPA or KEDA...
→
+
Probes Distinct + Kubernetes Collector Requires each container's liveness and readiness probes to check different...
→
+
Probe Timeouts + Kubernetes Collector Requires every probe's timeoutSeconds to be at least `min_probe_timeout_seconds`...
→
+
Allowed Registries + Kubernetes Collector Requires every container and init container image to come from a registry...
→
+
Deprecated Api Versions + Kubernetes Collector Fails manifests and rendered charts that use an apiVersion listed...
→
+
Pod Annotations + Kubernetes Collector Requires the pod template annotations in `required_pod_annotations` and fails...
→

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 200+ built-in guardrails.

Works with any process
check AI agent rules & prompt files
check Post-mortem action items
check Security & compliance policies
check Testing & quality requirements
Auto-ID My Guardrails
Paste your AGENTS.md or manual process doc and get guardrails in minutes
Book a Demo