Skip to content

feat: VM proxy com IP brasileiro em southamerica-east1 - #156

Merged
Winzen merged 2 commits into
mainfrom
feat/proxy-br-southamerica-east1
Sep 24, 2026
Merged

Winzen merged 2 commits into
mainfrom
feat/proxy-br-southamerica-east1

Conversation

@Winzen

@Winzen Winzen commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Contexto

Algumas fontes de dados (Receita Federal, entre outras) bloqueiam requisições vindas de IP não-brasileiro. O cluster GKE roda em us-central1, então todo tráfego de saída sai com IP americano (#155).

O caso real mais urgente é br_rf_cnpj: as 5 sub-flows do dataset falham repetidamente em produção com ConnectionError: RemoteDisconnected ao chamar arquivos.receitafederal.gov.br — confirmado via logs reais do Prefect 3, não só suspeita.

Entre as duas opções levantadas na issue (cluster GKE novo em São Paulo vs. VM proxy), a VM proxy foi escolhida por custar ~1/4 a 1/8 do preço e não exigir manter infraestrutura de cluster extra pra resolver o bloqueio de uma fonte específica.

Detalhes técnicos

Fluxo geral

Só as chamadas de rede que precisam de IP brasileiro passam pela VM — todo o resto do pod (upload pro GCS, query no BigQuery, heartbeat da API do Prefect, leitura do Vault) continua saindo direto do GKE, sem tocar a VM:

Pod do flow (GKE, us-central1)
   │
   ├── requests/httpx com proxies=/proxy=  ──►  VM brasil-proxy (southamerica-east1)
   │   (só as 3 chamadas que precisam)          Squid :3128, Basic Auth
   │                                                  │
   │                                                  │ CONNECT (túnel opaco,
   │                                                  │ TLS ponta a ponta)
   │                                                  ▼
   │                                            arquivos.receitafederal.gov.br
   │
   └── GCS / BigQuery / API do Prefect / Vault  ──►  direto, sem proxy

Módulo terraform/brasil_proxy/

  • VM e2-small em southamerica-east1-a, IP externo estático, metadata_startup_script instalando e configurando Squid no primeiro boot.
  • Squid configurado como forward proxy autenticado, sem cache e sem interceptação de TLS (sem SSL-bump) — pra HTTPS, o Squid só abre um túnel CONNECT e repassa bytes; o handshake TLS acontece direto entre o pod e o servidor de destino, o Squid nunca vê o conteúdo:
    http_port 3128
    auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
    auth_param basic realm proxy
    acl authenticated proxy_auth REQUIRED
    http_access allow authenticated
    http_access deny all
    
  • Firewall libera só a porta do proxy, sem allowlist de IP (não há Cloud NAT gerenciado por Terraform em lugar nenhum hoje, então não dá pra garantir a origem do tráfego do GKE) — a autenticação do Squid é o único controle de acesso.
  • Senha gerada via random_password, alfanumérica (a primeira tentativa incluía [/] no conjunto de caracteres especiais, o que quebrava o parser de URL do Python quando embutida em http://user:senha@host:porta), guardada em Secret Manager.
  • terraform/outputs.tf reexporta IP/usuário/senha do módulo pra fora dele.

Distribuição da credencial

A credencial chega nos pods estendendo o secret gcp-credentials já existente (que já é injetado via envFrom em todo pod do work pool), em vez de criar um secret novo dedicado — segue o padrão real já usado no backend (k8s/website/django/prod), onde um secret vai sendo estendido incrementalmente em vez de multiplicar secrets por tema. Nenhuma chave antiga foi perdida (BASEDOSDADOS_CONFIG/CREDENTIALS_PROD/CREDENTIALS_STAGING, e em prod também DBT_SERVICE_ACCOUNT).

A credencial nunca passa pelo GitHub Actions nem fica salva na configuração de nenhuma deployment do Prefect — só existe no Secret Manager (registro) e no Secret real do Kubernetes.

A mudança de código em pipelines (branch test/proxy-br-rf-cnpj, PR basedosdados/pipelines#2094) fica reduzida a um helper compartilhado (brasil_proxy_url()/brasil_proxy_dict() em pipelines/utils/utils.py) e 3 chamadas de rede em br_rf_cnpj/utils.py — nenhuma mudança em flows.py, já que a variável chega automaticamente via envFrom.

Documentação completa (diagrama de sequência, mecânica do Squid, capacidade/concorrência): ftwca/investigacoes/iac/como-funciona-vm-proxy-squid.md

Testes

Algumas fontes de dados (Receita Federal, entre outras) bloqueiam
requisições vindas de IP não-brasileiro. O cluster GKE roda em
us-central1, então todo tráfego de saída sai com IP americano (iac#155).

Novo módulo terraform/brasil_proxy: VM pequena em southamerica-east1
rodando Squid como forward proxy autenticado (usuário/senha via
Secret Manager), IP externo estático, firewall liberando só a porta
do proxy. Sem allowlist de IP confiável (não há Cloud NAT gerenciado
por Terraform em lugar nenhum hoje), então a autenticação do Squid é
o controle de acesso principal.

Validado localmente antes desse PR: Squid rodando numa máquina local,
exposto via túnel ngrok, testado contra um deployment real em dev no
Prefect 3 (branch test/proxy-br-rf-cnpj em pipelines) — checagem de
data e pipeline completa (download + upload + dbt) funcionando via
proxy improvisado.

SKIP=infracost_breakdown: binário infracost não instalado localmente;
o workflow infracost.yaml já roda essa checagem de verdade no CI.
Estende o secret gcp-credentials já existente (injetado via envFrom em
todo pod do work pool) com a chave BRASIL_PROXY_URL, em vez de criar um
secret dedicado — segue o padrão real já usado no backend
(k8s/website/django/prod), onde um secret é estendido incrementalmente
em vez de multiplicar secrets por tema. Nenhuma chave antiga foi
perdida (BASEDOSDADOS_CONFIG/CREDENTIALS_PROD/STAGING, e em prod
também DBT_SERVICE_ACCOUNT).

Corrige também o conjunto de caracteres da senha gerada pelo
random_password: o override_special original incluía [ ], que quebra
o parser de URL do Python (ValueError: Invalid IPv6 URL) quando a
senha vai embutida em http://user:senha@host:porta. Trocado pra
alfanumérico puro (special = false).

Adiciona terraform/outputs.tf reexportando IP/usuário/senha do módulo
brasil_proxy pra fora dele, necessário pra extrair os valores reais
depois do apply.

Validado de ponta a ponta: br_rf_cnpj__dicionario rodou completo em
dev contra a VM e o secret reais (download da Receita Federal, upload,
sem tocar produção).

SKIP=infracost_breakdown,pretty-format-yaml: ferramentas locais com
ambiente quebrado (infracost não instalado, pretty-format-yaml sem
pkg_resources na venv do pre-commit) — sem relação com o conteúdo
deste commit; infracost.yaml roda de verdade no CI.
@github-actions

Copy link
Copy Markdown

💰 Infracost report

Monthly estimate increased by $24 📈

Changed project Baseline cost Usage cost* Total change New monthly cost
basedosdados/iac/terraform +$24 - +$24 (+5%) $482

*Usage costs can be estimated by updating Infracost Cloud settings, see docs for other options.

Estimate details
Key: * usage cost, ~ changed, + added, - removed

──────────────────────────────────
Project: main

+ module.brasil_proxy.google_compute_instance.brasil_proxy
  +$20

    + Instance usage (Linux/UNIX, on-demand, e2-small)
      +$19

    + Standard provisioned storage (pd-standard)
      +$0.60

+ module.brasil_proxy.google_compute_address.brasil_proxy_static_ip
  +$4

    + IP address (standard VM)
      +$4

+ module.brasil_proxy.google_secret_manager_secret_version.brasil_proxy_password
  +$0.06

    + Active secret versions
      +$0.06

    + Access operations
      Monthly cost depends on usage
        +$0.03 per 10K requests

+ module.brasil_proxy.google_secret_manager_secret.brasil_proxy_password
  Monthly cost depends on usage

    + Active secret versions
      Monthly cost depends on usage
        +$0.06 per versions

    + Access operations
      Monthly cost depends on usage
        +$0.03 per 10K requests

    + Rotation notifications
      Monthly cost depends on usage
        +$0.05 per rotations

Monthly cost change for basedosdados/iac/terraform
Amount:  +$24 ($458 → $482)
Percent: +5%

──────────────────────────────────
Key: * usage cost, ~ changed, + added, - removed

*Usage costs can be estimated by updating Infracost Cloud settings, see docs for other options.

36 cloud resources were detected:
∙ 18 were estimated
∙ 18 were free

Infracost estimate: Monthly estimate increased by $24 ↑
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Changed project                                    ┃ Baseline cost ┃ Usage cost* ┃ Total change ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━━━━━━━━━━┫
┃ basedosdados/iac/terraform                         ┃          +$24 ┃           - ┃   +$24 (+5%) ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━━━━━┛
This comment will be updated when code changes.

@mergify

mergify Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@Winzen Winzen self-assigned this Sep 23, 2026
@laura-l-amaral laura-l-amaral linked an issue Sep 23, 2026 that may be closed by this pull request
5 of 9 tasks
@laura-l-amaral

Copy link
Copy Markdown
Contributor

@Winzen antes (ou logo depois) do merge, vamos incluir essa mudança no manual da equipe dados, pra que quem for escrever/manter pipelines saiba que o proxy existe e como usar. Sugestão do que precisa entrar:

Por que tomamos essa decisão

  • Algumas fontes (ex.: Receita Federal / br_rf_cnpj) bloqueiam requisições de IP não-brasileiro, e o cluster GKE roda em us-central1 → toda saída tem IP americano (Egress com IP brasileiro pra pipeline bloqueada por IP americano #155). O sintoma típico é ConnectionError: RemoteDisconnected (ou timeout/403) só em produção, funcionando na máquina local.
  • Entre subir um cluster GKE novo em São Paulo e uma VM proxy, escolhemos a VM proxy (e2-small com Squid em southamerica-east1): custa ~1/4 a 1/8 do cluster (~US$ 24/mês) e não exige manter infraestrutura extra só pra resolver o bloqueio de algumas fontes.
  • O proxy é um túnel CONNECT sem interceptação de TLS: ele não vê o conteúdo das requisições.

Como a equipe dados deve usar

  • A variável BRASIL_PROXY_URL já chega automaticamente em todo pod do work pool (via secret gcp-credentials), não precisa mexer em flows.py nem na deployment.
  • Usar os helpers de pipelines/utils/utils.py:
    • requests: requests.get(url, ..., proxies=brasil_proxy_dict())
    • httpx: AsyncClient(proxy=brasil_proxy_url()) / Client(proxy=brasil_proxy_url())
  • Passar o proxy só nas chamadas à fonte que bloqueia IP estrangeiro. Nunca setar HTTP_PROXY/HTTPS_PROXY global no processo: isso desviaria GCS, BigQuery, Vault e a API do Prefect pela VM.
  • Em dev local a variável não existe e os helpers retornam None, então a chamada sai direto (sem proxy), sem precisar de if.
  • Exemplo de referência: pipelines/datasets/br_rf_cnpj/utils.py (feat: suporte a proxy com IP brasileiro em br_rf_cnpj pipelines#2094).
  • Checklist pra quando uma nova fonte começar a falhar só em prod: (1) confirmar nos logs do Prefect que é bloqueio de conexão; (2) aplicar o helper nas chamadas de rede daquela fonte; (3) testar o flow em dev antes de subir pra prod.
  • Pontos de atenção: a VM é uma instância única (e2-small), então evitar usar o proxy para downloads massivos ou paralelos sem necessidade; se o proxy cair, os flows que dependem dele vão falhar. Em caso de problema, acionar quem cuida da infra.

Vale linkar também a doc detalhada que você escreveu (diagrama de sequência, capacidade/concorrência).

@Winzen

Winzen commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

@laura-l-amaral feito — mergeado em basedosdados/manual-equipe-dados#7, só documentação (sem mudança de código).

Ficou dividido em 3 páginas, cada uma na categoria certa do manual:

  • ADR-0005 (infraestrutura/adr/): o "por que" da decisão e as alternativas descartadas.
  • Explicação (infraestrutura/prefect/explicacao/): a referência técnica que você pediu — diagrama de fluxo, mecânica do túnel CONNECT, como a credencial chega no pod, capacidade/concorrência.
  • Como fazer (pipelines/como-fazer/): uso dos helpers, o que nunca fazer, e o checklist pra quando uma fonte nova começar a bloquear.

@Winzen
Winzen merged commit 5548c7e into main Sep 24, 2026
3 checks passed
@Winzen
Winzen deleted the feat/proxy-br-southamerica-east1 branch September 24, 2026 02:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Egress com IP brasileiro pra pipeline bloqueada por IP americano

2 participants