Kubernetes principi di funzionamento

in Architetture Software, Informatica

Kubernetes – Principi di Funzionamento

Reading Time: 6 minutes

In articolo precedente ho descritto l’architettura alla base di Kubernetes.

Kubernetes è il direttore d’orchestra per containers: coordina i containers assicurandosi che i risultati architetturali e funzionali siano quelli che ci si era prefissato.

Con docker vengono creati containers a partire da immagini realizzate tramite dockerfile o scaricate da repository esistenti, come dockerhub.

Gli strumenti messi a disposizione da docker (e dalla sua linea di comando) sono piuttosto semplici: consentono la creazione di immagini e la loro esecuzione durante lo svilupppo, ma in ambiente di staging e produzione, spesso si ha la necessità di un grado di configurazione decisamente più elevato.

Con il crescere della complessità e dell’affidabilità si rende necessario definire un sistema che consenta di gestire in maniera semplice
le funzionalità e l’iterazione dei singoli container. In questo si ha la necessità di utilizzare un orchestratore.

Kubernates si occupa proprio questo, cercando di semplificare le operazioni di configurazione, il deploy ed il controllo dei containers.

Struttura

L’intera architettura di Kubernetes è composta da una serie di elementi:

  • nodi master: i gestori dell’infrastruttura e all’interno dei quali non sono eseguiti i container. I nodi master espongono all’esterno le api per poter interagire/configurare i nodi interni. La comunicazione tra master e nodi interni avviene attraverso un servizio chiamato kubelet.
  • nodi standard (interni): nodi dove sono posizionati i containers.

Nodi master e nodi standard compogono quello che tecnicamente viene definito un cluster kubernetes.

Kubernetes

Solitamente all’interno di un cluster sono presenti diversi nodi master e migliaia di nodi dislocati all’interno di aree geografiche differenti.

Kubernetes Cluster


L’allocazione dei containers all’interno dei nodi avviene in maniera automatica ed è lo stesso Kubernetes a decidere dove allocare/spostare i containers, in base al carico dei singoli nodi.
Ad esempio, se un nodo è particolarmente sovraccarico, i containers verranno spostati automaticamente su un altro nodo con maggiore disponibilità e carico minore.

Organizzazione dei container: Pods

Il numero elevato di containers con cui si interagisce viene semplificato introducendo il concetto di Pod.
Un pod è un gruppo di container eseguito all’interno di un nodo, ed all’esterno viene visto come un’unica unità.
I containers all’interno di un pod condividono le stesse risorse, gli stessi storage e lo stesso networking. Verso l’esterno tutti i containers presenti all’interno di un pod condividono lo stesso indirizzo IP, che è privato e visibile solo all’interno del cluster.

Pods

Sarà compito del nodo master mantere una tabella di routing adeguata per consentire la comunicazione tra containers presenti all’interno di nodi / pods differenti. Solitamente all’interno di un pod è presente un solo container, ma non è una regola generale: è infatti possibile eseguire più containers all’interno di un singolo pod.

Definizione di pods

La configurazione di un cluster Kubernetes viene effettuata utilizzando file nel formato YAML, un particolare formato molto usato nella definizione di file serializzati.

apiVersion: v1
kind: Pod
metadata:
   name: my-app
spec: 
    containers:
    -name: my-app
     image: my-app
    -name: nginx-ssl
     image: nginx
     ports: 
     - containerPort: 80
     - containerPort: 443

Nell’esempio viene definito il nome del pod che deve essere realizzato (tramite metadata) e l’elenco dei containers che dovranno essere posizionati all’interno del pod stesso. Dopo le prime due righe relative alla versione delle API che si intende utilizzare ed il tipo di file (Pod in questo caso), si procede con la definizione di tutte le altre informazioni necessarie.

Per ogni container viene indicata l’immagine sorgente, ed eventualmente tutte le features che dovranno essere associate. Nell’esempio, per il secondo container, vengono definite anche le porte che devono essere aperte per renderlo raggiungibile esternamente (porta 80 e porta 443 trattandosi di un server nginx).

Il container my-app, invece, non espone verso l’esterno nessuna porta, ma potrà comunicare con l’altro container tramite la rete condivisa all’interno del pod.

Una volta salvato, il file .yaml dovrà essere eseguito, per poter effettuare il deploy del pod all’interno di Kubernetes. E’ possibile interagire con il cluster utilizzando il comando kubectl (che tratterò in prossimo post).

Eseguendo dalla console:

$> kubectl apply -f filename.yaml

verrà impartito il comando di deploy al master node (che ricordo essere l’unico nodo ad esporre le api verso l’esterno) che si occuperà del deploy del pod all’interno di uno dei nodi già precedentemente allocati. Il tutto in maniera trasparente per l’utente.

Kubernates e Labels

Le labels sono dei metadata che possono essere associati a qualsiasi oggetto dell’ api di Kubernates. Consentono di etichettare oggetti, in modo da poterli facilmente recuperare, ricercare ed interrogare. In un ambiente composto da numersosi pods, le label sono sicuramente un ottimo strumento per poter raggruppare e individuare facilmente un’insieme di pods. In pratica, sono un ottimo strumento per organizzare i pods.

Kubernates Deployment

Kubernetes, soddisfa in maniera eccellente il ruolo di orchestratore e consente di monitorare il numero di nodi attivi in qualsiasi momento. In fase di configurazione del cluster è possibile definire il numero di nodi minimo e massimo che dovranno essere sempre attivi. Kubernates utilizza il concetto di deployment per definire questo tipo di configurazione in una sorta di failover automatico. Anche in questo caso la configurazione del deployment viene fatta utilizzando un file .yaml.

apiVersion: v1
kind: Deployment
metadata:
   name: frontend
spec: 
    replicas: 4
    selector: 
      role: web
    template:
      metadata: 
       name: web
       labels: 
         role: web
    spec: 
      containers:
      -name: my-app
       image: my-app
      -name: nginx-ssl
       image: nginx
       ports: 
        - containerPort: 80
        - containerPort: 443

A differenza del file precedente è stato indicato il kind Deployment, ed il parametro replicas che indica quanti pods di ciascun tipo dovranno essere eseguiti contemporaneamente. Passando il file yaml alle api del nodo master:

$> kubectl apply -f deployment.yaml

Supponendo che nel nostro cluster siano presenti 3 nodi, Kubernetes si occuperà della distribuzione dei pods tra i vari nodi, preoccupandosi di allocare due pods presumibilmente sul nodo con maggiori prestazioni / minore carico. Nel caso si verificassero problemi con un nodo tali per cui il nodo stesso diventi non più utilizzabile, Kubernetes provvederà al deploy automatico dei pods sui nodi rimasti attivi.

Entrando un pò più nel dettaglio, Kubernetes esegue periodicamente una serie di controlli per verificare lo stato di salute di ogni singolo pod. Si tratta in pratica di periodiche interrogazioni che verificano le risposte di ogni singolo pod. Se un pod non è raggiungibile o non risponde verrà considerato come non funzionante: la prima operazione che verrà effettuata sarà quella di un riavvio del pod e se tutto ritornerà alla normalità il pod verrà considerato funzionante. Questo tipo di iterezione viene effettuata tramite i kubelet.

Condivisione delle risorse e Pod

All’interno di un pod, i containers condividono le stesse risorse ed il filesystem locale. Questo filesystem è privato, non raggiungibile dall’esterno e soprattutto non persistente. In pratica, non viene mantenuto in caso di riavvio del pod. Ancora una volta Kubernetes ci viene in aiuto, introducendo il concetto di Volume: un volume è uno storage condiviso tra i container di un pod e soprattutto mantiene la persistenza in caso di restart di un pod.

Per la creazione di un volume è necessario utilizzare il comando :

$> kubectl create <volume>

Una volta creato un volume diventa automaticamente disponibile per ciascun container, che devono effettuare un’operazione di mount per poter accedere. La definizione dell’operazione di mount deve essere indicata all’interno del file yaml, per ogni singolo container:

....
containers:
- name: cont001
  image: cont001:v1
  volumeMounts: 
  - name: <volume>
    mountPath: /shared-data

Esistono particolari volumi legati alla vita del pod: offrono storage solo per lo specifico pod in cui sono definiti e se il pod viene terminato o smette di funzionare, lo storage viene perso.

Kubernetes e Services

I pods, per come sono realizzati, non sono dotati di persistenza: un pod potrebbe essere fermato, arrestato o più semplicemente fallire nell’esecuzione. A causa di questa loro natura, ad ogni riavvio potrebbero avere un indirizzo ip diverso: evidentemente si verifica un potenziale problema per la comunicazione tra i container del sistema, che non riuscirebbero più a comunicare tra loro. Anche in questo caso Kubernetes ci viene in aiuto, introducendo un concetto molto importante, quello dei servizi. Un servizio è sostanzialmente un endpoint che fornisce un indirizzo ip fisso ad un gruppo di pods. La comunicazione all’interno dell’applicazione avverrà attraverso questo endpoint che rimarrà invariato anche a seguito di riavvi/ sostituzione dei pods sottostanti. Un pò come avviene per i deployment anche all’interno dei servizi vengono definiti dei selettori che indicano quali pods devono essere considerati. I pods che hanno label corrette vengono presi in carico direttamente dal servizio. Risulterà piuttosto chiaro che tutte le risorse di Kubernetes devono essere configurate con un file .yaml . Ovviamente ciò avviene anche per la configurazione dei servizi.

apiVersion: v1
kind: Service
metadata: 
  name: web-frontend
spec: 
  ports: 
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP
    selector: 
       role:web
    type: NodePort

Attraverso il selettore (selector) viene definita l’etichetta che verrà utilizzata da Kubernetes per associare il servizio con i pods, mentre mediante type viene definito il tipo di accesso del servizio (NodePort). In pratica questo servizio sarà accessibile attraverso una porta da tutti i nodi del cluster .

Nel prossimo post tratterò nello specifico il comando kubectl, che consente di utilizzare le api di Kubernetes.