It’s time to deploy my frontend resources using SAM. This lab
includes creating S3 bucket, CloudFront distribution and making it online
Lab 70
Services Covered
-
AWS SAM
Lab description
It’s time to start deploying the Cloud Resume Challenge resources using the AWS SAM. I already have deployed everything through a manual setup in the console and it’s working fine. What I’m actually gonna do is to deploy those resources as Infrastructure as Code. But I don’t want to downtime my running profile page so I thought that I’ll make a mirror page for now with similar setup under another domain.
Lab date
22-10-2021
Prerequisites
- AWS account,
- SAM CLI installed
- Recommended: aws-vault
Lab steps
- In an empty folder run
sam init
This will initiate a sample application in your folder and set-up SAM. Choose AWS Quick Start as Zip, I’ll use Python for my Lambda code and Hello World example – stack includes API Gateway, IAM Role and Lambda function.
- Next run (aws-vault is not necessary if you want to use your default AWS CLI credentials):
aws-vault exec <<YOUR-USER>> --no-session -- sam deploy --guided
This will deploy the stack in AWS.
- Ok, now that my sample resources and SAM are deployed it’s time to create CRC specific resources using updating that existing stack. I wanted my site to be hosted under myprofile.cloudofthings.net domain so the S3 bucket for the frontend code needs to be called accordingly. But since I’m using CloudFront domain name later on so it doesn’t really matter. Then you need to run
sam build
aws-vault exec <<YOUR-USER>> --no-session -- sam deploy
- The bucket configuration needs to be public and set-up for website hosting so I added the properties and Bucket Policy to the template:
MyWebsite: Type: AWS::S3::Bucket Properties: AccessControl: PublicRead WebsiteConfiguration: IndexDocument: index.html BucketName: myprofile.cloudofthings.net BucketPolicy: Type: AWS::S3::BucketPolicy Properties: PolicyDocument: Id: WebPolicy Version: 2012-10-17 Statement: - Sid: PublicReadForGetBucketObjects Effect: Allow Principal: "*" Action: "s3:GetObject" Resource: !Join - "" - - "arn:aws:s3:::" - !Ref MyWebsite - /* Bucket: !Ref MyWebsite
sam build && aws-vault exec sam-user --no-session -- sam deploy
That worked nicely. I wanted to keep my bucket private so I’ve set-up a CloudFront Origin Access Identity, and adjusted the buckets policy, I don’t it just felt a bit more secure to only allow CloudFront to get objects from the bucket:
MyWebsite: Type: AWS::S3::Bucket Properties: AccessControl: Private WebsiteConfiguration: IndexDocument: index.html BucketName: myprofile.cloudofthings.net BucketPolicy: Type: AWS::S3::BucketPolicy Properties: PolicyDocument: Id: WebPolicy Version: 2012-10-17 Statement: - Sid: PublicReadForGetBucketObjects Effect: Allow Principal: AWS: !Sub "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}" Action: "s3:GetObject" Resource: !Join - "" - - "arn:aws:s3:::" - !Ref MyWebsite - /* Bucket: !Ref MyWebsite CloudFrontOriginAccessIdentity: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: CloudFrontOriginAccessIdentityConfig: Comment: "Serverless website in S3"
- Next step is to create a CloudFront distribution for that website. For testing purposes initially I’ve set-up low TTL so that the content gets updated more often and then I’ve raised the default to one week. I’ve chosen the cheapest Price Class option, so only North America and Europe gets cached content (just in case my Free Tier runs out before)
MyDistribution: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: DefaultCacheBehavior: Compress: 'true' ViewerProtocolPolicy: redirect-to-https TargetOriginId: s3-website DefaultTTL: 86400 MinTTL: 1 MaxTTL: 86400 ForwardedValues: QueryString: false PriceClass: PriceClass_100 Origins: - DomainName: !GetAtt MyWebsite.DomainName Id: s3-website S3OriginConfig: OriginAccessIdentity: Fn::Sub: 'origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}' Enabled: "true" DefaultRootObject: index.html HttpVersion: http2
- Just a side note on DNS in my case [^1]
- I added a new record in my hosted zone for my new domain:
MyRoute53Record: Type: AWS::Route53::RecordSetGroup Properties: HostedZoneId: <<HOSTED_ZONE_ID>> RecordSets: - Name: myprofile.cloudofthings.link Type: A AliasTarget: HostedZoneId: Z2FDTNDATAQYW2 DNSName: !GetAtt MyDistribution.DomainName
- Then I added a ACM Certificate to myprofile.cloudofthings.link:
ACMCertificate: Type: "AWS::CertificateManager::Certificate" Properties: DomainName: myprofile.cloudofthings.link DomainValidationOptions: - DomainName: cloudofthings.link HostedZoneId: <<HOSTED_ZONE_ID>> ValidationMethod: DNS
Then the process froze for a very long time so I added the output DNS Records manually into Route 53 Hosted Zone, I’m not sure why caused the pause, but as soon as I created it CloudFormation almost instantly finished deploying the stack. The documentation isn’t very clear on that.
- Next step was to attach the Certificate to the CloudFormation distribution and create an Alias:
Properties: DistributionConfig: ViewerCertificate: AcmCertificateArn: <<ACM_CERTIFICATE_IN_US_EAST_1_ARN>< SetSupportMethod: sni-only Aliases: - myprofile.cloudofthings.net
Here’s where I run into a complicated problem. Since the CloudFront accepts Certificates created only in us-east-1 region, I couldn’t attach the one I tried to create in the template. As far as I know there’s no simple support for creating resources in another region and then referencing them in CloudFront. So here I had to go and create a Certificate in Console, I know there’s a way to deploy resources in another regions using various stack but at my level it’s seems bit to advanced.
Alright! So the frontend is set-up and running!
- Now that the website is secured and online it’s time to deploy resources for the backend: the visitors counter.
Files: