<p>I have a 5 node cluster(1-master/4-worker). Is it possible to configure a StatefulSet where I can make a pod(s) to run on a given node knowing it has sufficient capacity rather Kubernetes Scheduler making this decision?</p>

<p>Lets say, my StatefulSet create 4 pods(replicas: 4) as myapp-0,myapp-1,myapp-2 and myapp-3. Now what I am looking for is:</p>

<p>myapp-0 pod-- get scheduled over---> worker-1</p>

<p>myapp-1 pod-- get scheduled over---> worker-2</p>

<p>myapp-2 pod-- get scheduled over---> worker-3</p>

<p>myapp-3 pod-- get scheduled over---> worker-4</p>

<p>Please let me know if it can be achieved somehow? Because if I add a toleration to pods of a StatefulSet, it will be same for all the pods and all of them will get scheduled over a single node matching the taint. </p>

<p>Thanks, J</p>

Jaraws
<p>You can delegate responsibility for scheduling arbitrary subsets of pods to your own custom scheduler(s) that run(s) alongside, or instead of, the default Kubernetes scheduler. </p>

<p>You can write your own custom  scheduler. A custom scheduler can be written in any language and can be as simple or complex as you need. Below is a very simple example of a custom scheduler written in Bash that assigns a node randomly. Note that you need to run this along with kubectl proxy for it to work.</p>

<pre class="lang-sh prettyprint-override"><code>SERVER='localhost:8001'

while true;

do

    for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"')

;

    do

        NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))


        NUMNODES=${#NODES[@]}

        CHOSEN=${NODES[$[$RANDOM % $NUMNODES]]}

        curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target": {"apiVersion": "v1", "kind"

: "Node", "name": "'$CHOSEN'"}}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/

        echo "Assigned $PODNAME to $CHOSEN"

    done

    sleep 1

done
</code></pre>

<p>Then just in your StatefulSet configuration file under specification section you will have to add <code>schedulerName: your-scheduler</code> line. </p>

<p>You can also use <a href="https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#always-co-located-in-the-same-node" rel="noreferrer">pod affinity:</a>.</p>

<p>Example:</p>

<pre><code>apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine
</code></pre>

<p>The below yaml snippet of the webserver statefuset has podAntiAffinity and podAffinity configured. This informs the scheduler that all its replicas are to be co-located with pods that have selector label <strong>app=store</strong>. This will also ensure that each web-server replica does not co-locate on a single node.</p>

<pre><code>apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.12-alpine
</code></pre>

<p>If we create the above two deployments, our three node cluster should look like below.</p>

<pre><code>node-1              node-2           node-3
webserver-1     webserver-2          webserver-3
cache-1             cache-2          cache-3
</code></pre>

<p>The above example uses PodAntiAffinity rule with topologyKey: <code>"kubernetes.io/hostname"</code> to deploy the redis cluster so that no two instances are located on the same host</p>

<p>You can simply define three replicas of specific pod and define particular pod configuration file, egg.:
There is label: <a href="https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodename" rel="noreferrer">nodeName</a> which is the simplest form of node selection constraint, but due to its limitations it is typically not used. nodeName is a field of PodSpec. If it is non-empty, the scheduler ignores the pod and the kubelet running on the named node tries to run the pod. Thus, if nodeName is provided in the PodSpec, it takes precedence over the above methods for node selection.</p>

<p>Here is an example of a pod config file using the nodeName field:</p>

<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-worker-1
</code></pre>

<p>More information about scheduler: <a href="https://kubernetes.io/blog/2017/03/advanced-scheduling-in-kubernetes/" rel="noreferrer">custom-scheduler</a>.</p>

<p>Take a look on this article: <a href="https://medium.com/kubernetes-tutorials/learn-how-to-assign-pods-to-nodes-in-kubernetes-using-nodeselector-and-affinity-features-e62c437f3cf8" rel="noreferrer">assigining-pods-kubernetes</a>.</p>
