Kubernetes Manifest Scaffolding Reference
Purpose: Scaffold raw Kubernetes manifests (Deployment, Service, Ingress, ConfigMap, Secret) and kustomize overlays for a workload that does not yet need full Helm packaging. Covers label conventions, namespace layout, resource requests/limits defaults, and probe wiring so the workload is admission-safe on first apply.
Scope Boundary
- Scaffold
k8s: raw manifest authoring and kustomize overlay layout (Deployment/Service/Ingress/ConfigMap/Secret, labels, namespace, requests/limits, probes). - Scaffold
helm: reusable, versioned chart packaging. If the workload must ship to 3+ environments or multiple tenants, escalate fromk8stohelm. - Scaffold
terraform: cluster-level provisioning (EKS / GKE / AKS control plane, node groups, IRSA). Manifests live ink8s; the cluster that runs them lives interraform. - Gear[gha] (elsewhere): CI/CD wiring —
kubectl apply -k,kustomize build, ArgoCD / Flux sync.k8sproduces the manifests;Gear[gha]wires the rollout. - Gateway (elsewhere): public API contract, versioning, breaking-change review. Ingress routing lives in
k8s; API rules (auth, rate limit, schema) live inGateway. - Native (elsewhere): mobile build/release concerns are out of scope for
k8s— route those toNative.
If the request is "package this workload for reuse" → helm. If it is "ship this single workload to one cluster" → k8s.
Workload Shape (minimum set)
| Resource | Required? | Notes |
|---|---|---|
Namespace |
✓ | One namespace per team or per bounded context; never deploy to default. Label the namespace with pod-security.kubernetes.io/enforce=restricted (see Pod Security Standards below). |
Deployment |
✓ | replicas: 2 minimum for anything fronted by a Service; single-replica only for non-HA jobs. |
Service |
✓ | ClusterIP by default; LoadBalancer only when ingress controller is absent. |
Ingress |
conditional | Required when HTTP(S) is exposed outside the cluster. |
ConfigMap |
conditional | Non-sensitive config. One CM per concern; avoid the "god CM". |
Secret |
conditional | Prefer ExternalSecrets / CSI driver over raw Secret objects. |
PodDisruptionBudget |
✓ for HA | minAvailable: 1 minimum for multi-replica workloads. |
HorizontalPodAutoscaler |
conditional | Only after resource requests are realistic (see defaults below). |
NetworkPolicy |
✓ for prod | Default-deny ingress + explicit egress allowlist per namespace; required on any cluster running untrusted or multi-tenant workloads. |
Pod Security Standards (2026 baseline)
By 2026, Pod Security Admission (PSA) enforcing the restricted policy is the default posture for new namespaces. Label every namespace explicitly — relying on the cluster-wide default has bitten too many teams:
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedThe restricted policy in 2026 requires every Pod spec to set:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]When the workload legitimately needs an escape (hostNetwork, privileged, write-able root FS), drop the namespace to baseline only with a written exception — never silently exempt workloads.
For stronger admission-time validation (custom policies, image-signature requirements), wire Kyverno (or OPA Gatekeeper) at the cluster level — PSA covers the standard cases, Kyverno covers the rest.
Label & Annotation Convention
Use the recommended Kubernetes labels — they are free, queryable, and required by many tools (ArgoCD, Prometheus operator).
metadata:
labels:
app.kubernetes.io/name: payments-api
app.kubernetes.io/instance: payments-api-prod
app.kubernetes.io/version: "1.24.3"
app.kubernetes.io/component: api
app.kubernetes.io/part-of: payments
app.kubernetes.io/managed-by: kustomizeSelector labels (Deployment.spec.selector.matchLabels) must be a stable subset — never include version or instance in selectors, they break rolling updates.
Resource Requests / Limits Defaults
Never ship without requests. Unrequested pods land in BestEffort QoS and are evicted first.
| Workload type | requests.cpu | requests.memory | limits.cpu | limits.memory |
|---|---|---|---|---|
| Small API | 100m |
128Mi |
500m |
256Mi |
| Standard API | 250m |
512Mi |
1000m |
1Gi |
| Batch / worker | 500m |
1Gi |
(no limit) | 2Gi |
| Sidecar (log/proxy) | 50m |
64Mi |
200m |
128Mi |
Prefer omitting limits.cpu (CPU throttling hurts latency more than it helps); always set limits.memory to prevent noisy-neighbor OOM cascade.
Probes
Liveness and readiness are not optional. Startup probe is required for slow-booting apps (>10s).
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet: { path: /livez, port: 8080 }
periodSeconds: 15
failureThreshold: 3
initialDelaySeconds: 30
startupProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 5
failureThreshold: 30 # allow 150s for JVM / cold-cache warmupNever point liveness at / or any path that touches downstream dependencies — liveness failure restarts the pod and cascades an outage through the whole tier.
Kustomize Overlay Layout
k8s/
base/
kustomization.yaml
namespace.yaml
deployment.yaml
service.yaml
configmap.yaml
overlays/
dev/
kustomization.yaml # patches replicas=1, lower requests
config-patch.yaml
staging/
kustomization.yaml
prod/
kustomization.yaml # patches replicas, HPA, PDB, prod image tag
hpa.yaml
pdb.yamlUse kustomization.yaml images: to pin tags per overlay. Use configMapGenerator / secretGenerator to avoid drift from hand-edited YAML.
Secret Handling
Raw Secret objects are base64, not encrypted. Three acceptable paths:
- External Secrets Operator (ESO) → pulls from AWS Secrets Manager / GCP Secret Manager / Vault.
- Secrets Store CSI Driver → mounts secrets as files, no K8s
Secretobject at all. - Sealed Secrets (Bitnami) → when the repo must be the source of truth and no secret manager exists.
Never commit a Secret with real data, even in dev/. Commit a placeholder and let ESO / CSI hydrate.
Anti-Patterns
- Deploying to
defaultnamespace — collides with cluster tooling and breaks RBAC granularity. latestimage tag — Kubernetes will not pull-refresh a running pod, and rollback becomes impossible.- Missing
resources.requests— evicted first under pressure, kills HPA accuracy. readinessProbepointing at a path that exercises the database — startup storms and thundering-herd restarts.- Single-replica
Deploymentbehind aServicewith no PDB — rolling node drain = full outage. - One monolithic
ConfigMapshared across unrelated workloads — changing one key restarts every consumer. - Including
versionlabel inselector.matchLabels— makes rolling updates orphan the old ReplicaSet. - Baking environment config into the image — breaks the 12-factor boundary and forces a rebuild per env.
- Setting
limits.cpuaggressively on latency-sensitive APIs — CPU throttling shows up as p99 spikes. - Using
hostNetwork: trueorhostPathvolumes without a documented reason — breaks admission policies and portability.
Handoff
When the k8s manifests are ready:
- To Gear[gha]: the
kustomize build overlays/<env>command, the image tag convention, and the promotion flow (dev → staging → prod). Gear[gha] wireskubectl apply/ ArgoCD sync / Flux Kustomization. - To Sentinel: RBAC assumptions,
ServiceAccountscope,NetworkPolicyposture (default-deny or not), and secret-handling path (ESO / CSI / Sealed Secrets). - To Beacon: probe endpoints, Prometheus scrape annotations (
prometheus.io/scrape,prometheus.io/port), and the metrics/log labels Beacon will key dashboards on. - To Canvas: topology — namespaces, services, ingress routes — so Canvas can render the cluster view.
- Upgrade path: if the workload is about to be installed into a second cluster or handed to another team, re-scaffold as a Helm chart via
scaffold helm.