Block Image

Il Pod in Kubernetes

Nell'articolo precedente Introduzione a Kubernetes abbiamo detto che Kubernetes è un orchestratore di container. Il suo obiettivo è distribuire i vari container sui diversi nodi del cluster, chiamati worker nodes.

Tuttavia, Kubernetes non distribuisce direttamente i container sui worker node.
I container sono incapsulati in un oggetto Kubernetes chiamato Pod.
Il Pod è l'oggetto più piccolo che puoi creare in Kubernetes. Rappresenta la singola istanza di una applicazione.

Block Image
Esempio di quattro Pod distribuiti su due worker nodes

Il Pod e lo scaling orizzontale

Mettiamo il caso che deployamo un Pod di una nostra applicazione. Avremo quindi una singola istanza di quella applicazione.
Cosa succederebbe se il numero di utenti che accedono a quell'applicazione aumentasse esponenzialmente?
Magari la nostra applicazione è un e-commerce, che durante il Black Friday viene stressata in modo pesante.
In quel caso, possiamo scalare orizzontalmente la nostra applicazione, ovvero distribuire il carico di lavoro tra più istanze della nostra applicazione. Cioè, aumentiamo il numero di Pod della nostra applicazione!

Block Image
Esempio di scaling orizzontale da uno a quattro Pod (istanze dell'applicazione e-commerce)

Dall'immagine possiamo notare che i quattro Pod sono distribuiti equamente sui due nodi.
Se il carico di lavoro aumenta ulteriormente ma i due nodi non possono più supportare altro carico, possiamo aggiungere un nuovo nodo del Cluster in modo tale da poter deployare altri Pod ancora su quest'ultimo.

Vedremo in un successivo articolo come effettuare il ridimensionamento di una applicazione su Kubernetes e quindi come effettuare lo scaling orizzontale.

Un Pod = Un container?

Abbiamo detto che il Pod è un contenitore di container. Quando vogliamo aumentare le repliche della nostra applicazione, aumentiamo il numero di Pod, e non il numero di container nello stesso Pod.
Quindi un Pod = un container? Quasi sempre sì. Il Pod deve contenere un singolo container della nostra applicazione.
Tuttavia, esistono concetti come Init Container e Sidecar-Container.

L'InitContainer è un container che serve a inizializzare il nostro container principale (cioè la nostra applicazione).
Si avvia per primo, termina il suo compito, cosicché il nostro container principale può avviarsi. L'InitContainer è un concetto che Kubernetes conosce e gestisce tramite il suo manifest yaml.

Il Sidecar-Container è invece un design pattern. Solitamente il sidecar container è un container che fa da contorno al nostro container principale. Ad esempio potrebbe essere un container che estrapola dallo standard output i log del nostro container principale per poi essere persistiti da qualche parte. Per saperne di più: kubernetes.io/docs/concepts/cluster-administration/logging.

Quindi in alcuni casi particolari, potrebbe essere utile usare più container nello stesso Pod.
Container dello stesso Pod fanno parte della stessa rete, quindi possono comunicare in localhost. Inoltre condividono lo stesso spazio di archiviazione.

Mettiamoci all'opera con Kind!

Nell'articolo precedente abbiamo visto come creare un cluster Kubernetes in locale tramite Kind.
Sempre nell'articolo precedente, abbiamo detto che l'utente utilizza la CLI kubectl da control-plane, quindi io consiglio di fare lo stesso utilizzando Kind, sebbene la stessa CLI la potremmo utilizzare tranquillamente dal nostro host.

Da terminale, eseguire il comando docker ps per trovare il container che rappresenta il control-plane di Kind:

Block Image

Una volta trovato il nome del container, eseguire su di esso il comando bash. Nel mio caso eseguo:
docker exec -it kind-control-plane bash.
Bene, ora siamo nel control-plane del nostro Cluster. Da qui creeremo il nostro primo Pod!

Come creare un Pod

Il comando per creare un Pod è kubectl run <pod_name> --image=<image_name>.

Creiamo un Pod di NGINX col seguente comando:
kubectl run nginx --image=nginx.
In questo caso verrà creato un Pod di NGINX utilizzando l'immagine ufficiale di NGINX prelevata dal DockerHub, con tag latest.

Per vedere la lista di Pod disponibili basta eseguire il comando:
kubectl get pods. Possiamo scrivere anche po invece di pods. Lo status del Pod passerà da ContainerCreating a Running.

Block Image

I Pod sono raggiungibili tramite il loro IP, da tutti i nodi del Cluster Kubernetes.
Possiamo eseguire il comando kubectl get po -owide per vedere qual è l'IP del Pod.
La flag -o indica "output", con wide abbiamo informazioni aggiuntive.

La flag -o può essere applicata non solo ai Pod, ma a tutti gli oggetti Kubernetes.

Block Image

Come possiamo notare, questo comando, oltre a mostrare l'IP del Pod, mostra anche il nome del nodo su cui è avviato.
Lo stato Running già basta per capire che il Pod è Up & Running correttamente. Comunque, eseguiamo una cURL:
curl 10.244.0.6:

Block Image

Lo stato del Pod dipende dallo stato del container o dei container nel Pod. Per approfondire: kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle

Bene, come possiamo notare, viene correttamente visualizzata la pagina di default di NGINX.

Ricordo ancora una volta che i vari comandi "kubectl run ...", "kubectl get po" funzionano anche se eseguiti da host e non per forza da dentro il container di Kind. Tuttavia, non sarete in grado di eseguire questa ultima cURL.

Attualmente il Pod è raggiungibile solo dai nodi del Cluster. Nell'articolo sui Service, vedremo meglio nel dettaglio la gestione della rete su Kubernetes.

Ovviamente, così come per Docker, non è buona norma parlare con i Pod/container tramite IP, poiché, essendo effimeri, vengono distrutti e ricreati facilmente, e ogni volta che succede questo, cambiano gli indirizzi IP.

Pod con YAML

Prima abbiamo creato un Pod di NGINX velocemente grazie al comando kubectl run.
Questa modalità è chiamata "modalità imperativa".
Tuttavia, la potenza di Kubernetes è quella di poter creare risorse in modo "dichiarativo" tramite dei manifest yaml.
Come già detto nell'articolo su Docker Compose, scrivere un manifest anziché eseguire imperativamente dei comandi da CLI ha tanti vantaggi, come il semplice fatto che un file è banalmente versionabile.

Un manifest yaml di Kubernetes ha sempre questi campi root:

apiVersion: #String
kind: #String
metadata: #Dictionary


spec: #Dictionary
  • apiVersion rappresenta la versione dell'API Kubernetes che vogliamo utilizzare. A seconda dell'oggetto da creare, la versione dell'API potrebbe cambiare. Per i Pod, la versione è v1
  • kind indica il tipo di oggetto che vogliamo creare. Ad esempio per nel caso volessimo creare un Pod, il valore di kind è proprio Pod
  • metadata indica, come dice il nome, i metadati che possiamo associare all'oggetto da creare, come il suo nome o delle etichette
  • spec sta per specification. Dipende dall'oggetto che vogliamo creare, qui inseriremo informazioni come nome dell'immagine, nome del container.

Un manifest yaml di un Pod ha questa struttura:

apiVersion: v1
kind: Pod
metadata:
  labels:
    <a_label>: <a_label_value>
  name: <pod_name>
spec:
  containers:
    image: <img_name>
    name: <container_name>

La struttura del manifest di un Pod è molto semplice. Tuttavia, la struttura del manifest di oggetti più complessi, come i Deployment, possiede campi aggiuntivi, difficili da ricordare tutti a memoria.
Fortunatamente non dobbiamo imparare a memoria la struttura dei manifest per ogni oggetto Kubernetes.
Possiamo eseguire il seguente comando per creare automaticamente un file yaml di un Pod, secondo le nostre esigenze, ovvero in questo caso un Pod di NGINX. Innanzitutto, cancelliamo il Pod NGINX precedente col comando kubectl delete po nginx.
Poi eseguiamo il seguente comando per creare il manifest del Pod di NGINX:
kubectl run nginx --image=nginx --dry-run=client -oyaml > nginx-pod.yaml.

La flag dry-run=client permette di vedere un'anteprima dell'oggetto che verrà creato, senza crearlo realmente.
Con -oyaml vogliamo che il comando crei un'anteprima dell'oggetto in formato yaml. Infine, vogliamo scrivere lo standard output, cioè il manifest yaml, su un file chiamato nginx-pod.yaml. Vediamo com'è il file appena creato:

apiVersion: v1
kind: Pod
metadata:
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

Come possiamo notare, il comando ha creato il manifest di un Pod contenente il container di NGINX. Inoltre è stata assegnata un'etichetta con chiave "run" e valore "nginx". Le etichette servono sia per poter filtrare i vari Pod durante una ricerca, sia per raggruppare i vari oggetti tramite Selector. Vedremo in un articolo successivo il concetto di Label & Selector.

Per creare il Pod, dobbiamo eseguire il seguente comando:
kubectl apply -f nginx-pod.yaml.

In un contesto reale, difficilmente creerai davvero un Pod. L'oggetto che creerai più spesso sarà il Deployment, che permette di creare automaticamente i Pod.

Altri comandi utili

Col comando kubectl logs -f <pod_name> possiamo vedere i log del nostro Pod.
Nel caso in cui il Pod contenga più container, possiamo specificare con la flag -c il nome del container.

Block Image

Col comando kubectl exec <pod_name> -- <cmd> possiamo eseguire un comando sul Pod. Come per Docker, ad esempio possiamo entrare dentro il Pod tramite bash o sh, col seguente comando: kubectl exec -it <pod_name> -- bash.

Block Image

Block Image

Possiamo avere descrizioni aggiuntive sul Pod utilizzando il comando:
kubectl describe po <pod_name>.

Block Image

Conclusioni

In questo articolo abbiamo parlato dell'oggetto Pod di Kubernetes. Abbiamo visto come creare un Pod di NGINX sia in modalità imperativa, sia in modalità dichiarativa. Per fare cioè, abbiamo utilizzato il nostro cluster Kubernetes creato precedentemente con Kind.

Articoli su Kubernetes: Kubernetes
Articoli su Docker: Docker
Libri consigliati su Docker e Kubernetes: