Acoplamento entre CDN, ALB e ECS para entrega de frontend e assets, uma experiência do que não fazer

Acoplamento entre CDN, ALB e ECS para entrega de frontend e assets, uma experiência do que não fazer

1 7
calendar_today agoschedule4 min read

Existe um padrão recorrente em ambientes legados que, à primeira vista, “funciona”, mas que invariavelmente trava qualquer tentativa de evolução: o acoplamento entre CDN, load balancer e computação para servir tanto aplicação, quanto arquivos estáticos. Esse modelo normalmente não nasce de uma decisão arquitetural consciente, ele emerge aos poucos, à medida que novos requisitos são encaixados em cima de soluções já existentes. O resultado final é um sistema onde ninguém consegue afirmar com segurança de onde vem um arquivo, qual camada é responsável por qual comportamento, ou o que vai quebrar ao desligar um serviço.

Considere um cenário típico. Um usuário acessa o site:

curl -I https://example.com/static/images/banner.jpg

A resposta vem com headers mistos:

HTTP/2 200
server: nginx
x-cache: Miss from cloudfront
via: 1.1 cloudfront.net (CloudFront)

Já nesse ponto há um sinal claro de acoplamento: a requisição passa por uma CDN, mas quem responde é um servidor dentro de um container. Ou seja, a CDN não é a origem real dos assets; ela apenas encaminha.

O fluxo real costuma ser algo assim:

[ Client ]
     |
     v
[   CDN   ]
     |
     v
[   ALB   ]
     |
     v
[  ECS    ]
     |
     v
[  EFS    ]
     |
     v
[  ECS    ]
     |
     v
[   ALB   ]
     |
     v
[   CDN   ]
     |
     v
[ Client ]

Aqui está o problema estrutural: um arquivo estático que deveria ser servido diretamente por um storage otimizado depende de recursos de computação (ECS), rede interna, mount de filesystem (EFS) e health check de container. Qualquer falha em qualquer ponto desse caminho derruba a entrega de um simples .jpg.

Esse tipo de arquitetura gera comportamentos difíceis de prever, por exemplo:

  • Desligar o serviço de frontend não derruba os assets
  • Desligar a CDN derruba tudo, mesmo com backend saudável
  • Invalidar cache muda completamente o comportamento
  • Alterar uma regra de roteamento afeta endpoints não relacionados

Esses sintomas indicam múltiplas “fontes de verdade” competindo ao mesmo tempo.

Outro exemplo comum é o uso de endpoints internos como contrato público:

https://example.com/api/filesystem/versão/public/my-project/files/123/banner/image.jpg

Por trás disso, existe algo como:

# pseudocódigo
@app.route("/api/filesystem/versão/public/my-project/files/<id>/<path>/<filename>")
def serve_file():
    base = "/mnt/efs/project/files"
    return send_from_directory(f"{base}/{id}/{path}", filename)

Isso parece simples, mas cria um acoplamento crítico, a URL pública depende diretamente da estrutura interna de armazenamento (/mnt/efs/...). Qualquer mudança nesse layout quebra consumidores externos. Além disso, você transforma seu backend em um servidor de arquivos, algo que ele não foi projetado para ser.

Outro anti-pattern frequente é usar o load balancer como origem de CDN para arquivos estáticos. A configuração costuma parecer com isso:

CDN Behavior:
/static/* → ALB

ALB Rule:
Host: example.com AND Path: /static/* → ECS Service

Esse encadeamento não adiciona valor. Ele apenas aumenta latência, custo e pontos de falha. O CDN poderia servir diretamente do storage, mas está preso a um caminho indireto.

A consequência prática aparece em erros intermitentes:

HTTP/2 502
x-cache: Error from cloudfront

ou

HTTP/2 503
server: awselb/2.0

Esses erros não são bugs isolados, são sintomas de uma arquitetura onde a entrega depende do estado de múltiplos recursos.

Outro problema sério é quando múltiplas distribuições de CDN coexistem com responsabilidades sobrepostas. Por exemplo:

CDN #1 → frontend
CDN #2 → assets
CDN #3 → fallback legado

Sem isolamento claro de rotas (/static, /api, /*), a ordem de avaliação dos behaviors passa a definir o comportamento do sistema. Isso não é arquitetura, é efeito colateral de configuração.

O ponto central aqui é que arquivos estáticos não deveriam depender de computação, visto que já possuímos inúmeras soluções modernas e otimizadas, não estamos mais aqui precisando upar arquivos em sFTP com o Dreamweaver. Computação escala com CPU e memória, assets escalam com throughput e cache. Misturar esses dois mundos gera ineficiência e fragilidade.

A alternativa correta é simples conceitualmente, mas exige disciplina arquitetural. Separe responsabilidades de forma rígida.

O fluxo ideal para frontend:

[ Client ] ---> [ Frontend Hosting ]

Para assets:

[ Client ] ---> [ CDN ] ---> [ Object Storage ]

E para backend:

[ Client ] ---> [ API ] ---> [ Compute ]

Nesse modelo, cada camada faz exatamente uma coisa. A CDN entrega conteúdo estático. O storage armazena arquivos. O backend processa lógica. O frontend renderiza.

Se existe dependência externa de caminhos como /static, você não quebra isso, você abstrai. Um exemplo simples usando rewrite:

// next.config.js
module.exports = {
  async rewrites() {
    return [
      {
        source: '/static/:path*',
        destination: 'https://cdn.example.com/static/:path*',
      },
    ];
  },
};

Para o consumidor externo, nada muda:

https://example.com/static/image.jpg

Mas internamente, você já desacoplou completamente a origem.

Outro ponto crítico é a migração de arquivos que hoje estão em filesystem acoplado. Um script simples resolve isso:

aws s3 sync /mnt/efs/my-project/files s3://example-assets-bucket/project/files

Depois, a URL pode ser padronizada:

https://cdn.example.com/project/files/123/banner/image.jpg

Sem passar por backend, sem depender de mount, sem latência adicional.

Se for necessário manter compatibilidade com endpoints antigos como /api/filesystem, a abordagem não é manter o backend servindo arquivos, mas sim redirecionar:

@app.route("/api/filesystem/1.0/public/my-project/files/<path:file>")
def redirect_to_cdn(file):
    return redirect(f"https://cdn.example.com/project/files/{file}", code=301)

Isso remove a responsabilidade do backend gradualmente, sem quebrar contratos existentes.

O erro mais comum em ambientes como esse é tentar corrigir o problema ajustando regras de CDN ou ALB. Isso não resolve. O problema não está na configuração isolada, está na ausência de fronteiras claras entre camadas.

Enquanto arquivos estáticos dependerem de containers, enquanto CDNs dependerem de load balancers para assets, e enquanto URLs públicas refletirem detalhes internos de filesystem, qualquer mudança continuará sendo arriscada.

A saída exige uma mudança de modelo mental: infraestrutura não é apenas “fazer funcionar”, é garantir previsibilidade. E previsibilidade só existe quando cada componente tem uma responsabilidade única, explícita e isolada.

Esse é apenas um relato de experiência que passe em migração/modernização de sistema legado.

1 Comment

0 votes
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

10 Proven Ways to Cut Your AWS Bill

rogo032 - Jan 16

How to Reduce Your AWS Bill by 50%

rogo032 - Jan 27

AWS Certifications Are a Building Block, Not the Final Destination

Ijay - Jun 16

A Simple ECS + ECR Project for Beginners

vishnu99 - Mar 19

3 Ways to Configure Resources in Terraform

Ijay - Apr 14
chevron_left
245 Points8 Badges
Montes Claros/MGgithub.com/marcosvile
5Posts
1Comments
4Connections
Sou graduado em Análise e Desenvolvimento de Sistemas, com experiência em Linux, Docker, redes e amb... Show more

Related Jobs

View all jobs →

Commenters (This Week)

1 comment
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!