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
gitrepo 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.hostGET
// 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.hostGET
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: 0Javascript 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:
