Variants

Variants are similar related configurations, typically derived from a common source. For example, configurations in development and production environments generally are similar but have some differences, which makes them variants. For simplicity, lets say we have one replica of our backend in development and three replicas in production.

With Infrastructure as Code, variants are generated from the same source code or templates. I previously wrote that variant generation is the primary capability of Infrastructure as Code. It’s really the raison d’etre. Creating a template or configuration generator wouldn’t make sense if you only needed one configuration.

spec:
replicas: {{ .Values.backend.replicas }}

Variants are a ubiquitous phenomenon in configuration. When configuration is WET (Write Every Time), as it is in ConfigHub, and not parameterized, how can multiple variants be maintained? It may be helpful to discuss another tool you may be familiar with first — Kustomize.

Kustomize

Variant generation is also the primary problem I intended kustomize to address in Kubernetes. Kustomize transforms configuration data out of place (i.e., without modifying the original). It starts with a base configuration and generates a modified variant of it. All of the desired transformations must be run on the base in sequence in order to render the final configuration for a variant.

Kustomize supports two main approaches to transforming configuration data, (a) patches and (b) plugins or functions. Patches are data and plugins/functions are code.

A patch could be expressed as an overlay, a strategic merge patch, or as an RFC 6902 JSON patch. Here’s an example of the former:

# kustomization.yaml
resources:
- deployment.yaml
patchesStrategicMerge:
- path: replicas-patch.yaml

# replicas-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 3

RFC 6902 patches look like this:

# kustomization.yaml
resources:
- deployment.yaml
patchesJson6902:
- target:
group: apps
version: v1
kind: Deployment
name: backend
path: replicas-patch.yaml

# replicas-patch.yaml
- op: replace
path: /spec/replicas
value: 3

Kustomize also has a built-in plugin, the Replica Count Transformer, that can change the replicas field specifically.

# kustomization.yaml
resources:
- deployment.yaml
replicas:
- name: backend
count: 3

Plugins/functions are easier to use. They are best for common cases, like changing the replicas field, where the effort to create them will be recouped by the number of uses. Patches are useful for ad hoc changes, but they can get complicated when many resources and fields need to be patched and when there are multiple layers of patches.

Variants without Patches in ConfigHub

ConfigHub supports two analogous mechanisms for creating and maintaining variants, cloning and functions, but we simplified the experience.

With ConfigHub, patches don’t need to be written and maintained. Instead, variants can be cloned. A clone is a downstream copy that keeps track of the original source of the configuration, called the upstream. The downstream copy can then can just be modified directly, in this case by changing the replicas count to 3, instead of changing that value in an input value file or in a patch.

For example, to clone all configurations from development into production, you could perform a create operation in the production environment using the development configurations as the sources:

cub unit create --space dev --where-space "Labels.Environment = 'prod'"

After changes have been made to the original, they can be propagated to the downstream clones via an upgrade operation. You can think of upgrade as a diff-and-patch operation, though it’s a bit more than that. Modifications to the downstream clones are preserved as overrides, similar to Kustomize overlays, but handled automatically.

cub unit update --space "*" --patch --upgrade --filter needs-upgrade

Cloning is essentially an inheritance mechanism. Another way you could think about it is that the upstream configuration provides default values for downstream configurations.

ConfigHub Functions

Like Kustomize plugins or functions, ConfigHub functions can perform common-case transformations. But because the configuration data is the source of record, functions can be invoked imperatively and their results will be persisted in the configuration data. The configuration is effectively always rendered and does not need to be re-rendered repeatedly.

cub function invoke --unit backend --space prod set-replicas 3

Functions can also be used to update configuration values across multiple variants the same way, by selecting which configurations the function(s) should be invoked on.

cub function invoke --where "Space.Labels.Environment = 'prod' AND Slug = 'backend'" --space "*" \
set-replicas 3

The value in the configuration is then just what you’d expect.

apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
...
spec:
replicas: 3
...

You can retrieve the value just as easily, regardless how it was set — whether by a function, yq, AI agent, GUI, editor, refresh from the live state, or whatever:

cub function invoke --unit backend --space prod get-replicas --output-values-only
3

If you want to ensure that workloads are replicated in production, that’s easy enough to validate directly on the WET configuration.

cub trigger create --space prod replicated Mutation Kubernetes/YAML vet-celexpr "(r.kind != 'Deployment' && r.kind != 'StatefulSet') || (has(r.spec.replicas) && r.spec.replicas > 1)"

WYSIWYG

I sometimes call this WET, fully rendered configuration data WYSIWYG — What You See Is What You Get. No templates, no input variables, no patches, no pipelines of transformations, no layers, no need to re-render configuration from its sources in order to use it.

When there is just a single configuration, WET configuration has always been simpler than DRY formats like templates. The challenge has been how to manage multiple configuration variants in a WET format. That is one of the challenges we solved with ConfigHub.

This is just scratching the surface of what ConfigHub can do, but it hopefully gives you a sense of how much simpler the configuration experience could be.

Whether you’ve tried several existing Kubernetes configuration tools and are looking for something simpler or are keeping a few configuration variants in sync by hand, you may be interested in checking out ConfigHub. If you do try it, we’d like to hear what you think about it. The signup link on our homepage is now live.

Feel free to email us at hello@confighub.com, or send me a message on LinkedIn, X/Twitter, or Bluesky.

If you found this interesting, you may be interested in other posts in my Infrastructure as Code and Declarative Configuration series or in my Kubernetes series.