Implantação no Kubernetes
Esta página descreve a implantação do FoxPlan em um cluster Kubernetes com o seu chart Helm. Toda a configuração é feita por um arquivo de valores: os templates do chart nunca precisam ser modificados.
Para um servidor único, sem cluster, veja Implantação com Docker Compose.
Arquivos para baixar
- foxplan-1.0.0.tgz — o chart Helm
- values.yaml — todos os 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
As imagens do FoxPlan são publicadas em um registro de acesso livre: nenhuma credencial a solicitar, nenhum imagePullSecrets a criar. O seu endereço é informado pelo FoxPlan junto com a sua licença; ele é preenchido no valor image.registry.
Arquitetura
| Componente | Objeto Kubernetes | Função |
|---|---|---|
| Aplicação | Deployment foxplan-app | Servidor FoxPlan (porta 5000), uma ou mais réplicas |
| Documentação | Deployment foxplan-docs | Esta documentação, servida em /docs |
| Exportação dos planejamentos | Deployment foxplan-export | PDF, PNG, Excel, MS Project e Primavera, servidos em /gantt-export |
| MongoDB | StatefulSet foxplan-mongodb | Banco de dados, um nó, volume persistente |
| Redis | StatefulSet foxplan-redis | Sessões e cache, volume persistente. Sempre implantado: o FoxPlan não inicia sem ele |
| Garage (opcional) | StatefulSet foxplan-garage | Armazenamento S3 dos arquivos, se você não tiver um |
| Roteamento | Ingress foxplan | / → aplicação, /docs → documentação, /gantt-export → exportação |
Os nomes são prefixados pelo nome da release Helm (foxplan nos exemplos). O pod de exportação inclui um pequeno proxy nginx que remove o prefixo /gantt-export: assim, o roteamento funciona com qualquer controlador de Ingress, sem anotação de reescrita específica de algum deles.
Pré-requisitos
- Um cluster Kubernetes 1.27 ou mais recente, e o Helm 3.
- Um controlador de Ingress: Traefik, ingress-nginx, HAProxy, o da sua oferta gerenciada…
- Uma classe de armazenamento padrão (ou uma classe indicada nos valores) para os volumes do MongoDB, do Redis e, se for o caso, do Garage.
- Pelo menos um nó amd64: as imagens de exportação são publicadas apenas para amd64. As demais imagens também existem para arm64.
- Um nome de domínio que aponte para o seu controlador de Ingress, e um certificado TLS: emitido pelo cert-manager ou fornecido por você em um Secret.
- Um servidor SMTP: códigos de registro, convites e redefinições de senha dependem dele. Sem ele, ninguém consegue se conectar.
- Somente para o escalonamento automático: metrics-server.
Obtenção das imagens
Os fluxos de rede são os da instalação com Docker Compose, descritos em Etapa 1: baixar as imagens — fluxos de rede: o registro do FoxPlan, e o Docker Hub para as imagens de terceiros. Eles partem dos nós do cluster, no momento em que baixam as imagens. Imagens de terceiros utilizadas pelo chart:
| Imagem | Utilizada |
|---|---|
mongo:8.2.7 | Sempre, exceto com MongoDB externo (mongodb.enabled: false) |
redis:7.4 | Sempre |
nginxinc/nginx-unprivileged:1.28-alpine | Proxy do pod de exportação |
dxflrs/garage:v2.1.0, curlimages/curl:8.14.1 | Somente com garage.enabled: true |
Obtenha as imagens a partir de um ambiente independente do seu SI, como descrito para o Docker Compose, e depois envie-as para o seu registro interno. Indique-o em seguida nos valores: image.registry para as imagens do FoxPlan, mongodb.image, redis.image, export.proxy.image, garage.image e garage.initImage para as demais. Se esse registro exigir autenticação, adicione o seu Secret em imagePullSecrets. Os nós deixam então de ter qualquer fluxo para a Internet.
Portas e SSO
O SSO corporativo não exige nenhuma porta adicional, seja o login padrão com Microsoft ou Google, seja um SSO personalizado. O retorno de um login é levado pelo navegador do usuário para https://<host>/login/oauth2/code/…, através do Ingress, como o resto da aplicação: o provedor de identidade nunca abre uma conexão para o cluster.
| Sentido | Porta | Entre | Função |
|---|---|---|---|
| Entrada | 443 (TCP) | Estações dos usuários → controlador de Ingress | A aplicação e o retorno do SSO |
| Entrada | 80 (TCP) | Estações dos usuários → controlador de Ingress | Redirecionamento para 443; desafio HTTP-01 do cert-manager |
| Saída | 443 (TCP) | Pods foxplan-app → provedor de identidade | Troca do token, chaves de assinatura, perfil — domínios na matriz de fluxos |
Não exponha a porta 5000 dos pods (NodePort, LoadBalancer): o Ingress basta. Se as suas NetworkPolicy filtram as saídas, autorize os pods foxplan-app a acessar o provedor de identidade, na 443 ou na porta própria dele — Keycloak em 8443, por exemplo.
- SSO padrão (Microsoft, Google): variáveis passadas por
app.extraEnveapp.extraSecretEnv. A URL de redirecionamento é construída a partir dehost:https://<host>/login/oauth2/code/azureou…/google, a declarar exatamente igual no Microsoft Entra ou no Google. - SSO personalizado (Okta, Keycloak, qualquer provedor OIDC, configurado em SSO corporativo): a URL de redirecionamento é reconstruída a partir dos cabeçalhos
X-Forwarded-Proto,X-Forwarded-HosteX-Forwarded-Portdefinidos pelo controlador de Ingress. O botão Verificar a configuração mostra a URL exata a declarar.
Se um balanceador de carga à frente do controlador de Ingress termina o TLS, o controlador recebe HTTP e transmite X-Forwarded-Proto: http: o FoxPlan calcula então uma URL em http://, que o provedor recusa (redirect_uri mismatch). Faça repassar os cabeçalhos do balanceador — use-forwarded-headers: "true" na ConfigMap do ingress-nginx, forwardedHeaders.trustedIPs no ponto de entrada do Traefik — ou mantenha o balanceador em TCP, sem descriptografar. A porta pública deve continuar sendo 443.
Dimensionamento
Solicitações (requests) e limites (limits) padrão do chart:
| Componente | Processador solicitado | Memória solicitada | Limite de memória | Volume |
|---|---|---|---|---|
| Aplicação (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 |
| Exportação (3 contêineres) | 0,36 | 656 Mi | 2,6 Gi | — |
| Documentação | 0,01 | 32 Mi | 256 Mi | — |
| Garage (opcional) | 0,1 | 256 Mi | 1 Gi | 1 Gi + 100 Gi |
Referências para a capacidade disponível nos nós:
| Usuários | Processador | Memória | Disco |
|---|---|---|---|
| Até 100 | 4 vCPU | 16 GB | 100 GB |
| De 100 a 500 | 8 vCPU | 32 GB, com 2 réplicas da aplicação | 200 GB |
| Acima disso | Escalonamento automático da aplicação, MongoDB em replica set |
A JVM da aplicação usa 75 % do seu limite de memória (app.javaOptions): para lhe dar mais memória, aumente app.resources.limits.memory, sem nenhum outro ajuste.
Instalação
1. Namespace e certificado
kubectl create namespace foxplan
Com o cert-manager, não há nada a criar: a anotação do emissor nos valores é suficiente (veja a etapa seguinte). Caso contrário, crie o Secret TLS do domínio, incluindo a cadeia completa:
kubectl -n foxplan create secret tls foxplan-tls --cert=fullchain.pem --key=privkey.pem
2. Valores
Parta do values.yaml baixado, ou de um arquivo que contenha apenas o que muda. O mínimo:
# foxplan-values.yaml
image:
registry: <endereço informado pelo FoxPlan>
tag: latest # fixe a versão implantada em produção
host: foxplan.mon-entreprise.fr
ingress:
className: traefik # ou nginx, haproxy… vazio = classe padrão do cluster
annotations:
cert-manager.io/cluster-issuer: letsencrypt # se você usa o cert-manager
app:
replicas: 1 # número de instâncias da aplicação — veja "Número de instâncias"
mail:
from: foxplan@mon-entreprise.fr
host: smtp.mon-entreprise.fr
port: 587
username: foxplan@mon-entreprise.fr
password: <senha SMTP>
Sem image.registry, host, mail.from ou mail.host, o Helm recusa a instalação com uma mensagem que nomeia o valor ausente.
Por padrão, o ingress-nginx limita os envios a 1 MB, o que bloqueia os anexos. Adicione estas anotações:
ingress:
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
# Cabeçalhos volumosos das respostas SSO (SAML, OIDC)
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
O Traefik não impõe limite: nenhuma anotação é necessária.
3. Implantação
helm upgrade --install foxplan foxplan-1.0.0.tgz -n foxplan -f foxplan-values.yaml
kubectl -n foxplan get pods -w
A primeira inicialização baixa as imagens e inicializa o banco: conte alguns minutos até que o pod foxplan-app esteja pronto.
4. Criar a primeira conta
Abra https://SEU_DOMINIO: enquanto nenhuma instância existir, uma página de diagnóstico é aberta (tela Primeira instalação) e mostra se tudo funciona corretamente — veja o que é verificado. Se tudo responder, clique em Criar a primeira instância com a minha conta. O código de registro é enviado pelo seu servidor SMTP; informe-o, siga o assistente e escolha em seguida a sua senha.
Depois, a qualquer momento, o ícone de diagnóstico à direita do seletor de idioma, na página de login, reabre esse diagnóstico.
Valores principais
| Valor | Padrão | Função |
|---|---|---|
image.registry | — | Endereço do registro do FoxPlan. Obrigatório |
image.tag | latest | Versão das imagens do FoxPlan |
image.pullPolicy | Always | Com latest, baixa novamente a imagem a cada inicialização de pod |
host | — | Nome de domínio. Obrigatório |
ingress.className, ingress.annotations | — | Controlador de Ingress e suas configurações |
ingress.tls.secretName | foxplan-tls | Secret TLS do domínio |
mail.* | — | Servidor SMTP. Obrigatório |
s3.* | vazio | O seu armazenamento S3 — veja Armazenamento de arquivos |
app.replicas | 1 | Réplicas da aplicação |
autoscaling.enabled | false | Escalonamento automático conforme o processador |
app.extraEnv, app.extraSecretEnv | — | Variáveis adicionais: SSO Microsoft ou Google… |
mongodb.enabled, mongodb.externalUri | true | MongoDB integrado, ou o seu |
export.amd64Only | true | Coloca o pod de exportação em um nó amd64 |
garage.enabled | false | Armazenamento S3 integrado |
secrets.* | gerados | Chave JWT e senhas — veja Segredos |
O arquivo values.yaml baixado documenta cada valor.
Armazenamento de arquivos
Documentos dos projetos, anexos dos contratos e fotos de perfil são guardados em um armazenamento de objetos compatível com S3. Lá eles permanecem privados: a própria aplicação os serve, após verificar as permissões.
O seu armazenamento existente — Ceph, NetApp StorageGRID, Scality, MinIO, AWS… Crie um bucket e credenciais dedicadas ao FoxPlan, e depois:
s3:
url: https://s3.mon-entreprise.local # sem o nome do bucket
bucket: foxplan
accessKey: <identificador de acesso>
secretKey: <chave secreta>
region: <região esperada pelo armazenamento>
A região entra na assinatura de cada requisição: se ela for diferente da do armazenamento, todas as requisições são recusadas e o diagnóstico mostra o armazenamento em erro. O bucket é endereçado no caminho da URL: nenhum DNS por bucket é necessário.
Garage, se você não tiver um — garage.enabled: true implanta o Garage, um armazenamento S3 open source desenvolvido na França. Um Job cria o bucket e a chave de acesso do FoxPlan na instalação; as credenciais são geradas e colocadas no Secret. A seção s3 é então ignorada. O Garage roda em um único nó: faça backup dele (veja Backup).
Sem armazenamento, a aplicação funciona; apenas os arquivos ficam indisponíveis. Nenhuma cota é aplicada on premise.
Número de instâncias
O número de instâncias da aplicação é definido no seu arquivo de valores, de uma destas duas formas.
Número fixo — app.replicas:
app:
replicas: 3
Automático — conforme a carga de CPU, entre um mínimo e um máximo (exige metrics-server). app.replicas é então ignorado:
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 6
targetCPUUtilizationPercentage: 70
Aplique com helm upgrade (comando completo em Atualização de versão) e verifique:
kubectl -n foxplan get pods -l app.kubernetes.io/component=app
kubectl -n foxplan get hpa # com escalonamento automático
Defina sempre esse número nos valores: um kubectl scale é sobrescrito na atualização seguinte. Cada instância solicita 1 vCPU e 4 Gi de memória (app.resources): seus nós devem poder acomodar todas, incluindo o máximo do escalonamento. A partir de 2 instâncias, o chart adiciona um PodDisruptionBudget que mantém pelo menos uma em execução durante as operações nos nós. Referência: 2 instâncias de 100 a 500 usuários, escalonamento automático acima disso.
As sessões ficam no Redis e as tarefas agendadas usam um bloqueio no Redis: as instâncias dividem os usuários sem afinidade de sessão, e uma tarefa é executada uma única vez.
Alta disponibilidade
- Aplicação: 2 instâncias ou mais — veja Número de instâncias.
- MongoDB: o MongoDB do chart é um nó único. Para ter alta disponibilidade, use um replica set (operador MongoDB, serviço gerenciado) com
mongodb.enabled: falseemongodb.externalUri: mongodb://utilisateur:mot-de-passe@hote1,hote2,hote3/foxplanweb?replicaSet=rs0&authSource=admin. - Redis: sempre implantado pelo chart, com persistência em disco. Uma indisponibilidade do Redis desconecta os usuários sem perda de dados: ele guarda apenas as sessões e o cache.
Certificados de entrada e de saída
O FoxPlan usa dois certificados diferentes, um por sentido de circulação:
| Fluxos de entrada | Fluxos de saída | |
|---|---|---|
| Função | Provar aos navegadores que o servidor é mesmo host | Permitir que a aplicação reconheça os seus serviços internos: AD FS, SMTP com TLS, S3… |
| Usado por | O controlador de Ingress | Os pods foxplan-app (Java) |
| Conteúdo | O certificado do domínio FoxPlan e a sua chave privada | O certificado público da autoridade que assinou esses serviços — raiz e intermediários. Nunca uma chave privada |
| Local | Secret TLS foxplan-tls (ingress.tls.secretName), ou cert-manager | Valor app.outboundCaCerts, ou ConfigMap existente indicada em app.outboundCaConfigMap |
| Formato | PEM: --cert = certificado do servidor seguido dos intermediários, --key = chave não criptografada | PEM, nomes de chaves em .crt. Um arquivo pode conter vários certificados |
| Obrigatório | Sim | Somente se um serviço chamado for assinado por uma autoridade não pública |
| Aplicação | O controlador relê o Secret quando ele é atualizado | Na inicialização dos pods. O helm upgrade os reinicia; uma ConfigMap externa modificada exige kubectl -n foxplan rollout restart deployment foxplan-app |
Não são os mesmos certificados, mas costumam vir da mesma PKI: o certificado da autoridade raiz que assinou o do FoxPlan é então também o que deve ser fornecido para os fluxos de saída.
Para os fluxos de saída:
# foxplan-values.yaml
app:
outboundCaCerts:
ca-interne.crt: |
-----BEGIN CERTIFICATE-----
MIIDFzCCAf+gAwIBAgIU...
-----END CERTIFICATE-----
ou, sem copiar o arquivo nos 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'
O chart recusa um nome que não termine em .crt e um conteúdo que traga uma chave privada; ele só mantém os blocos BEGIN/END CERTIFICATE. Para saber se uma autoridade é necessária e converter um .cer, .p7b ou .pfx para PEM, veja Docker Compose: os comandos são os mesmos.
Segredos
Deixados vazios nos valores, a chave JWT e as senhas do MongoDB e do Redis (e as chaves do Garage) são geradas na primeira instalação, guardadas no Secret foxplan-secrets e depois preservadas a cada atualização. Esse Secret também sobrevive a helm uninstall.
SECRET_KEY_JWT também criptografa as configurações SSO armazenadas no banco. Restaurar um banco sem essa chave torna essas configurações ilegíveis.
kubectl -n foxplan get secret foxplan-secrets -o yaml > foxplan-secrets.yaml
Para gerenciar os segredos você mesmo (Vault, External Secrets…), crie um Secret que contenha SECRET_KEY_JWT, MONGO_PASSWORD, REDIS_PASSWORD, MAIL_SMTP_PASSWORD, FILE_S3_ACCESS_KEY e FILE_S3_SECRET_KEY (mais URL_MONGO com um MongoDB externo), e indique-o em secrets.existingSecret. As senhas do MongoDB devem permanecer alfanuméricas: elas entram tal como estão na URL de conexão.
Backup
Faça backup em conjunto, no mesmo momento: do banco, dos arquivos e do Secret.
Banco — a quente, a partir do pod MongoDB, que inclui as ferramentas:
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, o mesmo comando com mongorestore --drop e kubectl exec -i … < archive. Em seguida, limpe o cache do Redis, caso contrário a aplicação serve dados desatualizados:
kubectl -n foxplan exec foxplan-redis-0 -- sh -c 'redis-cli -a "$REDIS_PASSWORD" --no-auth-warning FLUSHALL'
Arquivos no Garage — a quente, com o rclone a partir de uma estação de administração:
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, inverta a origem e o destino. Com o seu próprio armazenamento S3, aplique a sua política de backup habitual. Os snapshots de volumes da sua plataforma (VolumeSnapshot) também servem, desde que o MongoDB e o Garage sejam capturados no mesmo momento.
Atualização de versão
helm upgrade foxplan foxplan-1.0.0.tgz -n foxplan -f foxplan-values.yaml
- Versão fixada (
image.tag): altere o valor e execute o comando novamente. - Com
latest: o comando sozinho não muda nada se os valores forem idênticos. Reinicie os pods para que eles baixem novamente a imagem:
kubectl -n foxplan rollout restart deployment
A implantação da aplicação é progressiva: uma nova réplica inicia e fica pronta antes que a antiga pare.
Desinstalação
helm uninstall foxplan -n foxplan
Os volumes (MongoDB, Redis, Garage) e o Secret são preservados: uma reinstalação recupera os dados. Para apagar tudo, exclua em seguida os PersistentVolumeClaim e o Secret do namespace.
Solução de problemas
| Sintoma | Causa provável |
|---|---|
helm recusa: «image.registry est obligatoire» | Valor obrigatório ausente: image.registry, host, mail.from ou mail.host |
Pod foxplan-export em Pending | Nenhum nó amd64: adicione um, ou export.amd64Only: false se os seus nós ARM emularem amd64 |
Pod foxplan-app reiniciando em loop | Logs: kubectl -n foxplan logs deploy/foxplan-app. Na maioria das vezes, MongoDB ou Redis inacessível |
Anexo recusado, erro 413 | Limite de envio do controlador de Ingress: veja o quadro ingress-nginx |
| Armazenamento em erro no diagnóstico | Região s3.region diferente da do armazenamento, ou armazenamento inacessível a partir dos pods |
| Nenhum código de registro recebido | SMTP: o diagnóstico da página de login indica se ele está acessível |
latest não se atualiza | kubectl -n foxplan rollout restart deployment: veja Atualização de versão |
| Configuração SSO ilegível após uma restauração | Secret foxplan-secrets diferente do que estava em vigor no momento do backup |
PKIX path building failed nos logs de foxplan-app | Autoridade interna do AD FS, do SMTP ou do S3 ausente de app.outboundCaCerts: veja Certificados de entrada e de saída |