---ci--- project: atelier phase: 5 milestone: v0.2 status: complete requirements: covered: [ATELIER-36, ATELIER-37, ATELIER-38, ATELIER-39, ATELIER-40, ATELIER-41, ATELIER-42, ATELIER-43, ATELIER-44, ATELIER-45, ATELIER-46, ATELIER-47, ATELIER-48, ATELIER-49, ATELIER-50, ATELIER-51, ATELIER-52, ATELIER-53, ATELIER-54, ATELIER-55, ATELIER-56, ATELIER-57, ATELIER-58, ATELIER-59] partial: [] ---/ci---
5.1 KiB
Bad Example: Bare Pod, No Resources
A Kubernetes manifest that violates Atelier's Kubernetes principles. Each violation is cited.
The Code
apiVersion: v1
kind: Pod
metadata:
name: api
namespace: default
spec:
containers:
- name: api
image: api:latest # :latest, unversioned
ports:
- containerPort: 8080
env:
- name: DATABASE_URL
value: "postgres://admin:hunter2@db:5432/app" # secret in plaintext, in the manifest
The team applies it with kubectl apply -f api-pod.yaml. When the pod crashes, they kubectl delete pod api && kubectl apply -f api-pod.yaml to "restart" it. There are no probes, no resource requests, no RBAC, no NetworkPolicy.
What Makes It Bad
Bare Pod, No Controller (k8s P2 Pods are Mortal)
- A
kind: Podwith no controller. When the node dies, the pod does not come back. When the team needs three replicas, they copy the YAML twice and rename it. - The "restart" workflow (
delete pod && apply) is manual recovery — exactly the manual-mutation anti-pattern fromdomains/devops/. - Fix: use a
Deployment. The controller replaces dead pods, scales, and rolls back. Seedomains/kubernetes/workloads.md.
No Resource Requests (k8s P4 Requests and Limits are Contracts)
- The container has no
resources.requestsorresources.limits. It isBestEffort— first evicted under node pressure. The scheduler has no signal to place it well; it lands wherever there is room, then gets killed when the node is full. - A workload without requests is an unbounded gamble on the scheduler.
- Fix: set CPU and memory requests on every prod container; set a memory limit; consider a CPU limit. See
domains/kubernetes/workloads.md.
No Probes (k8s P5 Probes Drive Health)
- No
readinessProbe— the Service routes traffic to the pod before it is ready. Users see 502s during startup. - No
livenessProbe— a wedged container runs forever; no one notices until the outage. - The platform cannot heal what it cannot see. A pod without probes is invisible to the controller's reconciliation.
- Fix: define readiness and liveness probes that check the workload's own health. See
domains/kubernetes/workloads.md.
:latest Image Tag (k8s P1 + IaC P5 Version Everything)
image: api:latestis unversioned. Everykubectl applypulls whatever is newest at that moment. Two pods "running the same manifest" run different images iflatestmoved between applies.- Rollback is impossible — there is no version to roll back to.
- Fix: pin the image to a version or a digest:
image: registry.example.com/api:v1.4.2orimage: registry.example.com/api@sha256:.... Seedomains/kubernetes/workloads.mdanddomains/infrastructure-as-code/terraform.md(P5 Version Everything).
Secret in Plaintext in the Manifest (k8s P9 Config and Secrets are Separate, IaC P10)
DATABASE_URLwith the password is in the manifest in plaintext. If the manifest is committed (it is), the secret is in git.- Rotating the secret requires editing the manifest and re-applying — no separation of config from secret.
- Fix: put the URL in a
Secret(created out-of-band or via a secrets tool) and reference it withvalueFrom.secretKeyRef. The manifest contains the reference, not the value. Seedomains/kubernetes/rbac.mdanddomains/security/secrets.md.
default Namespace (k8s P6 Namespaces Bound Blast Radius)
- The pod runs in
default. There is no namespace boundary for quota, RBAC, or NetworkPolicy. Every other workload indefaultcan reach it; an outage in one affects the namespace all share. - Fix: give every prod workload a named namespace sized to its blast radius.
defaultis for nothing in production. Seedomains/kubernetes/networking.mdanddomains/kubernetes/workloads.md.
No RBAC, No NetworkPolicy (k8s P7 RBAC by Intent, P6 Namespaces Bound Blast)
- No
serviceAccountName— the pod uses thedefaultServiceAccount, a shared identity. - No
NetworkPolicy— every pod in the cluster can reachapi. The network is flat by default. - Fix: a dedicated ServiceAccount with a least-privilege Role bound by intent. A default-deny NetworkPolicy with explicit allows. See
domains/kubernetes/rbac.mdanddomains/kubernetes/networking.md.
The Cascade
The violations compound. A bare pod with no probes crashes silently and is not restarted. :latest means the "restart" pulls a different image than the one that crashed. The plaintext secret in the manifest is now in git, so rotating it means editing the manifest and re-applying — and the new manifest still has no controller, so the next crash starts the manual loop again. default namespace means the blast radius is the whole cluster.
Cross-Domain Links
domains/devops/P4 Rollback First— with:latestand no controller, there is no rollback path.domains/security/authorization— the shareddefaultServiceAccount and flat network are the k8s expression of zero-trust violation.domains/errors/P2 Fail Loudly— a pod with no probes fails silently; the platform has no signal to act on.