Skip to content

LGUG2Z/microfest

Repository files navigation

Microfest

Manifest and configuration management for single page applications driven by micro frontend apps. Built to scale horizontally across multiple production and non-production environments by default.

Background

If you have adopted a micro frontend metaframework like single-spa, you will have noticed a bit of a disconnect between the development, build and deployment stories. While providing more flexibility in development of new features as a SPA evolves with relatively little cost, the build and deployment complexity increases with every new micro app added.

At a small scale, it possible to:

  • Build all the micro app bundles at the same time
  • Take the bundle hashes and update a manifest with them
  • Run a container with everything built and the updated manifest inside

As more micro apps are added and the build time increases:

  • It becomes ineffective to rebuild everything for changes to one micro app
  • It make more sense to build bundles individually and serve them from a bucket or CDN

After the build process becomes independent:

  • Updating the manifest with the newly hashed bundle names becomes trickier
  • Either a new container can be rebuilt every time with the manifest generated from build args
  • Or a new manifest can be mounted into the running container using a Docker volume or Kubernetes ConfigMap
  • But then this requires killing the existing containers and spinning up new ones to pick up the changes

When the deployment process becomes independent:

  • There needs to be a way to keep track of manifest versioning and history
  • A git repo with automated commits and pushes on CI can handle this to a point
  • But you need to either implement a semaphore or be sure to not be deploying two micro apps at the same time
  • And then you also probably need a way to push more than one micro app at once in some cases

Finally, managing all of this across multiple production and non-production environments has the potential to get very messy very quickly. Enter microfest.

Overview

microfest provides a RESTful API for managing releases and manifests for micro frontend applications which scales horizontally across multiple production and non-production environments with minimal configuration and zero templating. Under the hood, microfest uses bbolt as a fast and reliable key/value store that is optimised for read-intensive workloads. As bbolt uses an exclusive write lock on the database, this nicely handles the potential problem of multiple concurrent updates.

microfest exposes the following routes:

/manifest

POST

Add a new, complete manifest for a hostname and set it as the manifest to be fetched when calling GET.

// full-manifest.json
{
  "navigation": "https://storage.googleapis.com/XXX/navigation.HASH.bundle.js",
  "settings": "https://storage.googleapis.com/XXX/settings.HASH.bundle.js",
  "help": "https://storage.googleapis.com/XXX/help.HASH.bundle.js"
}
curl -X POST 'http://localhost:8000/manifest?host=production.host' \
     -H 'Content-Type: application/json' -H 'X-API-KEY: XXX' \
     -d @full-manifest.json
     
# created manifest for host production.host

GET

// curl -X GET 'http://localhost:8000/manifest?host=production.host'

{
  "help": "https://storage.googleapis.com/XXX/help.HASH.bundle.js",
  "navigation": "https://storage.googleapis.com/XXX/navigation.HASH.bundle.js",
  "settings": "https://storage.googleapis.com/XXX/settings.HASH.bundle.js"
}

PUT

Create a new manifest by taking the current manifest for a hostname and patching the diff from the payload.

// partial-manifest.json
{
  "settings": "https://storage.googleapis.com/XXX/settings.FIXED.bundle.js"
}
curl -X PUT 'http://localhost:8000/manifest?host=production.host' \
     -H 'Content-Type: application/json' -H 'X-API-KEY: XXX' \
     -d @partial-manifest.json
     
# created manifest for host production.host
// curl -X GET 'http://localhost:8000/manifest?host=production.host'
{
  "help": "https://storage.googleapis.com/XXX/help.HASH.bundle.js",
  "navigation": "https://storage.googleapis.com/XXX/navigation.HASH.bundle.js",
  "settings": "https://storage.googleapis.com/XXX/settings.FIXED.bundle.js"
}

/configuration

POST

Add a new configuration version for a hostname and set it as the configuration to be fetched when calling GET.

// configuration.json
{
  "value1": "xxxxx",
  "value2": "xxxxx"
}
curl -X POST 'http://localhost:8000/configuration?host=production.host' \
     -H 'Content-Type: application/json' -H 'X-API-KEY: XXX' \
     -d @configuration.json
     
# created configuration for host production.host

GET

Get the configuration for a specific hostname.

curl -X GET 'http://localhost:8000/configuration?host=production.host'
{
  "value1": "xxxxx",
  "value2": "xxxxx"
}

/healthcheck

GET

A healthcheck endpoint for readiness and liveness probes.

curl -X GET 'http://localhost:8000/healthcheck'

# HTTP/1.1 200 OK
# Date: Sat, 13 Jul 2019 12:39:16 GMT
# Content-Length: 0

Javascript Example

Once you have microfest deployed, it can be called to dynamically load the manifest for the correct environment in a single-spa application like this:

fetch(`https://mf.example.com/manifest?host=${window.location.hostname}`, { headers: {"X-API-KEY": apiKey }})
  .then(res => res.json())
  .then((manifest) => {
    window.manifest = manifest;

    navigation();
    settings();
    help();

    singleSpa.start();
  });

Deploying on Kubernetes

In the ./kubernetes a set of kustomize manifests are provided to help you get up and running with microfest as quickly as possible on Kubernetes. Modify the values with the comments # your own X here:

./kubernetes/deployment.yaml
16:                  - key: node-type # your own key here
19:                      - general # your own value here
53:          key: group # your own key here
55:          value: general # your own value here

./kubernetes/ingress.yaml
13:    - host: microfest.example.com # your own host here
21:        - microfest.example.com # your own host here
22:      secretName: tls # your own tls secret name here

./kubernetes/kustomization.yaml
7:namespace: microfest # your own namespace here
16:      - API_KEY=bla # your own API key here

./kubernetes/namespace.yaml
6:  name: microfest # your own namespace here

It is important ensure that Node Affinity and Taint Tolerations for your nodes are set when deploying microfest, to ensure that any subsequent redeployments of microfest will always find the BoltDB data volume on the same node to reattach.

Once you have modified the values, run kustomize build ./kubernetes | kubectl apply -f - to get microfest running on your cluster.

Performance

Report from running vegeta attack -duration=60s on the GET /manifest route:

Requests      [total, rate]            3000, 50.02
Duration      [total, attack, wait]    59.982073627s, 59.980867273s, 1.206354ms
Latencies     [mean, 50, 95, 99, max]  1.556704ms, 1.185781ms, 1.99684ms, 4.028128ms, 234.552687ms
Bytes In      [total, mean]            3558000, 1186.00
Bytes Out     [total, mean]            0, 0.00
Success       [ratio]                  100.00%
Status Codes  [code:count]             200:3000
Error Set:

About

Manifest management for single page applications driven by micro frontend apps

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published