Kubernetes StatefulSets for Trading Systems
StatefulSets vs Deployments, pod identity, PersistentVolumes, and graceful shutdown patterns for trading infrastructure.
🎯 What You'll Learn
- Understand when StatefulSets are required vs Deployments
- Configure pod identity and stable network names
- Implement graceful shutdown for trading workloads
- Use PersistentVolumes for order state persistence
Why StatefulSets for Trading?
Trading systems are stateful:
- Order management systems track open orders
- Market makers maintain inventory positions
- Matching engines preserve order book state
Deployments are for stateless apps. StatefulSets are for stateful apps.
StatefulSets vs Deployments
| Feature | Deployment | StatefulSet |
|---|---|---|
| Pod naming | Random (app-xyz123) | Ordered (app-0, app-1) |
| Scaling | All pods equal | Ordered startup/shutdown |
| Storage | Shared or none | Per-pod PersistentVolumes |
| Network | Random IPs | Stable DNS names |
| Use case | Stateless web apps | Databases, trading systems |
When a Deployment pod restarts, it gets a new identity. Your order manager loses track of which orders belong to which pod. With StatefulSets, trading-0 is always trading-0 — even after restarts. This enables consistent order routing, per-pod state persistence, and predictable failover.
StatefulSet Definition
# trading-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: order-manager
spec:
serviceName: "order-manager"
replicas: 3
selector:
matchLabels:
app: order-manager
template:
metadata:
labels:
app: order-manager
spec:
terminationGracePeriodSeconds: 60 # Time to drain orders
containers:
- name: order-manager
image: trading/order-manager:v1.2
ports:
- containerPort: 8080
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "kill -SIGTERM 1 && sleep 30"]
volumeMounts:
- name: order-state
mountPath: /data/orders
volumeClaimTemplates:
- metadata:
name: order-state
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "fast-ssd"
resources:
requests:
storage: 50Gi
```diff
This creates:
- `order-manager-0`, `order-manager-1`, `order-manager-2`
- Each pod gets its own 50Gi persistent volume
- 60s grace period for order draining
---
## Stable Network Identity
StatefulSets create DNS entries:
```bash
# Each pod gets a predictable DNS name
order-manager-0.order-manager.trading.svc.cluster.local
order-manager-1.order-manager.trading.svc.cluster.local
order-manager-2.order-manager.trading.svc.cluster.local
```text
Your order routing can use these stable names:
```python
# Route orders by symbol partition
def get_order_manager(symbol):
partition = hash(symbol) % 3
return f"order-manager-{partition}.order-manager:8080"
```diff
---
## Graceful Shutdown: Draining Orders
Trading pods can't just terminate. They must:
1. Stop accepting new orders
2. Complete pending orders
3. Persist state to disk
4. Terminate
```yaml
spec:
terminationGracePeriodSeconds: 120 # 2 minutes to drain
containers:
- name: order-manager
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 1. Signal app to stop accepting new orders
kill -SIGUSR1 1
# 2. Wait for pending orders to complete
/app/wait-for-drain.sh
# 3. Persist final state
/app/checkpoint-state.sh
```python
---
## Common Misconceptions
**Myth:** "I can use Deployments and just save state to Redis."
**Reality:** External state stores add latency and failure modes. For hot trading state (open orders, positions), local SSD with StatefulSet is faster and simpler.
**Myth:** "StatefulSets are slow to scale."
**Reality:** StatefulSets scale one pod at a time by default (ordered). For trading, this is a feature-you don't want 3 new order managers stealing positions simultaneously.
**Myth:** "PersistentVolumes survive node failures."
**Reality:** Depends on storage class. EBS-backed volumes survive node failures. Local NVMe does not. Choose based on your durability requirements.
---
## Headless Service for DNS
StatefulSets require a headless service:
```yaml
apiVersion: v1
kind: Service
metadata:
name: order-manager
labels:
app: order-manager
spec:
clusterIP: None # Headless!
ports:
- port: 8080
name: http
selector:
app: order-manager
```diff
`clusterIP: None` tells Kubernetes not to load-balance-instead, DNS returns individual pod IPs.
---
## AWS EKS Configuration
For trading on AWS:
```yaml
# Use io2 EBS for low-latency persistent storage
storageClassName: io2
# Node affinity for dedicated trading nodes
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- trading
# Pod anti-affinity: spread across AZs
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: order-manager
topologyKey: topology.kubernetes.io/zone
```diff
---
## Practice Exercises
### Exercise 1: Create a StatefulSet
```bash
kubectl apply -f trading-statefulset.yaml
kubectl get pods -w # Watch ordered startup
```text
### Exercise 2: Test Pod Identity
```bash
# Delete a pod, watch it come back with same name
kubectl delete pod order-manager-1
kubectl get pods -w
# order-manager-1 returns (not random name)
```text
## Exercise 3: Graceful Shutdown
```bash
# Scale down and watch drain
kubectl scale statefulset order-manager --replicas=2
# order-manager-2 gets preStop signal first
Key Takeaways
- StatefulSets = stable identity - Pod names survive restarts
- Ordered scaling - One at a time, predictable
- Per-pod storage - Each pod gets its own PersistentVolume
- Graceful shutdown is mandatory - Use preStop hooks
What’s Next?
Continue learning: Trading System Metrics
Expert version: Kubernetes StatefulSets: Why Trading Systems Need State
Want to go deeper?
Weekly infrastructure insights for engineers who build trading systems.
Free forever. Unsubscribe anytime.
You're in. Check your inbox.
Questions about this lesson? Working on related infrastructure?
Let's discuss