Skip to content

Arquitetura K8s

Arquitetura K8s — Cilium, ArgoCD, Karmada, Velero, integracao com storage

Documento de referencia/decisao consolidando:

  • Decisoes ja tomadas sobre stack K8s multi-site (Cilium, ArgoCD split, Karmada agnostico, K3s 3-node etcd, Traefik consolidado, MetalLB, WireGuard)
  • Como a stack K8s se conecta com a stack de storage (LINSTOR, PBS, MinIO) ja documentada em storage-strategy.md e linstor-conceitos.md
  • Tools complementares (Velero) e como eles se relacionam sem se sobrepor a o que ja existe
  • Workload stateful vs stateless — onde failover automatico funciona e onde nao funciona

Plano vivo de execucao: prompts/plano-endereçamento-rede.md (endereçamento canonico v4 — versionado no repo) e ~/.claude/plans/revisar-questionar-alguns-pontos-para-steady-piglet.md (revisao detalhada — vive fora do repo, na home do autor; nao versionada). Este doc e a versao consolidada/didatica daquele material e e a fonte de verdade publica do projeto.

Principio orientador

Do usuario (decisao explicita, persistida em memoria):

  • Maxima cautela com: cluster Proxmox (corosync, quorum, pmxcfs), storage ZFS das VMs, VyOS — sempre backup antes
  • Flexibilidade total (reinstalavel sem dor) com: LINSTOR, K3s, Karmada, Cilium, WireGuard, ClusterMesh

Consequencia pratica: se LINSTOR/K3s/Karmada/Cilium quebra, reinstala. Se Proxmox/ZFS quebra = problema serio, rollback imediato. Isso explica por que o doc nao trata Cilium/Karmada como “experimentais a evitar” — sao parte do design.

Estado das decisoes (2026-04-22, ainda valido)

Decidido

#DecisaoDetalhe
1CNI Cilium 1.17.xFranca: migrar Flannel→Cilium in-place. SP: Cilium desde inicio. Cluster ID 1 (fca), 2 (sp). CA compartilhada (caminho recomendado, nao absoluto)
2Cilium ClusterMeshInterconecta cluster-fcacluster-sp. Usa Pod/Service CIDRs nao-sobrepostos
3Cilium BGPv2Anuncia VIPs ao VyOS (Franca ASN 65001, SP 65002). MetalLB pra LB pool
4K3s 3-node etcd HA em Francavm-cpps-02 + 03 + 04 (04 a provisionar). Elimina SPOF de control plane
5Traefik consolidado no cluster FrancaDaemonSet em traefik-system. Dissolve K3s do debian-proxy. Reversivel via GitOps
6ArgoCD split federated+local1 ArgoCD por site, mesmo repo Git, sem conflito de lock
7Karmada como plataforma agnósticaMember clusters intercambiaveis via labels (nao clusterNames). Timing: agora, junto com Cilium (evita migracao dupla)
8MetalLB LB poolFranca 192.168.0.240-250, SP 192.168.10.240-250 (inclui endpoint ClusterMesh API)
9WireGuard site-to-site/30: 10.255.0.1 (Franca) ↔ 10.255.0.2 (SP)
10Pod CIDR / Service CIDRFranca 10.41.0.0/16 / 10.141.0.0/16. SP 10.42.0.0/16 / 10.142.0.0/16
11Layout repo Gitclusters/<member>/ + federated/ + shared/base/ (member intercambiavel)

Em aberto (depende de implementacao)

  • Velero — sera adotado quando houver workload K8s critica fora do Tutor; backup destino MinIO local
  • MinIO — ainda nao deployado; instalacao quando primeiro use case (datasets, NextCloud, ou destino Velero/PBS frio) aparecer
  • linstor-csi (Piraeus) dentro do K8s — design pendente: rodar Piraeus operator nas VMs K3s ou usar CSI proxy ao LINSTOR Proxmox? Decidir antes de migrar workloads stateful
  • Longhorn vs linstor-csi — alternativa K8s-native nao avaliada formalmente; default atual e LINSTOR ja existente
  • drbd-reactor — failover automatico do controller LINSTOR; nao essencial pra K8s

Stack por camada (estado alvo)

┌────────────────────────────────────────────────────────────────┐
│ Apps (CPPS, Tutor/OpenEdX, NextCloud, jupyter, GPU services) │ L8
├────────────────────────────────────────────────────────────────┤
│ GitOps / CD ........................... ArgoCD (1 por site) │ L7
├────────────────────────────────────────────────────────────────┤
│ Federacao multi-cluster ............... Karmada (agnostico) │ L6
├────────────────────────────────────────────────────────────────┤
│ Backup K8s (manifests + PVs) .......... Velero (planejado) │ L5
├────────────────────────────────────────────────────────────────┤
│ Ingress L7 ............................ Traefik DaemonSet │ L4'
├────────────────────────────────────────────────────────────────┤
│ LB / IPAM externo ..................... MetalLB + Cilium BGPv2 │ L4
├────────────────────────────────────────────────────────────────┤
│ K8s control plane ..................... K3s (3-node etcd HA) │ L4
├────────────────────────────────────────────────────────────────┤
│ CNI / rede pod / cross-cluster ........ Cilium 1.17.x + │ L3
│ ClusterMesh │
├────────────────────────────────────────────────────────────────┤
│ CSI / StorageClass K8s ................ linstor-csi (TBD) / │ L2
│ local-path (interim) │
├────────────────────────────────────────────────────────────────┤
│ Block storage HA ...................... LINSTOR + DRBD │ L1
├────────────────────────────────────────────────────────────────┤
│ Hipervisor / VMs ...................... Proxmox VE │ L0
├────────────────────────────────────────────────────────────────┤
│ Conectividade inter-site .............. WireGuard /30 + BGP │ L-1
└────────────────────────────────────────────────────────────────┘

Camadas tem responsabilidades distintas mas com dependencias de integracao reais. Trocar CNI ou CSI envolve mudancas operacionais e em manifests; Velero depende do que o CSI expoe; Karmada para stateful depende da estrategia de storage.

ArgoCD — GitOps split federated+local

Decisao

2 ArgoCDs (1 em cada cluster K8s). Mesmo repo Git. Sem ferramenta de lock cross-cluster.

Layout do repo

clusters/
├── franca/
│ ├── labels.yaml # site=franca, provider=onprem, capability=gpu, gpu.model=a5500, tier=prod, region=sudeste-br
│ ├── apps/ # apps de plataforma so de Franca (Traefik, ingress, monitoring local)
│ └── kustomization.yaml
├── sp/
│ ├── labels.yaml # site=sp, provider=onprem, capability=gpu, gpu.model=a5000, ...
│ ├── apps/ # apps de plataforma de SP
│ └── kustomization.yaml
federated/
├── tutor-openedx/ # apps que rodam em ambos os sites via Karmada
├── ...
shared/
└── base/ # bases Kustomize comuns

Por que split (e nao 1 ArgoCD federado)

  • Sem conflito de lock: cada ArgoCD escreve so no proprio cluster
  • Falha local nao quebra deploy do outro site: ArgoCD SP ofine nao impede deploy em Franca
  • Karmada cuida da federacao via API, ArgoCD nao precisa “saber” de multi-cluster
  • Reversibilidade: se Karmada for removido, cada ArgoCD continua autonomo

Apps em uso

  • tutor-openedx — ja em producao, mergeada em main 2026-04-17 (memory project_tutor_openedx)
  • Caddy mantido como router interno do Tutor (1 NodePort vs 6); Traefik na frente

Onde NAO usar ArgoCD

  • Recursos nao-K8s (Proxmox VMs, VyOS, switches, DNS) — esses moram em Pulumi/Ansible/Cloudflare
  • Manifests com computacao em runtime (ArgoCD prefere declarativo puro)

Cilium — CNI + ClusterMesh + BGP

Decisao

Cilium 1.17.x em ambos os clusters. 3 features importantes:

  1. CNI primario: substitui Flannel em Franca (in-place), default em SP
  2. ClusterMesh: interconecta os 2 clusters K8s no nivel de pods/services
  3. BGPv2: anuncia VIPs MetalLB ao VyOS de cada site (Cilium BGPv2 ↔ VyOS Franca/SP)

Configuracao K3s pra Cilium

Terminal window
k3s --flannel-backend=none \
--disable-network-policy \
--disable=servicelb \
--cluster-cidr=10.41.0.0/16 \ # ou 10.42.0.0/16 em SP
--service-cidr=10.141.0.0/16 # ou 10.142.0.0/16 em SP

Migracao Flannel → Cilium (Franca) — rollback

Nao basta kubectl drain se Cilium falhar ao subir. Drain so move pods entre nodes; se Cilium em si nao iniciar (CNI loader error, conflict de iptables, eBPF kernel module ausente), todos os nodes ficam com pods em CrashLoopBackOff e nao tem como Flannel retomar sozinho.

Procedure de rollback se Cilium nao subir limpo:

Terminal window
# 1. Em cada server K3s, restaurar os flags originais do Flannel
# Editar /etc/systemd/system/k3s.service (ou /etc/rancher/k3s/config.yaml)
# REMOVER: --flannel-backend=none --disable-network-policy --disable=servicelb
# Adicionar: --flannel-backend=vxlan (ou o backend que estava antes)
# 2. Remover Cilium (libera o CNI socket)
helm uninstall cilium -n kube-system
# 3. Restart K3s pra carregar Flannel de novo
systemctl restart k3s # nos servers
systemctl restart k3s-agent # nos workers
# 4. Validar pods saindo do CrashLoopBackOff
kubectl get pods -A | grep -v Running
# 5. Se ainda houver pods presos, reiniciar eles:
kubectl delete pods -A --field-selector status.phase!=Running

Pre-requisito de seguranca antes de migrar: snapshot Proxmox de cada VM K3s server. Rollback via snapshot e o caminho mais rapido se o procedimento acima nao restaurar.

ClusterMesh — pre-requisitos

  • Pod e Service CIDRs nao-sobrepostos entre clusters (✓ — 10.41 vs 10.42, 10.141 vs 10.142)
  • Conectividade IP entre cluster nodes (resolvido via WireGuard /30 + BGP)
  • Mesmo datapath mode (encapsulation OU native-routing) nos dois
  • Cluster ID unico (1 = fca, 2 = sp) e nome unico (cluster-fca, cluster-sp)

Sobre CA do Cilium: a abordagem recomendada (e adotada no plano) e CA compartilhada entre os clusters — simplifica setup, e o caminho mais documentado. ClusterMesh tambem suporta CAs separadas via clustermesh-apiserver com troca de certificados explicita; mais complexo de operar, raramente justificavel pra setup de 2 clusters numa mesma organizacao.

Por que Cilium (vs Flannel/Calico)

  • eBPF-based — substitui parte do datapath tradicional (kube-proxy iptables) conforme configuracao
  • Hubble — observability de fluxo de rede (complementa metrics/logs/traces, nao substitui)
  • Network Policies L7 — filtrar HTTP method/path, gRPC method (alem de L3/L4)
  • ClusterMesh nativo — sem precisar de Submariner ou tooling externo
  • BGP integrado — Cilium BGPv2 fala direto com VyOS, sem MetalLB-FRR-Bird etc

Onde NAO ajuda

Cilium so resolve rede dentro do K8s. VMs Proxmox fora de K8s (vyos, gateway, GPU bare-metal, edge) continuam usando rede tradicional — Cilium nao toca nelas.

Karmada — federacao multi-cluster agnostica

Decisao crucial

Karmada nao e “instalado pra integrar Franca+SP”. E plataforma de federacao onde member clusters sao intercambiaveis via labels.

# Cluster Franca
labels:
site: franca
provider: onprem
capability: gpu
gpu.model: a5500
tier: prod
region: sudeste-br
# Cluster SP (hoje)
labels:
site: sp
provider: onprem
capability: gpu
gpu.model: a5000
tier: prod
region: sudeste-br

Anti-pattern (corrigido pelo design agnostico)

# ERRADO — acoplado a clusterNames
clusterAffinity:
clusterNames: [franca, sp]
# CERTO — adiciona novo cluster so com labels
placement:
clusterAffinity:
labelSelector:
matchExpressions:
- key: provider
operator: In
values: [onprem]
- key: capability
operator: In
values: [gpu]

Cenarios futuros que o design absorve sem refator

CenarioAcao
SP cancelado, Hetzner-SP entraRemove cluster SP do Karmada, join Hetzner com mesmas labels. Policies inalteradas
Adiciona DigitalOcean pra burst de GPUJoin novo cluster com provider: do, capability: gpu. Policies existentes incluem o cluster como candidato elegivel
Reorganiza por GPU modelCria PropagationPolicy com gpu.model: a5000 em vez de hardcode de cluster

Importante sobre labelSelector: ele define o conjunto de candidatos que a politica considera elegiveis. A selecao efetiva (qual cluster recebe o workload, em que peso) depende ainda de:

  • ReplicaSchedulingType (Duplicated vs Divided) — se duplica em todos os candidatos ou divide
  • WeightPreference (StaticWeightList, DynamicWeight) — distribuicao por peso
  • ClusterTolerations e taints
  • Estado de saude reportado pelo cluster ao Karmada

Adicionar um cluster com as labels certas o torna elegivel, nao garante que recebera carga sem ajuste explicito de politica.

Limite real: workload stateful

Karmada distribui pods conforme PropagationPolicy. Mas pra workload stateful (DB, Tutor/OpenEdX, NextCloud), o storage do PV precisa existir nos 2 lados antes do failover — Karmada sozinho nao resolve isso. Opcoes:

Estrategia statefulMecanismo
App stateless (3 replicas em SP, 2 em Franca, sem state local)Karmada apenas — ideal pra services HTTP, controllers
App stateful com state em DB externo replicadoKarmada agenda app; DB replica via mecanismo proprio (Postgres logical replication, etc.)
App stateful com PV localKarmada nao faz failover automatico. Precisa replicar PV externamente (LINSTOR snapshot ship, Velero backup, etc.) e restore manual no destino

Recomendacao do design: comecar com workloads stateless federadas; stateful continuam pinados num site especifico via PropagationPolicy ate ter solucao de storage cross-site madura.

Reversibilidade

Se SP for cancelado e nenhum substituto externo entrar, Karmada vira debito. Opcao explicita no plano: removel-o e voltar pra ArgoCDs autonomos.

Failure mode do Karmada control plane (host cluster)

Karmada tem topologia host + members. O karmada-controller-manager, karmada-scheduler, karmada-apiserver rodam no host cluster. Pelo plano, host = Franca.

Cenario critico: Franca cai com o Karmada-host junto.

ComponenteContinua funcionando?Por que
K8s cluster SP standaloneCada cluster member roda autonomo; control plane K3s/etcd local nao depende do Karmada
ArgoCD-sp (deploy de apps locais a SP)ArgoCD split — cada um e independente
Apps stateless ja rodando em SPCilium + ClusterMesh continuam (ClusterMesh nao depende do Karmada)
Novos workloads federadosSem Karmada-host, nenhuma PropagationPolicy nova e processada
Re-federacao apos mudancaKarmada-host precisa estar up pra reconciliar
Trafego cross-cluster ja estabelecidoClusterMesh roda no datapath, nao precisa de Karmada

Implicacao: durante outage de Franca, SP continua servindo trafego mas vira “estatico” — nao recebe novas decisoes de federacao. Apps em SP continuam, novos deploys via ArgoCD-sp funcionam, mas qualquer redistribuicao orquestrada pelo Karmada para.

Mitigacoes possiveis (em aberto)

  1. Karmada-host em cluster dedicado (nao em Franca nem SP): VM separada, possivelmente em cloud provider mais resiliente. Aumenta complexidade
  2. Karmada-host HA com replicas em ambos os sites: Karmada suporta etcd externo + replicas multiplas do controller. Quando Franca cair, replica em SP assume. Exige etcd cluster cross-site (latencia importa, etcd e sensivel)
  3. Aceitar o limite e operar Karmada como “best-effort”: Franca cai, federacao para, mas apps continuam. SP standalone segue. Quando Franca volta, Karmada reconcilia

Recomendacao pra contexto atual: opcao 3. Equipe pequena, complexidade extra de etcd cross-site nao compensa o ganho. Aceitar que durante outage de Franca, Karmada nao orquestra federacao — apps continuam servindo trafego.

Velero — backup K8s

Decisao

Adotar quando houver workload K8s critica fora do Tutor. Hoje Tutor tem snapshots LINSTOR (PV) + git (manifests via ArgoCD) — protecao basica suficiente.

O que Velero faz (precisao importante)

Velero combina mecanismos distintos, frequentemente confundidos:

MecanismoO que capturaOnde mora o conteudo
Resource backup (manifests)Objetos K8s (Deployment, ConfigMap, Secret, PVC) em YAMLObject store (MinIO/S3)
CSI snapshotSnapshot do PV via VolumeSnapshot API (linstor-csi suporta)Storage backend (LINSTOR) — local no cluster
Volume data movement (uploader)Conteudo do PV copiado pra object store. Default em Velero ≥ 1.10: Kopia. Restic ainda suportado via --uploader-type=restic, mas nao e o caminho recomendado novoObject store (MinIO/S3)

Importante: CSI snapshot nao vira backup off-cluster automaticamente. Se voce so configura CSI snapshot, perde dados se o storage backend cair junto. Pra protecao real, combinar CSI snapshot + uploader Kopia — snapshot da consistencia, uploader envia pro MinIO. Velero orquestra os dois numa unica Backup resource.

Velero CSI plugin tambem suporta “data mover” baseado no snapshot CSI — copia o snapshot (em vez do filesystem) pro object store. Vantagem: backup point-in-time sem afetar a aplicacao. Esse e o caminho moderno que substitui o uso direto de Kopia/Restic em muitos casos.

Restore com Velero

  • Recriacao dos objetos K8s (Deployment, Service, ConfigMap, etc.) e quase sempre automatica
  • Reidratacao do PV depende: plugin usado, StorageClass do destino, modo de backup, mapeamento --existing-resource-policy
  • Pra K8s pra outro cluster (DR), pode precisar --namespace-mappings, --restore-pvs=true e ajustes manuais

Quando Velero vs PBS vs LINSTOR ship

VMs Proxmox tradicionais (vyos, gateway, GPU labs)
→ PBS (Proxmox Backup Server) — granularidade VM, dedup, restore granular
Apps em K8s (manifests + dados de PV)
→ Velero (resource backup + CSI snapshot + uploader Kopia, ou data mover via CSI)
→ backup destino: MinIO local em cada site, com replication MinIO cross-site
PVs em K8s (camada extra de DR de bloco)
→ LINSTOR backup ship (snapshot LINSTOR pro outro cluster ou S3)
→ granularidade resource (PV inteiro), nao K8s-aware
Datasets fora de PV (objetos, models, NextCloud blobs)
→ MinIO replication ou rclone scheduled

Pouca sobreposicao funcional direta entre Velero/PBS/LINSTOR ship — mas existe sobreposicao intencional de protecao, defesa em profundidade.

Escopo (corrige confusao comum)

  • Velero nao cobre VM Proxmox fora do K8s
  • Velero nao substitui PBS pra VMs tradicionais
  • Velero nao substitui LINSTOR backup ship (que opera no nivel block, K8s-agnostico)

linstor-csi vs alternativas K8s-native

Decisao pendente (em aberto)

Como o K8s consome storage replicado? Tres caminhos plausiveis:

OpcaoComo funcionaProsCons
A. linstor-csi (Piraeus)Operator dentro do K8s, satellites nas VMs K3s, controller compartilhado com LINSTOR ProxmoxReaproveita LINSTOR ja deployado; uma so stack de storageLINSTOR “dentro” de LINSTOR (VM disk e LINSTOR; PV dentro do VM e LINSTOR). Acoplamento operacional
B. linstor-csi com LINSTOR Proxmox diretoSatellites no K3s falam direto com LINSTOR controller na rede 10.10.10.xSem LINSTOR aninhadoExige rede do K3s alcancar LINSTOR — dependencia entre K8s e infra Proxmox
C. Longhorn (K8s-native, separado)Storage K8s desacoplado do LINSTOR. Replicacao Longhorn dentro do K3sDesacopla K8s do LINSTOR/Proxmox; equipe pequena tem 1 stack pra cada coisaDuas stacks de storage replicado pra operar; perde o LINSTOR existente em K8s

Status: nao decidido. Recomendacao do storage-strategy.md foi linstor-csi sem comparar Longhorn formalmente. Vale revisitar antes de migrar workloads stateful.

Longhorn como alternativa (em aberto)

  • Longhorn e storage replicado K8s-native — concorre com linstor-csi (nao com local-path)
  • Vantagem: stack autocontida no K8s, desacopla totalmente do LINSTOR/Proxmox. Equipe gerencia 1 stack pra cada coisa (LINSTOR pra VMs, Longhorn pra PVs K8s)
  • Desvantagem: passa a ter 2 mecanismos de replicacao DRBD-like operados em paralelo. Pra equipe pequena, mais carga operacional
  • Decisao pendente: nao escolhido nenhum dos dois ainda. Comparar formalmente antes de migrar workloads stateful pra K8s

OpenEBS

Familia de opcoes K8s-native (Mayastor, ZFS-localpv, jiva). Maior flexibilidade mas adocao menor que Longhorn. Nao avaliado em detalhe — fora do escopo atual.

MetalLB + Cilium BGPv2

Decisao

Modelo de ownership (importante — tem dois caminhos plausiveis)

Quem faz IPAM (alocacao de IP pro Service) vs quem anuncia o IP (BGP/L2)? Dois modos validos, nao misturar sem decidir explicitamente:

ModoIPAM (alocacao)Anuncio (rota externa)Quando
A. MetalLB IPAM-only + Cilium BGPv2 advertiseMetalLB controller (speaker desativado)Cilium BGP fala com VyOSQuando o pool de IPs precisa do address-management do MetalLB (memory leasing, multi-pool com prioridade)
B. Cilium LB-IPAM standalone (sem MetalLB)Cilium LB IPAM (1.13+) usa CiliumLoadBalancerIPPoolCilium BGP fala com VyOSStack mais simples — uma so controller pra LoadBalancer, menos componentes

Risco se nao decidir: se MetalLB e Cilium ambos tentarem alocar IP pro mesmo LoadBalancer Service, conflito de owner. Sintoma: Service em estado pending ou IP “flapping” entre pools.

Recomendacao pra SP/Franca: comecar com modo B (Cilium LB-IPAM standalone) — menos um componente pra operar, suficiente pro caso atual. Reverter pra modo A so se aparecer feature do MetalLB que se prove necessaria.

Decisao concreta a tomar antes de provisionar: registrar como loadBalancerClass no Service ou via flag global no controller.

Pools (independente do modo escolhido acima)

SitePool LoadBalancer
Franca192.168.0.240-250
SP192.168.10.240-250

Inclui endpoint ClusterMesh API (necessario pra Cilium descobrir o outro cluster).

WireGuard site-to-site + BGP

Decisao do tunel

/30 dedicado pro tunel inter-site:

  • Franca: 10.255.0.1
  • SP: 10.255.0.2

Usado pra:

  • Conectividade pod-to-pod cross-cluster (via ClusterMesh)
  • BGP peering inter-site (Franca ASN 65001 ↔ SP ASN 65002)
  • Acesso administrativo SSH se necessario

Quais prefixes BGP sao trocados onde

BGP em duas camadas — intra-site (Cilium ↔ VyOS local) e inter-site (VyOS ↔ VyOS via WireGuard). Cada uma anuncia coisas diferentes:

PrefixAnunciado porOuvido porDirecaoAlcance
LoadBalancer VIPs locais (192.168.0.240-250 / 192.168.10.240-250)Cilium BGPv2VyOS localIntra-siteTracked pelo VyOS local pra port-forward + roteamento
Pod CIDR local (10.41.0.0/16 / 10.42.0.0/16)Cilium BGPv2VyOS localIntra-siteVyOS sabe rotear pods se algo na rede mgmt precisar alcancar pod (raro mas possivel pra debug)
Service CIDR local (10.141.0.0/16 / 10.142.0.0/16)nao anunciadoClusterIP nao precisa rotear externamente
Pod CIDR remoto (peer cluster)VyOS (apos receber do Cilium local)VyOS peer (via WireGuard)Inter-siteNecessario pra ClusterMesh — pod em SP fala com pod em Franca via tunel
LoadBalancer VIPs remotosPode ou nao ser anunciado inter-siteOpcionalAnunciar so se quiser failover de trafego entre VIPs cross-site (geralmente NAO — mantem regional)
WireGuard /30 (10.255.0.0/30)nao precisa anunciar via BGPRota direta na interface WG

Decisao concreta pendente: anunciar VIPs remotos via BGP inter-site? Default recomendado: nao — mantem trafego externo regional (cada site responde pelo seu DNS). Failover cross-site fica como decisao explicita de DR, nao automatico.

Pre-requisitos pra que ClusterMesh efetivamente funcione

Alem dos pre-requisitos do Cilium (CIDRs nao-sobrepostos, datapath consistente, cluster IDs):

  • VyOS Franca e VyOS SP precisam ter rota pra Pod CIDR do peer via tunel WG
  • Pra cada PodIP de SP que vier do tunel, VyOS Franca rotea pro K3s node correto via Cilium BGP local
  • Firewall VyOS precisa permitir trafego entre Pod CIDRs cross-site (default: bloqueado)

K3s 3-node etcd HA em Franca

Decisao

3 nos K3s servers (etcd HA): vm-cpps-02 + vm-cpps-03 + vm-cpps-04 (04 a provisionar).

Ganhos:

  • Elimina SPOF de control plane — 1 nó pode cair sem quebrar API
  • Permite drain pra migracao Cilium e qualquer manutencao
  • Karmada control plane confiavel — Karmada-host roda com etcd HA
  • Live migration entre nodes Proxmox — VMs K3s podem migrar com seus discos LINSTOR replicados

Migracao Franca

In-place com janela de manutencao. Com 3 nos, kubectl drain move pods antes do restart, downtime so do pod, nao do cluster.

Traefik consolidado

Decisao

Traefik DaemonSet em namespace traefik-system no cluster Franca principal. Dissolve o K3s separado do “debian-proxy” — menos clusters pra operar, HA ganho pelos 3 nos do cluster principal.

Reversivel via GitOps em ~1 dia se necessario (separar de novo).

Padrao de exposicao

Internet
↓ DNS Cloudflare → IP publico VyOS
VyOS (firewall + roteamento)
↓ port-forward 80/443 → MetalLB VIP em 192.168.0.240
MetalLB anuncia VIP via Cilium BGPv2
Traefik DaemonSet (traefik-system, NetworkPolicy L7 isolada)
↓ por Host header
Apps em namespaces (tutor-openedx, etc)

Distribuicao de Secrets e CAs cross-site

ArgoCD split + Karmada cria pergunta nao-trivial: como Secrets chegam em cada cluster sem virar repo de plain-text?

Opcoes consideradas

OpcaoComo funcionaProsCons
Sealed Secrets (Bitnami)Encrypta com chave do cluster destino; YAML cifrado vai pro GitSimples, GitOps-friendly, sem dependencia externaCada cluster tem chave propria — Secret cifrado pra Franca nao serve em SP. Reseal ao mover entre clusters
External Secrets Operator (ESO)K8s busca Secrets de backend externo (Vault, AWS SM, GCP SM, Bitwarden) em runtimeMesmo Secret resolvido em ambos os clusters; rotacao centralizadaDependencia de backend externo (precisa Vault ou similar)
SOPS + KMSEncrypta no Git via SOPS; ArgoCD plugin descriptografa antes de aplicarFunciona com mesmo Secret em multiplos clustersPlugin extra no ArgoCD; gestao de chaves KMS
Karmada PropagationPolicy + plain SecretSecret em texto plano no Git; Karmada propagaTrivialInseguro — secrets viram plain text no Git. Nao usar

Recomendacao pra contexto atual

External Secrets Operator + Vault self-hosted ou Sealed Secrets dependendo do volume de secrets:

  • Sealed Secrets se o volume e baixo (< 50 secrets) e a maioria sao por-site (DB password do Postgres em Franca, etc.). Reseal manual quando precisar mover. Sem dependencia externa
  • ESO + Vault se o volume cresce ou se varios apps compartilham os mesmos secrets cross-site (ex: token de API externa, credencial S3 do MinIO). Vault em VM dedicada ou em K8s

Status: nenhum dos dois deployado ainda. Tutor/OpenEdX usa Secrets diretos (Kustomize generators) — funcional mas nao escala. Decidir antes de federar muitos apps via Karmada.

CAs do Cilium ClusterMesh

Decisao do plano: CA compartilhada (ver secao Cilium). Em pratica:

Terminal window
# Gerar CA uma vez, exportar pra ambos clusters
cilium clustermesh ca generate
cilium clustermesh ca install --context=cluster-fca
cilium clustermesh ca install --context=cluster-sp

Material da CA mora no cilium-ca Secret no namespace kube-system de cada cluster — gerenciar como secret sensivel (igual kubeconfig do admin). Nao versionar em Git em texto plano.

Workload stateful vs stateless: onde failover funciona

Stateless (Cilium ClusterMesh + Karmada cobrem se configurado)

Apps sem state local (HTTP services, controllers, web frontends, batch jobs idempotentes):

  • Karmada distribui replicas em SP e Franca via PropagationPolicy
  • Cilium ClusterMesh roteia entre clusters somente services marcados como global — Service nao vira cross-cluster sozinho. Exige a annotation:
    metadata:
    annotations:
    service.cilium.io/global: "true"
    service.cilium.io/affinity: "local" # prefere endpoint do mesmo cluster
    Sem essa annotation, Service vive isolado no proprio cluster
  • Failover de trafego cross-site nao e automatico do ClusterMesh — precisa de uma camada externa (DNS health-checking, GSLB, anycast BGP, ou load balancer L4/L7 frontal). MetalLB+BGP local divulga VIPs dentro do site, nao orquestra failover entre sites
  • Storage cross-site nao precisa — pods sao descartaveis

Exemplos viaveis: Traefik (com global service), controllers Karmada, ArgoCD agents, jupyter notebooks sem disco, pods de inferencia stateless

Stateful (failover automatico NAO funciona sem trabalho extra)

Apps com state local em PVs (DB, Tutor, NextCloud, MLflow tracking):

  • Karmada agenda pods, mas o PV mora em um cluster especifico
  • Failover requer:
    • Storage replicado cross-site (LINSTOR snapshot ship + restore — manual)
    • Ou app-level replication (Postgres logical replication, MinIO bucket replication)
    • Ou aceita downtime + restore via Velero/PBS

Recomendacao: stateful apps fica pinada num site via PropagationPolicy (site: franca por exemplo). Failover e assistido (downtime + restore explicito), nao automatico.

Multi-site: como rede + storage + workload se compoem

SITE FRANCA SITE SP
───────────────── ─────────────────
[Apps no Git] ─── ArgoCD-fca ─── K8s [Apps no Git] ─── ArgoCD-sp ─── K8s
│ │
[Karmada (host=franca)] ────────────────────────►
PropagationPolicy: stateless = ambos sites
stateful = pin no site
K3s 3-node etcd HA K3s (single ou multi node)
Cilium 1.17 CNI Cilium 1.17 CNI
Hubble observability Hubble observability
│ ◄────── Cilium ClusterMesh ──────────► │
│ (Pod IPAM nao sobrepoe) │
│ │
MetalLB pool 192.168.0.240-250 MetalLB pool 192.168.10.240-250
│ ◄────── BGPv2 → VyOS-fca / VyOS-sp ────►│
│ │
▼ ▼
Traefik DaemonSet (consolidado) Traefik (se houver)
│ │
PVs via linstor-csi (TBD) PVs via linstor-csi (TBD)
│ │
▼ ▼
LINSTOR cluster fca LINSTOR cluster sp (SSD em prod)
(apos reset) pve-ippri-11/12/31, VIP 10.10.10.1
│ ◄── snapshot shipping cron ────────────►│
│ │
Proxmox VMs (K3s nodes + outros) Proxmox VMs (K3s nodes + outros)
│ │
PBS (cobre VMs nao-K8s) PBS (cobre VMs nao-K8s)
│ ◄── PBS sync ──────────────────────────►│
│ │
WireGuard /30 10.255.0.1 ◄────────────────► 10.255.0.2
│ │
MinIO local (planejado) MinIO local (planejado)
Velero destinos (apps K8s) Velero destinos (apps K8s)
Datasets / NextCloud objstore Datasets / NextCloud objstore
│ ◄── MinIO replication ─────────────────►│
│ │
VyOS (firewall, BGP, port-forward) VyOS (firewall, BGP, port-forward)

Tools considerados e descartados (por que nao agora)

Longhorn nao esta nesta lista — continua como decisao em aberto (ver “linstor-csi vs alternativas K8s-native” e “Decisoes pendentes”). Vale comparacao formal antes de migrar workloads stateful.

Submariner

  • Considerado: alternativa a Cilium ClusterMesh pra rede multi-cluster
  • Por que nao: Cilium ja escolhido como CNI, ClusterMesh vem “de graca” sem outro tooling. Submariner exigiria mais 1 componente
  • Reconsiderar se: precisar federar com cluster usando outro CNI (ex: AKS/EKS gerenciado)

Flux (vs ArgoCD)

  • Considerado: GitOps alternativo
  • Por que nao: ArgoCD ja em producao com Tutor, time familiarizado, dashboard maduro. Trocar nao traz ganho proporcional ao churn
  • Reconsiderar se: footprint operacional do ArgoCD virar problema (improvavel pra escala atual)

Kasten K10

  • Considerado: backup K8s comercial
  • Por que nao: Velero + Kopia + MinIO cobre o caso open-source. Orcamento academico nao justifica subscricao
  • Reconsiderar se: compliance (LGPD/HIPAA) exigir features especificas do K10

OpenEBS

  • Considerado: familia K8s-native (Mayastor, ZFS-localpv)
  • Por que nao: adocao menor que outras opcoes; ecosistema mais fragmentado
  • Reconsiderar se: precisarmos de feature especifica (ZFS-localpv, NVMe-oF)

Anti-patterns a evitar

ErroPor que
Tratar Cilium ClusterMesh como substituto de LINSTOR cross-siteCamadas diferentes — Cilium e rede, LINSTOR e storage. Pra apps stateful precisa dos 2; stateless basta ClusterMesh
Esperar failover automatico Karmada pra workload stateful sem replicacao de storageKarmada agenda pods; storage do PV nao migra sozinho. Stateful fica pinado por site, failover e assistido
Snapshot CSI Velero como unica protecao de PVsSnapshot CSI fica no mesmo storage backend. Se storage cair, perde tudo. Combinar com Restic pra MinIO off-cluster
Velero como backup de VMs ProxmoxVelero so cobre K8s; VM tradicional precisa PBS ou vzdump
Karmada hardcoded com clusterNamesAnula o design agnostico. Sempre usar labelSelector
ArgoCD pra recursos nao-K8s diretamentePulumi/Ansible cobrem infra; ArgoCD so pra K8s manifests (operadores podem ser excecao)
Misturar StorageClasses sem default claroConfunde devs (PVCs vao pra storage diferente sem ele saber). Padronizar default + nomes
Adotar Longhorn alem do linstor-csi2 stacks de replicacao — escolher uma
Adotar Submariner alem do Cilium ClusterMeshRedundante. ClusterMesh ja resolve

Decisoes pendentes (acompanhar)

  1. linstor-csi vs Longhorn — formalizar comparacao e decidir
  2. vm-cpps-04 — provisionar pra completar 3-node etcd HA Franca
  3. MinIO — onde rodar (em K8s? em VM dedicada?), quando subir
  4. Velero — quando comecar a usar (gatilho: primeira workload K8s critica alem do Tutor que precise de restore granular ou DR formalizada). Mesmo gatilho usado em “Em aberto” e na secao Velero
  5. drbd-reactor — adicionar pra failover automatico controller LINSTOR (P3)
  6. DR drill — definir cadencia (trimestral?) e template (ja em linstor-troubleshooting.md)

Referencias

Documentos internos

  • Plano detalhado: prompts/plano-endereçamento-rede.md (endereçamento canonico v4)
  • Plano de execucao detalhado: ~/.claude/plans/revisar-questionar-alguns-pontos-para-steady-piglet.md (fora do repo — home do autor)
  • Storage e cross-site: storage-strategy.md
  • LINSTOR conceitos: linstor-conceitos.md
  • Troubleshooting + DRBD verify + monitoring: linstor-troubleshooting.md
  • Tutor/OpenEdX (em producao): tutor-openedx.md, memory project_tutor_openedx
  • GPU platform: roadmap/gpu-platform.md, memory project_gpu_platform

Upstream