<?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: sidpalas</title>
    <description>The latest articles on Forem by sidpalas (@sidpalas).</description>
    <link>https://forem.com/sidpalas</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%2F385135%2F6e442701-0095-405c-85f8-5eabe9019909.png</url>
      <title>Forem: sidpalas</title>
      <link>https://forem.com/sidpalas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sidpalas"/>
    <language>en</language>
    <item>
      <title>How to Properly Manage Application Secrets (From Beginner to Expert!) 🔐</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Tue, 03 Nov 2020 03:44:03 +0000</pubDate>
      <link>https://forem.com/sidpalas/how-to-properly-manage-application-secrets-from-beginner-to-expert-1dh6</link>
      <guid>https://forem.com/sidpalas/how-to-properly-manage-application-secrets-from-beginner-to-expert-1dh6</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/7NTFZoDpzbQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Where do you fall on the scale? Are there any levels I missed?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Level -2: No authentication&lt;/li&gt;
&lt;li&gt;Level -1: All passwords = "password"&lt;/li&gt;
&lt;li&gt;Level 0:  Hardcode everywhere&lt;/li&gt;
&lt;li&gt;Level +1: Move secrets into a config file (and add to .gitignore)&lt;/li&gt;
&lt;li&gt;Level +2: Encrypt config file&lt;/li&gt;
&lt;li&gt;Level +3: Use secret manager (e.g. &lt;a href="https://aws.amazon.com/secrets-manager/"&gt;AWS Secrets Manager&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Level +4: Dynamic ephemeral credentials (using a tool like &lt;a href="https://www.vaultproject.io/"&gt;Hashicorp Vault&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Port Knocking (Network Security Technique) Explained and Demoed in 5 Minutes!</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Tue, 06 Oct 2020 18:28:47 +0000</pubDate>
      <link>https://forem.com/sidpalas/port-knocking-network-security-technique-explained-and-demoed-in-5-minutes-5d92</link>
      <guid>https://forem.com/sidpalas/port-knocking-network-security-technique-explained-and-demoed-in-5-minutes-5d92</guid>
      <description>&lt;p&gt;Remember the secret knock you used to have to determine if someone was allowed into your treehouse as a kid? 👊&lt;/p&gt;

&lt;p&gt;There is a digital equivalent called "port knocking" in which a secret sequence of connection requests is used to open up firewall access. 🧱&lt;/p&gt;

&lt;p&gt;I just released a quick (&amp;lt;5 min) video in which I explain how it works and provide a working demo!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/IBR3oLqGBj4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>How to Debug Docker Containers (Python + VSCode)</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Mon, 20 Jul 2020 17:14:02 +0000</pubDate>
      <link>https://forem.com/sidpalas/debugging-docker-containers-3cgj</link>
      <guid>https://forem.com/sidpalas/debugging-docker-containers-3cgj</guid>
      <description>&lt;p&gt;When containers are working they are amazing and make running your code across different environments seamless...&lt;/p&gt;

&lt;p&gt;However, when something goes wrong, the additional layers of abstraction can make debugging them difficult.&lt;/p&gt;

&lt;p&gt;Here are three techniques you can use to debug them:&lt;/p&gt;

&lt;p&gt;1) Override the entrypoint at runtime and exec into the container&lt;br&gt;
2) Copy files into or out of the container for inspection/comparison&lt;br&gt;
3) Run a remote debugger inside the container and attach to it over the network&lt;/p&gt;

&lt;p&gt;I created a video walking through how to use these three techniques:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/qCCj7qy72Bg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Leave your own tips in the comments!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>tutorial</category>
      <category>debugging</category>
    </item>
    <item>
      <title>DISCUSS: What was your first web app deployment experience like?</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Fri, 10 Jul 2020 22:53:15 +0000</pubDate>
      <link>https://forem.com/sidpalas/discuss-what-was-your-first-web-app-deployment-experience-like-2632</link>
      <guid>https://forem.com/sidpalas/discuss-what-was-your-first-web-app-deployment-experience-like-2632</guid>
      <description>&lt;p&gt;Did you deploy to one of the major cloud providers such as AWS, GCP, or Azure? &lt;/p&gt;

&lt;p&gt;Did you use a PaaS option like Heroku?&lt;/p&gt;

&lt;p&gt;How did it go? Any lessons learned you can share?&lt;/p&gt;




&lt;p&gt;I recently had a chance to talk with Dylan Albertazzi, a CS student at Oregon State University, about various options for deploying the web application he has been working on!&lt;/p&gt;

&lt;p&gt;We discussed the tradeoffs of options ranging from Heroku to DigitalOcean and how he could go about scaling the application down the road.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/zX1dol4j2JA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>[Video] Trying to learn Swift by solving interview prep questions!</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Sun, 21 Jun 2020 03:30:09 +0000</pubDate>
      <link>https://forem.com/sidpalas/video-trying-to-learn-swift-by-solving-interview-prep-questions-1kel</link>
      <guid>https://forem.com/sidpalas/video-trying-to-learn-swift-by-solving-interview-prep-questions-1kel</guid>
      <description>&lt;p&gt;I thought it would be fun to try solving an interview algorithm question in swift even though I have never used it before.&lt;/p&gt;

&lt;p&gt;It was bit of a struggle, but I managed to get it working eventually... enjoy!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/TYY6y8Nwo-o"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>swift</category>
      <category>beginners</category>
      <category>ios</category>
    </item>
    <item>
      <title>The HIDDEN COST of Machine Learning!</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Fri, 12 Jun 2020 23:26:26 +0000</pubDate>
      <link>https://forem.com/sidpalas/the-hidden-cost-of-machine-learning-58hd</link>
      <guid>https://forem.com/sidpalas/the-hidden-cost-of-machine-learning-58hd</guid>
      <description>&lt;p&gt;There can be hidden costs associated with developing and deploying machine learning applications that differ from traditional software systems!&lt;/p&gt;

&lt;p&gt;It is important to consider these when deciding if ML is right for your particular use case.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/F2NpGbbZ6s8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Doing Stupid Stuff with GitHub Actions! (FIVE Actions Implemented and Explained) </title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Sat, 06 Jun 2020 16:58:12 +0000</pubDate>
      <link>https://forem.com/sidpalas/doing-stupid-stuff-with-github-actions-five-actions-implemented-and-explained-305l</link>
      <guid>https://forem.com/sidpalas/doing-stupid-stuff-with-github-actions-five-actions-implemented-and-explained-305l</guid>
      <description>&lt;p&gt;I decided to have some fun and implement a variety of ridiculous GitHub Actions as a way to learn more about and highlight various features of GitHub Actions.&lt;/p&gt;

&lt;p&gt;Here are the actions with timestamp links:&lt;/p&gt;

&lt;p&gt;1) &lt;a href="https://www.youtube.com/watch?v=w7-ugGAYVCo&amp;amp;t=80s"&gt;Holiday Reminder&lt;/a&gt; -- Sending myself a holiday reminder email&lt;br&gt;
2) &lt;a href="https://www.youtube.com/watch?v=w7-ugGAYVCo&amp;amp;t=120s"&gt;Recursive Action&lt;/a&gt; -- An action that calls itself&lt;br&gt;
3) &lt;a href="https://www.youtube.com/watch?v=w7-ugGAYVCo&amp;amp;t=224s"&gt;Exponential Action&lt;/a&gt; -- Like the recursive action, but the action calls itself multiple times&lt;br&gt;
4) &lt;a href="https://www.youtube.com/watch?v=w7-ugGAYVCo&amp;amp;t=325s"&gt;Smart Lights&lt;/a&gt; -- Turning on/off smart home lights with each commit&lt;br&gt;
5) &lt;a href="https://www.youtube.com/watch?v=w7-ugGAYVCo&amp;amp;t=380s"&gt;Tic-Tac-Toe&lt;/a&gt; -- A game of tic-tac-toe where the compute player plays within an action&lt;/p&gt;

&lt;p&gt;Also, the GitHub repo can be found here: &lt;a href="https://github.com/sidpalas/stupid-actions"&gt;https://github.com/sidpalas/stupid-actions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/w7-ugGAYVCo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>github</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using GitHub Actions as a Makeshift Uptime Monitor (Video Tutorial // 3 min)</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Sat, 23 May 2020 16:06:11 +0000</pubDate>
      <link>https://forem.com/sidpalas/using-github-actions-as-a-makeshift-uptime-monitor-video-tutorial-3-min-5a4p</link>
      <guid>https://forem.com/sidpalas/using-github-actions-as-a-makeshift-uptime-monitor-video-tutorial-3-min-5a4p</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/o5ZLWpvus50"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;With just 15 lines of &lt;code&gt;yaml&lt;/code&gt; we can configure GitHub Actions as a simple uptime monitor for a website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Uptime Monitor&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Run every 10 minutes&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*/10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ping_site&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Uptime Check&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ping Site&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;srt32/uptime@v0.2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;url-to-hit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://devopsdirective.com/"&lt;/span&gt;
        &lt;span class="na"&gt;expected-statuses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Watch the video for the full explanation and walkthrough!&lt;/p&gt;




&lt;p&gt;What other non-standard use cases can you think of to do within GitHub actions (or other CI tool)? Leave a comment and let me know!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>github</category>
      <category>webdev</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Creating a Password Protected Website with IAP, App Engine, and CircleCI (Written + Video Guides)</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Sat, 16 May 2020 18:52:20 +0000</pubDate>
      <link>https://forem.com/sidpalas/creating-a-password-protected-website-with-iap-app-engine-and-circleci-written-video-guides-59i8</link>
      <guid>https://forem.com/sidpalas/creating-a-password-protected-website-with-iap-app-engine-and-circleci-written-video-guides-59i8</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you have a static website that needs to be password protected, using Google's Identity Aware Proxy along with App Engine is one of the simplest ways to accomplish this.&lt;/p&gt;

&lt;p&gt;If you want to follow along with a site of your own, I have provided a working example in this &lt;strong&gt;&lt;a href="https://github.com/sidpalas/password-protected-docs"&gt;GitHub repo&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; I have also seen &lt;a href="https://douglasduhaime.com/posts/s3-lambda-auth.html"&gt;S3 + Lambda used to accomplish this&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Table of Contents:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
The Need

&lt;ul&gt;
&lt;li&gt;Other static hosting solutions&lt;/li&gt;
&lt;li&gt;Handling passwords&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Solution (IAP + App engine)&lt;/li&gt;
&lt;li&gt;
Example Site

&lt;ul&gt;
&lt;li&gt;Configuring App Engine&lt;/li&gt;
&lt;li&gt;Setting up IAP&lt;/li&gt;
&lt;li&gt;
Configuring CI/CD with CircleCI

&lt;ul&gt;
&lt;li&gt;Workflow&lt;/li&gt;
&lt;li&gt;1) checkout&lt;/li&gt;
&lt;li&gt;2) Install Sphinx&lt;/li&gt;
&lt;li&gt;3) Make Docs&lt;/li&gt;
&lt;li&gt;4) Deploy Docs&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Next steps

&lt;ul&gt;
&lt;li&gt;Custom Domain&lt;/li&gt;
&lt;li&gt;Custom OAuth screen&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Need
&lt;/h2&gt;

&lt;p&gt;A company I have been working with (&lt;a href="https://gauntlet.network/"&gt;Gauntlet Networks&lt;/a&gt;) needed to set up a website containing documentation for the simulation SDK they are building, but wanted to restrict access to only their clients and employees.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other static hosting solutions
&lt;/h3&gt;

&lt;p&gt;There are a plethora of high-quality, affordable options (&lt;a href="https://pages.github.com/"&gt;GitHub Pages&lt;/a&gt;, &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;, a &lt;a href="https://devopsdirective.com/posts/2020/02/hugo-and-caddy-on-gcp/"&gt;Free Tier Cloud VM&lt;/a&gt;, or even an &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html"&gt;S3 bucket&lt;/a&gt;), but adding a layer of authentication makes the task significantly more challenging.&lt;/p&gt;

&lt;p&gt;Netlify does offer &lt;a href="https://docs.netlify.com/visitor-access/password-protection/#site-wide-protection"&gt;password protection&lt;/a&gt; using basic authentication for their paid plans, but using this would still require distributing the password to the authorized individuals and rotating it whenever it was necessary to revoke anyone's access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling passwords
&lt;/h3&gt;

&lt;p&gt;Historically, implementing authentication would require adding significant complexity to our website's architecture. We would need a database of some kind to store the (hashed) passwords and server-side processing to check if a login attempt is valid. We would also probably need to add some form of email confirmation/password reset functionality because people are &lt;a href="https://xkcd.com/936/"&gt;terrible at remembering their passwords&lt;/a&gt;. All of the sudden the one hour task of getting the documentation site set up just ballooned into something that could take weeks to accomplish!&lt;/p&gt;

&lt;p&gt;Luckily, we don't actually need to take on all of this complexity ourselves. A standard called &lt;a href="https://en.wikipedia.org/wiki/OAuth"&gt;OAuth&lt;/a&gt; allows us to leverage the authentication system of another entity (for example, Google) to provide "secure delegated access" to our content. &lt;/p&gt;




&lt;h2&gt;
  
  
  Solution (IAP + App engine)
&lt;/h2&gt;

&lt;p&gt;OAuth by itself would allow us to set up a sign-in flow using an external authentication provider, but for this use case, we want the process to be completely hands off.&lt;/p&gt;

&lt;p&gt;This is where Google's &lt;a href="https://cloud.google.com/iap/docs/concepts-overview"&gt;Identity Aware Proxy (IAP)&lt;/a&gt; comes in. Cloud IAP uses Google Sign-In and GCP's Identity and Access Management (IAM) to handle authentication and authorization to control access to GCP resources. Granting access to the site can then be managed using individual Google accounts and/or Google groups.&lt;/p&gt;

&lt;p&gt;Given that Gauntlet was already using Google Cloud Platform, leveraging IAP in conjunction with Google App Engine to host the static site provided a simple, clean solution to satisfy the need.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yYZFDzJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/iap-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yYZFDzJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/iap-app.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Example Site
&lt;/h2&gt;

&lt;p&gt;Because a documentation website was the use case that inspired this post, I am using the &lt;a href="https://www.sphinx-doc.org/en/master/usage/quickstart.html"&gt;Sphinx Python documentation generator quick start&lt;/a&gt; as the basis for this demo. The static site files will be hosted using App Engine, with CircleCI for CI/CD.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/jPMWWLf9W-Q"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring App Engine
&lt;/h3&gt;

&lt;p&gt;Google has documentation outlining how to &lt;a href="https://cloud.google.com/appengine/docs/standard/python/getting-started/hosting-a-static-website"&gt;use app engine to create a static site&lt;/a&gt; by adding an &lt;code&gt;app.yaml&lt;/code&gt; file to the codebase that tells App Engine how to interpret URL request paths. Sphinx stores its output in the &lt;code&gt;/docs/build/html&lt;/code&gt; directory by default, so I used the following app.yaml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python27&lt;/span&gt;
&lt;span class="na"&gt;api_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;threadsafe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;handlers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
  &lt;span class="na"&gt;static_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/build/html/index.html&lt;/span&gt;
  &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/build/html/index.html&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/(.*)&lt;/span&gt;
  &lt;span class="na"&gt;static_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/build/html/\1&lt;/span&gt;
  &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs/build/html/(.*)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With this in place, a running the &lt;code&gt;gcloud app deploy&lt;/code&gt; command within the root of the project deploys the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up IAP
&lt;/h3&gt;

&lt;p&gt;With the website deployed, the next step was to follow Google's guide to enabling &lt;a href="https://cloud.google.com/iap/docs/app-engine-quickstart"&gt;IAP for App Engine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ftCR8md6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/iap-page.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ftCR8md6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/iap-page.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This involves configuring a few things for the Oauth Consent page (app name, support email, etc...), but the process is relatively painless. &lt;/p&gt;

&lt;p&gt;The final step is to grant the &lt;code&gt;IAP-secured Web App User&lt;/code&gt; access to all authorized individuals. In the screenshot below, I granted access to &lt;code&gt;allAuthenticatedUsers&lt;/code&gt; which allows anyone with a Google account to access it. (&lt;a href="https://devops-directive-project.uc.r.appspot.com/"&gt;Test it out here!&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XnhYBimE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/iap-access.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XnhYBimE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/iap-access.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring CI/CD with CircleCI
&lt;/h3&gt;

&lt;p&gt;At this point, the website is live and access controlled, but in order to minimize future work, it is useful to set up a system to handle continuous integration and deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://circleci.com/"&gt;CircleCi&lt;/a&gt; offers simple integration with GitHub and a &lt;a href="https://circleci.com/pricing/"&gt;free tier&lt;/a&gt; that will easily handle a small project like this. The first step is to create a CircleCI account and then grant access to the GitHub repo containing the source files using their &lt;a href="https://github.com/marketplace/circleci"&gt;GitHub marketplace app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/e7K8oUUyv-Q"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  Workflow
&lt;/h4&gt;

&lt;p&gt;To define our workflow, we create a file at &lt;code&gt;.circleci/config.yml&lt;/code&gt; containing two parts, a workflow definition and a job definition.&lt;/p&gt;

&lt;p&gt;We define the workflow using version 2 of the CircleCI API. This workflow tells CircleCi to run the &lt;code&gt;build_and_deploy&lt;/code&gt; job on any push to the &lt;code&gt;master&lt;/code&gt; branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;build_and_deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;build_and_deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;only&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we define the &lt;code&gt;build_and_deploy&lt;/code&gt; job. Each CircleCI job runs within a Docker image, and because the goal is to deploy to Google App Engine, the &lt;a href="https://hub.docker.com/r/google/cloud-sdk/"&gt;&lt;code&gt;google/cloud-sdk&lt;/code&gt;&lt;/a&gt; provides a good starting point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.1&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build_and_deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     
    &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google/cloud-sdk:slim&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The job is broken down into four steps:&lt;br&gt;
1) checkout,&lt;br&gt;
2) Sphinx installation,&lt;br&gt;
3) Generating the website&lt;br&gt;
4) Deploying to App Engine&lt;/p&gt;


&lt;h4&gt;
  
  
  1) checkout
&lt;/h4&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As its name suggests, this step retrieves the source code within the repo and stores it in the working directory.&lt;/p&gt;
&lt;h4&gt;
  
  
  2) Install Sphinx
&lt;/h4&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Sphinx&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;apt install -y python-pip &amp;amp;&amp;amp; \&lt;/span&gt;
            &lt;span class="s"&gt;python3 -m pip install -r requirements.txt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;google/cloud-sdk:slim&lt;/code&gt; container image we are using is based on debian, so we can install Sphinx and its dependencies by first installing &lt;code&gt;pip&lt;/code&gt; with &lt;code&gt;apt&lt;/code&gt; and then using &lt;code&gt;pip&lt;/code&gt; to install the &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/p&gt;
&lt;h4&gt;
  
  
  3) Make Docs
&lt;/h4&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Make Docs&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;cd docs &amp;amp;&amp;amp; make html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Sphinx quickstart creates a Makefile within the &lt;code&gt;/docs&lt;/code&gt; directory which can be used to generate the documentation site. This step changes directory into &lt;code&gt;/docs&lt;/code&gt; and executes this make target passing &lt;code&gt;html&lt;/code&gt; as the destination for Sphinx to store its output.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; &lt;code&gt;html&lt;/code&gt; here needs to match the directory used in the App Engine &lt;code&gt;app.yaml&lt;/code&gt; config (&lt;code&gt;make html&lt;/code&gt; will build the site into &lt;code&gt;docs/build/html&lt;/code&gt; where App Engine is configured to find them)&lt;/p&gt;
&lt;h4&gt;
  
  
  4) Deploy Docs
&lt;/h4&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy Docs&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;echo ${GCLOUD_SERVICE_KEY} &amp;gt; /tmp/sa_key.json&lt;/span&gt;
            &lt;span class="s"&gt;gcloud auth activate-service-account --key-file=/tmp/sa_key.json&lt;/span&gt;
            &lt;span class="s"&gt;rm /tmp/sa_key.json&lt;/span&gt;
            &lt;span class="s"&gt;gcloud --quiet config set project ${GOOGLE_PROJECT_ID}&lt;/span&gt;
            &lt;span class="s"&gt;gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}&lt;/span&gt;
            &lt;span class="s"&gt;gcloud --quiet app deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The final step in the job is to deploy the site. In order to do this we first need to create a service account (&lt;a href="https://console.cloud.google.com/iam-admin/serviceaccounts"&gt;https://console.cloud.google.com/iam-admin/serviceaccounts&lt;/a&gt;) and grant it the necessary permissions. Although it seems as though &lt;code&gt;App Engine Deployer&lt;/code&gt; would be sufficient, it turns out there are actually a few additional permissions involved with deploying the site. The screenshot below shows the 5 roles I needed to add to achieve a successful deploy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--01m0cLWD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/sa-roles.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--01m0cLWD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/sa-roles.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, I created a key for the service account (&lt;code&gt;.json&lt;/code&gt; format) and stored its content as an environment variable in CircleCI. I also stored the Zone and Project ID as environment variables to ensure the deployment was targeting the correct app engine environment. &lt;/p&gt;

&lt;p&gt;There was one final "gotcha" having to do with the service account key. My first attempt tried to pass it into the &lt;code&gt;gcloud auth&lt;/code&gt; command by piping it in as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GCLOUD_SERVICE_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; | gcloud auth activate-service-account &lt;span class="nt"&gt;--key-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This caused &lt;code&gt;gcloud&lt;/code&gt; to assume the key was a &lt;code&gt;.p12&lt;/code&gt; type and fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ERROR: &lt;span class="o"&gt;(&lt;/span&gt;gcloud.auth.activate-service-account&lt;span class="o"&gt;)&lt;/span&gt; Missing required argument &lt;span class="o"&gt;[&lt;/span&gt;ACCOUNT]: An account is required when using .p12 keys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As shown in the full example at the beginning of the step, I was able to solve this by redirecting the echo output to a temporary file containing the json key and pass the filepath to &lt;code&gt;gcloud auth&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9BFdz3sb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/green-build.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9BFdz3sb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/green-build.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! We now have a password protected website, with CI/CD, that falls within the free usage tiers of GCP + CircleCI. If this site had heavy traffic or data egress it could exceed the free tier, but for this internal documentation use case it that is unlikely. &lt;/p&gt;




&lt;h3&gt;
  
  
  Next steps
&lt;/h3&gt;

&lt;p&gt;There are a couple of additional steps that I will leave as an exercise to the reader.&lt;/p&gt;

&lt;h4&gt;
  
  
  Custom Domain
&lt;/h4&gt;

&lt;p&gt;By default, App Engine assigns a domain such as &lt;a href="https://devops-directive-project.uc.r.appspot.com/"&gt;https://devops-directive-project.uc.r.appspot.com/&lt;/a&gt; to the website. It is possible to map a custom domain to the site by following this &lt;a href="https://cloud.google.com/appengine/docs/standard/python/mapping-custom-domains"&gt;guide from Google&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Custom OAuth screen
&lt;/h4&gt;

&lt;p&gt;The default Oauth consent screen is fairly boring, but can be customized with logo image, application homepage link, etc... from &lt;a href="https://console.cloud.google.com/apis/credentials/consent"&gt;Google Cloud Console&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y7Ca-eWP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/oauth-screen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y7Ca-eWP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://devopsdirective.com/static/images/oauth-screen.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>gcp</category>
    </item>
    <item>
      <title>[VIDEO] In-Depth Caddy Webserver Set-Up Walkthrough (localhost → Google Cloud)</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Thu, 14 May 2020 03:58:10 +0000</pubDate>
      <link>https://forem.com/sidpalas/in-depth-webserver-set-up-walkthrough-11o0</link>
      <guid>https://forem.com/sidpalas/in-depth-webserver-set-up-walkthrough-11o0</guid>
      <description>&lt;p&gt;This is a &lt;strong&gt;long&lt;/strong&gt; video, but in 60 minutes it covers a full progression from running a webserver locally all the way to deploying it on Google Cloud Platform!&lt;/p&gt;

&lt;p&gt;You will learn about webservers, cloud-based virtual machines, Docker, and more!&lt;/p&gt;

&lt;p&gt;I have provided timestamps and some descriptions if you want to find specific portions of the video.&lt;/p&gt;

&lt;p&gt;If you find the video useful, a like/subscription is always appreciated! &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xo_4n2Reh58"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Timestamps:&lt;br&gt;
1:30 - Workshop start&lt;br&gt;
2:06 - Load file in Chrome&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually loading a &lt;code&gt;.html&lt;/code&gt; file from a web browser will display it, but doesn't allow for anything beyond a single page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3:23 - Run Python development web server (locally)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single line of python can be used to run a local webserver:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;cgi&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;4:47 - Access site via internal IP address via another device on the same network&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By looking up the local IP address of the computer running the local server (using &lt;code&gt;ifconfig | grep 192&lt;/code&gt;) we can connect from another device on the same network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;6:44 - Install Caddy (locally)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://caddyserver.com/v1/"&gt;Caddy&lt;/a&gt; is a webserver that handles https setup automatically!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;7:03 - Run Caddy webserver (locally)&lt;br&gt;
8:23 - Configure Caddy using Caddyfile&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;Caddyfile&lt;/code&gt; is a how we define the configuration for Caddy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;12:05 - Set up GCP Compute Engine Virtual Machine with Debian Linux&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This effectively gives us a server running in a Google datacenter! &lt;/li&gt;
&lt;li&gt;Running a single &lt;code&gt;f1-micro&lt;/code&gt; VM falls within the google free tier so it shouldn't cost anything!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;13:23 - Connect to VM via SSH&lt;br&gt;
14:31 - Install Caddy (on VM)&lt;br&gt;
16:04 - Run Caddy webserver (on VM)&lt;br&gt;
17:11 - Configure firewall rule for port 80 &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By default, GCP does not allow access to the virtual machine, so in order to connect, we have to configure a firewall rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;18:46 - Explaining why we want to use a container&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By using Docker, we can ensure that the site will run the same on the Debian virtual machine as on your local system (whichever OS it happens to be running)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;22:00 - Install Docker (locally)&lt;br&gt;
23:18 - Run Python Docker image&lt;br&gt;
24:23 - Docker exec into the container&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Docker exec ... bash&lt;/code&gt; provides a shell session within the container. This is similar to SSH'ing into a remote machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;26:00 - Run Python development web server (locally, inside container)&lt;br&gt;
27:00 - Set up port forwarding to Python container&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By default, the ports of the container are isolated from those of the host system, so they must be explicitly forwarded in order to access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;28:26 - Run Python development web server &lt;a href="https://dev.tolocally,%20inside%20container"&gt;Take 2&lt;/a&gt;&lt;br&gt;
29:27 - Find Caddy container image on DockerHub&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DockerHub is like GitHub, but for container images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;30:35 - Run Caddy webserver (locally, inside container)&lt;br&gt;
33:00 - Define Dockerfile&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can make modifications to and build on top of the public image we are using!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;34:39 - Build custom Caddy container image&lt;br&gt;
35:34 - Run custom Caddy webserver (locally, inside container)&lt;br&gt;
36:05 - Modify and rebuild custom Caddy container&lt;br&gt;
37:27 - Configure Docker to mount host filesystem&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because the website files are stored outside of the container, configuring Docker to mount the proper directory allows Caddy to serve those files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;39:58 - Run Caddy web server with bind mounts (locally, inside container)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At this point we have everything working how we need it to and are ready to deploy it to the GCP virtual machine!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;42:25 - Set up GCP Compute Engine Virtual Machine with Container Optimized OS&lt;br&gt;
45:36 - Copy files to VM using gcloud scp &lt;br&gt;
47:54 - Run Caddy web server with bind mounts (on VM, inside container)&lt;br&gt;
50:22 - Configure domain DNS settings&lt;br&gt;
52:22 - Configure Caddyfile for HTTPS &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;These days HTTPS should be used for all sites. Google even &lt;a href="https://security.googleblog.com/2014/08/https-as-ranking-signal_6.html"&gt;reduces its search rank of sites that do not!&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;54:29 - Run Caddy web server with HTTPS (on VM, inside container)&lt;br&gt;
55:36 - Accept Let's Encrypt terms of service&lt;br&gt;
56:37 - Use environment variable to avoid TOS dialog&lt;br&gt;
57:23 - Explaining why we also need to add a bind mount to save HTTPS certificate outside the container&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is necessary because we want to persist the HTTPS certificate even if the container gets restarted. By mounting the host filesystem we can store that file outside the boundaries of the container.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>cloud</category>
      <category>tutorial</category>
      <category>docker</category>
    </item>
    <item>
      <title>Load Testing Caddy Web Server on a GCP F1-Micro Instance Using K6 (k6.io)</title>
      <dc:creator>sidpalas</dc:creator>
      <pubDate>Thu, 14 May 2020 01:50:48 +0000</pubDate>
      <link>https://forem.com/sidpalas/load-testing-caddy-web-server-on-a-gcp-f1-micro-instance-using-k6-k6-io-301d</link>
      <guid>https://forem.com/sidpalas/load-testing-caddy-web-server-on-a-gcp-f1-micro-instance-using-k6-k6-io-301d</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I used the &lt;a href="https://k6.io/" rel="noopener noreferrer"&gt;K6&lt;/a&gt; load testing framework to benchmark the Compute Engine &lt;a href="https://cloud.google.com/compute/docs/machine-types#n1_shared-core_machine_types" rel="noopener noreferrer"&gt;f1-micro&lt;/a&gt; and &lt;a href="https://caddyserver.com/v1/" rel="noopener noreferrer"&gt;Caddy web server&lt;/a&gt; hosting &lt;a href="https://devopsdirective.com" rel="noopener noreferrer"&gt;devopsdirective.com&lt;/a&gt;. With CloudFlare caching turned off, the server was able to serve an onslaught 800 virtual users continuously reloading the page (while maintaining a median request duration of &lt;code&gt;&amp;lt;400ms&lt;/code&gt;), but started dropping requests when increasing the load further.&lt;/p&gt;

&lt;p&gt;Originally published @ &lt;a href="https://devopsdirective.com/posts/2020/03/load-testing-f1-micro/" rel="noopener noreferrer"&gt;DevOps Directive&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Table of Contents:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Background&lt;/li&gt;
&lt;li&gt;
Testing

&lt;ul&gt;
&lt;li&gt;Site Setup&lt;/li&gt;
&lt;li&gt;K6 Performance Testing Framework&lt;/li&gt;
&lt;li&gt;Replicating Current Peak&lt;/li&gt;
&lt;li&gt;Key Takeaways&lt;/li&gt;
&lt;li&gt;Ramping It Up!&lt;/li&gt;
&lt;li&gt;Virtual Users and Server Load&lt;/li&gt;
&lt;li&gt;Snags Along the Way&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;(Aside) Total Costs&lt;/li&gt;

&lt;li&gt;Conclusions&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://devopsdirective" rel="noopener noreferrer"&gt;DevOps Directive&lt;/a&gt; is a static website generated with &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; and hosted using &lt;a href="https://caddyserver.com/v1/" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt; running on an &lt;a href="https://cloud.google.com/compute/docs/machine-types#n1_shared-core_machine_types" rel="noopener noreferrer"&gt;f1-micro&lt;/a&gt; GCP Compute Engine instance with &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt; in front of it (see &lt;a href="https://devopsdirective/posts/2020/02/hugo-and-caddy-on-gcp/index.md" rel="noopener noreferrer"&gt;The Making of This Site&lt;/a&gt; post for details). On a normal day, the site used to get between 1 (&lt;em&gt;thanks Dad!&lt;/em&gt;) and 20 visitors, but recently, two articles made it to the front page of Hacker News &lt;a href="https://news.ycombinator.com/item?id=22512149" rel="noopener noreferrer"&gt;Link-1&lt;/a&gt; and &lt;a href="https://news.ycombinator.com/item?id=22661029" rel="noopener noreferrer"&gt;Link-2&lt;/a&gt; bringing outsized swells in traffic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fgoogle-analytics-2-peaks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fgoogle-analytics-2-peaks.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a single hour on March 7th, a total of 1307 people visited this site. Thankfully, even with all of that traffic, CPU usage of the virtual machine never even reached 10% (and the short spikes correspond to redeploying the site with copy edits).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fmarch-7-traffic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fmarch-7-traffic.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, the setup has proven itself capable of embracing a Hacker News hug without dying, but I wanted to get a sense of what kind of load it can actually handle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Site Setup
&lt;/h3&gt;

&lt;p&gt;In order to avoid causing any impact to my actual site, I spun up an identical replica on a separate virtual machine using &lt;a href="https://github.com/sidpalas/hugo-gcp-deploy" rel="noopener noreferrer"&gt;this script&lt;/a&gt; and configured the &lt;a href="https://test.devopsdirective.com/" rel="noopener noreferrer"&gt;https://test.devopsdirective.com/&lt;/a&gt; subdomain (which will likely be inactive at the time you are reading this) to resolve to it.&lt;/p&gt;

&lt;p&gt;Here is a summary of the configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compute Engine f1-micro Instance (0.2 vCPU burstable to 1 vCPU for short periods, 0.6GB Memory) running Container Optimized OS (COS)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hub.docker.com/r/abiosoft/caddy/" rel="noopener noreferrer"&gt;Caddy (1.0.3) container image&lt;/a&gt; with the site content files built directly into the container&lt;/li&gt;
&lt;li&gt;Cloudflare configured to proxy traffic and set to the "standard" caching level (I performed tests with caching turned on and caching turned off)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;NOTE:&lt;/em&gt; I didn't tune/configure the COS image running on the VM, nor specify resource requests in the &lt;code&gt;docker run&lt;/code&gt; command. &lt;/p&gt;

&lt;h3&gt;
  
  
  K6 Performance Testing Framework
&lt;/h3&gt;

&lt;p&gt;To perform the load test I used &lt;a href="https://k6.io/" rel="noopener noreferrer"&gt;k6.io&lt;/a&gt;, an open source performance testing framework designed for building automated tests with a straightforward javascript config file. It uses the concept of "virtual users" (VUs) which in &lt;a href="https://k6.io/docs/getting-started/running-k6#adding-more-vus" rel="noopener noreferrer"&gt;their words&lt;/a&gt; are "glorified, parallel &lt;code&gt;while(true)&lt;/code&gt; loops" to load test a site. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/brasMBAezJY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicating Current Peak
&lt;/h3&gt;

&lt;p&gt;First, I created a script to approximate the load that the two HN posts brought. The peak hour had 1443 page views, or 0.4 pageviews/second. To account for the load not being constant across the entire hour, I rounded this up to 1 pageview/second.&lt;/p&gt;

&lt;p&gt;K6 is able use a &lt;a href="https://en.wikipedia.org/wiki/HAR_(file_format)" rel="noopener noreferrer"&gt;HAR file&lt;/a&gt; to create a representative set of HTTP requests. I used 1 virtual user and adjusted the pause between iterations to achieve just over 1 pageload/second (with a "pageload" corresponding to the batch of HTTP requests). I excluded external requests for things like the Google Analytics script. The full K6 configuration script can be found as a &lt;a href="https://gist.github.com/sidpalas/7f284eb88a832ba21190b1b0cd5f5ba9" rel="noopener noreferrer"&gt;GitHub gist&lt;/a&gt; and the resulting output can be seen below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;check_failure_rate.........: 0.00%   ✓ 0   ✗ 138
checks.....................: 100.00% ✓ 414 ✗ 0  
data_received..............: 66 MB   549 kB/s
data_sent..................: 219 kB  1.8 kB/s
group_duration.............: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;873.64ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;850.89ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;864.69ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.13s    p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;891.35ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;908.24ms
http_req_blocked...........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;81.77µs  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;156ns    &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;446ns    &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;133.52ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;861ns    p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;1.42µs  
http_req_connecting........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7.06µs   &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s       &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s       &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11.69ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;0s       p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;0s      
http_req_duration..........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;26.89ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.6ms   &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;23.11ms  &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;213.29ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;38.19ms  p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;47.38ms 
http_req_receiving.........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;726.31µs &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;62.01µs  &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;242.75µs &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;29.96ms  p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;1.84ms   p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;2.41ms  
http_req_sending...........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;52.23µs  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11.27µs  &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;38.27µs  &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.07ms   p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;106.59µs p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;115.18µs
http_req_tls_handshaking...: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;72.85µs  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s       &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0s       &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120.64ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;0s       p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;0s      
http_req_waiting...........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;26.11ms  &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12.37ms  &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;22.51ms  &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;212.53ms p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;37.05ms  p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;45.32ms 
http_reqs..................: 1656    13.799982/s
iteration_duration.........: &lt;span class="nv"&gt;avg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;873.71ms &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;850.96ms &lt;span class="nv"&gt;med&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;864.75ms &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.13s    p&lt;span class="o"&gt;(&lt;/span&gt;90&lt;span class="o"&gt;)=&lt;/span&gt;891.46ms p&lt;span class="o"&gt;(&lt;/span&gt;95&lt;span class="o"&gt;)=&lt;/span&gt;908.31ms
iterations.................: 137     1.141665/s
vus........................: 1       &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
vus_max....................: 1       &lt;span class="nv"&gt;min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Key Takeaways
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;All requests returned successfully (&lt;code&gt;Status: 200 OK&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The request duration ranged from 13ms to 213ms&lt;/li&gt;
&lt;li&gt;The 95th percentile duration was 47ms&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ramping It Up!
&lt;/h3&gt;

&lt;p&gt;With that test as a baseline, I proceeded to run a series of tests, each 60 seconds long, starting with 6 virtual users and increasing the number of VUs with each test. I also reduced the delay between virtual user iterations to 100ms. The most important metric is &lt;code&gt;http_req_duration&lt;/code&gt;, which represents is the total request time (&lt;code&gt;http_req_sending + http_req_waiting + http_req_receiving&lt;/code&gt;), which I have plotted below for the full set of tests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fhttp_req_duration.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fhttp_req_duration.png"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Unsuprisingly... a CDN with caching makes a big difference&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Up until around 50 VUs, the response time remains flat, with an uncached median of 68ms and a cached median of 31ms.&lt;/p&gt;

&lt;p&gt;After 50 VUs, the response times begin to climb in a linear fashion. At 800 VUs the uncached median was 349ms and the cached median was 67ms. As would be expected at these higher loads, most (90+%) of the &lt;code&gt;http_req_duration&lt;/code&gt; is spent in the &lt;code&gt;http_req_waiting&lt;/code&gt; stage.&lt;/p&gt;

&lt;p&gt;The uncached configuration finally gave out during the 1600 virtual user test, with only 414 successful responses, indicating that ~74% of the virtual users never received a response.&lt;/p&gt;

&lt;h4&gt;
  
  
  Virtual Users and Server Load
&lt;/h4&gt;

&lt;p&gt;It is important to note that while the virtual users run in parallel with each other, they run in serial with themselves. Each individual VU waits until its current pageload is complete before making a new set of requests. As the server slows down under load, this causes the total rate of requests to drop in the more demanding tests. The impact is clearly seen in the total amount of data received during the tests plotted below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fdata_received.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fdata_received.png"&gt;&lt;/a&gt; &lt;br&gt;
&lt;em&gt;Data received (and pageloads/s) peaks before the more demanding tests&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These were the two most informative plots, but all of the data and code to generate plots can be found in a notebook in this &lt;a href="https://github.com/sidpalas/f1-micro-caddy-benchmark" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;. You can load an interactive copy of the notebook using the following binder link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fbinder.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fbinder.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mybinder.org/v2/gh/sidpalas/f1-micro-caddy-benchmark/master?filepath=plotting-load-test-results.ipynb" rel="noopener noreferrer"&gt;Link to interactive notebook&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Snags Along the Way
&lt;/h3&gt;

&lt;p&gt;I did run into some technical limitations when configuring and executing these tests. Here are the main issues and how I overcame them:&lt;/p&gt;

&lt;p&gt;1) &lt;strong&gt;Bandwidth Limitations:&lt;/strong&gt; My home internet was not sufficient to support the load test. Moving to a GCP virtual machine with sufficient bandwidth (Measured @ 900+ Mbps) as the test client running K6 solved this. For the later tests in the cached configuration, this actually still became a limiting factor.&lt;/p&gt;

&lt;p&gt;2) &lt;strong&gt;Memory Limitations:&lt;/strong&gt; After moving from my laptop to an n1-standard-1 instance as the testing client, the more demanding tests caused K6 to run out of memory (&lt;code&gt;fatal error: runtime: out of memory&lt;/code&gt;). Moving to an n1-standard-8 (30GB memory) solved this.&lt;/p&gt;

&lt;p&gt;3) &lt;strong&gt;Unix Resource Limits:&lt;/strong&gt; Because each request group makes multiple HTTP requests, the final test with 1600 target virtual users surpasses the &lt;a href="https://k6.io/docs/misc/fine-tuning-os#user-resource-limits" rel="noopener noreferrer"&gt;default maximum number of open files&lt;/a&gt; allowed by the OS for a single process to manage at once. Using multiple test client VMs in parallel solved this, but increasing the open file limit with &lt;code&gt;ulimit -n &amp;lt;NEW_LARGER_LIMIT&amp;gt;&lt;/code&gt; is the approach I ended up using.&lt;/p&gt;

&lt;h2&gt;
  
  
  (Aside) Total Costs
&lt;/h2&gt;

&lt;p&gt;The total cost to run this experiment was $2.82:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$1.60 for 40.1 GB of network egress&lt;/li&gt;
&lt;li&gt;$0.50 for running the f1-micro server for a ~3 days&lt;/li&gt;
&lt;li&gt;$0.72 for running the n1-standard-8 testing client for a ~2 hrs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;I am continuously amazed at the level of load that even such a tiny virtual machine can handle when serving static content!&lt;/p&gt;

&lt;p&gt;Utilizing a service like Cloudflare to help cache and serve content reduces the load on the server significantly. It cut the response times in half under light load and prevented the server from being overwhelmed under heavy load.&lt;/p&gt;

&lt;p&gt;I would have liked to record realtime resource (CPU + Memory usage) on the server VM but the GCP cloud monitoring agent isn't compatible with Container Optimized OS, so I settled for the rough 1 min averaged view in the GCP console:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fpeak-cpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevopsdirective.com%2Fposts%2F2020%2F03%2Fload-testing-f1-micro%2Fimages%2Fpeak-cpu.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Now we're cooking with gas! (bursting above the 0.2 vCPU limit for a short period)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This test gives me confidence that my current server configuration should be able to handle quite a bit of growth before needing any major overhaul. &lt;/p&gt;

&lt;p&gt;In the future, I hope to do similar benchmarking across other hosting options. If someone has a contact at &lt;a href="https://twitter.com/github" rel="noopener noreferrer"&gt;@github&lt;/a&gt; or &lt;a href="https://twitter.com/Netlify" rel="noopener noreferrer"&gt;@netlify&lt;/a&gt; that could grant me permission to run a test against a Github Pages or Netlify Starter site let me know! Or maybe at &lt;a href="https://twitter.com/bluehost" rel="noopener noreferrer"&gt;@bluehost&lt;/a&gt; so I can benchmark some Wordpress installs...&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>testing</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
