Despliegue en Kubernetes
Esta página describe el despliegue de FoxPlan en un clúster de Kubernetes con su chart de Helm. Toda la configuración se realiza mediante un archivo de valores: nunca es necesario modificar las plantillas del chart.
Para un servidor único, sin clúster, consulte Despliegue con Docker Compose.
Archivos para descargar
- foxplan-1.0.0.tgz — el chart de Helm
- values.yaml — todos los valores, comentados
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
Las imágenes de FoxPlan se publican en un registro de acceso libre: no hay credenciales que solicitar ni imagePullSecrets que crear. FoxPlan le comunica su dirección junto con su licencia; se indica en el valor image.registry.
Arquitectura
| Componente | Objeto de Kubernetes | Función |
|---|---|---|
| Aplicación | Deployment foxplan-app | Servidor FoxPlan (puerto 5000), una o varias réplicas |
| Documentación | Deployment foxplan-docs | Esta documentación, servida bajo /docs |
| Exportación de las planificaciones | Deployment foxplan-export | PDF, PNG, Excel, MS Project y Primavera, servidos bajo /gantt-export |
| MongoDB | StatefulSet foxplan-mongodb | Base de datos, un nodo, volumen persistente |
| Redis | StatefulSet foxplan-redis | Sesiones y caché, volumen persistente. Siempre desplegado: FoxPlan no arranca sin él |
| Garage (opcional) | StatefulSet foxplan-garage | Almacenamiento S3 de los archivos, si no dispone de uno |
| Enrutamiento | Ingress foxplan | / → aplicación, /docs → documentación, /gantt-export → exportación |
Los nombres llevan como prefijo el de la release de Helm (foxplan en los ejemplos). El pod de exportación incluye un pequeño proxy nginx que elimina el prefijo /gantt-export: así, el enrutamiento funciona con cualquier controlador de Ingress, sin anotación de reescritura propia de alguno de ellos.
Requisitos previos
- Un clúster Kubernetes 1.27 o posterior, y Helm 3.
- Un controlador de Ingress: Traefik, ingress-nginx, HAProxy, el de su oferta gestionada…
- Una clase de almacenamiento por defecto (o una clase indicada en los valores) para los volúmenes de MongoDB, de Redis y, en su caso, de Garage.
- Al menos un nodo amd64: las imágenes de exportación solo se publican para amd64. Las demás imágenes también existen para arm64.
- Un nombre de dominio que apunte a su controlador de Ingress, y un certificado TLS: emitido por cert-manager o proporcionado por usted en un Secret.
- Un servidor SMTP: los códigos de registro, las invitaciones y los restablecimientos de contraseña dependen de él. Sin él, nadie puede conectarse.
- Solo para el escalado automático: metrics-server.
Obtención de las imágenes
Los flujos de red son los de la instalación con Docker Compose, descritos en Paso 1: descargar las imágenes — flujos de red: el registro de FoxPlan, y Docker Hub para las imágenes de terceros. Parten de los nodos del clúster, en el momento en que descargan las imágenes. Imágenes de terceros utilizadas por el chart:
| Imagen | Utilizada |
|---|---|
mongo:8.2.7 | Siempre, salvo con un MongoDB externo (mongodb.enabled: false) |
redis:7.4 | Siempre |
nginxinc/nginx-unprivileged:1.28-alpine | Proxy del pod de exportación |
dxflrs/garage:v2.1.0, curlimages/curl:8.14.1 | Solo con garage.enabled: true |
Obtenga las imágenes desde un entorno independiente de su sistema de información, como se describe para Docker Compose, y súbalas después a su registro interno. Indíquelo a continuación en los valores: image.registry para las imágenes de FoxPlan, mongodb.image, redis.image, export.proxy.image, garage.image y garage.initImage para las demás. Si este registro exige autenticación, añada su Secret en imagePullSecrets. Los nodos dejan entonces de tener cualquier flujo hacia Internet.
Puertos y SSO
El SSO empresarial no requiere ningún puerto adicional, ya se trate del inicio de sesión estándar de Microsoft o Google o de un SSO personalizado. El retorno de un inicio de sesión lo transporta el navegador del usuario hacia https://<host>/login/oauth2/code/…, a través del Ingress, como el resto de la aplicación: el proveedor de identidad nunca abre una conexión hacia el clúster.
| Sentido | Puerto | Entre | Función |
|---|---|---|---|
| Entrante | 443 (TCP) | Puestos de los usuarios → controlador de Ingress | La aplicación y el retorno del SSO |
| Entrante | 80 (TCP) | Puestos de los usuarios → controlador de Ingress | Redirección a 443; desafío HTTP-01 de cert-manager |
| Saliente | 443 (TCP) | Pods foxplan-app → proveedor de identidad | Intercambio del token, claves de firma, perfil — dominios en la matriz de flujos |
No exponga el puerto 5000 de los pods (NodePort, LoadBalancer): basta con el Ingress. Si sus NetworkPolicy filtran las salidas, autorice los pods foxplan-app hacia el proveedor de identidad, en 443 o en su propio puerto — Keycloak en 8443, por ejemplo.
- SSO estándar (Microsoft, Google): variables pasadas por
app.extraEnvyapp.extraSecretEnv. La URL de redirección se construye a partir dehost:https://<host>/login/oauth2/code/azureo…/google, que debe declararse exactamente igual en Microsoft Entra o Google. - SSO personalizado (Okta, Keycloak, cualquier proveedor OIDC, configurado en SSO de empresa): la URL de redirección se reconstruye a partir de las cabeceras
X-Forwarded-Proto,X-Forwarded-HostyX-Forwarded-Portque añade el controlador de Ingress. El botón Verificar la configuración muestra la URL exacta que debe declarar.
Si un balanceador de carga situado delante del controlador de Ingress termina el TLS, el controlador recibe HTTP y transmite X-Forwarded-Proto: http: FoxPlan calcula entonces una URL en http://, que el proveedor rechaza (redirect_uri mismatch). Haga que se retransmitan las cabeceras del balanceador — use-forwarded-headers: "true" en la ConfigMap de ingress-nginx, forwardedHeaders.trustedIPs en el punto de entrada de Traefik — o deje el balanceador en TCP, sin descifrar. El puerto público debe seguir siendo 443.
Dimensionamiento
Solicitudes (requests) y límites (limits) por defecto del chart:
| Componente | Procesador solicitado | Memoria solicitada | Límite de memoria | Volumen |
|---|---|---|---|---|
| Aplicación (por réplica) | 1 | 4 Gi | 6 Gi | — |
| MongoDB | 0,5 | 2 Gi | 4 Gi | 50 Gi |
| Redis | 0,1 | 256 Mi | 1 Gi | 5 Gi |
| Exportación (3 contenedores) | 0,36 | 656 Mi | 2,6 Gi | — |
| Documentación | 0,01 | 32 Mi | 256 Mi | — |
| Garage (opcional) | 0,1 | 256 Mi | 1 Gi | 1 Gi + 100 Gi |
Referencias para la capacidad disponible en los nodos:
| Usuarios | Procesador | Memoria | Disco |
|---|---|---|---|
| Hasta 100 | 4 vCPU | 16 GB | 100 GB |
| De 100 a 500 | 8 vCPU | 32 GB, con 2 réplicas de la aplicación | 200 GB |
| Más allá | Escalado automático de la aplicación, MongoDB en replica set |
La JVM de la aplicación toma el 75 % de su límite de memoria (app.javaOptions): para darle más memoria, aumente app.resources.limits.memory, sin ningún otro ajuste.
Instalación
1. Espacio de nombres y certificado
kubectl create namespace foxplan
Con cert-manager, no hay nada que crear: basta con la anotación del emisor en los valores (consulte el paso siguiente). De lo contrario, cree el Secret TLS del dominio, con la cadena completa incluida:
kubectl -n foxplan create secret tls foxplan-tls --cert=fullchain.pem --key=privkey.pem
2. Valores
Parta del values.yaml descargado, o de un archivo que contenga solo lo que cambia. Lo mínimo:
# foxplan-values.yaml
image:
registry: <dirección comunicada por FoxPlan>
tag: latest # fije la versión desplegada en producción
host: foxplan.mon-entreprise.fr
ingress:
className: traefik # o nginx, haproxy… vacío = clase por defecto del clúster
annotations:
cert-manager.io/cluster-issuer: letsencrypt # si utiliza cert-manager
app:
replicas: 1 # número de instancias de la aplicación — consulte «Número de instancias»
mail:
from: foxplan@mon-entreprise.fr
host: smtp.mon-entreprise.fr
port: 587
username: foxplan@mon-entreprise.fr
password: <contraseña SMTP>
Sin image.registry, host, mail.from o mail.host, Helm rechaza la instalación con un mensaje que indica el valor que falta.
Por defecto, ingress-nginx limita los envíos a 1 MB, lo que bloquea los archivos adjuntos. Añada estas anotaciones:
ingress:
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
# Cabeceras voluminosas de las respuestas SSO (SAML, OIDC)
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
Traefik no impone ningún límite: no se necesita ninguna anotación.
3. Despliegue
helm upgrade --install foxplan foxplan-1.0.0.tgz -n foxplan -f foxplan-values.yaml
kubectl -n foxplan get pods -w
El primer arranque descarga las imágenes e inicializa la base de datos: cuente con unos minutos antes de que el pod foxplan-app esté listo.
4. Crear la primera cuenta
Abra https://SU_DOMINIO: mientras no exista ninguna instancia, se abre una página de diagnóstico (pantalla Primera instalación) que le muestra si todo funciona correctamente — consulte qué se comprueba. Si todo responde, haga clic en Crear la primera instancia con mi cuenta. El código de registro se envía a través de su servidor SMTP; introdúzcalo, siga el asistente y elija después su contraseña.
Después, en cualquier momento, el icono de diagnóstico situado a la derecha del selector de idioma, en la página de inicio de sesión, vuelve a abrir este diagnóstico.
Valores principales
| Valor | Por defecto | Función |
|---|---|---|
image.registry | — | Dirección del registro de FoxPlan. Obligatorio |
image.tag | latest | Versión de las imágenes de FoxPlan |
image.pullPolicy | Always | Con latest, vuelve a descargar la imagen en cada arranque de pod |
host | — | Nombre de dominio. Obligatorio |
ingress.className, ingress.annotations | — | Controlador de Ingress y sus ajustes |
ingress.tls.secretName | foxplan-tls | Secret TLS del dominio |
mail.* | — | Servidor SMTP. Obligatorio |
s3.* | vacío | Su almacenamiento S3 — consulte Almacenamiento de archivos |
app.replicas | 1 | Réplicas de la aplicación |
autoscaling.enabled | false | Escalado automático según el procesador |
app.extraEnv, app.extraSecretEnv | — | Variables adicionales: SSO de Microsoft o Google… |
mongodb.enabled, mongodb.externalUri | true | MongoDB integrado, o el suyo |
export.amd64Only | true | Coloca el pod de exportación en un nodo amd64 |
garage.enabled | false | Almacenamiento S3 integrado |
secrets.* | generados | Clave JWT y contraseñas — consulte Secretos |
El archivo values.yaml descargado documenta cada valor.
Almacenamiento de archivos
Los documentos de los proyectos, los archivos adjuntos de los contratos y las fotos de perfil se guardan en un almacenamiento de objetos compatible con S3. Allí permanecen privados: la propia aplicación los sirve, tras comprobar los permisos.
Su almacenamiento existente — Ceph, NetApp StorageGRID, Scality, MinIO, AWS… Cree un bucket y credenciales dedicadas a FoxPlan y, a continuación:
s3:
url: https://s3.mon-entreprise.local # sin el nombre del bucket
bucket: foxplan
accessKey: <identificador de acceso>
secretKey: <clave secreta>
region: <región esperada por el almacenamiento>
La región forma parte de la firma de cada solicitud: si difiere de la del almacenamiento, todas las solicitudes se rechazan y el diagnóstico muestra el almacenamiento en error. El bucket se direcciona en la ruta de la URL: no se necesita ningún DNS por bucket.
Garage, si no dispone de uno — garage.enabled: true despliega Garage, un almacenamiento S3 de código abierto desarrollado en Francia. Un Job crea el bucket y la clave de acceso de FoxPlan durante la instalación; las credenciales se generan y se colocan en el Secret. La sección s3 se ignora entonces. Garage se ejecuta en un único nodo: haga una copia de seguridad (consulte Copia de seguridad).
Sin almacenamiento, la aplicación funciona; solo los archivos dejan de estar disponibles. On premise no se aplica ninguna cuota.
Número de instancias
El número de instancias de la aplicación se define en su archivo de valores, de una de estas dos formas.
Número fijo — app.replicas:
app:
replicas: 3
Automático — según la carga de CPU, entre un mínimo y un máximo (requiere metrics-server). app.replicas se ignora entonces:
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 6
targetCPUUtilizationPercentage: 70
Aplique el cambio con helm upgrade (comando completo en Actualización de versión) y compruebe:
kubectl -n foxplan get pods -l app.kubernetes.io/component=app
kubectl -n foxplan get hpa # con escalado automático
Defina siempre este número en los valores: un kubectl scale se sobrescribe en la siguiente actualización. Cada instancia solicita 1 vCPU y 4 Gi de memoria (app.resources): sus nodos deben poder alojarlas todas, incluido el máximo del escalado. A partir de 2 instancias, el chart añade un PodDisruptionBudget que mantiene al menos una durante las operaciones en los nodos. Referencia: 2 instancias de 100 a 500 usuarios, escalado automático por encima.
Las sesiones están en Redis y las tareas programadas toman un bloqueo en Redis: las instancias se reparten los usuarios sin afinidad de sesión y una tarea solo se ejecuta una vez.
Alta disponibilidad
- Aplicación: 2 instancias o más — consulte Número de instancias.
- MongoDB: el MongoDB del chart es un nodo único. Para disponer de alta disponibilidad, utilice un replica set (operador de MongoDB, servicio gestionado) con
mongodb.enabled: falseymongodb.externalUri: mongodb://utilisateur:mot-de-passe@hote1,hote2,hote3/foxplanweb?replicaSet=rs0&authSource=admin. - Redis: siempre desplegado por el chart, con persistencia en disco. Una indisponibilidad de Redis desconecta a los usuarios sin pérdida de datos: solo contiene las sesiones y la caché.
Certificados entrantes y salientes
FoxPlan utiliza dos certificados distintos, uno por sentido de circulación:
| Flujos entrantes | Flujos salientes | |
|---|---|---|
| Función | Demostrar a los navegadores que el servidor es realmente host | Permitir que la aplicación reconozca sus servicios internos: AD FS, SMTP con TLS, S3… |
| Usado por | El controlador de Ingress | Los pods foxplan-app (Java) |
| Contenido | El certificado del dominio FoxPlan y su clave privada | El certificado público de la autoridad que firmó esos servicios — raíz e intermedios. Nunca una clave privada |
| Ubicación | Secret TLS foxplan-tls (ingress.tls.secretName), o cert-manager | Valor app.outboundCaCerts, o ConfigMap existente indicada en app.outboundCaConfigMap |
| Formato | PEM: --cert = certificado del servidor seguido de los intermedios, --key = clave sin cifrar | PEM, nombres de claves en .crt. Un archivo puede contener varios certificados |
| Obligatorio | Sí | Solo si un servicio llamado está firmado por una autoridad no pública |
| Aplicación | El controlador vuelve a leer el Secret cuando se actualiza | Al arrancar los pods. helm upgrade los reinicia; una ConfigMap externa modificada requiere kubectl -n foxplan rollout restart deployment foxplan-app |
No son los mismos certificados, pero a menudo proceden de la misma PKI: el certificado de la autoridad raíz que firmó el de FoxPlan es entonces también el que hay que proporcionar para los flujos salientes.
Para los flujos salientes:
# foxplan-values.yaml
app:
outboundCaCerts:
ca-interne.crt: |
-----BEGIN CERTIFICATE-----
MIIDFzCCAf+gAwIBAgIU...
-----END CERTIFICATE-----
o, sin copiar el archivo en los valores:
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'
El chart rechaza un nombre que no termine en .crt y un contenido que incluya una clave privada; solo conserva los bloques BEGIN/END CERTIFICATE. Para saber si hace falta una autoridad y convertir un .cer, .p7b o .pfx a PEM, vea Docker Compose: los comandos son los mismos.
Secretos
Si se dejan vacíos en los valores, la clave JWT y las contraseñas de MongoDB y de Redis (y las claves de Garage) se generan en la primera instalación, se guardan en el Secret foxplan-secrets y se conservan en cada actualización. Este Secret también sobrevive a helm uninstall.
SECRET_KEY_JWT también cifra las configuraciones SSO almacenadas en la base de datos. Restaurar una base de datos sin esta clave hace que esas configuraciones sean ilegibles.
kubectl -n foxplan get secret foxplan-secrets -o yaml > foxplan-secrets.yaml
Para gestionar los secretos usted mismo (Vault, External Secrets…), cree un Secret que contenga SECRET_KEY_JWT, MONGO_PASSWORD, REDIS_PASSWORD, MAIL_SMTP_PASSWORD, FILE_S3_ACCESS_KEY y FILE_S3_SECRET_KEY (más URL_MONGO con un MongoDB externo), e indíquelo en secrets.existingSecret. Las contraseñas de MongoDB deben ser alfanuméricas: se incluyen tal cual en la URL de conexión.
Copia de seguridad
Se deben guardar juntos, en el mismo momento: la base de datos, los archivos y el Secret.
Base de datos — en caliente, desde el pod de MongoDB, que incluye las herramientas:
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
Para restaurar, el mismo comando con mongorestore --drop y kubectl exec -i … < archive. Vacíe después la caché de Redis; de lo contrario, la aplicación sirve datos obsoletos:
kubectl -n foxplan exec foxplan-redis-0 -- sh -c 'redis-cli -a "$REDIS_PASSWORD" --no-auth-warning FLUSHALL'
Archivos en Garage — en caliente, con rclone desde un puesto de administración:
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"
Para restaurar, invierta el origen y el destino. Con su propio almacenamiento S3, aplique su política de copia de seguridad habitual. Las instantáneas de volúmenes de su plataforma (VolumeSnapshot) también son válidas, siempre que se tomen MongoDB y Garage en el mismo momento.
Actualización de versión
helm upgrade foxplan foxplan-1.0.0.tgz -n foxplan -f foxplan-values.yaml
- Versión fijada (
image.tag): cambie el valor y vuelva a ejecutar el comando. - Con
latest: el comando por sí solo no cambia nada si los valores son idénticos. Reinicie los pods para que vuelvan a descargar la imagen:
kubectl -n foxplan rollout restart deployment
El despliegue de la aplicación es progresivo: una nueva réplica arranca y queda lista antes de que se detenga la anterior.
Desinstalación
helm uninstall foxplan -n foxplan
Los volúmenes (MongoDB, Redis, Garage) y el Secret se conservan: una reinstalación recupera los datos. Para borrarlo todo, elimine a continuación los PersistentVolumeClaim y el Secret del espacio de nombres.
Resolución de problemas
| Síntoma | Causa probable |
|---|---|
helm rechaza: «image.registry est obligatoire» | Falta un valor obligatorio: image.registry, host, mail.from o mail.host |
Pod foxplan-export en Pending | Ningún nodo amd64: añada uno, o export.amd64Only: false si sus nodos ARM emulan amd64 |
Pod foxplan-app que se reinicia en bucle | Registros: kubectl -n foxplan logs deploy/foxplan-app. Lo más habitual: MongoDB o Redis inaccesible |
Archivo adjunto rechazado, error 413 | Límite de envío del controlador de Ingress: consulte el recuadro ingress-nginx |
| Almacenamiento en error en el diagnóstico | Región s3.region distinta de la del almacenamiento, o almacenamiento inaccesible desde los pods |
| No se recibe ningún código de registro | SMTP: el diagnóstico de la página de inicio de sesión indica si es accesible |
latest no se actualiza | kubectl -n foxplan rollout restart deployment: consulte Actualización de versión |
| Configuración SSO ilegible tras una restauración | Secret foxplan-secrets distinto del vigente en el momento de la copia de seguridad |
PKIX path building failed en los registros de foxplan-app | Autoridad interna de AD FS, del SMTP o del S3 ausente de app.outboundCaCerts: vea Certificados entrantes y salientes |