Déploiement Kubernetes
Cette page décrit le déploiement de FoxPlan sur un cluster Kubernetes avec son chart Helm. Toute la configuration passe par un fichier de valeurs : les modèles du chart n'ont jamais à être modifiés.
Pour un serveur unique, sans cluster, voir Déploiement Docker Compose.
Fichiers à télécharger
- foxplan-1.0.0.tgz — le chart Helm
- values.yaml — toutes les valeurs, commentées
curl -O https://app.fox-plan.com/docs/onpremise/kubernetes/foxplan-1.0.0.tgz
curl -o foxplan-values.yaml https://app.fox-plan.com/docs/onpremise/kubernetes/values.yaml
Les images FoxPlan sont publiées sur un registre en accès libre : aucun identifiant à demander, aucun imagePullSecrets à créer. Son adresse vous est communiquée par FoxPlan avec votre licence ; elle se renseigne dans la valeur image.registry.
Architecture
| Composant | Objet Kubernetes | Rôle |
|---|---|---|
| Application | Deployment foxplan-app | Serveur FoxPlan (port 5000), une ou plusieurs répliques |
| Documentation | Deployment foxplan-docs | Cette documentation, servie sous /docs |
| Export des plannings | Deployment foxplan-export | PDF, PNG, Excel, MS Project et Primavera, servis sous /gantt-export |
| MongoDB | StatefulSet foxplan-mongodb | Base de données, un nœud, volume persistant |
| Redis | StatefulSet foxplan-redis | Sessions et cache, volume persistant. Toujours déployé : FoxPlan ne démarre pas sans |
| Garage (option) | StatefulSet foxplan-garage | Stockage S3 des fichiers, si vous n'en avez pas |
| Routage | Ingress foxplan | / → application, /docs → documentation, /gantt-export → export |
Les noms sont préfixés par celui de la release Helm (foxplan dans les exemples). Le pod d'export embarque un petit proxy nginx qui retire le préfixe /gantt-export : le routage fonctionne ainsi avec n'importe quel contrôleur d'Ingress, sans annotation de réécriture propre à l'un d'eux.
Prérequis
- Un cluster Kubernetes 1.27 ou plus récent, et Helm 3.
- Un contrôleur d'Ingress : Traefik, ingress-nginx, HAProxy, celui de votre offre managée…
- Une classe de stockage par défaut (ou une classe désignée dans les valeurs) pour les volumes de MongoDB, de Redis et, le cas échéant, de Garage.
- Au moins un nœud amd64 : les images d'export sont publiées en amd64 uniquement. Les autres images existent aussi en arm64.
- Un nom de domaine qui pointe vers votre contrôleur d'Ingress, et un certificat TLS : délivré par cert-manager, ou fourni par vous dans un Secret.
- Un serveur SMTP : codes d'inscription, invitations et réinitialisations de mot de passe en dépendent. Sans lui, personne ne peut se connecter.
- Pour la mise à l'échelle automatique seulement : metrics-server.
Récupération des images
Les flux réseau sont ceux de l'installation Docker Compose, décrits dans Étape 1 : télécharger les images — flux réseau : le registre FoxPlan, et Docker Hub pour les images tierces. Ils partent des nœuds du cluster, au moment où ils téléchargent les images. Images tierces utilisées par le chart :
| Image | Utilisée |
|---|---|
mongo:8.2.7 | Toujours, sauf MongoDB externe (mongodb.enabled: false) |
redis:7.4 | Toujours |
nginxinc/nginx-unprivileged:1.28-alpine | Proxy du pod d'export |
dxflrs/garage:v2.1.0, curlimages/curl:8.14.1 | Seulement avec garage.enabled: true |
Récupérez les images depuis un environnement indépendant de votre SI, comme décrit pour Docker Compose, puis poussez-les dans votre registre interne. Indiquez-le ensuite dans les valeurs : image.registry pour les images FoxPlan, mongodb.image, redis.image, export.proxy.image, garage.image et garage.initImage pour les autres. Si ce registre exige une authentification, ajoutez son Secret dans imagePullSecrets. Les nœuds n'ont alors plus aucun flux vers Internet.
Ports et SSO
Le SSO d'entreprise n'exige aucun port supplémentaire, qu'il s'agisse de la connexion Microsoft ou Google standard ou d'un SSO personnalisé. Le retour d'une connexion est porté par le navigateur de l'utilisateur vers https://<host>/login/oauth2/code/…, à travers l'Ingress, comme le reste de l'application : le fournisseur d'identité n'ouvre jamais de connexion vers le cluster.
| Sens | Port | Entre | Rôle |
|---|---|---|---|
| Entrant | 443 (TCP) | Postes des utilisateurs → contrôleur d'Ingress | L'application et le retour du SSO |
| Entrant | 80 (TCP) | Postes des utilisateurs → contrôleur d'Ingress | Redirection vers 443 ; challenge HTTP-01 de cert-manager |
| Sortant | 443 (TCP) | Pods foxplan-app → fournisseur d'identité | Échange du jeton, clés de signature, profil — domaines dans la matrice des flux |
N'exposez pas le port 5000 des pods (NodePort, LoadBalancer) : l'Ingress suffit. Si vos NetworkPolicy filtrent les sorties, autorisez les pods foxplan-app vers le fournisseur d'identité, sur 443 ou sur son port propre — Keycloak sur 8443, par exemple.
- SSO standard (Microsoft, Google) : variables passées par
app.extraEnvetapp.extraSecretEnv. L'URL de redirection est construite à partir dehost:https://<host>/login/oauth2/code/azureou…/google, à déclarer à l'identique chez Microsoft Entra ou Google. - SSO personnalisé (Okta, Keycloak, tout fournisseur OIDC, configuré dans SSO entreprise) : l'URL de redirection est reconstruite à partir des en-têtes
X-Forwarded-Proto,X-Forwarded-HostetX-Forwarded-Portposés par le contrôleur d'Ingress. Le bouton Vérifier la configuration affiche l'URL exacte à déclarer.
Si un répartiteur de charge placé devant le contrôleur d'Ingress termine le TLS, le contrôleur reçoit du HTTP et transmet X-Forwarded-Proto: http : FoxPlan calcule alors une URL en http://, que le fournisseur refuse (redirect_uri mismatch). Faites relayer les en-têtes du répartiteur — use-forwarded-headers: "true" dans la ConfigMap d'ingress-nginx, forwardedHeaders.trustedIPs sur le point d'entrée de Traefik — ou laissez le répartiteur en TCP, sans déchiffrer. Le port public doit rester 443.
Dimensionnement
Demandes (requests) et plafonds (limits) par défaut du chart :
| Composant | Processeur demandé | Mémoire demandée | Plafond mémoire | Volume |
|---|---|---|---|---|
| Application (par réplique) | 1 | 4 Gi | 6 Gi | — |
| MongoDB | 0,5 | 2 Gi | 4 Gi | 50 Gi |
| Redis | 0,1 | 256 Mi | 1 Gi | 5 Gi |
| Export (3 conteneurs) | 0,36 | 656 Mi | 2,6 Gi | — |
| Documentation | 0,01 | 32 Mi | 256 Mi | — |
| Garage (option) | 0,1 | 256 Mi | 1 Gi | 1 Gi + 100 Gi |
Repères pour la capacité disponible sur les nœuds :
| Utilisateurs | Processeur | Mémoire | Disque |
|---|---|---|---|
| Jusqu'à 100 | 4 vCPU | 16 Go | 100 Go |
| De 100 à 500 | 8 vCPU | 32 Go, avec 2 répliques de l'application | 200 Go |
| Au-delà | Mise à l'échelle automatique de l'application, MongoDB en replica set |
La JVM de l'application prend 75 % de son plafond mémoire (app.javaOptions) : pour lui donner plus de mémoire, relevez app.resources.limits.memory, sans autre réglage.
Installation
1. Espace de noms et certificat
kubectl create namespace foxplan
Avec cert-manager, rien à créer : l'annotation de l'émetteur, dans les valeurs, suffit (voir l'étape suivante). Sinon, créez le Secret TLS du domaine, chaîne complète comprise :
kubectl -n foxplan create secret tls foxplan-tls --cert=fullchain.pem --key=privkey.pem
2. Valeurs
Partez du values.yaml téléchargé, ou d'un fichier qui ne contient que ce qui change. Le minimum :
# foxplan-values.yaml
image:
registry: <adresse communiquée par FoxPlan>
tag: latest # épinglez la version déployée en production
host: foxplan.mon-entreprise.fr
ingress:
className: traefik # ou nginx, haproxy… vide = classe par défaut du cluster
annotations:
cert-manager.io/cluster-issuer: letsencrypt # si vous utilisez cert-manager
app:
replicas: 1 # nombre d'instances de l'application — voir « Nombre d'instances »
mail:
from: foxplan@mon-entreprise.fr
host: smtp.mon-entreprise.fr
port: 587
username: foxplan@mon-entreprise.fr
password: <mot de passe SMTP>
Sans image.registry, host, mail.from ou mail.host, Helm refuse l'installation avec un message qui nomme la valeur manquante.
ingress-nginx limite par défaut les envois à 1 Mo, ce qui bloque les pièces jointes. Ajoutez ces annotations :
ingress:
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
# En-têtes volumineux des retours SSO (SAML, OIDC)
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
Traefik n'impose pas de limite : aucune annotation n'est nécessaire.
3. Déploiement
helm upgrade --install foxplan foxplan-1.0.0.tgz -n foxplan -f foxplan-values.yaml
kubectl -n foxplan get pods -w
Le premier démarrage télécharge les images et initialise la base : comptez quelques minutes avant que le pod foxplan-app soit prêt.
4. Créer le premier compte
Ouvrez https://VOTRE_DOMAINE : tant qu'aucune instance n'existe, une page de diagnostic s'ouvre (écran Première installation) et vous montre si tout fonctionne correctement — voir ce qui est vérifié. Si tout répond, cliquez sur Créer la première instance avec mon compte. Le code d'inscription part par votre serveur SMTP ; saisissez-le, suivez l'assistant, puis choisissez votre mot de passe.
Ensuite, à tout moment, l'icône de diagnostic placée à droite du choix de la langue, sur la page de connexion, rouvre ce diagnostic.
Valeurs principales
| Valeur | Défaut | Rôle |
|---|---|---|
image.registry | — | Adresse du registre FoxPlan. Obligatoire |
image.tag | latest | Version des images FoxPlan |
image.pullPolicy | Always | Avec latest, retélécharge l'image à chaque démarrage de pod |
host | — | Nom de domaine. Obligatoire |
ingress.className, ingress.annotations | — | Contrôleur d'Ingress et ses réglages |
ingress.tls.secretName | foxplan-tls | Secret TLS du domaine |
mail.* | — | Serveur SMTP. Obligatoire |
s3.* | vide | Votre stockage S3 — voir Stockage des fichiers |
app.replicas | 1 | Répliques de l'application |
autoscaling.enabled | false | Mise à l'échelle automatique selon le processeur |
app.extraEnv, app.extraSecretEnv | — | Variables supplémentaires : SSO Microsoft ou Google… |
mongodb.enabled, mongodb.externalUri | true | MongoDB intégré, ou le vôtre |
export.amd64Only | true | Place le pod d'export sur un nœud amd64 |
garage.enabled | false | Stockage S3 intégré |
secrets.* | générés | Clé JWT et mots de passe — voir Secrets |
Le fichier values.yaml téléchargé documente chaque valeur.
Stockage des fichiers
Documents des projets, pièces jointes des contrats et photos de profil sont rangés dans un stockage objet compatible S3. Ils y restent privés : l'application les sert elle-même, après contrôle des droits.
Votre stockage existant — Ceph, NetApp StorageGRID, Scality, MinIO, AWS… Créez un bucket et des identifiants dédiés à FoxPlan, puis :
s3:
url: https://s3.mon-entreprise.local # sans le nom du bucket
bucket: foxplan
accessKey: <identifiant d'accès>
secretKey: <clé secrète>
region: <région attendue par le stockage>
La région entre dans la signature de chaque requête : si elle diffère de celle du stockage, toutes les requêtes sont refusées et le diagnostic affiche le stockage en erreur. Le bucket est adressé dans le chemin de l'URL : aucun DNS par bucket n'est nécessaire.
Garage, si vous n'en avez pas — garage.enabled: true déploie Garage, un stockage S3 open source développé en France. Un Job crée le bucket et la clé d'accès de FoxPlan à l'installation ; les identifiants sont générés et placés dans le Secret. La section s3 est alors ignorée. Garage tourne sur un seul nœud : sauvegardez-le (voir Sauvegarde).
Sans stockage, l'application fonctionne ; seuls les fichiers sont indisponibles. Aucun quota n'est appliqué on premise.
Nombre d'instances
Le nombre d'instances de l'application se règle dans votre fichier de valeurs, de deux façons.
Nombre fixe — app.replicas :
app:
replicas: 3
Automatique — selon la charge processeur, entre un minimum et un maximum (exige metrics-server). app.replicas est alors ignoré :
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 6
targetCPUUtilizationPercentage: 70
Appliquez avec helm upgrade (voir Montée de version pour la commande complète), puis vérifiez :
kubectl -n foxplan get pods -l app.kubernetes.io/component=app
kubectl -n foxplan get hpa # avec la mise à l'échelle automatique
Réglez toujours ce nombre dans les valeurs : un kubectl scale est écrasé à la mise à jour suivante. Chaque instance demande 1 vCPU et 4 Gi de mémoire (app.resources) : vos nœuds doivent pouvoir les accueillir toutes, maximum de la mise à l'échelle compris. À partir de 2 instances, le chart ajoute un PodDisruptionBudget qui en garde au moins une pendant les opérations sur les nœuds. Repère : 2 instances de 100 à 500 utilisateurs, la mise à l'échelle automatique au-delà.
Les sessions sont dans Redis et les tâches planifiées prennent un verrou dans Redis : les instances se partagent les utilisateurs sans affinité de session, et une tâche ne s'exécute qu'une fois.
Haute disponibilité
- Application : 2 instances ou plus — voir Nombre d'instances.
- MongoDB : le MongoDB du chart est un nœud unique. Pour de la haute disponibilité, utilisez un replica set (opérateur MongoDB, service géré) avec
mongodb.enabled: falseetmongodb.externalUri: mongodb://utilisateur:mot-de-passe@hote1,hote2,hote3/foxplanweb?replicaSet=rs0&authSource=admin. - Redis : toujours déployé par le chart, avec persistance sur disque. Une indisponibilité de Redis déconnecte les utilisateurs sans perte de données : il ne porte que les sessions et le cache.
Certificats entrants et sortants
FoxPlan utilise deux certificats différents, un par sens de circulation :
| Flux entrants | Flux sortants | |
|---|---|---|
| Rôle | Prouver aux navigateurs que le serveur est bien host | Permettre à l'application de reconnaître vos services internes : AD FS, SMTP en TLS, S3… |
| Utilisé par | Le contrôleur d'Ingress | Les pods foxplan-app (Java) |
| Contenu | Le certificat du domaine FoxPlan et sa clé privée | Le certificat public de l'autorité qui a signé ces services — racine et intermédiaires. Jamais de clé privée |
| Emplacement | Secret TLS foxplan-tls (ingress.tls.secretName), ou cert-manager | Valeur app.outboundCaCerts, ou ConfigMap existante désignée par app.outboundCaConfigMap |
| Format | PEM : --cert = certificat du serveur suivi des intermédiaires, --key = clé non chiffrée | PEM, noms de clés en .crt. Un fichier peut contenir plusieurs certificats |
| Obligatoire | Oui | Seulement si un service appelé est signé par une autorité non publique |
| Prise en compte | Le contrôleur relit le Secret à sa mise à jour | Au démarrage des pods. helm upgrade les redémarre ; une ConfigMap externe modifiée demande kubectl -n foxplan rollout restart deployment foxplan-app |
Ce ne sont pas les mêmes certificats, mais ils viennent souvent de la même PKI : le certificat de l'autorité racine qui a signé celui de FoxPlan est alors aussi celui à fournir pour les flux sortants.
Pour les flux sortants :
# foxplan-values.yaml
app:
outboundCaCerts:
ca-interne.crt: |
-----BEGIN CERTIFICATE-----
MIIDFzCCAf+gAwIBAgIU...
-----END CERTIFICATE-----
ou, sans recopier le fichier dans les valeurs :
helm upgrade --install foxplan foxplan-1.0.0.tgz -n foxplan -f foxplan-values.yaml \
--set-file 'app.outboundCaCerts.ca-interne\.crt=ca-interne.crt'
Le chart refuse un nom qui ne finit pas par .crt et un contenu qui porte une clé privée ; il ne garde que les blocs BEGIN/END CERTIFICATE. Pour savoir si une autorité est nécessaire et convertir un .cer, .p7b ou .pfx au format PEM, voir Docker Compose : les commandes sont les mêmes.
Secrets
Laissés vides dans les valeurs, la clé JWT et les mots de passe de MongoDB et de Redis (et les clés de Garage) sont générés à la première installation, rangés dans le Secret foxplan-secrets, puis conservés à chaque mise à jour. Ce Secret survit aussi à helm uninstall.
SECRET_KEY_JWT chiffre aussi les configurations SSO stockées en base. Restaurer une base sans cette clé rend ces configurations illisibles.
kubectl -n foxplan get secret foxplan-secrets -o yaml > foxplan-secrets.yaml
Pour gérer les secrets vous-même (Vault, External Secrets…), créez un Secret qui porte SECRET_KEY_JWT, MONGO_PASSWORD, REDIS_PASSWORD, MAIL_SMTP_PASSWORD, FILE_S3_ACCESS_KEY et FILE_S3_SECRET_KEY (plus URL_MONGO avec un MongoDB externe), et désignez-le par secrets.existingSecret. Les mots de passe de MongoDB doivent rester alphanumériques : ils entrent tels quels dans l'URL de connexion.
Sauvegarde
À sauvegarder ensemble, au même moment : la base, les fichiers et le Secret.
Base — à chaud, depuis le pod MongoDB, qui embarque les outils :
kubectl -n foxplan exec foxplan-mongodb-0 -- sh -c \
'mongodump --username "$MONGO_INITDB_ROOT_USERNAME" \
--password "$MONGO_INITDB_ROOT_PASSWORD" \
--authenticationDatabase admin --archive --gzip' \
> foxplan-$(date +%F-%H%M).archive.gz
Pour restaurer, la même commande avec mongorestore --drop et kubectl exec -i … < archive. Videz ensuite le cache Redis, sans quoi l'application sert des données périmées :
kubectl -n foxplan exec foxplan-redis-0 -- sh -c 'redis-cli -a "$REDIS_PASSWORD" --no-auth-warning FLUSHALL'
Fichiers dans Garage — à chaud, avec rclone depuis un poste d'administration :
kubectl -n foxplan port-forward svc/foxplan-garage 3900:3900 &
KEY=$(kubectl -n foxplan get secret foxplan-secrets -o jsonpath='{.data.FILE_S3_ACCESS_KEY}' | base64 -d)
SECRET=$(kubectl -n foxplan get secret foxplan-secrets -o jsonpath='{.data.FILE_S3_SECRET_KEY}' | base64 -d)
rclone sync :s3:foxplan ./foxplan-files \
--s3-provider Other --s3-endpoint http://127.0.0.1:3900 --s3-region garage \
--s3-access-key-id "$KEY" --s3-secret-access-key "$SECRET"
Pour restaurer, inversez la source et la destination. Avec votre propre stockage S3, appliquez sa politique de sauvegarde habituelle. Les instantanés de volumes de votre plateforme (VolumeSnapshot) conviennent aussi, à condition de prendre MongoDB et Garage au même moment.
Montée de version
helm upgrade foxplan foxplan-1.0.0.tgz -n foxplan -f foxplan-values.yaml
- Version épinglée (
image.tag) : changez la valeur, puis relancez la commande. - Avec
latest: la commande seule ne change rien si les valeurs sont identiques. Redémarrez les pods pour qu'ils retéléchargent l'image :
kubectl -n foxplan rollout restart deployment
Le déploiement de l'application est progressif : une nouvelle réplique démarre et devient prête avant que l'ancienne s'arrête.
Désinstallation
helm uninstall foxplan -n foxplan
Les volumes (MongoDB, Redis, Garage) et le Secret sont conservés : une réinstallation retrouve les données. Pour tout effacer, supprimez ensuite les PersistentVolumeClaim et le Secret de l'espace de noms.
Dépannage
| Symptôme | Cause probable |
|---|---|
helm refuse : « image.registry est obligatoire » | Valeur obligatoire absente : image.registry, host, mail.from ou mail.host |
Pod foxplan-export en Pending | Aucun nœud amd64 : ajoutez-en un, ou export.amd64Only: false si vos nœuds ARM émulent l'amd64 |
Pod foxplan-app qui redémarre en boucle | Journaux : kubectl -n foxplan logs deploy/foxplan-app. MongoDB ou Redis injoignable, le plus souvent |
Pièce jointe refusée, erreur 413 | Limite d'envoi du contrôleur d'Ingress : voir l'encadré ingress-nginx |
| Stockage en erreur au diagnostic | Région s3.region différente de celle du stockage, ou stockage injoignable depuis les pods |
| Aucun code d'inscription reçu | SMTP : le diagnostic de la page de connexion indique s'il est joignable |
latest ne se met pas à jour | kubectl -n foxplan rollout restart deployment : voir Montée de version |
| Configuration SSO illisible après restauration | Secret foxplan-secrets différent de celui en vigueur lors de la sauvegarde |
PKIX path building failed dans les journaux de foxplan-app | Autorité interne d'AD FS, du SMTP ou du S3 absente de app.outboundCaCerts : voir Certificats entrants et sortants |