Skip to main content

Affinity & Anti-Affinity

The difference between nodeAffinity and nodeSelectors is nodeAffinity allows you to use matchExpressions (for more complex rules).

  • nodeAffinity - uses Labels on Nodes to make a scheduling decision with matchExpressions
    • requiredDuringSchedulingIgnoredDuringExecution - a pod is not scheduled if the defined rules are not evaluate to true
    • preferredDuringSchedulingIgnoredDuringExecution - if the rules are not evaluated to true, the pod is scheduled
  • podAffinity - use labels and selectors to schedule Pods onto the same Node, Zone as some other Pod (with matchLabels or matchExpressions)
  • podAntiAffinity - schedule Pods onto the different Node, Zone as some other Pod

More infi here https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity

Using Affinity to Control Pod Placement

In this example, I want to deploy a cache server, on a node that already have a web server (for lower latency).

spec:
containers:
- name: hello-world-cache
...
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- hello-world-web
topologyKey: "kubernetes.io/hostname"

Demo 1

Using Affinity to schedule Pods to Nodes

Create a file named deployment-affinity.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-web
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-web
template:
metadata:
labels:
app: hello-world-web
spec:
containers:
- name: hello-world-web
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-cache
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-cache
template:
metadata:
labels:
app: hello-world-cache
spec:
containers:
- name: hello-world-cache
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- hello-world-web
topologyKey: "kubernetes.io/hostname"
#Let's start off with a deployment of web and cache pods
#Affinity: we want to have always have a cache pod co-located on a Node where we a Web Pod
kubectl apply -f deployment-affinity.yaml

#Let's check out the labels on the nodes, look for kubernetes.io/hostname which
#we're using for our topologykey
kubectl describe nodes c1-node1 | head
kubectl get nodes --show-labels

#We can see that web and cache are both on the name node
kubectl get pods -o wide

#If we scale the web deployment
#We'll still get spread across nodes in the ReplicaSet, so we don't need to enforce that with affinity
kubectl scale deployment hello-world-web --replicas=2
kubectl get pods -o wide

#Then when we scale the cache deployment, it will get scheduled to the same node as the other web server
kubectl scale deployment hello-world-cache --replicas=2
kubectl get pods -o wide

#Clean up the resources from these deployments
kubectl delete -f deployment-affinity.yaml

Demo 2

Using Anti-Affinity to schedule Pods to Nodes

Let's create 2 files, deployment-antiaffinity.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-web
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-web
template:
metadata:
labels:
app: hello-world-web
spec:
containers:
- name: hello-world-web
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- hello-world-web
topologyKey: "kubernetes.io/hostname"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-cache
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-cache
template:
metadata:
labels:
app: hello-world-cache
spec:
containers:
- name: hello-world-cache
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- hello-world-web
topologyKey: "kubernetes.io/hostname"

and deployment-antiaffinity-corrected.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-web
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-web
template:
metadata:
labels:
app: hello-world-web
spec:
containers:
- name: hello-world-web
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- hello-world-web
topologyKey: "kubernetes.io/hostname"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-cache
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-cache
template:
metadata:
labels:
app: hello-world-cache
spec:
containers:
- name: hello-world-cache
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- hello-world-web
topologyKey: "kubernetes.io/hostname"
#Now, let's test out anti-affinity, deploy web and cache again. 
#But this time we're going to make sure that no more than 1 web pod is on each node with anti-affinity
kubectl apply -f deployment-antiaffinity.yaml
kubectl get pods -o wide

#Now let's scale the replicas in the web and cache deployments
kubectl scale deployment hello-world-web --replicas=4

#One Pod will go Pending because we can have only 1 Web Pod per node
#when using requiredDuringSchedulingIgnoredDuringExecution in our antiaffinity rule
kubectl get pods -o wide --selector app=hello-world-web

#To 'fix' this we can change the scheduling rule to preferredDuringSchedulingIgnoredDuringExecution
#Also going to set the number of replicas to 4
kubectl apply -f deployment-antiaffinity-corrected.yaml
kubectl scale deployment hello-world-web --replicas=4

#Now we'll have 4 pods up an running, but doesn't the scheduler already ensure replicaset spread? Yes!
kubectl get pods -o wide --selector app=hello-world-web

#Let's clean up the resources from this demos
kubectl delete -f deployment-antiaffinity-corrected.yaml