Kubernetes Manifest Skill
Summary
- The Kubernetes Manifest Skill is a packaged automation that reviews workload YAML against a fixed checklist covering probes, resource requests and limits, labels, and Pod Security Standards compliance.
- Insight: Almost every "the pod is unhealthy but traffic still hits it" incident traces back to a missing readiness probe or an absent resource request, both of which are one-line manifest defects a machine can catch before merge.
- Key Concepts: liveness/readiness/startup probes, requests vs limits, QoS classes, recommended labels,
securityContext, and Pod Security Standards (restricted). - When to Use: Run it on every pull request that touches a Deployment, StatefulSet, DaemonSet, Job, or the Helm/Kustomize output that renders them.
- Limitations: It reviews rendered manifests, not cluster state - it cannot tell you whether your probe threshold is right for your actual startup time, only that a probe exists.
Recipe
The shortest path is to render the manifests, then run a policy check against the rendered output.
helm template myapp ./charts/myapp > /tmp/rendered.yaml
kubeconform -strict -kubernetes-version 1.36.2 /tmp/rendered.yaml
kube-linter lint /tmp/rendered.yamlkubeconform validates schema against the target Kubernetes minor; kube-linter applies best-practice checks.
Render first, always. Linting a Helm chart's raw templates checks Go template text, not the YAML that actually reaches the API server.
Working Example
Here is a Deployment carrying the defects the skill flags most often.
Before:
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
spec:
replicas: 3
selector:
matchLabels:
app: checkout
template:
metadata:
labels:
app: checkout
spec:
containers:
- name: checkout
image: registry.example.com/checkout:latest
ports:
- containerPort: 8080The skill reports six findings: a mutable :latest tag, no probes, no resource requests or limits, no securityContext, no recommended labels, and no PodDisruptionBudget for a 3-replica service.
After:
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout
labels:
app.kubernetes.io/name: checkout
app.kubernetes.io/version: "1.8.3"
app.kubernetes.io/managed-by: Helm
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: checkout
template:
metadata:
labels:
app.kubernetes.io/name: checkout
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: checkout
image: registry.example.com/checkout:1.8.3
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 256Mi
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]The image is pinned to the release version, so a rollback is a real rollback rather than a coin flip.
Probes are split by job: startup absorbs slow boot, readiness gates traffic, liveness restarts a wedged process.
The pod- and container-level securityContext fields together satisfy the restricted Pod Security Standard.
Deep Dive
Probes each answer a different question
Readiness answers "should this pod receive traffic" and controls Endpoints membership.
Liveness answers "is this process wedged" and its only remedy is a container restart.
Startup answers "is boot still in progress" and suspends the other two until it passes.
The skill flags a liveness probe that shares a path with readiness, because a slow dependency then turns a traffic pause into a restart loop.
Requests and limits set QoS, not just capacity
Requests drive scheduling and are what the scheduler reserves; limits cap runtime consumption.
A pod with equal requests and limits on every resource is Guaranteed; unequal is Burstable; neither is BestEffort and it is evicted first under node pressure.
Memory limits are enforced by the kernel with an OOM kill, so set memory requests equal to limits for predictable behavior.
CPU limits throttle rather than kill, and aggressive CPU limits are a common latency cause - many teams set CPU requests and omit CPU limits deliberately.
The skill requires requests on every container and flags a missing memory limit.
Recommended labels make workloads addressable
The app.kubernetes.io/* label set is the community convention that dashboards, cost tooling, and NetworkPolicies key off.
spec.selector.matchLabels is immutable after creation, so the skill flags selectors built from volatile values like a version or a commit SHA.
Pod Security Standards are enforced at the namespace
The restricted profile requires runAsNonRoot, allowPrivilegeEscalation: false, capabilities.drop: ["ALL"], and a seccompProfile of RuntimeDefault or Localhost.
Enforcement comes from namespace labels, and the skill checks the manifest would survive them.
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.36Pin enforce-version rather than letting it float to latest, so a cluster upgrade cannot silently tighten admission on your workloads.
Gotchas
A liveness probe with an aggressive failureThreshold during slow startup produces an endless restart loop; use a startup probe instead of loosening liveness.
readOnlyRootFilesystem: true breaks apps that write temp files, so pair it with an emptyDir mounted at /tmp rather than dropping the setting.
Setting only a memory limit with no request makes the pod Burstable with a request equal to the limit - the skill still flags it because the intent is ambiguous.
Warning-mode Pod Security labels are silent in CI; the skill catches restricted violations before the namespace ever rejects them.
A PodDisruptionBudget with minAvailable equal to replicas blocks every node drain, stalling cluster upgrades indefinitely.
Alternatives
kube-linter is a fast standalone checker with a good default rule set and is the right baseline when you want zero configuration.
OPA Gatekeeper and Kyverno enforce the same rules at admission, which is stronger because it also covers manifests applied outside CI.
kubeconform only validates schema, so it complements rather than replaces a best-practice linter.
Use the skill in CI for explained, fixable findings at review time; use an admission controller as the backstop that no one can bypass.
FAQs
Should this run against Helm charts or rendered YAML?
Rendered YAML, produced by helm template or kustomize build, because that is what the API server actually receives.
Does it replace an admission controller?
No - CI checks give fast feedback with context, admission enforces at the cluster boundary. Run both.
Which Kubernetes version should it validate against?
Pin it to the minor your clusters run, currently 1.36.2, and bump it deliberately as part of the upgrade.
Why flag :latest if we always deploy the same digest?
Because :latest makes the manifest non-reproducible; if you resolve to a digest at deploy time, have the skill assert the digest is present.
Can it enforce probes on Jobs?
Readiness is meaningless for a Job, so the skill scopes probe checks to long-running controllers and skips Jobs and CronJobs.
Related
- What a Packaged SME Skill Is
- Agent Skills Basics
- Dockerfile Review Skill
- Incident Triage Skill
- Agent Skills Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).