Cette partie va consister à migrer mon serveur Nextcloud existant vers le cluster Kubernetes (k8s).
La première chose à vérifier : Existe-t-il un container officiel Nextcloud pour les plateformes “Arm64” ?
Le dépôt existe bien, maintenu par la communauté Nextcloud (officiel) et disponible pour Arm64 (Docker Registry).
Contexte et Architecture
Architecture
- Cluster Kubernetes (ARM64)
- Serveur GlusterFS (ARM64)
- Serveur de Base de données Postgres (ARM64)
Contexte
J’ai déjà une installation nextcloud sur mon Datacenter, répartie sur un serveur Postgres, un serveur web Apache/PhP et les données NextCloud sur un disque SSD.
Les données Nextcloud seront migrées sur le serveur GlusterFS (voir installer un serveur GlusterFS. Les données Postgres restent sur le même serveur :
Il est impératif de faire une sauvegarde de tout l’ensemble Nextcloud avant de migrer vers Kubernetes.
Préparation du déploiement
Nous allons préparer le déploiement de Nextcloud dans Kubernetes fonction des spécifications fournies par la documentation relative au conteneur “Nextcloud-Docker”. Rappel : je pars d’une configuration de production déjà existante
A l’heure où j’écris cette documentation, les spécifications de “volumes” pour Nextcloud sont :
- /var/www/html Main folder, needed for updating
- /var/www/html/custom_apps installed / modified apps
- /var/www/html/config local configuration
- /var/www/html/data the actual data of your Nextcloud
- /var/www/html/themes/<YOUR_CUSTOM_THEME> theming/branding
Mon service est concerné par : config , custom_apps et data, je n’utilise pas de thèmes spécifiques.
Préparation des volumes
Serveur GlusterFS
je vais donc créer trois volumes GlusterFS :
- nextcloud-config
- nextcloud-data
- nextcloud-apps
Sur le serveur GlusterFS, mes volumes seront stockés sous “/Disk1/gfs-nextcloud”, je créé et démarre les volumes gluster nécessaires :
BASEGFS="/Disk1/gfs-nextcloud"
mkdir $BASEGFS
VOLUMES="config data apps"
for VOLUME in $VOLUMES
do
# Create dir
mkdir $BASEGFS/$VOLUME
# Create volume
gluster volume create nextcloud-$VOLUME $(hostname):/$BASEGFS/$VOLUME
# Start volume
gluster volume start nextcloud-$VOLUME
done
Firewall du serveur Gluster : Ajouter un volume au domaine Gluster, veut aussi dire modifier les règles de firewall du “Gluster Server” (1 volume exporté = 1 nouveau port), voir la doc installer un serveur GlusterFS
Serveur Nextcloud (celui qui dispose des données à migrer)
Je monte les volumes gluster, ceci permet de vérifier le bon fonctionnement du serveur Gluster :
mount -t glusterfs [gluster host]:/[volume name]
, par exemple :
# Mount du volume nextcloud-config sur /mnt du serveur Nextcloud, vous devrez peut etre au préalable, installer le client gluster : `apt -y install glusterfs-client`
mount -t glusterfs gluster.mydomaine:/nextcloud-config /mnt
# Pour démonter
umount /mnt
Une fois monté, et selon le volume, copiez le contenu nécessaire, répéter l’opération pour chacun des volume :
- le contenu de “config” de l’installation nextcloud existante dans le volume Gluster : nextcloud-config
- le contenu de “data” de l’installation nextcloud existante dans le volume Gluster : nextcloud-data
- le contenu de “apps” de l’installation nextcloud existante, sans les apps livrées par défaut, dans le volume Gluster : nextcloud-apps
ATTENTION pour le volume apps : Voici la liste des apps nextcloud (core) présentes par défaut :
LIST="accessibility activity admin_audit cloud_federation_api comments contactsinteraction dashboard dav encryption federatedfilesharing federation files files_external files_pdfviewer files_rightclick files_sharing files_trashbin files_versions files_videoplayer firstrunwizard logreader lookup_server_connector nextcloud_announcements notifications oauth2 password_policy photos privacy provisioning_api recommendations serverinfo settings sharebymail support survey_client systemtags text theming twofactor_backupcodes updatenotification user_ldap user_status viewer weather_status workflowengine"
ATTENTION Cette liste peut changer à tous moments selon la version que vous migrez… Le plus simple est de copier le contenu de apps dans le volume “nextcloud-apps” et de supprimer les répertoires mentionnés ci-avant.
LIST="[valeur précédemment citée"
cd [Votre volume gluster]
for rep in $LIST
do
rm -rf "$rep"
done
ATTENTION Si vous copier les données directement dans le répertoire du serveur Gluster, sans passer par un client Gluster, vous serez confrontés à un problème de synchronisation entre ce qui est disposé sur le serveur gluster et ce que “voit” les clients. Voir la solution à la fin de cette procédure
Automatisation
Votre service nextcloud actif est installé dans “/var/www/nextcloud”, le noms des volumes Gluster est conforme à ceux précités, rsync et le client glusterfs sont installés :
Sur le serveur nextcloud actif Nous allons procéder au transfert des données active vers le serveur GlusterFS :
# Arret du service
systemctl stop apache2
NEXTCLOUDPLACE="/var/www/nextcloud"
HOSTNAMEGLUSTER="gluster.server" # ou adresse IP du serveur gluster
VOLUMES="config data apps"
# Pour etre sûr
umount /mnt
for V in $VOLUMES
do
mount -t glusterfs "$HOSTNAMEGLUSTER:/nextcloud-$V /mnt
rsync -ravH "$NEXTCLOUDPLACE/$V/ /mnt/
## Suppression des répertoires apps "non custom"
if [ "$V" == "apps" ];then
LIST="accessibility activity admin_audit cloud_federation_api comments contactsinteraction dashboard dav encryption federatedfilesharing federation files files_external files_pdfviewer files_rightclick files_sharing files_trashbin files_versions files_videoplayer firstrunwizard logreader lookup_server_connector nextcloud_announcements notifications oauth2 password_policy photos privacy provisioning_api recommendations serverinfo settings sharebymail support survey_client systemtags text theming twofactor_backupcodes updatenotification user_ldap user_status viewer weather_status workflowengine"
for rep in $LIST
do
rm -rf "/mnt/$rep"
done
fi
umount /mnt
done
La durée de l’opération dépend de la volumétrie à transférer
Ajustement de la configuration Nextcloud
La configuration de nextcloud “config.php” de l’installation existante est maintenant copiée dans le volume GlusterFs “nextcloud-config”. Editer ce fichier en remplaçant la valeur de la propriété ‘datadirectory’ par “/var/www/html/data” qui est la configuration standard de l’image Docker fabriquée par Nextcloud :
<?php
$CONFIG = array (
[...]
'datadirectory' => "/var/www/html/data"
[...]
Base de données
Rien à faire dans mon cas, la base reste en place sur le serveur de base de données actuel. Je rappelle que mon serveur Nextcloud n’héberge pas la base de données “postgres”, elle est déjà disposé sur un autre serveur.
Préparation du serveur de base de données Postgres
Le firewall du serveur et le fichier pg_hba.conf doivent autoriser les accès à tous les “Workers” du cluster Kubernetes. Pour postgres il s’agit généralement du port 5432 qui doit diposer d’une règle : INPUT Allow 5432/TCP (toutes les adresse IP des workers). Pour le pg_hba.conf, vous avez limité l’accès à la base Nextcloud à une seule machine, pour indiquer plusieurs machines agir sur le masque de sous-réseau (subnet.ninja-subnet-cheat-sheet), ou bien ajouter autant de lignes que nécessaire :
- un réseau complet - - Attention ceci élargit la surface d’attaque
host nextcloud nextcloud 172.28.4.0/24 md5
ou bien autant de lignes que de hosts autorisés à joindre la base de données (meilleur choix), exemple ici avec 3 workers
[...]
host nextcloud nextcloud 172.28.4.72/32 md5
host nextcloud nextcloud 172.28.4.73/32 md5
host nextcloud nextcloud 172.28.4.74/32 md5
[...]
Pour appliquer les modifications, ne redémarrez pas le service Postgres (restart) pour ne pas “couper la production”, utilisez le paramètre “reload” : … systemctl reload postgresql …
Création du manifest
Le manifest sera composé de :
- 1 namespace : nextcloud
- 1 service gluster
- 1 endpoint gluster
- 3 Volumes persistants : Il s’agit des accès à nos 3 volumes Glusterfs
- 1 deployment
- 1 service réseau permettant l’accès à l’application nextcloud de type : NodePort
Fichier à créer : “manifest-nextcloud.yaml” sur le master (commande kubectl), Adapter les valeurs :
- [adresse IP du serveur glusterfs]
- [Nom du volume gluster fs pour les données nextcloud]
- [Nom du volume gluster fs pour les apps nextcloud]
- [Nom du volume gluster fs pour la config nextcloud]
apiVersion: "v1"
kind: "Namespace"
metadata:
name: "nextcloud"
labels:
name: "nextcloud"
---
apiVersion: "v1"
kind: "Service"
metadata:
name: "glusterfs-cluster"
namespace: "nextcloud"
spec:
ports:
- port: 1
---
apiVersion: v1
kind: "Endpoints"
metadata:
name: "glusterfs-cluster"
namespace: "nextcloud"
subsets:
- addresses:
- ip: [adresse IP du serveur glusterfs]
ports:
- port: 1
---
apiVersion: "v1"
kind: "PersistentVolume"
metadata:
name: "gluster-pv-[Nom du volume gluster fs pour les données nextcloud]"
namespace: "nextcloud"
spec:
capacity:
storage: "400Gi"
accessModes:
- "ReadWriteMany"
storageClassName: "[Nom du volume gluster fs pour les données nextcloud]"
persistentVolumeReclaimPolicy: "Recycle"
glusterfs:
endpoints: "glusterfs-cluster"
path: "[Nom du volume gluster fs pour les données nextcloud]"
readOnly: false
---
apiVersion: "v1"
kind: "PersistentVolume"
metadata:
name: "gluster-pv-[Nom du volume gluster fs pour les apps nextcloud]"
namespace: "nextcloud"
spec:
capacity:
storage: "1Gi"
accessModes:
- "ReadWriteMany"
storageClassName: "[Nom du volume gluster fs pour les apps nextcloud]"
persistentVolumeReclaimPolicy: "Recycle"
glusterfs:
endpoints: "glusterfs-cluster"
path: "[Nom du volume gluster fs pour les apps nextcloud]"
readOnly: false
---
apiVersion: "v1"
kind: "PersistentVolume"
metadata:
name: "gluster-pv-[Nom du volume gluster fs pour la config nextcloud]"
namespace: "nextcloud"
spec:
capacity:
storage: "4k"
accessModes:
- "ReadWriteMany"
storageClassName: "[Nom du volume gluster fs pour la config nextcloud]"
persistentVolumeReclaimPolicy: "Recycle"
glusterfs:
endpoints: "glusterfs-cluster"
path: "[Nom du volume gluster fs pour la config nextcloud]"
readOnly: false
---
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
name: "glusterfs-claim-[Nom du volume gluster fs pour les données nextcloud]"
namespace: "nextcloud"
spec:
accessModes:
- "ReadWriteMany"
storageClassName: "[Nom du volume gluster fs pour les données nextcloud]"
resources:
requests:
storage: "400Gi"
---
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
name: "glusterfs-claim-[Nom du volume gluster fs pour les apps nextcloud]"
namespace: "nextcloud"
spec:
accessModes:
- "ReadWriteMany"
storageClassName: "[Nom du volume gluster fs pour les apps nextcloud]"
resources:
requests:
storage: "1Gi"
---
apiVersion: "v1"
kind: "PersistentVolumeClaim"
metadata:
name: "glusterfs-claim-[Nom du volume gluster fs pour la config nextcloud]"
namespace: "nextcloud"
spec:
accessModes:
- "ReadWriteMany"
storageClassName: "[Nom du volume gluster fs pour la config nextcloud]"
resources:
requests:
storage: "4k"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: "nextcloud"
namespace: "nextcloud"
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: nextcloud
replicas: 1
template:
metadata:
labels:
app: nextcloud
spec:
containers:
- name: nextcloud
image: nextcloud
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/var/www/html/config"
name: glusterfsvol-config
- mountPath: "/var/www/html/data"
name: glusterfsvol-data
- mountPath: "/var/www/html/custom_apps"
name: glusterfsvol-apps
volumes:
- name: glusterfsvol-config
persistentVolumeClaim:
claimName: glusterfs-claim-[Nom du volume gluster fs pour la config nextcloud]
- name: glusterfsvol-data
persistentVolumeClaim:
claimName: glusterfs-claim-[Nom du volume gluster fs pour les données nextcloud]
- name: glusterfsvol-apps
persistentVolumeClaim:
claimName: glusterfs-claim-[Nom du volume gluster fs pour les apps nextcloud]
---
apiVersion: v1
kind: Service
metadata:
name: "nextcloud"
namespace: "nextcloud"
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: "nextcloud"
Appliquer le manifest
kubectl apply -f nextcloud-deployment.yaml
# deployment.apps/nextcloud created
Vérifiez que le container démarre : (Je vous conseille de relire la documentation d’installation d’un cluster si vous ne disposez pas de la complétion automatique “kubectl”
kubectl get pod -n nextcloud <touche tab>
# La fin du log indique l'état du pod, attendre quelques minutes pour un démarrage complet...
# répéter la commande précédente, jusqu'au démarrage complet : READY 1/1
NAME READY STATUS RESTARTS AGE
nextcloud-6759b5df47-blqbk 1/1 Running 0 5d23h
Exposition
Notez que le manifest inclut le “service nextcloud” et permet d’exposer cette application à l’extérieur du cluster (type NodePort).
Pour connaître ce port :
kubectl get service -n nextcloud nextcloud
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nextcloud NodePort 10.107.203.125 <none> 80:31812/TCP 63d
Pour atteindre ce nouveau service voir la procédure Exposer les services avec un serveur HAPROXY
Mettre à l’échelle NextCloud (Horizontal scaling)
Concernant l’étape de mise à l’échelle (ici horizontal scaling, c’est à dire deux serveurs web qui utilisent les mêmes ressources), il faut un service Redis et apporter quelques modifications à la configuration du “deployment K8s”. Nextcloud à choisi de stocker les sessions dans un service Redis, plus rapide qu’une implémentation dans la base de données Postgres.
Installation d’un serveur Redis
J’ai choisi de l’installer sur le serveur de base de données, peu chargé, voir la documentation:
Mise en place de deux replicas sur deux workers différents
Ici, je vais ajouter la notion de replicas, c’est à dire le nombre de pods nécessaire à la bonne exécution de votre plan. Pour ma part, je vais répartir sur deux workers. On va donc aussi voir la notion de NodeSelector.
Pour appliquer ces modifications :
- Vous repartez du manifest créé précédemment puis exécuter la commande :
kubectl apply -f nextcloud-deployment.yaml
OU bien vous modifiez directement le “deployment” avec le paramètre “edit” de kubectl :
kubectl edit deployments.apps -n nextcloud nextcloud
[ESC:wq]
# Kube applique les modifications immédiatement ou bien renvoie une erreur
Notion de replicas
Fichier manifest : node /spec (root), ajoutez “replicas:[nombre de replique]”
[...]
spec:
replicas: 2
[...]
Notion affinity (NodeSelector)
Attribut node spec/template/spec ajout de l’affinité qui permet de démarrer un pod sur un serveur déjà ‘équipé’ du container démarré :
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nextcloud
topologyKey: kubernetes.io/hostname
[...]
Partie Nexcloud
Ces informations sont issues de la documentation Nextcloud. Pour démarrer un container Nextcloud devant utiliser un stockage des sessions, Nextcloud utilise le service Redis. Vous devez donc fournir, au moyens de variables d’environnement, les informations nécessaires à la connexion au service Redis. Par défaut, Nextcloud stocke les sessions sous forme de fichiers et localement (donc dans chaque conteneur). Quand vous souhaitez mettre à l’échelle, nextcloud utilise Redis comme seul point de stockage des sessions. Ceci permet de rester authentifié lorsque le load balancer décide de vous diriger vers un serveur, autre que celui qui vous a fournit la session d’authentification.
Fichier manifest : node spec/template/spec/containers
[...]
containers:
- env:
- name: REDIS_HOST
value: [adresse IP ou FDQN du serveur Redis]
- name: REDIS_HOST_PORT
value: "6379"
- name: REDIS_HOST_PASSWORD
value: [Mot de passe d'accès au service Redis]
[...]
Attention j’ai fait simple : la bonne pratique empêche le stockage de mots de passe en “clair” dans les configurations, pour rendre la configuration plus sécurisée, voir la notion de “secrets Kubernetes”.
Après application des modifications, les deux pods démarrent bien sur deux workers différents, le serveur Haproxy connait déjà les deux nodes, il ne reste plus qu’à vérifier, sur le serveur Haproxy, que le traffic passe bien sur les deux serveurs (ma config haproxy spécifie un équilibrage de type “roundrobin”).
Niveau de version de l’image Docker nextcloud
Si vous scaler horizontalement cette application, changer le nom de l’image Docker, en indiquant un numéro de version précis, par exemple : nextcloud:20.0.7. La configuration que j’ai donné ci-avant, permet de vérifier qu’il n’existe pas une version plus récente dans le dépôt Docker Hub. C’est pratique quand vous disposez d’un seul POD. Lorsque nextcloud prévient d’une évolution de version, il suffit de stopper le POD, K8s le redémarre automatiquement avec la nouvelle version de Nextcloud.
Lors du redémarrage d’un des conteneurs nextcloud, le serveur worker ira chercher la dernière version disponible sur le repository DockerHub, et dans le cas où vous avez des réplicas, les autres containers continueront d’utiliser l’ancienne version.
Vous serez très rapidement confrontés à un conflit de version entre les conteneurs en cours d’exécution, avec une demande d’évolution d’un côté, puis d’un “downgrade” de l’autre, juste après que le nouveau conteneur a exécuté “l’upgrade” !!!
Problèmes rencontrés
Glusterfs
- Le point de montage gluster ne fait apparaître qu’une partie des fichiers attendus : vous n’avez pas copié les fichiers au travers d’un client Glusterfs La solution est ici