Pular para conteúdo

Ambiente local — infraestrutura e deployment

Configuração do ambiente local mantida em totvs-gateway-infra-local.

Entrada / APIDomínio / processamentoSegurançaDados / cacheEventos / jobsSistema externo

Retângulos representam serviços ou grupos nomeados; cilindros, armazenamento; caixas com bordas duplas, mensageria; formas arredondadas, sistemas externos. As setas indicam chamadas ou transporte de dados, conforme o rótulo.

Ambientes e fronteiras

Ambiente: ambiente local experimental de desenvolvimento, integração e validação arquitetural. Kind, Helm e Helmfile são escolhas desse ambiente local e permitem demonstrar alternativas e melhorias. Não representam o processo corporativo de entrega, administrado pelo TOTVS Apps. Consulte ambientes corporativos e estudos experimentais.

Esta visão descreve somente o cluster backend do ambiente local. O cliente administrativo fica fora dessa fronteira; sua independência de deployment é contexto fornecido pelo responsável.

O Helmfile NGINX e o Helmfile experimental Istio declaram local-win e local-ubuntu, com contexto kind-totvs-gateway. Kind declara um control-plane e três workers. O default global.namespace é totvs-gateway; Core e Broker usam esse valor. Não há equivalência comprovada com a separação cloud em namespaces control-plane/data-plane.

Fontes: NGINX Helmfile, Istio Helmfile, Kind, valores globais.

Entrada do ambiente local

Como o perfil NGINX encaminha chamadas administrativas e de execução? Todos os workloads estão dentro da fronteira local; consumidores permanecem externos.

%%{init: {"theme":"base","themeVariables":{"fontFamily":"Arial, sans-serif","fontSize":"16px","lineColor":"#64748b","primaryTextColor":"#172b4d"},"flowchart":{"htmlLabels":true,"useMaxWidth":true,"nodeSpacing":28,"rankSpacing":38,"padding":14,"curve":"linear","subGraphTitleMargin":{"top":12,"bottom":16}},"sequence":{"useMaxWidth":true,"actorMargin":28,"width":145,"height":45,"diagramMarginX":32,"diagramMarginY":12,"messageMargin":24,"wrap":true,"mirrorActors":false},"themeCSS":".edgeLabel, .edgeLabel p {background-color:var(--diagram-surface)!important;color:var(--diagram-text)!important}\n.cluster rect {fill:var(--diagram-group)!important;stroke:var(--diagram-line)!important}\n.cluster-label span, .cluster-label text {color:var(--diagram-text)!important;fill:var(--diagram-text)!important}\n.flowchart-link, .actor-line {stroke:var(--diagram-line)!important}\nmarker path {fill:var(--diagram-line)!important;stroke:var(--diagram-line)!important}\n.actor {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\ntext.actor, .messageText, .loopText, .labelText {fill:var(--diagram-text)!important}\n.messageLine0, .messageLine1, .loopLine {stroke:var(--diagram-line)!important}\n.labelBox {fill:var(--diagram-surface)!important;stroke:var(--diagram-line)!important}\n.note {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.noteText {fill:var(--diagram-text)!important}\n.nodeLabel, .nodeLabel p, .nodeLabel span {color:var(--diagram-text)!important}\n.node .nodeLabel, .node .nodeLabel p {font-size:17px;font-weight:600;line-height:1.4;text-align:center}\n.nodeLabel small {display:inline-block;margin-top:4px;font-size:15px;font-weight:400;line-height:1.4;color:var(--diagram-description)!important}\ntext.actor, text.actor tspan {font-size:17px;font-weight:600}\n.node.entry rect, .node.entry polygon, .node.entry path, .node.entry circle {fill:var(--diagram-entry)!important;stroke:var(--diagram-entry-border)!important}\n.node.domain rect, .node.domain polygon, .node.domain path, .node.domain circle {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\n.node.security rect, .node.security polygon, .node.security path, .node.security circle {fill:var(--diagram-security)!important;stroke:var(--diagram-security-border)!important}\n.node.store rect, .node.store polygon, .node.store path, .node.store circle {fill:var(--diagram-store)!important;stroke:var(--diagram-store-border)!important}\n.node.async rect, .node.async polygon, .node.async path, .node.async circle {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.node.external rect, .node.external polygon, .node.external path, .node.external circle {fill:var(--diagram-external)!important;stroke:var(--diagram-external-border)!important}\n text.actor tspan, .loopText tspan, .labelText tspan, .messageText tspan, .noteText tspan {fill:var(--diagram-text)!important}\n\n"}}%%
flowchart TB
    client(["Clientes externos"]):::external
    subgraph lab["Ambiente local Kind · backend"]
      ingress["Ingress NGINX"]:::entry
      bff["BFF<br/><small>Administração</small>"]:::entry
      checker["Security Checker"]:::security
      broker["Broker<br/><small>Execução</small>"]:::domain
      ingress -->|HTTP| bff
      ingress -->|Subrequest| checker
      ingress -->|Autorizado| broker
    end
    client -->|Portas do host| ingress
    classDef entry stroke-width:1.5px;
    classDef domain stroke-width:1.5px;
    classDef security stroke-width:1.5px;
    classDef external stroke-width:1.5px;

Estado e eventos do Core

Como o Pod Core se conecta à persistência e à mensageria do ambiente local? O Dispatcher é um sidecar nesse deployment.

%%{init: {"theme":"base","themeVariables":{"fontFamily":"Arial, sans-serif","fontSize":"16px","lineColor":"#64748b","primaryTextColor":"#172b4d"},"flowchart":{"htmlLabels":true,"useMaxWidth":true,"nodeSpacing":28,"rankSpacing":38,"padding":14,"curve":"linear","subGraphTitleMargin":{"top":12,"bottom":16}},"sequence":{"useMaxWidth":true,"actorMargin":28,"width":145,"height":45,"diagramMarginX":32,"diagramMarginY":12,"messageMargin":24,"wrap":true,"mirrorActors":false},"themeCSS":".edgeLabel, .edgeLabel p {background-color:var(--diagram-surface)!important;color:var(--diagram-text)!important}\n.cluster rect {fill:var(--diagram-group)!important;stroke:var(--diagram-line)!important}\n.cluster-label span, .cluster-label text {color:var(--diagram-text)!important;fill:var(--diagram-text)!important}\n.flowchart-link, .actor-line {stroke:var(--diagram-line)!important}\nmarker path {fill:var(--diagram-line)!important;stroke:var(--diagram-line)!important}\n.actor {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\ntext.actor, .messageText, .loopText, .labelText {fill:var(--diagram-text)!important}\n.messageLine0, .messageLine1, .loopLine {stroke:var(--diagram-line)!important}\n.labelBox {fill:var(--diagram-surface)!important;stroke:var(--diagram-line)!important}\n.note {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.noteText {fill:var(--diagram-text)!important}\n.nodeLabel, .nodeLabel p, .nodeLabel span {color:var(--diagram-text)!important}\n.node .nodeLabel, .node .nodeLabel p {font-size:17px;font-weight:600;line-height:1.4;text-align:center}\n.nodeLabel small {display:inline-block;margin-top:4px;font-size:15px;font-weight:400;line-height:1.4;color:var(--diagram-description)!important}\ntext.actor, text.actor tspan {font-size:17px;font-weight:600}\n.node.entry rect, .node.entry polygon, .node.entry path, .node.entry circle {fill:var(--diagram-entry)!important;stroke:var(--diagram-entry-border)!important}\n.node.domain rect, .node.domain polygon, .node.domain path, .node.domain circle {fill:var(--diagram-domain)!important;stroke:var(--diagram-domain-border)!important}\n.node.security rect, .node.security polygon, .node.security path, .node.security circle {fill:var(--diagram-security)!important;stroke:var(--diagram-security-border)!important}\n.node.store rect, .node.store polygon, .node.store path, .node.store circle {fill:var(--diagram-store)!important;stroke:var(--diagram-store-border)!important}\n.node.async rect, .node.async polygon, .node.async path, .node.async circle {fill:var(--diagram-async)!important;stroke:var(--diagram-async-border)!important}\n.node.external rect, .node.external polygon, .node.external path, .node.external circle {fill:var(--diagram-external)!important;stroke:var(--diagram-external-border)!important}\n text.actor tspan, .loopText tspan, .labelText tspan, .messageText tspan, .noteText tspan {fill:var(--diagram-text)!important}\n\n"}}%%
flowchart TB
    subgraph lab["Ambiente local Kind · estado e eventos"]
      core["Pod Core<br/><small>+ Outbox Dispatcher</small>"]:::domain
      mongo[("MongoDB")]:::store
      mq[["RabbitMQ"]]:::async
      core -->|Documentos| mongo
      core -->|Eventos do dispatcher| mq
    end
    classDef domain stroke-width:1.5px;
    classDef store stroke-width:1.5px;
    classDef async stroke-width:1.5px;
Dependência local Consumidor / relação
Valkey Serviços com cache e quotas, conforme configuração
MinIO, condicional Object Storage via S3
PostgreSQL Keycloak via SQL
OTel Collector, condicional Serviços com OTLP habilitado
Elasticsearch, condicional Exportação HTTP do Collector
Cache Monitor CronJob independente

O namespace default dos workloads Gateway é totvs-gateway. Perfis e dependências condicionais estão detalhados abaixo.

O desenho é resumido: as dependências por serviço estão na matriz. O perfil Ubuntu desabilita explicitamente Broker, Builder, Router, Checker, Discovery e Monitoring. A presença deles no chart não significa que sejam instalados por todo perfil.

Workloads e dependências

Item Configuração comprovada Limite
Agregador 21 dependências condicionais; 22 charts em apps Web Pages está fora da lista de dependências
Core / Dispatcher Dois containers no Pod Core Imagem do sidecar não é identificada pelo commit do Core
Cache Monitor CronJob em chart próprio; script no repo Invalidator Não é um novo microserviço Git
Tunnel Fonte e client Cloudflare presentes Chart local correspondente não localizado
MongoDB Replica set com uma réplica; persistência desabilitada nos valores comuns Não caracteriza HA ou política produtiva
Valkey / RabbitMQ Serviços locais com persistência declarada Persistência local não define a política de backup corporativa
Keycloak / PostgreSQL Dependência de identidade e banco local Não comprova PostgreSQL gerenciado cloud
MinIO / GCS Object Storage implementa ambos; local seleciona MinIO Não igualar configuração local a bucket produtivo
Telemetria Collector e Elasticsearch declarados Habilitação e destino dependem do perfil local

Fontes: Chart agregador, Core Deployment, Cache Monitor, valores Ubuntu, dependências comuns, Object Storage.

Ingresso e segurança declarada

O Ingress Broker usa POST /securitychecker/v1/auth e propaga X-Auth-Token e X-Gateway-Discovery. O BFF recebe a entrada administrativa. devMode também condiciona rotas/portas de desenvolvimento. Exposição pública, NetworkPolicy e autorização interna devem ser definidas para cada ambiente; as rotas de desenvolvimento não constituem política de segurança corporativa.

Istio tem Helmfile e manifests próprios. Istio e Kubernetes Gateway API estão em validação arquitetural exclusivamente no ambiente local, conforme orientação do responsável. Não integram a entrega oficial corporativa. Objetivos, hipóteses e critérios propostos estão em Experimentos.

Fontes: Ingress Broker, Ingress BFF, rotas de desenvolvimento.

Imagens e portal documental

Tags mutáveis como develop e latest não identificam uma versão exata do código. As análises registram os riscos de rastreabilidade e as inconsistências da configuração local.

O portal é estático: MkDocs gera HTML, NGINX serve na porta 8080 e o Service mapeia porta 80. O pipeline existente depende de um template Azure externo. Fontes: Dockerfile documental, NGINX, manifests do portal, pipeline. O link do Dockerfile aponta à base do snapshot; a alteração documental atual permanece em revisão.