Kubernetes + Heketi
Passiamo ora a parlare dello storage in Kubernetes. Il modo più ovvio per creare i persistent volumes e i persistent volume claims è il modo manuale. Si sceglie l'eventuale driver…
> Articolo d'archivio del 2018, dal vecchio nutellino.it: comandi e versioni potrebbero non essere più attuali.

Passiamo ora a parlare dello storage in Kubernetes. Il modo più ovvio per creare i persistent volumes e i persistent volume claims è il modo manuale.
Si sceglie l'eventuale driver e si confiugrano i persistent volumes che poi verranno utilizzati dai persistent volume claims.
Tutto ciò però rende macchinosa e lenta la gestione dello storage in kubernetes. Per questo motivo sono stati creati dei sistemi di volume management con i quali si possono creare persistent volumes on demand tramite i persistent volume claims che vengono richiesti. Questa funzionalità si basa su un altro concetto di Kubernetes chiamato Storage Classes.
In questa guida vedremo come attivare Heketi, un provisioner che gestisce volumi basati su tecnologia GlusterFS. Questi volumi avranno come proprietà intrinseca la ridondanza e l'HA, perché creeremo una topologia basata sul mirroring di 3 nodi.
Il requisito per poter seguire questa guida passo passo è:
- Un cluster Kubernetes esistente
- Poter aggiungere 3 nodi kubernetes i quali hanno ognuno un disco aggiuntivo collegato alla macchina (in parole povere un /dev/sdb non formattato)
Nel mio caso ho 3 nodi aggiuntivi che ho chiamato diversamente per poterli riconoscere:
> kubectl get nodes
NAME STATUS ROLES AGE VERSION
nodokube-1.c.nth-passage-183120.internal Ready <none> 1d v1.8.10-rancher1
nodokube-2.c.nth-passage-183120.internal Ready <none> 1d v1.8.10-rancher1
nodokube-3.c.nth-passage-183120.internal Ready <none> 1d v1.8.10-rancher1
nodokube-4.c.nth-passage-183120.internal Ready <none> 1d v1.8.10-rancher1
nodokube-6-g1.c.nth-passage-183120.internal Ready <none> 1d v1.8.10-rancher1
nodokube-7-g2.c.nth-passage-183120.internal Ready <none> 1d v1.8.10-rancher1
nodokube-8-g3.c.nth-passage-183120.internal Ready <none> 1d v1.8.10-rancher1- nodokube-6-g1
- nodokube-7-g2
- nodokube-8-g3
Questi 3 nodi contengono un disco aggiuntivo ssd da 30GB l'uno collegato in /dev/sdb.
Quello che faremo sarà seguire la guida presente in https://github.com/heketi/heketi/blob/master/docs/admin/install-kubernetes.md
Verifichiamo il firewall
Nel mio caso i nodi sono tutti ubuntu, quindi non ho bisogno di aprire alcuna porta nel firewall. Nel caso in cui siano CentOs o altre distribuizioni, bisogna lanciare i comandi di iptables per aprire le porte di gluster nei nodi interessati:
iptables -N HEKETI
iptables -A HEKETI -p tcp -m state --state NEW -m tcp --dport 24007 -j ACCEPT
iptables -A HEKETI -p tcp -m state --state NEW -m tcp --dport 24008 -j ACCEPT
iptables -A HEKETI -p tcp -m state --state NEW -m tcp --dport 2222 -j ACCEPT
iptables -A HEKETI -p tcp -m state --state NEW -m multiport --dports 49152:49251 -j ACCEPT
service iptables savePrerequisiti aggiuntivi
Nella guida ufficiale non sono indicati alcuni passaggi fondamentali, tra cui aggiungere in tutti i nodi del cluster il driver per il collegamento ad un volume gluster e ai 3 nodi che conterranno i pod di glusterfs la possibilità di gestire volumi in thin provision.
Dobbiamo quindi fare in tutti i nodi questi due comandi:
apt-get install -y glusterfs-client
modprobe dm_thin_poolDeploy
GlusterFS daemonset
Creiamo il primo file, glusterfs-daemonset.json
{
"kind": "DaemonSet",
"apiVersion": "extensions/v1beta1",
"metadata": {
"name": "glusterfs",
"labels": {
"glusterfs": "deployment"
},
"annotations": {
"description": "GlusterFS Daemon Set",
"tags": "glusterfs"
}
},
"spec": {
"template": {
"metadata": {
"name": "glusterfs",
"labels": {
"glusterfs-node": "daemonset"
}
},
"spec": {
"nodeSelector": {
"storagenode" : "glusterfs"
},
"hostNetwork": true,
"containers": [
{
"image": "gluster/gluster-centos:latest",
"imagePullPolicy": "Always",
"name": "glusterfs",
"volumeMounts": [
{
"name": "glusterfs-heketi",
"mountPath": "/var/lib/heketi"
},
{
"name": "glusterfs-run",
"mountPath": "/run"
},
{
"name": "glusterfs-lvm",
"mountPath": "/run/lvm"
},
{
"name": "glusterfs-etc",
"mountPath": "/etc/glusterfs"
},
{
"name": "glusterfs-logs",
"mountPath": "/var/log/glusterfs"
},
{
"name": "glusterfs-config",
"mountPath": "/var/lib/glusterd"
},
{
"name": "glusterfs-dev",
"mountPath": "/dev"
},
{
"name": "glusterfs-cgroup",
"mountPath": "/sys/fs/cgroup"
}
],
"securityContext": {
"capabilities": {},
"privileged": true
},
"readinessProbe": {
"timeoutSeconds": 3,
"initialDelaySeconds": 60,
"exec": {
"command": [
"/bin/bash",
"-c",
"systemctl status glusterd.service"
]
}
},
"livenessProbe": {
"timeoutSeconds": 3,
"initialDelaySeconds": 60,
"exec": {
"command": [
"/bin/bash",
"-c",
"systemctl status glusterd.service"
]
}
}
}
],
"volumes": [
{
"name": "glusterfs-heketi",
"hostPath": {
"path": "/var/lib/heketi"
}
},
{
"name": "glusterfs-run"
},
{
"name": "glusterfs-lvm",
"hostPath": {
"path": "/run/lvm"
}
},
{
"name": "glusterfs-etc",
"hostPath": {
"path": "/etc/glusterfs"
}
},
{
"name": "glusterfs-logs",
"hostPath": {
"path": "/var/log/glusterfs"
}
},
{
"name": "glusterfs-config",
"hostPath": {
"path": "/var/lib/glusterd"
}
},
{
"name": "glusterfs-dev",
"hostPath": {
"path": "/dev"
}
},
{
"name": "glusterfs-cgroup",
"hostPath": {
"path": "/sys/fs/cgroup"
}
}
]
}
}
}
}E lanciamo il comando di creazione
$ kubectl create -f glusterfs-daemonset.json
daemonset "glusterfs" createdA questo punto non abbiamo ancora i pod di glusterfs avviati, bisogna prima taggare i nodi con la chiave corretta. Procediamo quindi al tag come indicato nel daemonset
"spec": {
"nodeSelector": {
"storagenode" : "glusterfs"
}$ kubectl label node nodokube-6-g1.c.nth-passage-183120.internal storagenode=glusterfs
node "nodokube-6-g1.c.nth-passage-183120.internal" labeled
$ kubectl label node nodokube-7-g2.c.nth-passage-183120.internal storagenode=glusterfs
node "nodokube-7-g2.c.nth-passage-183120.internal" labeled
$ kubectl label node nodokube-8-g3.c.nth-passage-183120.internal storagenode=glusterfs
node "nodokube-8-g3.c.nth-passage-183120.internal" labeledA questo punto il daemonset troverà i nodi con il tag corretto e procederà alla creazione dei pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
glusterfs-296z2 0/1 Running 0 36s
glusterfs-4cd5v 0/1 Running 0 32s
glusterfs-pgvgt 0/1 Running 0 40s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
glusterfs-296z2 1/1 Running 0 2m
glusterfs-4cd5v 1/1 Running 0 1m
glusterfs-pgvgt 1/1 Running 0 2mHeketi service account
Ora dobbiamo creare un service account per heketi, creiamo il file heketi-service-account.json
{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"name": "heketi-service-account"
}
}e lanciamo quindi il comando di creazione:
$ kubectl create -f heketi-service-account.json
serviceaccount "heketi-service-account" createdOra dobbiamo fare in modo che questo service account possa controllare i pod glusterfs. Creiamo quindi un role binding al service account appena creato
$ kubectl create clusterrolebinding heketi-gluster-admin --clusterrole=edit --serviceaccount=default:heketi-service-account
clusterrolebinding "heketi-gluster-admin" createdA questo punto creiamo un secret che avrà al suo interno le configurazioni dell'istanza di Heketi. La configurazione dovrà contenere il kubernetes executor per far si che il server Heketi possa controllare i pod glusterfs.
Creiamo quindi il file heketi.json
{
"_port_comment": "Heketi Server Port Number",
"port": "8080",
"_use_auth": "Enable JWT authorization. Please enable for deployment",
"use_auth": false,
"_jwt": "Private keys for access",
"jwt": {
"_admin": "Admin has access to all APIs",
"admin": {
"key": "My Secret"
},
"_user": "User only has access to /volumes endpoint",
"user": {
"key": "My Secret"
}
},
"_glusterfs_comment": "GlusterFS Configuration",
"glusterfs": {
"_executor_comment": "Execute plugin. Possible choices: mock, kubernetes, ssh",
"executor": "kubernetes",
"_db_comment": "Database file name",
"db": "/var/lib/heketi/heketi.db",
"kubeexec": {
"rebalance_on_expansion": true
},
"sshexec": {
"rebalance_on_expansion": true,
"keyfile": "/etc/heketi/private_key",
"fstab": "/etc/fstab",
"port": "22",
"user": "root",
"sudo": false
}
},
"_backup_db_to_kube_secret": "Backup the heketi database to a Kubernetes secret when running in Kubernetes. Default is off.",
"backup_db_to_kube_secret": false
}ATTENZIONE in questo esempio abbiamo disattivato l'autenticazione.
Lanciamo la creazione di questo secret:
$ kubectl create secret generic heketi-config-secret --from-file=./heketi.json
secret "heketi-config-secret" createdAvvio del pod di bootstrap di Heketi
A questo punto lanciamo un pod di bootstrap che si occuperà di creare il nostro cluster e di salvare la configurazione di heketi per il deploy successivo di produzione.
Creiamo il file heketi-bootstrap.json
{
"kind": "List",
"apiVersion": "v1",
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "deploy-heketi",
"labels": {
"glusterfs": "heketi-service",
"deploy-heketi": "support"
},
"annotations": {
"description": "Exposes Heketi Service"
}
},
"spec": {
"selector": {
"name": "deploy-heketi"
},
"ports": [
{
"name": "deploy-heketi",
"port": 8080,
"targetPort": 8080
}
]
}
},
{
"kind": "Deployment",
"apiVersion": "extensions/v1beta1",
"metadata": {
"name": "deploy-heketi",
"labels": {
"glusterfs": "heketi-deployment",
"deploy-heketi": "deployment"
},
"annotations": {
"description": "Defines how to deploy Heketi"
}
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"name": "deploy-heketi",
"labels": {
"name": "deploy-heketi",
"glusterfs": "heketi-pod",
"deploy-heketi": "pod"
}
},
"spec": {
"serviceAccountName": "heketi-service-account",
"containers": [
{
"image": "heketi/heketi:dev",
"imagePullPolicy": "Always",
"name": "deploy-heketi",
"env": [
{
"name": "HEKETI_EXECUTOR",
"value": "kubernetes"
},
{
"name": "HEKETI_FSTAB",
"value": "/var/lib/heketi/fstab"
},
{
"name": "HEKETI_SNAPSHOT_LIMIT",
"value": "14"
},
{
"name": "HEKETI_KUBE_GLUSTER_DAEMONSET",
"value": "y"
}
],
"ports": [
{
"containerPort": 8080
}
],
"volumeMounts": [
{
"name": "db",
"mountPath": "/var/lib/heketi"
},
{
"name": "config",
"mountPath": "/etc/heketi"
}
],
"readinessProbe": {
"timeoutSeconds": 3,
"initialDelaySeconds": 3,
"httpGet": {
"path": "/hello",
"port": 8080
}
},
"livenessProbe": {
"timeoutSeconds": 3,
"initialDelaySeconds": 30,
"httpGet": {
"path": "/hello",
"port": 8080
}
}
}
],
"volumes": [
{
"name": "db"
},
{
"name": "config",
"secret": {
"secretName": "heketi-config-secret"
}
}
]
}
}
}
}
]
}e quindi lanciamolo, verra creato un service e un deployment chiamati deploy-heketi:
$ kubectl create -f heketi-bootstrap.json
service "deploy-heketi" created
deployment "deploy-heketi" createdVerifichiamo lo stato del pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
deploy-heketi-6b496b5777-9f2rp 0/1 Running 0 26s
glusterfs-296z2 1/1 Running 0 1h
glusterfs-4cd5v 1/1 Running 0 1h
glusterfs-pgvgt 1/1 Running 0 1h
kubectl get pods
NAME READY STATUS RESTARTS AGE
deploy-heketi-6b496b5777-9f2rp 1/1 Running 0 1m
glusterfs-296z2 1/1 Running 0 1h
glusterfs-4cd5v 1/1 Running 0 1h
glusterfs-pgvgt 1/1 Running 0 1hOra che abbiamo lanciato il pod di bootstrap possiamo configurare il portforwarding nella nostra macchina per utilizzare heketi-cli (scaricate heketi-cli da https://github.com/heketi/heketi/releases/).
Prendiamo il nome del pod di heketi, nel mio caso deploy-heketi-6b496b5777-9f2rp e lanciamo il seguente comando
$ kubectl port-forward deploy-heketi-6b496b5777-9f2rp :8080
Forwarding from 127.0.0.1:64622 -> 8080Nel mio caso è stato fatto il forward della porta 64622 del mio host verso il pod di deploy-heketi.
Verifichiamone il funzionamento:
$ curl http://localhost:64622/hello
Hello from HeketiSettiamoci per comidita la variabile d'ambiente contenente l'indirizzo di heketi in modo da non dover scrivere ogni volta l'indirizzo del pod
$ export HEKETI_CLI_SERVER=http://localhost:64622Caricamento topologia server gluster
A questo punto dobbiamo caricare la topologia dei nostri nodi contenenti i pod daemonset di glusterfs, indicando anche qual'è il disco della macchina che verrà formattato per l'utilizzo con i volumi di glusterfs.
Creiamo il file topology-load.json
{
"clusters": [
{
"nodes": [
{
"node": {
"hostnames": {
"manage": [
"nodokube-6-g1.c.nth-passage-183120.internal"
],
"storage": [
"10.142.0.8"
]
},
"zone": 1
},
"devices": [
"/dev/sdb"
]
},
{
"node": {
"hostnames": {
"manage": [
"nodokube-7-g2.c.nth-passage-183120.internal"
],
"storage": [
"10.142.0.9"
]
},
"zone": 1
},
"devices": [
"/dev/sdb"
]
},
{
"node": {
"hostnames": {
"manage": [
"nodokube-8-g3.c.nth-passage-183120.internal"
],
"storage": [
"10.142.0.7"
]
},
"zone": 1
},
"devices": [
"/dev/sdb"
]
}
]
}
]
}Notiamo alcuni parametri:
- storage: è stato inserito l'ip fisso del nodo contenente il pod
- manage: è stato inserito l'hostname del nodo, nel mio caso nodokube-6-g1.c.nth-passage-183120.internal, etc
- devices: è stato inserito il path del disco aggiunto alla macchina virtuale
Carichiamo quindi la topologia via heketi-cli nel nostro pod heketi:
$ ./heketi-cli topology load --json=topology-load.json
Creating cluster ... ID: 9ed46f67a517d5e46b9245e1214c2c03
Allowing file volumes on cluster.
Allowing block volumes on cluster.
Creating node nodokube-6-g1.c.nth-passage-183120.internal ... ID: 0379d0bb941a80c6ec65fed344306602
Adding device /dev/sdb ... OK
Creating node nodokube-7-g2.c.nth-passage-183120.internal ... ID: 7d271bdb173887adc09ecfc677604378
Adding device /dev/sdb ... OK
Creating node nodokube-8-g3.c.nth-passage-183120.internal ... ID: 8a547e452d0442f7e44a317e75c9fba8
Adding device /dev/sdb ... OKSalvataggio database di heketi in un volume gluster
Qui arriva un punto abbastanza "magico". Dobbiamo creare un volume provisionato da deploy-heketi che verrà poi usato dal vero pod heketi che utlizzeremo.
Chidiamo a heketi di crearci il volume
$ ./heketi-cli setup-openshift-heketi-storage
Saving heketi-storage.json
$ kubectl create -f heketi-storage.json
secret "heketi-storage-secret" created
endpoints "heketi-storage-endpoints" created
service "heketi-storage-endpoints" created
job "heketi-storage-copy-job" createdDobbiamo attendere che il job sia completato, dopodiché possiamo procedere all'eliminazione dei pod di deploy
Verifichiamo lo stato con:
$ kubectl get jobs
NAME DESIRED SUCCESSFUL AGE
heketi-storage-copy-job 1 1 1mEliminiamo il deploy di bootstrap:
$ kubectl delete all,service,jobs,deployment,secret --selector="deploy-heketi"
deployment "deploy-heketi" deleted
job "heketi-storage-copy-job" deleted
pod "deploy-heketi-6b496b5777-9f2rp" deleted
service "deploy-heketi" deleted
secret "heketi-storage-secret" deletedCreazione del long-term Heketi
Creiamo il file di deploy di heketi finale, heketi-deployment.json
{
"kind": "List",
"apiVersion": "v1",
"items": [
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "heketi-db-backup",
"labels": {
"glusterfs": "heketi-db",
"heketi": "db"
}
},
"data": {
},
"type": "Opaque"
},
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "heketi",
"labels": {
"glusterfs": "heketi-service",
"deploy-heketi": "support"
},
"annotations": {
"description": "Exposes Heketi Service"
}
},
"spec": {
"selector": {
"name": "heketi"
},
"ports": [
{
"name": "heketi",
"port": 8080,
"targetPort": 8080
}
]
}
},
{
"kind": "Deployment",
"apiVersion": "extensions/v1beta1",
"metadata": {
"name": "heketi",
"labels": {
"glusterfs": "heketi-deployment"
},
"annotations": {
"description": "Defines how to deploy Heketi"
}
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"name": "heketi",
"labels": {
"name": "heketi",
"glusterfs": "heketi-pod"
}
},
"spec": {
"serviceAccountName": "heketi-service-account",
"containers": [
{
"image": "heketi/heketi:dev",
"imagePullPolicy": "Always",
"name": "heketi",
"env": [
{
"name": "HEKETI_EXECUTOR",
"value": "kubernetes"
},
{
"name": "HEKETI_FSTAB",
"value": "/var/lib/heketi/fstab"
},
{
"name": "HEKETI_SNAPSHOT_LIMIT",
"value": "14"
},
{
"name": "HEKETI_KUBE_GLUSTER_DAEMONSET",
"value": "y"
}
],
"ports": [
{
"containerPort": 8080
}
],
"volumeMounts": [
{
"mountPath": "/backupdb",
"name": "heketi-db-secret"
},
{
"name": "db",
"mountPath": "/var/lib/heketi"
},
{
"name": "config",
"mountPath": "/etc/heketi"
}
],
"readinessProbe": {
"timeoutSeconds": 3,
"initialDelaySeconds": 3,
"httpGet": {
"path": "/hello",
"port": 8080
}
},
"livenessProbe": {
"timeoutSeconds": 3,
"initialDelaySeconds": 30,
"httpGet": {
"path": "/hello",
"port": 8080
}
}
}
],
"volumes": [
{
"name": "db",
"glusterfs": {
"endpoints": "heketi-storage-endpoints",
"path": "heketidbstorage"
}
},
{
"name": "heketi-db-secret",
"secret": {
"secretName": "heketi-db-backup"
}
},
{
"name": "config",
"secret": {
"secretName": "heketi-config-secret"
}
}
]
}
}
}
}
]
}Lanciamo il deploy:
$ kubectl create -f heketi-deployment.json
service "heketi" created
deployment "heketi" createdRicreiamoci la regola di forward per testare che heketi funzioni
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
glusterfs-296z2 1/1 Running 0 2h
glusterfs-4cd5v 1/1 Running 0 2h
glusterfs-pgvgt 1/1 Running 0 2h
heketi-84684b87c-hqgld 1/1 Running 0 24s
$ kubectl port-forward heketi-84684b87c-hqgld :8080
Forwarding from 127.0.0.1:49337 -> 8080
$ export HEKETI_CLI_SERVER=http://localhost:49337Verifichiamo il cluster:
$ ./heketi-cli cluster list
Clusters:
Id:9ed46f67a517d5e46b9245e1214c2c03 [file][block]
$ ./heketi-cli volume list
Id:e38d822489a5a5401f30c41d3c24fb8c Cluster:9ed46f67a517d5e46b9245e1214c2c03 Name:heketidbstorageCreiamo una Storage Classes
Per prima cosa dobbiamo scoprire qual'è il clusterip del pod heketi:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
heketi ClusterIP 10.43.105.131 <none> 8080/TCP 9m
heketi-storage-endpoints ClusterIP 10.43.124.82 <none> 1/TCP 14m
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 1dNel mio caso 10.43.105.131. Creiamo il file heketi-storageclasses.yaml
apiVersion: storage.k8s.io/v1beta1
kind: StorageClass
metadata:
name: gluster-heketi
provisioner: kubernetes.io/glusterfs
parameters:
resturl: "http://10.43.105.131:8080"
restuser: "joe" # non servono nell'esempio non essendoci autenticazione
restuserkey: "My Secret Life" # non servono nell'esempio non essendoci autenticazioneE creiamola con:
$ kubectl create -f heketi-storageclasses.yaml
storageclass "gluster-heketi" createdRendiamola default per tutti i prossimi deployment che utilizzando persistent volume claim:
$ kubectl patch storageclass gluster-heketi -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass "gluster-heketi" patchedPVC di test!
Creiamo finalmente il nostro persistent volume claim di test, chiamiamolo test-pvc.yml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gluster1
annotations:
volume.beta.kubernetes.io/storage-class: gluster-heketi
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2GiOra arriva il momento della verità, la richiesta di creare un persistent volume al volo a heketi.
$ kubectl create -f test-pvc.yaml
persistentvolumeclaim "gluster1" createdVerifichiamo:
$ kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc/gluster1 Bound pvc-9fd5feda-3725-11e8-902a-02611b0ceca9 2Gi RWX gluster-heketi 25s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv/pvc-9fd5feda-3725-11e8-902a-02611b0ceca9 2Gi RWX Delete Bound default/gluster1 gluster-heketi 19sYEAH ci siamo riusciti! Ora non dovremo più preoccuparci di dover creare manualmente i pv all'interno di kubernetes!
$ cat ~/archivio/kubernetes-heketi.md
WARNING: questo file è del 2018.
L'ho scritto io, ma un io di qualche anno fa: potrebbe non rispecchiare più quello che penso né quello che so fare.
Continuare? [s/N]