<?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>DEV Community: StackPath</title>
    <description>The latest articles on DEV Community by StackPath (@stackpath).</description>
    <link>https://dev.to/stackpath</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%2Forganization%2Fprofile_image%2F767%2F18a9af43-ebc7-4d10-8ed5-535d0dc3e6f2.jpg</url>
      <title>DEV Community: StackPath</title>
      <link>https://dev.to/stackpath</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stackpath"/>
    <language>en</language>
    <item>
      <title>Pwnd Password Checking on the Edge</title>
      <dc:creator>Karl L. Hughes</dc:creator>
      <pubDate>Mon, 03 Aug 2020 18:06:33 +0000</pubDate>
      <link>https://dev.to/stackpath/pwnd-password-checking-on-the-edge-1nbk</link>
      <guid>https://dev.to/stackpath/pwnd-password-checking-on-the-edge-1nbk</guid>
      <description>&lt;p&gt;One of the most effective ways to secure your users’ data is to ensure that they select a strong password. Unfortunately, putting the burden on your users rarely works:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The bad news is that most users don't pick strong passwords. This has been proven time and time again...Even worse, most users re-use these bad passwords across multiple websites.” - &lt;a href="https://blog.codinghorror.com/the-dirty-truth-about-web-passwords/" rel="noopener noreferrer"&gt;Jeff Atwood, Founder of StackOverflow&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Many application developers implement password rules that require a particular password length, a combination of characters, or various forms of capitalization to address this issue. Some of these commonly-used password rules are &lt;a href="https://blog.codinghorror.com/password-rules-are-bullshit/" rel="noopener noreferrer"&gt;not that helpful&lt;/a&gt;, but long, hard to guess passwords are generally good for security. Checking password length is easy enough, but how can you ensure that the user’s password is hard to guess?&lt;/p&gt;

&lt;p&gt;One method is to check if they’re using a password that’s been compromised before. &lt;/p&gt;

&lt;p&gt;If you’ve worked in web development for long, you may have heard of the NIST security guidelines. In their Digital Identity Guidelines, they specify:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“When processing requests to establish and change memorized secrets, verifiers SHALL compare the prospective secrets against a list that contains values known to be commonly-used, expected, or compromised. For example, the list MAY include, but is not limited to: Passwords obtained from previous breach corpuses.” - &lt;a href="https://pages.nist.gov/800-63-3/sp800-63b.html" rel="noopener noreferrer"&gt;NIST Digital Identity Guidelines&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When users set their passwords, NIST recommends that you ensure they don’t use a password previously exposed in a data breach. While you might hear about some large data breaches on the news, there’s no way you can keep up with all the data breaches happening around the world. If you tried, you’d never have time to get any coding done!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have I Been Pwned?&lt;/strong&gt;&lt;br&gt;
A few years ago, Troy Hunt - a well-known security researcher - set up a website to address this very issue. Users can enter their password on &lt;a href="https://haveibeenpwned.com/Passwords" rel="noopener noreferrer"&gt;HaveIBeenPwned.com&lt;/a&gt; to see if it’s been part of a data breach and, therefore, not safe to use.&lt;/p&gt;

&lt;p&gt;Unfortunately, this isn’t something most people do, but Have I Been Pwned also &lt;a href="https://haveibeenpwned.com/API/v3" rel="noopener noreferrer"&gt;has an API&lt;/a&gt;. Using the REST API, you can check that a user signing up for your application does not use a compromised password &lt;em&gt;before&lt;/em&gt; you let them register. When used in addition to requiring a long, sufficiently random password, this can significantly increase your platform’s data security and minimize brute force password attacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checking for Pwned Passwords on the Edge&lt;/strong&gt;&lt;br&gt;
If you’re using an authentication service, you will need to figure out the best way to implement password strength checking into your signup flow. While you could modify your authentication flow, you can also host your pwned password checking service on the edge with StackPath.&lt;/p&gt;

&lt;p&gt;Enforcing strict password rules will annoy some users, but you can minimize this annoyance by making responses as fast as possible. In this tutorial, you’ll see how to use &lt;a href="https://www.stackpath.com/" rel="noopener noreferrer"&gt;StackPath’s edge hosting&lt;/a&gt; and the &lt;a href="https://haveibeenpwned.com/API/v3" rel="noopener noreferrer"&gt;Pwned Password API&lt;/a&gt; to ensure that users are signing up with uncompromised passwords, and you'll see how edge hosting can offer a 14x performance improvement over traditional hosting when caching responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Node Application
&lt;/h2&gt;

&lt;p&gt;To demonstrate the use of the Pwned Password API on StackPath, I’ve created an &lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express application&lt;/a&gt; in a &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; image. This project's code is &lt;a href="https://github.com/karllhughes/stackpath-pwned" rel="noopener noreferrer"&gt;available on Github&lt;/a&gt; if you’d like to follow along. It uses the same Dockerfile and Express server &lt;a href="https://support.stackpath.com/hc/en-us/articles/360022805791-Edge-Computing-Building-a-Containerized-NodeJS-Web-App" rel="noopener noreferrer"&gt;outlined here&lt;/a&gt;, so you may want to start by reading that tutorial if you’re not familiar with Node or Docker.&lt;/p&gt;

&lt;p&gt;Signup requests from a web browser will be sent to the Node application. When the client posts a &lt;code&gt;password&lt;/code&gt;, the Node app will call the Pwned Password API. The whole flow can be visualized here:&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%2Fozdh07nc7dsx1cbla3s3.jpg" 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%2Fozdh07nc7dsx1cbla3s3.jpg" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Pwned Password API takes the first five characters of a &lt;a href="https://en.wikipedia.org/wiki/SHA-1" rel="noopener noreferrer"&gt;SHA1 hash&lt;/a&gt; of the password and returns a list of hashed password suffixes to the Node application. This use of a partial hash minimizes any risk in posting secure data to a third-party service.&lt;/p&gt;

&lt;p&gt;If you want to see the raw results that the Pwned Password API returns, visit &lt;code&gt;https://api.pwnedpasswords.com/range/21BD1&lt;/code&gt; in your browser. You will see a list of hashes followed by the number of times they’ve been seen in plain text on the internet:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0018A45C4D1DEF81644B54AB7F969B88D65:1
00D4F6E8FA6EECAD2A3AA415EEC418D38EC:2
011053FD0102E94D6AE2F8B83D76FAF94F6:1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In order to create the partial hash, prefixes, and suffixes in Node, you can use the &lt;a href="https://www.npmjs.com/package/sha1" rel="noopener noreferrer"&gt;sha1 library&lt;/a&gt; and built-in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring" rel="noopener noreferrer"&gt;substring&lt;/a&gt; function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Shah-1 hash the password and break it into a prefix and suffix
const hashedPassword = sha1(password).toUpperCase();
const prefix = hashedPassword.substring(0, 5);
const suffix = hashedPassword.substring(5);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, call the Pwned Password API using the &lt;code&gt;prefix&lt;/code&gt; and check for the presence of &lt;code&gt;suffix&lt;/code&gt; in the response. In this case, I’m using the &lt;a href="https://www.npmjs.com/package/node-fetch" rel="noopener noreferrer"&gt;node-fetch&lt;/a&gt; library, but you can use whichever HTTP request package you prefer.&lt;/p&gt;

&lt;p&gt;Because performance is one of the primary reasons to use edge hosting for password validation, you can cache the response from the Pwned Password API. While caching hashed passwords would normally have some security implications, these are pubicly available compromised password hashes, so it's less of a concern.&lt;/p&gt;

&lt;p&gt;Here's the code that handles caching and checking against the API's results:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Check against cache
const cachedResults = hashedPasswordCache.get(prefix);
if (cachedResults) {
  // Check if the cached result includes the suffix of the hash
  return generateResponse(res, cachedResults, suffix);
} else {
  // Send the first 5 chars to pwned password API
  fetch('https://api.pwnedpasswords.com/range/' + prefix)
    .then(tmp =&amp;gt; tmp.text())
    .then(body =&amp;gt; {
      // Cache the response for future use
      hashedPasswordCache.set(prefix, body);
      // Check if the response includes the suffix of the hash
      return generateResponse(res, body, suffix);
    }).catch(err =&amp;gt; {
    // Handle any API errors
    console.error(err);
    res.status(400).json({message: 'Something went wrong.'});
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This demo lets the user know if their password has been pwned or not, but in production, you probably want to pass valid usernames and passwords along to your authentication service to create the user’s account and log them in.&lt;/p&gt;

&lt;p&gt;Next, you need to build your application as a Docker image. Open your terminal and navigate to the repository. Build the Dockerfile using your Docker Hub username:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t &amp;lt;YOUR_USERNAME&amp;gt;/stackpath-pwned .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And push the image to Docker Hub:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker push &amp;lt;YOUR_USERNAME&amp;gt;/stackpath-pwned:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now your image is publicly available. In the next step, you'll deploy the application to the edge using StackPath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the Application
&lt;/h2&gt;

&lt;p&gt;Once you’ve created the Node application that calls the Pwned Password API and responds appropriately, you can deploy it to &lt;a href="https://www.stackpath.com/" rel="noopener noreferrer"&gt;StackPath&lt;/a&gt;. StackPath’s edge hosting network runs your Docker containers on servers worldwide, so you get the fastest response times possible.&lt;/p&gt;

&lt;p&gt;First, create a new Workload and enter the Docker image you just pushed to Docker Hub above:&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%2F6qbh4m97n5u0tqyigfrq.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%2F6qbh4m97n5u0tqyigfrq.png" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You don’t need any environment variables for this project, but you should check the box to “Add Anycast IP Address” and make sure port 80 is open.&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%2Fzz10twsd66uti7nlmuoy.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%2Fzz10twsd66uti7nlmuoy.png" width="800" height="906"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, choose the PoPs (points of presence) where you want your application to be hosted. I wanted to spread my nodes across the world for benchmarking, but you should select locations according to your use case. I used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chicago&lt;/li&gt;
&lt;li&gt;San Jose&lt;/li&gt;
&lt;li&gt;New York&lt;/li&gt;
&lt;li&gt;London&lt;/li&gt;
&lt;li&gt;Singapore&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within a couple of minutes, your StackPath application will be running, and you'll be able to access it from the Anycast IP address shown on your Workload's Overview.&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%2F3edp3l9aq9fg7ummtn3l.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%2F3edp3l9aq9fg7ummtn3l.png" width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarking Against Traditional Hosting
&lt;/h2&gt;

&lt;p&gt;Generally, the closer your code is to your users, the faster it will run. To demonstrate the performance benefits that StackPath offers over traditional hosting in the case of pwned password checking, I set up a DigitalOcean droplet in the San Francisco region with similar specs to the StackPath Workload (2GB, 1vCPU).&lt;/p&gt;

&lt;p&gt;I used a &lt;a href="https://github.com/karllhughes/response-timer" rel="noopener noreferrer"&gt;response timer Docker image&lt;/a&gt; to test the application's speed in four locations around the world. The initial uncached response times are pretty similar because in both hosting environments, a request must be made to the Pwned Password API. But, when the result is cached and an API call isn't necessary, StackPath provides significantly faster responses.&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%2F47j3tv8tjzk28qy0owj4.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%2F47j3tv8tjzk28qy0owj4.png" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On average, responses from StackPath were almost 14x faster than those from a traditional web server.&lt;/p&gt;

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

&lt;p&gt;Passwords are &lt;a href="https://www.troyhunt.com/passwords-evolved-authentication-guidance-for-the-modern-era/" rel="noopener noreferrer"&gt;just one piece&lt;/a&gt; in the quest for application security, but they’re a cornerstone of implementing strong security practices. By enforcing strong passwords and requiring users to select passwords that haven’t been compromised, you can lower their accounts' risk. To combat any delay in checking passwords, you can utilize edge hosting with StackPath, as demonstrated above.&lt;/p&gt;

</description>
      <category>security</category>
      <category>hosting</category>
      <category>stackpath</category>
      <category>haveibeenpwned</category>
    </item>
    <item>
      <title>API Consolidation and Load Balancing at the Edge with Traefik and StackPath</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Mon, 03 Aug 2020 15:58:30 +0000</pubDate>
      <link>https://dev.to/stackpath/api-consolidation-and-load-balancing-at-the-edge-with-traefik-and-stackpath-n4p</link>
      <guid>https://dev.to/stackpath/api-consolidation-and-load-balancing-at-the-edge-with-traefik-and-stackpath-n4p</guid>
      <description>&lt;p&gt;&lt;a href="https://containo.us/traefik/"&gt;Traefik&lt;/a&gt; is an open source edge router for the cloud. Like a hostess at a restaurant that meets incoming guests and shows them to their tables, traefik intercepts requests at the edge of the network and relays them to their services.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll learn how to install and configure highly available, low-latency API gateways with traefik on StackPath's &lt;a href="https://www.stackpath.com/edge-academy/edge-computing/"&gt;edge computing&lt;/a&gt; network.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Gateway Setup
&lt;/h2&gt;

&lt;p&gt;In the course of this tutorial, we'll deploy the following infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gateways&lt;/strong&gt;: one or more traefik gateways distributed over different &lt;a href="https://blog.stackpath.com/point-of-presence/"&gt;PoPs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config DB&lt;/strong&gt;: a single database container with the gateway's routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Services&lt;/strong&gt;: some API servers to test the routes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P2O_rp01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u0y6e4ew1eb3f9z1f3y7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P2O_rp01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u0y6e4ew1eb3f9z1f3y7.png" alt="Diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Prepare the API Servers
&lt;/h2&gt;

&lt;p&gt;To learn how traefik works, we’ll deploy some test APIs in StackPath. You can replace these services with other &lt;a href="https://www.stackpath.com/products/containers/"&gt;containers&lt;/a&gt; or &lt;a href="https://www.stackpath.com/products/virtual-machines/"&gt;VMs&lt;/a&gt;. Or, if you already have APIs running elsewhere, you can route traffic to them from StackPath.&lt;/p&gt;

&lt;p&gt;To get started, log in or create a &lt;a href="https://stackpath.com/"&gt;StackPath&lt;/a&gt; account. The first time you log in to StackPath, you’ll be asked to create a &lt;em&gt;Stack&lt;/em&gt;. Stacks are like projects, a group of deployed resources and services. In this tutorial, we’ll focus on deploying &lt;a href="https://www.stackpath.com/edge-academy/edge-computing/"&gt;edge computing&lt;/a&gt; services.&lt;/p&gt;

&lt;p&gt;After logging in, click &lt;strong&gt;Workloads&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create Workload&lt;/strong&gt; in the left navigation menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YIo59FFN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vi5j3ljkydezw5nfbe0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YIo59FFN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vi5j3ljkydezw5nfbe0h.png" alt="Create workload"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set the workload type to &lt;strong&gt;Container&lt;/strong&gt;. Then, type the name of the Docker image that implements your API service. As an example, we can use &lt;a href="https://hub.docker.com/r/containous/whoami"&gt;containous/whoami&lt;/a&gt;, a service that always responds with its IP address and hostname.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ni5Er88o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kedshsj1b9m0vg5esnhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ni5Er88o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kedshsj1b9m0vg5esnhg.png" alt="Whoami image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Settings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the Settings section, you can define environment variables and startup commands for the container. You can also expose ports to the internet.&lt;/p&gt;

&lt;p&gt;Unless you need direct access to your containers, avoid opening any ports in this section. We should route all public connections from the gateway.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Spec&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Select a machine in &lt;strong&gt;Spec&lt;/strong&gt; and set the name of the &lt;strong&gt;Deployment Target&lt;/strong&gt;. Deployment targets are used to configure auto-scaling.&lt;/p&gt;

&lt;p&gt;Choose one PoP and set the number of instances to two.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rXyb4tSD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8vn7mnagfe03ikajaoii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rXyb4tSD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8vn7mnagfe03ikajaoii.png" alt="Whoami target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create Workload&lt;/strong&gt; and wait a few seconds until your containers are running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Install Etcd
&lt;/h2&gt;

&lt;p&gt;Traefik has two types of settings: static and dynamic, with the former being activated when the gateway boots up and the latter taking effect without interruption. Dynamic settings are stored in distributed databases like Consul, ZooKeeper, or etcd.&lt;/p&gt;

&lt;p&gt;Here we’ll use &lt;a href="https://etcd.io/"&gt;etcd&lt;/a&gt;, an easy-to-use key-value database for the cloud.&lt;/p&gt;

&lt;p&gt;Create a second workload on StackPath. Like you did in Step 1, go to &lt;strong&gt;Workloads&lt;/strong&gt; &amp;gt; &lt;strong&gt;Create Workload&lt;/strong&gt;. Set the name of the workload to “my-api-config”. Then, select &lt;strong&gt;Container&lt;/strong&gt; in Workload Type, and type &lt;code&gt;quay.io/coreos/etcd:v3.4.0&lt;/code&gt; in Image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0MpohDfR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f18yo7wutk05euedjhtj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0MpohDfR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/f18yo7wutk05euedjhtj.png" alt="etcd image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Settings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You don’t have to open any ports here as all communication with etcd can take place over the private network.&lt;/p&gt;

&lt;p&gt;We’ll configure etcd with &lt;strong&gt;Environment Variables&lt;/strong&gt;. There are three settings we’re interested in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ETCD_DATA_DIR&lt;/strong&gt; - Tells etcd where the database is located. We’ll point it to a persistent mount to preserve the data across reboots and upgrades. Set it to &lt;code&gt;/etcd-data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ETCD_LISTEN_CLIENT_URLS&lt;/strong&gt; - Defines the ports and interfaces that etcd binds to. Set it to &lt;code&gt;http://0.0.0.0:2379&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ETCD_ADVERTISE_CLIENT_URLS&lt;/strong&gt; - This is the DNS name or IP address the clients use to connect with etcd. StackPath provides a free internal Discovery DNS service that simplifies this step enormously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovery DNS
&lt;/h3&gt;

&lt;p&gt;StackPath's &lt;a href="https://support.stackpath.com/hc/en-us/articles/360037534432-Edge-Compute-DNS-Discovery"&gt;Discovery DNS service&lt;/a&gt; helps you connect your containers and VMs using predictable names instead of IP addresses. DNS entries are automatically created when you update workloads. You can use hostnames, workload names, ports, and SRV records to define your network.&lt;/p&gt;

&lt;p&gt;For instance, we can find all healthy containers in a given workload following this formula:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;WORKLOAD_NAME.STACK_NAME.edgeengine.internal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, if the workload name for etcd is &lt;code&gt;my-api-config&lt;/code&gt; and the stack is called &lt;code&gt;my-default-stack&lt;/code&gt;, then its FQDN (Fully Qualified Domain Name) is &lt;code&gt;my-api-config.my-default-stack.edgeengine.internal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll only deploy a standalone etcd instance. Later, you might decide to upgrade it to a cluster. If you follow this pattern, the system naturally grows as you add new instances.&lt;/p&gt;

&lt;p&gt;Set this value to &lt;code&gt;http://my-api-config.YOUR_STACK_NAME.edgeengine.internal:2379&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NvaMfAHx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wikuyf90ukulvti68bf4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NvaMfAHx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wikuyf90ukulvti68bf4.png" alt="ETCD Environment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Spec&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Additional Volume&lt;/strong&gt; set the mount path to &lt;code&gt;/etcd-data&lt;/code&gt; and set the volume size.&lt;/p&gt;

&lt;p&gt;Set the &lt;strong&gt;Deployment target&lt;/strong&gt; and click &lt;strong&gt;Create Workload&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nUZl95-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/28gnmf48o2u74smwzruz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nUZl95-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/28gnmf48o2u74smwzruz.png" alt="etcd target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Overview&lt;/strong&gt; in the left menu and scroll down to the Manage Your Instances section and enable &lt;strong&gt;Remote Management&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--shR8FG2m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y0hr1281gjv9r77x828p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--shR8FG2m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y0hr1281gjv9r77x828p.png" alt="Enable Remote"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on your etcd instance and scroll down to the Instance Details section.&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Run &amp;amp; Open Terminal&lt;/strong&gt; button next to Remote Management. A terminal window opens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2spTzq8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sogoblbw2auyfuzsxzdp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2spTzq8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/sogoblbw2auyfuzsxzdp.png" alt="Run terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Define the username and password environment variables. We’ll need to repeat the export commands each time we connect to the etcd instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ETCDCTL_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;root
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ETCDCTL_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TYPE_YOUR_ETCD_PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new username and enable authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl user add &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ETCDCTL_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ETCDCTL_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
etcdctl user grant-role root &lt;span class="nv"&gt;$ETCDCTL_USER&lt;/span&gt;
etcdctl auth &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the following command to get etcd ready for the traefik configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;true

&lt;/span&gt;OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave the terminal window open. We’ll use it to configure Traefik next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - Deploy Traefik
&lt;/h2&gt;

&lt;p&gt;Traefik gateways can issue certificates, buffer and filter requests, and authenticate users right on the edge of your network where it makes the most sense. In this step, we’ll create a third workload with the traefik containers.&lt;/p&gt;

&lt;p&gt;Create a new workload in StackPath. Select &lt;strong&gt;Container&lt;/strong&gt; in Workload Type. On Image, type &lt;code&gt;traefik:2.2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jfLkO_D5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1md8bh6miw1mruqw8w8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jfLkO_D5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1md8bh6miw1mruqw8w8y.png" alt="Traefik Image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Settings&lt;/strong&gt; and enable the &lt;strong&gt;Add Anycast IP&lt;/strong&gt; option so users are always routed to the closest gateway. Then, on &lt;strong&gt;Public Ports&lt;/strong&gt;, add ports 80 (HTTP) and 443 (HTTPS). Traefik also offers an optional dashboard on port 8080. The dashboard shows all sorts of interesting information so you may want to open that port too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O9nvWVTz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/koz7u7k31d1awo2cb0jx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O9nvWVTz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/koz7u7k31d1awo2cb0jx.png" alt="Traefik Net"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to tell Traefik where to find the configuration database. Use the &lt;strong&gt;+&lt;/strong&gt; buttons in &lt;strong&gt;Commands&lt;/strong&gt; to add the following lines—one line per command. Fill in your etcd DNS and password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;traefik
&lt;span class="nt"&gt;--providers&lt;/span&gt;.etcd.endpoints&lt;span class="o"&gt;=&lt;/span&gt;my-api-config.YOUR_STACK_NAME.edgeengine.internal:2379
&lt;span class="nt"&gt;--providers&lt;/span&gt;.etcd.username&lt;span class="o"&gt;=&lt;/span&gt;root
&lt;span class="nt"&gt;--providers&lt;/span&gt;.etcd.password&lt;span class="o"&gt;=&lt;/span&gt;YOUR_ETCD_PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wish to enable the dashboard, also add these commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--api&lt;/span&gt;
&lt;span class="nt"&gt;--api&lt;/span&gt;.insecure&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UAJJcB8A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dgwlflyrvzm2raa4pn1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UAJJcB8A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dgwlflyrvzm2raa4pn1l.png" alt="Traefik Start"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Spec&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Deployment Target&lt;/strong&gt;, set the name of the deployment and choose as many PoPs and instances as you need. There is no limit to the number of instances and PoPs you can deploy. Prioritize locations that are close to you and your users to reduce latency.&lt;/p&gt;

&lt;p&gt;Depending on the load you expect, two or three nodes may be enough. Or perhaps you find that you need 20 or 30 nodes spread all over the world. The good news is that you can scale it up and down at the click of a button—in seconds, without interruptions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tw6HxkXL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jrvtqy75suva8z4x1ybs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tw6HxkXL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jrvtqy75suva8z4x1ybs.png" alt="Traefik Target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create Workload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Overview&lt;/strong&gt;, scroll down, and copy the gateway’s anycast IP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I1GTCQ9b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ogeqw36x4k7lu47hp32v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I1GTCQ9b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ogeqw36x4k7lu47hp32v.png" alt="Anycast IP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 - Configure Routes and Services
&lt;/h2&gt;

&lt;p&gt;In this section we’ll try out two common gateway scenarios: load balancer and API consolidation.&lt;/p&gt;

&lt;p&gt;Traefik uses &lt;strong&gt;routers&lt;/strong&gt; to match incoming requests with their destinations. Routers check for specific patterns in headers, hostnames, paths, or query strings, and forwards them to the corresponding &lt;strong&gt;service&lt;/strong&gt;. Replies are sent back to the client as if they had originated from the gateway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load Balancer Scenario&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A load balancer distributes incoming connections among two or more API servers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--10VwjFlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4fze165p1ailos7c24qu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--10VwjFlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4fze165p1ailos7c24qu.png" alt="Load Balancer Scenario"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Traefik uses a tree to represent the routers on the configuration database. To define the routes, we only have to create the relevant nodes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;traefik
└── http
    ├── middlewares
    ├── routers
    └── services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we need to find out the DNS names of the API services we plan to proxy. On StackPath, go to the test service workloads you created in &lt;strong&gt;Step 1&lt;/strong&gt; and take note of their hostnames.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9lgQLwgy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3s2l1ethq0800ok04lxb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9lgQLwgy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3s2l1ethq0800ok04lxb.png" alt="API Instances"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we benefit from StackPath’s DNS discovery service. We can target any particular instance as &lt;code&gt;HOSTNAME.STACK_NAME.edgeengine.internal&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, in this case, the FQDN for the services are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;my-api-services-my-api-server-group-sea-0.my-default-stack.edgeengine.internal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;my-api-services-my-api-server-group-sea-1.my-default-stack.edgeengine.internal&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you are proxying API services deployed outside of StackPath, the DNS resolver tactic won’t work. You’ll need to provide for DNS resolution or IP addresses yourself.&lt;/p&gt;

&lt;p&gt;Go back to the etcd terminal window and type the following commands to define a load balancer service called &lt;code&gt;whoami&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik/http/services/whoami/loadbalancer/servers/0/url &lt;span class="nt"&gt;--&lt;/span&gt; http://YOUR_API_SERVICE_1_HOSTNAME
etcdctl put traefik/http/services/whoami/loadbalancer/servers/1/url &lt;span class="nt"&gt;--&lt;/span&gt; http://YOUR_API_SERVICE_2_HOSTNAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we have to set the rules that send incoming traffic into the load balancer. This router matches requests to the &lt;code&gt;/whoami&lt;/code&gt; path (for example, &lt;code&gt;example.com/whoami&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik/http/routers/whoami/rule &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'Path(`/whoami`)'&lt;/span&gt;
etcdctl put traefik/http/routers/whoami/service &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;whoami&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once configured, you can check the new route on the dashboard (if you have enabled it).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gLiRb_HD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0ugn9e1unsp86omm9rn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gLiRb_HD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0ugn9e1unsp86omm9rn7.png" alt="Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To try the load balancer, you can use &lt;a href="https://curl.haxx.se/"&gt;curl&lt;/a&gt; or a browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; http://GATEWAY_ANYCAST_IP/whoami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then test how the load balancer is working by making two consecutive requests to the gateway’s anycast IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Hostname: my-api-services-my-api-server-group-sea-0
IP: 10.128.144.3
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the second request, you should get a different API service IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Hostname: my-api-services-my-api-server-group-sea-1
IP: 10.128.144.4
&lt;span class="o"&gt;[&lt;/span&gt;..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;API Consolidation Scenario&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;API consolidation is another everyday scenario for a gateway. Traefik allows developers to solidify all their API services under a single endpoint. In this example, we’ll learn how to merge two APIs services.&lt;/p&gt;

&lt;p&gt;Imagine we have two different APIs, foo and bar, which we want to present as a single entity. The easiest way to achieve this is by using different paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;example.com/foo&lt;/code&gt; is relayed to the foo service&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;example.com/bar&lt;/code&gt; is relayed to the bar service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4vkVtIHs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/snjqyw35gbweu7g1m0a3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4vkVtIHs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/snjqyw35gbweu7g1m0a3.png" alt="API Consolidation Scenario"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, create both services in the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik/http/services/foo/loadbalancer/servers/0/url &lt;span class="nt"&gt;--&lt;/span&gt; http://YOUR_API_SERVICE_1_HOSTNAME
etcdctl put traefik/http/services/bar/loadbalancer/servers/0/url &lt;span class="nt"&gt;--&lt;/span&gt; http://YOUR_API_SERVICE_2_HOSTNAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the foo and bar services do not know about the gateway, we have to remove &lt;code&gt;/foo&lt;/code&gt; and &lt;code&gt;/bar&lt;/code&gt; from the requests. While the requests are passing through the gateway, we can rewrite them using &lt;strong&gt;middlewares&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Create a &lt;a href="https://docs.traefik.io/middlewares/stripprefix/"&gt;StripPrefix&lt;/a&gt; middleware to remove the extra paths.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik/http/middlewares/strip-foobar/stripPrefix/prefixes/0 &lt;span class="nt"&gt;--&lt;/span&gt; /foo
etcdctl put traefik/http/middlewares/strip-foobar/stripPrefix/prefixes/1 &lt;span class="nt"&gt;--&lt;/span&gt; /bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, create two routes to match &lt;code&gt;/foo&lt;/code&gt; and &lt;code&gt;/bar&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik/http/routers/foo/service &lt;span class="nt"&gt;--&lt;/span&gt; foo
etcdctl put traefik/http/routers/foo/middlewares/0 &lt;span class="nt"&gt;--&lt;/span&gt; strip-foobar
etcdctl put traefik/http/routers/foo/rule &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'Path(`/foo`)'&lt;/span&gt;

etcdctl put traefik/http/routers/bar/service &lt;span class="nt"&gt;--&lt;/span&gt; bar
etcdctl put traefik/http/routers/bar/middlewares/0 &lt;span class="nt"&gt;--&lt;/span&gt; strip-foobar
etcdctl put traefik/http/routers/bar/rule &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'Path(`/bar`)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dashboard now shows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---lcS5KhI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hryx6z2vb3fpz1umesgy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---lcS5KhI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hryx6z2vb3fpz1umesgy.png" alt="Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once again, you can try the gateway. The &lt;code&gt;/foo&lt;/code&gt; path should respond with one IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl GATEWAY_ANYCAST_IP/foo

Hostname: my-api-services-my-api-server-group-sea-0
IP: 10.128.144.3
&lt;span class="o"&gt;[&lt;/span&gt;..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the &lt;code&gt;/bar&lt;/code&gt; path should always return the other IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl GATEWAY_ANYCAST_IP/bar

Hostname: my-api-services-my-api-server-group-sea-1
IP: 10.128.144.4
&lt;span class="o"&gt;[&lt;/span&gt;..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5 - Add More Middlewares
&lt;/h2&gt;

&lt;p&gt;In addition to StripPrefix, traefik ships with many other middlewares to filter, buffer, and authenticate requests. For instance, we can try &lt;a href="https://docs.traefik.io/middlewares/ratelimit/"&gt;RateLimit&lt;/a&gt;, which prevents abuse of the APIs and mitigates Denial of Service (DoS) attacks.&lt;/p&gt;

&lt;p&gt;First, configure the middleware with the following commands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik/http/middlewares/ratelimit-foobar/rateLimit/average &lt;span class="nt"&gt;--&lt;/span&gt; 10
etcdctl put traefik/http/middlewares/ratelimit-foobar/rateLimit/burst &lt;span class="nt"&gt;--&lt;/span&gt; 20
etcdctl put traefik/http/middlewares/ratelimit-foobar/rateLimit/period &lt;span class="nt"&gt;--&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, add it to the &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;bar&lt;/code&gt; routers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;etcdctl put traefik/http/routers/foo/middlewares/1 &lt;span class="nt"&gt;--&lt;/span&gt; ratelimit-foobar
etcdctl put traefik/http/routers/bar/middlewares/1 &lt;span class="nt"&gt;--&lt;/span&gt; ratelimit-foobar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it. Clients exceeding 10 requests per second on average or a burst of 20 requests will get a 429 error code (too many requests).&lt;/p&gt;

&lt;p&gt;Check the full list of middlewares &lt;a href="https://docs.traefik.io/middlewares/overview/"&gt;here&lt;/a&gt;. And, as an exercise, try combining the &lt;a href="https://docs.traefik.io/middlewares/circuitbreaker/"&gt;CircuitBreaker&lt;/a&gt; and &lt;a href="https://docs.traefik.io/middlewares/retry/"&gt;retry&lt;/a&gt; middlewares to keep track of service health and retry failed requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 - Securing the Gateway
&lt;/h2&gt;

&lt;p&gt;Traefik has many options to secure the gateway. Before using it in production, check the following settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboard
&lt;/h3&gt;

&lt;p&gt;The dashboard shows potentially sensitive information about your architecture. If you don't need it, you can disable it by removing the &lt;code&gt;--api&lt;/code&gt; and &lt;code&gt;--api.insecure&lt;/code&gt; startup options in the container workload.&lt;/p&gt;

&lt;p&gt;If you plan to keep using the dashboard, ensure that it’s adequately secured.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use StackPath network policies to control what IPs can access the dashboard.&lt;/li&gt;
&lt;li&gt;Add a password by setting up one of the authentication methods described &lt;a href="https://docs.traefik.io/operations/api/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;Traefik ships with several authentication middlewares. If your API service requires users to authenticate, you can set up one of the following middlewares.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.traefik.io/middlewares/basicauth/"&gt;BasicAuth&lt;/a&gt; uses basic HTTP authentication to control access of known users. Usernames and passwords are stored encrypted on the dynamic configuration.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.traefik.io/middlewares/digestauth/"&gt;DigestAuth&lt;/a&gt; authenticates using HTTP &lt;a href="https://tools.ietf.org/html/rfc2617"&gt;digests&lt;/a&gt;. The user credentials are stored on the dynamic configuration.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.traefik.io/middlewares/forwardauth/"&gt;ForwardAuth&lt;/a&gt; controls access permissions using a custom-defined external API service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SSL/TLS
&lt;/h3&gt;

&lt;p&gt;Traefik can act as a TLS terminator. If your API servers are in a private network you may not need HTTPS transport between them and the gateway. In such scenarios, traefik can offload the encryption workload.&lt;/p&gt;

&lt;p&gt;Before using the setup in production, you should activate TLS on your gateway. There are two methods for setting up certificates.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.traefik.io/https/tls/"&gt;Standard certificates&lt;/a&gt;: you can add a disk volume on your traefik container to store the certificates.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.traefik.io/https/tls/"&gt;Let's Encrypt&lt;/a&gt;: you can use Let's Encrypt to get free and automated certificates. This setting is static and must be enabled when traefik starts up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once configured, you can add the &lt;a href="https://docs.traefik.io/middlewares/redirectscheme/"&gt;RedirectScheme&lt;/a&gt; middleware to redirect all incoming HTTP to HTTPS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we have learned how to get started with traefik on StackPath by deploying some conventional gateway scenarios.&lt;/p&gt;

&lt;p&gt;While we focused only on HTTP, traefik can also work with TCP and UDP traffic, which adds the possibility of routing content related to streaming, conferencing, and gaming.&lt;/p&gt;

&lt;p&gt;Besides routing, traefik adds observability to your APIs.&lt;/p&gt;

&lt;p&gt;As a next step, learn how to use the &lt;a href="https://docs.traefik.io/observability/metrics/overview/"&gt;metrics&lt;/a&gt; and &lt;a href="https://docs.traefik.io/observability/tracing/overview/"&gt;tracing&lt;/a&gt; integrations to monitor the network&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image source: &lt;a href="https://joshuaavalon.io/setup-traefik-step-by-step"&gt;https://joshuaavalon.io/setup-traefik-step-by-step&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>traefik</category>
      <category>networking</category>
    </item>
    <item>
      <title>How to Deploy a FoundationDB Cluster at the Edge with StackPath</title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Wed, 29 Apr 2020 19:10:19 +0000</pubDate>
      <link>https://dev.to/stackpath/how-to-deploy-a-foundationdb-cluster-at-the-edge-with-stackpath-1b9n</link>
      <guid>https://dev.to/stackpath/how-to-deploy-a-foundationdb-cluster-at-the-edge-with-stackpath-1b9n</guid>
      <description>&lt;p&gt;&lt;em&gt;This tutorial was originally published in &lt;a href="https://blog.stackpath.com/foundationdb"&gt;StackPath Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.foundationdb.org/"&gt;FoundationDB&lt;/a&gt; is an open-source distributed NoSQL database released by Apple under the Apache 2.0 license. It is non-relational, so instead of using tables it organizes data as a set of ordered key-values. It can also handle massive volumes of data and is exceptionally well suited for write-intensive workloads.&lt;/p&gt;

&lt;p&gt;FoundationDB has been designed from the ground up for &lt;a href="https://blog.stackpath.com/distributed-system/"&gt;distributed computing&lt;/a&gt; and, to get the most out of FoundationDB's performance features, you need to put the database cluster as close as possible to where it is needed. To do this, you can leverage StackPath's low-latency &lt;a href="https://www.stackpath.com/products/virtual-machines/"&gt;edge compute VMs&lt;/a&gt; to run FoundationDB clusters.&lt;/p&gt;

&lt;p&gt;We'll show you how in the tutorial below. But first, let's review what makes FoundationDB great and determine if it's a good fit for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  FoundationDB: A Distributed, Multi-Model Database
&lt;/h2&gt;

&lt;p&gt;As the name implies, FoundationDB focuses on working with simple data types such as strings, integers, floating-point numbers, and arrays. Higher-order types are available via installable &lt;em&gt;Layers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Currently, there are two official Layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Record Layer&lt;/strong&gt;: a Java library that offers structured records and additional data types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document Layer&lt;/strong&gt;: a layer that offers a document-oriented MongoDB compatible API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Core features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal scaling&lt;/strong&gt;: as we add more nodes to the cluster, the performance of the system increases proportionally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault tolerance&lt;/strong&gt;: the database remains available even if some nodes in the cluster go down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinated transactions&lt;/strong&gt;: FoundationDB automatically routes transactions inside the cluster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong consistency&lt;/strong&gt;: unlike other NoSQL databases that compromise on consistency (e.g. eventual consistency), FoundationDB uses strict serialization, the most robust consistency model available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-blocking&lt;/strong&gt;: FoundationDB uses Multi-Version Concurrency Control (MVCC) for consistency; performance is not affected by slow clients and deadlocks are impossible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-tuning&lt;/strong&gt;: FoundationDB automatically optimizes its settings for best performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network aware topology&lt;/strong&gt;: FoundationDB is data center- and region-aware, using network information for tuning and fault tolerance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Is FoundationDB right for your project?
&lt;/h3&gt;

&lt;p&gt;The scalable nature of FoundationDB makes it &lt;strong&gt;a great fit for&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-intensive workloads&lt;/li&gt;
&lt;li&gt;Write-intensive workloads&lt;/li&gt;
&lt;li&gt;Workloads with strong transactional requirements&lt;/li&gt;
&lt;li&gt;Databases distributed over several data centers and regions&lt;/li&gt;
&lt;li&gt;Multi-petabyte databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FoundationDB design deliberately limits the size of the transactions. Because of this, &lt;strong&gt;the following workloads are NOT compatible with FoundationDB&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large transactions: there is a 10 MB limit on transaction size.&lt;/li&gt;
&lt;li&gt;Long transactions: transactions longer than 5 seconds are canceled.&lt;/li&gt;
&lt;li&gt;Large payloads: FoundationDB doesn’t support keys larger than 10 kB and values larger than 100 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying FoundationDB on StackPath
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sizing virtual machines
&lt;/h3&gt;

&lt;p&gt;Each FoundationDB process uses up to one CPU and is assigned its own data directory. We can run as many processes as CPU cores we have in the machine.&lt;/p&gt;

&lt;p&gt;FoundationDB &lt;a href="https://apple.github.io/foundationdb/configuration.html"&gt;minimum specs&lt;/a&gt; for production are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RHEL or CentOS 6 or 7&lt;/li&gt;
&lt;li&gt;Ubuntu 12.04 or later&lt;/li&gt;
&lt;li&gt;4 GB of RAM per CPU process&lt;/li&gt;
&lt;li&gt;Storage as required (more on that below)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FoundationDB has two storage modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;memory&lt;/strong&gt;: Optimized for small databases. The whole data must fit in memory. This mode is ideal for fast-cache applications. FoundationDB still requires some disk space to ensure consistency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ssd&lt;/strong&gt;: This mode stores the data on disk and is intended for big databases. The amount of usable space depends on the chosen redundancy level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FoundationDB has three levels of redundancy. Each redundancy level represents a different tradeoff between usable space and fault tolerance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;single&lt;/strong&gt;: Best for 1 or 2 nodes. Doesn’t provide any fault tolerance. Maximizes space available for data. Good for development and cache storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;double&lt;/strong&gt;: Best for 3 or 4 nodes. Data is duplicated. The cluster can survive the failure of 1 or 2 nodes. The amount of usable free space is halved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;triple&lt;/strong&gt;: Best for more than 5 nodes. Data is triplicated across the cluster nodes. Provides the highest safety level for a single-data-center cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a general rule, keep in mind the following when deciding what type of machine to choose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In order to ease management, try to keep all machines in the cluster the same type and size.&lt;/li&gt;
&lt;li&gt;Leave enough free space to accommodate sudden bursts of incoming data.&lt;/li&gt;
&lt;li&gt;Memory is king for databases; dedicate all the memory you can afford to databases.&lt;/li&gt;
&lt;li&gt;Increasing the redundancy level prevents data loss, but reduces the usable space for data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Network and security
&lt;/h3&gt;

&lt;p&gt;FoundationDB doesn’t have any built-in security mechanisms; there are no database users or passwords, nor read and write permissions. As a result, most deployments will likely only run inside a private network.&lt;/p&gt;

&lt;p&gt;Access control must be controlled by other means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network permissions&lt;/strong&gt;: each FoundationDB process listens on a separate port, starting from port TCP 4500 for the first process, 4501 for the second process, and so on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection file&lt;/strong&gt;: clients must have a copy of the cluster file (fdb.cluster) to connect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Certificates&lt;/strong&gt;: access can be secured by configuring mutual TLS certificates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To get started you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A StackPath account&lt;/li&gt;
&lt;li&gt;Beginner-level knowledge of Linux servers: editors, executing commands, and connecting via SSH&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1 - Create an SSH key pair
&lt;/h3&gt;

&lt;p&gt;You will need an SSH key to access your StackPath servers. If you already have one, you can skip to &lt;strong&gt;Step 2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To create an SSH key pair in Linux or Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your_email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, install &lt;a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/"&gt;PuTTY&lt;/a&gt; and use &lt;code&gt;puttygen.exe&lt;/code&gt; to generate the key pair.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - Create the first VM
&lt;/h3&gt;

&lt;p&gt;In this section, we’ll create our first Virtual Machine (VM) on StackPath.&lt;/p&gt;

&lt;p&gt;To start, go to &lt;a href="https://stackpath.com/"&gt;StackPath&lt;/a&gt; and log in or sign up for an account.&lt;/p&gt;

&lt;p&gt;In the left menu, click &lt;strong&gt;Workloads&lt;/strong&gt; and then &lt;strong&gt;Create Workload&lt;/strong&gt;. Name your workload and, on &lt;strong&gt;Workload Type&lt;/strong&gt;, select VM.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Image&lt;/strong&gt; select the most recent Ubuntu LTS version available (Ubuntu 18.04 LTS).&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Settings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wa-a4Xx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5g1t1zh4qwr05ffwwgb1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wa-a4Xx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5g1t1zh4qwr05ffwwgb1.png" alt="vm1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Public Ports&lt;/strong&gt;, type 22 and select TCP.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;First Boot Key&lt;/strong&gt;, copy the contents of your SSH public key (the file with the &lt;code&gt;.pub&lt;/code&gt; extension, for instance: &lt;code&gt;id_rsa.pub&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lnKhToGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w4ysd8r4izaqt0axmo6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lnKhToGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w4ysd8r4izaqt0axmo6i.png" alt="vm2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Continue to Spec&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Select the machine &lt;strong&gt;Spec&lt;/strong&gt; that suits your needs. The &lt;strong&gt;SP3&lt;/strong&gt; machine has enough power to run two FoundationDB processes.&lt;/p&gt;

&lt;p&gt;If you’re planning to use the ssd mode, add a volume for the data. On &lt;strong&gt;Additional Volume&lt;/strong&gt;, type /var/lib/foundationdb and set the amount of storage you want to dedicate to the databases on this node.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Deployment Target&lt;/strong&gt;, type the name of the deployment group. Deployment targets are groups of instances with the same scaling needs.&lt;/p&gt;

&lt;p&gt;Select one of the &lt;a href="https://blog.stackpath.com/point-of-presence/"&gt;PoPs&lt;/a&gt; that are near you or your users.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Instances per PoP&lt;/strong&gt;, select 1.&lt;/p&gt;

&lt;p&gt;Create the machine using the &lt;strong&gt;Create Workload&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FPtKe-JU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qwjqwo3mbu2zsbazxuef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FPtKe-JU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qwjqwo3mbu2zsbazxuef.png" alt="vm3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 - Install FoundationDB
&lt;/h3&gt;

&lt;p&gt;Wait a few seconds for the machine. You’ll know it’s ready when it reaches the Running status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W9ILYjAA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ich2du91su7ecowfpjey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9ILYjAA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ich2du91su7ecowfpjey.png" alt="map1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the Public IP, as shown in StackPath.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZiFF95KK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lsb0t4p9gcja5xepq7bn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZiFF95KK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lsb0t4p9gcja5xepq7bn.png" alt="public1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open a terminal in your machine or start PuTTY.&lt;/p&gt;

&lt;p&gt;Connect to the Public IP with SSH or PuTTY using the key generated in &lt;strong&gt;Step 1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Log in with the ubuntu user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh ubuntu@YOUR_NODE1_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to the FoundationDB &lt;a href="https://apple.github.io/foundationdb/downloads.html"&gt;download page&lt;/a&gt; and pick the newest version of FoundationDB server and client packages for Ubuntu.&lt;/p&gt;

&lt;p&gt;Copy the addresses of the packages and download both files to the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://www.foundationdb.org/downloads/6.2.11/ubuntu/installers/foundationdb-clients_6.2.11-1_amd64.deb
wget https://www.foundationdb.org/downloads/6.2.11/ubuntu/installers/foundationdb-server_6.2.11-1_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install them with dpkg.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; foundationdb-clients_6.2.11-1_amd64.deb foundationdb-server_6.2.11-1_amd64.deb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few seconds, your first FoundationDB instance should be up and running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ps &lt;span class="nt"&gt;-e&lt;/span&gt; | egrep &lt;span class="s2"&gt;"(fdb|backup_agent)"&lt;/span&gt;

14108 ?        00:00:02 fdbserver
14106 ?        00:00:00 fdbmonitor
14107 ?        00:00:00 backup_agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FoundationDB has three types of processes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fdbserver&lt;/strong&gt;: is the core server process, each of these uses a single CPU. For dedicated machines, we want to run as many fdbserver processes as CPUs the system has. Each fdbserver has a dedicated port, starting from TCP 4500.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fdbmonitor&lt;/strong&gt;: monitors the configuration files and the server processes. Whenever we make a change in the configuration, fdbmonitor automatically reloads the servers. fdbmonitor is also in charge of managing the backup_agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;backup_agent&lt;/strong&gt;: is responsible for taking backups and importing data into the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get the server status information use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fdbcli &lt;span class="nt"&gt;--exec&lt;/span&gt; status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At any point, you can start, stop, or restart the database with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop foundationdb
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start foundationdb
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart foundationdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4 - Configure the first node
&lt;/h3&gt;

&lt;p&gt;In this section, we’ll prepare FoundationDB for production.&lt;/p&gt;

&lt;p&gt;Copy the configuration to a temporary directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /etc/foundationdb/foundationdb.conf /tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the file with your preferred editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /tmp/foundationdb.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And find the &lt;code&gt;[fdbserver]&lt;/code&gt; section and uncomment the &lt;code&gt;datacenter_id&lt;/code&gt; line.&lt;/p&gt;

&lt;p&gt;Set a data center name; you should use an identical string for all nodes in the same PoP. For example, for a node in New York, a good name would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datacenter_id = nyc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If your server has two CPUs&lt;/strong&gt;: add the following line right before the &lt;code&gt;[backup_agent]&lt;/code&gt; section; this tells FoundationDB to start a second process on port 4501.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[fdbserver.4501]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If your server has more than two CPUs&lt;/strong&gt;: repeat the previous step until the number of processes matches the number of CPUs.&lt;/p&gt;

&lt;p&gt;When you complete the modifications, the file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[fdbmonitor]
user = foundationdb
group = foundationdb

[general]
restart_delay = 60
cluster_file = /etc/foundationdb/fdb.cluster

[fdbserver]
command = /usr/sbin/fdbserver
public_address = auto:$ID
listen_address = public
datadir = /var/lib/foundationdb/data/$ID
logdir = /var/log/foundationdb
datacenter_id = nyc

[fdbserver.4500]
[fdbserver.4501]

[backup_agent]
command = /usr/lib/foundationdb/backup_agent/backup_agent
logdir = /var/log/foundationdb

[backup_agent.1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file and copy it back to &lt;code&gt;/etc/foundationdb&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /tmp/foundationdb.conf /etc/foundationdb/foundationdb.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now find a second fdbserver process (or as many processes as you configured).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps &lt;span class="nt"&gt;-e&lt;/span&gt;|egrep &lt;span class="s2"&gt;"(fdb|backup_agent)"&lt;/span&gt;
14106 ?        00:00:00 fdbmonitor
14107 ?        00:00:09 backup_agent
14412 ?        00:00:00 fdbserver
14413 ?        00:00:00 fdbserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also check how many processes are running with &lt;code&gt;fdbcli&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fdbcli &lt;span class="nt"&gt;--exec&lt;/span&gt; status

&lt;span class="o"&gt;[&lt;/span&gt;...]
Cluster:
  FoundationDB processes - 2
  Zones                  - 1
  Machines               - 1
  Memory availability    - 4.1 GB per process on machine with least available

&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5 - Make server listen for external connections
&lt;/h3&gt;

&lt;p&gt;In this step, we’ll open the ports for external connections.&lt;/p&gt;

&lt;p&gt;Keep in mind that if you have opened ports 4500 or 4501 in StackPath, your server may be publicly accessible. In that case, ensure that you've read all the security considerations carefully before continuing.&lt;/p&gt;

&lt;p&gt;To allow external connections, install Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;python &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the &lt;code&gt;make_public.py&lt;/code&gt; script. FoundationDB binds itself to the server’s private IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/lib/foundationdb/make_public.py
/etc/foundationdb/fdb.cluster is now using address 10.128.96.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart foundationdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first server is ready! Now we'll copy the configuration files to your machine.&lt;/p&gt;

&lt;p&gt;End your SSH session with logout or CTRL+D and copy the config and cluster files to your computer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp ubuntu@YOUR_NODE1_IP:&lt;span class="s2"&gt;"/etc/foundationdb/*"&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6 - Create more machines
&lt;/h3&gt;

&lt;p&gt;In this step, we’ll create additional nodes to form a cluster. You can stop here if you plan on deploying only one machine.&lt;/p&gt;

&lt;p&gt;To provision the rest of the machines, go back to your StackPath dashboard. Locate your FoundationDB workload and click the Gear icon to modify its settings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TRW3wXPM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n8xhhm1ukc1cd9qd8i0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TRW3wXPM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n8xhhm1ukc1cd9qd8i0j.png" alt="vm4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Edit&lt;/strong&gt; next to Spec, Storage &amp;amp; Deployment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gVsNvA6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i6lhc2wda7sfz9r3jvny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gVsNvA6a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/i6lhc2wda7sfz9r3jvny.png" alt="vm5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Deployment Target&lt;/strong&gt; section, increase the number of instances to the number of nodes you want in the cluster.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GGFFJzN3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w19llc8gi41n87fn2zhp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GGFFJzN3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/w19llc8gi41n87fn2zhp.png" alt="vm6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Save Changes&lt;/strong&gt; and wait for the new servers to reach the Running status.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lowhgkhT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ix1zrmo62t2lm6y68l28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lowhgkhT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ix1zrmo62t2lm6y68l28.png" alt="vm7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;FoundationDB clients and servers connect to the cluster using the information contained in the fdb.cluster file.&lt;/p&gt;

&lt;p&gt;To add a node to a cluster, we have to copy this file from any server already in the cluster and put it on &lt;code&gt;/etc/foundationdb&lt;/code&gt; on the new machine. Once restarted, the new node will automatically join the cluster and start synchronizing.&lt;/p&gt;

&lt;p&gt;For each of the new servers, repeat these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy configuration files you saved in your machine at the end of &lt;strong&gt;Step 5&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp foundationdb.conf fdb.cluster ubuntu@YOUR_NEW_SERVER_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Repeat Step 3 to install FoundationDB with its default configuration.&lt;/li&gt;
&lt;li&gt;Overwrite the default config and cluster files.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;foundationdb.conf fdb.cluster /etc/foundationdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Restart FoundationDB.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart foundationdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7 - Configure the cluster
&lt;/h3&gt;

&lt;p&gt;In this section, we’ll finalize the configuration of the cluster. Since the servers are linked up, we only need to run the following commands once.&lt;/p&gt;

&lt;p&gt;First, ensure you are connected via SSH to any of the servers in the cluster. Then, start an interactive &lt;code&gt;fdbcli&lt;/code&gt; session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fdbcli

Using cluster file `/etc/foundationdb/fdb.cluster'.

The database is unavailable; type `status' for more information.

Welcome to the fdbcli. For help, type `help'.

fdb&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type status and check the number of machines in the Cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; status

[...]
Cluster:
  FoundationDB processes - 6
  Machines               - 3
[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the coordinator mode to auto so the cluster can automatically nominate &lt;a href="https://apple.github.io/foundationdb/configuration.html#choosing-coordination-servers"&gt;coordinators&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; coordinators auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also set a name for the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; coordinators description=mycluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few seconds, you should see that the coordinators are activated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; coordinators

Cluster description: mycluster
Cluster coordinators (3): 10.128.96.3:4500,10.128.96.4:4500,10.128.96.5:4500
Type `help coordinators' to learn how to change this information.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you wish to activate ssd storage mode, set the redundancy level now (single, double or triple).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; configure double ssd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the coordinator settings and the redundancy level with status.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; status

Configuration:
  Redundancy mode        - double
  Storage engine         - ssd-2
  Coordinators           - 3

[..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Congratulations! You’ve learned about FoundationDB and how to deploy it using StackPath’s edge compute VMs. You’re on your way to using high performance, low-latency databases.&lt;/p&gt;

&lt;p&gt;As a next step, learn how to use &lt;a href="https://apple.github.io/foundationdb/configuration.html#datacenter-aware-mode"&gt;datacenter mode&lt;/a&gt; to extend your database over more data centers. Then, learn how regions can help you build a truly global distributed database so that data is always close to your users.&lt;/p&gt;

&lt;p&gt;Finally, check out these links to continue learning about FoundationDB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://apple.github.io/foundationdb/architecture.html"&gt;FoundationDB Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apple.github.io/foundationdb/configuration.html#choosing-a-redundancy-mode"&gt;Datacenter and Region Modes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apple.github.io/foundationdb/layer-concept.html"&gt;Learn about FoundationDB Layers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.stackpath.com/edge-computing/"&gt;Learn about StackPath Edge Computing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>foundationdb</category>
      <category>tutorial</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>How to Run Clickhouse DBMS at the Edge with StackPath </title>
      <dc:creator>Tomas Fernandez</dc:creator>
      <pubDate>Wed, 15 Apr 2020 16:12:21 +0000</pubDate>
      <link>https://dev.to/stackpath/how-to-run-clickhouse-dbms-at-the-edge-with-stackpath-a67</link>
      <guid>https://dev.to/stackpath/how-to-run-clickhouse-dbms-at-the-edge-with-stackpath-a67</guid>
      <description>&lt;p&gt;&lt;em&gt;This tutorial originally appeared on &lt;a href="https://blog.stackpath.com/clickhouse/" rel="noopener noreferrer"&gt;StackPath Blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Businesses use data analytics to extract meaningful, decision-making information for their day-to-day operations. Analytics can discover hidden relations, find unexpected patterns, learn about customer behavior, understand the past and predict the future.&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll learn about Clickhouse, a column-oriented database for real-time reports. We will explore how it works and take it out for a test drive using StackPath Containers.&lt;/p&gt;

&lt;p&gt;Before diving into the tutorial, first we’ll provide an overview of column-based databases, ClickHouse, and the benefits of running ClickHouse on StackPath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Column-Based Databases
&lt;/h2&gt;

&lt;p&gt;Compared with traditional row-based databases, column databases have a subtle yet profound difference in how data is stored on disk. &lt;/p&gt;

&lt;p&gt;A traditional database stores data as rows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;COMPANY&lt;/th&gt;
&lt;th&gt;CEO_NAME&lt;/th&gt;
&lt;th&gt;CEO_NAME&lt;/th&gt;
&lt;th&gt;YEAR_TO&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Bill Gates&lt;/td&gt;
&lt;td&gt;Bill Gates&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Satya Nadella&lt;/td&gt;
&lt;td&gt;Satya Nadella&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;Steve Cook&lt;/td&gt;
&lt;td&gt;Steve Cook&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Facebook&lt;/td&gt;
&lt;td&gt;M. Zuckerberg&lt;/td&gt;
&lt;td&gt;M. Zuckerberg&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;Steve Jobs&lt;/td&gt;
&lt;td&gt;Steve Jobs&lt;/td&gt;
&lt;td&gt;2001&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A column-based database  DBs serialize them by columns:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;COLUMN&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;COMPANY&lt;/td&gt;
&lt;td&gt;Microsoft (1,2)&lt;/td&gt;
&lt;td&gt;Microsoft (1,2)&lt;/td&gt;
&lt;td&gt;Facebook (4)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CEO_NAME&lt;/td&gt;
&lt;td&gt;Bill Gates (1)&lt;/td&gt;
&lt;td&gt;Bill Gates (1)&lt;/td&gt;
&lt;td&gt;Steve Cook (3)&lt;/td&gt;
&lt;td&gt;M. Zuckerberg (4)&lt;/td&gt;
&lt;td&gt;Steve Jobs (5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YEAR_FROM&lt;/td&gt;
&lt;td&gt;1975 (1)&lt;/td&gt;
&lt;td&gt;1975 (1)&lt;/td&gt;
&lt;td&gt;2011 (3)&lt;/td&gt;
&lt;td&gt;2014 (4)&lt;/td&gt;
&lt;td&gt;1997 (5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YEAR_TO&lt;/td&gt;
&lt;td&gt;2000 (1)&lt;/td&gt;
&lt;td&gt;2000 (1)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our expected workload determines what type of database we should choose. Row I/O is designed to get all the information for a small set of records while column I/O is designed to get a small subset of columns for a huge set of records (or even a whole table).&lt;/p&gt;

&lt;p&gt;Now, imagine we ask the server to calculate how many years each of the persons on the tables above were CEOs of their respective companies. A row database has to retrieve each row, then get to the columns with the relevant years and discard the rest. A column database only needs two I/O operations (regardless of the size of the table): one for each date column. No data is discarded. A column system will perform much better in this case and scales much better with large tables.&lt;/p&gt;

&lt;p&gt;As a result of their design, column databases have unique properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Efficient column aggregation and materialized views.&lt;/li&gt;
&lt;li&gt;Wide tables with optional columns pose no problems. On the contrary,
they are encouraged.&lt;/li&gt;
&lt;li&gt;Because similar data compress best, column orientation uses less space on disk than rows.&lt;/li&gt;
&lt;li&gt;Due to less reliance on locking, column architectures can run with a much higher degree of parallelization.&lt;/li&gt;
&lt;li&gt;Batch inserts are more efficient on columns than on rows.&lt;/li&gt;
&lt;li&gt;Column databases have less reliance on indexes, which reduces overhead in the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ClickHouse: a Distributed Column-Based DBMS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://clickhouse.tech/" rel="noopener noreferrer"&gt;Clickhouse&lt;/a&gt; is a database management system (DBMS) created by Yandex, the second-largest web analytics platform in the world, and released as open-source software under the Apache 2.0 License.&lt;/p&gt;

&lt;p&gt;Beyond Yandex, many other big companies have adopted Clickhouse for their analytics, including Cisco, Cloudflare, CERN, Bloomberg, and Spotify.&lt;/p&gt;

&lt;p&gt;Clickhouse main features are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;True column-based DBMS.&lt;/li&gt;
&lt;li&gt;A subset of the SQL Language feature-rich with analytics and statistics functions.&lt;/li&gt;
&lt;li&gt;Fault tolerance and no-downtime upgrades thanks to replication.&lt;/li&gt;
&lt;li&gt;Multi-petabyte scale support.&lt;/li&gt;
&lt;li&gt;Several engine modes for databases and tables.&lt;/li&gt;
&lt;li&gt;Parallel processing on a single node.&lt;/li&gt;
&lt;li&gt;Distributed queries using multiple nodes.&lt;/li&gt;
&lt;li&gt;Support approximated calculations through sampling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ClickHouse supports two mechanisms to scale out databases. &lt;strong&gt;Sharding&lt;/strong&gt; distributes big tables among multiple machines and requests get automatically parallelized. &lt;strong&gt;Replication&lt;/strong&gt; creates redundant copies of the data and can be used to distribute and filter data to different data centers. Also, ClickHouse supports multi-master replication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Clickhouse right for your project?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Clickhouse is good for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OLAP and OLEP&lt;/strong&gt; workloads as long as they don’t need full-text
search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Warehouse&lt;/strong&gt;: Clickhouse can store and process petabytes of data and build customized reports on the fly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Big tables&lt;/strong&gt;: tables with hundreds or thousands of columns.&lt;/li&gt;
&lt;li&gt;Applications that need a high read throughput.&lt;/li&gt;
&lt;li&gt;Applications that make heavy use of aggregated columns or materialized views.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Clickhouse is not good for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OLTP (Online Transactional Processing) workloads. Clickhouse doesn’t support full-fledged transactions.&lt;/li&gt;
&lt;li&gt;Applications with heavy write requirements.&lt;/li&gt;
&lt;li&gt;Applications with complex or &lt;strong&gt;random write or update&lt;/strong&gt; activity.&lt;/li&gt;
&lt;li&gt;Applications that need full-text search.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running Clickhouse in StackPath
&lt;/h2&gt;

&lt;p&gt;Databases have some unique requirements that can clash with public cloud&lt;br&gt;
common offerings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: high per-GB costs can make downloads and uploads expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bandwidth&lt;/strong&gt;: big databases can take a long time to load or export.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt;: round trip times can add unacceptable latencies to the queries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.stackpath.com/edge-academy/edge-computing/" rel="noopener noreferrer"&gt;Edge Computing&lt;/a&gt; solves these issues by moving servers as close as possible to customers and sources of data. As for StackPath, it offers the best of both worlds: the convenience and flexibility of the cloud with on premise-like performance.&lt;/p&gt;
&lt;h2&gt;
  
  
  Single Instance Configuration
&lt;/h2&gt;

&lt;p&gt;In this section, we’ll deploy a single ClickHouse edge computing node in order to get familiar with the system. Then, we’ll learn how to implement a cluster.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;First, we need to create a custom Docker image with our private config. Before continuing, ensure you have the following things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; installed on your workstation. On Mac and Windows, we recommend installing &lt;a href="https://www.docker.com/products/docker-desktop" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt;. On Linux machines, install Docker from repositories.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;strong&gt;Docker Hub&lt;/strong&gt; account to store the images. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Preparing the Docker Image
&lt;/h3&gt;

&lt;p&gt;Yandex supplies two ready-to-use Docker images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;clickhouse-server&lt;/strong&gt;: is the server component; it stores the data,
accepts client connections, and processes requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;clickhouse-client&lt;/strong&gt;: is the official command-line client, much
like psql or mysql.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To download the images to your machine, use the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull yandex/clickhouse-server
docker pull yandex/clickhouse-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the Clickhouse server includes a single user called &lt;strong&gt;default&lt;/strong&gt; with a blank password. For testing purposes, having a blank password is not a problem, but since we’re going to deploy the node in production, we need to secure it.&lt;/p&gt;

&lt;p&gt;To customize the user settings, we need to change a file called &lt;code&gt;users.xml&lt;/code&gt;, which is inside the clickhouse-server image.&lt;/p&gt;

&lt;p&gt;To do this, create a directory to store our files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-clickhouse-server
&lt;span class="nb"&gt;cd &lt;/span&gt;my-clickhouse-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start an instance of the container so we can copy out the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker create &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; clickhouse-server clickhouse server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the file from the container to your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;cp &lt;/span&gt;clickhouse-server:/etc/clickhouse-server/users.xml &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;users.xml&lt;/code&gt; with a text editor and locate the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;users&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!-- This is the default user with a blank password --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;password&amp;gt;&amp;lt;/password&amp;gt;&lt;/span&gt;

        . . .

    &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/users&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clickhouse supports several password settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plaintext&lt;/strong&gt;: insecure and not recommended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double SHA1&lt;/strong&gt;: uses a cryptographic hash function to make the password harder to guess to anyone looking at our image. Double SHA1 strings go inside &lt;code&gt;&amp;lt;password_double_sha1_hex&amp;gt;&amp;lt;/password_double_sha1_hex&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA256&lt;/strong&gt;: arguably the most secure alternative and the one we will use. This option uses &lt;code&gt;&amp;lt;password_double_sha256_hex&amp;gt;&amp;lt;/password_sha256_hex&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To generate a SHA256 password in &lt;strong&gt;Linux&lt;/strong&gt; or &lt;strong&gt;Mac&lt;/strong&gt;, execute the following command in a new terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &amp;lt; /dev/urandom | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c8&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sha256sum&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line printed is the password, the second is the encoded SHA256 string. Alternatively, you can use an online SHA256 generator to obtain the string. In either case, keep both the password and the SHA string at hand as we’ll need them both soon.&lt;/p&gt;

&lt;p&gt;Going back to &lt;code&gt;users.xml&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Remove the &lt;code&gt;&amp;lt;password&amp;gt;&amp;lt;/password&amp;gt;&lt;/code&gt; line to get rid of the blank password. Then add the following line with your SHA256 string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;password_sha256_hex&amp;gt;&lt;/span&gt;78bb49329e64a3df1ea5a48fb09ce22ed5223171cf140e3b08683ab3926a0b9b&lt;span class="nt"&gt;&amp;lt;/password_sha256_hex&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;users&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;password_sha256_hex&amp;gt;&lt;/span&gt;78bb49329e64a3df1ea5a48fb09ce22ed5223171cf140e3b08683ab3926a0b9b&lt;span class="nt"&gt;&amp;lt;/password_sha256_hex&amp;gt;&lt;/span&gt;

        . . .

    &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/users&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build the Image
&lt;/h3&gt;

&lt;p&gt;Create a file called &lt;code&gt;Dockerfile&lt;/code&gt; with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; yandex/clickhouse-server&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; users.xml /etc/clickhouse-server&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, build the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-clickhouse-server &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test if it starts correctly with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; my-server my-clickhouse-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, from a different terminal, try connecting using your password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--link&lt;/span&gt; clickhouse:clickhouse-server yandex/clickhouse-client &lt;span class="nt"&gt;--host&lt;/span&gt; clickhouse-server &lt;span class="nt"&gt;--user&lt;/span&gt; default &lt;span class="nt"&gt;--password&lt;/span&gt; YOUR_PASSWORD
ClickHouse client version 20.3.2.1 &lt;span class="o"&gt;(&lt;/span&gt;official build&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Connecting to 151.139.47.86:9000 as user default.
Connected to ClickHouse server version 20.3.2 revision 54433.

clickhouse-server :&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Push the Image to the Registry
&lt;/h3&gt;

&lt;p&gt;Before we can use the image in StackPath, we have to put it in a Docker registry. To do this, log in to Docker Hub with your account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker login &lt;span class="nt"&gt;-u&lt;/span&gt; YOUR_DOCKERHUB_USERNAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build the image using your Docker Hub username as a prefix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; YOUR_DOCKERHUB_USERNAME/clickhouse-server:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push the image to Docker Hub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push YOUR_DOCKERHUB_USERNAME/clickhouse-server:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy to StackPath
&lt;/h3&gt;

&lt;p&gt;Once we have pushed the image to the Docker Registry, we can deploy it in StackPath.&lt;/p&gt;

&lt;p&gt;Head over to &lt;a href="https://stackpath.com" rel="noopener noreferrer"&gt;StackPath&lt;/a&gt;. Login with your account or sign up for a new one. Then, click on the &lt;strong&gt;Create&lt;/strong&gt; button and select &lt;strong&gt;Workload&lt;/strong&gt;.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fixtdmxocrtst9yqlla74.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fixtdmxocrtst9yqlla74.png" alt="Create Workload 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll name the workload “clickhouse-servers” and set the type to &lt;strong&gt;Container&lt;/strong&gt;. For Image, type the address to your Docker image: &lt;code&gt;YOUR_DOCKERHUB_USERNAME/clickhouse-server:latest&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you are using a private repository or a different registry, check the &lt;strong&gt;Add Image Pull Credentials&lt;/strong&gt; box and type in the pull details.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Continue to Settings&lt;/strong&gt;.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg28lnxt3shytopuexs63.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg28lnxt3shytopuexs63.png" alt="Create Workload 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want the servers to be accessible to everyone, add the following ports under &lt;strong&gt;Public ports&lt;/strong&gt;. This will assign Public IPs and open the ports to the Internet. &lt;/p&gt;

&lt;p&gt;All containers get a Private IP assigned automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Port &lt;code&gt;8123 TCP&lt;/code&gt; is the default HTTP API Endpoint.&lt;/li&gt;
&lt;li&gt;Port &lt;code&gt;9000 TCP&lt;/code&gt; is the default port for the native clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click on &lt;strong&gt;Continue to Spec&lt;/strong&gt;.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyt0urf5zjb1m3fggbrkb.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fyt0urf5zjb1m3fggbrkb.png" alt="Create VM1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Spec&lt;/strong&gt;, choose the machine size. Clickhouse developers &lt;a href="https://clickhouse.tech/docs/en/operations/requirements/" rel="noopener noreferrer"&gt;recommend&lt;/a&gt; a minimum of 4GB of RAM to allow for complex queries. If in doubt, choose the smallest machine, you can always scale the machine up with a few clicks later on.&lt;/p&gt;

&lt;p&gt;Next, we’ll need a volume to persist our data, under &lt;strong&gt;Additional volume&lt;/strong&gt; type &lt;code&gt;/var/lib/clickhouse&lt;/code&gt; and set its size on GBs.&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;Deployment Target&lt;/strong&gt;, we will group servers with similar scaling needs. Type a descriptive name like “clickhouse-servers”. Then select the &lt;a href="https://blog.stackpath.com/point-of-presence/" rel="noopener noreferrer"&gt;PoP&lt;/a&gt;, which is nearest to you or to your customers. Set the number of instances to 1.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Create Workload&lt;/strong&gt;.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkni3sl2te4epj4b7eok2.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fkni3sl2te4epj4b7eok2.png" alt="Create VM2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait a few seconds until the instance is on the &lt;strong&gt;Running&lt;/strong&gt; state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the Connection
&lt;/h3&gt;

&lt;p&gt;The following test will only work if you have opened ports 8123 and 9000:&lt;/p&gt;

&lt;p&gt;Click on the running instance to get its details.&lt;br&gt;
Then, scroll down to the &lt;strong&gt;Instance details&lt;/strong&gt; section and copy the &lt;strong&gt;Public IP&lt;/strong&gt; address.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2i0pn9n6wd8jvyb528lh.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2i0pn9n6wd8jvyb528lh.png" alt="Public IP 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, open a terminal and try the &lt;code&gt;/ping&lt;/code&gt; endpoint with Curl or a browser. If the server is running it should respond with "Ok".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; YOUR_PUBLIC_IP:8123/ping

Ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also connect with Clickhouse Native Client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; yandex/clickhouse-client &lt;span class="nt"&gt;--host&lt;/span&gt; YOUR_PUBLIC_IP &lt;span class="nt"&gt;--user&lt;/span&gt; default &lt;span class="nt"&gt;--password&lt;/span&gt; YOUR_PASSWORD

ClickHouse client version 20.3.2.1 &lt;span class="o"&gt;(&lt;/span&gt;official build&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Connecting to 151.139.31.12:9000 as user default.
Connected to ClickHouse server version 20.3.2 revision 54433.

clickhouse-servers-clickhouse-servers-jfk-0 :&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can execute &lt;a href="https://clickhouse-docs.readthedocs.io/en/latest/query_language/" rel="noopener noreferrer"&gt;Clickhouse SQL&lt;/a&gt; commands on your new server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clickhouse-servers-clickhouse-servers-jfk-0 :&lt;span class="o"&gt;)&lt;/span&gt; create database my_db&lt;span class="p"&gt;;&lt;/span&gt;

CREATE DATABASE my_db

Ok.

0 rows &lt;span class="k"&gt;in &lt;/span&gt;set. Elapsed: 0.393 sec.

clickhouse-servers-clickhouse-servers-jfk-0 :&lt;span class="o"&gt;)&lt;/span&gt; show databases&lt;span class="p"&gt;;&lt;/span&gt;

SHOW DATABASES

┌─name────┐
│ default │
│ my_db   │
│ system  │
└─────────┘

3 rows &lt;span class="k"&gt;in &lt;/span&gt;set. Elapsed: 0.326 sec.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sharded Cluster Configuration
&lt;/h2&gt;

&lt;p&gt;In this section, we’ll deploy two more database instances to create a&lt;br&gt;
3-node Edge Computing cluster.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create More Instances
&lt;/h3&gt;

&lt;p&gt;First, we’ll create the remaining instances so that we can get their&lt;br&gt;
Private IPs.&lt;/p&gt;

&lt;p&gt;On the left navigation menu, click on &lt;strong&gt;Workloads&lt;/strong&gt; and select your container workload. Then, click on the &lt;strong&gt;Gear&lt;/strong&gt; icon to edit the workload configuration.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3ul6smir9sj0197o3nsv.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3ul6smir9sj0197o3nsv.png" alt="Edit Workload"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Edit&lt;/strong&gt; icon next to &lt;strong&gt;Spec, Storage &amp;amp; Deployment&lt;/strong&gt; and Increase the number of &lt;strong&gt;Instances per PoP&lt;/strong&gt; to 3. Click on &lt;strong&gt;Save Changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;StackPath will create the remaining instances in a few seconds.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2i0pn9n6wd8jvyb528lh.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2i0pn9n6wd8jvyb528lh.png" alt="Public IP 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the instances are ready, copy the &lt;strong&gt;Private IP&lt;/strong&gt; address of each one:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fptf2rh796j9q8dr27rgx.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fptf2rh796j9q8dr27rgx.png" alt="Public IP 3"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Configure Clickhouse Shards
&lt;/h3&gt;

&lt;p&gt;We have to tell the Clickhouse where all the nodes are. For this, we&lt;br&gt;
need to first get the &lt;code&gt;config.xml&lt;/code&gt; file from the Clickhouse image.&lt;/p&gt;

&lt;p&gt;Open a terminal and type the following command to copy the file out of the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;cp &lt;/span&gt;clickhouse-server:/etc/clickhouse-server/config.xml &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, open the file and locate the &lt;code&gt;remote_servers&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;remote_servers&lt;/span&gt; &lt;span class="na"&gt;incl=&lt;/span&gt;&lt;span class="s"&gt;"clickhouse_remote_servers"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Test only shard config for testing distributed storage --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;test_shard_localhost&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;shard&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;replica&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;host&amp;gt;&lt;/span&gt;localhost&lt;span class="nt"&gt;&amp;lt;/host&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9000&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/replica&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/shard&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/test_shard_localhost&amp;gt;&lt;/span&gt;

        . . . 
&lt;span class="nt"&gt;&amp;lt;/remote_servers&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove all the lines inside the &lt;code&gt;&amp;lt;remote_servers&amp;gt; &amp;lt;/remote_servers&amp;gt;&lt;/code&gt; elements and type the following config. Replace the IP Addresses with your servers &lt;strong&gt;Private IPs&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;remote_servers&lt;/span&gt; &lt;span class="na"&gt;incl=&lt;/span&gt;&lt;span class="s"&gt;"clickhouse_remote_servers"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;my_sharded_config&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;shard&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;replica&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;host&amp;gt;&lt;/span&gt;SERVER_1_PRIVATE_IP&lt;span class="nt"&gt;&amp;lt;/host&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9000&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/replica&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/shard&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;shard&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;replica&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;host&amp;gt;&lt;/span&gt;SERVER_2_PRIVATE_IP&lt;span class="nt"&gt;&amp;lt;/host&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9000&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/replica&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/shard&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;shard&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;replica&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;host&amp;gt;&lt;/span&gt;SERVER_3_PRIVATE_IP&lt;span class="nt"&gt;&amp;lt;/host&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;port&amp;gt;&lt;/span&gt;9000&lt;span class="nt"&gt;&amp;lt;/port&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/replica&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/shard&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/my_sharded_config&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/remote_servers&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit your &lt;code&gt;Dockerfile&lt;/code&gt; to add the modified config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; yandex/clickhouse-server&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; users.xml /etc/clickhouse-server&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; config.xml /etc/clickhouse-server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build a new version of the image (notice we’re using a different tag&lt;br&gt;
    here).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; YOUR_DOCKERHUB_USERNAME/clickhouse-server:sharded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push the new image to the Docker Registry, same as before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push YOUR_DOCKERHUB_USERNAME/clickhouse-server:sharded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update StackPath Workload
&lt;/h3&gt;

&lt;p&gt;The final step is to tell StackPath to use our new configuration. To do this, go to your StackPath Workload and click the &lt;strong&gt;Gear&lt;/strong&gt; button again to change its configuration. Edit the &lt;strong&gt;Image&lt;/strong&gt; name with the new tag:     &lt;code&gt;YOUR_DOCKERHUB_USERNAME/clickhouse-server:sharded&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Save Changes&lt;/strong&gt;.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmmxg7kkf9yepibwzackn.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fmmxg7kkf9yepibwzackn.png" alt="Change Name"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;StackPath will start recycling the containers. When this is done, you should find all the containers running the newest version:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2xlsbbpj211xugcfk2rz.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2xlsbbpj211xugcfk2rz.png" alt="Public IP 4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the Cluster
&lt;/h3&gt;

&lt;p&gt;Again, we can use Curl or any browser to check each of the ClickHouse nodes. Just replace the IP addresses with your Private IPs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; YOUR_PUBLIC_IP_1:8123/ping
Ok

curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; YOUR_PUBLIC_IP_2:8123/ping
Ok

curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; YOUR_PUBLIC_IP_2:8123/ping
Ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also can connect to any of the instances to check the cluster health.&lt;br&gt;
Run the following query to see the cluster status.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; from system.clusters&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; yandex/clickhouse-client &lt;span class="nt"&gt;--host&lt;/span&gt; YOUR_PUBLIC_IP &lt;span class="nt"&gt;--user&lt;/span&gt; default &lt;span class="nt"&gt;--password&lt;/span&gt; YOUR_PASSWORD

ClickHouse client version 20.3.2.1 &lt;span class="o"&gt;(&lt;/span&gt;official build&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Connecting to 151.139.31.12:9000 as user default.
Connected to ClickHouse server version 20.3.2 revision 54433.

clickhouse-servers-clickhouse-servers-jfk-0 :&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; from system.clusters&lt;span class="p"&gt;;&lt;/span&gt;

SELECT &lt;span class="k"&gt;*&lt;/span&gt;
FROM system.clusters

┌─cluster────────────┬─shard_num─┬─shard_weight─┬─replica_num─┬─host_name───┬─host_address─┬─port─┬─is_local─┬─user────┬─default_database─┬─errors_count─┬─estimated_recovery_time─┐
│ my_sharded_config  │         1 │            1 │           1 │ 10.128.96.2 │ 10.128.96.2  │ 9000 │        0 │ default │                  │            0 │                       0 │
│ my_sharded_config  │         2 │            1 │           1 │ 10.128.96.4 │ 10.128.96.4  │ 9000 │        1 │ default │                  │            0 │                       0 │
│ my_sharded_config  │         3 │            1 │           1 │ 10.128.96.3 │ 10.128.96.3  │ 9000 │        0 │ default │                  │            0 │                       0 │
└────────────────────┴───────────┴──────────────┴─────────────┴─────────────┴──────────────┴──────┴──────────┴─────────┴──────────────────┴──────────────┴─────────────────────────┘

3 rows &lt;span class="k"&gt;in &lt;/span&gt;set. Elapsed: 2.140 sec.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it!  If you made it this far, you have successfully deployed a 3-node cluster on StackPath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;For the next step in your road to mastering ClickHouse, try setting up a second cluster on a different PoP and establishing &lt;a href="https://clickhouse.tech/docs/en/operations/table_engines/replication/" rel="noopener noreferrer"&gt;table replication&lt;/a&gt; between the two data centers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you want to learn more, check out these related docs and projects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.stackpath.com/containers/" rel="noopener noreferrer"&gt;What are Containers?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Clickhouse &lt;a href="https://clickhouse.tech/docs/en/query_language/" rel="noopener noreferrer"&gt;SQL
Reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Clickhouse &lt;a href="https://clickhouse.tech/docs/en/operations/" rel="noopener noreferrer"&gt;Operations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Clickhouse official
&lt;a href="https://github.com/housepower/ClickHouse-Native-JDBC" rel="noopener noreferrer"&gt;JDBC&lt;/a&gt; and
&lt;a href="https://github.com/ClickHouse/clickhouse-odbc" rel="noopener noreferrer"&gt;ODBC&lt;/a&gt; drivers.&lt;/li&gt;
&lt;li&gt;Clickhouse &lt;a href="https://clickhouse.tech/docs/en/interfaces/third-party/client_libraries/" rel="noopener noreferrer"&gt;third party
drivers&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>docker</category>
      <category>clickhouse</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Optimizing First-Frame Bitrate for HLS with Serverless Edge Compute</title>
      <dc:creator>Robert Gibb</dc:creator>
      <pubDate>Wed, 18 Dec 2019 20:25:01 +0000</pubDate>
      <link>https://dev.to/stackpath/optimizing-first-frame-bitrate-for-hls-with-serverless-edge-compute-jmi</link>
      <guid>https://dev.to/stackpath/optimizing-first-frame-bitrate-for-hls-with-serverless-edge-compute-jmi</guid>
      <description>&lt;p&gt;In this article we’ll show you how to use StackPath’s &lt;a href="https://www.stackpath.com/products/edge-computing/serverless-scripting/" rel="noopener noreferrer"&gt;serverless edge product&lt;/a&gt; with HLS to deliver the right bitrate to the right device with the lowest possible delay. Whether you’re using cloud serverless for HLS or a different streaming solution entirely, this article will introduce you to the possibilities of optimizing streams with serverless scripting and low-latency edge compute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This tutorial is detailed and requires considerable effort to follow, but completing it can significantly reduce content delivery costs for you and buffer time for your users. It could be the catalyst for inspiring an evolution of how your video content is consumed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When responding to an HLS request, the streaming server determines which video quality (i.e. ts file) the client will attempt to play before switching to a lower or higher quality video. This switch depends on available bandwidth and device type.&lt;/p&gt;

&lt;p&gt;Switching to the wrong quality first degrades the user experience considerably. For instance, sending a high quality file to a low-end device may cause the video/device to freeze, even on a good connection. And sending a low quality file to a high-end device with a good connection may cause the viewer to experience a low quality viewing experience for a prolonged period.&lt;/p&gt;

&lt;p&gt;It may seem that sending a medium quality file first is a good solution, but it’s actually quite lazy. Instead, you can solve for the best solution in every case by using serverless scripting.&lt;/p&gt;

&lt;p&gt;Serverless scripting, also known as function-as-a-service (FaaS), allows you to optimize responses on a per-device, per-request basis without touching your origin server’s configuration or code. There’s also no loss of cacheability and you can decrease latency further by making the decision at the edge instead of in a far-off data center.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding how HLS works
&lt;/h3&gt;

&lt;p&gt;For developers who are not already aware, HLS works by sending the client an index, also known as a manifest. This index contains a list of ts files called chunks that make up the video. The client requests the appropriate chunk based on play time.&lt;/p&gt;

&lt;p&gt;In an adaptive bitrate implementation, the manifest provides links to several alternative playlists called variants instead of listing chunks directly. All variants have an identical number of chunks and video content, but they differ in bitrate and resolution (i.e. quality).&lt;/p&gt;

&lt;p&gt;According to the HLS spec, clients should request the top listed quality in the manifest, play it, and calculate the available bandwidth (i.e. chunk size and download time). Based on the calculation, the client switches to a higher or lower quality until the bitrate of the video is lower than the maximum available bandwidth. This ensures seamless play.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Falternative_hls_index_files.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Falternative_hls_index_files.png" alt="Sample HLS index listing three variants"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(The &lt;strong&gt;image&lt;/strong&gt; above is a sample HLS index listing three variants, each with a different bandwidth, resolution, and index file.)&lt;/p&gt;

&lt;p&gt;To summarize, choosing the video quality based on the available bandwidth is the responsibility of the client, but choosing the default quality to start with is the responsibility of the server. This default quality is usually either the highest one, or simply the first one uploaded to the server. In all cases, it is the same regardless of which device requests the video and where the video is requested from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the problems with HLS
&lt;/h3&gt;

&lt;p&gt;We’ve established that the default video quality isn’t often the best to include in the initial response with HLS, but let’s look at some different scenarios to further understand why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario #1:&lt;/strong&gt; High-End Device | High Quality Video | Low Bandwidth | Any Screen Size&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_scenario_1.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_scenario_1.png" alt="HLS scenario with a high-end device and low bandwidth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this scenario, the device downloads the master playlist (playlist.m3u8) and starts with best quality as dictated by the server. The device downloads the first chunk and it takes a whole minute (62,352ms). During this time, the viewer is waiting, not watching anything. This is a terrible video streaming experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario #2:&lt;/strong&gt; High-End Device | Medium Quality Video | Medium Bandwidth | Small Screen&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_scenario_2.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_scenario_2.png" alt="HLS scenario with a high-end device and medium bandwidth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this scenario, the device spends seven seconds downloading a 10-second second chunk. After this, it starts to play.&lt;/p&gt;

&lt;p&gt;So we’ve fixed the problem in Scenario #1 but we still waited seven seconds for the video to start. But we’re playing in a 800x450 pixel player and medium quality has a resolution of 1280x720 with a bitrate to match. Therefore, low quality is beyond enough here and waiting seven seconds for a higher quality file is unnecessary. If we started with a screen-appropriate quality we’d risk less delay before optimization catches up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario #3:&lt;/strong&gt; High-End Device | Medium Quality Video | High Bandwidth | Big Screen&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_scenario_3.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_scenario_3.png" alt="HLS scenario with high-end device and high bandwidth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this scenario, the device downloads the initial ts file quickly and, upon downloading, plays 10 seconds of medium quality video before switching to high quality.&lt;/p&gt;

&lt;p&gt;So we’ve eliminated the problem in Scenario #2 but we still had to tolerate 10 seconds of lower quality video before optimizing up. And the next time we load another video (even on the same site), we’re going to go through all of this again. It would be better if we could remember the current quality and start with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; There’s an edge case scenario containing a low-end device and high-quality start but there’s not much to expand on. As you might expect, the video starts to stutter and lag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solutions for HLS problems
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Client-side solutions
&lt;/h4&gt;

&lt;p&gt;Naturally, all these scenarios are much better solved for on the client side. For example, VideoJS, one of the most popular libraries for HLS playback on the web, will immediately start with the highest quality whose resolution fits the current player size, eliminating one of the issues above.&lt;/p&gt;

&lt;p&gt;But not all players do this. For instance, &lt;a href="https://developer.android.com/guide/topics/media/exoplayer" rel="noopener noreferrer"&gt;ExoPlayer&lt;/a&gt;, Google's HLS player on Android devices, performs no such capping. However, it does let developers customize its selection logic to fit their use case.&lt;/p&gt;

&lt;h4&gt;
  
  
  Server-side solutions with serverless
&lt;/h4&gt;

&lt;p&gt;The vast majority of media servers make use of a static master playlist file and custom development is often required to create an adaptive system. But even if you have the logic for changing the order of file delivery at your origin server based on device type, for instance, what do you do with your CDN? Cache the generated master playlist for each one of hundreds of different user agents? With the request sent all the way to origin every time you see a new one? Or perhaps you don’t cache the master playlist and delegate processing to your origin completely? Either way, you’re still slowing everything down and adding a excessive load to your origin.&lt;/p&gt;

&lt;p&gt;Edge serverless solves this by allowing you to implement custom logic right at the edge. Moreover, this approach is backend agnostic. A serverless script can manipulate the fetched resources as a man-in-the-middle with zero reliance on any feature of the backend system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objective
&lt;/h2&gt;

&lt;p&gt;This tutorial will show you how to use serverless scripting to optimize HLS quality by device at request time, at the edge server, without any contribution from your origin server or any effect on cacheability.&lt;/p&gt;

&lt;p&gt;Serverless scripting allows you to write custom logic (implemented by js code) to process requests at the edge server. It can modify a client's request before fetching it and also modify the response. The possibilities with this serverless use case are endless and, therefore, the techniques in this tutorial serve as more of a demonstration than a definitive solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1) An origin serving a multi-quality HLS stream.&lt;/strong&gt; This may be powered by nginx-rtmp, Mist, Wowza, Nimble, or any form of media server. As an example we'll use the url &lt;code&gt;http://awesomeMedia/&lt;/code&gt;, serving an HLS stream at &lt;code&gt;http://awesomeMedia/hls.m3u8&lt;/code&gt;. Here is the example playlist served at that URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=842x480
480p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p.m3u8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally an encoder in your media pipeline will take care of auto generating this. In the file above, we are serving four different qualities at 1080p, 720p, 480p, and 360p—each with a defined bitrate appropriate to the resolution, as defined in the #EXT-X-STREAM-INF lines above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) A &lt;a href="https://control.stackpath.com/register/" rel="noopener noreferrer"&gt;StackPath account&lt;/a&gt; with serverless scripting enabled.&lt;/strong&gt; Refer to &lt;a href="https://support.stackpath.com/hc/en-us/articles/360001455123" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt; for instructions on enabling serverless for your site for $10/month. Alternatively, you can test the concept for free using &lt;a href="https://sandbox.edgeengine.io/" rel="noopener noreferrer"&gt;the Sandbox&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set up your site with serverless
&lt;/h2&gt;

&lt;p&gt;The first step is to sign up for a StackPath account, add your site, and enable Serverless Scripting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1)&lt;/strong&gt; Sign up for a StackPath account &lt;a href="https://control.stackpath.com/register/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Then select &lt;strong&gt;Website &amp;amp; Application Services&lt;/strong&gt; and choose the services you require.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fchoose_stackpath_service.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fchoose_stackpath_service.png" alt="StackPath control panel page showing various services"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Edge Delivery Bundle&lt;/strong&gt; is recommended but the bare minimum is the &lt;strong&gt;CDN&lt;/strong&gt; service.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fchoose_edge_delivery_service.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fchoose_edge_delivery_service.png" alt="StackPath control panel page showing various edge delivery services"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2)&lt;/strong&gt; Log in to the StackPath Control Panel and create/select an active Stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3)&lt;/strong&gt; Select &lt;strong&gt;Sites&lt;/strong&gt; in the left-hand navigation bar and click &lt;strong&gt;Create Site&lt;/strong&gt;.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_create_site.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_create_site.png" alt="Sites overview in StackPath CP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4)&lt;/strong&gt; Check &lt;strong&gt;Serverless Scripting&lt;/strong&gt;, enter your domain (or IP address), and select &lt;strong&gt;Continue&lt;/strong&gt;.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_create_site_1.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_create_site_1.png" alt="Create sites page in StackPath CP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5)&lt;/strong&gt; Finally, take note of your site’s edge address, which you can now use for delivery of your content all over the globe. Alternatively, you can use the StackPath DNS service to have your domain name resolve to the ideal edge and always serve content from StackPath’s CDN.&lt;/p&gt;

&lt;p&gt;In this example, our HLS video is now accessible at &lt;code&gt;http://j3z000b6.stackpathcdn.com/HLS.m3u8&lt;/code&gt; with the &lt;code&gt;.ts&lt;/code&gt; chunks available on the same path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create your script
&lt;/h2&gt;

&lt;p&gt;Now that we have a site with Serverless Scripting enabled we can deploy our first script. We can do this manually through the &lt;a href="https://control.stackpath.com/" rel="noopener noreferrer"&gt;web control panel&lt;/a&gt; or through the &lt;a href="https://github.com/stackpath/serverless-scripting-cli" rel="noopener noreferrer"&gt;Serverless Scripting CLI&lt;/a&gt;. The web option is suitable for testing or manual deployment. It’s simple, intuitive, and comes with a nice web editor. But if you’re developing with your own tools or integrating with a CI/CD pipeline you’ll appreciate the ease and automatability of a CLI single-line deployment.&lt;/p&gt;

&lt;p&gt;In this step our objective is to deploy a script to process requests for the path &lt;code&gt;http://awesomeMedia/HLS.m3u8&lt;/code&gt;. When a user visits the site, rather than fetching the file HLS.m3u8 straight from cache (or origin), the serverless engine will execute the script to decide what happens. The script could choose to block the request, deliver the file normally, modify the file, or write a new response altogether.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Using the control panel
&lt;/h3&gt;

&lt;p&gt;Navigate to the control panel and click &lt;strong&gt;Scripts&lt;/strong&gt;. Then click the &lt;strong&gt;Add Script&lt;/strong&gt; button. You’ll be taken straight to the code editor. Enter a name for your script and the route it will run for. You can also enter multiple routes, or use wildcards.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_serverless_scripts_cp.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_serverless_scripts_cp.png" alt="StackPath serverless scripts CP page"&gt;&lt;/a&gt;&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fcreate_serverless_script_stackpath_cp.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fcreate_serverless_script_stackpath_cp.png" alt="Enter script name in StackPath cp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Using the serverless CLI
&lt;/h3&gt;

&lt;p&gt;To use the Serverless CLI we’ll have to install the CLI package, create a configuration file named &lt;code&gt;sp-serverless.json&lt;/code&gt; in the project directory, and publish. But first we’ll need an API key and ID information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1)&lt;/strong&gt; Using the dashboard, take note of your &lt;strong&gt;Stack ID&lt;/strong&gt; and &lt;strong&gt;Site ID&lt;/strong&gt;.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_stack_id_site_id.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_stack_id_site_id.png" alt="StackPath site id and stack id"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2)&lt;/strong&gt; To generate API keys for use by the CLI, go to &lt;strong&gt;API Management&lt;/strong&gt; in the dashboard and click &lt;strong&gt;Generate Credentials&lt;/strong&gt;. Then record the &lt;strong&gt;Client ID&lt;/strong&gt; and &lt;strong&gt;API Client Secret&lt;/strong&gt;.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_api_management_cp.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_api_management_cp.png" alt="StackPath API management"&gt;&lt;/a&gt;&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_api_client_secret.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_api_client_secret.png" alt="StackPath API client secret"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3)&lt;/strong&gt; Install the CLI by running the following command in a terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g @stackpath/serverless-scripting-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4)&lt;/strong&gt; To configure the CLI, run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sp-serverless auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will set up authentication for the CLI to use your account. It will ask you for the details above interactively.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_cli_authentication.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_cli_authentication.png" alt="StackPath client terminal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5)&lt;/strong&gt; Clone the skeleton repository by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/stackpath/serverless-scripting-examples/tree/master/hls-initialization.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or create your files manually. If you do this, create a directory for the project. Then create the following two files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A file named &lt;code&gt;sp-serverless.json&lt;/code&gt; with the following content. Substitute your Stack ID and Site ID where indicated.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "stack_id": "",
  "site_id": "",
  "scripts": [
    {
      "name": "HLS Optimizer",
      "paths": [
        "HLS.m3u8"
      ],
      "file": "default.js”
     }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file indicates the deployment details to the Serverless CLI. The paths array indicates the routes your script will be active for and multiple scripts/paths can be configured using the same file. You can add extra scripts for extra routes by copying the first object in the scripts array and modifying as needed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A file named &lt;code&gt;default.js&lt;/code&gt; with the following content. This will be the script you’ll deploy. For now it’s the default skeleton script and has no effect on response.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;addEventListener("fetch", event =&amp;gt; {
  event.respondWith(handleRequest(event.request));
});
/**
 * Fetch and return the request body
 * @param {Request} request
 */
async function handleRequest(request) {
  try {
    /* Modify request here before sending it with fetch */
    let response = await fetch(request);

    /* Modify response here before returning it */
    return response;
  } catch (e) {
    return new Response(e.stack || e, { status: 500 });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6)&lt;/strong&gt; Finally, deploy by issuing the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sp-serverless deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the command to run every time you update your script or make changes to its configuration in the &lt;code&gt;sp-serverless.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;From this point forward this is a development tutorial. You can skip this and use the finished product by going directly to Step 6 at the end of the tutorial, with one caveat: you’ll need to deploy a container as detailed in Step 3. Alternatively, if you’d like to follow along without creating a StackPath account you can use &lt;a href="https://sandbox.edgeengine.io/" rel="noopener noreferrer"&gt;the Sandbox&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Parse the HLS master playlist
&lt;/h2&gt;

&lt;p&gt;If using the Sandbox, start by filling in the origin route you’d like to apply the script to in the URL textbox (e.g. &lt;code&gt;http://awesomeMedia/HLS.m3u8&lt;/code&gt;) and select &lt;strong&gt;Raw&lt;/strong&gt; as the display mode (optional). The Sandbox provides console access, so that you can issue console.log commands and view stack traces live. Other options for &lt;a href="https://developer.stackpath.com/docs/en/EdgeEngine/debug/" rel="noopener noreferrer"&gt;debugging serverless scripts&lt;/a&gt; are to return information in the body of the response, or in its headers.&lt;/p&gt;

&lt;p&gt;The default (skeleton) script just returns the original manifest as is. The essential ingredient is simply a request handler bound to fetch that receives the original request from the client, fetches the response, and returns it untouched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;addEventListener("fetch", event =&amp;gt; {
  event.respondWith(handleRequest(event.request));
});

/**
 * Fetch and return the request body
 * @param {Request} request
 */
async function handleRequest(request) {
  try {
    /* Modify request here before sending it with fetch */

    let response = await fetch(request);

    /* Modify response here before returning it */

    return response;
  } catch (e) {
    return new Response(e.stack || e, { status: 500 });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is helpfully marked with suggested locations for modifying the request, the response, and returning them.&lt;/p&gt;

&lt;p&gt;First, let's pass the request upstream and examine the response before returning it.&lt;/p&gt;

&lt;p&gt;Modify the script as shown below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Modify response here before returning it */
const upstreamContent = await response.text();
/* Log the response body to the console */
console.log(upstreamContent)

return new Response(upstreamContent, {
    status: response.status,
    statusText: response.statusText,
    headers: response.headers
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After clicking &lt;strong&gt;Run Script&lt;/strong&gt; we are greeted with the result in the console, showing the contents of the master playlist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[info] #EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=842x480
480p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p.m3u8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We’ve changed the original line returning the response in the code. Rather than simply returning the same response object, we’re generating a new one with the same header and content. This is because attempting to return the original object after having already resolved the promise of text() will result in an error.&lt;/p&gt;

&lt;p&gt;We now have the HLS master playlist in a variable so it’s time to parse it for available variants (qualities).&lt;/p&gt;

&lt;p&gt;For a master playlist with several variants, the playlist starts with several “header” tags, then defines every variant in a line beginning with “EXT-X-STREAM-INF”. More information can be found in the &lt;a href="https://tools.ietf.org/html/rfc8216#section-8.4" rel="noopener noreferrer"&gt;HLS spec&lt;/a&gt;. But for the purposes of this script a &lt;a href="https://regexr.com/4lpoj" rel="noopener noreferrer"&gt;regex solution&lt;/a&gt; is ideal, as implemented in this new function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function parseM3u8(body) {
  // Parse M3U8 manifest into an object array, containing bitrate, resolution, and codec
  var regex = /^#EXT-X-STREAM-INF:BANDWIDTH=(\d+)(?:,RESOLUTION=(\d+x\d+))?,?(.*)\r?\n(.*)$/gm;
  var qualities = [];
  while ((match = regex.exec(body)) != null) {
    qualities.push({bitrate: parseInt(match[1]), resolution: match[2], playlist: match[4], codec: match[3]});
  }
  return qualities;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function continues to look for #EXT-X-STREAM-INF lines and extracts bandwidth, resolution, playlist link, and, finally, preserves any extra information found (usually codec information). To explore how it works, you can use a handy tool called RegExr.&lt;/p&gt;

&lt;p&gt;To test this function, we can add a console log line anywhere in the handler function:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;console.log(parseM3u8(upstreamContent))&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[info] [ { bitrate: 800000,
    resolution: '640x360',
    playlist: '360p.m3u8',
    codec: '' },
  { bitrate: 1400000,
    resolution: '842x480',
    playlist: '480p.m3u8',
    codec: '' },
  { bitrate: 2800000,
    resolution: '1280x720',
    playlist: '720p.m3u8',
    codec: '' },
  { bitrate: 5000000,
    resolution: '1920x1080',
    playlist: '1080p.m3u8',
    codec: '' } ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And…success! The test worked as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Parsing the User Agent
&lt;/h2&gt;

&lt;p&gt;By examining the headers of the request before sending it upstream we can find the user agent of the client browser. In the marked location of the skeleton script for modifying requests, we log the user agent by adding two lines of code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Modify request here before sending it with fetch */
const userAgent = request.headers.get('User-Agent');
console.log(userAgent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[info] Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aside from the original headers sent by the browser, there is a set of additional StackPath headers that are added to the original request. These contain the IP address, location, server region, and other information about the request. You can find a list of them in the &lt;a href="https://developer.stackpath.com/docs/en/EdgeEngine/request-header-variables/" rel="noopener noreferrer"&gt;StackPath Developer Docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In order to make meaningful decisions about which quality to prioritize and which video resolutions to send, there are three main pieces of information we need to learn about from the client device:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type (desktop/phone/tablet)&lt;/li&gt;
&lt;li&gt;Screen resolution&lt;/li&gt;
&lt;li&gt;Power measurements to be used for deciding whether to cap at a certain quality for devices with poor processing capabilities &lt;strong&gt;(optional)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While there are many JavaScript libraries for parsing the user agent, none of them can tell us the screen resolution of a mobile device. Doing so not only requires a complicated parser that understands all the different ways UA strings are formed, but also a database with information about the devices once they’re identified. This parser and database combination is formally called a &lt;a href="https://en.wikipedia.org/wiki/Device_Description_Repository" rel="noopener noreferrer"&gt;Device Description Repository (DDR)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Today, there are plenty of commercial DDR services. But only one is both reliable and free: &lt;a href="http://openddr.mobi/" rel="noopener noreferrer"&gt;OpenDDR&lt;/a&gt;. This service can be deployed in the form of a docker container, providing a simple API that returns JSON-formatted data for a given UA that we will call from within our script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling the API in Serverless
&lt;/h3&gt;

&lt;p&gt;Before deploying OpenDDR, we’ll use the &lt;a href="http://openddr.demo.jelastic.com/servlet/" rel="noopener noreferrer"&gt;demo endpoint&lt;/a&gt; to develop the necessary function in our script and test the integration. The function fires a new http request to the API, parses the response, and passes the returned information to the main handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; /* Modify request here before sending it with fetch */
    const userAgent = request.headers.get('User-Agent');
    console.log(userAgent);
    /* Consult the DDR microservice in regards to the user agent */
    const deviceData = await getDeviceData(userAgent);
    console.log(deviceData);

async function getDeviceData(ua) {
  try {
    const res = await fetch('http://openddr.demo.jelastic.com/servlet/classify?ua='+ua);
    const data = await res.json();
    return data.results.attributes;
  } catch (e) {
    throw new Error('DDR communication failed')
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[info] Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36
[info] { model: 'Pixel XL',
  ajax_support_getelementbyid: 'true',
  marketing_name: 'Pixel XL',
  is_bot: 'false',
  from: 'openddr',
  displayUnit: 'pixel',
  displayWidth: '1440',
  device_os: 'Android',
  id: 'PixelXL',
  xhtml_format_as_attribute: 'false',
  dual_orientation: 'true',
  nokia_series: '0',
  device_os_version: '8.0',
  nokia_edition: '0',
  vendor: 'Google',
  cpu: 'Qualcomm Snapdragon 821',
  mobile_browser_version: '67.0.3396.87',
  ajax_support_events: 'true',
  is_desktop: 'false',
  cpuRegister: '64-Bit',
  image_inlining: 'true',
  ajax_support_inner_html: 'true',
  ajax_support_event_listener: 'true',
  mobile_browser: 'Chrome',
  ajax_manipulate_css: 'true',
  displayHeight: '2560',
  cpuCores: 'Quad-Core',
  is_tablet: 'false',
  memoryInternal: '32/128GB, 4GB RAM',
  inputDevices: 'touchscreen',
  ajax_support_javascript: 'true',
  cpuFrequency: '2150 MHz',
  is_wireless_device: 'true',
  ajax_manipulate_dom: 'true',
  is_mobile: 'true',
  xhtml_format_as_css_property: 'false' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response from the DDR contains more info than the UA itself has. In particular, we are interested in displayWidth and displayHeight, but the information in there is pretty extensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For a desktop (or laptop) device, the display resolution is still rarely identifiable using this method. For example, the UA for Google Chrome 77.0 on Windows 10 is the same regardless of your display. Also, viewers may rarely play the video in full screen depending on the type of content you’re serving.&lt;/p&gt;

&lt;p&gt;Deploying a container&lt;/p&gt;

&lt;p&gt;The function above is currently making use of the demo API endpoint offered on the OpenDDR website and is unsuitable for production use. Therefore, we’re going to be deploying our own instance using an &lt;a href="https://www.stackpath.com/products/edge-computing/containers/" rel="noopener noreferrer"&gt;Edge Container&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;StackPath’s Edge Containers offer a way of hosting containers in diverse locations so that no request has to travel all the way to a centralized computing node. This is particularly useful for stateless applications such as the one we’re deploying. (A more detailed tutorial can be found &lt;a href="https://support.stackpath.com/hc/en-us/articles/360022756051-Getting-Started-With-StackPath-Edge-Computing" rel="noopener noreferrer"&gt;here&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;To start, head back to the control panel and click &lt;strong&gt;Workloads&lt;/strong&gt;. Then click &lt;strong&gt;Continue&lt;/strong&gt; to add Edge Compute to your Stack.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fedge-compute-workload.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fedge-compute-workload.png" alt="StackPath edge compute workloads"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, click &lt;strong&gt;Create Workload&lt;/strong&gt;.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fedge-compute-workload-1.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fedge-compute-workload-1.png" alt="StackPath edge compute workloads"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can configure your container:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: (Any name works)&lt;/li&gt;
&lt;li&gt;Image: Enter &lt;strong&gt;0x41mmar/openddr-api:latest&lt;/strong&gt;, which refers to &lt;a href="https://hub.docker.com/r/0x41mmar/openddr-api" rel="noopener noreferrer"&gt;this container&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Anycast IP: Disabled for now (more on this below)&lt;/li&gt;
&lt;li&gt;Public Ports: Enter 8080, the only port exposed by the container image above&lt;/li&gt;
&lt;li&gt;Spec: The resources allocated to each instance. SP-1 (1 vCPU, 2GB Mem) should be enough.&lt;/li&gt;
&lt;li&gt;Deployment Target: Locations where the container will be deployed. Select a name and one or more locations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unlike the ephemeral IPs assigned to containers when they’re started, the Anycast IP is a static IP assigned to the workload for its entire lifetime. The Anycast IP has significant performance benefits. Traffic towards an Anycast IP will enter the StackPath Network at the closest Edge Location and be routed to the instance using [StackPath’s private backbone(&lt;a href="https://blog.stackpath.com/network-backbone-speed/" rel="noopener noreferrer"&gt;https://blog.stackpath.com/network-backbone-speed/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Finally, click &lt;strong&gt;Create Workload&lt;/strong&gt;. You’ll be redirected to the container’s overview page where you’ll see the container starting. When it’s fired up, you’ll see the IP addresses associated with it. Make note of the public IP.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_instance.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fstackpath_instance.png" alt="StackPath instances"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, let’s replace the demo URL in the classifier function with our own container’s, using the IP above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const res = await fetch('http://101.139.43.73:8080/classify?ua='+ua);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Decision Tree
&lt;/h2&gt;

&lt;p&gt;The information required to make a decision about the optimization has been collected. Now the objective is to decide on the order of variants in the master playlist. We’ll create a simple algorithm to determine the best way to sort the variants in the file based on the following rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Never start with a variant whose resolution is higher than the display, even if the device will optimize up afterwards.&lt;/li&gt;
&lt;li&gt;If the best quality variant satisfying condition (1) has a relatively high bitrate (&amp;gt;=4 Mbps), start one step lower.&lt;/li&gt;
&lt;li&gt;If device is quite old/low-end, start at the lowest quality and cap to display resolution to avoid performance issues.&lt;/li&gt;
&lt;li&gt;If nothing is known for sure (desktop devices), assume resolution is appropriate for 720p and apply the rules above.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rule 4 makes sense because, according to &lt;a href="https://gs.statcounter.com/screen-resolution-stats/desktop/worldwide/#monthly-201808-201908-bar" rel="noopener noreferrer"&gt;StatCounter&lt;/a&gt;, at least 52.91% of desktop/laptop devices in use today have a display above 720p, but only 19% have 1080p displays.&lt;/p&gt;

&lt;p&gt;The resulting algorithm is shown in the diagram below. Note that this is not a definitive solution and can be changed easily to fit your unique scenario.&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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_algorithm_diagram.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%2Fblog.stackpath.com%2Fwp-content%2Fuploads%2F2019%2F11%2Fhls_algorithm_diagram.png" alt="HLS algorithm diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is straightforward to implement in JavaScript, as is the execution of the sorting and capping. This translates into the following function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function decisionTree(deviceData, qualities) {
  /* Logic deciding on ordering and capping of available qualities */
  /* Returns config object to the spec of the output function above */

  // get higher dimension of resolution
  var res = Math.max.apply({},[deviceData.displayHeight,deviceData.displayWidth]);
  //is it a desktop device? assume 1280x720
  if (deviceData.is_desktop == "true")
    return {top: 2, cap: false, res: 1280};
  else {
    // mobile device. Is it an old device?
    if ((deviceData.device_os == "iOS" &amp;amp;&amp;amp; parseInt(deviceData.device_os_version) &amp;lt; 7) || (deviceData.device_os == "Android" &amp;amp;&amp;amp; parseInt(deviceData.device_os_version) &amp;lt; 6) || (deviceData['release-year'] &amp;lt; 2012))
return {top: -1, cap: true, res: res};
   else
     return {top: 2, cap: false, res: res};
  }

  //default
  return {top: 2, cap: false, res: res}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All we have to return is the assumed display resolution, our order decision, and capping decision. The code is more or less a direct implementation of the graph above. One thing to note is the nature of variables returned by OpenDDR, as most of them are strings and require some conversion.&lt;/p&gt;

&lt;p&gt;More complex decision making is easy to implement in much the same way. For example, Apple provides guidelines on bitrates for particular resolutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Output and Putting All Together
&lt;/h2&gt;

&lt;p&gt;Now we need to rewrite the master playlist from scratch using the available information and decisions from the decision tree above. The function below is extensively commented and easily readable, but is basically sorting qualities, moving the required quality to the top of the list, and, if needed, deleting one that are too high.&lt;/p&gt;

&lt;p&gt;First, we check if certain qualities need to be removed by checking the boolean config.cap. If some do, we filter the qualities array to remove offending variants unless there is only one of them.&lt;/p&gt;

&lt;p&gt;Second, we sort the available variants in descending order by bitrate, unless it is required to place the lowest one on top (config.top=-1). If the top quality is to be the first, we’re done. If we’re to apply the 4Mbps rule, then we progressively test qualities for the required conditions (&amp;lt;=display, &amp;lt;4Mb).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function writeSortedManifest(qualities, config) {
  /* Sort qualities, optionally cap at a certain resolution, */
  /* then rewrite into correct HLS master playlist syntax */
  /* config = {cap: bool, top: int, res: int} */
  // top = 1: highest quality first, 2: highest quality within res or next one if &amp;gt;4Mbps, 0: middle quality first, -1: lowest quality first

  //cap
  //remove qualities with a resolution higher than a certain value (player resolution) 
  if (config.cap) {
    newQualities = qualities.filter((x)=&amp;gt; Math.max.apply({},x.resolution.split('x')) &amp;lt;= config.res );
  // anything left?
  if (newQualities.length &amp;gt; 0)
    qualities = newQualities;
  }

  // sort array so either best or worst quality is the first
  // dir = 1 for descending, -1 for ascending. use 1 for anything except top==-1
  // if top==-1, this is all we need to do
  var dir = config.top==-1 ? -1: 1;
  qualities.sort((a,b) =&amp;gt; (a.bitrate&amp;gt;b.bitrate)? -1*dir: dir)

  //if applying resolution rule (top==2), process from top to bottom to find variant satisfying conditions
  if (config.top==2) {
    for (var i in qualities) {
      // assume it's this one for now
      var topChoice = qualities[i];

      // convert "1280x720" to 1280; accomodate top dimension and the rest will be fine if using a sane aspect ratio
      var topDim = qualities[i].resolution.split('x').sort()[1];

      // For this variant:
      // is res &amp;lt;= display?
      if (topDim &amp;lt;= config.res) {
        // yes! but is bitrate &amp;lt;4Mbps?
        if (qualities[i].bitrate &amp;lt; 4000000) {
          // great! done here.
          break;
        }
        else if (qualities[i+1]) {
          //it's not, so choose next option if it exists
          topChoice = qualities[i+1];
          break;
        }
        else {
          // next option doesn't exist? ok, fine. We'll take this one
          break;
        }
      }
    }
  // Now let's move the top choice top
  qualities.splice(0,0,topChoice);
  }

  //if middle quality required to be the first, move it there
  if (config.top==0) {
    var m = Math.floor(qualities.length/2);
    var middleItem = qualities[m];
    qualities.splice(m,1);
    qualities.splice(0,0,middleItem); 
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going back to the main handler function, we just need to call the functions and pass the data as needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function handleRequest(request) {
  try {
    const userAgent = request.headers.get('User-Agent');
    var deviceData, response;
    [deviceData, response] = await Promise.all([getDeviceData(userAgent), fetch(request)]);
    const upstreamContent = await response.text();
    var variants = parseM3u8(upstreamContent);
    var config = decisionTree(deviceData, variants);
    var output = writeSortedManifest(variants, config)
    /* Return modified response */
    response.headers.set("Content-Length", output.length)
    return new Response(output, {
    status: response.status,
    statusText: response.statusText,
    headers: response.headers
    });
  } catch (e) {
    return new Response(e.stack || e, { status: 500 });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we’ve made sure to send the two external IO requests asynchronously. The DDR request has no dependency on the upstream fetch, and vice versa. This saves us time.&lt;/p&gt;

&lt;p&gt;One little quirk in this code is the line right before the return statement that calculates the new content-length. This is necessary because the length (in bytes) of the body may have changed after being regenerated due to the ambiguity of new line characters. Our generator function only uses new line characters (\n), but the original file may or may not have used carriage returns (\r). A simple alternative would be to preserve the original line breaks with a slight modification of the regex and generator function, and have the final line be the exact same length as the original.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Final Deployment and Testing
&lt;/h2&gt;

&lt;p&gt;If you’re using the web control panel to upload your code you can find the complete code on GitHub &lt;a href="https://github.com/stackpath/serverless-scripting-examples/tree/master/hls-initialization" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Or, if you’re using the CLI and have followed the instructions in Step 1, you’ll have cloned a repository that already contains this code. Simply modify your &lt;code&gt;sp-serverless.json&lt;/code&gt; file to use hls.js rather than &lt;code&gt;default.js&lt;/code&gt; and replace the URL in line 53 (getDeviceData) with that of your own openddr container, per Step 3.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "stack_id": "",
  "site_id": "",
  "scripts": [
    {
      "name": "HLS Optimizer",
      "paths": [
        "HLS.m3u8"
      ],
      "file": "hls.js"
     }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the file will have had a script ID added automatically if you’ve deployed before. If you have, be sure to leave it in, then simply enter the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sp-serverless deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it!&lt;/p&gt;

&lt;p&gt;With the script saved and deployed, we can now test the response with a variety of devices. The fastest way to do this is to use the Developer Tools available in your favorite browser. You can also use an extension/addon for the purpose of device switching.&lt;/p&gt;

&lt;p&gt;Here are the results for a few different devices:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Device&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;UA&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;First Variant&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Capping&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Comment&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows Laptop&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36&lt;/td&gt;
&lt;td&gt;720p&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Guessing display is 720p capable, and bitrate of 720p variant is 2.8Mbps, acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Pixel 2&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36&lt;/td&gt;
&lt;td&gt;720p&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Definitely 1080p capable, but we’re not risking that high a bitrate on first request, falling down to 720p&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Galaxy Ace 3&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Linux; Android 4.2.2; GT-S7275R Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.95 Mobile Safari/537.36&lt;/td&gt;
&lt;td&gt;360p&lt;/td&gt;
&lt;td&gt;360p is the only quality sent&lt;/td&gt;
&lt;td&gt;Old low-end device, anything more is quite wasted on it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTC One M8&lt;/td&gt;
&lt;td&gt;Mozilla/5.0 (Linux; Android 4.4.2; HTC6525LVW Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.59 Mobile Safari/537.36&lt;/td&gt;
&lt;td&gt;360p&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Old device now, but not low-end, and display is good. Starting low but sending all qualities.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This article demonstrates the potential for Serverless Scripting as a solution for HLS streaming optimization. The ideal solution, as always, remains proper optimization on the client side. But with so many different players implementing so many different behaviors a solution of this type can make a significant difference to the end viewer’s experience. Serverless Scripting makes this quite easy and accessible, particularly when compared to the alternative of dynamic processing at the origin.&lt;/p&gt;

&lt;p&gt;The decision algorithm implemented may or may not be fitting for a particular audience or system, but we hope that it is explained and demonstrated satisfactorily for any developer to modify it to fit her own needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taking this even further
&lt;/h3&gt;

&lt;p&gt;A number of improvements can be made to the script to make it even more powerful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cookies:&lt;/strong&gt; The entire problem of first-variant selection stems from the fact that the bandwidth is unknown on first play. But what if the viewer has just watched another stream from your site? Use cookies to remember what variant the client last settled on within a certain time window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side device detection:&lt;/strong&gt; If the client is viewing the video inside a web or phone app you control, a bit of JS can provide much better information than we can find with the user agent alone. It’s possible to have some JS on your website that writes this information in a cookie for the Serverless Script to consume, and thus mitigate the lack of information about desktop devices (for instance). Naturally, this kills the quality of being server-agnostic and not requiring any changes to your origin configuration or deployment, but the trade off can be useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Checking:&lt;/strong&gt; Upgrade the script to deal with various error scenarios. What if the DDR API is unresponsive? Information ambiguous? Decide on which assumptions to make and how to order variants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statistics:&lt;/strong&gt; The numbers used in the assumptions above (e.g. a high bitrate is 4Mbps) are based on industry recommendations and general experience, but proper statistics are lacking. For example, we could enhance the script by making more statistically sound assumptions about available bandwidth for a particular region—or even for a particular class of device in a particular region, and so on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Statistics II:&lt;/strong&gt; Following from the point above, we can use a microservice with a db (similar to the DDR one above) to keep track of which devices, IPs, and regions settle on certain qualities, or which have problems starting the stream. This is useful for studying your audience and optimizing your configuration, but bonus points if you use the information automatically while sorting variants to adjust the rules as needed. For instance, if we have enough data to see that iPhones on Vodaphone 4G in Manhattan settle on 2Mbps most often, just start them there. This takes the previous point to its logical conclusion, but beware of the &lt;a href="https://en.wikipedia.org/wiki/Law_of_large_numbers" rel="noopener noreferrer"&gt;law of large numbers&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>serverless</category>
    </item>
    <item>
      <title>How We Perform Frontend Testing on Our Customer Portal</title>
      <dc:creator>Thomas Ladd</dc:creator>
      <pubDate>Fri, 08 Nov 2019 17:56:35 +0000</pubDate>
      <link>https://dev.to/stackpath/how-we-perform-frontend-testing-on-our-customer-portal-985</link>
      <guid>https://dev.to/stackpath/how-we-perform-frontend-testing-on-our-customer-portal-985</guid>
      <description>&lt;p&gt;An effective automated testing strategy is crucial for ensuring teams can deliver quality updates to web applications quickly. We are lucky to have a lot of great options in the space right now for testing. However, with a lot of options comes the difficulty of sorting through which one(s) to pick. Then, once the tools are chosen, you need to decide when to use each one.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.stackpath.com"&gt;StackPath&lt;/a&gt; we're very happy with the level of confidence we've achieved in our customer portal. So, in this post, we will share the set of tools we use to test our customer portal and inspire confidence in its performance.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing Principles
&lt;/h1&gt;

&lt;p&gt;Before diving into specific tools, it’s worth thinking about what good tests look like. Prior to starting work on &lt;a href="https://control.stackpath.com/login"&gt;the customer portal&lt;/a&gt;, we wrote down the principles we wanted to follow when writing tests. Going through that process first helped us decide which tools to select.&lt;/p&gt;

&lt;p&gt;The four principles we wrote down (with a little bit of hindsight thrown in) are listed below.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Tests should be thought of as an optimization problem
&lt;/h3&gt;

&lt;p&gt;An effective testing strategy is about maximizing value (confidence in the application working) and minimizing cost (time spent maintaining tests and running tests). Questions we often ask when writing tests related to this principle are:&lt;/p&gt;

&lt;p&gt;What’s the possibility that this test actually catches a bug?&lt;br&gt;
Is this test adding value and does that value justify its cost?&lt;br&gt;
Could I derive the same level of confidence as I do from this test with another test that is easier to write/maintain/run?&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Avoid excessive mocking
&lt;/h3&gt;

&lt;p&gt;One of my favorite explanations of mocking is &lt;a href="http://blog.testdouble.com/posts/2018-03-06-please-dont-mock-me"&gt;Justin Searls’s talk at Assert.js 2018&lt;/a&gt;. He goes into a lot more detail and subtlety than I will here, but in the talk, he refers to mocking as punching holes in reality, and I think that’s a very instructive way of looking at mocks. While mocking does have a place in our tests, we have to weigh the reduction of cost the mock provides by making the test easier to write and run against the reduction in value caused by punching that hole in reality.&lt;/p&gt;

&lt;p&gt;Previously, engineers on our team relied heavily on unit tests where all child dependencies were mocked using &lt;a href="https://airbnb.io/enzyme/docs/api/shallow.html"&gt;enzyme’s shallow rendering API&lt;/a&gt;. The shallow rendered output would then be verified using &lt;a href="https://jestjs.io/docs/en/snapshot-testing"&gt;Jest snapshots&lt;/a&gt;. All of these sorts of tests followed a similar template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shallow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// Optionally interact with wrapper to get the component in a certain state&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toMatchSnapshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These sorts of tests punch a ton of holes in reality. You can pretty easily get to 100% test coverage with this strategy. The tests take very little thought to write, but without something testing all of the numerous integration points they provide very little value. The tests may all pass, but I’m not too sure if my app works or not. Even worse, all of the mocking has a hidden cost that pops up later.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Tests should facilitate refactoring—not make it more painful
&lt;/h3&gt;

&lt;p&gt;Tests like the one shown above make refactoring more difficult. If I notice I have the same repeated code over and over again and extract it to another component later, every test I had for components that use that new component will fail. The shallow rendered output is different; where before I had the repeated markup, now I have the new component.&lt;/p&gt;

&lt;p&gt;A more complicated refactoring that involves adding some components and removing others results in even more churn as I have to add new test files and remove others. Regenerating the snapshots is easy, but what value are these tests really providing me? Even if they could catch a bug, I’m more likely to miss it amongst the number of snapshot changes and just accept the newly generated ones without thinking too hard about it.&lt;/p&gt;

&lt;p&gt;So these sorts of tests don’t help much with refactoring. Ideally, no test should fail when I refactor and no user-facing behavior is changed. Conversely, if I do change user-facing behavior, at least one test should fail. If our tests follow these two rules, they are the perfect tool for ensuring I didn’t change any user-facing behavior while refactoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Tests should mimic how a user actually uses the application
&lt;/h3&gt;

&lt;p&gt;If I want my tests to only fail when user-facing behavior changes, it follows that my tests ought to interact with my application in the same sort of way an actual user would. For example, my tests should actually interact with form elements and type in input fields the same way a user would. They should never reach into a component and manually call lifecycle methods, set state, or anything else that is implementation-specific. Since the user-facing behavior is what I’m ultimately wanting to assert, it’s logical that the tests should be operating in a way that closely matches a real user.&lt;/p&gt;

&lt;h1&gt;
  
  
  Testing Tools
&lt;/h1&gt;

&lt;p&gt;Now that we’ve defined what our goals are for our tests, let’s look at what tools we ultimately chose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Typescript
&lt;/h3&gt;

&lt;p&gt;We use TypeScript throughout our codebase. Our backend services are written in Go and communicate using gRPC, which allows us to generate typed gRPC clients for use in our GraphQL server. The GraphQL server’s resolvers are typed using generated types from &lt;a href="https://github.com/dotansimha/graphql-code-generator"&gt;graphql-code-generator&lt;/a&gt;. Finally, our queries, mutations, and subscriptions components/hooks are also generated with full type coverage. End-to-end type coverage eliminates an entire class of bugs resulting from the shape of data not being what you expect. Generating types from schema and protobuf files ensures our entire system remains consistent across the stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jest (Unit Tests)
&lt;/h3&gt;

&lt;p&gt;We use &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; as our unit testing framework along with &lt;a href="https://testing-library.com/docs/react-testing-library/intro"&gt;@testing-library/react&lt;/a&gt;. In these tests, we test functions or components in isolation from the rest of the larger system. We typically test functions/components that are used frequently throughout the app and/or have a lot of different code paths that are difficult to target all of in an integration or end-to-end (E2E) test.&lt;/p&gt;

&lt;p&gt;For us, unit tests are about testing the fine-grained details. Integration and E2E tests do a good job of handling the broad strokes of the application generally working, but sometimes you need to make sure little details are correct and it would be too costly to write an integration test for each possible case.&lt;/p&gt;

&lt;p&gt;For instance, we want to ensure that keyboard navigation works for our dropdown select component, but we don’t need to verify every instance of it in our app. We test the behavior in depth in isolation so that we can just focus on higher-level concerns when testing the pages that use that component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cypress (Integration Tests)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt; integration tests are at the core of our testing suite. When we started building out the StackPath portal they were the first tests we wrote because they deliver a lot of value for fairly small cost. Cypress renders our whole app in a browser and runs through test scenarios. Our entire frontend is running exactly as it would for a user. The network layer, however, is mocked. Every network request that would go to our GraphQL server is instead mocked with fixture data.&lt;/p&gt;

&lt;p&gt;Mocking the network layer provides a number of benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tests are faster.&lt;/strong&gt; Even if your backend is super fast, the number of calls made for an entire test suite run add up. With the responses being mocked, they can return instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests are more reliable.&lt;/strong&gt; One of the difficulties with full E2E tests is accounting for variability in the network and stateful backend data. When every request is mocked that variability is gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard-to-replicate scenarios can be simulated with ease.&lt;/strong&gt; For instance, it would be difficult to reliably force calls to fail. If we want to test that our app responds correctly when a call fails, being able to force that failure is helpful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While mocking our entire backend may seem like a problem, all of our fixture data is typed using the same generated TypeScript types our app uses, so it is guaranteed to be at least structurally equivalent to what an unmocked backend would return. For most of our tests, we are happy with the tradeoff that mocking the network provides.&lt;/p&gt;

&lt;p&gt;The developer experience with Cypress is also really good. The tests run in the Cypress Test Runner which shows your tests on the left and your app running in a main iframe performing those tests. After a test run, you can highlight individual steps in your tests to see what your app was doing at that point. Since the test runner is itself running in a browser, you also have access to developer tools to help debug tests.&lt;/p&gt;

&lt;p&gt;Oftentimes when writing frontend tests it can take a lot of time to assess what a test is actually doing and what state the DOM is in at a particular point in the test. Cypress makes this part really easy because you can just see it happening right in front of you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C2wgKrph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.imgur.com/omzz4ZP.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C2wgKrph--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.imgur.com/omzz4ZP.gif" alt="Cypress test runner gif"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These tests exemplify a lot of our state testing principles. Cost to value ratio is favorable, the tests very closely mimic how an actual user interacts with the app, and the only thing being mocked is the network layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cypress (E2E Tests)
&lt;/h3&gt;

&lt;p&gt;Our E2E tests are also written in Cypress, but for these we do not mock the network (or anything else). Our tests run against our actual GraphQL server which communicates with actual instances of our backend services.&lt;/p&gt;

&lt;p&gt;E2E tests are immensely valuable because they can definitively tell you if something works or not. Nothing is being mocked, so it’s using the app exactly as a user would. E2E tests are higher cost as well though. They are slower, take more thought to prevent intermittent failures, and take more work to ensure your tests are always in a known state before running.&lt;/p&gt;

&lt;p&gt;Tests typically need to start from a known state, do some operations, and then arrive at some other known expected state. With the integration tests, this is easy to accomplish because the API calls are mocked and thus are the same every test run. For E2E tests, it’s more complicated because the backend storage now holds state which could be mutated as the result of a test. Somehow, you have to ensure that when you start a test, you’re in a known state.&lt;/p&gt;

&lt;p&gt;At the beginning of our E2E test run, we run a script that seeds a new account with new stacks, sites, workloads, monitors, etc by making API calls directly. Each test run operates on different instances of the data, but the test setup is identical. The seed script emits a file with the data our tests use when running (instance ids and domains mostly). This seed script is what allows us to get into a known state before running our tests.&lt;/p&gt;

&lt;p&gt;Since these E2E tests are higher cost, we write less of them than integration tests. We cover the critical functionality of our app: user registration/login, creating and configuring a site/workload, etc. From our extensive integration tests, we know that our frontend generally works, so these just need to ensure that there isn’t something slipping through the cracks when hooked up to the rest of the system.&lt;/p&gt;

&lt;h1&gt;
  
  
  Downsides to this multipronged testing strategy
&lt;/h1&gt;

&lt;p&gt;While we’ve been really happy with our tests and the general stability of our app, there are definitely downsides to going with this sort of multipronged testing strategy.&lt;/p&gt;

&lt;p&gt;First, it means everyone on the team needs to be familiar with multiple testing tools instead of just one. Everyone needs to know Jest, @testing-library/react, and Cypress. Not only do we have to know how to write tests in these different tools; we also have to make decisions all the time about which tool to use. Should I write an E2E test covering this functionality or is just writing an integration test fine? Do I need unit tests covering some of these finer-grain details as well?&lt;/p&gt;

&lt;p&gt;There is undoubtedly a mental load here that isn’t present if you only have one choice. In general, we start with integration tests as the default and then add on an E2E test if we feel the functionality is particularly critical and backend-dependent. Or we start with unit tests if we feel integration tests cannot reasonably cover the number of different details involved.&lt;/p&gt;

&lt;p&gt;We definitely still have some gray areas, but patterns start to emerge after going through this thought process enough times. For instance, testing form validation tends to be done in unit tests due to the number of different scenarios and everyone on the team is aware of that at this point.&lt;/p&gt;

&lt;p&gt;Another downside to this approach is that collecting test coverage, while not impossible, is more difficult. While chasing test coverage can result in bad tests just for the sake of making a number go up, it can still be a useful automated way of finding holes in your tests. The trouble with having multiple testing tools is that you have to combine test coverage to find out which parts of your app are truly not covered. It’s possible, but it’s definitely more complicated.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;While some challenges exist when using many different testing tools, each tool serves its purpose and we think is worth including in our overall testing strategy. When starting a new application, or adding tests to an existing one, integration tests are a great place to start. Adding some base-level E2E tests around absolutely critical functionality early on is a good idea as well.&lt;/p&gt;

&lt;p&gt;With those two pieces in place, you should be able to make changes to your application with pretty reasonable confidence. If you start to notice bugs creeping in, stop and assess what sort of tests could have caught those bugs and if it indicates a deficiency in the overall strategy.&lt;/p&gt;

&lt;p&gt;We definitely did not arrive at our current test setup overnight and it is something we expect to keep evolving as we continue growing. For the time being though, we feel good about our current approach to testing.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
