Про CloudFront
Принялся за облегчения рутинных задач. Очень часто стали обращаться с просьбой создать CloudFront дистрибуцию, которая будет смотреть на s3 корзину или ALB. Немного поговорим что такое CloudFront и зачем он нужен.
CloudFront – амазоновский веб сервис, который позволяет ускорить доставку как статического, так и динамического контента. Данные распределяться по сети дата центров (edge locations).
Когда пользователь запрашивает нужный контент, запрос отправляется к ближайшему дата центру. Дата центр дает быстрое время ответа, с него контент передается пользователю, таким образом уменьшая время ответа от сервера к пользователю.
В случае если данные уже имеются – они будут переданы сразу же пользователю, но когда данных нет – они запрашиваются в S3 корзины или у другого HTTP-сервера которого ми указываем в качестве исходного местоположения данных.
Для реализации опишем данные ресурсы в cloudformation теплейте, которые будем постоянно использовать для создания и изменения ресурсов
В результате имеем следующее ресурсы которые будем создавать:
- CloudFront with S3 – для раздачи статического и динамического контента (веб сайт хостинг, веб зеркала).
- CloudFront with ALB – дистрибуция смотрит на уже созданный фронтенд апликейшен который живет за ALB в kubernetes
План реализации
Что нам понадобиться:
- Пользователь у которого должен быть доступ(создание, удаление) к CloudFormation, Route53, S3, CloudFront
- Настроений aws-cli
- офф документация по 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 – доменное имя для дистрибуции
- HosterZoneId – ID хостед зоны в которой будем создавать запись
- 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==
Теперь осталось автоматизировать это дело, но про это в следующем посте.
Ссылки по теме
- https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-route53.html
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html
- https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-s3.html