Lo Scheduler decide a che Nodo assegnare i Pod, in funzione delle risorse richieste, risorse disponibili ecc. con l'aiuto di una funzione che permette di scegliere il Nodo compatibile migliore.
Nella definizione yaml di un Pod è presente il campo nodeName
. Questo non viene inserito dall'utente ma da Kubernetes, è indica il nodo in cui è deployato il Pod. Lo Scheduler cerca i Pod che non hanno questa proprietà impostata e li assegna ad un nodo.
Uno Pod che non ha un nodo assegnato rimane nello stato Pending. È possibile assegnare manualmente il Pod ad un nodo semplicemente inserendo nodeName
manualmente nella specifica yaml. Questa operazione può essere esegiota solo durante la creazione del Pod.
# pod-definition.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 8080
nodeName: node02
Se il Pod è già stsato creato, per assegnarlo ad un nodo è necessario utilizzare un Binding per richiedere al cluster di assegnare il Pod ad un certo nodo:
apiVersion: v1
kind: Binding
metadata:
name: nginx
target:
apiVersion: v1
kind: Node
name: node02
La definizione yaml va trasformata in JSON e inviata tramite curl all'API di Kubernetes:
curl --header "Content-Type: application/json" --request POST --data '{"apiVersion":"v1", "kind":"Binding", ...}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding
È possibile assegnare qualsiasi label agli oggetti di Kubernetes, in modo da porteli categorizzare, filtrare e selezionare tramite i selector successivamente. I labels vanno inseriti nel tag metadata
dell'oggetto.
Per selezionare degli oggetti in funzione dei labels
:
kubectl get pods --selector app=my-app
Per contare velocemente tutti gli oggetti con un certo label:
kubectl get pods -l app=my-app --no-headers | wc -l
Un Service o ReplicaSet utilizza i campi selector
e matchLabels
per selezionare i Pod che deve gestire. Il Service o ReplicaSet prenderà in carico i Pod che contengono tutti i labels
presenti in matchLabels
. I Pod, viceversa, possono avere anche più labels
diversi, ma per essere presi in carico devono avere almeno i labels
che il servizio sta cercando.
Le annotations
sono altri metadati che vengono usati solo a scopo informativo per inserire alcune informazioni nella definizione dell'oggetto. Molte volte vengono create da Kubernetes durante l'esecuzione.
Sono delle regole che permettono di limitare il deploy di Pod in alcuni nodi. Ad un certo nodo viene applicata una taint
(un "repellente"). Di default tutti i Pod sono incapacitati dal repellente, per cui nessun Pod verrà deployato nel nodo dallo Scheduler. Alcuni Pod possono però essere abilitati ad essere deployati sul nodo aggiungendo ai Pod voluti una toleration
, cioè il Pod diventa tollerante al repellente.
Nota: questo sistema NON assicura che un certo Pod verrà deployato su un nodo a cui il Pod è tollerante. Il repellente assicura però che il nodo non avrà Pod in esecuzione che non sono tolleranti al suo repellente. Se ci si vuole assicurare che un Pod venga eseguito in un certo nodo, bisogna invece utilizzare la funzionalità di Affinity.
Anche il Master Node del cluster potrebbe ricevere dei Pod, ma è tainted da Kubernetes quando il cluster viene creato. È possibile applicare un repellente ai Pod in modo che questi vengano deployati anche nel nodo master, ma è buona norma non farlo. Per conoscere il Taint del master:
kubectl describe node kubemaster | grep Taint
Per applicare un taint
ad un nodo:
kubectl taint nodes <nome_nodo> key=value:taint-effect
Per rimuovere un taint
aggiungere un -
alla fine:
kubectl taint nodes <nome_nodo> key=value:taint-effect-
I taint-effect
hanno dei valori ben definiti:
NoSchedule
: il Pod non verrà deployato nel nodo.PreferNoSchedule
: lo Scheduler cercherà di non deployare il Pod sul nodo.NoExecute
: il Pod non verrà deployato nel nodo, ma anche i Pod già presenti verranno fermati e rimossi dal nodo.
un esempio può essere:
kubectl taint nodes node1 app=blue:NoSchedule
Le tolerations
vengono applicate nelle specifiche di un Pod:
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: nginx-container
image: nginx
tolerations:
- key: "app"
operator: "Equal"
value: "blue"
effect: "NoSchedule"
Nota: tutti i valori definiti per la toleration
devono essere stringhe tra doppi apici.
I valori possibili per il campo operator
sono:
- Equal: se la
key
ha un valorevalue
. - Exists: se la
key
esiste. Non deve essere specificato alcunvalue
.
Pod che hanno un grosso carico computazionale potrebbero essere assegnati a nodi con poche risorse, e viceversa. Con il NodeSelector è possibile schedulare un Pod su un ben preciso nodo in funzione dei suoi label:
# pod-specification.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: data-processor
image: data-processor
nodeSelector:
size: Large # deve essere un label assegnato al nodo
Per applicare un label
ad un nodo:
kubectl label nodes <node-name> <label-key>=<label-value>
NodeSelector è molto limitato in quanto può selezionare solo nodi con un certo label, non può selezionare combinazioni di labels o escluderne alcune. Per questo è necessario usare Node Affinity.
Node Affinity permette di creare regole di selezione molto più complesse per selezionare i nodi in cui schedulare i Pod.
Per schedulare un Pod in un nodo che combacia con più valori possibili del label
, ad esempio:
# pod-definition.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: data-processor
image: data-processor
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In
values: # il Pod viene deployato in un nodo con uno dei due valori
- Large
- Medium
I valori possibili per gli operatori sono:
- In
- NotIn
- Exists
- DoesNotExist
I tipi di Affinity sono:
requiredDuringSchedulingIgnoredDuringExecution
: il Pod deve essere schedulato su un nodo che rispetta le regole di affinità.preferredDuringSchedulingIgnoredDuringExecution
: lo scheduler cercherà di rispettare le regole di affinità ma non è garantito.
dove:
- DuringScheduling: si intende quando il Pod deve essere creato e schedulato in un nodo.
- DuringExecution: quando un Pod è già in esecuzione in un nodo.
- required: se non c'è un nodo disponibile, il Pod rimane nello stato Pending. Viene utilizzato quando la selezione del nodo è cruciale.
- preferred: se non c'è un nodo disponibile, viene schedulato su un altro nodo a caso.
Al momento tutte le regole di affinità ignorano i Pod in esecuzione (IgnoredDuringExecution). Questo significa che se viene rimosso un label dal nodo, un Pod che aveva affinità col nodo ma non ce l'ha più continua comunque a funzionare.
Una combinazione corretta di Taints e Affinity permette di schedulare un Pod esattamente nel nodo che ci interessa, evitando al contempo che altri Pod interferiscano.
- Applicare al nodo una
taint
corrispondente allatoleration
del Pod. Questo assicura che altri Pod non possano essere deployati su quel nodo. Ma il Pod interessato potrebbe essere schedulato in un altro nodo senza alcuntaint
. - Applicare un
affinity
al Pod corrispondente ailabels
del nodo. In questo modo lo scheduler dovrà deployare il Pod nel nodo che ha affinità con esso. - Con questa combinazione, il Pod verrà sempre deployato nel nodo voluto, ed altri Pod estranei rimarranno sicuramente esclusi dal nodo.
Le risorse di un nodo sono CPU, Memoria e Disco. Lo Scheduler si occupa di deployare i Pod in nodi che abbiano abbastanza risorse disponibili per eseguirli. Se nessun nodo è libero, il Pod rimarra in Pending. Controllando gli eventi di un Pod è possibile verificare con precisione quale risorsa non è sufficiente. Per fare ciò:
kubectl describe pod <nome_pod> | grep "Events"
Di default, Kubernetes non assegna alcun requisito di risorse di default. Per specificare dei requisiti di risorse in un Pod, si utilizza il campo resources
:
# pod-definition.yaml
apiVersion: v1
kind: Pod
metadata:
# ...
spec:
containers:
- name: my-app
# ...
resources:
requests:
memory: "1Gi"
cpu: 1
- CPU: 1 CPU corrisponde ad un Hyperthread, oppure una vCPU (simile ad un core). La definizione varia in base al cloud provider utilizzato. Il valore minimo applicabile è
1m
(100m
corrispondono a 0.1 CPU). - Memory: si specifica in bytes e suoi multipli. I multipli nel sistema internazionale (Kilobyte, Megabyte, Gigabyte) sono abbreviati con
K
,M
,G
, mentre i multipli in potenza di due (Kibibyte, Mebibyte, Gibibyte ) sono abbreviati conKi
,Mi
,Gi
.
Il consumo di risorse può aumentare o diminuire durante l'esecuzione del Pod. Se il consumo aumenta troppo, potrebbe soffocare gli altri Pod ed anche i processi interni al nodo. Per questo motivo è necessario limitare le risorse utilizzabili da un Pod. Di default Kubernetes non applica alcun limite alle risorse. È possibile e consigliato quindi specificare il limite massimo di risorse utilizzabili da un Pod nel suo yaml:
# pod-definition.yaml
apiVersion: v1
kind: Pod
metadata:
# ...
spec:
containers:
- name: my-app
# ...
resources:
limits:
memory: "2Gi"
cpu: 2
Se un Pod cerca di utilizzare più CPU del previsto, la CPU va in throttling ma il Pod rimane in esecuzione. Se invece il Pod utilizza più memoria del previsto per un certo lasso di tempo, verrà terminato.
I limiti sono assegnati al Pod, per cui nel caso di Pod con molteplici container al suo interno, questi container dovranno condividere le risorse e i suoi limiti.
È possibile impostare un valore di default per limits
e requests
utilizzando LimitRange.
# mem-limit-range.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
defaultRequest:
memory: 256Mi
type: Container
# cpu-limit-range.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-limit-range
spec:
limits:
- default:
cpu: 512Mi
defaultRequest:
cpu: 256Mi
type: Container
Un DeamonSets funziona come un ReplicaSet, ma replica il Pod in ogni singolo nodo presente nel cluster, anche quando altri nodi vengono aggiunti successivamente.
È utile per avere in ogni singolo nodo dei Pod di utility come un monitor o un logger o log viewer. Anche il kube-proxy
è un DeamonSets, perchè è necessario in ogni singolo nodo.
Le specifiche yaml sono praticamente uguali ad un ReplicaSet:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: monitoring-daemon
labels:
app: nginx
spec:
selector:
matchLabels:
app: monitoring-agent
template:
metadata:
labels:
app: monitoring-agent
spec:
containers:
- name: monitoring-agent
image: monitoring-agent
Non è possibile creare un Daemon Set con kubectl create
, ma è possibile comunque sfruttare i comandi da terminale con un piccolo trucchetto:
- Creare un Deployment (
kubectl create deployment <nome_deployment> --dry-run=client -o yaml > ds.yaml
) - Rimuovere i campi
replicas
,status
,strategy
e sostituire akind: DaemonSet
. - Eseguire con
kubectl apply -f ds.yaml
Ogni singolo nodo esegue al suo interno kubelet
. Il servizio non può operare tutte le funzionalità di un cluster, la sua funzione è di creare e rimuovere i Pod.
Un singolo nodo può pertanto gestire Pod anche senza essere connesso ad un cluster. Questi Pod, gestiti autonomamente dal nodo, vengono detti Pod statici.
Il nodo controlla periodicamente la cartella etc/kubernetes/manifests
, ed esegue tutte le specifiche yaml presenti al suo interno. Se un file viene cancellato, anche il Pod viene rimosso. Se un file viene modificato, il Pod viene ricreato.
Nota: kubelet
può gestire solo e unicamente i Pod, per cui tutti i manifesti yaml devono essere del kind
Pod.
Il nodo può gestire contemporaneamente Pod statici e Pod del cluster. I Pod statici saranno visibili anche dal kube-apiserver
e quindi visibili utilizzando kubectl get pods
, ma saranno in sola lettura e non potranno essere modificati.
I Pod Statici sono utili per creare nuovi nodi Master, ed è la stessa strategia che usa kubeadm
. Per creare un nuovo master, basta installare kubelet
nella nuova istanza e inserire nella cartella dei manifest le specifiche yaml di tutti i componenti principali di Kubernetes, come etcd
, kube-apiserver
, controller-manager
. Questi verranno istanziati come Pod nel kube-system
.
All'esecuzione di kubectl
è possibile passare un parametro per modificare la cartella di default contentente i file yaml:
# kubelet.service
/usr/local/bin/kubelet --pod-manifest-path=/etc/kubernetes/manifests
I file del servizio sono posizionati in /lib/systemd/system/
.
In alternativa, è possibile indicare un file di configurazione esterno:
# kubelet.service
/usr/local/bin/kubelet --config=kubeconfig.yaml
# kubeconfig.yaml
staticPodPath: /etc/kubernetes/manifests
Questo approccio è quello usato da kubeadm
durante il setup del cluster. Per trovare la posizione del file su un nodo in cui kubelet
è già in esecuzione:
ps aux | grep kubelet
Nell'output che comprare, trovare l'opzione config=/path/to/config.yaml
che indicherà la posizione del file di configurazione.
È possibile creare e deployare degli Scheduler personalizzati. Diversi Scheduler possono coesistere contemporaneamente nel cluster, ed è possibile impostare per ogni Pod a quale Scheduler deve essere affidato.
Per vedere gli Scheduler attivi:
kubectl get pods --namespace=kube-system
Il deploy di un nuovo Scheduler viene effettuato come con kube-scheduler
.
ExecStart=/usr/local/bin/kube-scheduler
--config=/etc/kubernetes/config/kube-scheduler.yaml
--scheduler-name=my-custom-scheduler # nome dello scheduler
Per personalizzare il nuovo Scheduler, è possibile modificare il suo file yaml e deployarlo:
apiVersion: vl
kind: Pod
metadata:
name: my-custom-scheduler
namespace: kube-system
spec:
containers:
command:
- kube-scheduler
- --address=127.0.0.1
- --kubeconfiq=/etc/kubernetes/scheduler.conf
- --leader-elect=true
- --scheduler-name=my-custom-scheduler
- --lock-object-name=my-custom-scheduler
image: k8s.gcr.io/kube-scheduler-amd64:v1.11
name: kube-scheduler
Il file esegue i comandi definiti nella proprietà command
, in particolare:
leader-elect
: necessario quando copie multiple dello scheduler sono in esecuzione in diversi nodi master. Solo una delle copie può funzionare alla volta. Se ci fossero problemi al Pod leader, un altra copia prenderebbe il suo posto. Questa opzione è necessaria quando si hanno più nodi master, mentre può rimanere a false quando il master è solo uno.lock-object-name
: necessario quandoleader-elect
ètrue
e sono presenti più nodi master. Permette di indicare durante l'elezione del leader che il nuovo Scheduler è diverso da quello di default e da eventuali altri Scheduler.scheduler-name
: nome del nuovo scheduler
Per indicare ad un Pod di utilizzare il nuovo scheduler:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
schedulerName: my-custom-scheduler
Nota: Se lo Scheduler non è configurato correttamente, il Pod rimarrà nello status di Pending.
Per verificare il corretto funzionamento dello Scheduler è possibile visionare gli Eventi del cluster e i logs dello Scheduler:
Per mostrare tutti gli eventi avvenuti nel Namespace corrente:
kubectl get events
Per vedere i logs dello Scheduler:
kubectl logs my-custom-scheduler -n kube-system