Two of this post's four affinity examples cannot be applied, the word "slightly" hides the whole difference between a filter and a score, and the page that replaced it still tells you to switch off a field through a gate that is locked on¶
Post — Advanced Scheduling in Kubernetes, 2017-03-31, Kubernetes v1.6 — Ian Lewis and David Oppenheimer of Google, part of Five Days of Kubernetes 1.6. Four features, all moving to beta in the same release: node affinity/anti-affinity, taints and tolerations, pod affinity/anti-affinity, and custom schedulers.
As written — the post opens by conceding the default scheduler is usually right, then states the premise for everything that follows:
Ultimately, you know much more about how your applications should be scheduled and deployed than Kubernetes ever will.
Node affinity is introduced as "a generalization of the nodeSelector feature which has been in Kubernetes since version 1.0", split into required and preferred. The required example is a pod that must land in one GCE zone:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
Then the sentence this exercise is built on:
You can prefer instead of require that pods are deployed to us-central1-a by slightly changing the pod spec to use preferredDuringSchedulingIgnoredDuringExecution:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
A third block does anti-affinity by swapping In for NotIn, and the operator list follows:
"Valid operators you can use are In, NotIn, Exists, DoesNotExist. Gt, and Lt." The post also names
a field that does not exist yet: "There are future plans to offer
requiredDuringSchedulingRequiredDuringExecution which will evict pods from nodes as soon as they
don't satisfy the node affinity rule(s)."
Taints and tolerations come next, with the example the reader's own cluster already runs — "you might want to mark your master node as schedulable only by Kubernetes system components" — and one command:
The three effects are given in a parenthesis: "The other taint effects are PreferNoSchedule, which is the preferred version of NoSchedule, and NoExecute, which means any pods that are running on the node when the taint is applied will be evicted unless they tolerate the taint." Then an alpha feature:
In addition to moving taints and tolerations to beta in Kubernetes 1.6, we have introduced an alpha feature that uses taints and tolerations to allow you to customize how long a pod stays bound to a node when the node experiences a problem like a network partition instead of using the default five minutes.
Pod affinity gets a worked scenario — front-ends in service S1 talking to back-ends in S2, wanted in one zone but not a zone chosen by hand — with the labels stated in prose as "service=S2" and "service=S1":
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: service
operator: In
values: [“S1”]
topologyKey: failure-domain.beta.kubernetes.io/zone
Anti-affinity is "just make two changes to the snippet above -- change podAffinity to podAntiAffinity and change topologyKey to kubernetes.io/hostname."
Custom schedulers close the post. A pod carrying schedulerName: my-scheduler is ignored by the
default scheduler and "will remain in a Pending state" until something else claims it, and the
post supplies that something: a scheduler in Bash, which lists unscheduled pods with that
schedulerName, picks a node with $RANDOM, and POSTs a Binding.
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/
The last paragraph is a warning worth keeping: the release notes have "details about how to change your configurations if you are already using the alpha version of one or more of these features (this is required, as the move from alpha to beta is a breaking change for these features)".
As it runs now — start with the two YAML blocks the post presents as variants of each other, because they are not variants. The pin's Pod API reference gives the required side a node selector and the preferred side something else entirely:
preferredDuringSchedulingIgnoredDuringExecution— PreferredSchedulingTerm array — The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. [...] compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.—
docs/reference/kubernetes-api/core/pod-v1.md:1814-1815
A PreferredSchedulingTerm has exactly two fields and both are required: preference, which is
a NodeSelectorTerm, and weight, an integer (pod-v1.md:2330-2334). So the post's second block is
wrong three times over. It supplies a mapping where an array is required. It has no preference
wrapper. And it has no weight at all — which is not a cosmetic omission, because weight is the
entire output of the preferred form: the required form answers yes or no, and the preferred form
answers with a number that is summed across terms. The two forms do not differ by one field name.
One is a filter and the other is a scoring function, and the post's word for the distance between
them is slightly.
The fourth block, the pod affinity one, fails for a different reason. Its selector value is
[“S1”] — typographic quotation marks, not the straight ones used in all three node affinity
blocks. YAML has no special meaning for “, so the value is the four-character string “S1”, and
the pin's label rules leave no room for it:
- must be 63 characters or less (can be empty),
- unless empty, must begin and end with an alphanumeric character (
[a-z0-9A-Z]),- could contain dashes (
-), underscores (_), dots (.), and alphanumerics between.—
docs/concepts/overview/working-with-objects/labels.md:79-81
A value that begins with “ cannot be a label value, so the selector cannot be a label selector.
The post's prose says the label is service=S1; its manifest asks for something no object can
carry. Step 7 finds out which of the two problems the API server reports.
Three of the post's five documentation links are dead paths at the pin:
/docs/user-guide/node-selection/ (the target of three separate links, each with its own anchor),
/docs/user-guide/replicasets/, and /docs/admin/multiple-schedulers/ all have zero occurrences.
One of the three anchors is misspelled in the source as well —
#taints-and-toleations-beta-feature — so it was broken before the page moved. What replaced the
node-selection page is docs/concepts/scheduling-eviction/assign-pod-node.md, and it is where the
rest of this exercise goes.
The operator list has been split into two tables. Four operators work everywhere; the two the post appends after a stray full stop do not:
The following operators can only be used with
nodeAffinity. [...]GtandLtoperators will not work with non-integer values. If the given value doesn't parse as an integer, the Pod will fail to get scheduled. Also,GtandLtare not available forpodAffinity.—
docs/concepts/scheduling-eviction/assign-pod-node.md:718-730
Hold that sentence — "can only be used with nodeAffinity" — against the last row of the ladder
below, which is an alpha gate at v1.37 whose whole purpose is to add Lt and Gt to tolerations.
The page's restriction is true today and is scheduled to stop being true, and nothing on the page
says so.
requiredDuringSchedulingRequiredDuringExecution, the post's future plan, has zero occurrences at
the pin: nine years, no field.
2016's StatefulSet exercise
found the same string typed into a live manifest and traces what happened to it there; what is new
here is only that the post promised it in the same release the annotation-based form was already on
its way out.
The taints half has aged better than the affinity half. The command is unchanged, the three effects
are unchanged, and the post's own master-node example is what kubeadm still does to your control
plane (node-role.kubernetes.io/control-plane:NoSchedule, at
reference/setup-tools/kubeadm/implementation-details.md:438). One phrase did not survive. The post
calls PreferNoSchedule "the preferred version of NoSchedule", which reads as a recommendation;
the pin calls it "a 'preference' or 'soft' version of NoSchedule" (taint-and-toleration.md:115).
Same word, opposite senses, and only the second one is about the scheduler.
The post's alpha feature had the strangest fate of the four. It offered to let you customise the five-minute eviction delay "instead of using the default five minutes" — and what shipped was the mechanism that produces the default:
This admission controller sets the default forgiveness toleration for pods to tolerate the taints
notready:NoExecuteandunreachable:NoExecutebased on the k8s-apiserver input parametersdefault-not-ready-toleration-secondsanddefault-unreachable-toleration-secondsif the pods don't already have toleration for taintsnode.kubernetes.io/not-ready:NoExecuteornode.kubernetes.io/unreachable:NoExecute. The default value [...] is 5 minutes.—
docs/reference/access-authn-authz/admission-controllers.md:234-241
Both flags default to 300 (kube-apiserver.md:465, :472). So the five minutes the post describes
as the default, in contrast to the thing you could now configure, is now written into the spec of
every pod you create as two tolerations you did not ask for. Step 6 reads them back.
The page that replaced the post now contradicts the feature-gate record about its own field.
Pod affinity grew a matchLabelKeys field, and the page announcing it says this:
The
matchLabelKeysfield is a beta-level field and is enabled by default in Kubernetes [current version]. When you want to disable it, you have to disable it explicitly via theMatchLabelKeysInPodAffinityfeature gate.—
docs/concepts/scheduling-eviction/assign-pod-node.md:387-390
The version is a shortcode that renders as whatever release the site is built for, so the note reads
as current no matter how old it is. Three things in it are wrong. The field is not beta-level:
MatchLabelKeysInPodAffinity went stable at v1.33, five releases before the pin. The instruction
cannot be followed: that stage carries locked: true, so the gate cannot be set to false. And the
line immediately above the note, at :386, is a source comment reading
UPDATE THIS WHEN PROMOTING TO STABLE — a reminder the author left in the published file, still
asking for the update that the gate record shows already happened. Two lines above that, the page's
own feature-state shortcode reads the same gate file and prints stable. The page states both
things at once, four lines apart, and the note that says beta is the one written in prose.
Custom schedulers changed most of all, and the change is in what schedulerName means. The post's
model is exclusion: the field's value is a name no built-in component answers to, so the pod stays
Pending until an outside process binds it. The pin's model is selection:
To determine if a scheduler is responsible for scheduling a specific Pod, the
spec.schedulerNamefield in a PodTemplate or Pod manifest must match theschedulerNamefield of theKubeSchedulerProfile.—
docs/tasks/extend-kubernetes/configure-multiple-schedulers.md:83-84
The replacement page also declines the post's actual subject — "A detailed description of how to
implement a scheduler is outside the scope of this document" — and its worked example runs
kube-scheduler itself as the second scheduler, built from a git clone of kubernetes/kubernetes.
So the post ships a scheduler and no configuration story; the page ships a configuration story and
no scheduler.
And the API call the post's scheduler is built on is documented nowhere. pod-v1.md lists around
forty operations on Pod, including one other write-only subresource, Create Eviction
(pod-v1.md:3389), with full request and response schemas. There is no Binding entry, and
kind: Binding has zero occurrences anywhere under docs/. The endpoint still works — step 9 uses
it — but the pin's documentation set contains no description of the resource, no schema for it, and
no mention that it exists.
Two smaller notes on the zone label, whose deprecation
2016's multi-zone exercise
already covers in full. It survives in three files at the pin, and two of them are permission lists
rather than documentation: NodeRestriction's allowed set for kubelet self-labelling
(admission-controllers.md:617-618, both entries carrying the parenthesis (deprecated)) and the
kubelet's --node-labels allowlist (kubelet.md:644). Deprecated since v1.17 and still explicitly
permitted for a node to write about itself.
The diff, and why — the post's four features had one thing in common in 2017 and nothing in common after, and the split runs along whether the feature was a field or a process.
Node affinity, pod affinity and tolerations are fields. Fields accrete: matchFields arrived beside
matchExpressions, namespaceSelector beside namespaces, matchLabelKeys and
mismatchLabelKeys beside labelSelector, Gt and Lt beside the original four operators. Every
line of the post's required-form YAML still applies, unchanged, in v1.37. That is nine years of
compatibility on a beta API, and it is the strongest thing about this post.
Custom schedulers were a process, and processes get replaced rather than extended. The post's answer
to "the scheduler is not enough" was write another one, and it demonstrated that the barrier was
low by writing one in twenty lines of Bash. The project's answer became configure the one you have
— scheduling profiles, then the scheduling framework's plugin points — which is a better answer and
a strictly narrower one. You can now change how scheduling scores without writing a scheduler, and
you can no longer find out from the documentation how to write a scheduler. The Binding resource
is where that shows: it was never removed, because the framework is built on it, but it stopped being
something a reader was expected to reach for, and the reference dropped it while keeping eviction,
its exact structural twin. An undocumented API is not a deprecated API. It is an API whose audience
changed from users to this project's own code, with no record of the change.
Which brings the matchLabelKeys note into focus, because it is the same failure in miniature. Two
sources describe one field: a stages: list, which is data, and a prose note, which is not. The
shortcode reads the data and prints stable. The note beside it says beta and tells you to use a
switch the data marks locked. Nobody was wrong at the time of writing — the note was accurate for
two releases — and nobody is responsible for the moment it stopped being accurate, because the thing
that changed was the file the note does not read. The author saw this coming clearly enough to leave
UPDATE THIS WHEN PROMOTING TO STABLE in the source, and the reminder is now the oldest inaccurate
thing on the page.
The two broken manifests are worth separating from all of that, because they are not decay. The
preferred-affinity block was invalid on the day it was published: PreferredSchedulingTerm had
preference and weight in v1.6 exactly as it does now. The pod affinity block was invalid on the
day it was published too, for the entirely different reason that a word processor changed two
characters. What made both survivable was the word slightly: it invites the reader to read the
second block as the first block with one word changed, which is how a reader gets a scheduling
concept right while copying a manifest that cannot be applied.
2016's StatefulSet exercise
shows a wrong field name surviving nine years because it sat where nothing checked it. Here nothing
checked it because nobody applied it.
The ladder — the gates behind the post's four features and their descendants.
AffinityInAnnotations¶
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.6 – v1.7 |
| deprecated | — | — | v1.8 – v1.8 |
removed: true, plus _build: {list: never, render: false}, so the file exists and the site does
not publish it. This is the gate for the annotation form of pod affinity, and it is the breaking
change the post's closing paragraph gestures at without naming. It entered in v1.6 — the release this
post announces — already off by default, was deprecated for exactly one release, and never
reached beta. The deprecated stage carries no defaultValue, which is the template gap recorded
against 2016's authoring ticket rather than a new one.
TaintBasedEvictions¶
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.6 – v1.12 |
| beta | true |
— | v1.13 – v1.17 |
| stable | true |
— | v1.18 – v1.20 |
removed: true. This is the post's alpha feature, and the dates match the post exactly: introduced
in v1.6, described as alpha, and it stayed alpha for seven releases before beta. Twelve releases from
introduction to stable, three more before the gate was deleted.
TaintNodesByCondition¶
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.8 – v1.11 |
| beta | true |
— | v1.12 – v1.16 |
| stable | true |
— | v1.17 – v1.18 |
removed: true. The other half of the same idea, and it arrived two releases after the post
without being mentioned in it: node conditions expressed as taints rather than read separately by the
scheduler. Note that it reached stable at v1.17, one release before TaintBasedEvictions did, having
started two releases later.
PodAffinityNamespaceSelector¶
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.21 – v1.21 |
| beta | true |
— | v1.22 – v1.23 |
| stable | true |
— | v1.24 – v1.25 |
removed: true. One release alpha, two beta, two stable, then deleted — five releases end to end,
against TaintBasedEvictions' fifteen. The post's pod affinity example has no namespace scoping of
any kind; this gate is what added the choice.
MatchLabelKeysInPodAffinity¶
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.29 – v1.30 |
| beta | true |
— | v1.31 – v1.32 |
| stable | true |
true |
v1.33 – |
Still present, and the only gate in this set with locked: true. This is the record that
assign-pod-node.md:386-390 contradicts.
TaintTolerationComparisonOperators¶
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.35 – |
Enables Lt and Gt for tolerations. Read it against assign-pod-node.md:718, "The following
operators can only be used with nodeAffinity", and against the post's own list, which put those
two operators in a sentence about node affinity with a full stop in the middle of it.
Six gates, and the shape of the set is the point: the two that carry the post's own features are
removed: true after fifteen and eleven releases, the two that extended pod affinity are
removed: true after five and eight, and the two live ones were introduced in v1.29 and v1.35. The
features stopped needing gates. The gates for extending them did not stop being created.
Topology — pair, fresh. Two nodes is the minimum for
this exercise and the maximum it needs: a control plane at 10.10.10.130 and one worker at
10.10.10.131. Node affinity needs two nodes with different labels to prefer between, pod
anti-affinity needs two nodes to spread across, and the control plane arrives with the exact taint
the post gives as its motivating example. Bring both guests up with
the five provision steps, install Kubernetes with
the node baseline procedure, join the worker,
and confirm both nodes are Ready before starting.
Do
- Read the taint the installer already set, which is the post's own example running unprompted:
kubectl get nodes -o custom-columns='NAME:.metadata.name,TAINTS:.spec.taints[*].key'
kubectl get node -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.taints}{"\n"}{end}'
kubectl get nodes --show-labels
kubectl get nodes -o json | grep -c 'failure-domain.beta.kubernetes.io/zone'
Match the taint you found against implementation-details.md:438. Then say which of the post's
three sentences about tolerations explains why the control plane still runs kube-apiserver
despite it, and check the last command's count against
2016's multi-zone exercise.
- Apply the post's first block exactly as printed, with its deprecated label key:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: aff-required }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
EOF
kubectl get pod aff-required
kubectl describe pod aff-required | tail -6
The manifest is accepted and the pod does not schedule. Quote the scheduler's message. Then label the worker with the post's deprecated key and value, and report how long the pod takes to move:
kubectl label node <worker> failure-domain.beta.kubernetes.io/zone=us-central1-a
kubectl get pod aff-required -w # Ctrl-C once it is Running
The label is deprecated and the scheduler honoured it. Read admission-controllers.md:617-618
and say what that list permits, and what it does not tell you about whether anything writes the
label.
- Apply the post's second block exactly as printed:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: aff-preferred }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
EOF
Record the error verbatim. Then write the form the API accepts and apply it:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: aff-preferred }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: "failure-domain.beta.kubernetes.io/zone"
operator: In
values: ["us-central1-a"]
EOF
kubectl get pod aff-preferred -o wide
Now delete the label from the worker and create the same pod again under a second name. It should still schedule. State the difference in outcome between step 2 and this step when no node matches, and count how many edits it took to get from the post's block to this one.
- Test the two operators the post appends to its list:
kubectl label node <worker> blogwalk.example/cores=8
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: aff-gt }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: blogwalk.example/cores
operator: Gt
values: ["4"]
EOF
kubectl get pod aff-gt -o wide
kubectl label node <worker> --overwrite blogwalk.example/cores=eight
kubectl delete pod aff-gt --now
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: aff-gt2 }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: blogwalk.example/cores
operator: Gt
values: ["4"]
EOF
kubectl describe pod aff-gt2 | tail -5
Compare the second outcome against assign-pod-node.md:727-729. Then say which of the post's six
operators the ladder's last gate would move, and which sentence on the pin's page stops being true
when that gate is on.
- The post's taint command, unchanged, and its toleration:
kubectl taint nodes <worker> key=value:NoSchedule
kubectl run t1 --image=registry.k8s.io/pause:3.10 --restart=Never
kubectl get pod t1 -o wide
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: t2 }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
EOF
kubectl get pod t2 -o wide
kubectl taint nodes <worker> key=value:NoSchedule-
Both the taint and the toleration are copied from the post verbatim and both work. Then switch the effect and watch what the post promises:
Say which pods went and which stayed, and use PreferNoSchedule to explain why the post's phrase
"the preferred version of NoSchedule" is the one sentence in this half you should not repeat to
anyone.
- Read back the tolerations you did not write:
kubectl taint nodes <worker> key=value:NoExecute-
kubectl run plain --image=registry.k8s.io/pause:3.10 --restart=Never
kubectl get pod plain -o jsonpath='{.spec.tolerations}{"\n"}' | tr ',' '\n'
Two tolerations with a tolerationSeconds you never asked for. Name the admission controller from
admission-controllers.md:234-241, give the two apiserver flags and their defaults, and then
state the inversion: what the post offered as an alpha feature, in relation to what those
tolerations now are.
- Apply the post's pod affinity block exactly as printed, typographic quotes included. Type them, or copy them from the post — do not straighten them:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pa-verbatim
labels: { service: S2 }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: service
operator: In
values: [“S1”]
topologyKey: failure-domain.beta.kubernetes.io/zone
EOF
Record the message. Then decide, from labels.md:79-81, whether the value was rejected as a label
value or never reached that check, and say which of the post's four YAML blocks are affected by
this and which are not.
- Build the anti-affinity case the post describes in prose, across the pair, and then meet the field the pin's page misdescribes:
kubectl taint nodes <control-plane> node-role.kubernetes.io/control-plane:NoSchedule-
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: { name: spread }
spec:
replicas: 3
selector: { matchLabels: { service: S1 } }
template:
metadata: { labels: { service: S1 } }
spec:
containers: [{ name: p, image: registry.k8s.io/pause:3.10 }]
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: service
operator: In
values: ["S1"]
topologyKey: kubernetes.io/hostname
EOF
kubectl get pods -l service=S1 -o wide
Two nodes, three replicas, a hard rule per host. Say what the third pod is doing and why untainting
the control plane was necessary for the first two. Then read
assign-pod-node.md:381-390 and attempt what it tells you to do:
grep -o "enable-admission-plugins=[^ ]*" /etc/kubernetes/manifests/kube-apiserver.yaml
grep -o "feature-gates=[^ ]*" /etc/kubernetes/manifests/kube-scheduler.yaml
# then, on the control plane, add to kube-scheduler.yaml's command:
# --feature-gates=MatchLabelKeysInPodAffinity=false
sudo crictl ps | grep scheduler
sudo crictl logs $(sudo crictl ps -a --name kube-scheduler -q | head -1) 2>&1 | tail -5
Report what the scheduler said. Then list the three things assign-pod-node.md:386-390 states
that the gate file does not support, and undo the edit.
- The post's custom scheduler, exactly as designed. In one shell:
In another, create the post's pod and confirm nothing takes it:
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata: { name: nginx, labels: { app: nginx } }
spec:
schedulerName: my-scheduler
containers: [{ name: nginx, image: nginx:1.10 }]
EOF
kubectl get pod nginx
kubectl describe pod nginx | tail -4
Note that describe shows no scheduler events at all — not a failure, an absence. Then bind it by
hand with the post's own call, one pod rather than a loop:
CHOSEN=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
curl -sS --header "Content-Type:application/json" --request POST \
--data '{"apiVersion":"v1","kind":"Binding","metadata":{"name":"nginx"},"target":{"apiVersion":"v1","kind":"Node","name":"'$CHOSEN'"}}' \
http://localhost:8001/api/v1/namespaces/default/pods/nginx/binding/
kubectl get pod nginx -o wide
It works. Now find the resource you just used in the pin's reference: search pod-v1.md's
Operations section for Binding, and search the whole docs/ tree for kind: Binding. Report
both counts, then find Create Eviction in the same list and say what distinguishes the two
subresources apart from one being documented.
- Close on what replaced the post's last section, without leaving the reference:
read
configure-multiple-schedulers.mdand the scheduler config API. Answer three questions. First: in the post's model, what does a pod'sschedulerNametell you about which component will schedule it, and in the pin's model, what does it tell you? Second: the pin's page useskube-scheduleritself as the second scheduler and declines to describe writing one — say what a reader who needs the post's section would do today. Third: your podnginxis bound and running underschedulerName: my-schedulerwith no such profile configured. Say whether that pod is now indistinguishable from a normally-scheduled pod, and how you would check.
Expect
kubectl get pods -o wide
kubectl get pod plain -o jsonpath='{range .spec.tolerations[*]}{.key}={.tolerationSeconds}{"\n"}{end}'
kubectl get pod nginx -o jsonpath='{.spec.schedulerName}|{.spec.nodeName}{"\n"}'
kubectl get nodes -o custom-columns='NAME:.metadata.name,TAINTS:.spec.taints[*].key'
A pod that scheduled on a deprecated label, a preferred-affinity pod that took three corrections to
apply, at least one Pending pod from the anti-affinity Deployment, two tolerations on a pod whose
manifest had none, and one pod bound by curl to a scheduler that does not exist.
By the end you should be able to say what weight does that the required form has no place for, name
the component that writes a five-minute toleration into every pod, give the two counts that show the
Binding resource is undocumented at the pin, and state the three claims
assign-pod-node.md:386-390 makes that its own gate file refutes.
Read on — the pin's scheduling framework page describes the extension points that replaced the post's approach. Read it and answer: at which of its points would the post's random-node scheduler have been a plugin, and does the page anywhere tell you that the mechanism the post used is still supported?
Teardown — kubectl delete deploy spread; kubectl delete pod --all; kubectl label node <worker>
failure-domain.beta.kubernetes.io/zone- blogwalk.example/cores- — then re-taint the control plane if
you plan to keep the cluster, and note why leaving it untainted is a change to the cluster the
installer built. Then the teardown step.