CDN de alta disponibilidade na AWS com S3, CloudFront e Route53
Tags: aws, cloudnative, cdn
Published: 2021-11-30 03:06:08

Este é um guia sem enrolações (prometo entrar no detalhe apenas do que for essencial) sobre como provisionar uma rede de distribuição de conteúdo (CDN) na AWS. Os passos são executados via console (GUI/interface) e **as capturas de tela tendem a ficar obsoletas com o passar do tempo, podendo não representar o estado atual da interface à medida que as soluções evoluem.**

> Em outra ocasião abordarei a criação utilizando IaC (Infrastructure as Code)

Pressuponho que:

- já possua uma conta na AWS, se não tiver [crie e logo de início ganhe 12 meses de nível gratuito](https://portal.aws.amazon.com/billing/signup#/start) (as ferramentas envolvidas no artigo são privilegiadas);
- tem um domínio (hosted zone) cadastrado no Route53 (caso preferir utilizar o seu).
- está buscando distribuir conteúdo de maneira replicada pelo mundo e com baixa latência. Caso os usuários forem de uma única região e não existir preocupação com futuras expansões, é possível utilizar apenas o S3 com a [opção static website](https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html).

### O que são conteúdos estáticos?

São arquivos em que o conteúdo não é criado dinamicamente, embora possa mudar com determinada frequência, em sua essência ele permanecerá o mesmo até a próxima alteração e, no momento que ela (alteração) ocorrer podemos realizar diversas operações para garantir que o novo conteúdo seja distribuído para os clientes através de práticas como a invalidação de cache.

**Exemplos de arquivos estáticos:** Imagens, aplicações SPAs (já que são compostas de arquivos de texto/scripts), tipografias, vídeos (você até pode, mas o tipo da distribuição do CloudFront deste artigo não tem este propósito — assunto para outra ocasião) e outros.

## CDN

Content Delivery Network (Rede de distribuição de conteúdo) — tem como principal motivo contribuir para a melhora do tempo de carregamento de páginas e aumentar a disponibilidade do conteúdo ao redor do mundo, normalmente esta inclui diversos servidores de borda (edge) que são próximos do usuário (e isto é o que garante a baixa latência).

## S3

O S3 é o serviço da AWS voltado ao armazenamento de arquivos, nele criamos diversos buckets (baldes) que são os espaços onde iremos realizar o upload. Um bucket pode ser organizado por pastas e conter configurações de vida útil de um objeto/arquivo (tais como expiração), permissões, CORS e uma infinidade de outros.

## CloudFront

O CloudFront é um serviço vital para inúmeros outros dentro da nuvem da AWS, muitas das vezes você o utiliza sem perceber. É ele quem garante a disponibilidade ao redor do mundo e melhores taxas de latência com base na localização do cliente. Embora este artigo trate de arquivos estáticos, não é só para isto que o CloudFront serve, podendo distribuir APIs através de [Lambda@Edge](https://aws.amazon.com/pt/lambda/edge/), transmissão ao vivo e sob demanda de vídeo, distribuição de atualizações para dispositivos IoT e outros.

Podemos adicionar um nome de domínio para uma distribuição (nome dado a instância do CloudFront) utilizando o Route53 (serviço para gerenciamento de DNS da AWS), configurar regras de proxy adicionando uma Lambda à frente para controlar CSP — Content Security Policies [assunto para próximos artigos, mas se tiver pressa, [veja esse artigo do Jake Wells](https://aws.amazon.com/pt/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/)], revalidação de cache, monitorar tráfego e uma infinidade de cenários.

---

> Antes de provisionar qualquer recurso cloud é importante analisar os custos envolvidos para evitar surpresas, se estiver em uma conta de nível gratuito trate de certificar quais os limites.

### Definição de preço

A AWS oferece no nível gratuito (de 12 meses), a cada mês: 50 GB de transferência de dados para fora, 2 milhões de solicitações HTTP ou HTTPS e 2 milhões de invocações do CloudFront Functions. Para mais detalhes, [consulte a definição de preço](https://aws.amazon.com/pt/cloudfront/pricing/?nc=sn&loc=3).

Se não estiver no nível gratuito, faça uma simulação [utilizando a calculadora de preços](https://calculator.aws/#/createCalculator/CloudFront).

Cenário hipotético de um CloudFront com as seguintes características e seus respectivos custos:

- Região us-east-1 (N. Virginia)
- 10 GB por mês de tráfego de saída para a internet = 0.85 USD
- 10 GB por mês de transferência para o servidor de origem (solicitações POST e PUT) = 0.20 USD
- 100.000 requisições HTTPS por mês = 0.10 USD

**0.85 USD + 0.20 USD + 0.10 USD = 1.15 USD (Custo total por mês)**

## Vamos lá…

Meu console se encontra em inglês para facilitar o entendimento e evitar traduzir alguns termos técnicos (mesmo que sua conta estiver em português é comum o console não ser 100% traduzido e ainda aparecer termos em inglês).

### S3 Bucket

O S3 é o local onde vamos armazenar os conteúdos da CDN, para isto precisamos criar um bucket (balde). Localize o S3 na busca de serviços:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hb7gw904d5zo6q02ozpj.png)
 
Clique em Create Bucket:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/czcrxkid000bv2pdkoww.png)
 
Com base em minha experiência em determinado momento você pode desejar ativar alguns recursos no bucket que dependem de requisitos que sigam boas práticas de nomenclatura, como o [Transfer acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html) e para isto [obrigatoriamente o nome não pode conter](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html) `.`

**Mais sobre convenção de nomes:**

- [Boas práticas de nomes para buckets do AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html);
- [Requisitos para utilizar Transfer acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html).

Seguindo as boas práticas, vamos utilizar hífen `—` para separar as palavras, preencha os dados para a criação do bucket:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/067pv8dpax83k2fgcepq.png)

- **Bucket name**: deve ser um nome único em toda a AWS;
- **AWS Region**: como o CloudFront ficará à frente do nosso bucket e esta será a única maneira de acessar os conteúdos, preferi utilizar `us-east-1` porque os custos são inferiores à São Paulo `sa-east-1` — [Mais sobre custos do S3](https://aws.amazon.com/pt/s3/pricing/). (dependendo de seus planos, verifique a legislação quanto ao local de armazenamento dos dados);
- **Block Public Access settings for this bucket**: mantenha o padrão;
- **Bucket Versioning**: fica a critério, isto criará novas versões caso o documento enviado tenha o mesmo nome e facilitará rollback em caso de sobrescritas acidentais, em contrapartida tende a aumentar o consumo de armazenamento do bucket ao longo do tempo [se não for configurada uma rotina automática de limpeza](https://docs.aws.amazon.com/pt_br/AmazonS3/latest/userguide/object-lifecycle-mgmt.html);
- Mantenha as demais opções sem alterar e crie o bucket clicando em **Create bucket**.

Após isto seu bucket estará criado:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wj7cc441895n4uivwxqq.png)

No S3 por enquanto não há mais o que fazer, já existe um local para armazenar os arquivos. O próximo passo é criar a distribuição do CloudFront.
 
### Distribuição do CloudFront

Localize o CloudFront na busca de serviços:

![Pesquisa pelo CloudFront na busca por serviços.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u7mnwpcj9ektslzbnxqb.png)

Clique em **Create Distribution**:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4mn53320sh52tne1er7.png)
 
Agora a lista de configurações começa a ficar um pouco extensa, esta etapa é de extrema importância, resolvi numerar os passos iniciais para facilitar o entendimento:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xblhife54z181b6tn5s1.png)
 
- **Origin domain (1)**: é o bucket do S3 a ser utilizado pelo CloudFront, pesquise pelo nome criado na seção anterior, no meu caso é `tiagoboeing-cdn-article.s3.us-east-1.amazonaws.com` ;
- **Origin path**: é opcional, ao deixar em branco o CloudFront estará utilizando a raiz do bucket como caminho relativo, mas se preferir é possível especificar uma determinada subpasta como raiz;
- **Name (2)**: por padrão este campo será preenchido com o nome do bucket, defina o valor que preferir para facilitar a identificação da distribuição, costumo utilizar o domínio associado à mesma;

**S3 bucket access**: como deixamos nosso bucket privado (de maneira proposital para evitar acesso direto à ele), vamos configurar uma política que permita ao CloudFront ler os arquivos que lá estão;

- (3) — Marque **Yes use OAI (bucket can restrict access to only CloudFront)**;
- (4) — Vamos criar a access identity, clique em **Create new OAI**, preencha com o nome desejado conforme o padrão que preferir, costumo utilizar o prefixo sugerido (`access-identity-`) + domínio da CDN, clique em criar e a **Origin access identity** será preenchida automaticamente.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tuib6eg6vcuclsfk0mwm.png)

- (5) — Marque a caixa **Bucket policy → Yes, update the bucket policy**.
 
**Para facilitar, um resumo apenas do que alterei mais abaixo:**

- **Em Default cache behavior — Viewer → Viewer protocol policy**: Redirect HTTP to HTTPS;
- **Em Settings — Alternate domain name (CNAME)**: adicionei o domínio que pretendo usar e vou configurar no Route53 posteriormente;
- **Em Settings — Custom SSL certificate**: clique em **Request certificate** e na nova aba será aberto o [AWS Certificate Manager](https://aws.amazon.com/pt/certificate-manager/?nc=sn&loc=1) (o certificado SSL é gratuito), os passos são auto-guiados:
* **Request a public certificate** → em **Fully qualified domain name** coloque o domínio que irá utilizar na CDN → escolha como pretende verificar o domínio (se estiver configurado no Route53 use DNS validation pois a validação é automática) → Clique em **Request**;
* Via DNS: Após solicitar, acesse o certificado e em **Domains** clique em **Create records in Route 53 → Create records**.
- **Em Settings — Default root object**: se for utilizar a CDN para hospedar páginas estáticas (SPAs) defina index.html
- **Em Settings — Description**: opcionalmente preencha com a observação que preferir.

Crie a distribuição e aguarde, o status ficará como **Deploying** e isto pode levar alguns minutos.

Abaixo o resultado de todas as alterações realizadas para não restar dúvidas, em vermelho os locais modificados:

Após alguns minutos… Pronto! Temos a distribuição criada! 🚀

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ntzrurlo47nf74yamow.png)
 
Clicando no título é possível verificar as configurações:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2x1gbwsr4zv5vjy0jrgx.png)
 
---

### Criar registro no Route53 para o CloudFront

Como ao longo do artigo optei por utilizar o subdomínio `cdn-article.tiagoboeing.com` para exemplo, resta apenas criar este registro no Route53. Copie o domínio de **Distribution domain name** (inclusive se ignorar esta etapa, este será o endereço de acesso da CDN).

> O passo é opcional, mas leve em conta que ao utilizar o domínio padrão do CloudFront as alterações nas propriedades do domínio durante a criação da distribuição e o certificado não devem ser realizadas.

Vou ser breve nesta etapa.

Estou presumindo que já existe uma hosted zone configurada. Acesse o Route53 buscando na lista de serviços; Clique na **Hosted zone** do seu domínio e vá para **Create record**.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c2r2n8qfkq25un9auxxn.gif)
 
- **Record name**: coloque o subdomínio utilizado durante a criação do CloudFront, no meu caso `cdn-article` ;
- **Record type**: A — Routes traffic to an IPv4 address and some AWS resources;
- Marque a caixa de seleção **Alias** e escolha **Route traffic to → Alias to CloudFront distribution**
* E na caixa **Choose distribution** se tiver sorte tente localizar sua distribuição. (Aqui há uma incógnita, às vezes a AWS simplesmente não encontra a distribuição, por isso a dica de copiar a **Distribution domain name** anteriormente) — se não conseguir localizar pela caixa basta colar o valor.
- Mantenha as demais opções com os valores padrões.

---

## Hora da diversão, vamos aos testes

Recapitulando…

1. Criamos o bucket no S3 para armazenar os arquivos da CDN;
2. Criamos a distribuição do CloudFront, definimos um nome de domínio, solicitamos um certificado SSL e anexamos nela;
3. Apontamos um subdomínio para a distribuição.

Vamos tentar acessar o subdomínio criado, no meu caso `cdn-article.tiagoboeing.com` (para você estará inacessível pois removi após finalizar o artigo).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ptucs6ip5pyz4q1nygg.png)
 
Sem pânico! Não há conteúdo no bucket do S3 e neste caso haverá erro mesmo, mas perceba que ao digitar `http://` automaticamente será alterado o endereço para utilizar HTTPS.

### Adicione qualquer arquivo no S3

Adicionei um arquivo HTML chamado `index.html` para testar a propriedade **Default root object** que configuramos durante a criação do CloudFront. Basta arrastar e soltar o arquivo no bucket (não customizei nenhuma configuração, apenas fiz upload).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eylq4l37j739biap0qib.png)

E… vamos testar novamente recarregando a página.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8qixnn8k0jzwtk6z24ko.png)
 
Tudo certo! A CDN está pronta para ser utilizada.
 
Por segurança vamos tentar acessar o S3 diretamente sem passar pelo CloudFront, o endereço será `https://tiagoboeing-cdn-article.s3.us-east-1.amazonaws.com/index.html` e é esperado um erro de permissão, isto indica que o bucket realmente está privado e apenas acessível via domínio do CloudFront:

![Tudo certo, o bucket está privado.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fvinstgiueo2y2otyfuv.png)

### Invalidação de cache

O CloudFront possui um cache configurável, a distribuição que criamos utiliza as políticas padrões recomendadas, isto significa que o **CloudFront entregará ao usuário o conteúdo que estiver em seu cache quando disponível e não expirado/invalidado**. Esta é a razão pela qual ao realizar upload de um novo arquivo e/ou modificar um existente as alterações não são refletidas automaticamente, você precisar criar uma invalidação de cache!

No momento que uma invalidação for criada, assim que houver uma requisição para a distribuição o CloudFront verificará por novos arquivos e criará o cache novamente.

Esta foi a configuração de cache que utilizamos:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjjqqrm7c5iqm776glo9.png)
 
E aqui as políticas associadas a ela. Temos um TTL padrão de 24 horas (86.400 segundos).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xn0uwjrwieynu1nkyj0e.png)
 
Modifiquei o título do arquivo HTML para demonstrar o cache.
 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbywuj7s7ewsd6algm9r.png)

Realiza upload ao S3 e sobrescrito o `index.html` existente… Ao voltar no browser e recarregar a página ainda visualizamos o conteúdo antigo:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h9stuvjt1bijavbtg7ai.png)
 
O motivo? Bem! O santo cache. Vamos criar uma invalidação. Volte ao CloudFront e clique na distribuição, depois acesse a aba **Invalidations**:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/089wzqec6fmwgx1xv8d3.png)
 
Clique em **Create invalidation** → na caixa **Add object paths** é possível:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pt3prtwbwph5m4t7346p.png)
 
- escolher objetos (arquivos) individualmente: `index.html`, `pasta/arquivo.txt`, etc;
- invalidar tudo de uma pasta específica: `pasta/*`;
- invalidar todo o conteúdo de todo o CloudFront: `/*`.

Como no S3 temos apenas um arquivo chamado `index.html` e para exemplificar vou invalidar todo o conteúdo de todo o CloudFront informando: `/*`.

**Tente ser o mais restritivo possível, evite criar invalidações usando `/*` pois isto tende a exigir maior tempo à medida que o conteúdo aumenta e fim do mês haverá uma surpresa ao verificar a fatura 😁, já que [cada invalidação possui um custo.](https://aws.amazon.com/pt/cloudfront/pricing/)**

> Não há cobrança adicional para os primeiros 1.000 caminhos solicitados para invalidação a cada mês. Acima desse limite, será cobrado 0,005 USD por solicitação de invalidação de caminho.

Invalidação finalizada:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0b0byhhilyeabqza0be7.png)
 
Agora é simples, basta voltar ao endereço da CDN, atualizar a página e o novo conteúdo será entregue ao cliente:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c7dzh9rt9mxzemzizfa6.png)
 
<img width="100%" style="width:100%" src="https://media.giphy.com/media/d31w24psGYeekCZy/giphy.gif">

## Ideias de próximos passos:

E talvez até mesmo artigo futuro… 🤔 O que acha?

- Automatize via CI/CD o fluxo de upload dos arquivos para o S3 e invalidação do cache do CloudFront, utilize-o para entregar suas funcionalidades e correções com agilidade e/ou testar as MRs/PRs durante o desenvolvimento;
- Configure a segurança utilizando Lambda@Edge para definir CSP — Content Security Policy. Duas ferramentas úteis para auxiliar na depuração: [Mozila Observatory](https://observatory.mozilla.org/) e [Google CSP Evaluator](https://csp-evaluator.withgoogle.com/);
Configure a distribuição para navegar corretamente entre as rotas de um SPA;
- Crie uma stack IaC (Infrastructure as Code) e evite configurações manuais via console da AWS sempre que precisar de uma nova CDN, faça deploy em poucos minutos com apenas alguns comandos. Ferramentas que podem auxiliar: [Terraform](https://www.terraform.io/), [Serverless Framework](https://www.serverless.com/) ou [AWS CloudFormation](https://aws.amazon.com/pt/cloudformation/).

---

### Fique por dentro das novidades

- [Se inscreva em meu canal do Youtube para acompanhar tutoriais.](https://www.youtube.com/channel/UCSVbLbpdFImwC9T7v7FH3hg)