d1aa5daf2b
---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---
157 lines
5.7 KiB
Markdown
157 lines
5.7 KiB
Markdown
# Good Example: Kubernetes Deployment
|
|
|
|
> A Kubernetes Deployment that follows Atelier's Kubernetes principles. Each aspect cites the principle it satisfies.
|
|
|
|
## The Deployment
|
|
|
|
A stateless web service deployed as a Deployment with probes, resource contracts, RBAC, and a rolling update strategy — the canonical "production workload" pattern.
|
|
|
|
### Manifest
|
|
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: api
|
|
namespace: api-prod
|
|
labels:
|
|
app: api
|
|
tier: web
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: api
|
|
strategy:
|
|
type: RollingUpdate
|
|
rollingUpdate:
|
|
maxSurge: 1
|
|
maxUnavailable: 0
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: api
|
|
tier: web
|
|
spec:
|
|
serviceAccountName: api
|
|
automountServiceAccountToken: false
|
|
containers:
|
|
- name: api
|
|
image: registry.example.com/api:v1.4.2 # pinned, not :latest
|
|
ports:
|
|
- containerPort: 8080
|
|
resources:
|
|
requests:
|
|
cpu: 250m
|
|
memory: 256Mi
|
|
limits:
|
|
memory: 512Mi
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /healthz/ready
|
|
port: 8080
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 5
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /healthz/live
|
|
port: 8080
|
|
initialDelaySeconds: 15
|
|
periodSeconds: 10
|
|
env:
|
|
- name: LOG_LEVEL
|
|
valueFrom:
|
|
configMapKeyRef:
|
|
name: api-config
|
|
key: log_level
|
|
- name: DATABASE_URL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: api-secrets
|
|
key: database_url
|
|
volumeMounts:
|
|
- name: config
|
|
mountPath: /etc/api
|
|
readOnly: true
|
|
volumes:
|
|
- name: config
|
|
configMap:
|
|
name: api-config
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: api
|
|
namespace: api-prod
|
|
spec:
|
|
selector:
|
|
app: api
|
|
ports:
|
|
- port: 80
|
|
targetPort: 8080
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: api-deny-ingress
|
|
namespace: api-prod
|
|
spec:
|
|
podSelector:
|
|
matchLabels:
|
|
app: api
|
|
policyTypes:
|
|
- Ingress
|
|
ingress:
|
|
- from:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
tier: edge
|
|
```
|
|
|
|
## What Makes It Good
|
|
|
|
### Controller, Not Bare Pod (k8s P2 Pods are Mortal)
|
|
- A `Deployment` manages the pods. If one dies, the controller replaces it. A bare pod has no recovery.
|
|
- See `domains/kubernetes/workloads.md`.
|
|
|
|
### Resource Contracts (k8s P4 Requests and Limits are Contracts)
|
|
- Every container has CPU and memory requests and a memory limit. The workload is `Burstable`, not `BestEffort` (first evicted under pressure).
|
|
- See `domains/kubernetes/workloads.md` for QoS classes.
|
|
|
|
### Probes (k8s P5 Probes Drive Health)
|
|
- `readinessProbe` gates traffic: a pod that is not ready is removed from the Service's endpoints.
|
|
- `livenessProbe` restarts a wedged container.
|
|
- The probes check the workload's own health (`/healthz/ready`, `/healthz/live`), not a dependency. A liveness probe that calls the database would cascade-restart on a DB blip.
|
|
- See `domains/kubernetes/workloads.md`.
|
|
|
|
### Image Pinning (k8s P1 + IaC P5 Version Everything)
|
|
- `image: registry.example.com/api:v1.4.2` — pinned to a version, not `:latest`. A pod restart pulls the same image it was built with.
|
|
- See `domains/infrastructure-as-code/terraform.md` and `domains/devops/P7 Immutability` for the immutability angle.
|
|
|
|
### RBAC (k8s P7 RBAC by Intent, Not Identity)
|
|
- `serviceAccountName: api` — the workload runs as a dedicated ServiceAccount, not the `default` shared identity.
|
|
- `automountServiceAccountToken: false` — the workload does not call the API, so it gets no token. See `domains/kubernetes/rbac.md`.
|
|
- A matching `Role` + `RoleBinding` (not shown) would grant `get, list, watch` on `configmaps` in this namespace — least privilege, scoped by intent.
|
|
|
|
### Config and Secrets Separate (k8s P9 Config and Secrets are Separate)
|
|
- `LOG_LEVEL` from a ConfigMap (non-sensitive). `DATABASE_URL` from a Secret (sensitive). Both injected at runtime; neither baked into the image.
|
|
- A configuration change does not require a rebuild. A secret rotation does not require an image redeploy.
|
|
- See `domains/kubernetes/rbac.md` and `domains/security/secrets.md`.
|
|
|
|
### Namespaces Bound Blast Radius (k8s P6 Namespaces Bound Blast Radius)
|
|
- The workload lives in `api-prod`, not `default`. The namespace is the unit of quota, RBAC, and NetworkPolicy. A problem in `api-prod` does not leak to other workloads.
|
|
- See `domains/kubernetes/networking.md`.
|
|
|
|
### NetworkPolicy Default-Deny (k8s P6, P7)
|
|
- The `NetworkPolicy` allows ingress only from the `edge` namespace. Without it, every pod in the cluster could reach `api`. Default-deny is the baseline; allows are the exceptions.
|
|
- See `domains/kubernetes/networking.md`.
|
|
|
|
### Roll Forward, Roll Back (k8s P10 Roll Forward Roll Back)
|
|
- `strategy: RollingUpdate` with `maxSurge: 1, maxUnavailable: 0` — the rollout adds a new pod before removing an old one. Availability is maintained.
|
|
- `kubectl rollout undo deployment/api` reverts to the previous ReplicaSet. The rollback is tested before it is needed.
|
|
- See `domains/kubernetes/workloads.md` and `domains/devops/P5 Progressive Delivery`.
|
|
|
|
### Cross-Domain Links
|
|
- `domains/devops/P4 Rollback First` — the rollout strategy makes the deploy reversible.
|
|
- `domains/security/authorization` — the ServiceAccount + Role model is the k8s expression of least-privilege authorization.
|
|
- `domains/observability/metrics` — the probes are the platform's observability into the workload's health; the workload's own metrics complete the picture. |