Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Kubernetes è un prodotto di Google frutto dell’evoluzione del cloud computing e delle necessità che con il passare del
tempo sono state soddisfatte con soluzioni ingegnose ed efficienti.
Inizialmente avevamo macchine fisiche su cui giravano sistemi operativi pesanti con applicativi monoblocco, una
soluzione che aveva molti svantaggi tra cui
- stop per manutenzioni a vari livelli dello stack (hardware, sistema operativo, componenti software proprietarie
e applicativo finale), nonchè alti costi di gestione
- problemi classici di Reliability/Avaiability applicazioni
- gestione delle risorse hardware (cpu, ram...) non ottimizzata e condivisa tra più applicativi, continuo pericolo
che le performance di un componente applicativo potessero influenzare negativamente anche gli altri (lock
risorse condivise etc)
- problemi di scalabilità del sistema
L’avvento delle virtual machines o vm fu il primo passo per risolvere alcuni di questi problemi:
- più sicurezza: avendo più macchine virtuali su una macchina fisica riusciamo a gestire meglio gli accessi al
sistema
- migliore utilizzo risorse: con la virtualizzazione possiamo partizionare le risorse hardware in modo razionale
- maggiore isolamento applicativo: facendo girare applicativi su macchine diverse è meno probabile che le
performance di un applicativo influissero negativamente su quelle di un’altro
- scalabilità e replicabilità: l’immagine di una vm è facilmente replicabile e trasportabile su nuovo hardware
all’occorrenza
- razionalizzazione e riduzione dei costi conseguente
restavano comunque problemi irrisolti come il fatto che ogni vm ha un suo sistema operativo, bisogna pagare le licenze
per ogni server, bisogna gestire la manutenzione e upgrade di ogni vm.
Un miglioramento lo portarono i containers: simili alle vm hanno il vantaggio di condividere il sistema operativo tra
applicazioni: sono leggeri, il sistema operativo è ridotto all’osso, sono indicati per applicazioni cloud-native, sono
scalabili dinamicamente molto più delle virtual machine, garantiscono disaccoppiamento tramite uso api, in ognuno di
essi possiamo definire piccoli microservizi che possono interagire tra loro per erogare un servizio più complesso, sono
portabili, possono girare quasi dovunque, supportano il DevOps deployment e l’Agile Deployment
Tutto l’occorrente per far girare un’applicazione (dipendenze, librerie, codice, tools...) viene impacchettato in una
“container image”, una template immutabile, in sola lettura, che comprende anche il virtual operating enviroment. Se
abbiamo bisogno di una istanza operativa eseguiamo la container image aggiungendo gli argomenti di runtime necessari
a ottenere la modalità di funzionamento ideale e otteniamo un container, ossia un’immagine in esecuzione con un
writable layer sotto (ram, disco etc). Le immagini possono essere versionate e pubblicate su repository pubbliche o
private, Docker è tra le applicazioni/servizi più conosciuti
- build fidelity: è garantita la consistenza della build e la fedeltà rispetto all’immagine iniziale, il modo in cui viene
prodotta una immagine è deterministico e altamente riproducibile e l’immagine gira allo stesso modo ogni volta
- portability: i container possono essere portati e hostati su diverse piattaforme, public clouds, private clouds, on-
premises. A prescindere da dove è deployato il container tutto resta consistente. E’ dunque fatto per essere scalabile
e cloud-native.
- resource segregation: utilizza in modo efficiente le risorse e isola le applicazioni tra di loro. Sono flessibili, è facile fare
rollback, automatizzazione, tutte cose importanti per il mondo devops
Kubernates è di fatto un container orchestrator: permette di gestire in modo dinamico ed efficiente i vantaggi offerti dal
meccanismo dei container (è integrato con docker ma è compatibile con qualsiasi tipo di container che risponda alle
specifiche CRI (container runtime interfaces) su un cluster di macchine fisiche permettendoci di partizionare il cluster in
più cloud (cluster logici) gestire in modo altamente dinamico le risorse (cpu, ram..) e le istanze in esecuzione,
monitorare, bilanciare, scalare in modo efficace e automatizzato i sistemi, gestire in modo sicuro password, chiavi ssh,
oauth tokens senza dover riconfigurare (stop/start) i containers attivi.
viene mantenuta la struttura chiave-valore come json, i due punti e uno spazio separano la chiave dal valore (scalars,
valori letterali come numeri, boolean e stringhe) e non vanno usate le virgolette a meno che non sia necessario a evitare
errori di sintassi quando ci troviamo in presenza di caratteri riservati che sono apice, doppio apice (che vanno escapati)
[] {} > | * & ! % # ` @ , e i due punti
l’indentazione, l’uso cioè di un determinato numero di spazi su ciascuna, permette di definire un oggetto (structure)
complesso di uno o più oggetti senza ricorrere alle graffe che comunque possono essere usate
con l’uso del trattino prima della definizione di una chiave identifichiamo l’inizio di un elemento di un array (sequences)
che può essere una normale coppia chiave-valore o un oggetto più complesso, è possibile ricorrere anche alle parentesi
quadre
con l’uso del triplo trattino o del triplo punto possiamo concatenare più documenti yaml in un unico file
se lo scalare inizia per pipe (“|”) tutto quello che segue compreso accapo e spazio verranno considerati parte del valore
se lo scalare inizia per “>” tutto quello che segue (spazi e accapo compresi) viene considerato parte del valore (come il
pipe) ma spazi e accapo verranno ignorati (come accade in html)
Se con json possiamo esprimere tutto su una riga con yaml non si può (l’accapo fa parte della sintassi), ma è possibile
passare da yamle a json molto facilmente
- docker desktop (possiamo abilitare la hyper-v di windows per la virtualizzazione o usare virtualbox o equivalenti
per la gestione delle macchine virtuali), tra le impostazioni di docker desktop abilitare kubernetes per avere
client e server standalone integrati a docker (pro, versione minimale e leggera che va bene per i test in locale,
contro, kubernetes server non è configurabile, ha un cluster con un solo nodo). A questo punto sulla power shell
di windows abbiamo a disposizione il comando “kubectl” per controllare kubernetes .
- minikube utilizzando un container runtime come docker o un vm enviroment come hyper-v nativo di windows o
tipo virtualbox, il metodo più rapido è abilitare hyper-v tra le features di windows e installare minikube con
l’installer o una alternativa proposte sul sito. Dopo l’installazione seguiamo le istruzioni per
installare/configurare kubectl oppure andiamo nella sezione documentazione del sito, sezione “install kubectl
binary with curl on windows”, troviamo un modo per installare su minikube l’ultima versione dell’eseguibile di
kubectl, che ci servirà per controllare kubernetes. Il nostro primo cluster parte a riga di comando con “minikube
start” e successivamente usiamo “minikube kubectl” o “kubectl” se abbiamo impostato un alias o abbiamo
installato una versione di kubectl apposita.
- docker (docker-ce, docker-ce-cli, containerd.io) con apt-get dopo aver installato le dipendenze necessarie, la
docker gpg key e aver aggiunto la repository docker alla lista di repo valide per le installazioni e gli update dei
pacchetti software
- kubernetes (kubelet, kubectl, kubeadmin) con apt-get dopo aver disattivato la swap di sistema, aver installato le
dipendenze necessarie, la gpg key google, aver aggiunto la repository kubernetes alla lista di repo valide per le
installazioni e gli update dei pacchetti software e aver installato l’addon per comunicare con i pod (weave-net)
Due approcci per usare kubernetes: con comandi/configurazioni imperativi (istruzioni che generano singoli task sul
sistema) o con il metodo dichiarativo (set di files che dicono a kubernetes lo stato desiderato che il sistema deve
raggiungere), nello specifico
Approccio imperativo:
Approccio dichiarativo: va (vanno) preparato i files manifest yaml con le specifiche desiderate per il sistema e
successivamente
- kubectl apply –f <path> per applicare/aggiornare gli oggetti di Kubernetes a partire dai manifesti yaml (possibile
indicare una cartella piuttosto che un file)
- kubectl diff –f <path> per confrontare (pre/post apply) l’attuale configurazione con i manifesti yaml che descrivono
gli oggetti
- kubectl get –f <path> –o yaml per verificare (pre/post apply) l’attuale configurazione con i manifesti yaml che
descrivono gli oggetti in formato yaml
kubectl api-resources per ottenere l’elenco delle api e gli oggetti conosciuti e l’eventuale sigla utilizzabile quando si
lanciano i comandi di cui sopra.
kubectl describe seguito dal tipo di oggetto torna le info dettagliate relative al tipo di oggetto che ci interessa
kubectl get seguito dal tipo di oggetto torna le info relative al tipo di oggetto che ci interessa
kubectl rollout status per conoscere lo stato di rollout di un deployment (ad esempio selezionandolo per nome)
kubectl exec <nome pod> –n <nome namespace> –it -- /bin/sh ottiene una shell (login) dentro a un pod specifico di un
namespace spec.
Oggetti di kubernetes
Gli oggetti di base di Kubernetes includono:
Namespace
Pod
Service
Volume
Namespace
Definisce una partizione logica sul cluster fisico. Non possono essere annidati, sono utili nel caso in cui abbiamo molti
utenti suddivisi per team/progetto Quando Kubernetes si avvia sono inizialmente presenti 4 namespaces:
POD
Sono la più piccola unità di scheduling di kubernetes, ci finiscono dentro una o più app containerizzate. Lo use case più
comune è 1 pod=1 container ma nel caso di app accoppiate che ad esempio hanno bisogno di condividere risorse o
usare risorse di storage e rete come un unica unità i pods possono wrappare più container. In questo caso i container
sono co-allocati e co-schedulati.
- set di volumi di storage, tutti i container dentro quel pod possono accedervi e quindi condividere dati, la
presenza dei volumi permette la sopravvivenza dei dati persistenti, anche nel caso in cui i containers in un pod
devono essere riavviati
- networking: ciascun pod ottiene un unico ip, ciascun container di un pod condivide con gli altri il nw namespace
compreso l’indirizzo ip e la porta, i container dentro lo stesso pod usano localhost per comunicare tra di loro,
oppure i canali standard di inter-process comunication. Container su pod diversi non possono usare il
meccanismo di inter-process comunication (a meno che non sia fatta un’apposita configurazione) ma usano l’ip
networking per comunicare con le entità esterne (comunicazione fatta tramite apposita configurazione???)
- privilege mode: i container in un pod possono girare in modalità privilegiata intervenendo sull’apposito flag nel
security context, è importante quando i container hanno bisogno di fare operazioni di amministrazione di
sistema come ad esempio accedere a risorse hardware, operazioni che richiedono particolari privilegi.
Normalmente i pod sono gestiti da un control plane, ad eccezione di quelli statici gestiti esternamente all’api server,
usati per eseguire un control plane self-hosted usando il demone kubelet su un nodo specifico che ci permette di gestire
i componenti del control plane. I pod statici vengono instanziati da una kubelet su un nodo specifico, possiamo dunque
affermare che kubelet si occupa di supervisionare il pod statico direttamente ed è capace di far ripartire il pod se va in
errore.
Services
E’ una astrazione che definisce (spesso questo pattern è chiamato micro-service)
- un set di pods
- delle policy policy di accesso a questi
Il set di pods puntati da un servizio è solitamente determinato da un selector sebbene sia possibile creare Service
endpoints senza l’uso di selectors
Per esempio se abbiamo bisogno di un backend con tre repliche per processare una immagine in modo stateless e non
importa al frontend quale delle repliche processi l’immagine. Il numero di pods di backend potrebbero anche cambiare
di numero senza che il il frontend se ne accorga: il service fa questo lavoro di disaccoppiamento.
Se le nostre applicazioni sono capaci di usare le API di kubernetes e il servizio di service discovery è possibile effettuare
delle query all’api-server per ottenere gli endpoint, che non cambiano anche se il numero di pod dietro al servizio
cambia. Se invece abbiamo applicazioni non-native, kubernetes mette a disposizione diverse alternative per accedere ai
pod di backend, come ad esempio una porta di rete o un load balancer etc
Tipi di servizio
- cluster ip: tipo di servizio di default, espone il servizio su un ip interno al cluster, questo servizio è raggiungibile
solo internamente al cluster
- nodePort: espone il servizio su una porta statica sull’ip di ciascun nodo, il nodePort service effettua il routing
delle richieste al servizio di ip del cluster che è creato automaticamente. Il nodePort service è raggiungibile
dall’esterno del cluster usando la combinazione di IP e nodePort
- load Balancer: espone esternamente il servizio usando il load balancer del cloud provider; nodeport e cluster ip
sono creati automaticamente e il loadBalancer esterno redirige le richieste a questi servizi
- external Name: il servizio viene mappato su un valore specificato nel name field esterno, ritornando un CNAME
record e il suo valore; non c’è nessun tipo di proxy configurato, puoi anche usare “ingress” per esporre un
service anche se non è un vero service type
Ogni volta che viene definito un service in un namespace viene creata una entry nel dns come segue
nota2: se tra le spec del container diamo un nome alla porta di ascolto, possiamo usare il medesimo nome nella
definizione del service
apiVersion: v1 apiVersion: v1
kind: Pod kind: Service
... ...
spec: spec:
containers: ...
... ports:
ports: - name: name-of-service-port
- containerPort: 80 protocol: TCP
name: http-web-svc port: 80
targetPort: http-web-svc
LimitRange
Serve a impostare limiti minimi e massimi di una risorsa per un namespace
Replicaset
Con replicasets kubernetes ci garantisce il numero che vogliamo di pods identici disponibili per i deployments, i
deployments usano i replicasets per l’autodiagnosi e la scalabilità, i deployments wrappano i replicasets, normalmente
si agisce sui deployments e mai sui replicasets
I replicasets vengono definiti con i yaml files come tutti gli altri oggetti
Daemonsets
I daemonset sono i controller che verificano che i pods stiano girando su tutti i nodi, ad esempio il pod di logging.
Quando aggiungiamo nodi al cluster il daemonset fa in modo che i pod replica stiano girando su tutti i nodi aggiunti,
quando li sganciamo dal cluster il daemonset si preoccupa della garbage collection dei pod. Se eliminiamo un
daemonset i pod creati dal daemonset vengono rimossi.
Scenario tipico:
normalmente abbiamo un daemonset per ogni tipologia di demone menzionata prima, configurazioni più complesse
prevedono più daemonsets per un tipo di demone ma con flag differenti
Deployment
Il Deployment è usato per aggiornamenti dichiarativi su pods e replicasets, il deployment può essere usato per definire
nuovi replicasets o per rimuovere deployments esistenti e acquisire le risorse che stavano usando. E’ possibille mettere
in pausa un deployment in corso in modo da poter aggiornare le configurazioni senza dover rifare tutto il rollout
Nodes
Sono i nodi (fisici) su cui si basa il cluster di kubernetes
è possibile rendere un nodo unschedulable, ossia evitare che vengano creati nuovi pod, con il comando kubectl cordon
$NODENAME, il comando può essere preceduto da kubectl drain <node name> per deallocare tutti i pod presenti su
quel nodo. A questo punto il nodo può essere tolto dal cluster, altrimenti, dopo l’opportuna manutenzione, con kubectl
uncordon $NODENAME è possibile dire a kubernetes di ricominciare ad allocare pods sul nodo.
Con il comando kubectl describe node <insert-node-name-here> è possibile ottenere le informazioni relative allo stato
di un nodo:
- aggiornamenti dello .status di un nodo, l’intervallo di default è 5’ che è molto più lungo del timeout di default di
40” per nodi non raggiungibili
- oggetti lease dentro il namespace kube-node-lease: questi oggetti sono più leggeri pertanto è consigliabile
questo approccio nei cluster molto grandi; i lease object vengono aggiornati ogni 10” di default; se
l’aggiornamento fallisce k. riprova con un ritardo esponenziale che parte da 200msec e arriva ai 7”
NODE Controller è un control plane di k. che gestisce diversi aspetti relativi ai nodi:
- assegnare il blocco CIDR quando il nodo viene registrato (se il CIDR assignment è attivo)
- mantenere la lista dei controller interni sui nodi aggiornata con la lista delle macchine disponibili del cloud
- mantenere aggiornato lo stato del nodo, aggiornando il valore Ready (true/unknown) dello stato del nodo in
base alla raggiungibilità del nodo; il controllo per ogni nodo avviene ogni 5”, valore che può essere modificato
intervenendo con l’opzione --node-monitor-period nel kube controller manager
- chiamare l’api initiated eviction se il nodo per tutti i pod del nodo non raggiungibile, di solito aspetta 5’ prima di
chiamare l’api se il nodo non è raggiungibile
Elenco comandi lanciati durante demo/laboratori e rispettivo significato
exec nginx-pod –n demo –it -- /bin/sh esegue un comando sul pod nginx-pod del namespace
demo (terminale interattivo)
label node k8s-worker3 zone=west aggiunge la label al nodo k8s-worker3 zone=west
labeled --list no k8s-worker1 visualizza tutte le label del nodo k8s-worker1
expose deploy nginx --port=80 espone il deploy con nome nginx nel default namespaces
sulla porta 80 nel cluster
taint node k8s-master1 rimuove (segno – alla fine)
node-role.kubernetes.io/master:NoSchedule- il taint node-role.kubernetes.io/master:NoSchedule
dal nodo k8s-master1
taint node k8s-worker3 memory=large:NoSchedule aggiunge il taint memory=large al nodo k8s-worker3, tutti i
nuovi pod che non hanno la toleration compatibile (=non
rispettano tale taint) non verranno schedulati su tale nodo
taint node k8s-worker3 diskType=ssd:NoExecute aggiunge il taint diskType=ssd al nodo k8s-worker3, il
NoExecute impone che vengano rimossi dal nodo tutti i
pod che non hanno toleration (non rispettano) con tale
taint
DOCKER
(sudo) docker ps elenco processi docker che stanno girando sulla macchina
(sudo) docker stats –all –format “table {{.Container}}\ torna le statistiche di utilizzo risorse di un determinato
t{{.CPUPerc}}\t{{.MemUsage}}” <id_container> container docker
HELM
sudo snap install heml - - classic installa helm (sul nodo master) in modalità classic,
disabilitando il security confinement
helm repo add http://charts.helm.sh/stable aggiunge la repo di charts stabili
helm search repo stable visualizza le chart disponibili sulla repo stable
helm install stable/mysql - -generate-name installa il chart mysql generando un nome in automatico