Pourquoi déployer XWiki sur Kubernetes ?
Vous le savez surement maintenant vu le nombre d’article que j’ai écrit sur le sujet, j’aime bien XWiki. C’est un super projet mené par des gens passionnés, réactifs et surtout, c’est cet outil qui a permis à mon entreprise d’adopter ENFIN le wiki comme outil de documentation, que ce soit pour documenter les procédures, mais aussi documenter entièrement les concepts de l’outil que nous développons.
D’un outil quasi-confidentiel utilisés par quelques admins peu motivés (sous Mediawiki), nous sommes donc progressivement passés à un outil utilisé par toutes les équipes, du dev. au support en passant par les ergo. et les CPs, utilisés par plusieurs entités internes et même accepté comme document contractuel par nos prestataires. J’ai d’ailleurs documenté la migration sur cet article.
Mais j’aime aussi XWiki car c’est selon moi l’exemple parfait d’une appli 2 ou 3 tiers relativement simple pour explorer l’écosystème Docker (construction des images, les problématiques réseau et stockage, …). Je joue régulièrement avec ma propre image sur le Dockerhub pour explorer un peu plus cet écosystème riche (c’est rien de le dire).
Quelle meilleure application donc pour réellement tester mon tout nouveau cluster Kubernetes (je ne compte pas nginx ou helloworld) ?
Kubernetes ?
Pour ceux qui ont besoin d’un rappel sur Kubernetes ou des premiers concepts et/ou comment l’installer, je vous invite à lire/relire mon article précédent sur le sujet.
A noter également (j’ai pas mal communiqué là-dessus sur les réseaux sociaux), la Linux Foundation, qui gère également la Cloud Native Computing Foundation (et qui elle-même gère Kubernetes), a mis à disposition gratuitement un cour en ligne de type MOOC sur edX. Vous pouvez même vous « certifier » à la fin si vous réussissez le QCM, moyennant 100$.
A l’issue de ces lectures, vous devriez donc avoir :
- la connaissance des différents concepts de Kubernetes
- un cluster Kubernetes opérationnel (une ou plusieurs machines, ou simplement un minikube en local)
Ce sont les prérequis pour la suite de cet article, même si je vais essayer de détailler tout ce qui va suivre.
Plan de bataille
Je ne vais pas vous le cacher, si on veut tout faire soit même, ce n’est pas « trivial ». Mais en décomposant toutes les étapes, vous allez voir que c’est logique.
Pour faire tourner mon image Docker zwindler/xwiki-tomcat8, j’ai besoin, sur mon cluster Kubernetes, des choses suivantes :
- un Deployment qui exécute la partie PostgreSQL. Ce Deployment contiendra :
- un ReplicaSet avec une seule instance car on ne souhaite avoir qu’un seul Pod avec l’image postgresql officielle
- Un Service de type ClusterIP pour lui permettre de communiquer avec le XWiki mais pas avec le monde extérieur
- Un PersistantVolumeClaim et son PersistentVolume associé pour stocker les données de la base
- Des Secrets pour permet de configurer les mots de passes de postgres et de l’utilisateur XWiki
- un Deployment qui exécute la partie XWiki. Ce Deployment contiendra :
- un ReplicaSet avec une seule instance car on ne fait tourner qu’un seul Pod avec l’image zwindler/xwiki-tomcat8, car on n’utilise qu’un tomcat à la fois pour exécuter XWiki
- Un Service de type NodePort (ou mieux si vous avez des LoadBalancers ou des Ingress à dispo.) qui permettra d’exposer le port interne au cluster Kubernetes vers le monde extérieur
- Un PersistantVolumeClaim avec le PersistentVolume pour stocker les pièces jointes car mon image Docker utilise l’option filesystem_store pour éviter de surcharger la base pour les PJ
- et aussi le Secret défini précédemment pour accéder à la BDD.
OK.
Voir ça posé dans une liste comme ça, ça peut faire peur.
Alors vous allez me dire : « c’est plus facile de lancer ton DockerCompose » (car j’ai montré qu’avec DockerCompose on peut démarrer une instance XWiki en 2 ou 3 lignes de commandes). Mais au final, tout ce que j’ai écris ici peut être concaténé dans un seul et même fichier YAML et le résultat est aussi simple dans l’absolu.
Créer les Secrets
Clairement, c’est un des gros points forts par rapport à la solution plus simple avec juste Docker / DockerCompose : la gestion des Secrets (et des configurations, dans une moindre mesure).
Ici, plutôt que d’avoir un mot de passe en clair dans mon fichier compose, le mot de passe est renseigné à part, soit via un fichier, soit via l’API. Et ces mots de passes ne pourront plus être consultés via la CLI, mais pourront être utilisés.
Pour créer un secret dans Kubernetes en CLI, il existe plusieurs méthodes. Soit on utilise la CLI directement depuis le prompt ou depuis un fichier plat (qui va se charger d’encoder le secret en base64 et de générer le YAML pour nous), soit on décide de créer le YAML nous-même. Je vous met les deux méthodes :
echo -n "xwikidb" > ./POSTGRES_USER.txt
echo -n "xwikipwd" > ./POSTGRES_PASSWORD.txt
kubectl create secret generic postgresql-xwiki-user-password --from-file=POSTGRES_USER.txt --from-file=POSTGRES_PASSWORD.txt
secret "postgresql-xwiki-user-password" created
#ou
cat > postgresql_xwiki_user_password.yaml << EOF
apiVersion: v1
kind: Secret
metadata:
name: postgresql-xwiki-user-password
type: Opaque
data:
username: $(cat POSTGRES_USER.txt| base64 -w 0)
password: $(cat POSTGRES_PASSWORD.txt| base64 -w 0)
EOF
Au final, pour un déploiement unitaire pour vous même, ça ne change pas grand-chose bien sûr. Vous avez renseigné en clair le mot de passe (ou en base64 mais c’est pareil) à un moment donné.
Cependant, dans le cadre d’un gros projet avec des développeurs qui poussent du code régulièrement d’un côté et des admins qui gèrent de l’autre les utilisateurs et les mots de passes, on peut permettre aux développeurs consommer les mots de passe sans jamais avoir à les connaitre (et laisser trainer sur Gitlab, ou pire, Github !).
kubectl describe secret postgresql-xwiki-user-password
Name: postgresql-xwiki-user-password
Namespace: default
Labels: <none>
Annotations:
Type: Opaque
Data
====
password: 8 bytes
username: 7 bytes
Créer les Persistent Volumes
Dans mon infrastructure, j’ai déjà un cluster GlusterFS. GlusterFS étant un SDS, il est particulièrement indiqué comme stockage persistant dans le cadre d’un cluster Kubernetes (comme Ceph, Cinder, S3 ou autre). Ces types de méthodes de stockage peuvent facilement se piloter par API et sont hautement disponibles.
Création de l’endpoint GlusterFS
cat > gluster-endpoints.yaml <<EOF
apiVersion: v1
kind: Endpoints
metadata:
name: gluster-cluster
subsets:
- addresses:
- ip: 10.0.0.1
ports:
- port: 1
protocol: TCP
- addresses:
- ip: 10.0.0.2
ports:
- port: 1
protocol: TCP
EOF
kubectl apply -f gluster-endpoints.yaml
endpoints "gluster-cluster" created
kubectl get endpoints gluster-cluster
NAME ENDPOINTS AGE
gluster-cluster 10.0.0.1:1,10.0.0.2:1 39s
cat > gluster-service.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: gluster-cluster
spec:
ports:
- port: 1
EOF
kubectl apply -f gluster-service.yaml
service "gluster-cluster" created
kubectl get service gluster-cluster
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gluster-cluster 10.96.114.181 <none> 1/TCP 1m
La partie GlusterFS a été configurée côté Kubernetes. On peut maintenant s’atteler à la création des PV et PVC associés. D’abord la base de données :
cat > gluster-pv-xwiki-pg.yaml << EOF
apiVersion: v1
kind: PersistentVolume
metadata:
name: gluster-pv-xwiki-pg
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
glusterfs:
endpoints: gluster-cluster
path: /xwiki-pg-storage
readOnly: false
persistentVolumeReclaimPolicy: Retain
EOF
kubectl create -f gluster-pv-xwiki-pg.yaml
persistentvolume "gluster-pv-xwiki-pg" created
cat > gluster-pvc-xwiki-pg.yaml << EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gluster-pvc-xwiki-pg
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
EOF
kubectl create -f gluster-pvc-xwiki-pg.yaml
persistentvolumeclaim "gluster-pvc-xwiki-pg" created
Et maintenant l’application XWiki
cat > gluster-pv-xwiki-app.yaml << EOF
apiVersion: v1
kind: PersistentVolume
metadata:
name: gluster-pv-xwiki-app
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
glusterfs:
endpoints: gluster-cluster
path: /xwiki-app-storage
readOnly: false
persistentVolumeReclaimPolicy: Retain
EOF
kubectl create -f gluster-pv-xwiki-app.yaml
persistentvolume "gluster-pv-xwiki-app" created
cat > gluster-pvc-xwiki-app.yaml << EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gluster-pvc-xwiki-app
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
EOF
kubectl create -f gluster-pvc-xwiki-app.yaml
persistentvolumeclaim "gluster-pvc-xwiki-app" created
Dans le cadre d’un test, vous pouvez vous contenter de faire simplement appel à un partage NFS ou même à du stockage local (si vous n’avez qu’un worker) mais sachez que vous n’aurez pas le même niveau de résilience ni les même facilités d’administration.
Si vous voulez un exemple de PersistentVolume utilisant NFS, je vous conseille cet article qui traite d’un sujet peu plus complexe mais qui donne les étapes de base pour mettre en place le PV et PVC : Créer un cluster PostgreSQL avec un seul et même Deployment en utilisant les StatefulSets.
Créer le Deployment postgreSQL
Voilà, on a tous les prérequis ! Il ne reste plus qu’à créer dans un premier temps notre base de données, puis notre XWiki. Comme indiqué au début, le Deployment contiendra tous les éléments qu’on a créé plus tôt, dont les Secrets et le Persistent Volume. Il manquera aussi un Service, que nous verrons plus tard.
cat > xwiki-pg.yaml << EOF
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: xwiki-pg
labels:
app: xwiki-pg
application: xwiki
spec:
replicas: 1
selector:
matchLabels:
app: xwiki-pg
application: xwiki
template:
metadata:
labels:
app: xwiki-pg
application: xwiki
name: xwiki-pg
spec:
containers:
- image: postgres:9.6
name: xwiki-pg
env:
- name: POSTGRES_DB
value: xwiki
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgresql-xwiki-user-password
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-xwiki-user-password
key: password
ports:
- containerPort: 5432
name: xwiki-pg
volumeMounts:
- name: xwiki-pg-storage
mountPath: /var/lib/postgresql/data/pgdata
volumes:
- name: xwiki-pg-storage
persistentVolumeClaim:
claimName: gluster-pvc-xwiki-pg
EOF
kubectl create -f xwiki-pg.yaml
Normalement, un Pod devrait se créer et monter le disque Gluster, puis s’initialiser avec les variables d’environnement qu’on lui a donné.
A partir de là, on ne peut cependant pas accéder à la base de données car on a pas encore créé le Service associé au Deployment. Par défaut, on ne donne pas de type, c’est donc un ClusterIP. Pour « savoir » vers quoi le Service doit rediriger, on a ajouté un Selector, qui pointe sur le tag « app » avec la valeur « xwiki-pg » définie plus haut.
cat > xwiki-pg-svc.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: xwiki-pg-svc
labels:
app: xwiki-pg
spec:
ports:
- port: 5432
protocol: TCP
selector:
app: xwiki-pg
EOF
kubectl create -f xwiki-pg-svc.yaml
service "xwiki-pg-svc" created
Créer le Deployment XWiki
Youhou ! Plus qu’un ! Ici pas de grosses différences, le principe est le même. On doit créer un Deployment puis un Service.
cat xwiki-tomcat.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: xwiki-app
labels:
app: xwiki-app
application: xwiki
spec:
replicas: 1
selector:
matchLabels:
app: xwiki-app
application: xwiki
template:
metadata:
labels:
app: xwiki-app
application: xwiki
name: xwiki-app
spec:
containers:
- image: zwindler/xwiki-tomcat8
name: xwiki-app
env:
- name: POSTGRES_INSTANCE
value: xwiki-pg
- name: POSTGRES_DB
value: xwiki
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgresql-xwiki-user-password
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-xwiki-user-password
key: password
ports:
- containerPort: 8080
name: tomcat-port
volumeMounts:
- name: xwiki-app-storage
mountPath: /usr/local/tomcat/work/xwiki
volumes:
- name: xwiki-app-storage
persistentVolumeClaim:
claimName: gluster-pvc-xwiki-app
kubectl apply -f xwiki-tomcat.yaml
deployment "xwiki-app" created
cat xwiki-tomcat-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: xwiki-app-svc
labels:
apps: xwiki-app
spec:
type: NodePort
ports:
- port: 8080
targetPort: tomcat-port
protocol: TCP
selector:
app: xwiki-app
kubectl apply -f xwiki-tomcat-svc.yaml
service "xwiki-app-svc" created
Est-ce que ça marche ?
kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
gluster-cluster 10.96.114.181 <none> 1/TCP 4h
kubernetes 10.96.0.1 <none> 443/TCP 10d
xwiki-app-svc 10.103.218.140 <nodes> 8080:31484/TCP 4m
xwiki-pg-svc 10.108.219.88 <none> 5432/TCP 16m
Tout d’un coup
Chose promise chose due ! Je vous ai dit en début d’article que vous pouviez tout déployer en quelques lignes de commandes. Dans l’absolu, vous pourriez tout déployer via un seul fichier. Je préfère découpler la partie stockage (qui va dépendre du stockage que vous allez utiliser) et la partie secret du reste pour les raisons que j’ai énoncé plus haut. Voilà ce que ça donne :
On récupère les sources sur github
git clone https://github.com/zwindler/docker-xwiki
A adapter en fonction de votre contexte, à minima les adresses IP si vous avez du gluster, et carrément à réécrire si vous utilisez autre chose (NFS ou CEPH par exemple) :
kubectl apply -f docker-xwiki/kubernetes/gluster-endpoints-service.yaml
endpoints "gluster-cluster" created
service "gluster-cluster" created
On injecte ensuite les secrets. A vous de les changer comme on a vu plus haut.
kubectl apply -f docker-xwiki/kubernetes/postgresql_xwiki_user_password.yaml
secret "postgresql-xwiki-user-password" created
Et on fini par… tout le reste ! Là normalement il n’y a rien à changer à part éventuellement des ports, les seules « variables » dans ce déploiement étant le stockage et les mots de passes (qu’on vient de gérer).
kubectl apply -f docker-xwiki/kubernetes/xwiki-tomcat-pg-allinone.yaml
persistentvolume "gluster-pv-xwiki-pg" created
persistentvolume "gluster-pv-xwiki-app" created
deployment "xwiki-pg" created
persistentvolumeclaim "gluster-pvc-xwiki-pg" created
service "xwiki-pg-svc" created
deployment "xwiki-app" created
persistentvolumeclaim "gluster-pvc-xwiki-app" created
service "xwiki-app-svc" created
Le mot de la fin
Voilà, avec ce tutoriel vous avez maintenant une vue détaillée, étape par étape, de ce qu’il faut mettre en place pour gérer « proprement » le déploiement d’une application java + postgreSQL via Kubernetes.
Clairement, c’est bien plus complexe qu’un simple Docker compose comme j’ai pu le présenté dans l’article sur ce sujet. Pour autant, il faut comparer ce qui est comparable, les deux installations n’ont rien à voir !
D’un côté, vous avez une application déployée sur un nœud docker simple (éventuellement avec Swarm maintenant que les deux sont compatibles).
De l’autre, vous avez une application qui est déployée sur un cluster de machines :
- Si l’une tombe en panne, la haute disponibilité est gérée : pas seulement sur le runtime, mais aussi sur le stockage !
- Vous pouvez segmenter votre cluster en namespaces, et donner des droits à certains namespace à certains utilisateurs avec un gestion fine des autorisations (RBAC)
- Vous disposez d’un dashboard et d’une API pour automatiser tous les déploiements (via Jenkins par exemple). Si vous voulez faire une mise à jour ou besoin de modifier un paramètre, c’est une simple commande ou un fichier de conf à appliquer à nouveau.
- Vous avez la possibilité de faire du scaling up/down, voire de l’autoscaling.
- …
La liste est longue ! En bref, vous passez d’une bidouille à la production.