Jace Hickman

Colorado Springs, CO

Connect with me:

Cloud Resume Challenge

Overview:

This challenge asks you to deploy a website as IaC hosted on a Cloud Provider of your choice. I went with Amazon Web Services and Terraform.

https://cloudresumechallenge.dev/docs/the-challenge/aws/

1. Certification:

I stumbled across this challenge on Reddit while pursuing my B.S. at WGU. I was still trying to land my first helpdesk job at this time and needed some side projects to speak to since I didn't have relevant work experience. This project in particular was interesting after I received my AWS Cloud Practitioner Certification. The certification was high-level and I wanted to see the technical side of it all.

2-3. HTML & CSS:

WGU also had a course on web development so I had a bit of experience here. But, I'm no web developer either. The goal here is to show my cloud skills so I didn't spend a ton of time on this part.

4-6. Static Website, HTTPS & DNS (S3 and Route53):

This is where the challenge really began. The web files are hosted on AWS S3 and I'd never interacted with it before. This part was fairly straightforward. AWS has great documentation and I generally followed the steps detailed here: S3 Hosting Guide

I created two buckets named jacehickman.com and www.jacehickman.com respectively. I configured them for public access and static website hosting.

The challenge then asks you to use HTTPS for security. Security sounded great to me. Leaned on AWS documentation for this piece as well: CloudFront HTTPS Setup

I obtained a certificate and edited the CloudFront Distribution behavior policy to redirect HTTP traffic to HTTPS.

For DNS, I used Route53 to purchase my domain and made 7 DNS records in total. In short, all traffic is routed to https://jacehickman.com.

7. Javascript:

I got stuck here for a bit because the only thing I knew about Javascript at the time was that it's a programming language. That's still mostly true today but for this step I made a counter.js script that incremented a Visitor Count every time the page was visited or refreshed, it wasn't great but it met the ask.

Just like that, I had a website deployed that anyone could access. I considered this a big win because I had something tangible to talk about in interviews.

Here's the final javascript counter.js

8-11. Database, API, & Python (DynamoDB, API Gateway, & Lambda):

This immediately threw a wrench in my plans for the Javascript I just got working. The challenge presents this as three different parts which it is, but it all works in tandem so I bounced back and forth between services. I created a DynamoDB table called VisitorTable (among many others through troubleshooting). I couldn't get Lambda to update the table; I overlooked that I needed to specify string, binary, or number upon table creation.

The challenge recommended making a Python script for the Lambda function, yet another language I'd never used at the time. The issues here began with attempting to update the item in DynamoDB. I gave up and decided to try just accessing the item. I eventually figured that out and moved on to actually updating it.

I didn't understand I couldn't rename the script in Lambda from the default lambda_function.py without specifying a new handler. Also, I was getting incorrect schema errors because as I mentioned earlier, the DynamoDB table was storing 1 as a string, not a number.

Finally, the internal testing I struggled a lot with. I was attempting to pass JSON to the script even though it needed no parameters to execute. I spent a ton of time trying to understand what I was supposed to pass when really it was as simple as nothing: {} The tests passed and onto the API. Here's the code for the function lambda_function.py

The API is used to invoke the Lambda function. At this point, I felt very in over my head. I knew what an API was definitionally but not how it worked at all. Then I learned a bit about CORS and that added even more complexity.

I used a POST method as recommended by the challenge. For the integration request to Lambda, I made a template passing {'visitor_id':'0'} to the function. For the integration response, my function already provides one so I opted to use that and made the response body map to $input.body.

At this point the API was deployed, but I needed a method to test it. I couldn't get Javascript to work and was struggling to understand invocation. I asked ChatGPT for some assistance and it spit out some PowerShell that worked and my DynamoDB table was incrementing.

Hiatus & Return:

I actually put down the project for some time at this step because I landed my first IT job and was learning a ton at work. I moved from Helpdesk > Systems Administrator > Another Systems Administrator role > to my current role as an Automaation Engineer. I learned so much along the way like Ansible, Git, Linux, and CI/CD Pipelines so I didn't feel the need to press on with the project.

Eventually, work exposed me to Terraform and I considered picking it back up to learn since I knew Terraform was right around the corner for this project. After that hiatus, I decided to finish the project but found some difficulty because I had to relearn everything I pieced together, but fortunately had much more knowledge.

I immediately put all my files into GitHub. I also installed the LiveServer plugin for vscode so I could see my site before deploying it easier. I rewrote my Javascript code and got it to send a request to my API finally but was blocked by CORS. I vaguely remembered being stuck here before I put it down. Turns out I never even configured CORS on my API so it was just blocking everything by default. I configured CORS to accept everything and moved on. The API response was nesting my lambda response in its own body so I was struggling to access the visitor_count. This ended up being a problem with the API. I changed the integration request content handling to "passthrough" so Lambda passed as is, instead of the API tossing some extra nesting on it.

The website was basically completed at this point. Maybe a little sore on the eyes, but it was done. Now to completely "restart" and write it all as IaC.

12. Infrastructure as Code (Terraform):

I had a couple choices here but opted for Terraform for the sole fact that it's available for use at my work and I wanted to learn more about it. I set up Terraform on my RHEL9 VM and AWS CLI. Since Terraform was new, I went through their tutorial to get some familiarity and I can't recommend it enough: Terraform Get Started

AWS has great documentation regarding Terraform I used heavily: Terraform AWS Docs

I was quite nervous about breaking my website so I was pretty adamant that I didn't want to destroy everything and redeploy. I wanted to edit what was already there and ideally make zero changes since it worked but have the option to redeploy/change. This probably added some difficulty but I think it was worth it.

I made a bunch of data sources for my environment. As I understood it this is essentially a read version of my current environment. Then I started to import everything so my tfstate knew what already existed. A lot of this was just fighting with syntax so Terraform was happy with the resource I was trying to import.

I then went down the line defining each resource and would periodically run 'terraform plan' to see what was going to happen. I can't express enough how helpful this is. It clearly shows whether a resource would be deleted, changed, or added. I was going for no changes wherever possible and went back and forth until I got it right.

I was finally ready to run my first 'terraform apply.' The 'terraform plan' output looked pretty good and... it fails. Who would've guessed deploying the entire backend didn't go as planned? Fortunately, my website was still up so I considered that good news.

One large oversight was that I didn't import everything. I found this out by providing my terraform plan output to ChatGPT and it told me that based on our history, there's a bunch of stuff I hadn't defined. This was confirmed because all of a sudden I had two APIs, another hosted domain in Route53, you get the idea.

Turns out if I import my API, for example, all the child configurations aren't also imported. I thought it was a package deal and I was mistaken. Long story short, you can see at the top of main.tf all of the things I ended up importing. A few more 'terraform plan' and 'terraform apply' runs later, I worked through the errors and had my website deployed using Terraform!

13-15. GitHub and CI/CD:

Next was to automate the process so when I changed code in my repository, my website frontend/backend was updated without me manually doing so. I used GitLab at work pretty often so I thought this would be a breeze. It wasn't too bad, but I was actually really surprised to see how different the syntax was between the two.

The challenge recommends a frontend and backend repository but I opted to not do this and just have a frontend and backend workflow to manage them. If the project was a giant codebase and had contributors other than myself, then maybe I'd reconsider. I kept it in one for ease of use.

I had to make some secrets for my AWS credentials so both workflows could use them. The deploy-backend.yml workflow deploys Terraform, the deploy-frontend.yml workflow deploys my website files and invalidates the CloudFront cache so content is up to date.

I seemingly was finished. I pushed my changes up to remote but 'terraform apply' failed in my workflow. This posed the last big issue, my tfstate was local. Everything I just applied wasn't in remote which led to "How do I get it there?" Turns out putting tfstate in a public repository is a terrible idea.

I came to learn that I could use AWS S3 to store my tfstate and configure it as my backend. I defined the resources for the backend in main.tf which included a S3 bucket for the literal tf_state and a table for the lock.

Once that was done, I made a backend.tf to tell Terraform this is where to look for your tf_state. I ran 'terraform init -reconfigure' and pushed my changes to remote. Both workflows passed, my website was up and running. The Cloud Resume Challenge was complete!

Final Thoughts:

I learned an incredible amount doing this project. I've done a few small side projects but nothing of this size. This project gives me something tangible to point to that shows that I can produce results. It's also incredibly cheap to run. It costs me less than a dollar a month and I have to renew my domain annually.

As for Terraform, it was interesting to learn. I use Ansible daily at work and I must say, I'm not sold on Terraform. I believe it's a powerful tool but I haven't seen it solve something Ansible can't. Now, that's probably due to my lack of use with it but I initially thought it was cloud agnostic. I'd define my infrastructure and I could deploy it to AWS, GCP, Azure and whatever else.

In hindsight, that seems like quite the undertaking. But I see now that it's defined, I could deploy this website in something like prod, dev, test without rewriting it all again. Overall, I'd recommend this challenge to anyone trying to learn IT or the Cloud. This exposes you to a variety of leading industry tools and I believe everyone learns best by doing.