Redis Operator provides full control over pod scheduling through standard Kubernetes scheduling primitives.
NodeSelector
Constrain pods to nodes with matching labels.
Key-value pairs that must all match node labels.spec:
nodeSelector:
disktype: ssd
region: us-west
Example: Schedule on SSD-backed nodes
-
Label nodes:
kubectl label nodes node-1 disktype=ssd
kubectl label nodes node-2 disktype=ssd
kubectl label nodes node-3 disktype=ssd
-
Configure cluster:
apiVersion: redis.io/v1
kind: RedisCluster
metadata:
name: my-cluster
spec:
instances: 3
storage:
size: 50Gi
nodeSelector:
disktype: ssd
-
Verify placement:
kubectl get pods -o wide
# All pods should be on node-1, node-2, or node-3
Affinity
Define advanced scheduling rules for co-location or separation.
Kubernetes Affinity object with nodeAffinity, podAffinity, and podAntiAffinity.
Pod Anti-Affinity (High Availability)
Use case: Ensure replicas are on different nodes/zones to survive node/zone failures.
Hard requirement (required):
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
redis.io/cluster: my-cluster
topologyKey: kubernetes.io/hostname
This ensures no two pods from the same cluster run on the same node.
Soft preference (preferred):
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
redis.io/cluster: my-cluster
topologyKey: kubernetes.io/hostname
This prefers different nodes but allows co-location if necessary.
Zone-Level Anti-Affinity
Spread across availability zones:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
redis.io/cluster: my-cluster
topologyKey: topology.kubernetes.io/zone
Requires:
- At least as many zones as
spec.instances
- Nodes labeled with
topology.kubernetes.io/zone
Node Affinity
Use case: Schedule only on nodes in specific regions.
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/region
operator: In
values:
- us-east-1
- us-east-2
Combined Affinity
Node affinity + pod anti-affinity:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values:
- m5.2xlarge
- m5.4xlarge
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
redis.io/cluster: my-cluster
topologyKey: kubernetes.io/hostname
Tolerations
Allow scheduling onto tainted nodes.
List of tolerations matching node taints.
Use case: Dedicate nodes to Redis workloads.
-
Taint nodes:
kubectl taint nodes redis-node-1 dedicated=redis:NoSchedule
kubectl taint nodes redis-node-2 dedicated=redis:NoSchedule
kubectl taint nodes redis-node-3 dedicated=redis:NoSchedule
-
Configure cluster:
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "redis"
effect: "NoSchedule"
-
Result: Redis pods can schedule on tainted nodes, but other workloads cannot.
Toleration Operators
Equal (exact match):
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
Exists (tolerate any value):
tolerations:
- key: "key1"
operator: "Exists"
effect: "NoSchedule"
Tolerate all taints:
tolerations:
- operator: "Exists" # No key specified
Taint Effects
NoSchedule: New pods won’t be scheduled (existing pods unaffected)
PreferNoSchedule: Avoid scheduling if possible
NoExecute: Evict existing pods that don’t tolerate the taint
Example (tolerate node under maintenance):
tolerations:
- key: "node.kubernetes.io/unschedulable"
operator: "Exists"
effect: "NoSchedule"
Topology Spread Constraints
Control how pods are spread across topology domains.
topologySpreadConstraints
[]TopologySpreadConstraint
Rules for distributing pods across failure domains.
Use case: Balance pods across zones with max skew of 1.
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
redis.io/cluster: my-cluster
Fields:
maxSkew: Maximum difference in pod count between any two zones
topologyKey: Node label key defining topology domains (e.g., zone, hostname)
whenUnsatisfiable: What to do if constraint can’t be satisfied
DoNotSchedule: Hard constraint (pod stays Pending)
ScheduleAnyway: Soft constraint (try to satisfy but allow violation)
labelSelector: Which pods to consider for distribution
Multi-Level Spread
Spread across zones and nodes:
spec:
topologySpreadConstraints:
# Zone level
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
redis.io/cluster: my-cluster
# Node level within each zone
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway # Soft constraint
labelSelector:
matchLabels:
redis.io/cluster: my-cluster
Example: 5 instances across 3 zones
| Zone | Nodes | Pods per Zone | Max Skew |
|---|
| us-east-1a | 2 | 2 | - |
| us-east-1b | 2 | 2 | - |
| us-east-1c | 1 | 1 | 1 (OK) |
MinDomains
Ensure minimum number of domains:
spec:
topologySpreadConstraints:
- maxSkew: 1
minDomains: 3 # Require at least 3 zones
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
redis.io/cluster: my-cluster
Pods stay Pending until 3 zones are available.
Complete Examples
Production HA Setup
apiVersion: redis.io/v1
kind: RedisCluster
metadata:
name: prod-redis
spec:
instances: 5
storage:
size: 100Gi
storageClassName: fast-ssd
# Schedule only on dedicated Redis nodes
nodeSelector:
workload: redis
# Tolerate Redis-dedicated taints
tolerations:
- key: "dedicated"
operator: "Equal"
value: "redis"
effect: "NoSchedule"
# Spread across zones and nodes
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values:
- r5.xlarge
- r5.2xlarge
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
redis.io/cluster: prod-redis
topologyKey: kubernetes.io/hostname
- labelSelector:
matchLabels:
redis.io/cluster: prod-redis
topologyKey: topology.kubernetes.io/zone
# Balanced distribution with hard zone constraint
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
redis.io/cluster: prod-redis
Cost-Optimized Setup (Spot Instances)
apiVersion: redis.io/v1
kind: RedisCluster
metadata:
name: dev-redis
spec:
instances: 3
storage:
size: 10Gi
# Prefer spot instances
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node.kubernetes.io/lifecycle
operator: In
values:
- spot
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # Soft anti-affinity
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
redis.io/cluster: dev-redis
topologyKey: kubernetes.io/hostname
# Tolerate spot instance taints
tolerations:
- key: "node.kubernetes.io/lifecycle"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
Multi-Region DR Setup
apiVersion: redis.io/v1
kind: RedisCluster
metadata:
name: dr-redis-west
namespace: default
spec:
instances: 3
storage:
size: 50Gi
# Pin to us-west region
nodeSelector:
topology.kubernetes.io/region: us-west-2
# Spread across zones within us-west
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
redis.io/cluster: dr-redis-west
# External replication from us-east
replicaMode:
enabled: true
source:
host: prod-redis-leader.us-east.example.com
port: 6379
clusterName: prod-redis-east
authSecretName: east-cluster-auth
Best Practices
Always use pod anti-affinity for HA clusters
Minimum: Node-level anti-affinity
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
redis.io/cluster: my-cluster
topologyKey: kubernetes.io/hostname
Recommended: Zone-level for multi-AZ clusters
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
redis.io/cluster: my-cluster
topologyKey: topology.kubernetes.io/zone
Dedicate nodes for production Redis
-
Create node pool:
# Example: GKE
gcloud container node-pools create redis-pool \
--cluster=my-cluster \
--machine-type=n2-standard-8 \
--num-nodes=3 \
--node-taints=dedicated=redis:NoSchedule \
--node-labels=workload=redis
-
Configure cluster:
spec:
nodeSelector:
workload: redis
tolerations:
- key: "dedicated"
operator: "Equal"
value: "redis"
effect: "NoSchedule"
Use topology spread for large clusters
Instead of just anti-affinity, use topology spread constraints for balanced distribution:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
redis.io/cluster: my-cluster
This ensures even distribution as you scale beyond 3 zones.
Test scheduling constraints before production
Dry-run to verify pod placement:
# Create cluster
kubectl apply -f rediscluster.yaml
# Check pod distribution
kubectl get pods -o wide -l redis.io/cluster=my-cluster
# Verify zone spread
kubectl get pods -l redis.io/cluster=my-cluster \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.nodeName}{"\n"}{end}' | \
while read pod node; do
zone=$(kubectl get node $node -o jsonpath='{.metadata.labels.topology\.kubernetes\.io/zone}')
echo "$pod -> $node -> $zone"
done
Troubleshooting
Pods stuck in Pending
Symptom:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-cluster-0 0/1 Pending 0 5m
Diagnosis:
kubectl describe pod my-cluster-0
# Look for events:
# 0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules.
Causes:
- Insufficient nodes: Anti-affinity requires N nodes for N pods
- Taint mismatch: Pods don’t tolerate node taints
- Resource constraints: Nodes lack CPU/memory
- Zone constraints: Not enough zones for topology spread
Solutions:
- Add more nodes:
kubectl scale nodepool --num-nodes=5
- Relax anti-affinity from
required to preferred
- Check node taints:
kubectl describe nodes | grep Taints
Uneven pod distribution
Symptom: 3 pods in zone A, 1 pod in zone B, 1 pod in zone C
Cause: Using preferredDuringSchedulingIgnoredDuringExecution (soft constraint)
Fix: Use requiredDuringSchedulingIgnoredDuringExecution (hard constraint)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # Changed from preferred
- labelSelector:
matchLabels:
redis.io/cluster: my-cluster
topologyKey: topology.kubernetes.io/zone
Then trigger rolling update:
kubectl annotate rediscluster my-cluster force-update="$(date)"
Label selector not matching
Symptom: Topology spread constraint not working
Cause: labelSelector doesn’t match pod labels
Debug:
# Check pod labels
kubectl get pods -l redis.io/cluster=my-cluster --show-labels
# Verify label selector
kubectl get pods -l redis.io/cluster=my-cluster,redis.io/role=primary
Fix: Use correct labels in topology spread:
topologySpreadConstraints:
- labelSelector:
matchLabels:
redis.io/cluster: my-cluster # Must match actual pod labels