Network Policy & Autoscaling
Como o isolamento de rede é aplicado e como o HPA decide quando escalar
NetworkPolicy: isolamento aplicado pelo CNI
Por padrão, todo pod de um cluster Kubernetes pode alcançar qualquer outro pod, em qualquer namespace, na rede plana de pods. Isso é um problema num cluster multi-cliente: sem restrição, um pod comprometido no namespace de um cliente poderia se conectar direto no Postgres de outro cliente, só sabendo o IP interno.
NetworkPolicy é o objeto que restringe isso — mas o objeto sozinho não faz nada: quem aplica a regra de fato é o CNI (Container Network Interface), o plugin de rede do cluster (aqui, flannel — ver orchestration-fundamentals para o papel dele no tráfego entre nodes). O CNI intercepta pacote a pacote e decide permitir ou derrubar com base nas NetworkPolicys que casam com aquele pod.
# charts/app/templates/networkpolicy.yaml
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
ingress:
- from: [{podSelector: {}}] # outros pods do mesmo namespace
- from: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: traefik}}}]
egress:
- to: [{podSelector: {}}] # mesmo namespace
- to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: kube-system}}}]
ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
- to: [{namespaceSelector: {matchLabels: {finatto.io/shared: "true"}}}]
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: [10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16]
podSelector: {} sem seletor específico significa “todo pod deste namespace” — a policy é default-deny: qualquer tráfego que não bate com uma das regras listadas é recusado. As exceções, na ordem: outro pod do mesmo namespace, o Traefik (para conseguir rotear ingress até aqui), DNS (namespace kube-system, portas 53), qualquer namespace marcado finatto.io/shared=true (MinIO, monitoring — infraestrutura que todo cliente precisa alcançar) e a internet pública exceto as faixas RFC1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) — que são justamente as faixas onde a rede interna de pods e nodes vive. Essa última regra é o que efetivamente impede um pod de um cliente de alcançar o Postgres de outro cliente pela rede de pods: ele pode sair para a internet, mas não pode entrar na faixa privada onde os outros namespaces existem.
PriorityClass: quem sobrevive sob pressão
NetworkPolicy decide quem pode falar com quem; PriorityClass decide quem o scheduler despeja primeiro quando falta recurso físico (ver o mecanismo de scheduling em orchestration-fundamentals). Três níveis, valor mais alto vence:
# cluster/priorityclasses.yaml
platform-critical value: 2000000 # Traefik, Prometheus, CNPG operator, ArgoCD, sealed-secrets
critical value: 1000000 # API do cliente, Redis, Postgres
standard value: 1000 # queue worker, scheduler, migrate job
A lógica: infraestrutura compartilhada precisa sobreviver antes de qualquer workload de cliente, porque ela é ponto único de falha para todos os clientes de uma vez — um Traefik despejado por falta de memória derruba o ingress de todo mundo simultaneamente, não só de um cliente. Dentro de um namespace de cliente, a API é critical e o worker de fila é standard: atraso processando um job é tolerável, a API fora do ar não é.
HPA: como o algoritmo decide escalar
HorizontalPodAutoscaler (HPA) é outro control loop: ele observa uma métrica (aqui, uso de CPU, coletado pelo metrics-server) contra um alvo, e ajusta replicas do Deployment para tentar manter a métrica média no alvo.
# charts/app/templates/hpa.yaml
spec:
minReplicas: {{ .Values.scaling.minReplicas }} # 1
maxReplicas: {{ .Values.scaling.maxReplicas }} # 4
metrics:
- type: Resource
resource: {name: cpu, target: {type: Utilization, averageUtilization: {{ .Values.scaling.targetCPU }}}} # 70
behavior:
scaleDown: {stabilizationWindowSeconds: 300, policies: [{type: Pods, value: 1, periodSeconds: 60}]}
scaleUp: {stabilizationWindowSeconds: 60, policies: [{type: Pods, value: 2, periodSeconds: 60}]}
Com targetCPU: 70, o HPA calcula réplicas desejadas = réplicas atuais × (uso médio de CPU / 70) a cada ciclo de avaliação e arredonda para cima. stabilizationWindowSeconds existe para não escalar para cima e para baixo repetidamente em resposta a um pico curto: em vez de agir no primeiro valor que ultrapassa o alvo, o HPA olha a janela inteira e usa a decisão menos agressiva dentro dela — 60s para subir (reage rápido, evita fila de request), 300s para descer (não desmancha capacidade extra só porque um minuto ficou ocioso). As policies limitam a velocidade da mudança em si: no máximo 2 pods a mais por minuto subindo, 1 pod a menos por minuto descendo — evita que um pico breve dispare 4 réplicas de uma vez ou que uma queda breve mate réplicas que vão fazer falta de novo em segundos.
spec.replicas do Deployment é omitido quando o HPA está ativo, e /spec/replicas está em ignoreDifferences no ApplicationSet (ver gitops-argocd) — sem isso, o self-heal do ArgoCD reverteria cada scale-up do HPA na sincronização seguinte, porque o Git não declara réplica nenhuma para esse campo.