Skip to main content
Version: V2-Next

Deployment Principles

These principles describe how we design the deployment repository. They apply to new components, to Add-ons and to merge request reviews. They are design guidelines, not strict rules. They do not require changes to running deployments.

  • Principles tells you what to do. Each principle has a review question.
  • Rationale tells you why. Read it before you change the architecture of the deployment.

Principles​

1. Uniform components​

  • Follow the structure and the patterns in Component Configuration.
  • Create new components from the template with just new-component.
  • Put a pattern that applies to many components into the component template or into defaults/. Do not copy it into each component.

Review: Does the change follow Component Configuration? If it adds a new pattern, can other components use it the same way?

2. Decoupled components​

  • Change only files in components/<component>/.
  • Connect to other components through interface files in your own folder, for example databases.yaml, secrets.yaml, keycloak-clients.yaml, apisix-routes.yaml and networkpolicies.yaml.
  • Exception: if you change an interface itself, change all components that use it.

Review: Does the change touch files outside the component folder? If yes, is it a change to an interface?

3. A provider does not know its consumers​

  • The consumer declares what it needs in its own folder. For example, a component that needs a database adds a databases.yaml. It does not edit the Postgres component.
  • A provider does not contain names, labels or settings of a specific consumer.
  • An Add-onAdd-onAn optional component that extends the Platform for specific use cases, for example for visualization or low-code data processing. An Add-on is not part of the platform core: it is maintained in a separate repository, included in the deployment as a Git submodule, and accesses the Platform only via its public interfaces. can use core interfaces. The core does not know specific Add-onsAdd-onAn optional component that extends the Platform for specific use cases, for example for visualization or low-code data processing. An Add-on is not part of the platform core: it is maintained in a separate repository, included in the deployment as a Git submodule, and accesses the Platform only via its public interfaces. and does not use their interfaces.

Review: Does a provider component contain the name of a consumer component? If yes, can the consumer declare this in its own folder?

4. Use only published interfaces​

A provider publishes:

  • the values in its default-environment.yaml.gotmpl, for example .Values.kafka.cluster.bootstrapService
  • derived values in its published.yaml.gotmpl, for example .Values.keycloak.app.url (see Published values)
  • the format of its interface files. The provider owns the format. Add a schema, for example components/keycloak/keycloak-clients.schema.json.

Other components use only these. They do not hard-code service names, labels or URLs of other components.

Put only values into default-environment.yaml.gotmpl that an environment may set or other components may read. Do not put internal values or secrets into it.

Review: Does the change read a value of another component that the other component does not publish? Does it hard-code a service name, a label or a URL of another component?

Published values​

Not implemented

The deployment does not support published.yaml.gotmpl yet.

A provider publishes values that it derives from other values, for example a URL, in published.yaml.gotmpl. These values use the final values of the environment.

components/keycloak/published.yaml.gotmpl
{{- $host := printf "%s.%s" (default "" .Values.keycloak.app.subdomain) .Values.global.domain | trimPrefix "." }}
keycloak:
app:
url: {{ .Values.keycloak.app.url | default (printf "https://%s%s" $host .Values.keycloak.app.pathPrefix) }}

Consumers read the value:

components/<consumer>/values/<part>/base-values.yaml.gotmpl
authServerUrl: {{ .Values.keycloak.app.url }}

Use default, so that an environment can still set the value directly, for example for an external KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs..

5. Reference components by name​

Review: Does the change contain a literal namespace, or a host name with a namespace in it?

6. One source of truth for each fact​

  • If a value is the same fact in all places, define it one time and derive it in the other places. Examples are global.domain, global.instanceSlug and securityDefaults.
  • If values are only equal by chance, keep a copy in each component. An example is resource limits.
  • If more than one consumer needs a derived value, the provider publishes it in published.yaml.gotmpl.

Review: Must a value in this change stay equal to a value in a different file? If yes, derive one from the other. If the value changes for one component, must it change for all others? If not, keep the copies.

7. A component can be removed​

  • When you remove a component from the components list, all other components still render and deploy.
  • Exception: a component that another component declares in needs in its civitas-component.yaml.

Review: If you remove this component from the components list, does the deployment of the other components still work?

8. Cluster operators are installed separately​

  • Cluster operators, for example CloudNativePGCloudNativePGA Kubernetes operator for running and managing PostgreSQL clusters. In CIVITAS/CORE it is used to provision and operate Postgres databases. and Strimzi, are not part of an instance.
  • helmfile-operators.yaml installs them one time per cluster. helmfile-instance.yaml.gotmpl installs an instance. See Installation.

Review: Does the change put cluster-wide resources into an instance? Does an instance depend on a specific version or configuration of an operator?

Rationale​

Uniform components​

When all components have the same structure, you find things fast and you understand a change fast. There is only one way to do a thing, so there is no discussion about which way is correct. Uniform components also let us write abstractions one time for all components: the generated helmfile.yaml.gotmpl, the defaults in defaults/environment/ and the collection of interface files.

Decoupled components and dependency direction​

Principles 2, 3, 4 and 7 make components decoupled. When a component and its connections are in one folder, you can add, change or remove the component without changes to other components. This is necessary for Add-ons. An Add-onAdd-onAn optional component that extends the Platform for specific use cases, for example for visualization or low-code data processing. An Add-on is not part of the platform core: it is maintained in a separate repository, included in the deployment as a Git submodule, and accesses the Platform only via its public interfaces. is a component that is not in the core. If the core had to know an Add-onAdd-onAn optional component that extends the Platform for specific use cases, for example for visualization or low-code data processing. An Add-on is not part of the platform core: it is maintained in a separate repository, included in the deployment as a Git submodule, and accesses the Platform only via its public interfaces., each new Add-onAdd-onAn optional component that extends the Platform for specific use cases, for example for visualization or low-code data processing. An Add-on is not part of the platform core: it is maintained in a separate repository, included in the deployment as a Git submodule, and accesses the Platform only via its public interfaces. would need a change to the core.

The dependency direction (consumer knows provider) makes sure of this. A new consumer only adds files in its own folder. The provider stays the same.

An interface change is the only change across component folders. This is acceptable, because we release all components together. Interfaces between components need no backward compatibility.

Published interfaces and one source of truth​

If a consumer reads internal values or hard-codes names of a provider, the provider cannot change them without breaking the consumer. Published values make the dependency visible and give the provider a clear contract.

Copied values and copied logic must stay equal by hand. When they do not, the deployment breaks in a different component folder than the one you changed.

But a shared value also couples the components that use it. If values are only equal by chance, a shared value forces unrelated components to change together. A copy costs little. Wrong coupling costs more.

References by component name​

The deployment supports single-namespace mode and multi-namespace mode, and an environment can move a component to a different namespace. Only a reference by component name works in all of these cases.

Separate cluster operators​

Many organizations run many applications on one shared cluster. Cluster operators are cluster-wide. When they are separate from the instances, the DevOps team of the cluster can manage them one time for all instances on the cluster.