AWS CloudFormation: Создание CloudFront дистрибуции с S3 или ALB

 

Про CloudFront

Принялся за облегчения рутинных задач. Очень часто стали обращаться с просьбой создать CloudFront дистрибуцию, которая будет смотреть на s3 корзину или ALB. Немного поговорим что такое CloudFront и зачем он нужен.

CloudFront – амазоновский веб сервис, который позволяет ускорить доставку как статического, так и динамического контента. Данные распределяться по сети дата центров (edge locations).

Когда пользователь запрашивает нужный контент, запрос отправляется к ближайшему дата центру. Дата центр дает быстрое время ответа, с него контент передается пользователю, таким образом уменьшая время ответа от сервера к пользователю.

В случае если данные уже имеются – они будут переданы сразу же пользователю, но когда данных нет – они запрашиваются в S3 корзины или у другого HTTP-сервера которого ми указываем в качестве исходного местоположения данных.

Для реализации опишем данные ресурсы в cloudformation теплейте, которые будем постоянно использовать для создания и изменения ресурсов

В результате имеем следующее ресурсы которые будем создавать:

  • CloudFront with S3 – для раздачи статического и динамического контента (веб сайт хостинг, веб зеркала).
  • CloudFront with ALB – дистрибуция смотрит на уже созданный фронтенд апликейшен который живет за ALB в kubernetes

План реализации

Что нам понадобиться:

  1. Пользователь у которого должен быть доступ(создание, удаление) к CloudFormation, Route53, S3, CloudFront
  2. Настроений aws-cli
  3. офф документация по CloudFormation

Как будем реализовывать:

Создадим два шаблона, один с именем cloudfront_with_s3.json, а второй cloudfront_with_alb.json (Почему json формат? Исторически так сложилось)

Описываем файл cloudfront_with_s3.json:

  • Параметры:
    • S3SiteBucketName – имя корзины
    • HosterZoneId ID хостед зоны в которой будем создавать запись
    • SiteSSLARN arn сертификата который должен быть создан обязательно в регионе us-eas-1
  • Ресурсы:
    • SiteBucket – корзина
    • BucketPolicy – политика доступа к корзине
    • CloudFrontCDN – дистрибуция
    • Route53 – создаст А запись (Alias)
  • Вывод:
    • Outputs – здесь будем выводить оригинальный URL дистрибуции, пример d111111abcdef8.cloudfront.net

Описываем файл cloudfront_with_s3.json:

  • Параметры:
    • LoadBalancerURL – передаум ендпнит лоад балансера
    • DNSName – доменное имя для дистрибуции
    • HosterZoneIdID хостед зоны в которой будем создавать запись
    • SiteSSLARN –  arn сертификата который должен быть создан обязательно в  регионе us-eas-1
  • Ресурсы:
    • CloudFrontCDN – дистрибуция
    • Route53 – создаст А запись (Alias)
  • Вывод:
    • Outputsздесь будем выводить оригинальный URL дистрибуции, пример d111111abcdef8.cloudfront.net

Для создания ресурсов можно воспользоваться aws-cli или загрузить шаблон через AWS Console –> CloudFormation. В примерах воспользуюсь AWS Console, так как в будущем планирую автоматизировать данный процесс.

Перед началом нужно помнить, что если вы создаете дистрибуцию для dev окружений ее нужно прятать за WAF, так как после создания она будет доступна всем.

Написание темплейта CloudFormation

Темплейт CloudFront with S3

Создаем файл используя IDE с именем cloudfront_with_s3.json, добавим сначала блок с параметрами:

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "AWS CloudFormation S3 website hosting with CloudFront CDN stack",

    "Parameters": {

        "S3SiteBucketName": {
            "Description": "S3 website hosting bucket name",
            "Type": "String",
            "Default": "example-cdn.****.*****"
        },

        "HosterZoneId": {
            "Description": "Hosted Zone ID",
            "Type": "String",
            "Default": "Z3*********LB6"
        },

        "SiteSSLARN": {
            "Description": "ACM SSL ARN",
            "Type": "String",
            "Default": "arn:aws:acm:us-east-1:*************:certificate/********-9751-****-****-1cb2****c8fa"
        }
    },

теперь опишем создание ресурсов, сначала будет s3 корзина:

    "Resources": {

        "SiteBucket": {
            "Type": "AWS::S3::Bucket",
            "DeletionPolicy": "Delete",
            "Properties": {
                "BucketName": { "Ref": "S3SiteBucketName" },
                "WebsiteConfiguration": {
                    "IndexDocument": "index.html",
                    "ErrorDocument": "404.html"
                }
            }
        },

политика доступа к корзине:

        "BucketPolicy": {
            "Type": "AWS::S3::BucketPolicy",
            "Properties": {
                "PolicyDocument": {
                    "Id": "MyPolicy",
                    "Version": "2012-10-17",
                    "Statement": [{
                        "Sid": "PublicReadForGetBucketObjects",
                        "Effect": "Allow",
                        "Principal": "*",
                        "Action": "s3:GetObject",
                        "Resource": { "Fn::Join": ["", [ "arn:aws:s3:::", { "Ref": "SiteBucket" }, "/*" ]] }
                    }]
                },
            "Bucket": { "Ref": "SiteBucket" }
            }
        },

переходим к самой дистрибуции, описиваем ее:

        "CloudFrontCDN": {
            "Type": "AWS::CloudFront::Distribution",
            "Properties": {
                "DistributionConfig": {
                    "Comment": "CDN with S3 Origin - Created using Jenkins job",
                    "Enabled": true,
                    "DefaultCacheBehavior": {
                        "TargetOriginId": { "Ref": "SiteBucket" },
                        "ViewerProtocolPolicy": "redirect-to-https",
                        "MinTTL": 0,
                        "AllowedMethods": [ "HEAD", "GET" ],
                        "CachedMethods": [ "HEAD", "GET" ],
                        "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
                        "OriginRequestPolicyId": "216adef6-5c7f-47e4-b989-5492eafa07d3",
                        "ForwardedValues": {
                            "QueryString": false,
                            "Cookies": {
                                "Forward": "none"
                            }
                        }
                    },
                    "Aliases" : [ { "Ref": "S3SiteBucketName" } ],
                    "Origins": [{
                        "DomainName": { "Fn::Join": [ ".", [ {"Ref": "SiteBucket"}, "s3-website.us-east-2.amazonaws.com" ] ] },
                        "Id": { "Ref": "SiteBucket" },
                        "CustomOriginConfig": {
                            "HTTPPort": "80",
                            "HTTPSPort": "443",
                            "OriginProtocolPolicy": "http-only"
                        }
                    }],
                    "ViewerCertificate": {
                        "SslSupportMethod": "sni-only",
                        "MinimumProtocolVersion": "TLSv1.2_2019",
                        "AcmCertificateArn": { "Ref": "SiteSSLARN" }
                    }
                },
                "Tags" : [{"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName" }] ]} }]
            }
        },

добавляем создание записи в Route53:

        "Route53": {
            "Type": "AWS::Route53::RecordSetGroup",
            "Properties": {
                "HostedZoneId": {"Ref": "HosterZoneId"},
                "RecordSets": [
                    {
                        "Name": { "Ref": "S3SiteBucketName" },
                        "Type": "A",
                        "AliasTarget": {
                            "HostedZoneId": "Z2FDTNDATAQYW2",
                            "DNSName": { "Fn::GetAtt": [ "CloudFrontCDN", "DomainName"] }
                        }
                    }
                ]
            }
        }
    },

и в конце добавим output где, будем выводить оригинальней URL дистрибуции, конечно можно добавить и больше – но пока это не нужно:

    "Outputs":{
        "DistributionName" : {
            "Description" : "Original URL to access the CloudFront distribution",
            "Value" : { "Fn::Join" : [ "", [{"Fn::GetAtt" : ["CloudFrontCDN", "DomainName"]} ]]}
        }
    }
}

Переходим в AWS Console –> CloudFormatiom –> Create Stack (with new resources):

далее Template is ready –> Upload a template File, выбираем файл и загружаем, жмем Next:

после добавления файла, нужно задать имя и ознакомиться со списком параметров которые мы описывали, жмем Next,

здесь можем задать дополнительніе параметры, проставить теги (которые лучше добавить в темплейте), пролистаем и жмем Next:

последний этап Review,  где перепроверяем и запускаем создание ресурсов Create Stack:

пошел процесс создания ресурсов, в Events можно посмотреть на каком этапе сейчас происходит создание, ожидаем:

обновляем информацию в Overview, проверим статус:

Что ж, стак создан. Переходим в терминал для проверки, сначала посмотрим на дистрибуцию:

aws cloudfront list-distributions| grep example
                        "example-cdn.***********"
                            "Id": "example-cdn.***********",
                            "DomainName": "example-cdn.***********.s3-website.us-east-2.amazonaws.com",
                    "TargetOriginId": "example-cdn.***********",
                        "CNAME": "example-cdn.***********",

создадим простой файл index.html и закинем в корзину:

<!DOCTYPE html>
<html>
  <body>
  <p>Status - OK. It works.</p>
  </body>
</html>

откроем линк в браузере:

и дернем курлом в терминале:

$ curl https://example-cdn.********** -I
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 77
Connection: keep-alive
Date: Sat, 04 Dec 2021 02:28:52 GMT
Last-Modified: Sat, 04 Dec 2021 02:28:10 GMT
ETag: "46bc9d777e98083a352f20bc6d9612ef"
Server: AmazonS3
X-Cache: Hit from cloudfront
Via: 1.1 e7677f5f22d50d1de533173754b9676c.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: BUD50-C1
X-Amz-Cf-Id: t3Z-VHXdKryWt-asgA4lZQpwHYVDkXquRzs3FYVvM5Sbp-OA83z0jQ==
Age: 245

хорошо, переходим к следующему файлу.

Темплейт CloudFront with ALB

Аналогично как в первом шаблоне, добавляем параметры:

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "AWS CloudFormation CDN CloudFront with ALB stack",

    "Parameters": {

        "LoadBalancerURL": {
            "Description": "Load Balancer URL",
            "Type": "String",
            "Default": "k8s-******-web****-dfa******8-******1.us-east-2.elb.amazonaws.com"
        },

        "DNSName": {
            "Description": "DNS Name for CF Distribution",
            "Type": "String",
            "Default": "example-cnd.betterme.world"
        },

        "HosterZoneId": {
            "Description": "Hosted Zone ID",
            "Type": "String",
            "Default": ""
        },

        "SiteSSLARN": {
            "Description": "ACM SSL ARN",
            "Type": "String",
            "Default": "arn:aws:acm:us-east-1:*************:certificate/********-5c33-4**-9**4-*****2e135"
        }
    },

сами ресурсы:

    "Resources": {

        "CloudFrontCDN": {
            "Type": "AWS::CloudFront::Distribution",
            "Properties": {
                "DistributionConfig": {
                    "Comment": "CDN with ALB - Created using Jenkins job",
                    "Enabled": true,
                    "HttpVersion": "http2",
                    "DefaultCacheBehavior": {
                        "TargetOriginId": { "Ref": "LoadBalancerURL" },
                        "ViewerProtocolPolicy": "redirect-to-https",
                        "MinTTL": 0,
                        "AllowedMethods": [ "OPTIONS", "HEAD", "GET" ],
                        "CachedMethods": [ "HEAD", "GET" ],
                        "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
                        "OriginRequestPolicyId": "216adef6-5c7f-47e4-b989-5492eafa07d3",
                        "Compress": true,
                        "ForwardedValues": {
                            "QueryString": false,
                            "Cookies": {
                                "Forward": "none"
                            }
                        }
                    },
                    "Aliases" : [ { "Ref": "DNSName" } ],
                    "Origins": [{
                        "DomainName": { "Ref": "LoadBalancerURL" },
                        "Id": { "Ref": "LoadBalancerURL" },
                        "CustomOriginConfig": {
                            "HTTPPort": "80",
                            "HTTPSPort": "443",
                            "OriginProtocolPolicy": "https-only"
                        }
                    }],
                    "ViewerCertificate": {
                        "SslSupportMethod": "sni-only",
                        "MinimumProtocolVersion": "TLSv1.2_2019",
                        "AcmCertificateArn": { "Ref": "SiteSSLARN" }
                    }
                },
                "Tags" : [{"Key" : "Name", "Value" : { "Fn::Join" : [ "-", [{ "Ref" : "AWS::StackName" }, "cdn" ] ]} }]
            }
        },

и Route53:

        "Route53": {
            "Type": "AWS::Route53::RecordSetGroup",
            "Properties": {
                "HostedZoneId": {"Ref": "HosterZoneId"},
                "RecordSets": [
                    {
                        "Name": { "Ref": "DNSName" },
                        "Type": "A",
                        "AliasTarget": {
                            "HostedZoneId": "Z2FDTNDATAQYW2",
                            "DNSName": { "Fn::GetAtt": [ "CloudFrontCDN", "DomainName"] }
                        }
                    }
                ]
            }
        }
    },

вывод:

    "Outputs":{
        "DistributionName" : {
            "Description" : "URL to access the CloudFront distribution",
            "Value" : { "Fn::Join" : [ "", [{"Fn::GetAtt" : ["CloudFrontCDN", "DomainName"]} ]]}
        }
    }
}

Проделываем все манипуляции как в примере выше, ждем создания ресурсов и проверяем.

curl https://example.app.****************/ -I
HTTP/2 200
content-type: text/html; charset=utf-8
content-length: 12132
date: Sun, 05 Dec 2021 01:56:53 GMT
x-powered-by: Next.js
x-frame-options: DENY
set-cookie: flow=417
etag: "2f64-I2GXA9S3RJSGV7tX8TqaigpZ1CM"
cache-control: private, no-cache, no-store, max-age=0, must-revalidate
vary: Accept-Encoding
x-cache: Miss from cloudfront
via: 1.1 a329142c11bf4b365acb0f902bcf447d.cloudfront.net (CloudFront)
x-amz-cf-pop: BUD50-C1
x-amz-cf-id: 9MxleGHEVg3nFb7RmD0pMCvdroUNPk-7WWsULbXhwVW9f_NokM-acA==

Теперь осталось автоматизировать это дело, но про это в следующем посте.

Ссылки по теме

Click to rate this post!
[Total: 0 Average: 0]