Framework documentation

# Framework Documentation

[Contribute on GitHub](https://github.com/kbst/terraform-kubestack)[Join #kubestack on Slack](https://slack.k8s.io/)

Topics

-   [Introduction](/documentation/)
-   [Platform Architecture](/documentation/platform-architecture/)
-   [GitOps Process](/documentation/gitops-process/)
-   [Inheritance Model](/documentation/inheritance-model/)
-   [Repository layout](/documentation/repository-layout/)
-   [Module Types](/documentation/module-types/)
-   [Clusters](/documentation/clusters/)
-   [Node-Pools](/documentation/node-pools/)
-   [Platform Features](/documentation/platform-features/)
-   [Extending Kubestack](/documentation/extending-kubestack/)

## Inheritance Model

**TL;DR:**

-   Desired configuration is set for the mission critical, external _apps_ or _apps-prod_ environment.
-   All other environments inherit configuration from _apps_ or _apps-prod_.
-   The inherited configuration can be overwritten per environment.

### Why inheritance

Inheritance from _apps_ to _ops_, or from _apps-prod_ to _apps_ and _ops_ is a cornerstone of Kubestack's reliable GitOps automation. The _ops_ environment serves the purpose of validating changes before they are promoted to _apps_ or _apps-prod_. Configuration drift risks rendering this protection ineffective.

Inheritance makes differences explicit. By default, everything is inherited. But if necessary, individual attributes of the inherited configuration can be overwritten. Explicit differences do not prevent configuration drift. But they make it easier to spot.

Kubestack implements inheritance to reduce the risk of configuration drift and increase automation reliability. The fewer differences there are, between the environment a change was validated against, and the environment it is promoted to, the less likely the promotion is to fail.

Because every module implements the same inheritance model, there is exactly one way to express a difference between environments. This makes inheritance a stable pattern for AI coding agents. Instead of inventing a new way to diverge environments in every session, an agent that has learned the Kubestack skill patches the inherited configuration.

### Implementation

Kubestack implements the inheritance model for all its [module types](/documentation/module-types/).

All Kubestack modules accept two input variables:

1.  `configuration`
2.  `configuration_base_key`

Configuration expects a `map` where the keys are the names of the OpenTofu workspaces. And the values are the per workspace configurations. The `configuration_base_key` defaults to `apps` and controls which environment all others inherit their configuration from.

#### Default environments

Given Kubestack's default environment names, _ops_ and _apps_, this is the basic structure of the configuration map:

```
configuration = {
  apps = {}

  ops = {}
}
```

#### Custom environments

For custom environments, consider this example with one internal _ops_ and two external environments _apps_ and _apps-prod_ for the application production and staging environments.

```
configuration_base_key = "apps-prod"
configuration = {
  apps-prod = {}

  apps-stage = {}

  ops = {}
}
```

### Inheritance rules

The inheritance is implemented in the [common/configuration](https://github.com/kbst/terraform-kubestack/tree/master/common/configuration) module that all other Kubestack modules use internally. The module loops through all keys in the base environment (_apps_ by default) and the current environment (determined by the value of the `terraform.workspace` variable, e.g. _ops_). If the key exists in the current environment, the value from the current environment is used. If it does not, the value from the base environment is used. This results in the following inheritance behaviour (assuming the default environment names):

1.  _ops_ inherits everything from _apps_
2.  any attribute that is set in _ops_ overwrites the inherited value from _apps_
3.  _ops_ can add attributes that are not set in _apps_

To explain this, take a look at the following examples:

1.  [A hypothetical example showing the inheritance rules >>](#hypothetical-example)
2.  [A practical example for scaling cluster and platform feature modules >>](#practical-examples)

### Hypothetical example

Consider the following fictitious configuration:

```
module "configuration" {
  source = "github.com/kbst/terraform-kubestack//common/configuration"

  configuration_base_key = "apps"
  configuration = {
    apps = {
      apps_key1 = "from_apps"
      apps_key2 = "from_apps"
    }

    ops = {
      ops_key   = "from_ops"
      apps_key1 = "from_ops"
    }
  }
}

output "apps_merged" {
  value = module.configuration.merged["apps"]
}

output "ops_merged" {
  value = module.configuration.merged["ops"]
}
```

This will result in the following outputs:

```
Outputs:

apps_merged = {
  "apps_key1" = "from_apps"
  "apps_key2" = "from_apps"
}

# ops overwrites apps_key1
# ops inherits apps_key2 unchanged
# ops adds ops_key
ops_merged = {
  "apps_key1" = "from_ops"
  "apps_key2" = "from_apps"
  "ops_key" = "from_ops"
}
```

It is **not** possible to overwrite an inherited value with `null` to remove the attribute from the configuration.

### Practical examples

The practical examples below show how to use inheritance to scale cluster modules and platform feature modules per environment.

#### Cluster modules

The scaling configuration for a cluster is specified in the _apps_ hash map. Half the compute resources would be wasted, if the _ops_ environment used the exact same scaling settings.

Avoiding configuration differences that break the automation is important. And money spent on making the automation more reliable is an investment into sustainability for any team.

But a configuration that works for a three node cluster isn't likely to fail for a 30 node cluster. And within reason, neither is it, if _ops_ uses burstable or shared-core nodes with reduced vCPUs and memory.

Below examples for AKS, EKS and GKE each auto-scale _apps_ between 3 and 18 nodes (4 vCPUs, 16 GB memory) and _ops_ between 3 and 6 nodes (2 vCPU burstable, 4 GB memory).

**Amazon**

```
configuration = {
  apps = {
    # abbreviated example configuration
    # ...

    default_node_pool = {
      instance_types = ["m5a.xlarge"]
      min_size       = 3
      max_size       = 18
    }
  }

  ops = {
    # smaller, cheaper instance type
    default_node_pool = {
      instance_types = ["t3a.medium"]

      # lower autoscaling min/max
      min_size = 3
      max_size = 6
    }
  }
}
```

#### Platform features

Similarly to how it makes sense to scale the cluster nodes differently for _apps_ and _ops_. It also makes sense to scale platform features differently. Since cluster modules and platform feature modules share the configuration inheritance, this works very similar. Just the configuration attributes are now Kubernetes/Kustomize specific and not AKS, EKS or GKE specific any more.

Below example scales the Nginx Ingress controller up to three replicas on _apps_ and down to one replica on _ops_. The module defaults to two replicas.

```
configuration = {
  apps = {
    replicas = [{
      name = "ingress-nginx-controller"
      count = 3
    }]
  }

  ops = {
    replicas = [{
      name = "ingress-nginx-controller"
      count = 1
    }]
  }
}
```

[PreviousGitOps Process](/documentation/gitops-process/ "GitOps Process")[NextRepository layout](/documentation/repository-layout/ "Repository layout")

On this page

-   [Inheritance Model](#inheritance-model)
-   [Why inheritance](#why-inheritance)
-   [Implementation](#implementation)
-   [Inheritance rules](#inheritance-rules)
-   [Hypothetical example](#hypothetical-example)
-   [Practical examples](#practical-examples)

For your agent

-   [SKILL.md](/SKILL.md)
-   [This page as Markdown](/documentation/inheritance-model/index.md)
