Pular para o conteúdo principal

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​

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 imagens do FoxPlan

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​

ComponenteObjeto KubernetesFunção
AplicaçãoDeployment foxplan-appServidor FoxPlan (porta 5000), uma ou mais réplicas
DocumentaçãoDeployment foxplan-docsEsta documentação, servida em /docs
Exportação dos planejamentosDeployment foxplan-exportPDF, PNG, Excel, MS Project e Primavera, servidos em /gantt-export
MongoDBStatefulSet foxplan-mongodbBanco de dados, um nó, volume persistente
RedisStatefulSet foxplan-redisSessões e cache, volume persistente. Sempre implantado: o FoxPlan não inicia sem ele
Garage (opcional)StatefulSet foxplan-garageArmazenamento S3 dos arquivos, se você não tiver um
RoteamentoIngress 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:

ImagemUtilizada
mongo:8.2.7Sempre, exceto com MongoDB externo (mongodb.enabled: false)
redis:7.4Sempre
nginxinc/nginx-unprivileged:1.28-alpineProxy do pod de exportação
dxflrs/garage:v2.1.0, curlimages/curl:8.14.1Somente com garage.enabled: true
Setor sensível: use o seu registro interno

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.

SentidoPortaEntreFunção
Entrada443 (TCP)Estações dos usuários → controlador de IngressA aplicação e o retorno do SSO
Entrada80 (TCP)Estações dos usuários → controlador de IngressRedirecionamento para 443; desafio HTTP-01 do cert-manager
Saída443 (TCP)Pods foxplan-app → provedor de identidadeTroca 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.extraEnv e app.extraSecretEnv. A URL de redirecionamento é construída a partir de host: https://<host>/login/oauth2/code/azure ou …/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-Host e X-Forwarded-Port definidos pelo controlador de Ingress. O botão Verificar a configuração mostra a URL exata a declarar.
TLS terminado antes do Ingress

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:

ComponenteProcessador solicitadoMemória solicitadaLimite de memóriaVolume
Aplicação (por réplica)14 Gi6 Gi—
MongoDB0,52 Gi4 Gi50 Gi
Redis0,1256 Mi1 Gi5 Gi
Exportação (3 contêineres)0,36656 Mi2,6 Gi—
Documentação0,0132 Mi256 Mi—
Garage (opcional)0,1256 Mi1 Gi1 Gi + 100 Gi

Referências para a capacidade disponível nos nós:

UsuáriosProcessadorMemóriaDisco
Até 1004 vCPU16 GB100 GB
De 100 a 5008 vCPU32 GB, com 2 réplicas da aplicação200 GB
Acima dissoEscalonamento 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.

ingress-nginx

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​

ValorPadrãoFunção
image.registry—Endereço do registro do FoxPlan. Obrigatório
image.taglatestVersão das imagens do FoxPlan
image.pullPolicyAlwaysCom 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.secretNamefoxplan-tlsSecret TLS do domínio
mail.*—Servidor SMTP. Obrigatório
s3.*vazioO seu armazenamento S3 — veja Armazenamento de arquivos
app.replicas1Réplicas da aplicação
autoscaling.enabledfalseEscalonamento automático conforme o processador
app.extraEnv, app.extraSecretEnv—Variáveis adicionais: SSO Microsoft ou Google…
mongodb.enabled, mongodb.externalUritrueMongoDB integrado, ou o seu
export.amd64OnlytrueColoca o pod de exportação em um nó amd64
garage.enabledfalseArmazenamento S3 integrado
secrets.*geradosChave 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: false e mongodb.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 entradaFluxos de saída
FunçãoProvar aos navegadores que o servidor é mesmo hostPermitir que a aplicação reconheça os seus serviços internos: AD FS, SMTP com TLS, S3…
Usado porO controlador de IngressOs pods foxplan-app (Java)
ConteúdoO certificado do domínio FoxPlan e a sua chave privadaO certificado público da autoridade que assinou esses serviços — raiz e intermediários. Nunca uma chave privada
LocalSecret TLS foxplan-tls (ingress.tls.secretName), ou cert-managerValor app.outboundCaCerts, ou ConfigMap existente indicada em app.outboundCaConfigMap
FormatoPEM: --cert = certificado do servidor seguido dos intermediários, --key = chave não criptografadaPEM, nomes de chaves em .crt. Um arquivo pode conter vários certificados
ObrigatórioSimSomente se um serviço chamado for assinado por uma autoridade não pública
AplicaçãoO controlador relê o Secret quando ele é atualizadoNa 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.

Faça backup do Secret junto com o banco

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​

SintomaCausa provável
helm recusa: «image.registry est obligatoire»Valor obrigatório ausente: image.registry, host, mail.from ou mail.host
Pod foxplan-export em PendingNenhum nó amd64: adicione um, ou export.amd64Only: false se os seus nós ARM emularem amd64
Pod foxplan-app reiniciando em loopLogs: kubectl -n foxplan logs deploy/foxplan-app. Na maioria das vezes, MongoDB ou Redis inacessível
Anexo recusado, erro 413Limite de envio do controlador de Ingress: veja o quadro ingress-nginx
Armazenamento em erro no diagnósticoRegião s3.region diferente da do armazenamento, ou armazenamento inacessível a partir dos pods
Nenhum código de registro recebidoSMTP: o diagnóstico da página de login indica se ele está acessível
latest não se atualizakubectl -n foxplan rollout restart deployment: veja Atualização de versão
Configuração SSO ilegível após uma restauraçãoSecret foxplan-secrets diferente do que estava em vigor no momento do backup
PKIX path building failed nos logs de foxplan-appAutoridade interna do AD FS, do SMTP ou do S3 ausente de app.outboundCaCerts: veja Certificados de entrada e de saída