Nella lezione precedente abbiamo imparato a pubblicare un nostro Deployment mediante la componente Service. In questo modo siamo riusciti a rendere disponibili le funzionalità di un workload mediante collegamento in rete. Proprio questa esposizione rappresenta una delle tematiche più calde nella creazione di applicazioni distribuite che, per loro natura, si prestano ad essere raggiunte da remoto sia dall'interno del cluster che dall'esterno.
Un aspetto molto importante però consiste nel curare il cosiddetto load balancing ovvero il bilanciamento del carico. Si tratta di una funzionalità fondamentale quando le applicazioni devono sostenere un carico di richieste variabile che può diventare improvvisamente imponente per determinati lassi di tempo. Teoricamente, un'architettura simile vede un load balancer in ingresso che riceve le richieste e le distribuisce in maniera uniforme tra una serie di "server" che replicano l'applicazione. Tali server possono essere macchine virtuali o fisiche ma nel nostro caso si tratterà di componenti Kubernetes dove le anime operative saranno i Pod. Inoltre, vale la pena ricordare che i load balancer possono essere ad esclusivo uso interno dell'applicazione o esposti verso l'esterno per rispondere alle richieste dell'utenza.
Il bilanciamento del carico in generale offre non solo la distribuzione delle richieste sui vari server ma anche una serie di facoltà correlate come, ad esempio, il continuo monitoraggio dei server e l'individuazione di quelli eventualmente non funzionanti. Ciò è indispensabile per poter distribuire le richieste senza che vadano perse ma al contempo diventa una forma di controllo sulle componenti che stanno gestendo l'applicazione.
Service e Ingress
In Kubernetes, esistono principalmente due modi fondamentali per gestire il load balancing e sono:
- l'utilizzo di Service, gli stessi incontrati nella lezione precedente, ma con un
type
impostato al valoreLoadBalancer
; - gli Ingress che rappresentano una componente a sé stante nello scenario di Kubernetes.
In generale, utilizzare il load balancing offerto dai Service potrebbe essere sufficiente tuttavia è spesso necessario ricorrere agli Ingress. La differenza di base tra essi consiste in una diversa collocazione nello stack di rete, infatti i Service eseguono un load balancing a livello TCP quindi non sono in grado di ispezionare i pacchetti al loro interno. Gli Ingress invece lavorano a livello HTTP pertanto in grado di leggere le intestazioni specifiche di questo protocollo applicativo. Inoltre, come vedremo, gli Ingress possono essere considerati uno strumento più articolato in quanto possono essere "programmati" con delle regole per gestire le richieste in base agli indirizzi
Load balancing con i Service
Forniamo un esempio di entrambi i casi. Prepareremo in questo paragrafo un semplice Deployment che andremo poi a pubblicare con un load balancer sia con un Service sia con un Ingress.
Ispiriamoci a quanto già visto nel corso di questa guida attivando, in modalità imperativa, un Web server di tipo NGINX:
$ kubectl create deployment my-server-web --image=nginx
deployment.apps/my-server-web created
Esponiamo il Deployment come sappiamo fare ovvero con un Service solo che questa volta lo imposteremo con type=LoadBalancer
:
$ kubectl expose deployment my-server-web --type=LoadBalancer --port=80
service/my-server-web exposed
Eseguendo il tutto su minikube otteniamo quanto dimostrato dalla seguente interrogazione:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 60s
my-server-web LoadBalancer 10.106.162.229 <pending> 80:30681/TCP 14s
Notiamo che nella colonna EXTERNAL-IP
è rimasto il valore pending
il che significa che non si è riuscito ad ottenere un indirizzo IP esterno. Ciò perché il nostro è un ambiente di esecuzione di prova mentre se avessimo attivato il tutto su una piattaforma Cloud avremmo ottenuto in pochi secondi un riferimento da contattare direttamente via browser da qualsiasi parte del mondo. Tuttavia, anche in minikube è possibile ottenere connettività per un Service di tipo LoadBalancer
ricorrendo ad un tunnel. Eseguendo infatti:
$ minikube tunnel
disporremo di un indirizzo da contattare via rete anche in questo caso: unico accorgimento da adottare consiste nell'eseguire il tunnel in una finestra terminale separata o in background in quanto risulterà bloccante per il prompt. Nel nostro ambiente di prova, applicando un tunnel si ottiene:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3m19s
my-server-web LoadBalancer 10.106.162.229 10.106.162.229 80:30681/TCP 2m33s
che rende possibili interrogazioni dirette come curl 10.106.162.229
per ottenere, nel nostro caso, l'homepage del server NGINX.
Applicazione di un Ingress
Pubblichiamo ora lo stesso Deployment mediante un Ingress. Questo si risolve in un controller in grado di smistare lavoro tra vari Service. Se si sta sperimentando il tutto con minikube, dovremo per prima cosa attivare un apposito add-on:
minikube addons enable ingress
Fermo restando il nostro Deployment, illustrato a inizio esempio, lo esporremo con un Service che in questo caso potrà essere di tipo NodePort (visto che il load balancer sarà l'Ingress):
kubectl expose deployment my-server-web --type=NodePort --port=80
e costruiremo nel file ingress.yaml
il nostro Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-server-web
port:
number: 80
Notiamo che nell'unica regola (sezione rules
) che abbiamo definito associamo un percorso (path: /
) con un backend, rappresentato in questo caso dal nostro Service my-server-web
.
Attivando il tutto con kubectl apply -f ingress.yaml
potremo vedere l'Ingress operativo con:
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
my-ingress nginx * 192.168.49.2 80 14m
ed invocando curl 192.168.49.2
otterremo la risposta da NGINX.
Ciò che rende davvero interessanti gli Ingress è la possibilità di definire una serie di regole articolando meglio la rete di backend raggiungibili. Si provi già in questo caso ad applicare un diverso percorso, ad esempio, con path: /mioserver
. Ricaricando il file YAML si vedrà che da questo momento NGINX non sarà più raggiungibile con curl 192.168.49.2
bensì con curl 192.168.49.2/mioserver
in virtù dell'annotation impostata nei metadati.