Sirviendo paginas estaticas con S3 y CloudFront


cloudfront

Serie sobre Serverless

Parte I: Sirviendo sitios web estáticos con S3 y CloudFront, Estás aquí.


Parte II: Enviando emails con AWS Lambda y SES desde un formulario HTML


Introducción

Este artículo es parte de una serie, la idea es levantar un sitio completamente serverless con funcionalidad de login, tal vez una página de perfil y alguna utilidad extra. Pero como estamos empezando, vamos a alojar un primer borrador del sitio con un formulario de contacto. Para la distribución de los archivos veremos cómo configurar CloudFront, y para almacenar los archivos usaremos S3. S3 es un servicio de almacenamiento de objetos que ofrece escalabilidad, disponibilidad de datos, seguridad y rendimiento líderes en la industria. CloudFront, por otro lado, es una red de entrega de contenido (CDN) rápida. El sitio que vamos a utilizar está escrito en elm y lo podés encontrar aquí.


S3

Primero necesitamos crear un bucket

aws s3api create-bucket --bucket techsquad-serverless-site --region us-east-1
# OUTPUT:
# {
#     "Location": "/techsquad-serverless-site"
# }

Podríamos servir directamente desde S3, pero eso puede volverse costoso en un sitio con mucho tráfico (Podés hacerlo habilitando el hosting web en el bucket).


Para que esta configuración funcione, primero necesitamos crear una identidad de acceso de origen de CloudFront

aws cloudfront create-cloud-front-origin-access-identity --cloud-front-origin-access-identity-config CallerReference=techsquad-serverless-site-cloudfront-origin,Comment=techsquad-serverless-site-cloudfront-origin
{
    "Location": "https://cloudfront.amazonaws.com/2018-11-05/origin-access-identity/cloudfront/E3IJG9M5PO9BYE",
    "ETag": "E2XHDQQ0DDY9IJ",
    "CloudFrontOriginAccessIdentity": {
        "Id": "E3IJG9M5PO9BYE",
        "S3CanonicalUserId": "c951e48af14afcf935c2455a6d503150c80f20df93b27af9ed0928eb48feb67d1b933aa1adb7e1bf88a7aacccccccccc",
        "CloudFrontOriginAccessIdentityConfig": {
            "CallerReference": "techsquad-serverless-site-cloudfront-origin",
            "Comment": "techsquad-serverless-site-cloudfront-origin"
        }
    }
}

Nuestra identidad de acceso de origen se creó correctamente, necesitamos obtener el S3CanonicalUserId para la política de nuestro bucket S3.


Limitemos el acceso a tu bucket con la siguiente política (guardarla como bucket-policy.json)

{
   "Version":"2012-10-17",
   "Id":"PolicyForCloudFrontPrivateContent",
   "Statement":[
        {
               "Sid":"techsquad-serverless-site-cloudfront-origin",
               "Effect":"Allow",
               "Principal":{"CanonicalUser":"c951e48af14afcf935c2455a6d503150c80f20df93b27af9ed0928eb48feb67d1b933aa1adb7e1bf88a7aacccccccccc"},
               "Action":"s3:GetObject",
               "Resource":"arn:aws:s3:::techsquad-serverless-site/*"
             }
      ]
}

Esta política solo permitirá que CloudFront obtenga los archivos del bucket S3, porque queremos evitar que los usuarios o cualquiera acceda al bucket directamente.


Luego simplemente adjuntamos esa política al bucket

aws s3api put-bucket-policy --bucket techsquad-serverless-site --policy file://bucket-policy.json

Estoy usando un ejemplo antiguo que creé y probablemente seguiré desarrollando a partir de él. Copiemos los archivos (los archivos fuente están en este repositorio de GitHub)

aws s3 sync . s3://techsquad-serverless-site/

Hasta ahora todo bien. Tenemos nuestro bucket S3 listo.


CloudFront

Vamos a usar este archivo para crear nuestra distribución de CloudFront (guardalo como distconfig.json o generalo con aws cloudfront create-distribution --generate-cli-skeleton > /tmp/distconfig.json y luego reemplazá los valores: Id, DomainName, TargetOriginId y el cname en Aliases.Items):

{
  "CallerReference": "techsquad-serverless-site-distribution",
  "Aliases": {
    "Quantity": 1,
    "Items": [
      "serverless.techsquad.rocks"
         ]
   },
  "DefaultRootObject": "index.html",
  "Origins": {
    "Quantity": 1,
    "Items": [
      {
        "Id": "techsquad-serverless-site-cloudfront",
        "DomainName": "techsquad-serverless-site.s3.amazonaws.com",
        "S3OriginConfig": {
          "OriginAccessIdentity": "origin-access-identity/cloudfront/E3IJG9M5PO9BYE"
        }
      }
    ]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "techsquad-serverless-site-cloudfront",
    "ForwardedValues": {
      "QueryString": true,
      "Cookies": {
        "Forward": "none"
      }
    },
    "TrustedSigners": {
      "Enabled": false,
      "Quantity": 0
    },
    "ViewerProtocolPolicy": "allow-all",
    "MinTTL": 3600
  },
  "CacheBehaviors": {
    "Quantity": 0
  },
  "Comment": "",
  "Logging": {
    "Enabled": false,
    "IncludeCookies": true,
    "Bucket": "",
    "Prefix": ""
  },
  "PriceClass": "PriceClass_All",
  "Enabled": true
}

Dejamos la mayoría de los valores en sus predeterminados, pero si querés saber más o personalizar tu despliegue, mirá acá o escribí aws cloudfront create-distribution help.


Finalmente, creemos la distribución de CloudFront para nuestro sitio

aws cloudfront create-distribution --distribution-config file://distconfig.json
# OUTPUT:
# {
#     "Location": "https://cloudfront.amazonaws.com/2018-11-05/distribution/E1M22XXNIJ5BLN",
#     "ETag": "EW1AZUQ33NKQ7",
#     "Distribution": {
#         "Id": "E1M22XXNIJ5BLN",
#         "ARN": "arn:aws:cloudfront::894527626897:distribution/E1M22XXNIJ5BLN",
#         "Status": "InProgress",
#         "LastModifiedTime": "2019-02-02T19:35:45.729Z",
#         "InProgressInvalidationBatches": 0,
#         "DomainName": "d3v3xtkl1l2ynj.cloudfront.net",
#         ...
#     }
# }

¡Uff, un montón de detalles! Pero lo que podríamos necesitar más adelante es el ETAG si queremos descargar y actualizar nuestra distribución, así que tenelo a mano. También podemos ver nuestra URL de CloudFront: d3v3xtkl1l2ynj.cloudfront.net en este caso.


Puede tardar unos minutos en inicializarse la distribución. Podés verificar el progreso con

aws cloudfront list-distributions | jq ".DistributionList.Items[0].Status"
# OUTPUT:
# "InProgress"

Una vez que esté lista, el estado será: “Deployed”. Y ahora, si vamos a la URL de CloudFront, deberías ver el sitio :). El bucket S3 solo permitirá que CloudFront acceda a los archivos, por lo que la única forma de servir el sitio es a través de CloudFront.


DNS

Lo único que falta es el registro en el DNS (No tengo este nombre de dominio en Route53, mala mía, pero un CNAME servirá por ahora). Así que lo añadimos y verificamos usando dig.

dig serverless.techsquad.rocks
# OUTPUT:
# dig CNAME serverless.techsquad.rocks
#
# ; <<>> DiG 9.13.5 <<>> CNAME serverless.techsquad.rocks
# ;; global options: +cmd
# ;; Got answer:
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52651
# ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
#
# ;; OPT PSEUDOSECTION:
# ; EDNS: version: 0, flags:; udp: 1452
# ;; QUESTION SECTION:


# ;serverless.techsquad.rocks.    IN      CNAME
#
# ;; ANSWER SECTION:
# serverless.techsquad.rocks. 292 IN      CNAME   d3v3xtkl1l2ynj.cloudfront.net.
#
# ;; Query time: 20 msec
# ;; SERVER: 1.1.1.1#53(1.1.1.1)
# ;; WHEN: Sat Feb 02 17:47:11 -03 2019
# ;; MSG SIZE  rcvd: 98

Como podemos ver, el registro ya está, así que ahora podemos ir a http://serverless.techsquad.rocks (esto solo funciona porque configuramos ese alias en la distribución). Podríamos agregar SSL creando un certificado con Amazon Certificate Manager, pero dejaremos eso como ejercicio o para un futuro pequeño artículo.


Comandos útiles

En caso de que necesites obtener información, acá algunos comandos útiles:


Este comando nos dará el Id de nuestra distribución

aws cloudfront list-distributions --output table --query 'DistributionList.Items[*].Id'
# OUTPUT:
# -------------------
# |ListDistributions|
# +-----------------+
# |  EFJVJEPWAPGU2  |
# +-----------------+

Este otro nos da el ETag (necesario para realizar actualizaciones, por ejemplo)

aws cloudfront get-distribution-config --id EFJVJEPWAPGU2 | jq '. | .ETag'
# OUTPUT:
# "E2TPQRAUPJL2P3"

Y este guardará la configuración actual en /tmp para que podamos actualizarla.

aws cloudfront get-distribution-config --id EFJVJEPWAPGU2 | jq '. | .DistributionConfig' > /tmp/curent-distribution-E2TPQRAUPJL2P

Próximos artículos

Este es el primer artículo de esta serie sobre tecnologías serverless. La idea es construir un sitio web completamente funcional usando solo tecnologías serverless. El próximo post cubrirá la función de AWS Lambda utilizada para enviar el formulario de contacto. Además, podés encontrar todo el código del sitio aquí.


Errata

Si encontrás algún error o tenés alguna sugerencia, por favor enviame un mensaje para que lo corrija.



No tienes cuenta? Regístrate aqui

Ya registrado? Iniciar sesión a tu cuenta ahora.

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido