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.
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.
How Do You Implement a CDN?
For starters, you’ll want to list your requirements and constraints. At Harness, ours were:
- The CDN should serve multiple resource files for our NextGen UI service — js, css, images, and videos
- We should be able to run it on multiple environments, such as a publicly exposed production environment, but also our test/QA environment
- 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.
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.
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.
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:
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.
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.
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.
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.
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.
Even though customLanguages support made it feel easy, we didn’t go down this path.
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.)
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”
We came up with the solution in two steps.
worker-loader: workers loaded as Blobs.
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.
But we took this solution further—with a twist. We built workers, but with a separate webpack config.
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).
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.