DEV Community

saifmomin
saifmomin

Posted on • Edited on

The Cloud Resume Challenge

I am exactly a year late in taking this Cloud Resume Challenge, I wish I had seen it earlier. Nevertheless I did it now for some fun and learning. It may feel simplistic to get a static website running on cloud, but when you actually do it, you will realize there is enormous scope for learning. You get to learn how a website runs - on the cloud - serverless; you will know how a web application works end-to-end full-stack , that constituets of frontend, CDN, DNS, TLS, Gateway, backend, IaC, and Automation. In designing the website, I haven't factored every quality attribute (security, cost, etc.) of every AWS services used, the output thus is far from perfect, you may excuse me on that.

See the short profile in action at https://saifmomin.net

The code on GitHub repo is here: Frontend and Backend

This serverless website solution offers the following benefits:

  • Is durable with Amazon S3 storage
  • Is performant by Amazon CloudFront content delivery network
  • Is secured by HTTPS and additional security headers
  • Is automated and deployed with AWS SAM

Serverless Website Architecture
Figure: Serverless Website Architecture

Step 2 HTML: First thing - you need to document your resume in HTML. So I created HTML5 page. The fact you are reading this blog is because HTML exist. Hypertext is an integral part of the world wide web, along with HTTP and URI, of course. One of the core aspect of web design is that web pages must be fluid to adapt to the varying sizes of the devices it is viewed on. Content shall be treated like liquid. HTML is responsive by default, to an extent, but you need CSS to make it truly fluid!

Step 3 CSS: The resume in plain HTML looks very textual, so I applied styling to it with Cascade Style Sheet (CSS3). CSS allows you to create Responsive Web Design, the main ingedient of which are - fluid grid, fluid media and media queries. This website is responsive by design, you may experience it on different screen sizes. There are frameworks like Bootstrap that helps you create responsive websites; I have used just CSS3 though.

Step 4 Static S3 Website: One you have a resume page in HTML and CSS, it is time to host it on the cloud. I deployed it to Amazon S3 as a static website. With S3, you can architect & host a modern static website without needing a webserver! It may look easy to host a static website from an S3 bucket but no, you need to evaluate your options thoughtfully. Two choices for hosting with S3 are REST API endpoint and Website endpoint and they have their trade-offs. I adopted website endpoint solution which uses a Referer key to allow access only for requests with the custom Referer key; my S3 Bucket Policy is configured to only accept requests that contain this header. This keeps your S3 bucket configured as a website with access restricted by a Referer header thus blocking direct access to website endpoint. You can also serve the static website hosted on S3 using REST endpoint as origin with access restricted via CloudFront OAI, this allows you to keep your bucket private, but you do not get website redirection support (here, redirection may be achieved using Lambda@Edge though). You can as well host static website with AWS Amplify, it has built-in CI/CD workflows.

Step 5 HTTPS: Your website is now in S3 bucket ready to be consumed by viewers, but wait, you need to deliver the website content securely to your viewers and at a good speed. That basically means you need a Content Delivery Network (CDN), and that is CloudFront in AWS. CloudFront has points of presence (PoPs) that deliver content to end users at low latency and secures your content at both network and application level.
To deliver the website over HTTPS (with a custom domain name - see step 6), I created a free public TLS certificate with AWS Certificate Manager (ACM) and integrated it with CloudFront. Apart from HTTPS support, CloudFront offers many other security features like adding AWS WAF & AWS Shield Standard (defended by default), Field-level Encryption, Signed URLs and Signed Cookies, Geo-Restriction, OAI et al that could be leveraged.

Step 6 DNS: Now your website is secured at the edge and can be delivered fast to your viewers through CDN (CloudFront). But you would not like your end users to access your website with a domain name like d111111abcdef8.cloudfront.net, you can do better - by having (buying) a meaningful domain name for your website. I bought the domain on Route53 and it created a public hosted zone with name servers. Route53 is also a DNS that routes your end users to your website by translating your website name to IP address.

Step 7 Javascript: At this stage you have a fully functional static website with HTML and CSS. Next, you need to display a visitor count on the website and for this you need a backend machinery (database to persist the count data, a service to process data, a gateway to front-door the service). To access your backend, you need Javascript at fontend. Along with visitor count, I also wanted to display live date. By design, JavaScript is a synchronous, blocking, & single-threaded. So how do you do it withour blocking the main thread - use Web Workers! I created a web worker that runs a worker thread in the background (note that Web Worker is not a part of JavaScript engine but a Browser API).

Step 8 Database: Here you create the first layer of your backend stack - a database to persist and update the visitor counter data. Since you are building your website for internet scale, you need a database that is highly scalable. Amazon DynamoDB is schema-less, join-less NoSQL database service from AWS that gives you ultra-high performance at hyper-scale! I created a single table with one partition key (website) and one attribute (visits) to persist the visitor count. For real-world projects, you need to design DynamoDB Table(s) carefully or else you may end up with a relational-like design. Strive for as few tables as possible or the heck go for single-table design.
DynamoDB has two read/write capacity modes and the pricing model is based on these two capacity modes - On-Demand and Provisioned. In general you start with On Demand and when the usage grows you may move to Provisoned mode, to gain some cost savings. Thus I opted for the on-demand capacity mode since the traffic is unpredictable for my new website.
For sake of simplicity, I made these assumptions for costing -

  • each time a user access my website, 1 write of 1 KB and 1 eventually consistent read of 1 KB are performed. Note that by default DynamoDB uses eventually consistent reads, unless you specify otherwise.
  • my website receives 1 million reads and 1 million writes per month
  • for my region ap-south-1 the monthly cost would be $0.14 (0.285/2) for reads + $1.42 for writes = approx $ 1.56 per month. First 25 GB stored per month is free, so this shouldn't add to the cost as my table wouldn't grow that big.

For real-world projects you would consider other parameters like backups, data transfer, DAX, Streams, etc for calculating the cost.

DynamoDB scales well, but how do you achieve concurrency at scale? DynamoDB allows you to do it using Atomic Counter or Conditional Writes (for busness-critical solutions) or maybe even Transactions. I made use of Atomic Counter. AWS provides the same visitor counter use case for atomic counter in their documentation.

Step 9 API: You have database table and Javascript code at frontend through which you can CRUD your database, but this is never a good practice. We needmore layers between the frontend client and the database - a service and an api to front-door the service. APIS are programmable interface that allow you to access a servce running somewhere on the cloud or the internet. Amazon API Gateway is a service that helps developers build secure and scalable APIs.

AWS offers two RESTful API flavors - REST API and HTTP API (confusingly named!) ; and a third type - websockets API. HTTP APIs in Amazon API Gateway are faster, cheaper (us-east-1: $1.00 per million vs $3.5 per million for REST API) and simpler to use (with native CORS support, JWT Authorizers instead of lambda authorizer, auto-deploy to $default stage, among others); but they lack features, like you can only have regional API endpoint with HTTP API. So for real-world projects, you must choose the right API type based on your requirements. See http-api-vs-rest.

Edge-optimized endpoint leverage Cloudfront PoP, but you can't edit the distribution, for example you cannot add a WAF. With Regional API endpoint, you can have your own CloudFront distribution in front of your API , and thus have full control like you can have a WAF. If you have users who would access your API from all over the globe and you do not want to manage CloudFront distribution for your APIs, then go for edge-optimized API. Though HTTP API looked promising with its low cost, speed and dev experience, but it only gives you regional endpoint and since my website is supposed to be accessed globally and that I want to stick with AWS managed CloudFront, I created a REST API. For throttling, I left the Default Method Throttling on Stage Settings. Note that there are two places where you can configure throttling - Server-side (Stage setting) and Per-Client (Usage Plan with API Key).

Step 10 Python: We have Javascript calling the API. The API now need to integrate with backend service - a lambda function + a programming language. Talking about Lambda - though the first AWS service, that is SQS, launched by AWS was serverless, it was Lambda which revolutionised the serverless movement. If cloud has disrupted the traditional data centres then Lambda has disrupted the cloud itself! Lambda natively supports Java, Go, PowerShell, Node.js, C#, Python, and Ruby code (at the time of this writing), but you can run any programming language on AWS Lambda using Custom Runtimes. I created a Lambda funtion with Python 3.8 runtime and wrote the code to connect with DynamoDB table. Important point to remember - make sure you have the right execution role (along with the right IAM Policy) attached to your Lambda to access DynamoDB.

Step 11 Tests: At this stage you have full backend ready, but wait, it is not complete without a test. To be truly agile, you should have unit tests for your code; Test Driven Development (TDD) is a practice that can help you with it. I wrote unit test that would test my Python code and also mock AWS environmnent. I used Python moto library to write tests. Moto is a library that allows your tests to easily mock out AWS Services.

Step 12 Infrastructure as Code: You now have backend infrastructue ready along with test, but you can still do better - turn your infrstructure into code. IaC solves the problem of environment drift, just like containers. AWS provides different ways to code your infra, like SAM, CDK, and Cloudformation at the core. I created a SAM YAML and provisioned the backend with SAM commands. For some reason that I coudnt figure out, I wasnt able to get the On Demand Billing Mode (BillingMode: PAY_PER_REQUEST) work through SAM, so I updated it through AWS CLI.

Step 13 Source Control: Now we have our frontend, backend and IaC code. The code needs to be managed in a repo. When you push code often to a repo for build and test, it gives you continuous integration. I created a private repo on GitHub. AWS has CodeCommit for Continuous Integration. AWS CodeCommit is a fully-managed source control service that hosts private Git repositories and makes it easy for teams to collaborate on code in a secure and highly scalable ecosystem. If you are working on AWS eco-system then CodeCommit could be the ideal chioce as it integrates very well with AWS services.

Step 14 CI/CD (Back end): Once the source code repo is setup on GitHub, we should automate further. Automation is a powerful. It both saves time and help reduce human error. Github Actions allows you to set up automated CI/CD workflows directly from the GitHub repo. I used the sample Python Application CI workflow from GitHub Actions and created the workflow with two jobs - a test job and a deploy job. When the test pass, SAM is packaged and deployed to AWS. Important warning - Do NOT keep AWS credentials in your code! On GitHub Actions - use GitHub secrets. On AWS - use IAM. AWS has CodePipeline to orchestrate a CI/CD pipeline. One important advantage of CodePipeline is that authentication is handled with IAM roles instead of access keys for IAM users; so no need to manage access keys. With GitHub Actions, you must store the IAM users access keys in GitHub secrets.

Step 15 CI/CD (Front end): Finally, we have our backend code with IaC and CI/CD. The last step is to create a repo for frontend code and automate it with CI/CD. I created a simple workflow that uploads frontend code to S3 bucket and invalidates the CloudFront cache as well. The first 1,000 CloudFront invalidation per month are free. There is another way to to remove a file from CloudFront edge caches before it expires, and that is to use a version identifier in file names or in folder names. Note: using a versioned file names or folder names with a version id is not the same as S3 object versioning.
See this.

Step 16 Blog post: Expressing your experience through writing is a good way to communicate. I have tried to share my experience through this blog!

Step 1 Certification: I did not fulfill the condition of taking the Cloud Practitioner certificate; I have other AWS certifications so I hope it is okay.

Hope this post helps you understand how to host a static website on AWS.

Top comments (0)