Topology Spread
k8s.topology-spread
Requires Deployments and StatefulSets that can run more than one replica (their autoscaler's
maximum, else spec.replicas) to have a topologySpreadConstraint on topology_key (zone by
default) that selects their pods, with maxSkew within topology_max_skew.
Compatible Integrations
This guardrail works with the following integrations. Click to see how to use Topology Spread with each collector.
Enable This Guardrail
Add the parent policy to your lunar-config.yml to enable this guardrail.
policies:
- uses: github://earthly/lunar-lib/policies/k8s@v1.0.5
include: [topology-spread]
# with: ...
How This Guardrail Works
This guardrail is part of the Kubernetes Guardrails policy. It evaluates data collected by integrations and produces a pass/fail check with actionable feedback.
When enabled, this check runs automatically on every PR and in AI coding workflows, providing real-time enforcement of your engineering standards.
Learn How Lunar Works →Configuration Options
These inputs can be configured in your lunar-config.yml to customize
how the parent policy (and this guardrail) behaves.
| Input | Required | Default | Description |
|---|---|---|---|
min_replicas
|
Optional |
3
|
Minimum replicas required for HPAs (default 3) |
max_limit_to_request_ratio
|
Optional |
4
|
Maximum ratio of limits to requests for CPU/memory (default 4) |
min_kubectl_version
|
Optional |
1.28
|
Minimum kubectl version required in CI (semver, e.g. "1.28") |
metadata_ips
|
Optional |
169.254.169.254
|
Comma-separated metadata endpoint IPs that metadata-egress-blocked keeps pods from reaching on TCP port 80 (add fd00:ec2::254 for IPv6 EKS clusters) |
topology_key
|
Optional |
topology.kubernetes.io/zone
|
Node label topology-spread requires a constraint on (default topology.kubernetes.io/zone) |
topology_max_skew
|
Optional |
1
|
Largest maxSkew topology-spread accepts on that constraint (default 1) |
topology_when_unsatisfiable
|
Required | — | whenUnsatisfiable value topology-spread requires on that constraint (DoNotSchedule or ScheduleAnyway). Empty accepts either. |
topology_require_match_label_keys
|
Optional |
false
|
Also require the constraint to set matchLabelKeys with pod-template-hash, so a rollout's skew counts only the new revision's pods (true/false) |
min_termination_grace_period_seconds
|
Optional |
30
|
Smallest terminationGracePeriodSeconds graceful-shutdown accepts. An unset value counts as the Kubernetes default, 30 (default 30) |
require_prestop
|
Optional |
true
|
Whether graceful-shutdown requires a preStop hook on at least one container (true/false) |
min_probe_timeout_seconds
|
Optional |
1
|
Smallest probe timeoutSeconds probe-timeouts accepts. An unset value counts as the Kubernetes default, 1 (default 1) |
allowed_registries
|
Required | — | Comma-separated registries, or registry/path prefixes, images may come from (e.g. "ghcr.io/acme,123456789012.dkr.ecr.us-east-1.amazonaws.com"). An image with no registry counts as docker.io. Empty skips allowed-registries. |
deprecated_api_versions
|
Required | — | Comma-separated apiVersions deprecated-api-versions fails, each optionally narrowed to one kind (e.g. "policy/v1beta1,networking.k8s.io/v1beta1:Ingress"). Empty skips the check. |
required_pod_annotations
|
Required | — | Comma-separated annotation keys every workload's pod template must set. Empty requires none. |
forbidden_pod_annotations
|
Required | — | Comma-separated annotation keys no workload's pod template may set (e.g. "karpenter.sh/do-not-disrupt"). Empty forbids none. |
Kubernetes Guardrails
This guardrail is part of the Kubernetes Guardrails policy, which includes 21 guardrails for deployment and infrastructure.
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.