<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sasquatch8946</title>
    <description>The latest articles on Forem by Sasquatch8946 (@sasquatch8946).</description>
    <link>https://forem.com/sasquatch8946</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1124298%2Fcd4f4061-a5d7-44b4-80f9-f115670b5c33.png</url>
      <title>Forem: Sasquatch8946</title>
      <link>https://forem.com/sasquatch8946</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sasquatch8946"/>
    <language>en</language>
    <item>
      <title>Why I switched back to Microsoft CDN Classic from Azure Front Door Standard</title>
      <dc:creator>Sasquatch8946</dc:creator>
      <pubDate>Thu, 30 Jan 2025 22:12:45 +0000</pubDate>
      <link>https://forem.com/sasquatch8946/why-i-switched-back-to-microsoft-cdn-classic-from-azure-front-door-standard-55bd</link>
      <guid>https://forem.com/sasquatch8946/why-i-switched-back-to-microsoft-cdn-classic-from-azure-front-door-standard-55bd</guid>
      <description>&lt;p&gt;&lt;em&gt;Edit: Since writing this blog, it has been pointed out to me that Cloudflare can enable end-to-end TLS encryption for HTTP endpoints in Azure if DNS records are configured in &lt;a href="https://developers.cloudflare.com/dns/proxy-status/#benefits" rel="noopener noreferrer"&gt;proxied mode&lt;/a&gt;. Proxy mode needs to be temporarily disabled in order for custom domain name validation to take place in whatever Azure service being used.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Also, another possible solution suggested to me by Gwyneth Peña-Siguenza is to use Azure Static Web Apps to host the front-end application.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;em&gt;I will test both solutions at a later date.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The Issue
&lt;/h1&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/sasquatch8946/my-exciting-journey-into-the-world-of-microsoft-azure-and-the-cloud-resume-challenge-3lk0"&gt;first ever post&lt;/a&gt;, I described my journey with the Cloud Resume Challenge. &lt;/p&gt;

&lt;p&gt;Right before sharing my finished product with the world, I heeded a warning in the Azure portal about my Front Door instance/CDN, which said that the Classic SKU I was using was going to be retired in 2027. It advised migrating to the Standard or Premium SKU. &lt;/p&gt;

&lt;p&gt;Since unexpectedly high costs are a common pitfall with deploying new cloud services, I was curious about the cost of the newer tiers, so I took a look at the &lt;a href="https://azure.microsoft.com/en-us/pricing/details/frontdoor/" rel="noopener noreferrer"&gt;relevant page&lt;/a&gt; in the Microsoft Azure documentation. &lt;/p&gt;

&lt;p&gt;I went through the doc too quickly, saw that the "request" and "outbound data transfer" costs were quite low as I expected. So, I migrated to the Standard tier without any further thought. &lt;/p&gt;

&lt;p&gt;Thankfully, when visiting the Cloud Resume Challenge Discord, I saw a post from someone saying that the new Azure Front Door SKUs were prohibitively expensive for most challenge participants. When I reviewed the pricing page once again, I noticed that a new base cost of $35/month had been introduced.&lt;/p&gt;

&lt;p&gt;The Discord post had recommended using Cloudflare CDN as an alternative; however, when I examined that option further I realized that in order to enable TLS on a custom domain through App Service, I would need at least the Basic tier, which costs in the ballpark of $50/month. Therefore, I concluded that Cloudflare CDN would not be suitable for my use case.&lt;/p&gt;

&lt;p&gt;Furthermore, Cloudflare CDN would also fail to provide encryption for those who follow the original guidelines of the Cloud Resume Challenge and choose to host their website in a Blob Storage account. See the below quote from &lt;a href="https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website" rel="noopener noreferrer"&gt;this Microsoft documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To enable HTTPS, you'll have to use Azure CDN because Azure Storage doesn't yet natively support HTTPS with custom domains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, in order to use Cloudflare CDN and host the front-end of my website in Azure, I would have to sacrifice TLS. &lt;/p&gt;

&lt;h1&gt;
  
  
  Lessons Learned
&lt;/h1&gt;

&lt;p&gt;Always review pricing for cloud services very carefully, and even then, after deploying a new service you should monitor the cost closely for a bit and have multiple &lt;a href="https://learn.microsoft.com/en-us/azure/cost-management-billing/costs/cost-mgt-alerts-monitor-usage-spending" rel="noopener noreferrer"&gt;budget alerts&lt;/a&gt; set up on your Azure subscription.&lt;/p&gt;

&lt;h1&gt;
  
  
  How I Switched Back
&lt;/h1&gt;

&lt;p&gt;There wasn't a way to do this without zero downtime unfortunately, so, last weekend, I had to delete my Front Door, dig for an old version of my frontdoor.bicep template in my git commit history, check it out with "git checkout commitsha -- /path/to/file," commit and push the change, and re-trigger my Azure DevOps pipeline. &lt;/p&gt;

&lt;p&gt;You can see the latest version of my template by going to my &lt;a href="https://github.com/Sasquatch8946/azure-resume-flask" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; and navigating to the /iac/frontend/ folder.&lt;/p&gt;

&lt;h1&gt;
  
  
  Moving forward
&lt;/h1&gt;

&lt;p&gt;Azure Classic SKU is going to reach end-of-life by some time in 2027. I and other Cloud Resume Challengers may have to consider moving all or part of our solution to a different cloud or hosting provider if we want to avoid increased costs.&lt;/p&gt;

</description>
      <category>cdn</category>
      <category>azure</category>
      <category>frontdoor</category>
      <category>bicep</category>
    </item>
    <item>
      <title>My exciting journey into the world of Microsoft Azure and the Cloud Resume Challenge</title>
      <dc:creator>Sasquatch8946</dc:creator>
      <pubDate>Mon, 20 Jan 2025 19:32:56 +0000</pubDate>
      <link>https://forem.com/sasquatch8946/my-exciting-journey-into-the-world-of-microsoft-azure-and-the-cloud-resume-challenge-3lk0</link>
      <guid>https://forem.com/sasquatch8946/my-exciting-journey-into-the-world-of-microsoft-azure-and-the-cloud-resume-challenge-3lk0</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@lukaszlada?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Łukasz Łada&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/sea-of-clouds-LtWFFVi1RXQ?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What is the Cloud Resume Challenge?
&lt;/h1&gt;

&lt;p&gt;The Cloud Resume Challenge was originally a blog post/book authored by Forest Brazeal, which can be found &lt;a href="https://cloudresumechallenge.dev/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The point of the project is to get hands-on learning about the cloud platform of your choice by building a fully functional resume website with an API that tracks the number of visitors to the site.&lt;/p&gt;

&lt;p&gt;Personally, my initial exposure was through Gwyneth Peña-Siguenza's &lt;a href="https://www.youtube.com/watch?v=ieYrBWmkfno&amp;amp;t=4231s" rel="noopener noreferrer"&gt;A Cloud Guru video on YouTube&lt;/a&gt;. I discovered Gwyneth's &lt;a href="https://www.youtube.com/@MadeByGPS" rel="noopener noreferrer"&gt;personal YouTube channel&lt;/a&gt; during my AZ-900 certification studies, and I highly recommend her channel to anyone interested in breaking into Cloud. &lt;/p&gt;

&lt;p&gt;Like with any difficult project, it is good to break up the Cloud Resume Challenge into smaller chunks that we can tackle individually, so we don't get intimidated by the sheer enormity of the project. &lt;/p&gt;

&lt;p&gt;Conveniently, Forest Brazeal does this for us. I will use his outline to help detail my own journey with the Cloud Resume Project. &lt;/p&gt;

&lt;p&gt;TL;DR: here's a diagram.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8etid1yt4y96a1ml7ze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8etid1yt4y96a1ml7ze.png" alt="infrastructure diagram" width="800" height="692"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 1 - Earn a cloud certification
&lt;/h1&gt;

&lt;p&gt;I started this project shortly after I earned my AZ-900 Azure Fundamentals certification in November of 2022. At the time of publishing this blog, I also hold the AZ-104 (Administrator Associate) and AZ-305 (Solutions Architect) certifications.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 2 - HTML
&lt;/h1&gt;

&lt;p&gt;I actually did part of a Codecademy front-end development course long before I committed to pursuing IT and studying CompTIA A+ materials, but I wouldn't say I'm anything more than a dilettante in this area. At first, I made a very bare bones site that looked like a paper resume, but ultimately, I ended up just using the template provided in ACG's video. &lt;/p&gt;

&lt;p&gt;My one innovation here is that I added verification links for my certifications. &lt;/p&gt;

&lt;h1&gt;
  
  
  Part 3 - CSS
&lt;/h1&gt;

&lt;p&gt;As with the HTML, I mostly just relied on ACG's template. &lt;/p&gt;

&lt;p&gt;The one thing I spent a good amount of time on was trying to get my LinkedIn picture to display correctly. When I first added my image, it looked very silly and stretched out. I temporarily remediated this by adding the CSS property "object-fit: cover." However, later, after deploying the website, I noticed that this produced pixelated artifacts on the edges of my image in Chromium-based browsers. &lt;/p&gt;

&lt;p&gt;I ended up following &lt;a href="https://blog.filestack.com/api/resize-image-css-easiest-methods-2022/" rel="noopener noreferrer"&gt;this article&lt;/a&gt; to figure out how to define the CSS properties so that I could resize my image while maintaining its native aspect ratio.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 4 - Static Website
&lt;/h1&gt;

&lt;p&gt;Originally, I followed ACG's guidance and created a static website using an Azure storage account. However, over time I became uncomfortable with the inability to securely store and access secrets, namely the Azure Function URL, which contains an authentication key. &lt;/p&gt;

&lt;p&gt;So, eventually, I migrated my app to a Python Flask app hosted in App Service. In this configuration, I am able to retrieve my Function App URL from an app setting that in turn retrieves the URL from Key Vault. App Service, like all the other services in my solution that require accessing Key Vault secrets, gets authenticated via a system-assigned managed identity. You can think of managed identities in Azure as managed service accounts for which you do not have to handle credentials (i.e. usernames and passwords). Most of the time you need to use a system-assigned managed identity for an Azure resource, you can enable it by going to Settings &amp;gt; Identity. In Key Vault, you can then create an access policy assigned to the managed identity or alternatively assign an RBAC role to it depending on which access control settings you use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 5 - HTTPS
&lt;/h1&gt;

&lt;p&gt;TLS 1.2 is configured in my Bicep (Infrastructure-as-Code) template for my Azure Front Door, which is a service that provides reduced latency and encryption for apps through Azure's cloud Content Delivery Network. In order for Front Door to create a managed TLS certificate for a custom domain, it needs to perform some validation. After creating my Front Door instance, I have to go in the Azure portal to Front Door &amp;gt; custom domains &amp;gt; clicking on the "pending" validation status &amp;gt; copy the TXT record value &amp;gt; create a new TXT record with my DNS provider. One small gotcha here: you may be tempted to copy the DNS record name provided in the Azure portal. First, you'll want to make sure you lop off the domain portion, since your domain provider probably automatically appends that to the "host" or subdomain you define (e.g. "www").&lt;/p&gt;

&lt;p&gt;Validating my custom domain, when I did things correctly, never took more than about 10-12 minutes. You can troubleshoot this by seeing if public DNS resolvers can see your TXT record. I just went to &lt;a href="https://dnschecker.org/" rel="noopener noreferrer"&gt;https://dnschecker.org/&lt;/a&gt;, searched the name of my DNS record ("_dnsauth.subdomain.domain"), and saw if there were results. This brings me to the next part.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 6 - DNS
&lt;/h1&gt;

&lt;p&gt;I used NameCheap as my DNS provider. Registering an account and purchasing a domain name was easy. If you pick a less popular TLD (top-level domain) you can get quite a low price (I think I pay $15 USD a year).&lt;/p&gt;

&lt;p&gt;After deploying all of my infrastructure and creating the text record to validate my custom domain, I created a CNAME record to associate the subdomain "www" with the URL of my Front Door instance, since creating a CNAME record for the root of the domain, i.e. "seanchapman.xyz", is not allowed. I then configured redirection so that visiting the root domain "seanchapman.xyz" automatically takes you to "&lt;a href="http://www.seanchapman.xyz." rel="noopener noreferrer"&gt;www.seanchapman.xyz.&lt;/a&gt;"&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 7 - JavaScript
&lt;/h1&gt;

&lt;p&gt;I wrote a small script that creates an event listener with the trigger "DOMContentLoaded." In other words, I created some code that would run any time the page was loaded, fetch the updated visitor counter, and insert that into the web page. After migrating to App Service, instead of having the JavaScript communicate directly with my API, I had it call an endpoint in my site that would run a specific function defined in my Python Flask app. The Python code then handles calling the API and getting the visitor counter, and returns that value back to the JavaScript.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 8 - Database
&lt;/h1&gt;

&lt;p&gt;I used Cosmos DB because of its fast response times and the fact that it allowed for a non-relational database. After all, the visitor counter only requires a simple key-value pair. I configured my Azure Function/API to initialize this value when it gets executed for the first time.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 9 - API
&lt;/h1&gt;

&lt;p&gt;I used an Azure Function app coded in Python using the Python Programming Model V2. There was a bit of a learning curve to figuring out the input and output &lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?tabs=isolated-process%2Cnode-v4%2Cpython-v2&amp;amp;pivots=programming-language-python" rel="noopener noreferrer"&gt;bindings&lt;/a&gt; that handled retrieving and updating the visitor counter in Cosmos DB, but once I figured that out the rest of the code was pretty simple to work out. In short, bindings in Azure Functions handle connections to other Azure services without you needing to write a ton of code yourself to connect to them.&lt;/p&gt;

&lt;p&gt;Additionally, since I was concerned about someone running a script to endlessly call my function app and jack up my Azure bill (though that would be difficult given you're only charged &lt;a href="https://azure.microsoft.com/en-us/pricing/details/functions/" rel="noopener noreferrer"&gt;$0.20 per 1,000,000 executions&lt;/a&gt;), I decided to put an API Management Service in between my App Service app and my Function App. The API Management service facilitates managing APIs and allows you to intercept calls to your API, re-route them to different back-ends, apply policies to them, etc. In my case, I created a policy that imposed rate-limiting on the API. Rate-limiting makes it so that only so many back-end requests can be made within a defined time period.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 10 - Python...or C#?
&lt;/h1&gt;

&lt;p&gt;C#/.NET are unsurprisingly first-class citizens when it comes to Azure (hint: they're made by Microsoft). I coded my Azure Function and web app in Python because I was much more familiar with it, but given that I'm using the cheapest and least performant SKUs of these services, I would probably see a &lt;a href="https://mikhail.io/serverless/coldstarts/azure/" rel="noopener noreferrer"&gt;noticeable improvement in performance&lt;/a&gt; if I were to re-code these things in C#. I've started learning C# and will refactor my code eventually. ACG's repository has C# code, but I didn't want to mindlessly copy the code without understanding how it worked. Hence, I created my own code in Python.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 11 - Tests
&lt;/h1&gt;

&lt;p&gt;I wanted to follow good development practices and also prevent glaring errors whenever I made changes to my site, so I created unit tests for both the Azure Function and the App Service app. Unit tests are pieces of code that are ran to check that the main code meets certain expectations, returns expected values, doesn't error out, etc.&lt;/p&gt;

&lt;p&gt;This is where using Azure Function input/output bindings came into play and made developing unit tests much, much easier. My original code for the Azure Function manually created a Cosmos DB client, and while it was easier initially than figuring out the input binding, it made it more difficult to pass in a mock Cosmos DB object. &lt;/p&gt;

&lt;p&gt;I have more notes in my original repository on this (see "Old repository" link below), but the biggest key to figuring out this portion is to use input/output bindings, and then view the documentation for those bindings to see the classes used by the binding parameters. In order to use the bindings and to mock the objects they create in your unit tests, you'll need to put the corresponding modules from the Python Azure SDK in your requirements.txt.&lt;/p&gt;

&lt;p&gt;I also created unit tests for the front-end Flask app, including one that ensures that sending an HTTP GET (equivalent to a user navigating to my site in the browser) successfully leads to my HTML page being rendered. Thankfully, the Flask Python module includes a "test_client" object that you can use as a fixture in all of your tests so you don't have to spin up a web server any time you want to run tests.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 12 - Infrastructure as Code (IaC)
&lt;/h1&gt;

&lt;p&gt;I ended up deciding to use Bicep to automate the setup of all the necessary resources for this project. Its syntax seemed more friendly than ARM, which I had used for much simpler deployments previously, and does not require one to maintain a state file and modules like Terraform.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 13 - Source Control
&lt;/h1&gt;

&lt;p&gt;I used Git/GitHub to store my code.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 14 - CI/CD (Back end)
&lt;/h1&gt;

&lt;p&gt;CI/CD (Continuous Integration/Continuous Development) pipelines consist of .yml files that contain a list of steps to be executed whenever triggered, usually by a "git push" to a specific repository or folder in a repository. Developers typically use them to automatically run unit tests against their code to check that new changes do not break functionality.&lt;/p&gt;

&lt;p&gt;For my use case, I used the pipelines both to test my code and to automate the deployment of infrastructure (via .bicep files) that the code needed to run on.&lt;/p&gt;

&lt;p&gt;In the original iteration of my project, I used GitHub Actions. Later I switched to Azure Devops pipelines in order to learn that particular service and also to keep my data contained within Azure services, which, at least on the face of it, seems more secure than storing secrets in an external third-party service.&lt;/p&gt;

&lt;p&gt;From a high-level, the basic structure of both my back-end and front-end pipelines are: deploy supporting resources &amp;gt; run unit tests to ensure code is (relatively) error-free &amp;gt; push code to relevant resource &amp;gt; deploy any further resources that the code needs to fully function.&lt;/p&gt;

&lt;p&gt;In order to be able to be able to deploy a resource &amp;gt; store some secret property from that resource in Key Vault &amp;gt; use that secret in a subsequent deployment, I had to rely on Az PowerShell commands to deploy one or a few templates at a time (using "New-AzResourceGroupDeployment"), so that the secrets from earlier deployments would be available to later ones. &lt;/p&gt;

&lt;p&gt;To repeatedly deploy resources without worrying about names conflicting with previous deployments and to easily refer back to resources created in previous steps, my .bicep templates use a guid/"seed" value that I randomly generate with the aid of a PowerShell script. I configure a variable in my Azure Devops pipeline containing this guid that is then passed to all my .bicep files.&lt;/p&gt;

&lt;p&gt;Some resources were trickier than others to create through IaC. The difficulty with automatically deploying my API management Service is that I needed to add my Function API key as a "named value" for use in the API Management policy that does the rate-limiting (defined in my "policy.xml"). I couldn't find a native way to retrieve Azure Function keys in Bicep, so I had to add a step to my .yml pipeline to run an Azure CLI script that grabs the primary key and stores it in Key Vault.&lt;/p&gt;

&lt;p&gt;After that, I found that I couldn't simultaneously deploy both the API Management policy and the named value. So, I resorted to using Az PowerShell to check for the named value, deploy a bicep file if it didn't exist, and then do the same for the policy.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 15 - CI/CD (Front end)
&lt;/h1&gt;

&lt;p&gt;The pipeline for the front end is much simpler. We only need to deploy an App Service and Front Door. &lt;/p&gt;

&lt;p&gt;Microsoft had a very good template for creating a Front Door instance with a custom domain. After some tweaking I incorporated this into my App Service deployment by turning Microsoft's template into a module with the "module" keyword in Bicep.&lt;/p&gt;

&lt;h1&gt;
  
  
  Part 16 - Blog post (&lt;em&gt;phew&lt;/em&gt;)
&lt;/h1&gt;

&lt;p&gt;I wrote this blog and published it to LinkedIn. &lt;/p&gt;

&lt;p&gt;It's been a very fun journey that I don't consider over yet. Any one of the above topics are vast and could be discussed in an entire blog series. &lt;/p&gt;

&lt;p&gt;I've used this project as a touchstone that I keep coming back to to learn new cloud/development topics. I will continue to do so and share what I've learned in future posts! &lt;/p&gt;

&lt;p&gt;Feel free to comment below or message me on LinkedIn if you have any questions, comments, or would like to discuss the Cloud Resume project in more depth.&lt;/p&gt;

&lt;p&gt;Links:&lt;br&gt;
&lt;a href="https://github.com/Sasquatch8946/azure-resume-flask" rel="noopener noreferrer"&gt;Current GitHub repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/Sasquatch8946/azure-resume" rel="noopener noreferrer"&gt;Old repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.seanchapman.xyz" rel="noopener noreferrer"&gt;Live website&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/sean-chapman-66a5b0107/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>resume</category>
      <category>azurefunctions</category>
      <category>azure</category>
    </item>
  </channel>
</rss>
