In this lesson: Design an application that can be killed and replaced at any moment.
Orchestration only works if your application cooperates. A system that can move a workload between machines assumes the workload does not mind being moved. Most of the discipline below is old, unglamorous, and the reason some migrations are boring and others take a year.
Configuration comes from the environment
apiVersion: v1
kind: ConfigMap
metadata:
name: yanjye-config
data:
APP_ENV: production
APP_DEBUG: "false"
DB_HOST: yanjye-db
---
apiVersion: v1
kind: Secret
metadata:
name: yanjye-secrets
type: Opaque
stringData:
DB_PASSWORD: a-long-random-string
envFrom:
- configMapRef: { name: yanjye-config }
- secretRef: { name: yanjye-secrets }
Never commit secrets to Git — including your manifests
Manifests belong in version control. Secrets do not. The usual answers are sealed secrets, which encrypt a value so only the cluster can decrypt it and the encrypted form is safe to commit, or an operator that pulls values from a real secret manager at run time.
Stateless, so that any pod can serve any request
If a request can only be served by the pod that handled the previous one, you cannot scale, cannot roll out, and cannot lose a node gracefully.
| State | Wrong place | Right place |
|---|---|---|
| Sessions | Files on the pod | Redis, or a signed cookie |
| Uploads | The pod's filesystem | Object storage |
| Cache | In-process only | Shared cache, with in-process as an optimisation |
| Scheduled jobs | Cron inside every pod | A CronJob, or a leader-elected worker |
| Logs | Files on the pod | Standard output, collected centrally |
Logs go to standard output
Do not write log files. Write to stdout and stderr and let the platform collect them. A file inside a pod disappears with the pod — usually the pod that crashed, taking the evidence with it.
Shutting down properly
When Kubernetes stops a pod it sends SIGTERM, waits, then sends SIGKILL. An application that ignores SIGTERM has every in-flight request killed mid-response on every single deploy.
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["sleep", "5"]
The preStop sleep looks odd but is the standard fix for a real race: removal from the load balancer and the SIGTERM happen concurrently, so without a short pause a few requests are routed to a pod that has already begun shutting down.
Handle SIGTERM in your code
pcntl_signal(SIGTERM, function () {
// stop accepting new work, finish what is in progress, then exit
$this->shouldStop = true;
});
This matters most for queue workers: without it, a deploy kills jobs halfway through. With it, the worker finishes its current job and exits cleanly.
Start fast, and do not do work at boot
A pod that takes ninety seconds to become ready makes every rollout slow and every recovery slow. Warm caches lazily rather than at startup, and never run migrations from the application's own start-up path — with four replicas that is four processes migrating the same database at once. Run migrations as a separate Job before the rollout.
Try it yourself
While your application is serving traffic, run kubectl delete pod on one of its pods and watch with a load generator running. Count the failed requests. If it is not zero, you have found either a missing readiness probe, unhandled SIGTERM, or state stored in the pod.