Kubernetes — Quality Checklist
Idiomatic manifest quality signals. Applied whenever the `k8s` profile is active. Findings are usually P2/P3; some (missing requests/limits, `:latest` tags) escalate.
Overview
Kubernetes — Quality Checklist
Idiomatic manifest quality signals. Applied whenever the k8s profile is active. Findings are usually P2/P3; some (missing requests/limits, :latest tags) escalate.
Contents
- Labels and annotations
- Image tags and digests
- Resource requests and limits
- Probes
- Ports, naming, and discoverability
- Declarative patterns
Labels and annotations
The recommended label set (SIG Apps, kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) applied consistently on every top-level resource:
app.kubernetes.io/name— the name of the application.app.kubernetes.io/instance— unique name identifying this instance.app.kubernetes.io/version— the application's version (semver or git SHA).app.kubernetes.io/component— the component role within the architecture (api,worker,cache).app.kubernetes.io/part-of— the higher-level product this is part of.app.kubernetes.io/managed-by— the tool managing the resource (Helm,kustomize,argocd).
Propagate the relevant labels to the Pod template (spec.template.metadata.labels) so logs, metrics, and events are queryable.
Annotations vs labels: annotations carry non-identifying metadata (git commit, deploy timestamp, owner, documentation URLs). Data used for selection goes into labels; narrative data goes into annotations. A long free-text note in a label is a finding.
Image tags and digests
:latest,:stable,:main, and other mutable tags are P1 findings — the image contents change without the manifest changing, which makes rollouts unreproducible.- Immutable tags (semver-pinned, build-id-pinned) are acceptable; digests (
repo/img@sha256:...) are preferred for production. imagePullPolicyset explicitly:IfNotPresentfor pinned tags/digests,Alwaysonly when mutability is an intentional property (rare).- Registry paths include the registry host (
ghcr.io/org/img:1.2.3, notorg/img:1.2.3) so the pull source is unambiguous and does not depend on Docker Hub implicit defaults.
Resource requests and limits
- Every container sets
resources.requests(cpuandmemory) — without requests, the scheduler uses BestEffort placement and the workload is evicted first under pressure. - Every container sets
resources.limits.memory— without a memory limit, a leaking process can OOM the node. (Memory limits without requests is also a finding — they differ.) - CPU limits are contentious: they prevent noisy-neighbor effects but can cause throttling that hurts latency. Absence of
cpulimit is acceptable when the workload's noisy-neighbor risk is analyzed; blind absence is a finding. GuaranteedQoS class requires every container in the Pod to haverequests == limitsfor BOTHcpuANDmemory, with all values > 0. Partial parity (e.g., memory-equal + diverged CPU, OR one container Guaranteed-shaped and another not) yieldsBurstable, notGuaranteed. Init and app containers count; ephemeral containers are excluded (they cannot declare resources). Document the QoS intent explicitly — reviewers cannot infer it from partial parity.- Ephemeral storage:
resources.requests.ephemeral-storageandlimits.ephemeral-storagefor workloads writing to emptyDir or container filesystems — prevents node disk exhaustion.
Probes
readinessProbeandlivenessProbeare distinct concerns — do not use the same probe for both:readinessProbegates traffic: "should I receive requests right now?". Failing a readiness probe removes the Pod from Service endpoints but does not restart it.livenessProberestarts the container: "am I wedged and unrecoverable?". Failing it kills the container.- Using the same HTTP path for both means a brief readiness blip triggers a restart, which is usually wrong.
startupProbefor slow-starting applications — lets liveness/readiness use short intervals without fighting long warm-ups.- Probe endpoints return cheaply (no DB round-trip on liveness; a cached readiness signal is fine).
initialDelaySeconds,periodSeconds,timeoutSeconds,failureThreshold,successThresholdtuned to the workload — defaults are rarely optimal.- HTTP probes specify the path and port explicitly; TCP probes are a fallback when HTTP is unavailable.
execprobes fork a subprocess per check — prefer HTTP/TCP when the application exposes them, butexecwith a lightweight binary (redis-cli PING,pg_isready) is the correct probe type for stateful services without HTTP endpoints. Avoid shells with complex logic.
Deeper probe semantics (startup vs liveness ordering, interaction with disruption, graceful shutdown coupling) belong to the reliability review — see reliability-checklist.md §Probe semantics and interaction when the diff contains a workload resource.
Ports, naming, and discoverability
- Container
ports[]entries havenameset —Service.spec.ports[].targetPortcan then refer to port names, which survive port-number changes. - Port names in BOTH
containerPorts[].nameANDService.spec.ports[].namefollow the IANA service-name rules (max 15 characters, lowercase, alphanumeric +-, must start/end with alphanumeric, and must contain at least one letter). Service.spec.typeexplicit (ClusterIP,NodePort,LoadBalancer) — defaulting toClusterIPis fine but naming it makes intent clear.Service.spec.ports[].appProtocolset when the protocol matters (http,grpc,tcp) — helps ingress controllers and service meshes.
Declarative patterns
- Manifests describe desired state, not actions. A manifest that includes shell commands to mutate cluster state via
lifecycle.postStarthooks doing kubectl-like work is a finding. - Prefer API-native primitives over creative workarounds:
PodDisruptionBudgetinstead of custom "don't evict me" annotations;NetworkPolicyinstead of per-Pod iptables rules; built-in probes instead of a liveness sidecar. - Annotations on
Ingressfor controller-specific features (rewrite rules, timeouts, TLS settings) — keep them consistent within a project, not a mix of controllers' quirks on different ingresses. Namespaceresources for every environment; avoiddefaultnamespace for workloads.Kind: Listwrappers (multi-resource files) are acceptable but consider one resource per file for reviewability; if multi-resource, separate with---.
Questions to ask
- "Could I roll this back by re-applying an older manifest?" — tests that nothing depends on mutable external state.
- "If I saw this manifest in production, could I tell which application, which version, which environment?" — tests labels.
- "What happens under memory pressure? Under CPU contention? Under a failed probe?" — tests resource/probe decisions.