Aller au contenu principal

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​

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
Registre d'images FoxPlan

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​

ComposantObjet KubernetesRôle
ApplicationDeployment foxplan-appServeur FoxPlan (port 5000), une ou plusieurs répliques
DocumentationDeployment foxplan-docsCette documentation, servie sous /docs
Export des planningsDeployment foxplan-exportPDF, PNG, Excel, MS Project et Primavera, servis sous /gantt-export
MongoDBStatefulSet foxplan-mongodbBase de données, un nœud, volume persistant
RedisStatefulSet foxplan-redisSessions et cache, volume persistant. Toujours déployé : FoxPlan ne démarre pas sans
Garage (option)StatefulSet foxplan-garageStockage S3 des fichiers, si vous n'en avez pas
RoutageIngress 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 :

ImageUtilisée
mongo:8.2.7Toujours, sauf MongoDB externe (mongodb.enabled: false)
redis:7.4Toujours
nginxinc/nginx-unprivileged:1.28-alpineProxy du pod d'export
dxflrs/garage:v2.1.0, curlimages/curl:8.14.1Seulement avec garage.enabled: true
Secteur sensible : passez par votre registre interne

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.

SensPortEntreRôle
Entrant443 (TCP)Postes des utilisateurs → contrôleur d'IngressL'application et le retour du SSO
Entrant80 (TCP)Postes des utilisateurs → contrôleur d'IngressRedirection vers 443 ; challenge HTTP-01 de cert-manager
Sortant443 (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.extraEnv et app.extraSecretEnv. L'URL de redirection est construite à partir de host : https://<host>/login/oauth2/code/azure ou …/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-Host et X-Forwarded-Port posés par le contrôleur d'Ingress. Le bouton Vérifier la configuration affiche l'URL exacte à déclarer.
TLS terminé avant l'Ingress

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 :

ComposantProcesseur demandéMémoire demandéePlafond mémoireVolume
Application (par réplique)14 Gi6 Gi—
MongoDB0,52 Gi4 Gi50 Gi
Redis0,1256 Mi1 Gi5 Gi
Export (3 conteneurs)0,36656 Mi2,6 Gi—
Documentation0,0132 Mi256 Mi—
Garage (option)0,1256 Mi1 Gi1 Gi + 100 Gi

Repères pour la capacité disponible sur les nœuds :

UtilisateursProcesseurMémoireDisque
Jusqu'à 1004 vCPU16 Go100 Go
De 100 à 5008 vCPU32 Go, avec 2 répliques de l'application200 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

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​

ValeurDéfautRôle
image.registry—Adresse du registre FoxPlan. Obligatoire
image.taglatestVersion des images FoxPlan
image.pullPolicyAlwaysAvec 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.secretNamefoxplan-tlsSecret TLS du domaine
mail.*—Serveur SMTP. Obligatoire
s3.*videVotre stockage S3 — voir Stockage des fichiers
app.replicas1Répliques de l'application
autoscaling.enabledfalseMise à l'échelle automatique selon le processeur
app.extraEnv, app.extraSecretEnv—Variables supplémentaires : SSO Microsoft ou Google…
mongodb.enabled, mongodb.externalUritrueMongoDB intégré, ou le vôtre
export.amd64OnlytruePlace le pod d'export sur un nœud amd64
garage.enabledfalseStockage S3 intégré
secrets.*générésClé 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: false et mongodb.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 entrantsFlux sortants
RôleProuver aux navigateurs que le serveur est bien hostPermettre à l'application de reconnaître vos services internes : AD FS, SMTP en TLS, S3…
Utilisé parLe contrôleur d'IngressLes pods foxplan-app (Java)
ContenuLe certificat du domaine FoxPlan et sa clé privéeLe certificat public de l'autorité qui a signé ces services — racine et intermédiaires. Jamais de clé privée
EmplacementSecret TLS foxplan-tls (ingress.tls.secretName), ou cert-managerValeur app.outboundCaCerts, ou ConfigMap existante désignée par app.outboundCaConfigMap
FormatPEM : --cert = certificat du serveur suivi des intermédiaires, --key = clé non chiffréePEM, noms de clés en .crt. Un fichier peut contenir plusieurs certificats
ObligatoireOuiSeulement si un service appelé est signé par une autorité non publique
Prise en compteLe contrôleur relit le Secret à sa mise à jourAu 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.

Sauvegardez le Secret avec la base

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ômeCause probable
helm refuse : « image.registry est obligatoire »Valeur obligatoire absente : image.registry, host, mail.from ou mail.host
Pod foxplan-export en PendingAucun 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 boucleJournaux : kubectl -n foxplan logs deploy/foxplan-app. MongoDB ou Redis injoignable, le plus souvent
Pièce jointe refusée, erreur 413Limite d'envoi du contrôleur d'Ingress : voir l'encadré ingress-nginx
Stockage en erreur au diagnosticRégion s3.region différente de celle du stockage, ou stockage injoignable depuis les pods
Aucun code d'inscription reçuSMTP : le diagnostic de la page de connexion indique s'il est joignable
latest ne se met pas à jourkubectl -n foxplan rollout restart deployment : voir Montée de version
Configuration SSO illisible après restaurationSecret foxplan-secrets différent de celui en vigueur lors de la sauvegarde
PKIX path building failed dans les journaux de foxplan-appAutorité interne d'AD FS, du SMTP ou du S3 absente de app.outboundCaCerts : voir Certificats entrants et sortants