Ambiente local — infraestrutura e deployment¶
Configuração do ambiente local mantida em totvs-gateway-infra-local.
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.