In una lezione precedente, abbiamo già visto i Volume in azione per la memorizzazione di dati su Kubernetes. Memorizzare dati è fondamentale in tante applicazioni ma è importante fare in modo che questi siano persistenti, al sicuro e liberi da conflitti in accesso e scrittura.
I Volume che abbiamo visto non sono l'unica soluzione per poter memorizzare dati ma ne esistono diverse. In questa lezione approfondiamo il meccanismo dei Persistent Volume in cui vedremo al lavoro una separazione tra i Pod che eseguono i server che memorizzano dati e gli spazi di archiviazione vera e propria. Per fare questo, avremo bisogno di gestire più componenti:
- un Persistent Volume che descriverà le caratteristiche fisiche dello storage. Sarà quest'ultimo a specificarne la capacità ed il mezzo fisico su cui risiederà (
hostpath
ovvero memorizzazione su nodo, storage in rete con NFS, dischi in Cloud, etc.); - il Persistent Volume Claim ovvero la richiesta di storage al Persistent Volume da parte del server che ne avrà bisogno;
- uno o più Pod (gestiti alla bisogna da Deployment, come di consueto) che conterranno il server operativo che mediante il Persistent Volume Claim chiederà risorse di storage al Persistent Volume.
Sebbene non lo utilizzeremo in questo esempio, sarà molto spesso fondamentale anche un Service, necessario ad esporre in rete le funzionalità dei Pod di cui abbiamo parlato.
Il nostro esempio
Nel nostro caso, sperimenteremo ancora su Minikube pertanto la memorizzazione vera e propria avverrà su un disco locale mediante hostPath
. Considerato però che lo scopo finale delle componenti Kubernetes è quello di installarle in ambienti server, molto spesso in Cloud, è importante sapere che le soluzioni che affronteremo potranno essere esportate in qualsiasi contesto semplicemente adattando il driver di memorizzazione definito nel Persistent Volume (vedasi il primo punto dell'elenco al paragrafo precedente), trasformandolo da una memorizzazione locale a una basata su componenti Cloud, NFS, etc.
Iniziamo la nostra costruzione analizzando una componente alla volta.
Il Persistent Volume
Per prima cosa, andiamo a creare un Persistent Volume (file storage-pv.yaml
) che forgerà lo spazio di storage:
apiVersion: v1
kind: PersistentVolume
metadata:
name: storage-pv
labels:
volume: persistent-storage
spec:
storageClassName: manual
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/data"
Tale configurazione potrà essere attivata con il comando:
$ kubectl apply -f storage-pv.yaml
e ciò avvierà una nuova componente:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
storage-pv 5Gi RWO Retain Available manual 9s
Anche in questa procedura avremo dimostrazione della comodità del collegamento dinamico tra componenti mediante label: creeremo diverse componenti ognuna delle quali sarà associata ad un'altra grazie alle etichette.
Il Persistent Volume Claim
Ora sarà necessario definire il Claim, componente mediante il quale per i Pod sarà possibile disporre dello spazio storage di cui hanno bisogno (file storage-pvc.yaml
):
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: storage-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: manual
resources:
requests:
storage: 5Gi
selector:
matchLabels:
volume: persistent-storage
Anche questo potrà essere avviato mediante il comando apply
:
$ kubectl apply -f storage-pvc.yaml
ed anche in questo caso si potrà vedere la nuova componente al lavoro:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
storage-pvc Bound storage-pv 5Gi RWO manual 4s
Inoltre, richiamando nuovamente kubectl get pv
troveremo alcuni campi aggiornati:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
storage-pv 5Gi RWO Retain Bound default/storage-pvc manual 5m59s
In particolare, il campo STATUS
è stato impostato come Bound
e CLAIM
come default/storage-pvc
ovvero il Persistent Volume Claim che abbiamo definito.
Il Pod operativo e la prova finale
A questo punto possiamo creare l'aggancio con il nostro Pod, Deployment o in generale la componente operativa che ne avrà bisogno. Noi definiamo un Pod che avvierà un server Web Apache:
kind: Pod
apiVersion: v1
metadata:
name: apache-web-server
spec:
volumes:
- name: volume-claim
persistentVolumeClaim:
claimName: storage-pvc
containers:
- name: httpd-server
image: httpd
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/local/apache2/htdocs"
All’interno del codice YAML notiamo in particolare che abbiamo definito un Volume (chiamato volume-claim
) rifacendoci al Claim che diventa il nostro tramite verso lo storage fisico. Mentre all'interno del Pod viene agganciato alla cartella home
di Apache Web Server collocata in /usr/local/apache2/htdocs
:
volumes:
- name: volume-claim
persistentVolumeClaim:
claimName: storage-pvc
...
...
volumeMounts:
- mountPath: "/usr/local/apache2/htdocs"
In questo modo, avremo un server Web che archivia le sue pagine all'interno del Persistent Volume e questo, grazie al Claim, sarà ormai una risorsa invocabile da altre componenti. Ad esempio, per esercizio, si potrà creare un nuovo Pod basato sull'immagine di un server Web NGINX, collegato allo stesso Claim, agganciato però alla cartella /usr/share/nginx/html
, home
di NGINX.