Network Policy & Autoscaling · MemphisLab Docs
MemphisLab Docs
k3s Platform

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.