Saltar al contenido principal

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​

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
Registro de imágenes de FoxPlan

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​

ComponenteObjeto de KubernetesFunción
AplicaciónDeployment foxplan-appServidor FoxPlan (puerto 5000), una o varias réplicas
DocumentaciónDeployment foxplan-docsEsta documentación, servida bajo /docs
Exportación de las planificacionesDeployment foxplan-exportPDF, PNG, Excel, MS Project y Primavera, servidos bajo /gantt-export
MongoDBStatefulSet foxplan-mongodbBase de datos, un nodo, volumen persistente
RedisStatefulSet foxplan-redisSesiones y caché, volumen persistente. Siempre desplegado: FoxPlan no arranca sin él
Garage (opcional)StatefulSet foxplan-garageAlmacenamiento S3 de los archivos, si no dispone de uno
EnrutamientoIngress 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:

ImagenUtilizada
mongo:8.2.7Siempre, salvo con un MongoDB externo (mongodb.enabled: false)
redis:7.4Siempre
nginxinc/nginx-unprivileged:1.28-alpineProxy del pod de exportación
dxflrs/garage:v2.1.0, curlimages/curl:8.14.1Solo con garage.enabled: true
Sector sensible: utilice su registro interno

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.

SentidoPuertoEntreFunción
Entrante443 (TCP)Puestos de los usuarios → controlador de IngressLa aplicación y el retorno del SSO
Entrante80 (TCP)Puestos de los usuarios → controlador de IngressRedirección a 443; desafío HTTP-01 de cert-manager
Saliente443 (TCP)Pods foxplan-app → proveedor de identidadIntercambio 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.extraEnv y app.extraSecretEnv. La URL de redirección se construye a partir de host: https://<host>/login/oauth2/code/azure o …/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-Host y X-Forwarded-Port que añade el controlador de Ingress. El botón Verificar la configuración muestra la URL exacta que debe declarar.
TLS terminado antes del Ingress

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:

ComponenteProcesador solicitadoMemoria solicitadaLímite de memoriaVolumen
Aplicación (por réplica)14 Gi6 Gi—
MongoDB0,52 Gi4 Gi50 Gi
Redis0,1256 Mi1 Gi5 Gi
Exportación (3 contenedores)0,36656 Mi2,6 Gi—
Documentación0,0132 Mi256 Mi—
Garage (opcional)0,1256 Mi1 Gi1 Gi + 100 Gi

Referencias para la capacidad disponible en los nodos:

UsuariosProcesadorMemoriaDisco
Hasta 1004 vCPU16 GB100 GB
De 100 a 5008 vCPU32 GB, con 2 réplicas de la aplicación200 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.

ingress-nginx

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​

ValorPor defectoFunción
image.registry—Dirección del registro de FoxPlan. Obligatorio
image.taglatestVersión de las imágenes de FoxPlan
image.pullPolicyAlwaysCon 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.secretNamefoxplan-tlsSecret TLS del dominio
mail.*—Servidor SMTP. Obligatorio
s3.*vacíoSu almacenamiento S3 — consulte Almacenamiento de archivos
app.replicas1Réplicas de la aplicación
autoscaling.enabledfalseEscalado automático según el procesador
app.extraEnv, app.extraSecretEnv—Variables adicionales: SSO de Microsoft o Google…
mongodb.enabled, mongodb.externalUritrueMongoDB integrado, o el suyo
export.amd64OnlytrueColoca el pod de exportación en un nodo amd64
garage.enabledfalseAlmacenamiento S3 integrado
secrets.*generadosClave 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: false y mongodb.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 entrantesFlujos salientes
FunciónDemostrar a los navegadores que el servidor es realmente hostPermitir que la aplicación reconozca sus servicios internos: AD FS, SMTP con TLS, S3…
Usado porEl controlador de IngressLos pods foxplan-app (Java)
ContenidoEl certificado del dominio FoxPlan y su clave privadaEl certificado público de la autoridad que firmó esos servicios — raíz e intermedios. Nunca una clave privada
UbicaciónSecret TLS foxplan-tls (ingress.tls.secretName), o cert-managerValor app.outboundCaCerts, o ConfigMap existente indicada en app.outboundCaConfigMap
FormatoPEM: --cert = certificado del servidor seguido de los intermedios, --key = clave sin cifrarPEM, nombres de claves en .crt. Un archivo puede contener varios certificados
ObligatorioSíSolo si un servicio llamado está firmado por una autoridad no pública
AplicaciónEl controlador vuelve a leer el Secret cuando se actualizaAl 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.

Haga una copia de seguridad del Secret junto con la base de datos

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íntomaCausa probable
helm rechaza: «image.registry est obligatoire»Falta un valor obligatorio: image.registry, host, mail.from o mail.host
Pod foxplan-export en PendingNingún nodo amd64: añada uno, o export.amd64Only: false si sus nodos ARM emulan amd64
Pod foxplan-app que se reinicia en bucleRegistros: kubectl -n foxplan logs deploy/foxplan-app. Lo más habitual: MongoDB o Redis inaccesible
Archivo adjunto rechazado, error 413Límite de envío del controlador de Ingress: consulte el recuadro ingress-nginx
Almacenamiento en error en el diagnósticoRegión s3.region distinta de la del almacenamiento, o almacenamiento inaccesible desde los pods
No se recibe ningún código de registroSMTP: el diagnóstico de la página de inicio de sesión indica si es accesible
latest no se actualizakubectl -n foxplan rollout restart deployment: consulte Actualización de versión
Configuración SSO ilegible tras una restauraciónSecret foxplan-secrets distinto del vigente en el momento de la copia de seguridad
PKIX path building failed en los registros de foxplan-appAutoridad interna de AD FS, del SMTP o del S3 ausente de app.outboundCaCerts: vea Certificados entrantes y salientes