Este artigo é uma continuação do primeiro sobre o mesmo assunto que foi escrito há algum tempo, mas sem abordar a utilização de Infrastructure as Code (IaC). Neste artigo, será apresentado um guia de como criar sua própria CDN de alta disponibilidade na AWS usando IaC com Terraform.
Utilize a CDN para hospedar um website estático e/ou servir arquivos estáticos com baixa latência e alta disponibilidade. Além de melhorar o desempenho do website, as CDNs garantem que o usuário sempre será servido do servidor mais próximo, o que reduz ainda mais a latência e garante uma experiência de navegação rápida e suave. Isso pode levar a um aumento de tráfego e conversões.
ℹ️ Para explicação dos serviços envolvidos, precificação e outras informações, leia o primeiro artigo sobre o mesmo assunto que demonstra a configuração via Console da AWS.
É necessário conhecimento do funcionamento do Terraform e da AWS. Não serão abordados aqui conceitos introdutórios sobre essas tecnologias.
Objetivos
Criar automaticamente toda a infraestrutura necessária dentro da AWS em poucos minutos e com alguns comandos, incluindo:
- Configuração dos serviços necessários;
- Criação dos registros DNS para validar o certificado e um alias para o CloudFront;
- Geração do certificado SSL para servir conteúdo utilizando protocolo HTTPS;
Pré-requisitos
- Conta AWS;
- AWS CLI devidamente configurado;
- Terraform devidamente instalado e configurado;
Composição da infraestrutura
- S3 como fonte dos arquivos;
- CloudFront para distribuir os arquivos;
- Route53 para criar os registros DNS no domínio próprio;
- Certificate Manager (ACM) para gerenciar os certificados SSL/TLS para a distribuição do CloudFront.
IaC - Infrastructure as Code
É uma prática de gerenciamento de infraestrutura de TI em que a infraestrutura é gerenciada por meio de código, em vez de configurações manuais. Com IaC, a infraestrutura é definida em arquivos de configuração que podem ser versionados, testados e implementados automaticamente, permitindo que os desenvolvedores gerenciem a infraestrutura de maneira mais eficiente e consistente. Ferramentas populares de IaC incluem o Terraform, o AWS CloudFormation e o Ansible.
Terraform
O Terraform é uma ferramenta de IaC que permite definir e provisionar infraestrutura em nuvem de forma declarativa, utilizando arquivos de configuração para descrever os recursos necessários. Com o Terraform, é possível criar, modificar e excluir recursos em diversos provedores de nuvem, como AWS, Azure e Google Cloud Platform, de forma consistente e replicável. Além disso, o Terraform possui recursos avançados, como a possibilidade de criar módulos reutilizáveis e gerenciar o estado da infraestrutura, permitindo uma gestão mais eficiente e segura da infraestrutura em nuvem.
Para mais informações, consulte a documentação oficial do Terraform.
ℹ️ Se desejar apenas o código-fonte para provisionar a infraestrutura acesse o repositório no GitHub.
Código-fonte
O projeto Terraform será dividido em vários arquivos para facilitar compreensão:
-
provider.tf
: Especifica com qual cloud e versões estamos trabalhando; -
main.tf
: Arquivo principal que centraliza configurações comuns do Terraform,data
,locals
, etc; -
outputs.tf
: Define as saídas dos recursos criados pelo Terraform; -
inputs.tf
: Define as variáveis de entrada para o Terraform; -
s3.tf
: Configurações do bucket, políticas e outros; -
certificate.tf
: Configurações do certificado SSL para servir conteúdo utilizando HTTPS; -
cloudfront.tf
: Criação e configuração do CloudFront; -
test-cdn.tf
: Apenas para demonstração e teste do funcionamento, este arquivo fará upload de um arquivo HTML para o bucket e invalidará o cache do CloudFront para que as alterações sejam refletidas rapidamente. Remova após validar a implementação; -
terraform.tfvars
: Arquivo onde o valor das variáveis pode ser especificado.
Para visualizar o conteúdo dos arquivos, confira o repositório no momento que o artigo foi escrito. As seções abaixo abordarão apenas arquivos chave para facilitar a compreensão.
Definição do provedor (provider.tf
)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.59.0"
}
}
}
provider "aws" {
skip_credentials_validation = true
}
Variáveis locais e queries (main.tf
)
locals {
service = var.service
stage = var.stage
resource_prefix_name = "${var.service}-${var.stage}"
route53_private_zone = var.route53_private_zone
route53_base_domain = var.route53_zone_domain
cdn_domain = var.cdn_domain
}
# Get AWS Account ID
data "aws_caller_identity" "current" {}
# Get Route53 Zone
data "aws_route53_zone" "domain_zone" {
name = "${local.route53_base_domain}."
private_zone = local.route53_private_zone
}
- São definidas algumas variáveis locais para utilizar nos demais arquivos;
- aws_caller_identity: consulta usada para obter o ID da conta e concatená-lo com o nome do bucket do S3 para garantir que o nome seja único na AWS e tentar evitar falhas na criação do bucket.
- aws_route53_zone: consulta uma Hosted Zone no Route53 que possua o domínio informado.
Definição das inputs
(inputs.tf
)
Na infraestrutura teremos as seguintes inputs
:
-
service
: nome com que os recursos serão criados dentro da AWS; -
stage
: estágio da aplicação caso desejar criar mais de uma CDN para o mesmo serviço (dev, test, prod). Nota: é possível utilizar a implantação contínua do próprio CloudFront; -
route53_private_zone
: define se a zona DNS é privada ou não; -
route53_zone_domain
: domínio da zona DNS -
cdn_domain
: nome do domínio para a CDN, pode ser deixado em branco para utilizar o domínio base (Hosted Zone do Route53); -
cloudfront_allowed_methods
: métodos HTTP permitidos no CloudFront; -
cloudfront_cached_methods
: métodos HTTP que serão mantidos em cache no CloudFront; -
cloudfront_default_root_object
: arquivo raiz padrão no CloudFront; -
cloudfront_http_version
: versão do protocolo HTTP no CloudFront.
variable "service" {
default = "terraform-cdn"
type = string
description = "Service name"
}
variable "stage" {
default = "dev"
type = string
description = "Stage (dev, test, prod)"
}
variable "route53_private_zone" {
default = false
type = bool
}
variable "route53_zone_domain" {
default = ""
type = string
description = "Route53 zone domain (base domain)"
}
variable "cdn_domain" {
default = ""
type = string
description = "Domain name (Where you want to deploy the CloudFront distribution. Leave empty to deploy inside base domain)"
}
variable "cloudfront_allowed_methods" {
default = ["GET", "HEAD", "OPTIONS"]
type = list(string)
}
variable "cloudfront_cached_methods" {
default = ["GET", "HEAD", "OPTIONS"]
type = list(string)
}
variable "cloudfront_default_root_object" {
default = "index.html"
type = string
}
variable "cloudfront_http_version" {
default = "http2"
type = string
}
Passando valores para as variáveis criadas (terraform.tfvars
)
Existem várias formas de passar valores para as variáveis do Terraform, incluindo a utilização de arquivos JSON, variáveis de ambiente ou a passagem de valores diretamente pela linha de comando no momento da execução do Terraform. Consulte a documentação oficial do Terraform para mais informações.
Neste exemplo será utilizado um arquivo com a extensão .tfvars
, então basta criar um arquivo terraform.tfvars
e definir o seguinte conteúdo:
route53_zone_domain = "tiagoboeing.com"
cdn_domain = "terraform-cdn.tiagoboeing.com"
Neste caso o domínio (Hosted Zone no Route53) é tiagoboeing.com
e a CDN será implantada em um subdomínio.
A propriedade route53_zone_domain
serve como base para o Terraform obter a Hosted Zone no Route53.
Criação do bucket no S3 (s3.tf
)
O primeiro passo é criar um bucket no S3 e definir uma política de segurança que restrinja o acesso público direto a qualquer conteúdo. Isso impedirá que um objeto seja tornado público diretamente via S3. O CloudFront será a única maneira de acessar os arquivos.
Crie um arquivo chamado s3.tf
e adicione o seguinte conteúdo:
resource "aws_s3_bucket" "bucket" {
bucket = "${local.resource_prefix_name}-bucket-${data.aws_caller_identity.current.account_id}"
tags = {
Stage = local.stage,
Service = local.service
}
}
resource "aws_s3_bucket_acl" "bucket_acl" {
bucket = aws_s3_bucket.bucket.id
acl = "private"
}
resource "aws_s3_bucket_public_access_block" "bucket_public_access" {
bucket = aws_s3_bucket.bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_policy" "bucket_policy" {
bucket = aws_s3_bucket.bucket.id
policy = jsonencode({
"Version" : "2008-10-17",
"Statement" : [
{
"Sid" : "AllowCloudFrontServicePrincipalReadOnly",
"Effect" : "Allow",
"Principal" : {
"Service" : "cloudfront.amazonaws.com",
},
"Action" : "s3:GetObject",
"Resource" : "arn:aws:s3:::${aws_s3_bucket.bucket.bucket}/*",
"Condition" : {
"StringEquals" : {
"aws:SourceArn" : aws_cloudfront_distribution.cloudfront.arn
}
}
}
]
})
depends_on = [
aws_s3_bucket.bucket
]
}
Estamos definindo os seguintes recursos:
-
aws_s3_bucket: Cria o bucket que o CloudFront utilizará como
origin
. - aws_s3_bucket_policy: Define uma política de acesso para que o CloudFront seja capaz de obter os objetos do bucket no S3 (necessário quando trabalhamos com buckets privados)
- aws_s3_bucket_acl: Define o controle de acesso ao bucket no S3 como privado.
- aws_s3_bucket_public_access_block: Define os parâmetros de acesso público para o bucket no S3 da AWS, bloqueando a criação de buckets públicos, políticas públicas, listagens públicas e controle de acesso público.
Criação e validação do certificado SSL (certificate.tf
)
resource "aws_acm_certificate" "certificate" {
domain_name = local.cdn_domain != "" ? local.cdn_domain : data.aws_route53_zone.domain_zone.name
validation_method = "DNS"
tags = {
Stage = local.stage,
Service = local.service
}
}
resource "aws_acm_certificate_validation" "certificate_validation" {
certificate_arn = aws_acm_certificate.certificate.arn
validation_record_fqdns = [for record in aws_route53_record.certificate_records : record.fqdn]
depends_on = [
aws_acm_certificate.certificate
]
timeouts {
create = "10m"
}
}
# Add Certificate Validation Records on Route53
resource "aws_route53_record" "certificate_records" {
for_each = {
for dvo in aws_acm_certificate.certificate.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.domain_zone.zone_id
}
Estamos definindo os seguintes recursos:
- aws_acm_certificate: criação do certificado SSL para a CDN utilizando o AWS Certificate Manager (ACM);
- aws_acm_certificate_validation: Define que a validação do certificado será realizada via DNS;
- aws_route53_record: adiciona registros no Route53 para validar o certificado SSL.
Criação do CloudFront (cloudfront.tf
)
resource "aws_cloudfront_origin_access_control" "cloudfront_acl" {
name = "ACL - ${local.resource_prefix_name}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_cloudfront_distribution" "cloudfront" {
enabled = true
is_ipv6_enabled = true
comment = "CDN for ${local.resource_prefix_name}"
default_root_object = var.cloudfront_default_root_object
http_version = var.cloudfront_http_version
aliases = local.cdn_domain != "" ? [local.cdn_domain] : local.route53_base_domain != "" ? [local.route53_base_domain] : []
origin {
origin_id = aws_s3_bucket.bucket.id
origin_access_control_id = aws_cloudfront_origin_access_control.cloudfront_acl.id
domain_name = aws_s3_bucket.bucket.bucket_regional_domain_name
}
default_cache_behavior {
target_origin_id = aws_s3_bucket.bucket.id
compress = true
allowed_methods = var.cloudfront_allowed_methods
cached_methods = var.cloudfront_cached_methods
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = false
minimum_protocol_version = "TLSv1.2_2021"
ssl_support_method = "sni-only"
acm_certificate_arn = aws_acm_certificate_validation.certificate_validation.certificate_arn
}
tags = {
Stage = local.stage,
Service = local.service
}
depends_on = [
aws_s3_bucket.bucket
]
}
# Create Route53 Record to CloudFront
resource "aws_route53_record" "domain_record" {
name = local.cdn_domain != "" ? local.cdn_domain : data.aws_route53_zone.domain_zone.name
type = "A"
zone_id = data.aws_route53_zone.domain_zone.zone_id
alias {
name = aws_cloudfront_distribution.cloudfront.domain_name
zone_id = aws_cloudfront_distribution.cloudfront.hosted_zone_id
evaluate_target_health = false
}
depends_on = [
aws_cloudfront_distribution.cloudfront
]
}
Estamos definindo os seguintes recursos:
- aws_cloudfront_origin_access_control: Cria uma política de acesso para o S3;
- aws_cloudfront_distribution: Cria e configura a distribuição do CloudFront;
- aws_route53_record: Adiciona um registro DNS no Route53 como alias para o CloudFront.
Teste da infraestrutura (test-cdn.tf
)
Aqui será a etapa onde um arquivo HTML será enviado para o bucket S3 e então o cache do CloudFront invalidado para garantir que as alterações reflitam imediatamente.
#############################
# Only for tests purposes
# Upload one HTML file to S3
#############################
resource "aws_s3_object" "public_folder" {
for_each = fileset("public/", "*")
bucket = aws_s3_bucket.bucket.id
key = each.value
source = "public/${each.value}"
etag = filemd5("public/${each.value}")
content_type = "text/html"
}
#############################
# Only for tests purposes
# Invalidate index.html from CloudFront cache
#############################
resource "null_resource" "cache_invalidation" {
# prevent invalidating cache before new s3 file is uploaded
depends_on = [
aws_s3_object.public_folder
]
for_each = fileset("${path.module}/public/", "**")
triggers = {
hash = filemd5("public/${each.value}")
}
provisioner "local-exec" {
# sleep is necessary to prevent throttling when invalidating many files; a dynamic sleep time would be more reliable
# possible way of dealing with parallelism (though would lose the indiviual triggers): https://discuss.hashicorp.com/t/specify-parallelism-for-null-resource/20884/2
command = "sleep 1; aws cloudfront create-invalidation --distribution-id ${aws_cloudfront_distribution.cloudfront.id} --paths '/${each.value}'"
}
}
Crie uma pasta chamada public
e adicione um arquivo index.html
com qualquer conteúdo apenas para teste.
Provisionar infraestrutura
Com os recursos configurados, é hora de provisionar a infraestrutura.
# Valide o plano de criação
terraform plan
# Se tudo estiver correto, aplique o plano para iniciar a criação
terraform apply
O provisionamento inicial normalmente leva alguns minutos e a distribuição do CloudFront pode demorar mais alguns minutos para ficar disponível.
Aguarde o tempo necessário. Ao finalizar, acesse o domínio configurado e valide se o arquivo HTML foi apresentado corretamente.
No exemplo do artigo o subdomínio onde a CDN foi provisionada é
terraform-cdn.tiagoboeing.com
Parabéns! Você agora possui uma CDN de alta disponibilidade na AWS utilizando IaC. Não hesite em explorar outras soluções e recursos da AWS e continue aprendendo!
Links do artigo
- Link permanente do repositório no momento que o artigo foi escrito
- Repositório no GitHub
- Demonstração da CDN
Leia também
- CDN de alta disponibilidade na AWS com S3, CloudFront e Route53
- Execução agendada de funções utilizando AWS e Serverless Framework
English
Redes sociais
Quer saber mais sobre mim, o autor deste artigo? Então dê uma olhada nas minhas redes sociais:
Espero que tenha gostado do artigo e encontrado informações úteis. Se tiver alguma dúvida ou sugestão, deixe nos comentários.
Você sabia que pode se tornar um Community Builder da AWS? Como Community Builder, você terá a oportunidade de se conectar com outros profissionais da área de tecnologia e aprender com especialistas da AWS. Além disso, você poderá compartilhar seus conhecimentos e experiências com outros membros da comunidade. Não perca a chance de se tornar um líder na sua área e se juntar a uma comunidade global de entusiastas da AWS! Saiba mais em aws.amazon.com/developer/community/community-builders.
Top comments (0)