How Harness Implemented CDN for Its UI Services

In this blog, we’ll walk through what a CDN is and how Harness implemented it into our environment.

Published on
12/16/22

With complex features and multiple collaborators, webapps have become increasingly harder to build and manage. As deployments increase in complexity and technology is more widespread throughout every industry, this efficiency is critical to organizations’ business objectives. Harness has managed this complexity by implementing a content delivery network (CDN) for our user interface (UI) Services. With a CDN, we ensure speed and stability for our platform.

In this blog, we’ll walk through what a CDN is and how Harness implemented it into our environment. We’ll discuss some of the challenges we ran into and how we addressed them.

What is a “CDN?” 

So what is a CDN? A CDN is simply a set of servers distributed across the globe. According to Gartner

Content delivery networks (CDNs) are a type of distributed computing infrastructure, where devices (servers or appliances) reside in multiple points of presence on multihop packet-routing networks, such as the Internet, or on private WANs. A CDN can be used to distribute rich media downloads or streams, deliver software packages and updates, and provide services such as global load balancing, Secure Sockets Layer acceleration and dynamic application acceleration via WAN optimization techniques.

Why Do You Need CDN Servers?

The use cases are simple:

  • Caching and faster loading: Resources are cached in a CDN and delivered from the servers geographically closest  to the user, so resources load faster.
  • Reduced load on application servers: Resources are cached and served from a CDN. This means the downstream app servers never receive requests for these static resources, which reduces the load.

For UI Services at Harness, without CDN, all the bundled files (including index.html, and other static files such as Javascript, styles, and images) come from the app server. But with CDN, only index.html comes from the app server. All the remaining static files come from the CDN server (here, static.harness.io).

CDNs improve efficiency

How Do You Implement a CDN?

For starters, you’ll want to list your requirements and constraints. At Harness, ours were:

  1. The CDN should serve multiple resource files for our NextGen UI service — js, css, images, and videos
  2. We should be able to run it on multiple environments, such as a publicly exposed production environment, but also our test/QA environment
  3. We should have a simple switch for CDN to turn it ON or OFF (in case things go wrong)

With this information documented, we started the implementation and ran into some challenges.

Challenges with CDN

Challenge 1: Run-time Versus Build-time Public Path

We used the same build for different environments and deploymentTypes (SaaS and on premise). We used Webpack for bundling and creating our build. 

Public Path helps us set the base path for all the assets within the application. In simpler terms, Public Path defines the host or directory that your assets should be served from.

Earlier, we used a build-time public path. Webpack has built-in support for defining a run-time  on-the-fly public-path, so we changed our code a bit. We added the hostname of our CDN server as the public path.

The name of the CDN server

Here static.harness.io is your CDN hostname. 

Challenge 2: Upload to GCS Step Versus gsutil

Our CDN is hosted on the Google Cloud Platform. We store our static assets in a Google Cloud Storage (GCS) bucket. We use Harness CD to deploy Harness.

Harness has built-in support for “Uploading Artifacts to GCS” — a basic, “quick-start” convenience step.

Step library

Our use case also included the need to:

  • Compress files (g-zip)
  • Add custom permissions on files being uploaded

To ensure that the latest bundled files are uploaded to GCS with the above use cases, instead of using the “Upload Artifacts to GCS” step, we use the “Run” step to add a custom script. In that script, we use gsutil

Run step in custom script

gsutil is a Python application created by Google that lets you access Cloud Storage from the command line. We used it to compress (g-zip) the bundled assets and set their permissions. Here’s a  snippet for reference:

Code snippet in Python

Challenge 3: Web Workers

We use Monaco Editor heavily for code-editing capabilities. If you have used Harness, chances are you have seen the Visual-vs-YAML view. The YAML view needs MonacoEditor.

YAML view

Monaco Editor uses Web Workers to run in a separate thread from the main JS thread.

However, web browsers don’t allow cross-domain web workers.

Web workers

This restriction means that if your website address starts with “app.harness.io,” but your Javascript files for MonacoEditor languages try to load from “static.harness.io” (CDN), web browsers will block them. Some MonacoEditor features, such as auto-complete, won’t work.

Trial and Error

We tried the following to fix this: monaco-editor-webpack-plugin and worker-loader

This monaco-editor-webpack-plugin plugin provides a publicPath similar to that of webpack, which tells which host to load the worker scripts from.

publicPath

But…it didn’t work for us. It didn’t “win over” Webpack’s run-time public path.

Next, we tried worker-loader. It was the same story here. It didn’t “win over” Webpack’s run-time public path either.

Webpack

We also thought of updating the version of monaco-editor-webpack-plugin, but we found that the new version was strongly tied to the version of MonacoEditor—we would have to update the version of both monaco-editor and monaco-editor-webpack-plugin. It would become a longer task.

Version matrix

Even though customLanguages support made it feel easy, we didn’t go down this path.

customLanguages

We then thought of configuring CDN to be behind “app.harness.io”—in other words, instead of the red path, take the green path. (Diagram below.)

CDN configuration

This won't work for vanity urls such as “customer.harness.io”, because the browser will still block worker calls since we configured CDN behind “app.harness.io

Solution

We came up with the solution in two steps.

worker-loader: workers loaded as Blobs.

worker-loader

But worker-loader is deprecated/archived in favor of webpack v5, so we didn’t use this.

We asked ourselves: What if we build worker files separately and manually try to change base-path? This required custom webpack configuration. A new "entry" in webpack, but it meant no lazy-loading of the monaco file.

The Monaco file

But we took this solution further—with a twist. We built workers, but with a separate webpack config.

Building workers

When referring to built files from within the component, we had to use current window.location. We got rid of the worker-loader and used recommended webpack v5 way for workers (using getWorker() and new Worker).

new Worker

There are potential issues, such as no hash in the bundled file for workers, so we added version “2” manually in filenames (filenames are editorWorker2 and yamlWorker2). If we ever need to update the worker files, we will need to manually change the version 2—which acts as our hash We are cognizant of this limitation, and we accepted it because automating this can be complex.

What’s Next for CDN and Harness? 

Solving complex challenges and ensuring uptime for thousands of users is no easy task. By implementing CDN, Harness has been able to increase speed and ensure uptime through this distributed network model.

Keep an eye out for part 2 of our CDN implementation where we’ll share how we implemented CDN for our Micro-frontend child apps.

The Modern Software Delivery Platform™

Loved by Developers, Trusted by Businesses
Get Started

Need more info? Contact Sales